ILogger no .NET | CSharp
- ILogger no .NET | CSharp
Visão geral
O ILogger é a abstração padrão de logging do ecossistema moderno do .NET. Ele permite gerar logs estruturados, aplicar filtros por categoria e nível, trocar destinos de saída sem mudar o código de negócio e integrar logging com DI, tracing e observabilidade. No ASP.NET Core, o pipeline de logging é baseado em ILogger, ILoggerFactory e providers, com suporte nativo a logging estruturado e de alto desempenho. (Microsoft Learn)
Arquitetura simplificada:
Código da aplicação
↓
ILogger / ILogger<T>
↓
ILoggerFactory
↓
ILoggerProvider
↓
Console / Debug / EventSource / EventLog / OpenTelemetry / terceiros
Pacotes e namespace
Na maior parte dos casos, você usa:
using Microsoft.Extensions.Logging;
Para os tipos no-op, como NullLogger, NullLoggerFactory e NullLoggerProvider, também é comum usar:
using Microsoft.Extensions.Logging.Abstractions;
O ecossistema de logging do .NET fica em torno de Microsoft.Extensions.Logging e Microsoft.Extensions.Logging.Abstractions. (Microsoft Learn)
Conceitos principais
ILogger
É a interface usada para escrever logs.
Exemplo:
public class PedidoService
{
private readonly ILogger _logger;
public PedidoService(ILogger logger)
{
_logger = logger;
}
}
Na prática, ILogger puro aparece menos em código de aplicação.
ILogger<T>
É a forma mais comum de uso, porque já define a categoria com base no tipo.
public class PedidoService
{
private readonly ILogger<PedidoService> _logger;
public PedidoService(ILogger<PedidoService> logger)
{
_logger = logger;
}
}
ILoggerFactory
É a fábrica usada para criar loggers dinamicamente.
public class PedidoProcessor
{
private readonly ILogger _logger;
public PedidoProcessor(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger("Pedidos.Processamento");
}
}
A orientação oficial para bibliotecas recomenda ILogger<T> quando o logger é interno de uma classe e ILoggerFactory quando a API precisa criar loggers para múltiplas classes ou categorias. (Microsoft Learn)
Categoria do logger
Todo logger possui uma categoria.
Com ILogger<T>, a categoria costuma ser o nome completo do tipo:
MinhaApp.Services.PedidoService
Com ILoggerFactory, a categoria pode ser definida manualmente:
var logger = loggerFactory.CreateLogger("Pagamentos");
A categoria é importante para:
- filtrar logs
- separar ruído de framework
- identificar a origem do log
- aplicar níveis diferentes por área do sistema
Níveis de log
Os níveis padrão são:
| Nível | Uso |
|---|---|
Trace | detalhe extremo, diagnóstico fino |
Debug | depuração e investigação |
Information | fluxo normal da aplicação |
Warning | anomalia ou situação inesperada sem quebra imediata |
Error | falha em uma operação |
Critical | falha grave, indisponibilidade ou risco sistêmico |
None | desabilita logging |
Exemplo:
_logger.LogTrace("Iniciando cálculo");
_logger.LogDebug("Valor calculado: {Valor}", valor);
_logger.LogInformation("Pedido criado");
_logger.LogWarning("Pedido demorou mais que o esperado");
_logger.LogError("Erro ao processar pedido");
_logger.LogCritical("Banco de dados indisponível");
Métodos de extensão mais usados
A interface é normalmente consumida por métodos de extensão como:
LogTraceLogDebugLogInformationLogWarningLogErrorLogCriticalLog
Exemplo genérico:
_logger.Log(
LogLevel.Information,
new EventId(1001, "PedidoCriado"),
"Pedido {PedidoId} criado com sucesso",
pedidoId);
Na prática, os métodos específicos por nível são mais legíveis.
Structured logging
O modelo recomendado é structured logging, isto é, registrar mensagem com propriedades nomeadas.
Errado:
_logger.LogInformation("Pedido " + pedidoId + " criado");
Correto:
_logger.LogInformation("Pedido {PedidoId} criado", pedidoId);
Vantagens:
- melhor indexação
- melhor consulta em observabilidade
- menos parsing de string
- melhor padronização
O ASP.NET Core e o .NET destacam explicitamente ILogger como infraestrutura de logs estruturados e de alto desempenho. (Microsoft Learn)
Message template
O texto com placeholders é o message template.
_logger.LogInformation(
"Pedido {PedidoId} processado em {ElapsedMilliseconds}ms para o cliente {ClienteId}",
pedidoId,
elapsedMs,
clienteId);
Boas práticas para nomes:
- use nomes estáveis
- prefira nomes claros
- mantenha consistência entre serviços
- evite nomes genéricos demais
Bom:
{PedidoId}
{ClienteId}
{CorrelationId}
{TraceId}
{ElapsedMilliseconds}
Ruim:
{Id}
{Valor}
{Tmp}
Logging de exceções
Sempre que houver exceção, passe a exceção como argumento próprio.
Correto:
catch (Exception ex)
{
_logger.LogError(ex, "Erro ao processar pedido {PedidoId}", pedidoId);
}
Errado:
catch (Exception ex)
{
_logger.LogError("Erro: {Mensagem}", ex.Message);
}
No formato correto, o provider consegue preservar stack trace, tipo da exceção e contexto.
EventId
EventId é útil para padronizar eventos relevantes.
private static readonly EventId PedidoCriadoEvent =
new(1001, nameof(PedidoCriadoEvent));
_logger.LogInformation(PedidoCriadoEvent, "Pedido {PedidoId} criado", pedidoId);
Exemplo com enum:
public enum PedidoLogEvents
{
PedidoCriado = 1001,
PedidoCancelado = 1002,
PagamentoFalhou = 1003
}
Uso:
_logger.LogWarning(
new EventId((int)PedidoLogEvents.PagamentoFalhou, nameof(PedidoLogEvents.PagamentoFalhou)),
"Falha no pagamento do pedido {PedidoId}",
pedidoId);
Scopes
BeginScope() cria um escopo lógico que enriquece todos os logs emitidos dentro daquele bloco.
Scope com template
using (_logger.BeginScope("PedidoId: {PedidoId}", pedidoId))
{
_logger.LogInformation("Iniciando processamento");
_logger.LogInformation("Processando pagamento");
_logger.LogInformation("Finalizando processamento");
}
Scope com dicionário
using (_logger.BeginScope(new Dictionary<string, object>
{
["PedidoId"] = pedidoId,
["ClienteId"] = clienteId,
["CorrelationId"] = correlationId
}))
{
_logger.LogInformation("Processando pedido");
}
Scopes são muito úteis para:
- correlação de logs
- rastreabilidade por operação
- propagação de contexto técnico
- observabilidade
IncludeScopes
Para que os scopes apareçam em determinados providers, é comum habilitar IncludeScopes.
Exemplo:
{
"Logging": {
"Console": {
"IncludeScopes": true
}
}
}
Isso é especialmente útil no console e em pipelines de observabilidade. A documentação oficial do ASP.NET Core expõe a configuração de providers e opções como IncludeScopes. (Microsoft Learn)
Activity e correlação com tracing
O ecossistema .NET integra logging com System.Diagnostics.Activity, permitindo correlação com tracing distribuído.
Os campos mais comuns são:
TraceIdSpanIdParentIdTagsBaggage
Configuração:
builder.Logging.Configure(options =>
{
options.ActivityTrackingOptions =
ActivityTrackingOptions.TraceId |
ActivityTrackingOptions.SpanId |
ActivityTrackingOptions.ParentId |
ActivityTrackingOptions.Tags |
ActivityTrackingOptions.Baggage;
});
Isso é importante para:
- correlação entre logs e traces
- depuração de fluxos distribuídos
- integração com OpenTelemetry
- observabilidade em microserviços
A documentação oficial de logging do ASP.NET Core trata ActivityTrackingOptions como parte da configuração do logging. (Microsoft Learn)
IsEnabled
IsEnabled permite verificar se um nível está ativo antes de montar contexto caro.
if (_logger.IsEnabled(LogLevel.Debug))
{
var diagnostico = MontarDiagnosticoPesado();
_logger.LogDebug("Diagnóstico: {@Diagnostico}", diagnostico);
}
Use isso quando:
- a montagem do log custa CPU
- envolve serialização
- depende de leitura cara de estado
- ocorre em hot path
Filtros de logging
O pipeline filtra logs antes de enviá-los para os providers.
Você pode filtrar por:
- nível
- categoria
- provider
- combinação de provider + categoria
Filtro global via appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Warning",
"MinhaApp": "Information",
"MinhaApp.Services.Pagamentos": "Debug",
"Microsoft": "Warning",
"Microsoft.AspNetCore": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
Leitura prática:
- tudo começa em
Warning - sua app sobe para
Information - uma área específica sobe para
Debug - logs do framework ficam mais silenciosos
A filtragem por categoria é parte central da configuração oficial do ASP.NET Core. (Microsoft Learn)
Filtro por provider via appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Warning"
},
"Console": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning"
},
"IncludeScopes": true
},
"Debug": {
"LogLevel": {
"Default": "Debug"
}
},
"EventSource": {
"LogLevel": {
"Default": "Warning"
}
}
}
}
Isso é útil quando você quer mais detalhe em um destino e menos em outro.
Filtro em código
Por categoria
builder.Logging.AddFilter("Microsoft", LogLevel.Warning);
builder.Logging.AddFilter("MinhaApp.Services", LogLevel.Information);
Por provider e categoria
builder.Logging.AddFilter<ConsoleLoggerProvider>("MinhaApp", LogLevel.Debug);
builder.Logging.AddFilter<DebugLoggerProvider>("Microsoft", LogLevel.Warning);
Filtro customizado
builder.Logging.AddFilter((provider, category, level) =>
{
if (provider.Contains("Console") && category.StartsWith("MinhaApp.Pagamentos"))
return level >= LogLevel.Debug;
if (category.StartsWith("Microsoft"))
return level >= LogLevel.Warning;
return level >= LogLevel.Information;
});
Providers
ILogger é abstração. Quem envia o log para um destino concreto é o provider.
O ASP.NET Core tem providers embutidos e também suporta providers de terceiros. Os providers built-in listados na documentação incluem Console, Debug, EventSource e EventLog no Windows. (Microsoft Learn)
Console provider
O provider de console escreve logs em stdout/stderr.
Uso:
builder.Logging.AddConsole();
É o provider mais comum em:
- desenvolvimento
- Docker
- Kubernetes
- ambientes com coleta por stdout
Exemplo de configuração:
{
"Logging": {
"Console": {
"IncludeScopes": true
},
"LogLevel": {
"Default": "Information"
}
}
}
Debug provider
Escreve logs para a saída de debug.
builder.Logging.AddDebug();
É útil principalmente em ambiente de desenvolvimento e debugging local.
EventSource provider
Escreve logs via EventSource, integrando com ferramentas de diagnóstico do ecossistema .NET.
builder.Logging.AddEventSourceLogger();
É importante em cenários com:
dotnet-tracedotnet-monitor- coleta diagnóstica
- troubleshooting em produção
EventLog provider
No Windows, é possível usar o Event Log do sistema operacional.
builder.Logging.AddEventLog();
Esse provider faz mais sentido em cenários Windows tradicionais.
Providers de terceiros
Alguns providers bastante comuns:
- Serilog
- NLog
- Seq
- Elasticsearch
- Azure Application Insights
- OpenTelemetry
A plataforma permite múltiplos providers simultaneamente. (Microsoft Learn)
Configuração padrão do ASP.NET Core
Ao criar a aplicação com WebApplication.CreateBuilder(args), o ASP.NET Core já configura logging com providers padrão do framework. A documentação oficial lista os providers nativos disponíveis e o pipeline padrão pode ser customizado usando ClearProviders(). (Microsoft Learn)
Exemplo:
var builder = WebApplication.CreateBuilder(args);
builder.Logging.ClearProviders();
builder.Logging.AddConsole();
builder.Logging.AddDebug();
builder.Logging.AddEventSourceLogger();
ClearProviders
ClearProviders() remove os providers previamente configurados.
builder.Logging.ClearProviders();
Use quando quiser:
- controle total do pipeline
- reduzir ruído
- configurar apenas destinos específicos
- substituir o pipeline padrão
Logging com DI
A forma mais comum é injetar ILogger<T> no construtor.
public class PagamentoService
{
private readonly ILogger<PagamentoService> _logger;
public PagamentoService(ILogger<PagamentoService> logger)
{
_logger = logger;
}
}
Em aplicações ASP.NET Core, isso funciona naturalmente com o container padrão.
Logging em Minimal APIs
app.MapPost("/pedidos", (CriarPedidoRequest request, ILoggerFactory loggerFactory) =>
{
var logger = loggerFactory.CreateLogger("PedidosEndpoint");
logger.LogInformation("Criando pedido para o cliente {ClienteId}", request.ClienteId);
return Results.Accepted();
});
Logging em middleware
app.Use(async (context, next) =>
{
var logger = context.RequestServices
.GetRequiredService<ILoggerFactory>()
.CreateLogger("RequestMiddleware");
logger.LogInformation(
"Recebendo {Method} {Path}",
context.Request.Method,
context.Request.Path);
await next();
logger.LogInformation(
"Resposta concluída com status {StatusCode}",
context.Response.StatusCode);
});
Logging em background services
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
public Worker(ILogger<Worker> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Worker executando em {Time}", DateTimeOffset.Now);
await Task.Delay(1000, stoppingToken);
}
}
}
High-performance logging
A stack oficial de logging do ASP.NET Core foi desenhada para alto desempenho, mas ainda assim existem práticas melhores para cenários de alto volume. A orientação oficial para bibliotecas recomenda preferir logging gerado por source generator e evitar interpolação de string em logs. (Microsoft Learn)
Evite interpolação e concatenação
Ruim:
_logger.LogInformation($"Pedido {pedidoId} criado");
_logger.LogInformation("Pedido " + pedidoId + " criado");
Melhor:
_logger.LogInformation("Pedido {PedidoId} criado", pedidoId);
LoggerMessage
Para hot paths, o modelo recomendado é LoggerMessage.
Exemplo com source generator
public static partial class PedidoLogs
{
[LoggerMessage(
EventId = 1001,
Level = LogLevel.Information,
Message = "Pedido {PedidoId} criado com sucesso")]
public static partial void PedidoCriado(ILogger logger, Guid pedidoId);
}
Uso:
PedidoLogs.PedidoCriado(_logger, pedidoId);
Vantagens:
- menos alocação
- menor overhead
- melhor performance em alto volume
- mais consistência
A orientação oficial de bibliotecas do .NET recomenda preferir logs gerados por source generator em vez de abordagens menos eficientes. (Microsoft Learn)
Quando usar LoggerMessage
Use quando:
- o ponto é hot path
- a aplicação tem altíssimo volume
- você precisa controlar alocação
- a latência importa muito
Não é obrigatório para todo log. Em muitos casos, LogInformation normal é suficiente.
NullLogger
NullLogger é um logger no-op, ou seja, não registra nada.
ILogger logger = NullLogger.Instance;
Versão tipada:
ILogger<PagamentoService> logger = NullLogger<PagamentoService>.Instance;
Ele é útil quando você precisa fornecer um ILogger, mas não quer emitir logs reais. A orientação oficial para autores de bibliotecas lista NullLogger.Instance e NullLogger<T> como defaults no-op suportados pela plataforma. (Microsoft Learn)
NullLoggerFactory
NullLoggerFactory é uma fábrica que cria NullLogger.
ILoggerFactory loggerFactory = NullLoggerFactory.Instance;
ILogger logger = loggerFactory.CreateLogger("QualquerCategoria");
É útil quando:
- sua API exige
ILoggerFactory - você quer fallback sem
null - precisa de no-op logging
- está em testes simples
A documentação da namespace Microsoft.Extensions.Logging.Abstractions descreve NullLoggerFactory como uma ILoggerFactory que cria NullLogger. (Microsoft Learn)
NullLoggerProvider
NullLoggerProvider é o provider no-op do ecossistema.
ILoggerProvider provider = NullLoggerProvider.Instance;
ILogger logger = provider.CreateLogger("Pagamentos");
Ele implementa ILoggerProvider, mas gera loggers que não escrevem nada.
Use quando:
- você está no nível de provider
- quer pipeline no-op
- precisa compor infraestrutura sem saída real
- quer evitar dependência em provider real
A orientação oficial de bibliotecas lista explicitamente NullLoggerProvider.Instance como um dos defaults no-op suportados. A documentação da namespace também o descreve como provider do NullLogger. (Microsoft Learn)
Diferença entre NullLogger, NullLoggerFactory e NullLoggerProvider
| Tipo | Implementa | Uso comum |
|---|---|---|
NullLogger | ILogger | quando precisa de um logger único sem saída |
NullLogger<T> | ILogger<T> | mesmo caso, mas com categoria tipada |
NullLoggerFactory | ILoggerFactory | quando precisa criar loggers dinamicamente |
NullLoggerProvider | ILoggerProvider | quando está configurando providers ou infraestrutura |
Exemplo prático com fallback para NullLogger
public class PagamentoService
{
private readonly ILogger<PagamentoService> _logger;
public PagamentoService(ILogger<PagamentoService>? logger = null)
{
_logger = logger ?? NullLogger<PagamentoService>.Instance;
}
public void Processar(Guid pedidoId)
{
_logger.LogInformation("Processando pagamento do pedido {PedidoId}", pedidoId);
}
}
Esse padrão pode ser útil em bibliotecas ou testes, mas em aplicação normal com DI o ideal é receber o logger do container.
Quando usar no-op logging
Use NullLogger* quando:
- o logging é opcional
- você quer evitar
null - está testando classe isolada
- quer comportamento sem saída real
Evite NullLogger* quando:
- você precisa observar comportamento real
- depende de logs para operação
- precisa correlacionar com tracing
- o problema verdadeiro é pipeline mal configurado
OpenTelemetry
OpenTelemetry é um framework aberto e agnóstico a fornecedor para geração, coleta e exportação de telemetria como logs, métricas e traces. A documentação oficial do OpenTelemetry o define dessa forma, e o .NET integra logs via provider específico para ILogger. (OpenTelemetry)
AddOpenTelemetry no logging
Exemplo básico:
using OpenTelemetry.Logs;
builder.Logging.ClearProviders();
builder.Logging.AddOpenTelemetry(options =>
{
options.IncludeScopes = true;
options.IncludeFormattedMessage = true;
options.ParseStateValues = true;
});
Opções relevantes:
IncludeScopes: inclui scopes no exportIncludeFormattedMessage: inclui mensagem renderizadaParseStateValues: preserva estado estruturado
Exemplo com OTLP
using OpenTelemetry.Logs;
using OpenTelemetry.Resources;
builder.Logging.ClearProviders();
builder.Logging.AddOpenTelemetry(options =>
{
options.IncludeScopes = true;
options.IncludeFormattedMessage = true;
options.ParseStateValues = true;
options.SetResourceBuilder(
ResourceBuilder.CreateDefault()
.AddService("pedidos-api"));
options.AddOtlpExporter();
});
Esse modelo é comum quando você exporta logs para collector ou backend de observabilidade.
Exemplo completo de configuração prática
using Microsoft.Extensions.Logging;
using OpenTelemetry.Logs;
using OpenTelemetry.Resources;
var builder = WebApplication.CreateBuilder(args);
builder.Logging.ClearProviders();
builder.Logging.Configure(options =>
{
options.ActivityTrackingOptions =
ActivityTrackingOptions.TraceId |
ActivityTrackingOptions.SpanId |
ActivityTrackingOptions.ParentId |
ActivityTrackingOptions.Tags |
ActivityTrackingOptions.Baggage;
});
builder.Logging.AddConsole(options =>
{
options.IncludeScopes = true;
});
builder.Logging.AddDebug();
builder.Logging.AddEventSourceLogger();
builder.Logging.AddFilter("Microsoft", LogLevel.Warning);
builder.Logging.AddFilter("Microsoft.AspNetCore", LogLevel.Warning);
builder.Logging.AddFilter("MinhaApp", LogLevel.Information);
builder.Logging.AddFilter("MinhaApp.Pagamentos", LogLevel.Debug);
builder.Logging.AddOpenTelemetry(options =>
{
options.IncludeScopes = true;
options.IncludeFormattedMessage = true;
options.ParseStateValues = true;
options.SetResourceBuilder(
ResourceBuilder.CreateDefault()
.AddService("minha-api"));
options.AddOtlpExporter();
});
var app = builder.Build();
app.MapGet("/", (ILoggerFactory loggerFactory) =>
{
var logger = loggerFactory.CreateLogger("Startup");
using (logger.BeginScope(new Dictionary<string, object>
{
["CorrelationId"] = Guid.NewGuid()
}))
{
logger.LogInformation("Aplicação iniciada");
}
return Results.Ok();
});
app.Run();
Exemplo prático em service
public class PedidoService
{
private readonly ILogger<PedidoService> _logger;
private static readonly EventId PedidoProcessadoEvent =
new(2001, nameof(PedidoProcessadoEvent));
public PedidoService(ILogger<PedidoService> logger)
{
_logger = logger;
}
public async Task ProcessarAsync(Guid pedidoId, Guid clienteId, CancellationToken ct)
{
using (_logger.BeginScope(new Dictionary<string, object>
{
["PedidoId"] = pedidoId,
["ClienteId"] = clienteId
}))
{
_logger.LogInformation("Iniciando processamento do pedido");
try
{
await Task.Delay(100, ct);
_logger.LogInformation(
PedidoProcessadoEvent,
"Pedido {PedidoId} processado com sucesso",
pedidoId);
}
catch (OperationCanceledException)
{
_logger.LogWarning("Processamento cancelado");
throw;
}
catch (Exception ex)
{
_logger.LogError(ex, "Erro ao processar pedido {PedidoId}", pedidoId);
throw;
}
}
}
}
Configuração sugerida para Development
{
"Logging": {
"LogLevel": {
"Default": "Information",
"MinhaApp": "Debug",
"Microsoft": "Warning"
},
"Console": {
"IncludeScopes": true
}
}
}
Boa prática:
- mais detalhe da aplicação
- menos ruído do framework
- scopes visíveis
Debugsó onde fizer sentido
Configuração sugerida para Produção
{
"Logging": {
"LogLevel": {
"Default": "Warning",
"MinhaApp": "Information",
"MinhaApp.Pagamentos": "Information",
"Microsoft": "Warning",
"Microsoft.AspNetCore": "Warning"
},
"Console": {
"IncludeScopes": true
}
}
}
Boa prática:
- evitar
Debugglobal - manter a app em
Information - framework em
Warning - preservar correlação
- exportar para backend centralizado
Logging de dados sensíveis
Nem todo dado deve ir para log.
Evite registrar:
- senha
- token
- JWT completo
- segredo de configuração
- dados bancários
- PII sem critério
- payload bruto inteiro sem necessidade
Melhor:
_logger.LogInformation(
"Callback recebido. Pedido: {PedidoId}, Valor: {Valor}, PayloadSize: {PayloadSize}",
pedidoId,
valor,
payload.Length);
Erros comuns
Concatenar string
_logger.LogInformation("Pedido " + pedidoId + " criado");
Usar interpolação sem necessidade em hot path
_logger.LogInformation($"Pedido {pedidoId} criado");
Passar só ex.Message
_logger.LogError(ex.Message);
Não usar scopes em operações correlacionadas
Deixar tudo em Debug em produção
Não filtrar Microsoft.*
Logar segredo
Nomear propriedades mal
Ruim:
_logger.LogInformation("Valor {Id}", pedidoId);
Melhor:
_logger.LogInformation("Pedido {PedidoId} criado", pedidoId);
Guia rápido
| Situação | Recomendação |
|---|---|
| Logger por classe | ILogger<T> |
| Logger dinâmico | ILoggerFactory.CreateLogger() |
| Fallback sem logs | NullLogger<T> ou NullLoggerFactory |
| Provider no-op | NullLoggerProvider.Instance |
| Correlação entre logs | BeginScope() |
| Log local e containers | AddConsole() |
| Debug local | AddDebug() |
| Diagnóstico .NET | AddEventSourceLogger() |
| Windows Event Log | AddEventLog() |
| Hot path | LoggerMessage |
| Observabilidade moderna | AddOpenTelemetry() |
| Reduzir ruído do framework | filtros por categoria/provider |
Referências
- Logging no ASP.NET Core e .NET. (Microsoft Learn)
- Providers built-in do ASP.NET Core. (Microsoft Learn)
- Guidance oficial para autores de bibliotecas .NET usando
ILogger,ILoggerFactory,NullLogger,NullLoggerFactoryeNullLoggerProvider. (Microsoft Learn) - Namespace
Microsoft.Extensions.Logging.Abstractions, incluindoNullLoggerFactoryeNullLoggerProvider. (Microsoft Learn) - Definição e papel do OpenTelemetry em logs, métricas e traces. (OpenTelemetry)