Cell CMS — Criando logs robustos e monitorando uma API
No último post falamos sobre duas bibliotecas para facilitar nossa vida o AutoMapper e o FluentValidation. No post de hoje vamos olhar mais algumas bibliotecas que facilitam a nossa vida, porém as bibliotecas de hoje irão nos auxiliar quando as coisas não saírem conforme o esperado!
Então hoje faremos três novos branches: feature/serilog, feature/app-insights e feature/healthcheck.
Por que devemos nos importar com Logs?
Debug é um crime onde o programador é, ao mesmo tempo, o Detetive, a Vítima e o Criminoso.
A clássica piadinha tem um belo fundo de verdade. Muitas vezes durante em ambientes de desenvolvimento (e até mesmo em produção, quem nunca?) precisamos parar features novas e ir caçar bugs.
O pior ainda é quando temos um bug que só acontece em um cenário complexo ou quando a descrição do bug se resume a alguém falar “Explodiu!” e sequer apontar o que estava fazendo na hora que “explodiu”. Nesses casos sua esperança é ter uma base de código organizada o suficiente para que seja possível, pelo menos, identificar os pontos de entrada que foram executados pelo usuário.
Agora imagine que sua base não é organizada. Ou é gigantesca. Ou você tem dezenas de microservices rodando ao mesmo tempo. Nesses casos você precisa sair caçando no código onde infernos a Exception aconteceu, sem muita esperança. É breakpoint pra tudo quanto é lado!
Não seria muito bom se tivessemos pelo menos um Rastro ou um Stacktrace decente e armazenado em algum lugar que não precisassemos fazer SSH, conectar em VPNs , etc?
Então, isso tudo existe já! E existem várias ferramentas para Produzir, Armazenar e Processar/Visualizar logs! Algumas das mais famosas para visualizar e extrair métricas são:
- Kibana
- Grafana
- Seq
- Prometheus
- Azure Monitor
Para a parte de armazenamento temos:
- Azure Log Analytics
- Logstash
- Stackify
- Splunk
E boa parte destas ferramentas permitem que utilizemos um tipo de log mais “avançado”: o Log estruturado.
Um log estruturado é simplesmente um log que contem uma estrutura que facilita seu processamento. Esta estrutura pode ser um JSON ou XML por exemplo.
Com uma estrutura bem definida você pode utilizar todas estas ferramentas para buscar informações relevantes no meio de seus Logs. Por exemplo, imagine que em um Controller, ao capturar um erro, você faça o log de alguns dados da Request e os chame de @Request. Ao utilizar uma destas ferramentas você poderia filtrar , facilmente (nada de precisar de trocentas REGEX!), por eventos contendo apenas objetos do tipo “Request”.
Tudo isso é bem legal, mas e como podemos gerar logs neste formato? Faço um ConvertToJson toda vez que escrever no ILogger?
Bem… você poderia fazer isso! Mas pra que reinventar a roda? Existem várias bibliotecas no universo .NET que já fazem isso para nós e, uma delas, é o Serilog!
Gerandos logs estruturados em um sistema .NET — Serilog
Utilizando a biblioteca Serilogpodemos manter nossa utilização do ILogger apenas alterando as dependências e configurações da nossa API.
Primeiro vamos instalar o Serilog. Adicione o NuGet Serilog.AspNetCore e abra o Program.cs para adicionar as seguintes configurações:
https://medium.com/media/2065cedc7854461ecc2d0e2e22905116/href
Execute a API e já ficará evidente a diferença com o log padrão:
Para finalizarmos a configuração vamos ao Startup.cs e adicionemos mais uma linha ao Configure:
https://medium.com/media/48630fb81d1ac13d76f661fa32f004a4/href
Agora sempre que utilizarmos os parâmetros (params object[]) ao escrever um evento seus dados serão armazenados junto da mensagem em nosso log! Por exemplo a seguinte utilização salvará os dados de uma request de POST /feed :
https://medium.com/media/dff87d30a34e56b22f1e960b6ab6dd56/href
Note a utilização do operador @Command ao invés de apenas Command . A presença do operador @ sinaliza que o Serilog deve serializar o objeto ao invés de utilizar o ToString para salva-lo!
Outro ponto legal sobre o Serilog são as diversas possibilidades de “Sinks”. Um sink é um “destino” para os seus logs. No exemplo acima utilizamos três sinks: Console, Debug e Arquivos (File). Porém a comunidade mantem mais de 20 diferentes sinks e para diversos destinos! Alguns são:
- Amazon CloudWatch
- ApplicationInsights
- Console
- Elasticsearch
- Telegram
- Microsoft Teams
- Stackify
A lista completa pode ser encontrada na wiki.
Recomendo a leitura da documentação completa para mais informações sobre como o Serilog processa os dados de seus eventos: https://github.com/serilog/serilog/wiki/Writing-Log-Events
Monitoramento em Tempo Real — ApplicationInsights
Agora que temos logs estruturados em nossa api e alguns arquivos com os logs, podemos pensar em monitorar nossa API. Temos várias opções para realizar isso, porém a mais plug and play possível é, na minha opinião, utilizarmos o Azure Application Insights.
O Azure Application Insights é uma solução para ingestão, monitoramento e análise de logs. Através dele você poderá, em um único lugar, obter métricas sobre:
- Disponibilidade (Quanto tempo fica rodando, sem erros)
- Falhas (Exceptions sem tratamento)
- Falhas em Dependências (por ex: chamadas a APIs de Terceiros)
- Consumidores (quem está chamando sua API)
Além das métricas também podemos, graças ao Azure Monitor, definir regras para alertas e alarmes com base nestas métricas , ver (em tempo real e série histórica) as instâncias que estão executando e receber dicas sobre possíveis gargalos em nossa aplicação.
Os detalhes do Application Insights são vastos! Por isso, hoje, vamos focar em coloca-lo em ação! Em um post futuro podemos fazer um completão sobre esta solução e o Azure Monitor!
Para configurar o Application Insights SDK temos duas opções: Realizar a configuração manualmente ou Deixar que o Visual Studio realize a configuração para nós. Vou fazer o passo-a-passo para o Visual Studio, porém a ideia é a mesma para o manual:
- Adicionar o NuGet Microsoft.ApplicationInsights.AspNetCore
- Criar , no Portal do Azure , um novo recurso do Application Insights
- Adicionar, via appsettings, variavel de ambiente ou hardcoded, a Instrumentation Key à API
Configurando o Application Insights através do Visual Studio
Com a Solution aberta clique com o direito em sua API, vá ao submenu Add e selecione a opção Application Insights Telemetry
Na tela que abrir clique em Get Started
Na próxima tela você deverá realizar login (caso ainda não o tenha feito) com sua conta do Azure, escolher uma subscription onde o recurso será utilizado/criado e, finalmente, escolher o resource. Feito isso, clique em Register.
Após confirmar tudo será configurado para você e uma nova tela será apresentada:
E se acessarmos nossa conta no Azure poderemos ver o recurso criado:
Se executarmos a API e esperarmos alguns minutos (pode levar até 5 minutos) poderemos ver os dados sendo enviados para o Azure. Abra o Application Insights criado e você poderá visualizar, de cara, algumas métricas:
Importante : O padrão salvará uma Instrumentation Key em seu AppSettings. Eu recomendo mover para o secrets.json ou para uma variável de ambiente. Feito isso, atualize a chamada ao AddAplicationInsightsTelemetry com um IConfiguration contendo todas as fontes desejadas de configurações! Isso é importante pois por padrão o SDK olha apenas o appsettings.json!
Quase tudo configurado, apenas nos falta integrar o Serilog e o ApplicationInsights!
Enviando logs do Serilog para o ApplicationInsight
Lembra das Sinks que comentei no começo do post? Iremos utilizar uma nova sink para integrar com o ApplicationInsights!
Adicione em sua API uma referência ao NuGet Serilog.Sinks.ApplicationInsights e voltemos ao Program.cs para configurar a escrita ao ApplicationInsights:
https://medium.com/media/afed5cabf4244eb68db65c8e1d3920c8/href
Importante: Dependendo das versões das bibliotecas um Warning poderá ser emitido para a TelemetryConfiguration.Active . Até o momento da escrita deste post (abril de 2020) a documentação não foi alterada para informar a melhor maneira de contornar isso, porém existe uma issue no GitHub do projeto contendo workarounds e sua possíveis alterações. Uma delas é utilizar TelemetryConfiguration.CreateDefault() ao inves do TelemetryConfiguration.Active.
Agora, se você acessar os traces em seu ApplicationInsights poderá ver os logs da API:
Verificando se a API está no ar — Healthchecks
A cereja no bolo 🍰 para “finalizarmos” esta parte da API será adicionar (e configurar) um HealthCheck para a API.
Um Healthcheck é , basicamente, um endpoint que outros programas (normalmente balanceadores de carga) podem chamar para saber se a API está funcionando!
No universo .NET temos uma biblioteca já pronta para isso e que vem embutida com os projetos .NET Core: Microsoft.AspNetCore.Diagnostics.HealthChecks. Então só precisamos adicionar os serviços à injeção de dependência e configurar qual será a rota do endpoint!
Para isso vamos alterar nosso Startup.cs da seguinte maneira:
https://medium.com/media/2b09a9462aeb2e5a8322786a664510f3/href
Para testar que deu certo execute a API e acesse a rota /health, você deverá ser recebido com uma mensagem: Healthy.
Podemos condicionar o nosso Healthy/Unhealthy também ao status da conexão da nossa API com o banco de dados! Caso você utilize o EntityFrameworkCore é só adicionar o NuGet Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore e encadear uma chamada ao .AddDbContext() após o AddHealthChecks(), por exemplo:
https://medium.com/media/a40d8ad6c46fcc13be78563de9c114ba/href
Considerações Finais
Hoje vimos algumas bibliotecas que facilitam com o monitoramento da nossa API. Antes de publicarmos pode parecer overkill ter tudo isso porém é um esforço não muito grande e com um payoff absurdo quando você precisar descobrir o motivo de seu app/site/api está fora do ar ou quais são aqueles gargalos que te custam seus leads.
Para o próximo posts vamos dar uma olhada em Docker, Docker Compose , a Integração deles com o Visual Studio, e algumas configurações finais antes de botarmos a API no ar!
Obrigado por lerem mais este post, até a próxima e Abraços!
Top comments (0)