Logs Estruturados com Serilog e Seq: Melhorando a Visibilidade e Análise de Dados em Aplicações
Tags: logs, serilog, seq, logestruturado
Published: 2024-05-10 01:48:15

O registro de **logs** é uma prática fundamental no desenvolvimento de software, fornecendo informações valiosas sobre o comportamento, desempenho e erros de uma aplicação. No entanto, o simples armazenamento de mensagens de texto em arquivos ou consoles pode não ser suficiente para obter uma compreensão profunda e eficaz do funcionamento de uma aplicação. É aqui que os **logs** estruturados entram em jogo, oferecendo uma abordagem mais organizada, legível e analisável para o registro de eventos. Neste artigo, vamos explorar os conceitos de **logs** estruturados e como o **[Serilog](https://serilog.net/)** em conjunto com o **[Seq](https://datalust.co/seq)** pode melhorar significativamente a visibilidade e análise de dados em aplicações. O objetivo desse artigo é mostrar um aplicação usando **Serilog** com **Seq**, para isso vamos do início até a integração completa.

# **A Importância dos Logs**

Os **logs**, registros detalhados de atividades e eventos em um sistema, assumem um papel crucial em diversos contextos, desde o desenvolvimento de software até a segurança de rede e a análise de negócios. Além disso, possibilitam uma atuação mais proativa do que reativa na resolução e identificação de problemas. Sua importância se manifesta em três pilares principais:

1 - Solução de Problemas e Depuração:

- Rastreamento de Falhas: **Logs** facilitam a identificação de erros e falhas em sistemas, fornecendo informações sobre:

a - Origem do Erro: Localização específica no código ou componente onde o erro ocorreu.

b - Causa do Erro: Mensagens de erro e detalhes técnicos que auxiliam na compreensão da causa raiz do problema.

c - Contexto do Erro: Informações sobre o estado do sistema e as ações que antecederam o erro, facilitando a análise e a correção.

d - Análise de Desempenho: **Logs** permitem a análise do desempenho do sistema, identificando gargalos e áreas de otimização.

e - Tempo de Resposta: Monitoramento do tempo de resposta de operações e requisições, detectando lentidão e gargalos.

f - Detecção de Anomalias: Monitoramento de padrões de comportamento e identificação de anomalias que podem indicar problemas em potencial.

2 - Segurança e Conformidade:

Auditoria e Rastreamento: **Logs** fornecem um registro completo das atividades em um sistema, permitindo:

a - Investigação de Incidentes: Rastreamento de atividades suspeitas e investigação de incidentes de segurança.

b - Análise de Conformidade: Demonstração da conformidade com regulamentações e normas através da auditoria de **logs**.

c - Detecção de Fraude: Identificação de atividades fraudulentas e acesso não autorizado ao sistema.

d - Prevenção de Ataques: **Logs** podem ser usados para identificar e prevenir ataques cibernéticos.

3 - Análise de Negócios e Inteligência:

Melhoria da Experiência do Cliente: Análise de **logs** de aplicativos e websites para:

a - Identificar pontos de atrito: Compreender os desafios que os usuários enfrentam e identificar áreas de melhoria na experiência do cliente.

b - Compreender o comportamento do cliente: Analisar padrões de uso e preferências para personalizar a experiência do cliente e otimizar campanhas de marketing.

# **O que são Logs Estruturados?**

**Logs** estruturados são registros de eventos que seguem um formato predefinido e organizado, geralmente em formato **JSON**, **XML** ou similar. Diferentes dos **logs** tradicionais, **logs** estruturados contém uma série informações e metadados adicionais, campos nomeados e valores associados usados para agrupamento e consultas,tornando-os mais legíveis, analisáveis e fáceis de correlacionar. Esta estruturação facilita a consulta, filtragem e visualização de **logs**, especialmente em ambientes de microserviços, contêineres e aplicações distribuídas. **Logs** estruturados devem conter informações relevantes ao negócio e ao desenvolvedor.   

Um **log** comum de exceções, por exemplo, envia dados da exceção, como mensagem, **stack trace**, **inner exceptions**. Um **log** estruturado de exceções, poderia conter informações que diagnosticam o servidor no qual o **log** foi gerado, qual a operação estava sendo processada, qual o ID ou dados mais completos dos seus objetos de negócio durante a operação, além da comum severidade. E não para por aí, ainda seria possível informar qual o cliente da requisição, no caso de **WebApps**, qual o usuário logado, entre outras diversas informações de aplicação que poderiam ser passadas para o **log**, ajudando no **troubleshooting** e análise de problemas ou mesmo de fluxos de negócio. Para o artigo vamos usar [Serilog](https://github.com/serilog/serilog) para criar os **logs** estruturados.

# **Introdução ao Serilog**

**Serilog** é uma biblioteca de **logging** para .NET e também outras linguagens que suporta a geração de **logs** estruturados de forma nativa. Ele permite que os desenvolvedores registrem facilmente eventos e erros em seus aplicativos, facilitando a depuração e a solução de problemas, além de oferece uma configuração flexível e extensível, permitindo que os desenvolvedores capturem e registrem informações detalhadas, contextuais e enriquecidas sobre o comportamento e o desempenho de suas aplicações. Ao contrário das estruturas de **log** tradicionais, o **Serilog** se concentra no **log** estruturado, o que significa que os **logs** são registrados em um formato estruturado que pode ser facilmente consultado e analisado.

**Por que usar o Serilog?**

O **Serilog** é uma biblioteca de **logging** que se destaca em muitos aspectos e oferece diversas vantagens sobre outras estruturas de **log** disponíveis no mercado. Abaixo estão algumas razões pelas quais muitos desenvolvedores optam por usar o **Serilog** em seus projetos em vez de outras bibliotecas de **logging**:

**1 - Logs Estruturados**

Uma das características mais distintivas do **Serilog** é o suporte nativo para **logs** estruturados. Em vez de simplesmente gravar mensagens de texto em arquivos ou consoles, o **Serilog** permite que os desenvolvedores gerem **logs** em formatos organizados, como **JSON**, **XML** ou outros, tornando-os mais fáceis de analisar, consultar e correlacionar. **Logs** estruturados são especialmente úteis em ambientes distribuídos e complexos, onde a capacidade de rastrear e analisar o fluxo de dados entre diferentes componentes é crucial.

**2 - Flexibilidade e Extensibilidade**

O **Serilog** é altamente flexível e extensível, permitindo que os desenvolvedores personalizem e configurem o sistema de **logging** de acordo com as necessidades específicas do projeto. Ele oferece uma variedade de **sinks** que representa qual o destino dos **logs**, existem também os enriquecedores de **log**, facilitando a integração com diferentes sistemas, serviços e plataformas de monitoramento e análise de **logs**.

O **Serilog** fornece [Sinks](https://github.com/serilog/serilog/wiki/Provided-Sinks ) que são os coletores para gravar eventos de **log** no armazenamento em vários formatos.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zufexchye1ylqu3ctub1.png)

**3 - Enriquecimento de Logs**

O **Serilog** facilita o enriquecimento de **logs** com informações adicionais, como propriedades personalizadas, contexto da aplicação, dados do ambiente e muito mais. Isso ajuda a fornecer informações mais detalhados e contextuais sobre o comportamento e o desempenho da aplicação, melhorando a capacidade de monitoramento, diagnóstico e resolução de problemas em tempo real.

**4 - Performance e Eficiência**

O **Serilog** foi projetado com foco em performance e eficiência, minimizando o impacto no desempenho da aplicação durante a geração e gravação de **logs**. Ele utiliza técnicas avançadas de **buffering**, assincronia e otimização para garantir que o **logging** não se torne um gargalo ou afete negativamente a experiência do usuário final.

**5 - Comunidade Ativa e Suporte**

O **Serilog** possui uma comunidade ativa, engajada e colaborativa de desenvolvedores, mantenedores e contribuidores que continuamente trabalham juntos para melhorar, aprimorar e estender a biblioteca. Além disso, o **Serilog** é bem documentado, possui uma ampla gama de recursos, tutoriais e exemplos disponíveis, e oferece suporte através de fóruns, repositórios e plataformas de discussão, proporcionando uma base sólida e confiável para os desenvolvedores e organizações que escolhem adotá-lo em seus projetos.

# **Seq: Gerenciamento de Logs e Rastreamentos em Tempo Real**

**Seq** é um servidor de pesquisa e análise em tempo real para **logs** e rastreamentos de aplicativos estruturados. Com sua interface intuitiva, armazenamento de eventos **JSON**, o que facilita a análise e a integração com outras ferramentas.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yqlss379bbxtded0r45w.png)

1 - Principais Recursos e Funcionalidades:

a - Interface de Usuário Amigável: A interface do **Seq** foi cuidadosamente projetada para facilitar a navegação e a análise de **logs**. Com recursos como busca rápida, filtros avançados e visualizações personalizadas, os usuários podem encontrar rapidamente as informações necessárias.

b - Armazenamento em Formato **JSON**: Os eventos de **log** no **Seq** são armazenados no formato **JSON**, o que permite uma estruturação flexível e facilita a integração com outras ferramentas e sistemas.

c - Busca e Análise em Tempo Real: Uma das principais vantagens do **Seq** é sua capacidade de buscar e analisar **logs** em tempo real. Isso permite que os usuários identifiquem e respondam rapidamente a problemas e anomalias em seus sistemas.

d - Integração com Plataformas e Ferramentas: O **Seq** oferece uma ampla gama de integrações com outras ferramentas e plataformas, incluindo sistemas de monitoramento, alerta e gerenciamento de incidentes. Isso permite uma integração perfeita em ambientes de desenvolvimento e operações.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/01z5n6ux0khces6s1e9f.png)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s1tz7zkkwb2kr7aeully.gif)

## Vamos colocar a mão na massa!

Vamos desenvolver uma **API** simples com integração ao **Seq** para armazenar nossos **logs**. Para isso, utilizaremos o **Serilog**. Serão duas aplicações com as seguintes tecnologias: **C#** e **Node.js**. A ideia não é focar na criação das **APIs**, mas sim na geração de **logs** e no monitoramento. Portanto, será uma **API** simples. 

### 1 - API .NET Csharp

Será uma **API** simples para o cadastro de eventos. A ideia é utilizar **logs** e visualizá-los no **Seq**.

**Pré-requisitos**:

- [.NET 8 ](https://dotnet.microsoft.com/pt-br/)
- [Docker](https://docs.docker.com/build/cloud/)

Para instalar o Serilog em nossa aplicação é bastante simples, os desenvolvedores podem usar o **NuGet** para instalar o pacote Serilog e, em seguida, configurá-lo usando uma variedade de abordagens, como JSON ou configuração baseada em código.

```bash
dotnet add package Serilog.Sinks.Seq --version 7.0.0
dotnet add package Serilog.Sinks.Dynatrace --version 1.0.7
dotnet add package Serilog.Enrichers.Thread --version 3.1.0
dotnet add package Serilog.Enrichers.Process --version 2.0.2
dotnet add package Serilog.Enrichers.Memory --version 1.0.4
dotnet add package Serilog.Enrichers.Environment --version 2.3.0
dotnet add package Serilog.Enrichers.CorrelationId --version 3.0.1
```

**a - Serviço**

```cs
using EventAPI.Models;

namespace EventAPI.Services;

public interface IEventService
{
    List<Event> GetAll();
    Event? GetById(int id);
    Event Create(Event newEvent);
    void Update(int id, Event updatedEvent);
    void Delete(int id);
    void BatchDelete();
}

```

```cs
using EventAPI.Models;

namespace EventAPI.Services;

public class EventService : IEventService
{
    private readonly ILogger<EventService> _logger;
    private List<Event> _events;

    public EventService(ILogger<EventService> logger)
    {
        _logger = logger;
        _events = new List<Event>();
    }

    public List<Event> GetAll()
    {
        return _events;
    }

    public Event? GetById(int id)
    {
        return _events.FirstOrDefault(e => e.Id == id);
    }

    public Event Create(Event newEvent)
    {
        _logger.LogInformation("Creating new event with title {title}", newEvent.Title);
        newEvent.Id = _events.Count > 0 ? _events.Max(e => e.Id) + 1 : 1;
        _events.Add(newEvent);
        return newEvent;
    }

    public void Update(int id, Event updatedEvent)
    {
        _logger.LogInformation("Updating event with id {id}", id);
        var existingEvent = _events.FirstOrDefault(e => e.Id == id);
        if (existingEvent is null) throw new InvalidOperationException("Event not found");
        existingEvent.Title = updatedEvent.Title;
        existingEvent.StartDateTime = updatedEvent.StartDateTime;
        existingEvent.EndDateTime = updatedEvent.EndDateTime;
    }

    public void Delete(int id)
    {
        _logger.LogInformation("Deleting event with id {id}", id);
        var existingEvent = _events.FirstOrDefault(e => e.Id == id);
        if (existingEvent is null) throw new InvalidOperationException("Event not found");
        _events.Remove(existingEvent);
    }

    public void BatchDelete()
    {
        if (_events.Count == 0)
        {
            throw new InvalidOperationException("No events to delete");
        }
        _events.Clear();
    }
}

```

Para algumas ações do serviço, foi disponibilizado o registro de **log** através da chamada **logger.LogInformation**, onde serão gravadas as informações passadas.

Os níveis de logs são:

O **Serilog** usa níveis como principal meio de atribuir importância aos eventos de log. Os níveis em ordem crescente de importância são:

- **_Verbose_**: Este nível é usado para informações de rastreamento e detalhes de depuração extremamente minuciosos. Geralmente, esses logs são ativados apenas em situações incomuns ou durante a depuração intensiva. Eles fornecem uma quantidade muito detalhada de informações, úteis principalmente para desenvolvedores durante o diagnóstico de problemas complexos.
- **_Debug_**: Logs neste nível são usados para informações relacionadas ao fluxo de controle interno e despejos de estado de diagnóstico para facilitar a identificação de problemas conhecidos. Eles fornecem detalhes úteis para os desenvolvedores entenderem a execução do código e identificarem possíveis problemas durante o desenvolvimento e a depuração.

- **_Information_**: Este é o nível padrão mínimo de log ativado. Ele é usado para eventos de interesse ou relevância para observadores externos. Isso pode incluir eventos importantes para monitoramento ou rastreamento de atividades do sistema, sem sobrecarregar o registro com muitos detalhes desnecessários.

- **_Warning_**: Logs neste nível indicam possíveis problemas ou degradação de serviço/funcionalidade. Eles sinalizam situações que podem precisar de atenção, mas que não causaram uma falha no sistema. Geralmente, são usados para alertar sobre condições anormais que podem precisar ser investigadas.

- **_Error_**: Este nível indica falhas dentro da aplicação ou do sistema conectado. Esses logs registram erros que ocorreram durante a execução do programa e que podem exigir intervenção para corrigir ou mitigar. Eles são úteis para identificar e resolver problemas que podem impactar negativamente o funcionamento do sistema.

- **_Fatal_**: Logs neste nível indicam erros críticos que causaram a falha completa da aplicação. Eles representam condições graves que exigem atenção imediata e intervenção para restaurar o funcionamento adequado do sistema. Esses logs geralmente são usados para registrar falhas catastróficas que impedem a continuidade da operação normal da aplicação.

Exemplo abaixo usa os níveis:

```cs
using Serilog;
using Serilog.Events;

class Program
{
    static void Main()
    {
        Log.Logger = new LoggerConfiguration()
            .MinimumLevel.Verbose() // Definindo o nível mínimo de log como Verbose
            .WriteTo.Console() // Escrevendo os logs no console
            .CreateLogger();

        Log.Verbose("Esta é uma mensagem de log Verbose."); // Este log será exibido
        Log.Debug("Esta é uma mensagem de log Debug."); // Este log será exibido
        Log.Information("Esta é uma mensagem de log Information."); // Este log será exibido
        Log.Warning("Esta é uma mensagem de log Warning."); // Este log será exibido
        Log.Error("Esta é uma mensagem de log Error."); // Este log será exibido
        Log.Fatal("Esta é uma mensagem de log Fatal."); // Este log será exibido

        Log.CloseAndFlush(); // Encerrando o logger
    }
}

```

**b - Configuração Serilog**

```cs
using Serilog;

namespace EventAPI.Extensions;

public static class LogSettings
{
  public static void AddLogSettings(this WebApplicationBuilder builder, string applicationName, ConfigurationManager configuration)
  {
    var seq = configuration["Settings:Seq"];
    if (string.IsNullOrWhiteSpace(seq)) throw new ArgumentNullException("Seq is required");
    Console.WriteLine($"Environment: {builder.Environment.EnvironmentName}");
    builder.Logging.ClearProviders();
    var logger = new LoggerConfiguration();
    logger.Enrich.WithProperty("ApplicationName", $"{applicationName} - {Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT")}")
    .MinimumLevel.Information()
    .Enrich.WithEnvironmentName()
    .Enrich.WithMachineName()
    .Enrich.WithProcessId()
    .Enrich.WithThreadId()
    .Enrich.WithMemoryUsage()
    .Enrich.FromLogContext()
    .Enrich.WithCorrelationId()
    .Enrich.WithCorrelationIdHeader();
    if (builder.Environment.EnvironmentName == "Development")
    {
      logger.WriteTo.Console(outputTemplate: "{Timestamp:yyy-MM-dd HH:mm:ss.fff} [{Level}] {Message}{NewLine}{Exception}");
    }
    logger.WriteTo.Seq(seq, restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Information);
    builder.Logging.AddSerilog(logger.CreateLogger());
  }
}
```

No trecho de código abaixo, temos a obtenção da **URL** configurada para cada ambiente do **Seq** . Com isso, os logs gerados pela nossa aplicação são armazenados no **Seq** e podem ser acessados via: [http://localhost:5341/](http://localhost:5341/).

```cs
using Serilog;

namespace EventAPI.Extensions;

public static class LogSettings
{
  public static void AddLogSettings(this WebApplicationBuilder builder, string applicationName, ConfigurationManager configuration)
  {
    var seq = configuration["Settings:Seq"];
    if (string.IsNullOrWhiteSpace(seq)) throw new ArgumentNullException("Seq is required");
    builder.Logging.ClearProviders();
    var logger = new LoggerConfiguration();
    ....
    logger.WriteTo.Seq(seq, restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Information);
    builder.Logging.AddSerilog(logger.CreateLogger());
  }
}
```

Foi configurado alguns [**Enrichments**](https://github.com/serilog/serilog/wiki/Enrichment) permitem adicionar contexto adicional às suas mensagens de **log**, o que pode ser extremamente útil para depuração e solução de problemas.

```cs
Enrich.WithEnvironmentName()
Enrich.WithMachineName()
Enrich.WithProcessId()
Enrich.WithThreadId()
Enrich.WithMemoryUsage()
Enrich.FromLogContext()
Enrich.WithCorrelationId()
Enrich.WithCorrelationIdHeader();
```
Foi utilizado o **Seq** para registrar os **logs**, uma vez que a coleta de informações por console é apenas para o ambiente de desenvolvimento.

```cs
    if (builder.Environment.EnvironmentName == "Development")
    {
      logger.WriteTo.Console(outputTemplate: "{Timestamp:yyy-MM-dd HH:mm:ss.fff} [{Level}] {Message}{NewLine}{Exception}");
    }
    logger.WriteTo.Seq(seq, restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Information);
```

**c - API**

```cs
using EventAPI.Models;
using EventAPI.Services;

namespace EventAPI.Endpoints;

public static class Events
{
  public static void RegisterEndpoints(this IEndpointRouteBuilder routes)
  {
    var events = routes.MapGroup("/api/v1/events")
    .WithName("Events")
    .WithOpenApi();
    events.MapGet("", (IEventService service) => service.GetAll())
    .WithName("GetAllEvents")
    .WithTags("Events");
    events.MapGet("/{id}", (int id, IEventService service) => service.GetById(id))
    .WithName("GetEventById")
    .WithTags("Events");
    events.MapPost("", (Event newEvent, IEventService service) => service.Create(newEvent))
    .WithName("CreateEvent")
    .WithTags("Events");
    events.MapPut("/{id}", (int id, Event updatedEvent, IEventService service) =>
    {
      service.Update(id, updatedEvent);
    })
    .WithName("UpdateEvent")
    .WithTags("Events");
    events.MapDelete("/{id}", (int id, IEventService service) =>
    {
      service.Delete(id);
    })
    .WithName("DeleteEvent")
    .WithTags("Events");
    events.MapDelete("/batch", (IEventService service) =>
    {
      service.BatchDelete();
    })
    .WithName("BatchDeleteEvents")
    .WithTags("Events");
  }
}
```

**d - Global Exception**

```cs
using Microsoft.AspNetCore.Diagnostics;

namespace EventAPI.ExceptionHandler;

public class GlobalException : IExceptionHandler
{
    private readonly ILogger<GlobalException> logger;
    public GlobalException(ILogger<GlobalException> logger)
    {
        this.logger = logger;
    }
    public ValueTask<bool> TryHandleAsync(
        HttpContext httpContext,
        Exception exception,
        CancellationToken cancellationToken)
    {
        var exceptionMessage = exception.Message;
        logger.LogError(
            "Error Message: {exceptionMessage}, time of occurrence {time}",
            exceptionMessage, DateTime.UtcNow);
        return ValueTask.FromResult(false);
    }
}
```

Este código mostra como criar um manipulador de exceções global personalizado em um aplicativo ASP.NET Core para registrar mensagens de erro de exceção usando o **Serilog ** ou outro mecanismo de registro. Este manipulador de exceções pode ser adicionado ao pipeline de middleware para lidar com exceções em todo o aplicativo.

**Caso queira subir a API local siga os passos:**

a - Crie um arquivo **docker-compose.yml**:

```bash
version: '3.4'

services:
  seq:
    image: datalust/seq:latest
    container_name: seq
    environment:
      - ACCEPT_EULA=Y
    ports:
      - 5341:5341
      - 8081:80
```

b - Depois execute o comando para subir o **Seq**:

```bash
docker-compose up
```

c - Acesse o **Seq** link:

```bash
 http://localhost:5341/
```

**Caso queria testar como a aplicação e Seq compartilhando a mesma rede em containers:**

Na raíz do projeto existe **docker-compose.yml**, execute os comandos:

a - Subir containers

```bash
docker-compose up -d --build
```

b - Descer **containers**

```bash
docker-compose down
```

**Para acessar Seq o link local é:**[http://localhost:5341/](http://localhost:5341/)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qyx1zo37b6jfocmnf725.png)

_**Para acessar o repositório com o código completo**_ [Serilog Seq Api](https://github.com/wandealves/SerilogSeqApiEventDotnet)

**Dicas**

1 - Uma melhoria que podemos fazer no código é usar **Serilog.Sinks.Async** para registar **logs** no console. Use esse coletor para reduzir a sobrecarga de registro de chamadas ,ficando o trabalho a cargo de uma Thread em segundo plano.

Instalando a partir do **Nuget**

```bash
dotnet add package Serilog.Sinks.Async
```

```cs
using Serilog;

class Program
{
  static async Task Main()
  {
    Log.Logger = new LoggerConfiguration()
        .MinimumLevel.Debug()
        .WriteTo.Async(wt => wt.Console())
        .WriteTo.File("logs/my-logs.txt", rollingInterval: RollingInterval.Day)
        .CreateLogger();

    Log.Information("Meu log!");

    int a = 10, b = 0;
    try
    {
      Log.Debug("Dividindo {A} by {B}", a, b);
      Console.WriteLine(a / b);
    }
    catch (Exception ex)
    {
      Log.Error(ex, "Algo deu errado");
    }
    finally
    {
      await Log.CloseAndFlushAsync();
    }
  }
}
```
2 - Não utilizar o **Serilog.Sinks.Console** em produção, O Console **Sink** é adequado para uso em desenvolvimento e depuração, por isso deve ser utilizado em ambiente local. Em produção é recomendado utilizar coletores como: **Elasticsearch**, **Rabbitmq**, **Seq**, **Sentry**, **Splunk** entre outros que podem ser visto em [Provided Sinks](https://github.com/serilog/serilog/wiki/Provided-Sinks)

3 - Podemo formatar a saída para temos **logs** para estruturados. Os coletores baseados em console e em arquivo, geralmente aceitam modelos de saída para controlar como os dados de eventos de **log** são formatados. 

**a - Formato de Texto Simples:**

```cs
  Log.Logger = new LoggerConfiguration()
            .WriteTo.Console()
        .CreateLogger();

```

**b - Formato JSON:**

```cs
   Log.Logger = new LoggerConfiguration()
      .WriteTo.Console(new JsonFormatter())
      .CreateLogger();

```

**Saída**

```bash
{"Timestamp":"2024-04-04T22:43:50.3868987-03:00","Level":"Information","MessageTemplate":"Meu log!"}
{"Timestamp":"2024-04-04T22:43:50.4401178-03:00","Level":"Error","MessageTemplate":"Algo deu errado","Exception":"System.DivideByZeroException: Attempted to divide by zero.\n   at Program.Main() in /home/wanderson/Developer/Repositorios/serilog_log_console/AppSerilogConsole/Program.cs:line 26"}
```
**c - Formato Estruturado:**

O formato **logs** gravados podem ser modificados usando o **outputTemplateparâmetro**. 

```cs
Log.Logger = new LoggerConfiguration()
    .WriteTo.Console(outputTemplate:
        "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}")
    .CreateLogger();
```

**Propriedades de saída:**

- **Exception**- A mensagem de exceção completa e o rastreamento de pilha, formatados em várias linhas. Vazio se nenhuma exceção estiver associada ao evento.

- **Level**- O nível de evento de log, formatado como o nome completo do nível. Para nomes de níveis mais compactos, use um formato como {Level:u3}ou {Level:w3}para nomes de níveis com três caracteres maiúsculos ou minúsculos, respectivamente.

- **Message**- A mensagem do evento de log, renderizada como texto simples. O :lespecificador de formato desativa a citação de strings e :jusa a renderização no estilo JSON para quaisquer dados estruturados incorporados.

- **NewLine**- Uma propriedade com o valor de System.Environment.NewLine.

- **Properties**- Todos os valores de propriedade de evento que não aparecem em nenhum outro lugar da saída. Use o :jformato para usar a renderização JSON.

- **Timestamp**- O carimbo de data/hora do evento, como um arquivo DateTimeOffset.

- **TraceId**- O ID do rastreamento que estava ativo quando o evento foi criado, se houver.

- **SpanId**- O ID do período que estava ativo quando o evento foi criado, se houver.

### 2 - API com Node.js

Será uma **API** simples para o cadastro de eventos. A ideia é utilizar **logs** e visualizá-los no **Seq**."

**Pré-requisitos**:

- [Node.js ](https://nodejs.org/en/download/)
- [Docker](https://docs.docker.com/build/cloud/)

Logs com Winston:

**[Winston](https://github.com/winstonjs/winston)** é uma biblioteca de registro popular para **node.js**, com suporte para registro estruturado. **Winston** utiliza "transportes" para representar diferentes destinos de **log**, incluindo o console padrão e opções de arquivo. **[Winston-seq](https://github.com/datalust/winston-seq)** é um transporte para **Winston**, que possibilita o envio de **logs** para o **Seq**.

Pacotes:

```bash
npm install winston
npm install @datalust/winston-seq
```
Configuração: 

import em sua aplicação Node.js:

```javascript
const winston = require("winston");
```

```javascript
const winston = require("winston");
const { combine, timestamp, json, printf } = winston.format;
const { SeqTransport } = require("@datalust/winston-seq");

const logLevels = {
  error: 0,
  warn: 1,
  info: 2,
  http: 3,
  verbose: 4,
  debug: 5,
  silly: 6
};

const customFormat = printf(({ level, message, label, timestamp }) => {
  return `${timestamp} [${label}] ${level}: ${message}`;
});

const setup = () => {
  const logger = winston.createLogger({
    levels: logLevels,
    level: process.env.LOG_LEVEL || "info",
    format: combine(
      timestamp({
        format: "YYYY-MM-DD hh:mm:ss.SSS A"
      }),
      json()
    ),
    transports: [
      new SeqTransport({
        serverUrl: process.env.SEQ_URL || "http://localhost:5341",
        onError: e => {
          console.error(e);
        }
      }),
      new winston.transports.Console()
    ]
  });
  return logger;
};

module.exports = { setup };

```

No código de configuração podemos ver os níveis do Log no **Winston** que são:

```json
{
  error: 0,
  warn: 1,
  info: 2,
  http: 3,
  verbose: 4,
  debug: 5,
  silly: 6
}

```

Uso dos logs na API:

```javascript
const { v4: uuidv4 } = require("uuid");
const logger = require("../configurations/logger").setup();

let events = [];

const list = (req, res) => {
  res.json(events);
};

const create = (req, res) => {
  const { code, title } = req.body;
  const newEvent = { id: uuidv4(), code, title };
  events.push(newEvent);
  res.status(201).json(newEvent);
};

const update = (req, res) => {
  const { id } = req.params;
  const { title } = req.body;
  const index = events.findIndex(event => event.id === id);
  if (index !== -1) {
    events[index].title = title;
    res.json(events[index]);
  } else {
    res.status(404).json({ mensagem: "Evento não encontrada" });
  }
};

const remove = (req, res) => {
  const { id } = req.params;
  const index = events.findIndex(event => event.id === id);
  if (index !== -1) {
    events.splice(index, 1);
    res.json({ mensagem: "Tarefa excluída com sucesso" });
  } else {
    logger.error("Tarefa não encontrada");
    res.status(404).json({ mensagem: "Tarefa não encontrada" });
  }
};

module.exports = { list, create, update, remove };
```
Os seis níveis de **log** acima de cada um correspondem a um método no criador de logs:

```javascript
logger.error('error');
logger.warn('warn');
logger.info('info');
logger.verbose('verbose');
logger.debug('debug');
logger.silly('silly');
```

**Caso queira subir a API local siga os passos:**

a - Crie um arquivo **docker-compose.yml**:

```bash
version: '3.4'

services:
  seq:
    image: datalust/seq:latest
    container_name: seq
    environment:
      - ACCEPT_EULA=Y
    ports:
      - 5341:5341
      - 8081:80
```

b - Depois execute o comando para subir o **Seq**:

```bash
docker-compose up
```
c - Acesse o **Seq** link:

```bash
 http://localhost:5341/
```

**Caso queria testar como docker-compose da aplicação:**

Na raiz do projeto existe **docker-compose.yml**, execute os comandos:

a - Subir containers

```bash
docker-compose up -d --build
```

b - Descer containers

```bash
docker-compose down
```

Obs.: É necessário conceder permissão local ao arquivo wait-for-seq.sh com o comando:

```bash
chmod +x wait-for-seq.sh
```

**Para acessar Seq o link local é:**[http://localhost:5341/](http://localhost:5341/)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qyx1zo37b6jfocmnf725.png)

**_Para acessar o repositório com o código completo_** [Serilog Seq API](https://github.com/wandealves/SerilogSeqApiEventNodeJS)

**Referências:**

[Seq](https://datalust.co/seq)
[Serilog](https://docs.datalust.co/docs/using-serilog)
[Github - Serilog](https://github.com/serilog/serilog)
[Henrique Mauri - Melhores práticas de utilização do Serilog](https://henriquemauri.net/melhores-praticas-de-utilizacao-do-serilog/)
[Henrique Mauri -Coletando logs com o Serilog no .NET 6](https://henriquemauri.net/coletando-logs-com-o-serilog-no-net-6/)
[Milan Jovanović - 5 Serilog Best Practices For Better Structured Logging](https://www.milanjovanovic.tech/blog/5-serilog-best-practices-for-better-structured-logging)
[Logging from Node.js](https://docs.datalust.co/docs/using-nodejs)
[Github - Winston](https://github.com/winstonjs/winston)
[Github - Winston-seq](https://github.com/datalust/winston-seq)
[A Complete Guide to Winston Logging in Node.js](https://betterstack.com/community/guides/logging/how-to-install-setup-and-use-winston-and-morgan-to-log-node-js-applications/)
[Node.js Logging with Winston](https://reflectoring.io/node-logging-winston/)
[Automated Logging in Express.js](https://mirzaleka.medium.com/automated-logging-in-express-js-a1f85ca6c5cd)
[Best Practices for Logging in Node.js](https://blog.appsignal.com/2021/09/01/best-practices-for-logging-in-nodejs.html)
[Winston Logger - Full tutorial with a sample Nodejs application](https://signoz.io/blog/winston-logger/)