Introdução

Toda vez que um problema acontece em produção, a primeira coisa que fazemos é abrir os logs. Mas em quantas vezes você encontrou algo assim?

1
Erro ao processar pedido

Sem OrderId. Sem CustomerId. Sem saber em qual instância aconteceu ou em qual versão da aplicação. Esse tipo de log existe em milhares de aplicações — e não ajuda absolutamente ninguém.

O problema não é falta de log. É falta de contexto, falta de estrutura e falta de controle. Quando a aplicação precisa de mais detalhes, você precisa alterar a configuração, fazer deploy, esperar o restart — e torcer para capturar o erro antes de reverter.

Neste artigo, vou mostrar uma estratégia completa de logging para .NET 8 que resolve esses três problemas de uma vez:

  • Estruturado: cada entrada de log carrega propriedades tipadas, não apenas texto plano
  • Contextual: toda linha de log inclui automaticamente informações da aplicação, do host e do request
  • Dinâmico: o nível de log pode ser alterado em produção com um timer de reversão automática — sem deploy, sem restart

Tudo isso usando ILogger<T> nativo, sem criar wrappers desnecessários, com compliance com SonarQube e integrado ao Azure Application Insights.

A implementação completa está disponível no repositório blog-zocateli-sample no GitHub. Ao longo do artigo, vou mostrar os trechos-chave e explicar o raciocínio por trás de cada decisão. Se quiser ver o contexto completo em qualquer momento, consulte os fontes no repositório.

📦 Código-fonte: A implementação completa deste artigo está no repositório blog-zocateli-sample no GitHub. Clone, explore e adapte ao seu contexto.

💡 Este artigo complementa o Pipeline de Configuração do .NET 8+, que explica como o sistema de configuração, IOptions<T> e secrets funcionam. A estratégia de logging descrita aqui depende diretamente desses conceitos.


O Problema: Por Que Seus Logs Não Ajudam

Considere esses dois cenários reais de log em produção:

Antes — log sem contexto:

1
2
3
info: Pedido processado
warn: Falha ao validar item
error: Erro ao processar pedido

Um analista de suporte olha para isso e não consegue responder nenhuma pergunta: qual pedido? De qual cliente? Em qual instância? Era a versão nova ou a antiga? Esse erro reproduz ou foi pontual?

Depois — log estruturado com contexto:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
{
  "Timestamp": "2026-03-07T14:32:18.123Z",
  "Level": "Error",
  "Message": "Failed to process order 3fa85f64-5717-4562-b3fc-2c963f66afa6 for customer C-1234",
  "OrderId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "CustomerId": "C-1234",
  "ApplicationName": "SampleApi",
  "ApplicationVersion": "1.0.0",
  "HostName": "pod-api-7b4f9c",
  "Environment": "Production",
  "CorrelationId": "0HN6KQ2JH2S5E:00000001",
  "EventId": 2004
}

Com esse segundo formato, o analista pode:

  • Filtrar por OrderId para ver tudo que aconteceu com aquele pedido
  • Agrupar por HostName para identificar se o problema é de uma instância específica
  • Usar o CorrelationId para rastrear todo o pipeline de um único request
  • Comparar ApplicationVersion para saber se o bug é da versão nova

O custo de troubleshooting cai drasticamente. Em vez de horas garimpando logs genéricos, você encontra a causa em minutos com uma query no Application Insights.

💡 Um log sem contexto em produção é como procurar um erro no Google sem digitar a mensagem de erro — tecnicamente você está pesquisando, mas não vai encontrar nada útil.


Logging Estruturado: O Que É e Por Que Importa

Logging estruturado significa que cada entrada de log carrega propriedades tipadas e indexáveis, não apenas uma string concatenada.

No .NET, a diferença é sutil no código mas enorme no resultado:

1
2
3
4
5
// ✅ Estruturado — "OrderId" vira uma propriedade separada
_logger.LogInformation("Order {OrderId} created for customer {CustomerId}", orderId, customerId);

// ❌ Interpolação — perde as propriedades, vira texto plano
_logger.LogInformation($"Order {orderId} created for customer {customerId}");

Quando você usa o template com {OrderId}, o ILogger preserva o valor como uma propriedade separada. No Application Insights, ela aparece em customDimensions e pode ser filtrada, agregada e correlacionada com queries KQL. Quando você usa string interpolation com $, o valor é embutido na mensagem — o Application Insights recebe apenas texto e não consegue indexar.

As consequências práticas são diretas:

  • Com propriedades estruturadas: customDimensions["OrderId"] == "3fa85f64..." — filtrável
  • Com string interpolada: precisa fazer contains ou regex na mensagem — lento e impreciso

Além da questão funcional, o SonarQube marca string interpolation em logs como regra S6664 (Log message template should be compile-time constant), já que pode haver impacto de performance e perde-se a capacidade de agrupar mensagens semelhantes.

⚠️ Nunca use $"Pedido {orderId}" em chamadas de log. Isso cria uma nova string a cada chamada (mesmo quando o nível de log está desabilitado) e perde a propriedade estruturada. Use sempre o template: "Pedido {OrderId}".


Enriquecimento Automático: Contexto da Aplicação em Todo Log

Se cada desenvolvedor precisa lembrar de incluir ApplicationName, HostName e CorrelationId em cada chamada de log, isso não vai acontecer. A solução é enriquecer automaticamente.

Middleware com ILogger.BeginScope()

O ILogger suporta escopos — um bloco que adiciona propriedades a todas as entradas de log feitas dentro dele. Combinando isso com um middleware, garantimos que todo log de qualquer request carrega o contexto da aplicação:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public sealed class LogEnrichmentMiddleware : IMiddleware
{
    private readonly IOptions<LoggingOptions> _options;
    private readonly IHostEnvironment _environment;

    public LogEnrichmentMiddleware(IOptions<LoggingOptions> options, IHostEnvironment environment)
    {
        _options = options;
        _environment = environment;
    }

    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        var logger = context.RequestServices
            .GetRequiredService<ILogger<LogEnrichmentMiddleware>>();

        var enrichmentData = new Dictionary<string, object>
        {
            ["ApplicationName"] = _options.Value.ApplicationName,
            ["ApplicationVersion"] = _options.Value.Version,
            ["HostName"] = Environment.MachineName,
            ["Environment"] = _environment.EnvironmentName,
            ["CorrelationId"] = context.TraceIdentifier
        };

        using (logger.BeginScope(enrichmentData))
        {
            await next(context);
        }
    }
}

O middleware é registrado como singleton (por implementar IMiddleware) e ativado no pipeline:

1
2
3
builder.Services.AddSingleton<LogEnrichmentMiddleware>();
// ...
app.UseMiddleware<LogEnrichmentMiddleware>();

As propriedades de ApplicationName e Version vêm de uma seção dedicada do appsettings.json:

1
2
3
4
5
6
7
8
{
  "Logging": {
    "Application": {
      "ApplicationName": "SampleApi",
      "Version": "1.0.0"
    }
  }
}

ℹ️ A classe LoggingOptions é configurada via IOptions<T> pattern. Se você quer entender como esse mecanismo funciona em profundidade, veja o artigo sobre Pipeline de Configuração do .NET 8+.

TelemetryInitializer para Application Insights

O BeginScope() enriquece os logs do ILogger, mas a telemetria que o Application Insights captura automaticamente (requests HTTP, dependências, exceptions) não passa pelo ILogger. Para garantir que toda telemetria carregue as mesmas propriedades, usamos um ITelemetryInitializer:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public sealed class ApplicationTelemetryInitializer : ITelemetryInitializer
{
    private readonly string _applicationName;
    private readonly string _version;
    private readonly string _hostName;
    private readonly string _environment;

    public ApplicationTelemetryInitializer(
        IOptions<LoggingOptions> options, IHostEnvironment env)
    {
        _applicationName = options.Value.ApplicationName;
        _version = options.Value.Version;
        _hostName = Environment.MachineName;
        _environment = env.EnvironmentName;
    }

    public void Initialize(ITelemetry telemetry)
    {
        telemetry.Context.GlobalProperties.TryAdd("ApplicationName", _applicationName);
        telemetry.Context.GlobalProperties.TryAdd("ApplicationVersion", _version);
        telemetry.Context.GlobalProperties.TryAdd("HostName", _hostName);
        telemetry.Context.GlobalProperties.TryAdd("Environment", _environment);
        telemetry.Context.Component.Version = _version;
    }
}

Registrado no DI como:

1
builder.Services.AddSingleton<ITelemetryInitializer, ApplicationTelemetryInitializer>();

Com essas duas peças, você tem cobertura completa: LogEnrichmentMiddleware para logs do ILogger e ApplicationTelemetryInitializer para request traces, dependency calls e exceptions capturadas automaticamente pelo SDK.


Nível de Log Dinâmico com Timer Automático

Cenário real: sua aplicação em produção está com um comportamento estranho. Você precisa ver logs de nível Debug para entender o que está acontecendo. Mas o appsettings.json em produção está configurado com Information.

O caminho tradicional seria: alterar a configuração, fazer deploy (ou restart), capturar os logs detalhados, e depois desfazer tudo para não afogar o Application Insights em volume (e custo). Esse processo pode levar de minutos a horas dependendo do seu pipeline.

A solução proposta aqui é um endpoint administrativo que altera o nível de log em runtime, com timer de reversão automática:

1
2
3
4
5
POST /api/admin/log-level
{
  "level": "Debug",
  "durationMinutes": 15
}

Após 15 minutos, o nível volta automaticamente ao configurado no appsettings.json. Sem deploy. Sem restart. Sem risco de esquecer o Debug ligado.

O Mecanismo: ConfigurationProvider Customizado

O sistema de logging do .NET lê os níveis de log do pipeline de configuração. A chave que define o nível padrão é Logging:LogLevel:Default, e por categoria fica Logging:LogLevel:{CategoryName}.

A estratégia é criar um ConfigurationProvider customizado que pode ser atualizado em runtime e que, quando alterado, dispara OnReload() para notificar o framework:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public sealed class DynamicLogLevelConfigurationProvider : ConfigurationProvider
{
    public override void Load()
    {
        // Starts empty — values are set dynamically via Update()
    }

    public void Update(string key, string value)
    {
        if (value is null)
        {
            Data.Remove(key);
        }
        else
        {
            Data[key] = value;
        }

        OnReload();
    }

    public void Clear()
    {
        Data.Clear();
        OnReload();
    }
}

O ponto crítico é o registro: esse provider deve ser adicionado por último no pipeline de configuração, para que seu valor sobrescreva o do appsettings.json:

1
2
3
// Em Program.cs — DEPOIS de CreateBuilder (que já adiciona appsettings)
((IConfigurationBuilder)builder.Configuration)
    .Add(new DynamicLogLevelConfigurationSource());

O Serviço com Timer

O DynamicLogLevelService orquestra a lógica de alteração e reversão automática:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public sealed class DynamicLogLevelService : IDisposable
{
    private readonly DynamicLogLevelConfigurationProvider _provider;
    private readonly ILogger<DynamicLogLevelService> _logger;
    private readonly object _lock = new();
    private Timer _revertTimer;
    private LogLevelOverride _currentOverride;

    public DynamicLogLevelService(
        IConfiguration configuration,
        ILogger<DynamicLogLevelService> logger)
    {
        _logger = logger;
        var root = configuration as IConfigurationRoot
            ?? throw new InvalidOperationException("...");
        _provider = root.Providers
            .OfType<DynamicLogLevelConfigurationProvider>()
            .FirstOrDefault()
            ?? throw new InvalidOperationException("...");
    }

    public void SetLogLevel(
        string category, LogLevel level, TimeSpan duration)
    {
        ArgumentOutOfRangeException
            .ThrowIfLessThanOrEqual(duration, TimeSpan.Zero, nameof(duration));

        var key = $"Logging:LogLevel:{category ?? "Default"}";

        lock (_lock)
        {
            _revertTimer?.Dispose();
            _provider.Update(key, level.ToString());

            _currentOverride = new LogLevelOverride(
                Category: category,
                Level: level,
                ExpiresAt: DateTimeOffset.UtcNow.Add(duration));

            _revertTimer = new Timer(
                callback: _ => RevertToDefault(),
                state: null,
                dueTime: duration,
                period: Timeout.InfiniteTimeSpan);
        }
    }

    public void RevertToDefault()
    {
        lock (_lock)
        {
            _revertTimer?.Dispose();
            _revertTimer = null;
            _provider.Clear();
            _currentOverride = null;
        }
    }
}

Pontos importantes desta implementação:

  • O Timer é criado com Timeout.InfiniteTimeSpan como period — ele executa uma única vez após o dueTime
  • Se SetLogLevel for chamado novamente antes do timer expirar, o timer anterior é descartado e um novo é criado
  • O lock garante thread-safety entre o timer (que executa em uma thread do ThreadPool) e chamadas simultâneas
  • IDisposable é implementado para cleanup correto do Timer

💡 Proteja esse endpoint com autorização adequada — em produção, apenas administradores devem alterar o nível de log. Veja o artigo sobre Autenticação e Autorização com JWT, OAuth2 e OpenID para estratégias de proteção.


ILogger Nativo: Sem Reinventar a Roda

Um padrão que eu vejo com frequência preocupante é a criação de um ICustomLogger ou LogService que encapsula o ILogger<T>:

1
2
3
4
5
6
// ❌ Anti-pattern — NÃO faça isso
public class CustomLogger
{
    private readonly ILogger<CustomLogger> _logger;
    public void LogInfo(string message) => _logger.LogInformation(message);
}

Esse wrapper quebra diversos mecanismos:

  • A categoria do log vira sempre CustomLogger em vez da classe real que originou o log
  • Filtros por namespace (Logging:LogLevel:MeuNamespace) deixam de funcionar
  • A rastreabilidade no Application Insights é prejudicada
  • Testes ficam mais complexos (precisa mockar o wrapper e não o ILogger)

A abordagem correta no .NET moderno é usar ILogger<T> diretamente e o atributo [LoggerMessage] para gerar os métodos de log via source generator:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static partial class LogMessages
{
    [LoggerMessage(
        EventId = 2000,
        Level = LogLevel.Information,
        Message = "Order {OrderId} created for customer {CustomerId}, total: {Total}")]
    public static partial void OrderCreated(
        ILogger logger, Guid orderId, string customerId, decimal total);

    [LoggerMessage(
        EventId = 2003,
        Level = LogLevel.Debug,
        Message = "Processing order {OrderId}: validating {ItemCount} items for customer {CustomerId}")]
    public static partial void OrderProcessingDebug(
        ILogger logger, Guid orderId, int itemCount, string customerId);

    [LoggerMessage(
        EventId = 2004,
        Level = LogLevel.Error,
        Message = "Failed to process order {OrderId} for customer {CustomerId}")]
    public static partial void OrderFailed(
        ILogger logger, Exception exception, Guid orderId, string customerId);
}

Benefícios do [LoggerMessage] Source Generator

  1. Performance: o source generator cria código que verifica o nível de log antes de montar a mensagem — zero alocação quando o log está desabilitado
  2. AOT-friendly: compatível com Native AOT, sem reflection em runtime
  3. SonarQube compliance: resolve as regras S6664 (template compile-time constant), S2629 (logging guard) e S6667 (structured logging)
  4. EventId: cada mensagem tem um ID numérico fixo, facilitando alertas e dashboards
  5. Testável: como são métodos estáticos com ILogger como parâmetro, é trivial passar um mock nos testes

O uso é direto em qualquer endpoint ou serviço:

1
LogMessages.OrderCreated(logger, newOrder.Id, newOrder.CustomerId, newOrder.Total);

⚠️ Criar um ICustomLogger que encapsula ILogger<T> adiciona uma camada sem valor, quebra a rastreabilidade e dificulta os testes. Use ILogger<T> diretamente com [LoggerMessage] — ele é a abstração correta.


Integração com Azure Application Insights

O Application Insights é o APM (Application Performance Management) do Azure que captura automaticamente requests HTTP, dependências (banco de dados, HTTP clients, filas) e exceptions. O logging estruturado que implementamos complementa essa telemetria automática com o contexto de negócio.

Setup

A configuração é mínima — uma linha no Program.cs e a connection string na configuração:

1
2
builder.Services.AddApplicationInsightsTelemetry();
builder.Services.AddSingleton<ITelemetryInitializer, ApplicationTelemetryInitializer>();
1
2
3
4
5
{
  "ApplicationInsights": {
    "ConnectionString": "InstrumentationKey=..."
  }
}

⚠️ Nunca coloque a connection string diretamente no código-fonte ou no appsettings.json commitado. Use variáveis de ambiente, Azure Key Vault ou User Secrets para desenvolvimento local. Veja como configurar no artigo Pipeline de Configuração do .NET 8+.

Como o Log Estruturado Aparece no Application Insights

Quando você faz um log usando [LoggerMessage] com propriedades estruturadas, cada propriedade aparece em customDimensions no Application Insights. Combinando com as propriedades injetadas pelo LogEnrichmentMiddleware e pelo ApplicationTelemetryInitializer, o resultado é:

ColunaValor
messageOrder 3fa85f64… created for customer C-1234, total: 150.00
customDimensions.OrderId3fa85f64-5717-4562-b3fc-2c963f66afa6
customDimensions.CustomerIdC-1234
customDimensions.Total150.00
customDimensions.ApplicationNameSampleApi
customDimensions.ApplicationVersion1.0.0
customDimensions.HostNamepod-api-7b4f9c
customDimensions.CorrelationId0HN6KQ2JH2S5E:00000001

Query KQL para Troubleshooting

Com essas propriedades indexadas, queries no Application Insights se tornam cirúrgicas:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// Todos os logs de um pedido específico
traces
| where customDimensions["OrderId"] == "3fa85f64-5717-4562-b3fc-2c963f66afa6"
| project timestamp, message, severityLevel,
          tostring(customDimensions["ApplicationVersion"]),
          tostring(customDimensions["HostName"])
| order by timestamp asc

// Erros agrupados por host (detecta instância problemática)
traces
| where severityLevel >= 3
| summarize ErrorCount = count() by tostring(customDimensions["HostName"])
| order by ErrorCount desc

// Correlacionar logs com o request HTTP
traces
| where customDimensions["CorrelationId"] == "0HN6KQ2JH2S5E:00000001"
| union requests
| where operation_Id == "0HN6KQ2JH2S5E:00000001"
| order by timestamp asc

ℹ️ O Application Insights já captura automaticamente requests HTTP, dependências e exceptions — o log estruturado complementa com o contexto de negócio que só a sua aplicação conhece.


A Aplicação de Exemplo

A aplicação de exemplo implementa todos os conceitos deste artigo em uma Minimal API .NET 8. A estrutura do projeto organiza os componentes de logging em uma pasta dedicada:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
dotnet-structured-logging-sample/
├── src/SampleApi/
│   ├── Program.cs                  # Composição: DI, middleware, endpoints
│   ├── appsettings.json            # Configuração com seção Logging:Application
│   ├── Logging/
│   │   ├── DynamicLogLevelConfigurationSource.cs
│   │   ├── DynamicLogLevelConfigurationProvider.cs
│   │   ├── DynamicLogLevelService.cs
│   │   ├── LogEnrichmentMiddleware.cs
│   │   ├── ApplicationTelemetryInitializer.cs
│   │   ├── LoggingOptions.cs
│   │   └── LogMessages.cs          # [LoggerMessage] source generator
│   ├── Endpoints/
│   │   ├── LogLevelEndpoints.cs    # GET/POST/DELETE /api/admin/log-level
│   │   └── OrderEndpoints.cs       # POST/GET /api/orders (exemplo de negócio)
│   └── Models/
│       ├── SetLogLevelRequest.cs
│       └── Order.cs
├── tests/SampleApi.Tests/          # 13 testes unitários com xUnit e NSubstitute
├── README.md
├── global.json                     # Pinado no SDK 8.0.418
└── SampleApi.sln

Diagrama de arquitetura mostrando o fluxo do request pelo middleware de enriquecimento, passando pelo ILogger com LoggerMessage source generator, e chegando ao Application Insights com customDimensions

O diagrama acima mostra como os componentes se conectam: cada request HTTP passa pelo LogEnrichmentMiddleware que abre um BeginScope() com as propriedades de contexto. Qualquer chamada de log feita dentro daquele request — seja nos endpoints, serviços ou em bibliotecas internas — herda automaticamente essas propriedades. Em paralelo, o ApplicationTelemetryInitializer garante que a telemetria automática do Application Insights (requests, dependencies) também carregue as mesmas informações.

Como Rodar Localmente

1
2
cd src/SampleApi
dotnet run

Testar o endpoint de log dinâmico:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Ativar Debug por 15 minutos
curl -X POST http://localhost:5000/api/admin/log-level \
  -H "Content-Type: application/json" \
  -d '{"level": "Debug", "durationMinutes": 15}'

# Verificar status atual
curl http://localhost:5000/api/admin/log-level

# Reverter manualmente ao padrão
curl -X DELETE http://localhost:5000/api/admin/log-level

O repositório completo com instruções detalhadas está em github.com/lzocateli/blog-zocateli-sample — Logging.


Dicas e Boas Práticas

Aqui estão as práticas que considero essenciais para uma estratégia de logging robusta:

  1. Use categorias de log com critério: o .NET usa o namespace da classe como categoria (ILogger<OrderService> gera categoria SampleApi.Endpoints.OrderService). Isso permite filtrar por namespace no appsettings.json — por exemplo, "SampleApi.Endpoints": "Debug" ativa debug apenas nos endpoints sem afetar o resto da aplicação.

  2. Nunca logue dados sensíveis: nomes, CPFs, tokens, senhas e dados financeiros não devem aparecer em logs. Além do risco de segurança, a LGPD exige proteção de dados pessoais. Se precisar rastrear um usuário, use um identificador opaco como CustomerId.

  3. Controle o custo do Application Insights: em alto volume, o Application Insights cobra por ingestão de dados. Use sampling adaptativo para reduzir volume sem perder visibilidade. A configuração padrão do SDK já inclui sampling — evite desabilitá-lo sem cálculo de custo.

  4. EventId consistente por mensagem: cada [LoggerMessage] deve ter um EventId único e fixo. Isso permite criar alertas no Azure Monitor baseados no EventId (mais confiável que regex na mensagem) e facilita dashboards de operação.

  5. Ambiente de desenvolvimento com JSON console: configure appsettings.Development.json para usar o JsonConsoleFormatter com IncludeScopes: true. Isso mostra as propriedades estruturadas direto no terminal durante o desenvolvimento, sem precisar do Application Insights.

  6. Teste os logs: com [LoggerMessage] e ILogger<T>, é trivial verificar nos testes unitários se os logs corretos foram emitidos. A aplicação de exemplo inclui testes do middleware que verificam as propriedades injetadas no scope.

  7. Log de operações administrativas como Warning: alterações de nível de log, reinicializações e operações manuais devem ser logados como Warning — visíveis por padrão e fáceis de auditar.


Conclusão

Logging eficiente não é sobre quantidade — é sobre qualidade e contexto. Neste artigo, cobrimos três pilares que, juntos, transformam logs de ruído em ferramenta de diagnóstico:

  • Estrutura: propriedades tipadas com [LoggerMessage] source generator em vez de strings concatenadas
  • Contexto automático: LogEnrichmentMiddleware com BeginScope() e ApplicationTelemetryInitializer garantem que toda entrada de log carrega as informações da aplicação, do host e do request
  • Controle dinâmico: ConfigurationProvider customizado com OnReload() e timer permite ativar Debug em produção de forma segura e temporária

Tudo isso funciona com o ILogger<T> nativo do .NET — sem wrappers, sem abstrações inventadas, sem quebrar SonarQube. O Application Insights recebe as propriedades em customDimensions, permitindo queries KQL cirúrgicas para troubleshooting.

Se sua aplicação ainda usa _logger.LogInformation($"Processando pedido {id}"), agora é um bom momento para evoluir. Clone o repositório de exemplo, adapte para o seu contexto e veja a diferença na próxima vez que precisar investigar um problema em produção.

Para trabalhar com logs em serviços background de longa duração, veja como aplicar essa mesma estratégia em .NET Worker e Background Service.


Leia Também


Referências