Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
O .NET 6 introduz o LoggerMessageAttribute tipo. Esse atributo faz parte do Microsoft.Extensions.Logging namespace e, quando usado, gera APIs de log de alto desempenho. O suporte de log de geração de origem foi projetado para fornecer uma solução de log altamente utilizável e de alto desempenho para aplicativos .NET modernos. O código-fonte gerado automaticamente depende da interface em conjunto com ILogger a LoggerMessage.Define funcionalidade.
O gerador de origem é acionado quando LoggerMessageAttribute é usado em partial métodos de registro. Quando acionado, pode gerar automaticamente a implementação dos métodos que está decorando ou produzir diagnósticos durante a compilação com dicas sobre o uso adequado. A solução de registo em tempo de compilação é consideravelmente mais rápida em tempo de execução do que as abordagens de registo existentes. Ele consegue isso eliminando o boxe, alocações temporárias e cópias na máxima extensão possível.
Utilização básica
Para usar o LoggerMessageAttribute, a classe e o método de consumo precisam ser partial. O gerador de código é acionado em tempo de compilação e gera uma implementação do partial método.
public static partial class Log
{
[LoggerMessage(
EventId = 0,
Level = LogLevel.Critical,
Message = "Could not open socket to `{HostName}`")]
public static partial void CouldNotOpenSocket(
ILogger logger, string hostName);
}
No exemplo anterior, o método de log é static e o nível de log é especificado na definição de atributo. Ao usar o atributo em um contexto estático, a ILogger instância é necessária como um parâmetro ou modifica a definição para usar a this palavra-chave para definir o método como um método de extensão.
public static partial class Log
{
[LoggerMessage(
EventId = 0,
Level = LogLevel.Critical,
Message = "Could not open socket to `{HostName}`")]
public static partial void CouldNotOpenSocket(
this ILogger logger, string hostName);
}
Você também pode optar por usar o atributo em um contexto não estático. Considere o exemplo a seguir em que o método de log é declarado como um método de instância. Nesse contexto, o método de log obtém o logger acessando um ILogger campo na classe que contém.
public partial class InstanceLoggingExample
{
private readonly ILogger _logger;
public InstanceLoggingExample(ILogger logger)
{
_logger = logger;
}
[LoggerMessage(
EventId = 0,
Level = LogLevel.Critical,
Message = "Could not open socket to `{HostName}`")]
public partial void CouldNotOpenSocket(string hostName);
}
A partir do .NET 9, o método de log também pode obter o logger de um ILogger parâmetro de construtor primário na classe que contém.
public partial class InstanceLoggingExample(ILogger logger)
{
[LoggerMessage(
EventId = 0,
Level = LogLevel.Critical,
Message = "Could not open socket to `{HostName}`")]
public partial void CouldNotOpenSocket(string hostName);
}
Se houver tanto um campo ILogger quanto um parâmetro de construtor primário, o método de log recupera o logger do campo.
Às vezes, o nível de log precisa ser dinâmico em vez de incorporado estaticamente no código. Você pode fazer isso omitindo o nível de log do atributo e, em vez disso, exigindo-o como um parâmetro para o método de log.
public static partial class Log
{
[LoggerMessage(
EventId = 0,
Message = "Could not open socket to `{HostName}`")]
public static partial void CouldNotOpenSocket(
ILogger logger,
LogLevel level, /* Dynamic log level as parameter, rather than defined in attribute. */
string hostName);
}
Você pode omitir a mensagem de log e String.Empty é fornecido para a mensagem. O estado contém os argumentos, formatados como pares chave-valor.
using System.Text.Json;
using Microsoft.Extensions.Logging;
using ILoggerFactory loggerFactory = LoggerFactory.Create(
builder =>
builder.AddJsonConsole(
options =>
options.JsonWriterOptions = new JsonWriterOptions()
{
Indented = true
}));
ILogger<SampleObject> logger = loggerFactory.CreateLogger<SampleObject>();
logger.PlaceOfResidence(logLevel: LogLevel.Information, name: "Liana", city: "Seattle");
readonly file record struct SampleObject { }
public static partial class Log
{
[LoggerMessage(EventId = 23, Message = "{Name} lives in {City}.")]
public static partial void PlaceOfResidence(
this ILogger logger,
LogLevel logLevel,
string name,
string city);
}
Considere a saída de log de exemplo ao usar o JsonConsole formatador.
{
"EventId": 23,
"LogLevel": "Information",
"Category": "\u003CProgram\u003EF...9CB42__SampleObject",
"Message": "Liana lives in Seattle.",
"State": {
"Message": "Liana lives in Seattle.",
"name": "Liana",
"city": "Seattle",
"{OriginalFormat}": "{Name} lives in {City}."
}
}
Restrições do método de log
Ao usar os LoggerMessageAttribute métodos de registro em log, algumas restrições devem ser seguidas:
- Os métodos de registro devem ser
partiale retornarvoid. - Os nomes dos métodos de registro em log não devem começar com um sublinhado.
- Os nomes de parâmetros dos métodos de log não devem começar com um sublinhado.
- Os métodos de registo não podem ser genéricos.
- Se um método de log for
static, aILoggerinstância será necessária como um parâmetro.
O modelo de geração de código depende do código que está sendo compilado com um compilador C# moderno, versão 9 ou posterior. O compilador C# 9.0 ficou disponível com o .NET 5. Para atualizar para um compilador C# moderno, edite seu arquivo de projeto para C# 9.0 de destino.
<PropertyGroup>
<LangVersion>9.0</LangVersion>
</PropertyGroup>
Para obter mais informações, consulte Versionamento de linguagem C#.
Anatomia do método logarítmico
A ILogger.Log assinatura aceita o LogLevel e, opcionalmente, um Exception, conforme mostrado no exemplo de código a seguir.
public interface ILogger
{
void Log<TState>(
Microsoft.Extensions.Logging.LogLevel logLevel,
Microsoft.Extensions.Logging.EventId eventId,
TState state,
System.Exception? exception,
Func<TState, System.Exception?, string> formatter);
}
Como regra geral, a primeira instância de ILogger, LogLevele Exception são tratados especialmente na assinatura do método de log do gerador de origem. As instâncias subsequentes são tratadas como parâmetros normais para o modelo de mensagem:
// This is a valid attribute usage
[LoggerMessage(
EventId = 110, Level = LogLevel.Debug, Message = "M1 {Ex3} {Ex2}")]
public static partial void ValidLogMethod(
ILogger logger,
Exception ex,
Exception ex2,
Exception ex3);
// This causes a warning
[LoggerMessage(
EventId = 0, Level = LogLevel.Debug, Message = "M1 {Ex} {Ex2}")]
public static partial void WarningLogMethod(
ILogger logger,
Exception ex,
Exception ex2);
Importante
Os avisos emitidos fornecem detalhes sobre o uso correto do LoggerMessageAttribute. No exemplo anterior, o WarningLogMethod relata um DiagnosticSeverity.Warning de SYSLIB0025.
Don't include a template for `ex` in the logging message since it is implicitly taken care of.
Suporte a nomes de modelos que não diferenciam maiúsculas de minúsculas
O gerador faz uma comparação que não diferencia maiúsculas de minúsculas entre itens no modelo de mensagem e nomes de argumento na mensagem de log. Isso significa que, quando o ILogger enumera o estado, o argumento é captado pelo modelo de mensagem, o que pode tornar os logs mais agradáveis de consumir:
public partial class LoggingExample
{
private readonly ILogger _logger;
public LoggingExample(ILogger logger)
{
_logger = logger;
}
[LoggerMessage(
EventId = 10,
Level = LogLevel.Information,
Message = "Welcome to {City} {Province}!")]
public partial void LogMethodSupportsPascalCasingOfNames(
string city, string province);
public void TestLogging()
{
LogMethodSupportsPascalCasingOfNames("Vancouver", "BC");
}
}
Considere o exemplo de saída de log ao usar o JsonConsole formatador:
{
"EventId": 13,
"LogLevel": "Information",
"Category": "LoggingExample",
"Message": "Welcome to Vancouver BC!",
"State": {
"Message": "Welcome to Vancouver BC!",
"City": "Vancouver",
"Province": "BC",
"{OriginalFormat}": "Welcome to {City} {Province}!"
}
}
Ordem indeterminada dos parâmetros
Não há restrições na ordenação dos parâmetros do método log. Um desenvolvedor pode definir o ILogger como o último parâmetro, embora possa parecer um pouco estranho.
[LoggerMessage(
EventId = 110,
Level = LogLevel.Debug,
Message = "M1 {Ex3} {Ex2}")]
static partial void LogMethod(
Exception ex,
Exception ex2,
Exception ex3,
ILogger logger);
Gorjeta
A ordem dos parâmetros em um método de log não é necessária para corresponder à ordem dos espaços reservados do modelo. Em vez disso, espera-se que os nomes de espaço reservado no modelo correspondam aos parâmetros. Considere a saída a seguir JsonConsole e a ordem dos erros.
{
"EventId": 110,
"LogLevel": "Debug",
"Category": "ConsoleApp.Program",
"Message": "M1 System.Exception: Third time's the charm. System.Exception: This is the second error.",
"State": {
"Message": "M1 System.Exception: Third time's the charm. System.Exception: This is the second error.",
"ex2": "System.Exception: This is the second error.",
"ex3": "System.Exception: Third time's the charm.",
"{OriginalFormat}": "M1 {Ex3} {Ex2}"
}
}
Mais exemplos de registo
Os exemplos a seguir demonstram como recuperar o nome do evento, definir o nível de log dinamicamente e formatar parâmetros de log. Os métodos de registro são:
-
LogWithCustomEventName: Recupere o nome do evento viaLoggerMessageatributo. -
LogWithDynamicLogLevel: Defina o nível de log dinamicamente, para permitir que o nível de log seja definido com base na entrada de configuração. -
UsingFormatSpecifier: Use especificadores de formato para formatar parâmetros de log.
public partial class LoggingSample
{
private readonly ILogger _logger;
public LoggingSample(ILogger logger)
{
_logger = logger;
}
[LoggerMessage(
EventId = 20,
Level = LogLevel.Critical,
Message = "Value is {Value:E}")]
public static partial void UsingFormatSpecifier(
ILogger logger, double value);
[LoggerMessage(
EventId = 9,
Level = LogLevel.Trace,
Message = "Fixed message",
EventName = "CustomEventName")]
public partial void LogWithCustomEventName();
[LoggerMessage(
EventId = 10,
Message = "Welcome to {City} {Province}!")]
public partial void LogWithDynamicLogLevel(
string city, LogLevel level, string province);
public void TestLogging()
{
LogWithCustomEventName();
LogWithDynamicLogLevel("Vancouver", LogLevel.Warning, "BC");
LogWithDynamicLogLevel("Vancouver", LogLevel.Information, "BC");
UsingFormatSpecifier(logger, 12345.6789);
}
}
Considere o exemplo de saída de log ao usar o SimpleConsole formatador:
trce: LoggingExample[9]
Fixed message
warn: LoggingExample[10]
Welcome to Vancouver BC!
info: LoggingExample[10]
Welcome to Vancouver BC!
crit: LoggingExample[20]
Value is 1.234568E+004
Considere o exemplo de saída de log ao usar o JsonConsole formatador:
{
"EventId": 9,
"LogLevel": "Trace",
"Category": "LoggingExample",
"Message": "Fixed message",
"State": {
"Message": "Fixed message",
"{OriginalFormat}": "Fixed message"
}
}
{
"EventId": 10,
"LogLevel": "Warning",
"Category": "LoggingExample",
"Message": "Welcome to Vancouver BC!",
"State": {
"Message": "Welcome to Vancouver BC!",
"city": "Vancouver",
"province": "BC",
"{OriginalFormat}": "Welcome to {City} {Province}!"
}
}
{
"EventId": 10,
"LogLevel": "Information",
"Category": "LoggingExample",
"Message": "Welcome to Vancouver BC!",
"State": {
"Message": "Welcome to Vancouver BC!",
"city": "Vancouver",
"province": "BC",
"{OriginalFormat}": "Welcome to {City} {Province}!"
}
}
{
"EventId": 20,
"LogLevel": "Critical",
"Category": "LoggingExample",
"Message": "Value is 1.234568E+004",
"State": {
"Message": "Value is 1.234568E+004",
"value": 12345.6789,
"{OriginalFormat}": "Value is {Value:E}"
}
}
Redação de informações confidenciais em logs
Ao registrar dados confidenciais, é importante evitar a exposição acidental. Mesmo com métodos de log gerados em tempo de compilação, o registro de valores confidenciais brutos pode levar a vazamentos de dados e problemas de conformidade.
A biblioteca Microsoft.Extensions.Telemetry fornece recursos avançados de registro em log e enriquecimento de telemetria para aplicativos .NET. Ele estende o pipeline de registro para aplicar automaticamente a redação aos dados classificados ao gravar logs. Ele permite que você aplique políticas de proteção de dados em todo o seu aplicativo, integrando a redação em seu fluxo de trabalho de registro. Ele foi criado para aplicativos que precisam de informações sofisticadas de telemetria e registro.
Para ativar a redação, utilize a biblioteca Microsoft.Extensions.Compliance.Redaction. Essa biblioteca fornece redatores — componentes que transformam dados confidenciais (por exemplo, apagando, mascarando ou fazendo hash) para que a saída seja segura. Os redatores são selecionados com base na classificação de dados, que permite rotular os dados de acordo com sua sensibilidade (como pessoal, privada ou pública).
Para usar a redação com métodos de log gerados pela fonte, você deve:
- Classifique seus dados confidenciais usando um sistema de classificação de dados.
- Registre e configure redatores para cada classificação em seu contêiner DI.
- Habilite a redação no pipeline de registro.
- Verifique seus registros para garantir que nenhum dado confidencial seja exposto.
Por exemplo, se você tiver uma mensagem de log que tenha um parâmetro considerado privado:
[LoggerMessage(0, LogLevel.Information, "User SSN: {SSN}")]
public static partial void LogPrivateInformation(
this ILogger logger,
[MyTaxonomyClassifications.Private] string SSN);
Você precisará ter uma configuração semelhante a esta:
using Microsoft.Extensions.Telemetry;
using Microsoft.Extensions.Compliance.Redaction;
var services = new ServiceCollection();
services.AddLogging(builder =>
{
// Enable redaction.
builder.EnableRedaction();
});
services.AddRedaction(builder =>
{
// configure redactors for your data classifications
builder.SetRedactor<StarRedactor>(MyTaxonomyClassifications.Private);
});
public void TestLogging()
{
LogPrivateInformation("MySSN");
}
A saída deve ser assim:
User SSN: *****
Essa abordagem garante que apenas os dados editados sejam registrados, mesmo ao usar APIs de log geradas em tempo de compilação. Você pode usar diferentes redatores para diferentes tipos de dados ou classificações e atualizar sua lógica de redação centralmente.
Para obter mais informações sobre como classificar seus dados, consulte Classificação de dados no .NET. Para obter mais informações sobre redação e redatores, consulte Redação de dados no .NET.
Resumo
Com o advento dos geradores de código-fonte C#, escrever APIs de log de alto desempenho é mais fácil. O uso da abordagem do gerador de código-fonte tem vários benefícios principais:
- Permite que a estrutura de log seja preservada e permite a sintaxe de formato exato exigida pelos Modelos de Mensagem.
- Permite fornecer nomes alternativos para os espaços reservados do modelo e usar especificadores de formato.
- Permite a passagem de todos os dados originais no estado em que se encontram, sem qualquer complicação em torno de como eles são armazenados antes que algo seja feito com eles (além de criar um
string). - Fornece diagnósticos específicos de registro em log e emite avisos para IDs de eventos duplicados.
Além disso, há benefícios em usar manualmente LoggerMessage.Define:
- Sintaxe mais curta e simples: uso declarativo de atributos em vez de codificação clichê.
- Experiência guiada do desenvolvedor: o gerador dá avisos para ajudar os desenvolvedores a fazer a coisa certa.
- Suporte para um número arbitrário de parâmetros de log.
LoggerMessage.Definesuporta um máximo de seis. - Suporte para nível de log dinâmico. Não é possível com apenas
LoggerMessage.Define.