Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
.NET 6 introduce il tipo LoggerMessageAttribute. Questo attributo fa parte dello spazio dei nomi Microsoft.Extensions.Logging e, se usato, genera API di registrazione con prestazioni elevate. Il supporto per la registrazione di generazione dell'origine progettato per offrire una soluzione di registrazione altamente utilizzabile e ad alte prestazioni per le applicazioni .NET moderne. Il codice sorgente generato automaticamente si basa sull'interfaccia ILogger insieme alle funzionalità LoggerMessage.Define.
Il generatore di origine viene attivato quando LoggerMessageAttribute viene usato nei metodi di registrazione partial. Quando viene attivato, è in grado di generare automaticamente l'implementazione dei partial metodi che sta decorando o produrre diagnostiche in fase di compilazione con suggerimenti sull'utilizzo appropriato. La soluzione di registrazione in fase di compilazione è notevolmente più veloce in fase di esecuzione rispetto agli approcci di registrazione esistenti. Questo risultato viene raggiunto eliminando il più possibile la conversione boxing, le allocazioni temporanee e le copie.
Utilizzo di base
Per usare LoggerMessageAttribute, la classe e il metodo di utilizzo devono essere partial. Il generatore di codice viene attivato in fase di compilazione e genera un'implementazione del metodo partial.
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);
}
Nell'esempio precedente il metodo di registrazione è static e il livello di log viene specificato nella definizione dell'attributo. Quando si usa l'attributo in un contesto statico, l'istanza di ILogger è necessaria come parametro oppure modificare la definizione per usare la parola chiave this per definire il metodo come metodo di estensione.
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);
}
È possibile scegliere di usare l'attributo anche in un contesto non statico. Si consideri l'esempio seguente in cui il metodo di registrazione viene dichiarato come metodo di istanza. In questo contesto, il metodo di registrazione ottiene il logger accedendo a un campo ILogger nella classe contenitore.
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 partire da .NET 9, il metodo di registrazione può anche ottenere il logger da un parametro del costruttore primario ILogger nella classe contenitore.
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 sono presenti sia un campo ILogger che un parametro del costruttore primario, il metodo di log ottiene il logger dal campo.
In alcuni casi, il livello di log deve essere dinamico anziché essere integrato in modo statico nel codice. A tale scopo, è possibile omettere il livello di log dall'attributo e richiederlo invece come parametro al metodo di registrazione.
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);
}
È possibile omettere il messaggio di registrazione e String.Empty viene fornito per il messaggio. Lo stato contiene gli argomenti, formattati come coppie chiave-valore.
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);
}
Si consideri l'output di registrazione di esempio quando si usa il formattatore JsonConsole.
{
"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}."
}
}
Vincoli del metodo di log
Quando si usa LoggerMessageAttribute nei metodi di registrazione, è necessario seguire alcuni vincoli:
- I metodi di registrazione devono essere
partiale restituirevoid. - I nomi dei metodi di registrazione non devono iniziare con un carattere di sottolineatura.
- I nomi dei parametri dei metodi di registrazione non devono iniziare con un carattere di sottolineatura.
- I metodi di registrazione non possono essere generici.
- Se un metodo di registrazione è
static, l'istanza diILoggerè obbligatoria come parametro.
Il modello di generazione del codice dipende dalla compilazione del codice con un compilatore C# moderno, versione 9 o successiva. Il compilatore C# 9.0 è diventato disponibile con .NET 5. Per eseguire l'aggiornamento a un compilatore C# moderno, modificare il file di progetto in modo che sia destinato a C# 9.0.
<PropertyGroup>
<LangVersion>9.0</LangVersion>
</PropertyGroup>
Per altre informazioni, vedere controllo delle versioni del linguaggio C#.
Anatomia del metodo di log
La ILogger.Log firma accetta LogLevel e facoltativamente , Exceptioncome illustrato nell'esempio di codice seguente.
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);
}
Come regola generale, la prima istanza di ILogger, LogLevele Exception vengono trattate appositamente nella firma del metodo di log del generatore di origine. Le istanze successive vengono considerate come parametri normali per il modello di messaggio:
// 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
Gli avvisi generati forniscono informazioni dettagliate sull'utilizzo corretto di LoggerMessageAttribute. Nell'esempio precedente l'oggetto WarningLogMethod restituisce un DiagnosticSeverity.Warning di SYSLIB0025.
Don't include a template for `ex` in the logging message since it is implicitly taken care of.
Supporto del nome del modello senza distinzione tra maiuscole e minuscole
Il generatore esegue un confronto senza distinzione tra gli elementi nel modello di messaggio e i nomi degli argomenti nel messaggio di log. Ciò significa che quando ILogger enumera lo stato, l'argomento viene prelevato dal modello di messaggio, il che può rendere i log più gradevoli da utilizzare:
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");
}
}
Si consideri l'output di registrazione di esempio quando si usa il formattatore JsonConsole:
{
"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}!"
}
}
Ordine dei parametri indeterminato
Non esistono vincoli sull'ordinamento dei parametri del metodo di log. Uno sviluppatore può definire il ILogger come ultimo parametro, anche se potrebbe sembrare un po' imbarazzante.
[LoggerMessage(
EventId = 110,
Level = LogLevel.Debug,
Message = "M1 {Ex3} {Ex2}")]
static partial void LogMethod(
Exception ex,
Exception ex2,
Exception ex3,
ILogger logger);
Suggerimento
L'ordine dei parametri in un metodo di log non deve corrispondere all'ordine dei segnaposto del modello. I nomi segnaposto nel modello devono invece corrispondere ai parametri. Si consideri l’output JsonConsole seguente e l'ordine degli errori.
{
"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}"
}
}
Altri esempi di registrazione
Gli esempi seguenti illustrano come recuperare il nome dell'evento, impostare il livello di log in modo dinamico e formattare i parametri di registrazione. I metodi di registrazione sono:
-
LogWithCustomEventName: recuperare il nome dell'evento tramite l’attributoLoggerMessage. -
LogWithDynamicLogLevel: impostare il livello di log in modo dinamico per consentire l'impostazione del livello di log in base all'input di configurazione. -
UsingFormatSpecifier: usare gli identificatori di formato per formattare i parametri di registrazione.
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);
}
}
Si consideri l'output di registrazione di esempio quando si usa il formattatore SimpleConsole:
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
Si consideri l'output di registrazione di esempio quando si usa il formattatore JsonConsole:
{
"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}"
}
}
Redazione di informazioni sensibili nei registri
Quando si registrano dati sensibili, è importante evitare l'esposizione accidentale. Anche con i metodi di registrazione generati in fase di compilazione, la registrazione di valori sensibili non elaborati può causare perdite di dati e problemi di conformità.
La libreria Microsoft.Extensions.Telemetry offre funzionalità avanzate di registrazione e arricchimento dei dati di telemetria per le applicazioni .NET. Estende la pipeline di log per applicare automaticamente la redazione ai dati classificati durante la scrittura dei log. Permette di applicare le politiche di protezione dei dati in tutta l'applicazione integrando la redazione nel flusso di lavoro di registrazione. È progettato per le applicazioni che richiedono dati di telemetria sofisticati e informazioni dettagliate sulla registrazione.
Per abilitare la redaction, usare la libreria Microsoft.Extensions.Compliance.Redaction . Questa libreria fornisce redattori, ovvero componenti che trasformano i dati sensibili, ad esempio cancellandoli, mascherandoli, o eseguendo l'hashing, affinché l’output sia sicuro. I redattori sono selezionati in base alla classificazione dei dati, che consente di etichettare i dati in base alla loro riservatezza, ad esempio come personale, privato o pubblico.
Per utilizzare la redazione con i metodi di registrazione generati dalla sorgente, è necessario:
- Classificare i dati sensibili usando un sistema di classificazione dei dati.
- Registrare e configurare i redattori per ogni classificazione nel contenitore DI.
- Abilitare la redazione nella pipeline di registrazione.
- Controllare i log per assicurarsi che non siano esposti dati sensibili.
Ad esempio, se si dispone di un messaggio di log con un parametro considerato privato:
[LoggerMessage(0, LogLevel.Information, "User SSN: {SSN}")]
public static partial void LogPrivateInformation(
this ILogger logger,
[MyTaxonomyClassifications.Private] string SSN);
Sarà necessario avere un'impostazione simile alla seguente:
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");
}
L'output dovrebbe essere simile al seguente:
User SSN: *****
Questo approccio garantisce che vengano registrati solo i dati elaborati, anche quando si usano LE API di registrazione generate in fase di compilazione. È possibile usare redattori diversi per tipi di dati o classificazioni diverse e aggiornare la logica di redazione in modo centralizzato.
Per altre informazioni su come classificare i dati, vedere Classificazione dei dati in .NET. Per ulteriori informazioni sulla redazione e sui redattori, vedere Redazione dei dati in .NET.
Riepilogo
Con l'avvento dei generatori di origine C#, la scrittura di API di registrazione ad alte prestazioni è più semplice. L'uso dell'approccio del generatore di origine presenta diversi vantaggi principali:
- Consente di mantenere la struttura di registrazione e di abilitare la sintassi di formato esatta richiesta dai modelli di messaggio.
- Consente di specificare nomi alternativi per i segnaposto del modello e di utilizzare gli identificatori di formato.
- Consente il passaggio di tutti i dati originali così com'è, senza alcuna complicazione sulla modalità di archiviazione prima che venga eseguita un'operazione (oltre alla creazione di un
string). - Fornisce la diagnostica specifica della registrazione e genera avvisi per gli ID evento duplicati.
Inoltre, esistono vantaggi rispetto all'uso manuale di LoggerMessage.Define:
- Sintassi più breve e semplice: uso di attributi dichiarativi anziché di codifica boilerplate.
- Esperienza di sviluppo guidata: il generatore fornisce avvisi per aiutare gli sviluppatori a fare quello che serve.
- Supporto per un numero arbitrario di parametri di registrazione.
LoggerMessage.Definene supporta un massimo di sei. - Supporto per il livello di log dinamico. Questo non è possibile con
LoggerMessage.Definesolo.