Trabalhando com correlationId em .Net + Serilog
Tags: csharp, serilog, correlationid, dotnet
Published: 2023-02-05 18:32:32

Em aplicações distribuídas, uma das coisas mais importantes que devemos ter, é rastreabilidade, principalmente de erros, e uma prática muito comum e eficiente, é trabalharmos com correlation id’s, para diferentes requisições ou workers.

Para implementarmos esse conceito, iremos utilizar como padrão, um exemplo de API, que pode ser encontrada em: otaviolarrosa/DotnetApi (github.com).

Primeiro, iremos utilizar os seguintes pacotes:

1.     Serilog
2.     Serilog.AspNetCore
3.     Serilog.Enrichers.CorrelationId

Conforme exemplificado no arquivo .csproj da aplicação

``` xml
  <Project Sdk="Microsoft.NET.Sdk.Web">

	<PropertyGroup>
		<TargetFramework>net6.0</TargetFramework>
		<Nullable>enable</Nullable>
		<ImplicitUsings>enable</ImplicitUsings>
	</PropertyGroup>

	<ItemGroup>
		<PackageReference Include="Microsoft.AspNet.WebApi.Core" Version="5.2.9" />
		<PackageReference Include="Serilog" Version="2.11.0" />
		<PackageReference Include="Serilog.AspNetCore" Version="6.0.1" />
		<PackageReference Include="Serilog.Enrichers.CorrelationId" Version="3.0.1" />
		<PackageReference Include="Serilog.Enrichers.Memory" Version="1.0.4" />
		<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
		<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
		<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.8">
			<PrivateAssets>all</PrivateAssets>
			<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
		</PackageReference>
	</ItemGroup>

	<ItemGroup>
		<ProjectReference Include="..\..\StreamNet\StreamNet\StreamNet.csproj" />
		<ProjectReference Include="..\Application\Application.csproj" />
		<ProjectReference Include="..\Consumers\Consumers.csproj" />
		<ProjectReference Include="..\Infrastructure\Infrastructure.csproj" />
		<ProjectReference Include="..\Producers\Producers.csproj" />
	</ItemGroup>

</Project>
```


Após isso, também é necessário configurar no startup do projeto, alguns pontos para utilizarmos o Serilog, tais como:

- Utilização do Serilog com o Enricher do CorrelationId
-     Utilização do Serilog, para utilizar o CorrelationId que vem do chamador da requisição, caso queira uma rastreabilidade integrada
-     Formatação de um template para facilitar a leitura dos logs

``` csharp
using Application.Extensions;
using Consumers;
using Infrastructure.Extensions;
using Producers;
using Serilog;
using StreamNet;

var builder = WebApplication.CreateBuilder(args);


Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Debug()
    .Enrich.WithCorrelationId() //Aqui, pedimos para o Serilog utilizar a feature de correlation Id.
    .Enrich.WithCorrelationIdHeader() //Aqui, pedimos para utilizar o correlation Id, de quem solicitou a chamada na requisição HTTP, para que o trace fique ainda mais rico, com os logs do frontend.
    .WriteTo.Console(outputTemplate: "[{CorrelationId} - {Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}") //Aqui, formatamos um template para formatação dos logs padronizados, à fim de facilitar a leitura.
    .CreateLogger();

builder.Host.UseSerilog(); //Adicionamos o Serilog dentro do Host do nosso serviço
builder.Services.AddHttpContextAccessor();
builder.Services.AddSingleton(builder.Logging);

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services.RegisterApplicationDependencies(builder.Configuration);
builder.Services.RegisterInfrastructureDependencies(builder.Configuration);
builder.Services.AddProducers();
builder.Services.AddConsumers();
builder.Services.AddTopicManagement();

var app = builder.Build();
app.RunMigrations();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseAuthorization();

app.MapControllers();

app.Run();
```

Após isso, temos apenas que utilizar a interface ILogger da abstração Microsoft.Extensions.Logging, como na controller abaixo:


``` csharp
using Application.UseCases.User.CreateUser.Input;
using Application.UseCases.User.RequestCreateUser;
using Microsoft.AspNetCore.Mvc;
using System.Net;

namespace Api.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class UserController : ControllerBase
    {
        private readonly IRequestCreateUserUseCase _requestCreateUserUseCase;
        private readonly ILogger<UserController> _logger;

        public UserController(IRequestCreateUserUseCase requestCreateUserUseCase, ILogger<UserController> logger)
        {
            _requestCreateUserUseCase = requestCreateUserUseCase;
            _logger = logger;
        }

        [HttpPost]
        [Route("/user")]
        public async Task<IActionResult> CreateUser([FromBody]RequestCreateUserInput userInput)
        {
            try
            {
                _logger.LogInformation("Starting request {method} with params {@input}", nameof(CreateUser), userInput);
                await _requestCreateUserUseCase.ExecuteAsync(userInput);
                _logger.LogInformation("Ended request {method} with params {@input}", nameof(CreateUser), userInput);
                return Ok();
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "A unexpected error occurred in {method} with params {@input}", nameof(CreateUser), userInput);
                return StatusCode((int)HttpStatusCode.InternalServerError, "A unexpected error occurred in CreateUser.");
            }
        }
    }
}
```


E assim, temos como resultado, um template bem simples de ler, e com o correlationId :)

![correlationIdTemplate](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9d2uljdrcpijirpp60qi.png)