Utilizando ILogger no ASP.NET
O ILogger é a abstração de logging do .NET que permite registrar informações sobre a execução, os erros e os avisos dos nossos sistemas de forma simples. Neste artigo, vamos entender como ele funciona, quais interfaces estão por trás dele e como usá-lo em uma aplicação ASP.NET Core.
O que é logging e onde o ILogger entra
Logging é o ato de escrever mensagens durante a execução do código, para que possamos acompanhar o que aconteceu depois. Em outras palavras, é o diário de bordo da aplicação.
No .NET, essa capacidade vem da biblioteca Microsoft.Extensions.Logging, que faz parte do conjunto de extensões da plataforma. Dessa forma, qualquer tipo de projeto pode usar a mesma API, seja uma API web, um worker ou um aplicativo de console.
Na prática, os logs ajudam a diagnosticar falhas em produção, medir o comportamento da aplicação e entender a sequência de eventos de uma requisição. Por isso, um bom log costuma valer mais do que horas de depuração.
Bibliotecas de logging no ecossistema .NET
Existem várias bibliotecas de logging no ecossistema, como NLog, Log4Net e Serilog, que fazem um trabalho parecido. No entanto, a abstração padrão que acompanha os templates da Microsoft é o ILogger.
A grande vantagem dessa abstração é que as bibliotecas de terceiros também se conectam a ela. Assim, o seu código depende apenas do ILogger, enquanto o destino real das mensagens muda conforme a configuração.
Se você quiser ir além dos provedores nativos, vale conhecer o artigo sobre logs no ASP.NET com Serilog. Ele mostra como enriquecer e enviar os logs para outros destinos.
Instalação do pacote de logging
A maioria dos templates já traz o logging configurado, porque o ASP.NET Core inclui as interfaces e as implementações no framework compartilhado. Portanto, em uma aplicação web você normalmente não precisa instalar nada.
Caso o seu projeto não possua a dependência, como em uma biblioteca de classes, basta adicionar o pacote Microsoft.Extensions.Logging. Segundo a documentação oficial de logging no ASP.NET Core, as interfaces ficam no pacote de abstrações e as implementações padrão ficam nesse pacote principal.
# Implementações padrão do logging
dotnet add package Microsoft.Extensions.Logging
# Apenas as interfaces, ideal para bibliotecas
dotnet add package Microsoft.Extensions.Logging.Abstractions
Esses pacotes disponibilizam o namespace Microsoft.Extensions.Logging, que contém os três itens que vamos estudar aqui. São eles o ILogger, o ILoggerFactory e o ILoggerProvider.
As interfaces por trás do ILogger
Antes de escrever a primeira mensagem, vale entender como as peças se encaixam. Afinal, o ILogger que recebemos no construtor é o resultado da colaboração entre uma fábrica e um ou mais provedores.
ILoggerFactory
O ILoggerFactory é a interface responsável por criar instâncias de loggers. Ela funciona como um facilitador para registrar provedores e acessar os loggers de forma centralizada.
Por definição, essa interface possui apenas dois métodos. O CreateLogger cria um novo logger, ou seja, uma categoria para os nossos logs, enquanto o AddProvider permite registrar outros provedores.
public interface ILoggerFactory : IDisposable
{
ILogger CreateLogger(string categoryName);
void AddProvider(ILoggerProvider provider);
}
Quando o código pede um ILogger, a fábrica seleciona as regras de filtro de cada provedor e devolve um logger que repassa as mensagens para todos eles. Dessa forma, uma única chamada pode aparecer no console e no depurador ao mesmo tempo.
ILoggerProvider
O ILoggerProvider serve para criar instâncias específicas dos loggers por categoria. Ele possui apenas um método, também chamado CreateLogger, que recebe o nome da categoria e devolve o logger correspondente.
public interface ILoggerProvider : IDisposable
{
ILogger CreateLogger(string categoryName);
}
Em resumo, a fábrica coordena e o provedor sabe escrever em um destino específico. Assim, trocar o destino dos logs significa trocar ou adicionar provedores, sem mexer no código que chama o ILogger.
Provedores nativos do ILogger
O Microsoft.Extensions.Logging já possui vários provedores nativos, também chamados de built-in. Quando a aplicação usa o WebApplication.CreateBuilder, o host adiciona automaticamente os quatro primeiros da lista abaixo.
- Console, que escreve as mensagens no terminal.
- Debug, que usa a classe
System.Diagnostics.Debuge aparece na janela de saída do depurador. - EventSource, que publica eventos para ferramentas como o
dotnet-trace. - EventLog, que grava no log de eventos do Windows.
- TraceSource, que integra o ILogger ao sistema de rastreamento clássico do .NET.
Vale notar que o ASP.NET Core não inclui um provedor para escrever logs em arquivos. Para isso, a documentação recomenda usar um provedor de terceiros.
Caso queira criar o seu próprio logger, basta implementar a interface ILoggerProvider. Mais adiante, veremos um exemplo completo dessa implementação.
Como usar o ILogger em um controller
Para usar o ILogger, precisamos de uma instância dele, e a forma mais comum de obtê-la é a interface genérica ILogger<T>. Nesse caso, T normalmente é a própria classe que vai escrever os logs.
O tipo genérico define a categoria do log, que corresponde ao nome completo da classe. Com isso, fica fácil identificar, ordenar e filtrar as mensagens de cada parte do sistema.
Recebendo o ILogger por injeção de dependência
Vamos tomar como base o exemplo abaixo, retirado do template padrão do ASP.NET Core MVC. Nele, temos uma instância do ILogger dentro do HomeController.
private readonly ILogger<HomeController> _logger;
Com o campo definido, podemos exigir a instância via construtor, usando a injeção de dependência. Então, o container do ASP.NET Core resolve o ILogger<HomeController> e entrega o objeto pronto.
public HomeController(ILogger<HomeController> logger)
=> _logger = logger;
Esse comportamento segue o princípio de inversão de controle, porque a classe não cria as próprias dependências. Por consequência, fica simples substituir o logger por um dublê nos testes.
Escrevendo a primeira mensagem
Para registrar alguma informação, basta chamar o método LogInformation. Em seguida, a mensagem aparece no console durante a execução da aplicação.
public IActionResult Index()
{
// Registra uma mensagem de nível Information
_logger.LogInformation("Inside HomeController Index");
return View();
}
Como resultado, o terminal exibe o texto abaixo durante a execução. Repare na última linha, que contém a mensagem que informamos no código.
dotnet run
Building...
info: Microsoft.Hosting.Lifetime[14]
Now listening on: https://localhost:7244
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5288
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: C:\dev\AspNetLoggerSample\
info: AspNetLoggerSample.Controllers.HomeController[0]
Inside HomeController Index
O prefixo info indica o nível, o texto seguinte mostra a categoria e o número entre colchetes é o identificador do evento. Portanto, cada linha já carrega contexto suficiente para filtrar a saída.
Criando loggers com o ILoggerFactory
Em alguns cenários, a classe genérica não resolve o problema. Por exemplo, você pode querer uma categoria com um nome livre, que não corresponda a nenhuma classe.
Nesse caso, injete o ILoggerFactory e chame o método CreateLogger com o nome desejado. O resultado continua sendo um ILogger comum, com os mesmos métodos de sempre.
public class ReportService
{
private readonly ILogger _logger;
public ReportService(ILoggerFactory loggerFactory)
{
// Categoria definida manualmente
_logger = loggerFactory.CreateLogger("Reports.Monthly");
}
public void Generate()
=> _logger.LogInformation("Monthly report generated");
}
Com isso, a categoria Reports.Monthly passa a aceitar regras próprias no appsettings.json. Ainda assim, prefira o ILogger<T> sempre que possível, porque ele evita erros de digitação no nome.
Usando o ILogger no Program.cs
Também é possível registrar mensagens diretamente no Program.cs, depois que a aplicação for construída. Para isso, o objeto WebApplication expõe a propriedade Logger.
var app = builder.Build();
// Logger da própria aplicação, disponível após o Build
app.Logger.LogInformation("Application configured, starting pipeline");
app.Run();
Esse recurso ajuda a registrar decisões tomadas na inicialização, como o ambiente ativo ou alguma configuração ausente. Contudo, lembre que ele só existe depois da chamada ao Build.
Níveis de log do ILogger
Cada mensagem tem um nível de severidade, representado pelo enum LogLevel. Além disso, existe um método de extensão para cada nível, o que deixa o código mais legível.
| Nível | Valor | Método | Uso recomendado |
|---|---|---|---|
| Trace | 0 | LogTrace | Mensagens muito detalhadas, desligadas por padrão |
| Debug | 1 | LogDebug | Informações de depuração durante o desenvolvimento |
| Information | 2 | LogInformation | Fluxo geral da aplicação |
| Warning | 3 | LogWarning | Eventos anormais que não interrompem a aplicação |
| Error | 4 | LogError | Falhas na operação ou requisição atual |
| Critical | 5 | LogCritical | Falhas que exigem atenção imediata |
| None | 6 | Nenhum | Desliga as mensagens de uma categoria |
Quando definimos um nível mínimo, o ILogger registra as mensagens daquele nível e de todos os níveis mais severos. Por exemplo, um mínimo Warning deixa passar Warning, Error e Critical.
A documentação recomenda cautela com os níveis mais baixos em produção. Afinal, Trace, Debug e Information geram muito volume e podem aumentar o custo de armazenamento.
Configurando o ILogger no appsettings.json
A configuração de logging costuma ficar na seção Logging do arquivo appsettings.json. Assim, você ajusta a verbosidade sem recompilar a aplicação.
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
Nesse exemplo, a categoria Default registra a partir de Information. Por outro lado, tudo que começa com Microsoft.AspNetCore só aparece a partir de Warning.
O ILogger aplica a regra com o prefixo de categoria mais longo que combinar com o logger. Desse modo, você pode deixar uma parte específica do sistema mais detalhada sem poluir o restante.
Também é possível configurar o nível por provedor, como em Logging:Console:LogLevel:Default. Nesse caso, a configuração do provedor sobrescreve a configuração geral.
Se preferir código, o método SetMinimumLevel define o nível padrão no Program.cs. Ainda assim, a recomendação oficial é manter essa definição na configuração.
var builder = WebApplication.CreateBuilder(args);
// Remove os provedores padrão e mantém apenas o console
builder.Logging.ClearProviders();
builder.Logging.AddConsole();
builder.Logging.SetMinimumLevel(LogLevel.Warning);
Mensagens estruturadas com o ILogger
O ILogger trabalha com templates de mensagem, que contêm marcadores nomeados entre chaves. Em vez de concatenar strings, você passa os valores como argumentos separados.
// Os valores viram campos estruturados no provedor
_logger.LogInformation("Getting order {OrderId} for customer {CustomerId}", orderId, customerId);
A ordem dos argumentos, e não o nome dos marcadores, define qual valor ocupa cada posição. Por isso, mantenha os nomes coerentes com a ordem para evitar confusão.
Essa abordagem permite o chamado log estruturado. Ou seja, o provedor recebe os valores separadamente e pode guardá-los como campos pesquisáveis.
Na prática, isso facilita consultas como "todos os logs do pedido 42" sem precisar interpretar o texto da mensagem. Além disso, evita o custo de montar a string quando o nível está desligado.
Registrando exceções
Os métodos de log possuem sobrecargas que recebem uma exceção como parâmetro. Desse jeito, o provedor consegue registrar a pilha de chamadas junto com a mensagem.
try
{
await _paymentService.ProcessAsync(orderId);
}
catch (Exception ex)
{
// A exceção vem antes do template da mensagem
_logger.LogError(ex, "Failed to process payment for order {OrderId}", orderId);
throw;
}
Repare que a exceção aparece como primeiro argumento, antes do template. Contudo, a forma como cada provedor exibe a exceção depende da implementação dele.
Agrupando mensagens com escopos
Um escopo agrupa um conjunto de operações lógicas e anexa os mesmos dados a todas as mensagens criadas dentro dele. Por exemplo, todos os logs de uma transação podem carregar o identificador dessa transação.
O método BeginScope devolve um objeto IDisposable, e o escopo dura até o descarte desse objeto. Então, o caminho mais simples é envolver as chamadas em um bloco using.
var transactionId = Guid.NewGuid().ToString();
using (_logger.BeginScope("Transaction {TransactionId}", transactionId))
{
// Todas as mensagens daqui carregam o TransactionId
_logger.LogInformation("Starting checkout");
_logger.LogInformation("Checkout finished");
}
O provedor de console suporta escopos, mas é preciso habilitar a opção IncludeScopes para vê-los na saída. Por fim, provedores de terceiros costumam transformar o escopo em propriedades do evento.
Criando um ILogger personalizado
Como vimos, criar um logger próprio exige implementar o ILoggerProvider. Além dele, precisamos de uma classe que implemente o ILogger e saiba escrever no destino desejado.
O exemplo abaixo escreve apenas avisos e erros no console, com cores diferentes para cada severidade. Ele é simples, mas mostra todos os métodos que a interface exige.
public sealed class ColorConsoleLoggerProvider : ILoggerProvider
{
public ILogger CreateLogger(string categoryName)
=> new ColorConsoleLogger(categoryName);
public void Dispose() { }
}
public sealed class ColorConsoleLogger : ILogger
{
private readonly string _categoryName;
public ColorConsoleLogger(string categoryName)
=> _categoryName = categoryName;
// Este logger não implementa escopos
public IDisposable BeginScope<TState>(TState state) => null!;
// Só aceita avisos e níveis mais severos
public bool IsEnabled(LogLevel logLevel)
=> logLevel >= LogLevel.Warning;
public void Log<TState>(
LogLevel logLevel,
EventId eventId,
TState state,
Exception? exception,
Func<TState, Exception?, string> formatter)
{
if (!IsEnabled(logLevel))
return;
Console.ForegroundColor = logLevel >= LogLevel.Error
? ConsoleColor.Red
: ConsoleColor.Yellow;
Console.WriteLine($"[{logLevel}] {_categoryName}: {formatter(state, exception)}");
Console.ResetColor();
}
}
Depois, basta registrar o provedor no Program.cs, junto dos demais. A partir daí, toda chamada ao ILogger também passa pelo novo destino.
builder.Logging.AddProvider(new ColorConsoleLoggerProvider());
Esse mesmo raciocínio permite enviar mensagens para serviços externos. Por exemplo, o artigo sobre usar o Discord como repositório de log explora uma ideia parecida.
Boas práticas com o ILogger
Algumas recomendações tornam os logs mais úteis e mais baratos. A seguir, reunimos as principais orientações da documentação e da prática diária.
- Use templates com marcadores nomeados em vez de interpolação de strings.
- Escolha o nível certo, porque um erro registrado como informação se perde no meio do ruído.
- Nunca registre senhas, tokens ou dados pessoais, já que os logs costumam circular por várias ferramentas.
- Prefira configurar níveis no
appsettings.jsonpara ajustá-los por ambiente. - Lembre que o ILogger não possui métodos assíncronos, pois o log deve ser rápido.
Sobre o último ponto, a documentação orienta a não escrever diretamente em um destino lento, como um banco de dados. Em vez disso, grave primeiro em um local rápido e mova os registros depois.
Conclusão
Neste artigo, vimos como usar o ILogger padrão do .NET em uma aplicação ASP.NET Core. Também entendemos o papel do ILoggerFactory e do ILoggerProvider, que tornam o sistema de logs extensível.
Com níveis bem definidos, mensagens estruturadas e escopos, os logs deixam de ser texto solto e passam a contar a história da aplicação. Portanto, vale investir tempo nessa configuração desde o início do projeto.
O próximo passo natural é conectar provedores de terceiros e enviar os registros para ferramentas de observabilidade. Ainda assim, tudo continua começando pela mesma interface ILogger.
FAQ
O que é o ILogger no .NET?
É a interface de logging da biblioteca Microsoft.Extensions.Logging. Ela permite escrever mensagens com níveis de severidade que os provedores enviam para destinos como o console.
Preciso instalar algum pacote para usar o ILogger no ASP.NET Core?
Normalmente não, porque o framework compartilhado do ASP.NET Core já inclui o logging. Em bibliotecas de classes, adicione o pacote Microsoft.Extensions.Logging.Abstractions.
Qual a diferença entre ILogger e ILogger?
O ILogger<T> é a versão genérica que usa o nome completo do tipo T como categoria. Na prática, é a forma mais usada para receber o logger por injeção de dependência.
Como mudar o nível mínimo de log?
Ajuste a seção Logging:LogLevel do appsettings.json ou chame SetMinimumLevel no Program.cs. A configuração por arquivo é a opção recomendada.
O ILogger grava logs em arquivo?
Não, os provedores nativos não escrevem em arquivos. Para isso, use um provedor de terceiros, como Serilog ou NLog.