Métricas! Nós usamos!

Pessoal, tudo bem? Quantos de vocês já presenciaram o seguinte diálogo nas empresas por onde passaram ou trabalham:

- Fulano, tem algum problema no <app>? O <outro app> começou a tomar timeout e não achamos onde está o problema. Está tudo certo ai do lado de vocês?
- Nossa cara, que estranho… vou dar uma olhada.

E essa olhada leva algumas horas, até que o problema se resolve sozinho e todo mundo se dá por satisfeito. Até que acontece de novo… e de novo… e de novo…

Santo monitoramento, Batman!

Não é incomum encontrar aplicações rodando por aí sem qualquer tipo de monitoramento além de um simples health check. Quando as anomalias surgem, os times correm para identificar o problema lendo logs manualmente.

Quero compartilhar algumas lições que aprendi nos últimos anos e algumas medidas que vejo sendo adotadas aqui no luizalabs. Também quero dar um exemplo prático de como podemos fazer em Go para gerar métricas estruturadas.

1- Antes de tudo, pense no que e como monitorar

A solução sempre vem depois da necessidade, nunca antes. Portanto, antes de sair desenvolvendo, pare para pensar nas informações que farão sentido serem monitoradas no seu app.

Pense também em como você deseja visualizar essas métricas (pizza, barra, métrica pura etc.), pois isso possui um impacto direto na maneira como seu app irá gerá-las.

2- Monitore indicadores de negócio além dos de infraestrutura

Muitas vezes focamos demais em monitorar métricas de infraestrutura como códigos HTTP retornados pelas APIs, latência, taxa de erros de aplicação etc. Sempre que uma anomalia aparece por ali, é fácil de identificar.

Mas o problema é que nem sempre uma anomalia pode vir acompanhada de erros, e é aí que mora o perigo. Esses cenários são muito difíceis de identificar quando não possuímos nenhuma monitoria de KPIs, daí a sua importância.

Pense que com ela podemos nos certificar, por exemplo, que o número de reservas de estoque caiu muito após uma atualização do site ou de campanhas de frete. Não por erros, mas por algum outro motivo (usabilidade prejudicada, frete muito alto etc.).

Sempre identifique os KPIs da sua aplicação e os acompanhe com tanta atenção quanto as métricas de infraestrutura, ou até mais.

3- Produza logs estruturados e enxutos

Logs estruturados são simples de serem indexados e utilizados como fonte de dados na geração de dashboards. Pessoalmente, me sinto mais a vontade para interpretar log em JSON do que em texto.

Abaixo, um exemplo como eventos em JSON deixam as informações separadas e estruturadas:

Mais fácil né?

Outro ponto importante: adote um padrão na geração dos logs. Pense neles como uma entidade da sua aplicação, com seu próprio schema. Isso evita que o mesmo tipo de dado seja logado com chaves diferentes, fazendo com que seus dashboards exibam informação incompleta.

4- Crie uma documentação para os seus logs

Essa é uma prática que adotei desde que entrei no luizalabs. Ter documentação básica sobre log pode ajudar muito caso alguém não muito familiarizado com o seu app precise dar uma olhada em alguma situação mais crítica.

Não precisa ser nada muito rebuscado: quais os principais campos e seus significados, campos que podem ser usados como filtro para facilitar troubleshooting, queries salvas no Kibana, etc.

5- Monitore recursos externos

Recursos externos, assim como nossas aplicações, também falham. Portanto, sempre monitore latência e erros de recursos como banco de dados, brokers de mensageria e principalmente APIs de outros times ou parceiros.

É sempre bom termos como identificar possíveis problemas nessas APIs porque não temos como saber o quão bem monitoradas elas são. Lembram do diálogo ali no começo do artigo? Pois é…

6- ❤ Dashboards

Dashboards sempre facilitam a leitura e interpretação das informações. Os eventos de log são sim muito úteis na hora de averiguar o que aconteceu em detalhes, mas para o dia a dia, dashboards são muito úteis.

Vou colocar aqui alguns exemplos de dashboards que utilizo muito nas minhas aplicações.

Muitas vezes eles tem variações, como no gráfico de linha que utilizo para latência. Se meu app tem outros tipos de comunicação que necessitam de monitoria, eu separo em visualizações diferentes (HTTP, banco de dados, mensageria etc).

Gráfico de linha, com latência média quebrada por rota da API
Gráfico de barra, com status codes quebrados por rota da API
Gráfico de pizza, com % de status codes de cada API externa de que meu app depende

Gerando métricas em Go com Logrus

Mas calma, antes vai um pouco de teoria ainda hehe.

Uma lib indispensável no arsenal de vocês deveria ser a Logrus. Ela é muito simples de ser adotada em um projeto existente porque é 100% compatível com a lib default, mas possui mecanismos muito úteis como os hooks.

O princípio do Logrus é tratar seus eventos de log como um Map. Ao longo das nossas funções, vamos adicionando chaves com os valores das métricas, e só no final geramos o evento com todas as chaves preenchidas.

Toda essa mecânica gira em torno de 2 tipos básicos da lib: Logger e Entry. O Entry é derivado do Logger e ambos possuem praticamente as mesmas funções.

A inserção de chaves é feita à partir da função WithFields. O retorno dessa função é sempre um ponteiro de uma nova Entry, então leve isso em consideração quando for inserir chaves mais de uma vez no corpo de uma função.

E como utilizamos o Logrus por aqui? Separamos tudo em uma package de utilities. Nessa package, temos um Logger privado que é utilizado como um "pai de todas as entries". Esse logger será instanciado e configurado no início da aplicação, onde registramos os hooks e configuramos o formato de log.

Nessa mesma package, criamos uma função, essa sim pública, que retorna um Entry configurado com os campos padrão(ambiente, versão e hostname). À partir daí, é só utilizar as entries para gerar os logs.

Agora, basta gerar os logs de maneira estruturada com as chaves. Eu geralmente coloco uma chave para identificar qual parte do sistema gerou a métrica (um handler da API, um service etc.).

E por último, o evento de log gerado ao bater nessa rota da API:

Parece mais complicado do que realmente é viu!? Então codem um pouco, utilizem o logrus no seu código e me digam como foi nos comentários ok!? A prática leva a perfeição.

PS: Se vocês derem uma lida na página do Logrus, verão a seção sobre Hooks. Com eles, podemos propagar um evento de log simultaneamente para outras fontes além do stdout. O próprio Logrus já possui alguns hooks implementados, e um deles envia logs para o ElasticSearch! Vale a pena dar uma conferida.

Até a próxima!