Algumas semanas atrás, um colega me pediu para passar uma semana no suporte, pois ele precisava de alguém que o cobrisse enquanto tirava um merecido descanso, e ele não conseguia encontrar ninguém. Como eu tinha acabado de concluir um projeto particularmente complexo e estava me sentindo um pouco cansado, disse “sim”: afinal de contas, a mudança me faria bem.
Parte do trabalho consistia em monitorar um conjunto de processos de apoio bastante importantes, para ver o quão bem eles estavam sendo realizados e se eles estavam tendo algum erro.
Nós, desenvolvedores, gastamos muito tempo e energia colocando log em nosso aplicativo, a fim de provar que ele está funcionando bem e para descobrir o que deu errado quando ocorre uma exceção. Esses arquivos de log são muitas vezes utilizados para nos dizer o quão bem ou mal nosso aplicativo está executando diariamente.
Eu estou ignorando outras técnicas, como a adição de probes ao seu aplicativo, por qualquer método que você escolher, como HTTP ou JMX. Eles fornecem informações imediatas sobre o seu aplicativo, em vez do monitoramento de segundo nível, em discussão aqui.
Há pelo menos três maneiras de monitorar arquivos de log:
- nunca
- reativamente
- proativamente
“Nunca” significa o que diz. Eu estou apostando que existem aqueles aplicativos que são lançados, nunca parecem apresentar falha e nunca são verificados.
A estratégia “reativa” é muito comum; Sra. Smith de Doncaster telefona para a empresa e reclama que o site deixou de funcionar quando ela estava tentando comprar um novo par de sapatos. Ela foi cobrada duas vezes e nunca recebeu qualquer calçado. Nessa empresa tradicional, ma qual os papéis de DEV e OPS são totalmente segregados, o desenvolvedor que investiga o problema solicita os logs de data e hora do incidente ao pessoal de OPS. O desenvolvedor, é claro, recebe de volta uma seção de log sem nenhuma relação com o incidente e tem que voltar a solicitar a seção correta – várias vezes. Depois disso, algumas semanas se passaram, e a Sra. Smith está irada. O log finalmente chega e o problema está resolvido.
Nesse cenário “reativo”, estou supondo que se trata de uma dessas empresas em que não se confia nos desenvolvedores, EM NENHUMA CIRCUNSTÂNCIA ou se permite que eles toquem nos servidores de produção. É muito comum, e a maioria de nós já passou por isso em algum momento. Os desenvolvedores devem receber a confiança de acessar os sistemas de produção; no entanto, como um desenvolvedor, você deve se lembrar de que há duas regras de ouro quando se trata de sistemas em produção:
- Não quebre nada.
- Se você quebrar alguma coisa, certifique-se de que tem alguém para culpar.
“Proativamente” significa que os arquivos de log são verificados regularmente: diariamente, de hora em hora ou o que seja. Mesmo que seu aplicativo contenha uma infinidade de JMX, HTTP ou outros probes, não há garantia de que eles vão detectar um problema. Probes só podem investigar o que você diga a eles para sondar, qualquer outro problema está além deles.
Voltando ao meu feitiço no suporte… por alguma razão, provavelmente histórica, a maior parte desse monitoramento consistia em verificar manualmente os arquivos de log por erros usando um conjunto documentado de comandos ‘grep’ através de um pouco de cortar e colar. Se você adicionar o tempo gasto fazendo-se trabalho repetitivo de copiar e colar, então eu reconheço que ele estava consumindo cerca de ½ dia de um homem por semana.
E isso me fez pensar… eu realmente não gosto de fazer esse tipo de tarefa. Eu não sou muito bom em tarefas manuais, propensas a erros, repetitivas e muito chatas. No desperdício de ½ dia por semana, obviamente compensaria automatizar essa tarefa, desde que o tempo não seja gasto discutindo a solução perfeita. Então, que opções existem?
Se você olhar para o final das opções, há o Splunk, que pode monitorar as mensagens de uma multiplicidade de fontes, tais como o daemon syslog.
Isso significa que uma maneira simples de enviar erros para Splunk é simplesmente configurar o syslog appender Log4j – mas isso está além do escopo deste artigo.
No final das contas, você poderia muito rapidamente escrever um script shell para fazer alguns comandos ‘grep’, escrevendo os resultados em um arquivo e enviá-lo para sua caixa de correio Unix.
Então, eu pensei num meio-termo. Quão difícil poderia ser escrever um aplicativo Spring que contivesse o maior número de classes genéricas e reutilizáveis possível e que varresse verificando periodicamente um monte de logs de erros, enviando os resultados por e-mail? Para esse tipo de coisa, e-mails são bons, porque você geralmente tem o hábito de lê-los.
Primeiros passos
Em qualquer projeto como este, há os primeiros passos intangíveis que fazem com que a coisa toda comece. Eles formam a resposta para a questão: sobre quais são as classes que você precisa escrever para que essa “coisa toda” seja lançada? Existem muitos métodos para determinar quais classes você precisa escrever, como simplesmente pegá-las fora do ar (coragem!), usando uma técnica de projeto formal, como UML, Prototipagem Rápida ou Teste Driven Design. Em todo caso, tudo o que você faz é chegar a alguns requisitos a partir dos quais os nomes de algumas classes aparecem. Por exemplo, neste caso, eu preciso:
- Procurar um determinado diretório e seus sub-diretórios (possivelmente) à procura de arquivos de um tipo particular.
- Se um arquivo for encontrado, em seguida verificar a data: é preciso procurar por erros?
- Se o arquivo é novo o suficiente para ser checado, então valide-o, procurando por exceções.
- Se ele contém exceções, são elas as que estamos procurando ou foram elas excluídas?
- Se ele contém o tipo de exceção que estamos procurando, na sequência, adicione os detalhes a um relatório.
- Quando todos os arquivos forem verificados, formate o relatório pronto para publicação.
- Publicar o relatório usando e-mail ou alguma outra técnica.
- A coisa toda será executada em um determinado momento todos os dias.
Isso levanta vários nomes de classes. Eu preciso de um FileLocator, FileValidator, RegExValidator, FileAgeValidator e um Report.
Tendo em conta que a palavra “Validador” surge várias vezes na lista acima, isso indica que posso usar uma interface, presumivelmente chamada Validador e criar várias implementações para executar as tarefas de validação acima. Essas implementações poderiam então ser combinadas para criar um aplicativo coerente.
Note que esta é apenas uma lista preliminar de ideias. Se você olhar para o código, não vai encontrar uma classe chamada Report, ela foi rebatizada de Results e reformulada para criar uma interface Formatter, as classes TextFormatter e HtmlFormatter, juntamente com a interface Publisher e a classe EmailPublisher.
No que diz respeito a executar isso periodicamente, há algumas escolhas. Em primeiro lugar, você pode escrever o código Java em conjunto com um script simples para chamá-lo e oferecê-lo ao crontab da sua máquina Unix. Isso seria bom, mas significa que o aplicativo não seria executado no Windows e seria limitado à execução como um aplicativo independente. Isso pode ser um bom negócio, por isso, a alternativa seria utilizar o Spring e o agendador Quartz, tornando a tarefa de configurar um agendamento cron bastante simples.
Quanto ao envio do relatório por correio eletrônico, novamente o Spring fornece um modelo de e-mail muito bom que simplifica o uso das classes de e-mail do Java .
Ok, então este é o ponto de partida: um conjunto de ideias vagas para classes que podem ser ligadas entre si de alguma forma fracamente acopladas para criar nosso aplicativo. Se você tomou uma rota formal, então pode querer gastar tempo documentando tudo isso, talvez até mesmo produzir um diagrama de classes, que é então adicionado a um documento do Word, e revisto várias vezes até que tudo fica gravado em pedra; no entanto, não tem que se preocupar com tudo o que está aqui…
Configurando o aplicativo
Como acontece com qualquer aplicativo, há a necessidade de descobrir como vamos definir o que é geralmente toda uma série de valores de propriedade e como eles serão utilizados pelo aplicativo. Esse aplicativo é impulsionado pelo arquivo app.properties, localizado, como de costume, no diretório src/main/resources.
# The path to the log file directory to scan for errors scan.in=/Library/Tomcat/logs # A regex defining what to look for - or what not to include scan.for=^.*Exception.* exclude=^.*IllegalStateException.* # The number of following lines to add to the report following.lines=10 # Where to email the report email.to=info@captaindebug.com # The max age of a file in days max.days=1000
A primeira dessas propriedades que nos interessa é a propriedade scan.in. Ela é a localização do diretório de log do nosso servidor web que e é usado como ponto de partida para a classe FileLocator, descrita na próxima seção.
Procurando por arquivos
Ao escrever um FileLocator, fui um pouco além dos requisitos e intencionalmente fiz um pouco de “verniz” .
Isso (o verniz) é uma péssima ideia. Você deve escrever código apenas o suficiente para atender aos requisitos funcionais – isto é, para fazer o trabalho. Neste caso, sendo um blog… o meu blog, eu posso justificá-lo dizendo que ele atende aos requisitos não-funcionais de documentar uma técnica que eu usei muitas vezes antes.
O código a seguir não apenas procura por arquivos de log em um diretório nomeado log file, ele procura todos os subdiretórios também.
@Service public class FileLocator { private static final Logger logger = LoggerFactory.getLogger(FileLocator.class); @Value("${scan.in}") private String scanIn; @Autowired @Qualifier("fileValidator") private Validator validator; /** Search for the files requested */ public void findFile() { logger.info("Searching in... {}", scanIn); File file = createFile(scanIn); search(file); } @VisibleForTesting File createFile(String name) { return new File(name); } private void search(File file) { if (file.isDirectory()) { logger.debug("Searching directory: {}", file.getName()); File[] files = file.listFiles(); searchFiles(files); } else { logger.debug("Validating file: {}", file.getName()); validator.validate(file); } } private void searchFiles(File[] files) { for (File file : files) { search(file); } } }
O código acima usa a velha técnica de recursividade para procurar arquivos de log. O principal ponto de entrada é o findFile(). Ele cria um objeto File do @Value Spring anotado pela variável de instância scanIn e passa para o método search(). Em seguida, verifica se esse objeto é um diretório e, se for, ele pega todos os arquivos no diretório e faz um loop por todos eles chamando search() em cada objeto File na lista. Se a verificação de diretório revela que o objeto File é um arquivo, em seguida, o arquivo de validação é chamado.
Até aqui tudo bem, com a primeira classe do aplicativo, podemos agora procurar por logs em um diretório; no entanto, se você quiser saber o que acontece quando um arquivo for encontrado, então você vai ter que esperar até meu próximo artigo ser publicado.
Um pensamento final: você precisa investigar todos os erros em seu sistema? Há um velho ditado filosófico: se uma árvore cai em uma floresta e não há ninguém lá para ouvi-la, ela ainda assim faz barulho? Da mesma forma, se o aplicativo gera uma exceção em que o usuário não é afetado, ainda trata-se de um erro? Você precisa gastar tempo investigando? Deixe-me saber o que você pensa?
O código para este artigo está disponível no Github em: https://github.com/roghughe/captaindebug/tree/master/error-track.
***
Artigo traduzido pela Redação iMasters, com autorização do autor. Publicado originalmente em http://www.captaindebug.com/2014/03/tracking-application-exceptions-with.html#.UyIlSvldXh4