Registro em log em C# e .NET

O .NET dá suporte ao registro em log estruturado e de alto desempenho por meio da API ILogger para ajudar a monitorar o comportamento do aplicativo e diagnosticar problemas. Os logs poderão ser gravados em destinos diferentes se você configurar provedores de registro em log diferentes. Os provedores básicos de registro em log são integrados e também existem muitos provedores de terceiros disponíveis.

Introdução

Este primeiro exemplo mostra as noções básicas, mas é adequado apenas para um aplicativo de console trivial. Na próxima seção, você verá como aprimorar o código levando em conta a escala, o desempenho, a configuração e os padrões de programação típicos.

using Microsoft.Extensions.Logging;

using ILoggerFactory factory = LoggerFactory.Create(builder => builder.AddConsole());
ILogger logger = factory.CreateLogger("Program");
logger.LogInformation("Hello World! Logging is {Description}.", "fun");

No exemplo anterior:

  • Cria um ILoggerFactory. O ILoggerFactory armazena toda a configuração que determina para onde as mensagens de log são enviadas. Nesse caso, você configura o provedor de registro em log do console para que as mensagens de log sejam gravadas no console.
  • Cria um ILogger com uma categoria chamada "Programa". A categoria é uma string associada a cada mensagem registrada em log pelo objeto ILogger. É usada para agrupar mensagens de log da mesma classe (ou categoria) ao pesquisar ou filtrar logs.
  • Chama o LogInformation para registrar uma mensagem no nível Information. O nível de log indica a gravidade do evento registrado em log e é usado para filtrar e deixar de fora as mensagens de log menos importantes. A inserção no log também inclui um modelo de mensagem"Hello World! Logging is {Description}." e um par de chave-valor Description = fun. O nome da chave (ou espaço reservado) vem da palavra entre chaves no modelo e o valor vem do argumento de método restante.

Este arquivo de projeto para este exemplo inclui dois pacotes NuGet:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
  </ItemGroup>

</Project>

Dica

Todo o código-fonte do exemplo de log está disponível no Navegador de Exemplos para download. Para obter mais informações, confira Procurar exemplos de código: log no .NET.

Registro em log em um aplicativo não trivial

Há várias alterações que você deve pensar em fazer no exemplo anterior ao registrar em log em um cenário menos trivial:

  • Se o seu aplicativo estiver usando Injeção de Dependência (DI) ou um host como o WebApplication ou um Host Genérico do ASP.NET, você deve usar objetos ILoggerFactory e ILogger de seus respectivos contêineres de DI em vez de criá-los diretamente. Para obter mais informações, confira Integração com DI e Hosts.

  • O registro em log da geração da origem do tempo de compilação costuma ser uma alternativa melhor do que os métodos de extensão do ILogger, como LogInformation. A geração da origem do registro em log oferece um melhor desempenho, uma digitação mais sólida e evita a propagação de constantes de string em todos os seus métodos. A desvantagem é que usar essa técnica requer um pouco mais de código.

using Microsoft.Extensions.Logging;

internal partial class Program
{
    static void Main(string[] args)
    {
        using ILoggerFactory factory = LoggerFactory.Create(builder => builder.AddConsole());
        ILogger logger = factory.CreateLogger("Program");
        LogStartupMessage(logger, "fun");
    }

    [LoggerMessage(Level = LogLevel.Information, Message = "Hello World! Logging is {Description}.")]
    static partial void LogStartupMessage(ILogger logger, string description);
}
  • A prática recomendada para nomes de categoria de logs é usar o nome totalmente qualificado da classe que está criando a mensagem de log. Isso ajuda a estabelecer uma relação entre as mensagens de log e o código que as produziu e oferece um bom nível de controle na filtragem de logs. O CreateLogger aceita um Type para tornar essa nomenclatura fácil de fazer.
using Microsoft.Extensions.Logging;

internal class Program
{
    static void Main(string[] args)
    {
        using ILoggerFactory factory = LoggerFactory.Create(builder => builder.AddConsole());
        ILogger logger = factory.CreateLogger<Program>();
        logger.LogInformation("Hello World! Logging is {Description}.", "fun");
    }
}
using Microsoft.Extensions.Logging;
using OpenTelemetry.Logs;

using ILoggerFactory factory = LoggerFactory.Create(builder =>
{
    builder.AddOpenTelemetry(logging =>
    {
        logging.AddOtlpExporter();
    });
});
ILogger logger = factory.CreateLogger("Program");
logger.LogInformation("Hello World! Logging is {Description}.", "fun");

Integração com hosts e injeção de dependência

Se o seu aplicativo estiver usando Injeção de Dependência (DI) ou um host como o WebApplication ou um Host Genérico do ASP.NET, você deve usar objetos ILoggerFactory e ILogger do contêiner de DI em vez de criá-los diretamente.

Obter um ILogger do DI

Esse exemplo obtém um objeto ILogger em um aplicativo hospedado usando APIs Mínimas do ASP.NET:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSingleton<ExampleHandler>();

var app = builder.Build();

var handler = app.Services.GetRequiredService<ExampleHandler>();
app.MapGet("/", handler.HandleRequest);

app.Run();

partial class ExampleHandler(ILogger<ExampleHandler> logger)
{
    public string HandleRequest()
    {
        LogHandleRequest(logger);
        return "Hello World";
    }

    [LoggerMessage(LogLevel.Information, "ExampleHandler.HandleRequest was called")]
    public static partial void LogHandleRequest(ILogger logger);
}

No exemplo anterior:

  • Criou um serviço singleton chamado ExampleHandler e mapeou as solicitações da web recebidas para executar a função ExampleHandler.HandleRequest.
  • A linha 8 define um construtor primário para o ExampleHandler, um recurso adicionado no C# 12. Usar o construtor C# de estilo mais antigo funcionaria igualmente bem, mas é um pouco mais detalhado.
  • O construtor define um parâmetro do tipo ILogger<ExampleHandler>. ILogger<TCategoryName> deriva de ILogger e indica qual categoria o objeto ILogger tem. O contêiner de DI localiza um ILogger com a categoria correta e o fornece como o argumento do construtor. Se nenhum ILogger com essa categoria já existir, o contêiner de DI o criará automaticamente a partir do ILoggerFactory no provedor de serviços.
  • O parâmetro logger recebido no construtor foi usado para registrar em log na função HandleRequest.

ILoggerFactory fornecido pelo host

Os construtores de host inicializam a configuração padrão e, em seguida, adicionam um objeto ILoggerFactory configurado ao contêiner de DI do host quando o host é criado. Antes de o host ser criado, você pode ajustar a configuração de registro em log por meio de HostApplicationBuilder.Logging, WebApplicationBuilder.Logging ou APIs semelhantes em outros hosts. Os hosts também aplicam a configuração do registro em log das fontes de configuração padrão, como appsettings.json, e variáveis de ambiente. Para obter mais informações, confira Configuração no .NET.

Esse exemplo se expande a partir do anterior para personalizar o ILoggerFactory fornecido pelo WebApplicationBuilder. Adiciona o OpenTelemetry como um provedor de registro em log que transmite os logs por meio do Protocolo OpenTelemetry (OTLP):

var builder = WebApplication.CreateBuilder(args);
builder.Logging.AddOpenTelemetry(logging => logging.AddOtlpExporter());
builder.Services.AddSingleton<ExampleHandler>();
var app = builder.Build();

Criar um ILoggerFactory com DI

Se estiver usando um contêiner de DI sem um host, use AddLogging para configurar e adicionar um ILoggerFactory ao contêiner.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

// Add services to the container including logging
var services = new ServiceCollection();
services.AddLogging(builder => builder.AddConsole());
services.AddSingleton<ExampleService>();
IServiceProvider serviceProvider = services.BuildServiceProvider();

// Get the ExampleService object from the container
ExampleService service = serviceProvider.GetRequiredService<ExampleService>();

// Do some pretend work
service.DoSomeWork(10, 20);

class ExampleService(ILogger<ExampleService> logger)
{
    public void DoSomeWork(int x, int y)
    {
        logger.LogInformation("DoSomeWork was called. x={X}, y={Y}", x, y);
    }
}

No exemplo anterior:

  • Criou um contêiner do serviço DI que contém um ILoggerFactory configurado para gravar no console
  • Adicionou um singleton ExampleService ao contêiner
  • Criou uma instância do ExampleService do contêiner de DI que também criou automaticamente um ILogger<ExampleService> a ser usado como o argumento do construtor.
  • Invocou o ExampleService.DoSomeWork que usou o ILogger<ExampleService> para registrar uma mensagem no console.

Configurar o registro em log

A configuração do registro em log é definida no código ou por meio de fontes externas, como, por exemplo, arquivos de configuração e variáveis de ambiente. O uso da configuração externa é benéfico sempre que possível porque pode ser alterado sem ser preciso recompilar o aplicativo. No entanto, algumas tarefas, como definir provedores de registro em log, só podem ser configuradas a partir do código.

Configurar o registro em log sem código

No caso de arquivos que usam um host, a configuração do registro em log geralmente é fornecida pela seção "Logging" dos arquivos appsettings.{Environment}.json. Para aplicativos que não usam um host, as fontes de configuração externas são configuradas explicitamente ou, alternativamente, configuradas no código.

O seguinte arquivo appsettings.Development.json é gerado pelos modelos do serviço .NET Worker:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  }
}

No JSON anterior:

  • As categorias de nível de log "Default", "Microsoft" e "Microsoft.Hosting.Lifetime" são especificadas.
  • O valor "Default" é aplicado a todas as categorias que não são especificadas de outra forma, efetivamente tornando todos os valores padrão para todas as categorias "Information". Você pode substituir esse comportamento especificando um valor para uma categoria.
  • A categoria "Microsoft" se aplica a todas as categorias que começam com "Microsoft".
  • A categoria "Microsoft" registra em log no nível de log Warning e superior.
  • A categoria "Microsoft.Hosting.Lifetime" é mais específica do que a "Microsoft", ou seja, a categoria "Microsoft.Hosting.Lifetime" faz o registro em log no nível "Information" e superiores.
  • Não é especificado um provedor de log específico, portanto LogLevel se aplica a todos os provedores de log habilitados, exceto por EventLog do Windows.

A propriedade Logging pode ter LogLevel e as propriedades do provedor de logs. A LogLevel especifica o nível mínimo de log nas categorias selecionadas. No JSON anterior, os níveis de log Information e Warning são especificados. LogLevel indica a severidade do log e varia de 0 a 6:

Trace = 0, Debug = 1, Information = 2, Warning = 3, Error = 4, Critical = 5 e None = 6.

Quando um LogLevel é especificado, o registro em log é habilitado para mensagens no nível especificado e superior. No JSON anterior, a categoria Default é registrada para Information e superior. Por exemplo, mensagens Information, Warning, Error e Critical são registradas em log. Se LogLevel não for especificado, o registro em log usará o nível Information como padrão. Para obter mais informações, confira Níveis de log.

Uma propriedade de provedor pode especificar uma propriedade LogLevel. LogLevel em um provedor especifica os níveis de log desse provedor e substitui as configurações de log que não são do provedor. Considere o seguinte arquivo appsettings.json:

{
    "Logging": {
        "LogLevel": {
            "Default": "Error",
            "Microsoft": "Warning"
        },
        "Debug": {
            "LogLevel": {
                "Default": "Information",
                "Microsoft.Hosting": "Trace"
            }
        },
        "EventSource": {
            "LogLevel": {
                "Default": "Warning"
            }
        }
    }
}

As configurações em Logging.{ProviderName}.LogLevel substituem as configurações em Logging.LogLevel. No JSON anterior, o nível de log padrão do provedor Debug é definido como Information:

Logging:Debug:LogLevel:Default:Information

A configuração anterior especifica o nível de log Information para todas as categorias de Logging:Debug:, exceto a Microsoft.Hosting. Quando uma categoria específica é listada, ela substitui a categoria padrão. No JSON anterior, as categorias "Microsoft.Hosting" e "Default" de Logging:Debug:LogLevel substituem as configurações em Logging:LogLevel

O nível mínimo de log pode ser especificado para:

  • Provedores específicos: por exemplo, Logging:EventSource:LogLevel:Default:Information
  • Categorias específicas: por exemplo, Logging:LogLevel:Microsoft:Warning
  • Todos os provedores e todas as categorias: Logging:LogLevel:Default:Warning

Todos os logs abaixo do nível mínimo não são:

  • Passados para o provedor.
  • Registrados nem exibidos.

Para suprimir todos os logs, especifique LogLevel.None. LogLevel.None tem o valor 6, que é superior a LogLevel.Critical (5).

Se um provedor oferecer suporte a escopos de log, IncludeScopes indicará se eles estão habilitados. Para obter mais informações, confira Escopos de log

O seguinte arquivo appsettings.json contém as configurações de todos os provedores internos:

{
    "Logging": {
        "LogLevel": {
            "Default": "Error",
            "Microsoft": "Warning",
            "Microsoft.Hosting.Lifetime": "Warning"
        },
        "Debug": {
            "LogLevel": {
                "Default": "Information"
            }
        },
        "Console": {
            "IncludeScopes": true,
            "LogLevel": {
                "Microsoft.Extensions.Hosting": "Warning",
                "Default": "Information"
            }
        },
        "EventSource": {
            "LogLevel": {
                "Microsoft": "Information"
            }
        },
        "EventLog": {
            "LogLevel": {
                "Microsoft": "Information"
            }
        },
        "AzureAppServicesFile": {
            "IncludeScopes": true,
            "LogLevel": {
                "Default": "Warning"
            }
        },
        "AzureAppServicesBlob": {
            "IncludeScopes": true,
            "LogLevel": {
                "Microsoft": "Information"
            }
        },
        "ApplicationInsights": {
            "LogLevel": {
                "Default": "Information"
            }
        }
    }
}

Na amostra anterior:

  • As categorias e os níveis não são valores sugeridos. O exemplo é fornecido para mostrar todos os provedores padrão.
  • As configurações em Logging.{ProviderName}.LogLevel substituem as configurações em Logging.LogLevel. Por exemplo, o nível em Debug.LogLevel.Default substitui o nível em LogLevel.Default.
  • O alias de cada provedor é usado. Cada provedor define um alias que pode ser usado na configuração no lugar do nome de tipo totalmente qualificado. Os aliases dos provedores internos são:
    • Console
    • Debug
    • EventSource
    • EventLog
    • AzureAppServicesFile
    • AzureAppServicesBlob
    • ApplicationInsights

Definir o nível de log por linha de comando, variáveis de ambiente e outras configurações

O nível de log pode ser definido por qualquer um dos provedores de configuração. Por exemplo, você pode criar uma variável de ambiente persistente chamada Logging:LogLevel:Microsoft com o valor Information.

Crie e atribua variáveis de ambiente persistentes, considerando o valor de nível de log.

:: Assigns the env var to the value
setx "Logging__LogLevel__Microsoft" "Information" /M

Em uma nova instância do prompt de comando, leia a variável de ambiente.

:: Prints the env var value
echo %Logging__LogLevel__Microsoft%

A configuração de ambiente anterior é mantida no ambiente. Para testar as configurações ao usar um aplicativo criado com os modelos do serviço .NET Worker, use o comando dotnet run no diretório do projeto depois que a variável de ambiente for atribuída.

dotnet run

Dica

Depois de definir uma variável de ambiente, reinicie o IDE (ambiente de desenvolvimento integrado) para garantir que as variáveis de ambiente adicionadas recentemente estejam disponíveis.

Em Serviço de Aplicativo do Azure, selecione Nova configuração de aplicativo na página Configuração > Configurações. As configurações do aplicativo do Serviço de Aplicativo do Azure são:

  • Criptografadas em repouso e transmitidas por um canal criptografado.
  • Expostas como variáveis de ambiente.

Para obter mais informações de como definir valores de configuração do .NET usando variáveis de ambiente, confira variáveis de ambiente.

Configurar o registro em log com código

Para configurar o registro em log no código, use a API ILoggingBuilder. O recurso pode ser acessado a partir de diferentes locais:

Esse exemplo mostra como configurar o provedor de registro em log do console e diversos filtros.

using Microsoft.Extensions.Logging;

using var loggerFactory = LoggerFactory.Create(static builder =>
{
    builder
        .AddFilter("Microsoft", LogLevel.Warning)
        .AddFilter("System", LogLevel.Warning)
        .AddFilter("LoggingConsoleApp.Program", LogLevel.Debug)
        .AddConsole();
});

ILogger logger = loggerFactory.CreateLogger<Program>();
logger.LogDebug("Hello {Target}", "Everyone");

No exemplo anterior, AddFilter é usado para ajustar o nível de log que é habilitado para várias categorias. O AddConsole é usado para adicionar o provedor de registro em log do console. Por padrão, os logs com nível de gravidade Debug não são habilitados, mas como a configuração ajustou os filtros, a mensagem de depuração "Olá, todo mundo" é exibida no console.

Como as regras de filtragem são aplicadas

Quando um objeto ILogger<TCategoryName> é criado, o objeto ILoggerFactory seleciona uma única regra por provedor para aplicar a esse agente. Todas as mensagens gravadas pela instância ILogger são filtradas com base nas regras selecionadas. A regra mais específica possível para cada par de categoria e provedor é selecionada entre as regras disponíveis.

O algoritmo a seguir é usado para cada provedor quando um ILogger é criado para uma determinada categoria:

  • Selecione todas as regras que correspondem ao provedor ou seu alias. Se nenhuma correspondência for encontrada, selecione todas as regras com um provedor vazio.
  • Do resultado da etapa anterior, selecione as regras com o prefixo de categoria de maior correspondência. Se nenhuma correspondência for encontrada, selecione todas as regras que não especificam uma categoria.
  • Se várias regras forem selecionadas, use a última.
  • Se não for selecionada nenhuma regra, use LoggingBuilderExtensions.SetMinimumLevel(ILoggingBuilder, LogLevel) para especificar o nível mínimo de registros em log.

Categoria do log

Quando um objeto ILogger é criado, uma categoria é especificada. Essa categoria é incluída em cada mensagem de log criada por essa instância de ILogger. A cadeia de caracteres da categoria é arbitrária, mas a convenção é usar o nome de classe totalmente qualificado. Por exemplo, em um aplicativo com um serviço definido como o seguinte objeto, a categoria pode ser "Example.DefaultService":

namespace Example
{
    public class DefaultService : IService
    {
        private readonly ILogger<DefaultService> _logger;

        public DefaultService(ILogger<DefaultService> logger) =>
            _logger = logger;

        // ...
    }
}

Se mais categorização for desejada, a convenção é usar um nome hierárquico anexando uma subcategoria ao nome de classe totalmente qualificado e especificar explicitamente a categoria usando LoggerFactory.CreateLogger:

namespace Example
{
    public class DefaultService : IService
    {
        private readonly ILogger _logger;

        public DefaultService(ILoggerFactory loggerFactory) =>
            _logger = loggerFactory.CreateLogger("Example.DefaultService.CustomCategory");

        // ...
    }
}

A chamada de CreateLogger com um nome fixo pode ser útil quando usada em várias classes/tipos para que os eventos possam ser organizados por categoria.

ILogger<T> é equivalente a chamar CreateLogger com o nome de tipo totalmente qualificado de T.

Nível de log

A seguinte tabela lista os valores de LogLevel, o método de extensão de conveniência Log{LogLevel} e o uso sugerido:

LogLevel Valor Método Descrição
Trace 0 LogTrace Contêm as mensagens mais detalhadas. Essas mensagens podem conter dados confidenciais do aplicativo. Essas mensagens são desabilitadas por padrão e não devem ser habilitadas em um ambiente de produção.
Depurar 1 LogDebug Para depuração e desenvolvimento. Use com cuidado em produção devido ao alto volume.
Informações 2 LogInformation Rastreia o fluxo geral do aplicativo. Pode ter um valor de longo prazo.
Aviso 3 LogWarning Para eventos anormais ou inesperados. Geralmente, inclui erros ou condições que não fazem com que o aplicativo falhe.
Erro 4 LogError Para erros e exceções que não podem ser manipulados. Essas mensagens indicam uma falha na operação ou na solicitação atual, não uma falha em todo o aplicativo.
Crítico 5 LogCritical Para falhas que exigem atenção imediata. Exemplos: cenários de perda de dados, espaço em disco insuficiente.
Nenhum 6 Especifica que nenhuma mensagem deve ser gravada.

Na tabela anterior, o LogLevel é listado da severidade menor para a maior.

O primeiro parâmetro do método Log, LogLevel, indica a severidade do log. Em vez de chamar Log(LogLevel, ...), a maioria dos desenvolvedores chama os métodos de extensão Log{LogLevel}. Os métodos de extensão Log{LogLevel} chamam o método Log e especificam o LogLevel. Por exemplo, estas duas chamadas de log são equivalentes em funcionalidades e produzem o mesmo log:

public void LogDetails()
{
    var logMessage = "Details for log.";

    _logger.Log(LogLevel.Information, AppLogEvents.Details, logMessage);
    _logger.LogInformation(AppLogEvents.Details, logMessage);
}

AppLogEvents.Details é a ID do evento e é representada implicitamente por um valor Int32 constante. AppLogEvents é uma classe que expõe várias constantes de identificador nomeadas e é exibida na seção ID do evento de log.

O código a seguir cria os logs Information e Warning:

public async Task<T> GetAsync<T>(string id)
{
    _logger.LogInformation(AppLogEvents.Read, "Reading value for {Id}", id);

    var result = await _repository.GetAsync(id);
    if (result is null)
    {
        _logger.LogWarning(AppLogEvents.ReadNotFound, "GetAsync({Id}) not found", id);
    }

    return result;
}

No código anterior, o primeiro parâmetro de Log{LogLevel}, AppLogEvents.Read, é a ID do evento de log. O segundo parâmetro é um modelo de mensagem com espaços reservados para valores de argumento fornecidos pelos parâmetros de método restantes. Os parâmetros de método serão explicados na seção modelos de mensagem mais adiante neste artigo.

Configure o nível de log apropriado e chame os métodos Log{LogLevel} corretos para controlar a quantidade de saída de log gravada em um determinado meio de armazenamento. Por exemplo:

  • Em produção:
    • O log nos níveis Trace ou Debug produz um alto volume de mensagens de log detalhadas. Para controlar os custos e não exceder os limites de armazenamento de dados, registre mensagens nos níveis Trace e Debug em um armazenamento de dados de alto volume e baixo custo. Considere limitar Trace e Debug a categorias específicas.
    • O registro em log nos níveis Warning a Critical deve produzir poucas mensagens de log.
      • Os custos e os limites de armazenamento geralmente não são preocupantes.
      • Poucos logs permitem mais flexibilidade nas opções de armazenamento de dados.
  • Em desenvolvimento:
    • Defina como Warning.
    • Adicione mensagens de Trace ou Debug ao solucionar problemas. Para limitar a saída, defina Trace ou Debug somente para as categorias em investigação.

O seguinte JSON define Logging:Console:LogLevel:Microsoft:Information:

{
    "Logging": {
        "LogLevel": {
            "Microsoft": "Warning"
        },
        "Console": {
            "LogLevel": {
                "Microsoft": "Information"
            }
        }
    }
}

ID de evento de log

Cada log pode especificar um identificador de evento. EventId é uma estrutura com a propriedade Id e a propriedade somente leitura opcional Name. O código-fonte de exemplo usa a classe AppLogEvents para definir IDs de evento:

using Microsoft.Extensions.Logging;

internal static class AppLogEvents
{
    internal static EventId Create = new(1000, "Created");
    internal static EventId Read = new(1001, "Read");
    internal static EventId Update = new(1002, "Updated");
    internal static EventId Delete = new(1003, "Deleted");

    // These are also valid EventId instances, as there's
    // an implicit conversion from int to an EventId
    internal const int Details = 3000;
    internal const int Error = 3001;

    internal static EventId ReadNotFound = 4000;
    internal static EventId UpdateNotFound = 4001;

    // ...
}

Dica

Para obter mais informações de como converter um int em um EventId, confira Operador EventId.Implicit(Int32 to EventId).

Uma ID de evento associa um conjunto de eventos. Por exemplo, todos os logs relacionados à leitura de valores de um repositório podem ser 1001.

O provedor de logs pode armazenar ou não a ID do evento em um campo de ID na mensagem de log. O provedor de Depuração não mostra IDs de eventos. O provedor de console mostra IDs de evento entre colchetes após a categoria:

info: Example.DefaultService.GetAsync[1001]
      Reading value for a1b2c3
warn: Example.DefaultService.GetAsync[4000]
      GetAsync(a1b2c3) not found

Alguns provedores de log armazenam a ID do evento em um campo, o que permite a filtragem na ID.

Modelo de mensagem de log

Cada API de log usa um modelo de mensagem. O modelo de mensagem pode conter espaços reservados para os quais são fornecidos argumentos. Use nomes para os espaços reservados, não números. A ordem dos espaços reservados e não de seus nomes, determina quais parâmetros serão usados para fornecer seus valores. No código a seguir, os nomes de parâmetro estão fora de sequência no modelo de mensagem:

string p1 = "param1";
string p2 = "param2";
_logger.LogInformation("Parameter values: {p2}, {p1}", p1, p2);

Esse código cria uma mensagem de log com os valores de parâmetro em sequência:

Parameter values: param1, param2

Observação

Esteja atento ao usar vários espaços reservados em um só modelo de mensagem, pois eles são baseados em ordinais. Os nomes não são usados para alinhar os argumentos aos espaços reservados.

Essa abordagem permite que os provedores de log implementem o log semântico ou estruturado. Os próprios argumentos são passados para o sistema de registro em log, não apenas o modelo de mensagem formatado. Essas informações permitem que os provedores de log armazenem os valores de parâmetro como campos. Considere o seguinte método de agente:

_logger.LogInformation("Getting item {Id} at {RunTime}", id, DateTime.Now);

Por exemplo, ao registrar em log no Armazenamento de Tabelas do Azure:

  • Cada entidade da Tabela do Azure pode ter as propriedades ID e RunTime.
  • As tabelas com propriedades simplificam as consultas nos dados registrados. Por exemplo, uma consulta pode encontrar todos os logs em um determinado intervalo de RunTime sem precisar analisar o tempo limite da mensagem de texto.

Formatação do modelo de mensagem de log

Os modelos de mensagem de log dão suporte à formatação de espaço reservado. Os modelos são livres para especificar qualquer formato válido para o argumento de tipo fornecido. Por exemplo, considere o seguinte modelo de mensagem Information do agente:

_logger.LogInformation("Logged on {PlaceHolderName:MMMM dd, yyyy}", DateTimeOffset.UtcNow);
// Logged on January 06, 2022

No exemplo anterior, a instância de DateTimeOffset é o tipo que corresponde a PlaceHolderName no modelo de mensagem do agente. Esse nome pode ser qualquer coisa, pois os valores são baseados em ordinais. O formato MMMM dd, yyyy é válido para o tipo DateTimeOffset.

Para obter mais informações sobre a formatação de DateTime e DateTimeOffset, confira Cadeias de caracteres de formato de data e hora personalizadas.

Exemplos

Os exemplos a seguir mostram como formatar um modelo de mensagem usando a sintaxe de espaço reservado {}. Além disso, um exemplo de escape da sintaxe de espaço reservado {} é mostrado com a saída. Por fim, a interpolação de cadeia de caracteres com espaços reservados de modelo também é mostrada:

logger.LogInformation("Number: {Number}", 1);               // Number: 1
logger.LogInformation("{{Number}}: {Number}", 3);           // {Number}: 3
logger.LogInformation($"{{{{Number}}}}: {{Number}}", 5);    // {Number}: 5

Dica

  • Na maioria dos casos, você deve usar a formatação de modelo de mensagens de log ao registrar em log. O uso da interpolação de cadeias de caracteres pode causar problemas de desempenho.
  • A regra de análise de código CA2254: o modelo deve ser uma expressão estática ajuda a alertar você para locais em que suas mensagens de log não usam a formatação adequada.

Registrar exceções em log

Os métodos do agente têm sobrecargas que usam um parâmetro de exceção:

public void Test(string id)
{
    try
    {
        if (id is "none")
        {
            throw new Exception("Default Id detected.");
        }
    }
    catch (Exception ex)
    {
        _logger.LogWarning(
            AppLogEvents.Error, ex,
            "Failed to process iteration: {Id}", id);
    }
}

O log de exceções é específico do provedor.

Nível de log padrão

Se o nível de log padrão não for definido, o valor do nível de log padrão será Information.

Por exemplo, considere o seguinte aplicativo de serviço de trabalho:

  • Criado com os modelos do .NET Worker.
  • appsettings.json e appsettings.Development.json excluídos ou renomeados.

Com a configuração anterior, o acesso à página de privacidade ou à home page gera várias mensagens Trace, Debug e Information com Microsoft no nome da categoria.

O seguinte código define o nível de log padrão quando ele não está definido na configuração:

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Logging.SetMinimumLevel(LogLevel.Warning);

using IHost host = builder.Build();

await host.RunAsync();

Função Filter

Uma função de filtro é invocada para todos os provedores e as categorias que não têm regras atribuídas por configuração ou no código:

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Logging.AddFilter((provider, category, logLevel) =>
{
    return provider.Contains("ConsoleLoggerProvider")
        && (category.Contains("Example") || category.Contains("Microsoft"))
        && logLevel >= LogLevel.Information;
});

using IHost host = builder.Build();

await host.RunAsync();

O código anterior exibe logs de console quando a categoria contém Example ou Microsoft e o nível de log é Information ou superior.

Escopos de log

Um escopo agrupa um conjunto de operações lógicas. Esse agrupamento pode ser usado para anexar os mesmos dados para cada log criado como parte de um conjunto. Por exemplo, todo log criado como parte do processamento de uma transação pode incluir a ID da transação.

Um escopo:

Os seguintes provedores dão suporte a escopos:

Use um escopo por meio do encapsulamento de chamadas de agente em um bloco using:

public async Task<T> GetAsync<T>(string id)
{
    T result;
    var transactionId = Guid.NewGuid().ToString();

    using (_logger.BeginScope(new List<KeyValuePair<string, object>>
        {
            new KeyValuePair<string, object>("TransactionId", transactionId),
        }))
    {
        _logger.LogInformation(
            AppLogEvents.Read, "Reading value for {Id}", id);

        var result = await _repository.GetAsync(id);
        if (result is null)
        {
            _logger.LogWarning(
                AppLogEvents.ReadNotFound, "GetAsync({Id}) not found", id);
        }
    }

    return result;
}

O seguinte JSON habilita escopos para o provedor de console:

{
    "Logging": {
        "Debug": {
            "LogLevel": {
                "Default": "Information"
            }
        },
        "Console": {
            "IncludeScopes": true,
            "LogLevel": {
                "Microsoft": "Warning",
                "Default": "Information"
            }
        },
        "LogLevel": {
            "Default": "Debug"
        }
    }
}

O código a seguir habilita os escopos para o provedor de console:

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Logging.ClearProviders();
builder.Logging.AddConsole(options => options.IncludeScopes = true);

using IHost host = builder.Build();

await host.RunAsync();

Criar logs em Main

O seguinte código registra logs em Main obtendo uma instância de ILogger da DI após a criação do host:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

using IHost host = Host.CreateApplicationBuilder(args).Build();

var logger = host.Services.GetRequiredService<ILogger<Program>>();
logger.LogInformation("Host created.");

await host.RunAsync();

O código anterior depende de dois pacotes NuGet:

O arquivo de projeto seria semelhante ao seguinte:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net7.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
    <PackageReference Include="Microsoft.Extensions.Logging" Version="7.0.0" />
  </ItemGroup>

</Project>

Sem métodos de agente assíncronos

O registro em log deve ser tão rápido que não justifique o custo de desempenho de código assíncrono. Se o armazenamento de dados de log estiver lento, não grave as mensagens diretamente nele. Grave as mensagens de log em um armazenamento rápido primeiro e depois passe-as para um armazenamento lento. Por exemplo, se você estiver enviado logs ao SQL Server, não faça isso diretamente em um método Log, pois os métodos Log são síncronos. Em vez disso, adicione mensagens de log de forma síncrona a uma fila na memória e faça com que uma função de trabalho de plano de fundo efetue pull das mensagens para fora da fila para fazer o trabalho assíncrono de envio de dados por push para o SQL Server.

Alterar os níveis de log em um aplicativo em execução

A API de Log não inclui um cenário para alterar os níveis de log enquanto um aplicativo está em execução. No entanto, alguns provedores de configuração conseguem recarregar a configuração, o que entra em vigor imediatamente na configuração de log. Por exemplo, o Provedor de Configuração de Arquivos recarrega a configuração de log por padrão. Se a configuração for alterada no código enquanto um aplicativo estiver em execução, o aplicativo poderá chamar IConfigurationRoot.Reload para atualizar a configuração de log do aplicativo.

Pacotes NuGet

As interfaces e implementações ILogger<TCategoryName> e ILoggerFactory estão inclusas na maioria dos SDKs do .NET como referência implícita de pacote. Elas também estão disponíveis explicitamente nos seguintes pacotes NuGet quando não são referenciadas implicitamente:

Para obter mais informações sobre qual SDK do .NET inclui referências implícitas de pacote, confira SDK do .NET: tabela para namespace implícito.

Confira também