Registrazione in C# e .NET
.NET supporta la registrazione strutturata e a prestazioni elevate tramite l'API ILogger per monitorare il comportamento dell'applicazione e diagnosticare i problemi. I log possono essere scritti in destinazioni diverse configurando diversi provider di registrazione. I provider di registrazione di base sono incorporati e sono disponibili anche molti provider di terze parti.
Questo primo esempio illustra le nozioni di base, ma è adatto solo per un'app console semplice. Questa app console di esempio si basa sui pacchetti NuGet seguenti:
Nella sezione successiva viene illustrato come migliorare il codice considerando la scalabilità, le prestazioni, la configurazione e i modelli di programmazione tipici.
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");
L'esempio precedente:
- Crea un oggetto ILoggerFactory.
ILoggerFactory
archivia tutte le configurazioni che determinano dove vengono inviati i messaggi di log. In questo caso, si configura il provider di registrazione della console in modo che i messaggi di log vengano scritti nella console. - Crea un oggetto ILogger con una categoria denominata "Program". La categoria è un oggetto
string
associato a ogni messaggio registrato dall'oggettoILogger
. Viene usato per raggruppare i messaggi di log dalla stessa classe (o categoria) durante la ricerca o il filtro dei log. - Chiama LogInformation per registrare un messaggio a livello
Information
. Il livello di log indica la gravità dell'evento registrato e viene usato per filtrare i messaggi di log meno importanti. La voce di log include anche un modello di messaggio"Hello World! Logging is {Description}."
e una coppia chiave-valoreDescription = fun
. Il nome della chiave (o segnaposto) deriva dalla parola all'interno delle parentesi graffe nel modello e il valore proviene dall'argomento del metodo rimanente.
Il file di progetto per questo esempio include due pacchetti 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="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.0" />
</ItemGroup>
</Project>
Suggerimento
Tutto il codice sorgente di esempio di registrazione è disponibile nel browser Samples per il download. Per altre informazioni, vedere Esplorare gli esempi di codice: Registrazione in .NET.
Quando si esegue l'accesso in uno scenario meno semplice, è consigliabile apportare diverse modifiche all'esempio precedente:
Se l'applicazione usa l’Inserimento delle dipendenze (DI) o un host, ad esempio WebApplication di ASP.NET o host generico è quindi consigliabile usare oggetti
ILoggerFactory
eILogger
dai rispettivi contenitori di inserimento delle dipendenze anziché crearli direttamente. Per altre informazioni, vedere Integrazione con l'inserimento delle dipendenze e degli host.La registrazione di generazione dell'origine in fase di compilazione è in genere un'alternativa migliore ai metodi di estensione
ILogger
comeLogInformation
. La generazione dell'origine di registrazione offre prestazioni migliori, tipizzazione più avanzata ed evita la distribuzione di costantistring
in tutti i metodi. Il compromesso consiste nel fatto che l'uso di questa tecnica richiede un po' più di codice.
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);
}
- La procedura consigliata per i nomi delle categorie di log consiste nell'usare il nome completo della classe che crea il messaggio di log. Ciò consente di correlare i messaggi di log al codice che li ha generati e offre un buon livello di controllo durante il filtro dei log. CreateLogger accetta un
Type
per semplificare questa denominazione.
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");
}
}
- Se non si usano i log della console come unica soluzione di monitoraggio di produzione, aggiungere i provider di registrazione che si prevede di usare. Ad esempio, è possibile usare OpenTelemetry per inviare log tramite protocollo OTLP (OpenTelemetry):
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");
Se l'applicazione usa l’Inserimento delle dipendenze (DI) o un host, ad esempio WebApplication di ASP.NET o host generico è quindi consigliabile usare oggetti ILoggerFactory
e ILogger
dal contenitore di inserimento delle dipendenze anziché crearli direttamente.
Questo esempio ottiene un oggetto ILogger in un'app ospitata usando ASP.NET API minime:
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);
}
L'esempio precedente:
- È stato creato un servizio singleton denominato
ExampleHandler
ed è stato eseguito il mapping delle richieste Web in ingresso per eseguire la funzioneExampleHandler.HandleRequest
. - La riga 8 definisce un costruttore primario per ExampleHandler, una funzionalità aggiunta in C# 12. L'uso del costruttore C# di stile precedente funziona altrettanto bene, ma è un po' più dettagliato.
- Il costruttore definisce un parametro di tipo
ILogger<ExampleHandler>
. ILogger<TCategoryName> deriva da ILogger e indica la categoria di cui dispone l'oggettoILogger
. Il contenitore DI individua un oggettoILogger
con la categoria corretta e lo specifica come argomento del costruttore. Se non esiste ancora alcunaILogger
con tale categoria, il contenitore di inserimento delle dipendenze lo crea automaticamente dalILoggerFactory
nel provider di servizi. - Il parametro
logger
ricevuto nel costruttore è stato usato per la registrazione nella funzioneHandleRequest
.
I generatori host inizializzano la configurazione predefinita, quindi aggiungono un oggetto ILoggerFactory
configurato al contenitore di inserimento delle dipendenze dell'host al momento della compilazione dell'host. Prima che l'host venga compilato, è possibile modificare la configurazione della registrazione tramite HostApplicationBuilder.Logging, WebApplicationBuilder.Logging o API simili in altri host. Gli host applicano anche la configurazione di registrazione dalle origini di configurazione predefinite come variabili di ambiente e appsettings.json . Per altre informazioni, vedi Configurazione in .NET.
In questo esempio viene espanso quello precedente per personalizzare l'oggetto ILoggerFactory
fornito da WebApplicationBuilder
. Aggiunge OpenTelemetry come provider di registrazione che trasmette i log tramite OTLP (protocollo OpenTelemetry):
var builder = WebApplication.CreateBuilder(args);
builder.Logging.AddOpenTelemetry(logging => logging.AddOtlpExporter());
builder.Services.AddSingleton<ExampleHandler>();
var app = builder.Build();
Se si usa un contenitore di inserimento delle dipendenze senza un host, usare AddLogging per configurare e aggiungere ILoggerFactory
al contenitore.
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);
}
}
L'esempio precedente:
- Creazione di un contenitore del servizio di inserimento delle dipendenze contenente un oggetto
ILoggerFactory
configurato per la scrittura nella console - Aggiunta di un singleton
ExampleService
al contenitore - È stata creata un'istanza
ExampleService
dal contenitore di inserimento delle dipendenze che ha creato automaticamente un oggettoILogger<ExampleService>
da usare come argomento del costruttore. - Richiamato
ExampleService.DoSomeWork
che usava perILogger<ExampleService>
registrare un messaggio nella console.
La configurazione della registrazione viene impostata nel codice o tramite origini esterne, ad esempio file di configurazione e variabili di ambiente. L'uso della configurazione esterna è utile quando possibile perché può essere modificato senza ricompilare l'applicazione. Tuttavia, alcune attività, ad esempio l'impostazione dei provider di registrazione, possono essere configurate solo dal codice.
Per le app che usano un host, la configurazione della registrazione viene in genere fornita dalla sezione "Logging"
di file appsettings.{Environment}
.json. Per le app che non usano un host, le origini di configurazione esterne vengono configurate in modo esplicito o configurate nel codice.
Di seguito il file appsettings. Development.json viene generato dai modelli di servizio .NET Worker:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
Nel codice JSON precedente:
- Vengono specificate le categorie
"Default"
,"Microsoft"
e"Microsoft.Hosting.Lifetime"
livello di log. - Il valore
"Default"
viene applicato a tutte le categorie non specificate diversamente, rendendo effettivamente tutti i valori predefiniti per tutte le categorie"Information"
. È possibile eseguire l'override di questo comportamento specificando un valore per una categoria. - La categoria
"Microsoft"
si applica a tutte le categorie che iniziano con"Microsoft"
. - La categoria
"Microsoft"
registra a livello di logWarning
e superiore. - La categoria
"Microsoft.Hosting.Lifetime"
è più specifica della categoria"Microsoft"
, quindi i log delle categorie"Microsoft.Hosting.Lifetime"
a livello di log"Information"
e superiori. - Non viene specificato un provider di log specifico, quindi
LogLevel
si applica a tutti i provider di registrazione abilitati, ad eccezione del Registro eventi di Windows.
La proprietà Logging
può avere le proprietà LogLevel e quelle del provider di log. LogLevel
specifica il livello minimo per la registrazione per le categorie selezionate. Nel codice JSON precedente vengono specificati i livelli di log Information
e Warning
. LogLevel
indica la gravità del log con valori compresi da 0 a 6:
Trace
= 0, Debug
= 1, Information
= 2, Warning
= 3, Error
= 4, Critical
= 5 e None
= 6.
Quando si specifica un LogLevel
, la registrazione viene abilitata per i messaggi al livello specificato e superiori. Nel codice JSON precedente, la categoria Default
viene registrata per Information
e versioni successive. Ad esempio, vengono registrati i messaggi Information
, Warning
, Error
e Critical
. Se non viene specificato un LogLevel
, viene impostato il livello di log predefinito Information
. Per altre informazioni, vedere Livelli di log.
Una proprietà del provider può specificare una proprietà LogLevel
. LogLevel
per un provider specifica i livelli da registrare per tale provider e sostituisce le impostazioni di log non specifiche del provider. Considerare il file appsettings.json seguente:
{
"Logging": {
"LogLevel": {
"Default": "Error",
"Microsoft": "Warning"
},
"Debug": {
"LogLevel": {
"Default": "Information",
"Microsoft.Hosting": "Trace"
}
},
"EventSource": {
"LogLevel": {
"Default": "Warning"
}
}
}
}
Le impostazioni in Logging.{ProviderName}.LogLevel
sostituiscono le impostazioni in Logging.LogLevel
. Nel codice JSON precedente il livello di log predefinito del provider di Debug
è impostato su Information
:
Logging:Debug:LogLevel:Default:Information
L'impostazione precedente specifica il livello di log Information
per ogni categoria Logging:Debug:
, ad eccezione di Microsoft.Hosting
. Quando viene elencata una categoria specifica, la categoria specifica sostituisce la categoria predefinita. Nel codice JSON precedente le categorie di Logging:Debug:LogLevel
"Microsoft.Hosting"
e "Default"
ignorano le impostazioni in Logging:LogLevel
È possibile specificare il livello minimo di log per:
- Provider specifici: ad esempio,
Logging:EventSource:LogLevel:Default:Information
- Categorie specifiche: ad esempio,
Logging:LogLevel:Microsoft:Warning
- Tutti i provider e tutte le categorie:
Logging:LogLevel:Default:Warning
I log al di sotto del livello minimo non vengono:
- Passati al provider.
- Registrati o visualizzati.
Per eliminare tutti i log, specificare LogLevel.None. Il valore di LogLevel.None
è 6, che è maggiore di LogLevel.Critical
(5).
Se un provider supporta gli ambiti di log, IncludeScopes
indica se tali ambiti sono abilitati. Per altre informazioni, vedere Ambiti di log
Il file di appsettings.json seguente contiene le impostazioni per tutti i provider predefiniti:
{
"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"
}
}
}
}
Nell'esempio precedente:
- Le categorie e i livelli non sono valori suggeriti. L'esempio viene fornito per visualizzare tutti i provider predefiniti.
- Le impostazioni in
Logging.{ProviderName}.LogLevel
sostituiscono le impostazioni inLogging.LogLevel
. Ad esempio, il livello inDebug.LogLevel.Default
sostituisce il livello inLogLevel.Default
. - Viene usato l'alias di ogni provider. Ogni provider definisce un alias che può essere utilizzato nella configurazione al posto del nome completo di tipo. Gli alias dei provider predefiniti sono:
Console
Debug
EventSource
EventLog
AzureAppServicesFile
AzureAppServicesBlob
ApplicationInsights
Il livello di log può essere impostato da uno qualsiasi dei provider di configurazione. Ad esempio, è possibile creare una variabile di ambiente persistente denominata Logging:LogLevel:Microsoft
con un valore di Information
.
Creare e assegnare una variabile di ambiente persistente, in base al valore del livello di log.
:: Assigns the env var to the value
setx "Logging__LogLevel__Microsoft" "Information" /M
In una nuova istanza del prompt dei comandi, leggere la variabile di ambiente.
:: Prints the env var value
echo %Logging__LogLevel__Microsoft%
L'impostazione dell'ambiente precedente è persistente nell'ambiente. Per testare le impostazioni quando si usa un'app creata con i modelli di servizio di lavoro .NET, usare il comando dotnet run
nella directory del progetto dopo l'assegnazione della variabile di ambiente.
dotnet run
Suggerimento
Dopo aver impostato una variabile di ambiente, riavviare l'ambiente di sviluppo integrato (IDE) per assicurarsi che siano disponibili le variabili di ambiente appena aggiunte.
In Servizio app di Azure selezionare Nuova impostazione applicazione nella pagina Impostazioni > Configurazione. Le impostazioni applicazione del Servizio app di Azure sono:
- Crittografate mentre sono inattive e trasmesse su un canale crittografato.
- Esposte come variabili di ambiente.
Per altre informazioni sull'impostazione dei valori di configurazione di ASP.NET Core usando le variabili di ambiente, vedere Variabili di ambiente.
Per configurare l'accesso al codice, usare l'API ILoggingBuilder. È possibile accedervi da posizioni diverse:
- Quando si crea direttamente
ILoggerFactory
, configurare in LoggerFactory.Create. - Quando si usa l'inserimento delle dipendenze senza un host, configurare in LoggingServiceCollectionExtensions.AddLogging.
- Quando si usa un host, configurare con HostApplicationBuilder.Logging, WebApplicationBuilder.Logging o altre API specifiche dell'host.
In questo esempio viene illustrata l'impostazione del provider di registrazione console e diversi filtri.
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");
Nell'esempio precedente AddFilter viene usato per regolare il livello di log abilitato per varie categorie. AddConsole viene usato per aggiungere il provider di registrazione della console. Per impostazione predefinita, i log con gravità Debug
non sono abilitati, ma poiché la configurazione ha modificato i filtri, nella console viene visualizzato il messaggio di debug "Hello Everyone".
Quando viene creato un oggetto ILogger<TCategoryName>, l'oggetto ILoggerFactory seleziona una singola regola per ogni provider da applicare al logger. Tutti i messaggi scritti da un'istanza di ILogger
vengono filtrati in base alle regole selezionate. Tra le regole disponibili viene selezionata la regola più specifica per ogni coppia di categoria e provider.
L'algoritmo seguente viene usato per ogni provider quando viene creato un ILogger
per una determinata categoria:
- Selezionare tutte le regole corrispondenti al provider o al relativo alias. Se non viene trovata alcuna corrispondenza, selezionare tutte le regole con un provider vuoto.
- Dal risultato del passaggio precedente, selezionare le regole con il prefisso di categoria corrispondente più lungo. Se non viene trovata alcuna corrispondenza, selezionare tutte le regole che non specificano una categoria.
- Se sono selezionate più regole, scegliere l'ultima.
- Se non sono selezionate regole, usare LoggingBuilderExtensions.SetMinimumLevel(ILoggingBuilder, LogLevel) per specificare il livello di registrazione minimo.
Quando viene creato un oggetto ILogger
, viene specificata una categoria. La categoria è inclusa in ogni messaggio di log creato da tale istanza di ILogger
. La stringa di categoria è arbitraria, ma per convenzione si usa il nome della classe completo. Ad esempio, in un'applicazione con un servizio definito come l'oggetto seguente, la categoria potrebbe essere "Example.DefaultService"
:
namespace Example
{
public class DefaultService : IService
{
private readonly ILogger<DefaultService> _logger;
public DefaultService(ILogger<DefaultService> logger) =>
_logger = logger;
// ...
}
}
Se si vuole categorizzare ulteriormente, per convenzione si usa un nome gerarchico aggiungendo una sottocategoria al nome della classe completo e specificando in modo esplicito la categoria usando LoggerFactory.CreateLogger:
namespace Example
{
public class DefaultService : IService
{
private readonly ILogger _logger;
public DefaultService(ILoggerFactory loggerFactory) =>
_logger = loggerFactory.CreateLogger("Example.DefaultService.CustomCategory");
// ...
}
}
La chiamata CreateLogger
con un nome fisso può essere utile quando viene usata in più classi/tipi in modo che gli eventi possano essere organizzati per categoria.
L'uso di ILogger<T>
equivale a chiamare CreateLogger
con il nome completo di tipo T
.
Nella tabella seguente sono elencati i valori LogLevel, il metodo di estensione Log{LogLevel}
pratico e l'utilizzo suggerito:
LogLevel | Valore | metodo | Descrizione |
---|---|---|---|
Traccia | 0 | LogTrace | Log che contengono i messaggi più dettagliati. Questi messaggi possono contenere dati sensibili dell'app. Questi messaggi sono disabilitati per impostazione predefinita e non devono essere abilitati in produzione. |
Debug | 1 | LogDebug | Per il debug e lo sviluppo. Usare con cautela in produzione a causa del volume elevato. |
Informazioni | 2 | LogInformation | Tenere traccia del flusso generale dell'app. Può avere valore a lungo termine. |
Avvertenza | 3 | LogWarning | Per gli eventi imprevisti o anomali. In genere include errori o condizioni che non causano l'esito negativo dell'app. |
Errore | 4 | LogError | Per errori ed eccezioni che non possono essere gestiti. Questi messaggi indicano un errore nell'operazione o nella richiesta corrente, non un errore a livello di app. |
Critico | 5 | LogCritical | Per gli errori che richiedono attenzione immediata. Esempi: scenari di perdita di dati, spazio su disco insufficiente. |
Nessuno | 6 | Specifica che non deve essere scritto alcun messaggio. |
Nella tabella precedente, LogLevel
è elencato in ordine crescente di gravità.
Il primo parametro del metodo Log, LogLevel, indica la gravità del log. Anziché chiamare Log(LogLevel, ...)
, la maggior parte degli sviluppatori chiama i metodi di estensione Log{LogLevel}. I Log{LogLevel}
metodi di estensione chiamano il metodo Log
e specificano il LogLevel
. Ad esempio, le due chiamate di registrazione seguenti sono equivalenti a livello funzionale e producono lo stesso log:
public void LogDetails()
{
var logMessage = "Details for log.";
_logger.Log(LogLevel.Information, AppLogEvents.Details, logMessage);
_logger.LogInformation(AppLogEvents.Details, logMessage);
}
AppLogEvents.Details
è l'ID evento ed è rappresentato in modo implicito da un valore Int32 costante. AppLogEvents
è una classe che espone varie costanti di identificatore denominato e viene visualizzata nella sezione ID evento log.
Il codice seguente crea i log 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;
}
Nel codice precedente, il primo Log{LogLevel}
parametro, AppLogEvents.Read
, è l'ID evento Log. Il secondo parametro è un modello di messaggio con segnaposto per i valori degli argomenti forniti dai parametri dei metodi rimanenti. I parametri dei metodi sono descritti nella sezione relativa al modello di messaggio più avanti in questo articolo.
Configurare il livello di log appropriato e chiamare i metodi Log{LogLevel}
corretti per controllare la quantità di output del log scritta in un determinato supporto di archiviazione. Ad esempio:
- In produzione:
- La registrazione ai livelli
Trace
oDebug
produce un volume elevato di messaggi di log dettagliati. Per controllare i costi e non superare i limiti di archiviazione dei dati, registrare i messaggi di livelloTrace
eDebug
in un archivio dati a basso costo per volumi elevati. Prendere in considerazione la limitazione diTrace
eDebug
a categorie specifiche. - La registrazione ai livelli da
Warning
aCritical
dovrebbe produrre pochi messaggi di log.- I costi e i limiti di archiviazione in genere non sono un problema.
- Pochi log consentono una maggiore flessibilità nelle scelte dell'archivio dati.
- La registrazione ai livelli
- In fase di sviluppo:
- Impostare su
Warning
. - Aggiungere messaggi
Trace
oDebug
durante la risoluzione dei problemi. Per limitare l'output, impostareTrace
oDebug
solo per le categorie in fase di indagine.
- Impostare su
Il codice JSON seguente imposta Logging:Console:LogLevel:Microsoft:Information
:
{
"Logging": {
"LogLevel": {
"Microsoft": "Warning"
},
"Console": {
"LogLevel": {
"Microsoft": "Information"
}
}
}
}
Ogni log può specificare un identificatore di evento, EventId è una struttura con Id
e proprietà Name
di sola lettura e facoltative. Il codice sorgente di esempio usa la classe AppLogEvents
per definire gli ID 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;
// ...
}
Suggerimento
Per altre informazioni sulla conversione di un int
in un EventId
, vedere Operatore EventId.Implicit(Int32 in EventId).
Un ID evento associa un set di eventi. Ad esempio, tutti i log correlati alla lettura dei valori da un repository potrebbero essere 1001
.
Il provider di registrazione può registrare l'ID evento in un campo ID, nel messaggio di registrazione o per nulla. Il provider Debug non visualizza gli ID evento. Il provider Console visualizza gli ID evento tra parentesi quadre dopo la categoria:
info: Example.DefaultService.GetAsync[1001]
Reading value for a1b2c3
warn: Example.DefaultService.GetAsync[4000]
GetAsync(a1b2c3) not found
Alcuni provider di registrazione archiviano l'ID evento in un campo, che consente di filtrare le informazioni in base all'ID.
Ogni API di log specifica un modello di messaggio. Il modello di messaggio può contenere segnaposto per i quali vengono forniti argomenti. Usare nomi per i segnaposto, non numeri. L'ordine dei segnaposto, non il loro nome, determina i parametri da usare per fornire i valori corrispondenti. Nel codice seguente i nomi dei parametri non sono in sequenza nel modello di messaggio:
string p1 = "param1";
string p2 = "param2";
_logger.LogInformation("Parameter values: {p2}, {p1}", p1, p2);
Il codice precedente crea un messaggio di log con i valori dei parametri in sequenza:
Parameter values: param1, param2
Nota
Tenere presente quando si usano più segnaposto all'interno di un singolo modello di messaggio, perché sono basati su ordinali. I nomi sono non utilizzati per allineare gli argomenti ai segnaposto.
Questo approccio consente ai provider di registrazione di implementare la registrazione semantica o strutturata. Al sistema di registrazione vengono passati gli argomenti e non solo il modello di messaggio formattato. Ciò consente ai provider di registrazione di archiviare i valori dei parametri come campi. Si consideri il metodo logger seguente:
_logger.LogInformation("Getting item {Id} at {RunTime}", id, DateTime.Now);
Ad esempio, quando si esegue la registrazione in Archiviazione tabelle di Azure:
- Ogni entità Tabella di Azure può avere le proprietà
ID
eRunTime
. - Le tabelle con proprietà semplificano le query sui dati registrati. Ad esempio una query può trovare tutti i log entro un determinato intervallo
RunTime
senza che sia necessario analizzare il tempo fuori dal messaggio di testo.
I modelli di messaggio di log supportano la formattazione segnaposto. I modelli sono liberi di specificare qualsiasi di formato valido per l'argomento di tipo specificato. Si consideri ad esempio il modello di messaggio di logger Information
seguente:
_logger.LogInformation("Logged on {PlaceHolderName:MMMM dd, yyyy}", DateTimeOffset.UtcNow);
// Logged on January 06, 2022
Nell'esempio precedente l'istanza DateTimeOffset
è il tipo che corrisponde a PlaceHolderName
nel modello di messaggio del logger. Questo nome può essere qualsiasi elemento perché i valori sono basati su ordinali. Il formato MMMM dd, yyyy
è valido per il tipo DateTimeOffset
.
Per altre informazioni sulla formattazione di DateTime
e DateTimeOffset
, vedere Stringhe di formato di data e ora personalizzate.
Negli esempi seguenti viene illustrato come formattare un modello di messaggio usando la sintassi del segnaposto {}
. Inoltre, viene visualizzato un esempio di escape della sintassi del segnaposto {}
con il relativo output. Viene infine mostrata anche l'interpolazione di stringhe con segnaposto per la creazione di modelli:
logger.LogInformation("Number: {Number}", 1); // Number: 1
logger.LogInformation("{{Number}}: {Number}", 3); // {Number}: 3
logger.LogInformation($"{{{{Number}}}}: {{Number}}", 5); // {Number}: 5
Suggerimento
- Nella maggior parte dei casi, è consigliabile usare la formattazione del modello di messaggio di log durante la registrazione. L'uso dell'interpolazione di stringhe può causare problemi di prestazioni.
- Regola di analisi del codice CA2254: il modello deve essere un'espressione statica consente di avvisare le posizioni in cui i messaggi di log non usano la formattazione corretta.
I metodi logger hanno overload che accettano un parametro di eccezione:
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);
}
}
La registrazione delle eccezioni è specifica del provider.
Se il livello di log predefinito non è impostato, il valore predefinito del livello di log è Information
.
Si consideri ad esempio l'app del servizio di lavoro seguente:
- Creato con i modelli di lavoro .NET.
- appsettings.json e appsettings.Development.json eliminato o rinominato.
Con la configurazione precedente, il passaggio alla pagina della privacy o alla home page produce molti messaggi Trace
, Debug
e Information
con Microsoft
nel nome della categoria.
Il codice seguente imposta il livello di log predefinito quando non è impostato nella configurazione:
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Logging.SetMinimumLevel(LogLevel.Warning);
using IHost host = builder.Build();
await host.RunAsync();
Una funzione di filtro viene richiamata per tutti i provider e le categorie a cui non sono assegnate regole tramite la configurazione o il codice:
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();
Il codice precedente visualizza i log della console quando la categoria contiene Example
o Microsoft
e il livello di log è Information
o superiore.
Un ambito raggruppa un set di operazioni logiche. Questo raggruppamento può essere usato per collegare gli stessi dati a ogni log creato come parte di un set. Ad esempio, ogni log creato come parte dell'elaborazione di una transazione può includere l'ID transazione.
Un ambito:
- È un tipo IDisposable restituito dal metodo BeginScope.
- Dura fino a quando non viene eliminato.
I provider seguenti supportano gli ambiti:
Un ambito si usa mediante il wrapping delle chiamate del logger in un blocco 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;
}
Il codice JSON seguente abilita gli ambiti per il provider di console:
{
"Logging": {
"Debug": {
"LogLevel": {
"Default": "Information"
}
},
"Console": {
"IncludeScopes": true,
"LogLevel": {
"Microsoft": "Warning",
"Default": "Information"
}
},
"LogLevel": {
"Default": "Debug"
}
}
}
Il codice seguente abilita gli ambiti per il provider Console:
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Logging.ClearProviders();
builder.Logging.AddConsole(options => options.IncludeScopes = true);
using IHost host = builder.Build();
await host.RunAsync();
Il codice seguente esegue la registrazione in Main
ottenendo un'istanza ILogger
dall'inserimento delle dipendenze dopo la costruzione dell'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();
Il codice precedente si basa su due pacchetti NuGet:
Il file di progetto sarà simile al seguente:
<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>
La registrazione deve essere così rapida da non giustificare l'impatto sulle prestazioni del codice asincrono. Se un archivio dati di registrazione è lento, non scrivervi direttamente. Valutare invece la possibilità di scrivere i messaggi di log prima in un archivio veloce e quindi spostarli nell'archivio lento in un secondo momento. Ad esempio, quando la registrazione viene eseguita in SQL Server, non farlo direttamente in un metodo Log
, poiché i metodi Log
sono sincroni. Al contrario, aggiungere i messaggi di log in modo sincrono a una coda in memoria e usare un ruolo di lavoro in background per eseguire il pull dei messaggi dalla coda per eseguire le operazioni asincrone di push dei dati in SQL Server.
L'API di registrazione non include uno scenario per modificare i livelli di log mentre un'app è in esecuzione. Tuttavia, alcuni provider di configurazione sono in grado di ricaricare la configurazione, che diventa effettiva immediatamente per la configurazione della registrazione. Ad esempio, il provider di configurazione file ricarica la configurazione della registrazione per impostazione predefinita. Se la configurazione viene modificata nel codice durante l'esecuzione di un'app, l'app può chiamare IConfigurationRoot.Reload per aggiornare la configurazione di registrazione dell'app.
Le interfacce e le implementazioni ILogger<TCategoryName> e ILoggerFactory sono incluse nella maggior parte degli elementi SDK .NET come riferimento implicito al pacchetto. Sono disponibili anche in modo esplicito nei pacchetti NuGet seguenti quando non viene fatto alcun riferimento in modo implicito:
- Le interfacce sono in Microsoft.Extensions.Logging.Abstractions.
- Le implementazioni predefinite sono in Microsoft.Extensions.Logging.
Per altre informazioni su quali elementi .NET SDK includano riferimenti impliciti ai pacchetti, vedere .NET SDK: tabella per lo spazio dei nomi implicito.
- Provider di registrazione in .NET
- Implementare un provider di registrazione personalizzato in .NET
- Formattazione del log della console
- Registrazione ad alte prestazioni in .NET
- Indicazioni sulla registrazione per gli autori di librerie .NET
- I bug relativi alla registrazione devono essere creati nel repository github.com/dotnet/runtime/
Feedback su .NET
.NET è un progetto di open source. Selezionare un collegamento per fornire feedback: