Cell CMS — Criando logs robustos e monitorando uma API
Tags: azure, applicationinsights, logging, csharp
Published: 2020-06-24 23:59:23

---
title: Cell CMS — Criando logs robustos e monitorando uma API
published: true
date: 2020-04-13 10:31:01 UTC
tags: azure,applicationinsights,logging,csharp
canonical_url: 
series: CellCms
---

### Cell CMS — Criando logs robustos e monitorando uma API

![](https://cdn-images-1.medium.com/max/1024/0*wqkuJAgPn2geI8Lm)<figcaption>Photo by <a href="https://unsplash.com/@lukechesser?utm_source=medium&amp;utm_medium=referral">Luke Chesser</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption>

No [último post](https://dev.to/ardc_overflow/cell-cms-validando-inputs-e-mapeamento-automatico-3a70-temp-slug-5105973) 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<T>?

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 [**Serilog**](https://serilog.net/)podemos manter nossa utilização do ILogger<T> 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:

<iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcarbon.now.sh%2Fembed%2FuLCzHqg3wHRdHdEaCStG%3F&amp;display_name=Carbon&amp;url=https%3A%2F%2Fcarbon.now.sh%2FuLCzHqg3wHRdHdEaCStG&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;scroll=auto&amp;schema=carbon" width="1024" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/2065cedc7854461ecc2d0e2e22905116/href">https://medium.com/media/2065cedc7854461ecc2d0e2e22905116/href</a></iframe>

Execute a API e já ficará evidente a diferença com o log padrão:

![](https://cdn-images-1.medium.com/max/979/1*TK5bZd9B0tVr100IB-MORg.png)<figcaption>Padrão dos logs de Console do Serilog</figcaption>

Para finalizarmos a configuração vamos ao Startup.cs e adicionemos mais uma linha ao Configure:

<iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcarbon.now.sh%2Fembed%2FM5g3OhDBUX0ReHDFLPKH%3F&amp;display_name=Carbon&amp;url=https%3A%2F%2Fcarbon.now.sh%2FM5g3OhDBUX0ReHDFLPKH&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;scroll=auto&amp;schema=carbon" width="1024" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/48630fb81d1ac13d76f661fa32f004a4/href">https://medium.com/media/48630fb81d1ac13d76f661fa32f004a4/href</a></iframe>

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 :

<iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcarbon.now.sh%2Fembed%2FV5cth5VE7M0JBGsF8nnE%3F&amp;display_name=Carbon&amp;url=https%3A%2F%2Fcarbon.now.sh%2FV5cth5VE7M0JBGsF8nnE&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;scroll=auto&amp;schema=carbon" width="1024" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/dff87d30a34e56b22f1e960b6ab6dd56/href">https://medium.com/media/dff87d30a34e56b22f1e960b6ab6dd56/href</a></iframe>

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](https://github.com/serilog/serilog/wiki/Provided-Sinks).

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](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:

1. **Adicionar** o NuGet Microsoft.ApplicationInsights.AspNetCore
2. **Criar** , no Portal do **Azure** , um novo **recurso do Application Insights**
3. 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**

![](https://cdn-images-1.medium.com/max/647/1*a6X2uD8Kn-SA3CP3y7-Qdg.png)

Na tela que abrir clique em **Get Started**

![](https://cdn-images-1.medium.com/max/806/1*q0dv1L5PVeApKWc73DCFSQ.png)

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**.

![](https://cdn-images-1.medium.com/max/548/1*XAJ3mYsMGbZ9eqDU0eFHhw.png)<figcaption>As configurações default para criar o resource</figcaption>

Após confirmar tudo será configurado para você e uma nova tela será apresentada:

![](https://cdn-images-1.medium.com/max/731/1*AYhhjeA9FfvA0j0Wxoje1g.png)

E se acessarmos nossa conta no Azure poderemos ver o recurso criado:

![](https://cdn-images-1.medium.com/max/1024/1*se8Xjr0CVuxncwCwaLbbTw.png)

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:

![](https://cdn-images-1.medium.com/max/1024/1*3VP-zdTbYG-gRBwb92RF4g.png)<figcaption>Métricas comuns do Application Insights</figcaption>

![](https://cdn-images-1.medium.com/max/1024/1*_ZCIk3JivlBVn12U8t1Pig.png)<figcaption>O application map, que nos permite visualizar um Grafo com os clients da API e suas dependências</figcaption>

> **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:

<iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcarbon.now.sh%2Fembed%2FUYTSWCsH1jOxVqborxSh%3F&amp;display_name=Carbon&amp;url=https%3A%2F%2Fcarbon.now.sh%2FUYTSWCsH1jOxVqborxSh&amp;image=https%3A%2F%2Fcarbon.now.sh%2Fapi%2Fimage%2FUYTSWCsH1jOxVqborxSh&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;scroll=auto&amp;schema=carbon" width="1024" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/afed5cabf4244eb68db65c8e1d3920c8/href">https://medium.com/media/afed5cabf4244eb68db65c8e1d3920c8/href</a></iframe>

> **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](https://github.com/serilog/serilog-sinks-applicationinsights/issues/121) 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:

![](https://cdn-images-1.medium.com/max/1024/1*KsZ9f_dNunhImQbHY9MS-w.png)<figcaption>Logs (comuns) da API dentro do ApplicationInsights</figcaption>

**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:

<iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcarbon.now.sh%2Fembed%2FfW1JxlfcIXsdNiakh5Z2%3F&amp;display_name=Carbon&amp;url=https%3A%2F%2Fcarbon.now.sh%2FfW1JxlfcIXsdNiakh5Z2&amp;image=https%3A%2F%2Fcarbon.now.sh%2Fapi%2Fimage%2FfW1JxlfcIXsdNiakh5Z2&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;scroll=auto&amp;schema=carbon" width="1024" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/2b09a9462aeb2e5a8322786a664510f3/href">https://medium.com/media/2b09a9462aeb2e5a8322786a664510f3/href</a></iframe>

Para testar que deu certo execute a API e acesse a rota /health, você deverá ser recebido com uma mensagem: **Healthy.**

![](https://cdn-images-1.medium.com/max/447/1*h_EVa0QChZdnLL--rQHKyw.png)

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<TSeuContext>() após o AddHealthChecks(), por exemplo:

<iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcarbon.now.sh%2Fembed%2FEdg1dkTwzZBcg2uSwKux%3F&amp;display_name=Carbon&amp;url=https%3A%2F%2Fcarbon.now.sh%2FEdg1dkTwzZBcg2uSwKux&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;scroll=auto&amp;schema=carbon" width="1024" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/a40d8ad6c46fcc13be78563de9c114ba/href">https://medium.com/media/a40d8ad6c46fcc13be78563de9c114ba/href</a></iframe>

**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!