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.

Operazioni preliminari

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'oggetto ILogger. 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-valore Description = 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="8.0.1" />
    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.1" />
  </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.

Registrazione in un'app non semplice

Quando si esegue l'accesso in uno scenario meno semplice, è consigliabile apportare diverse modifiche all'esempio precedente:

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");
    }
}
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");

Integrazione con host e inserimento delle dipendenze

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.

Ottenere un ILogger dall'inserimento delle dipendenze

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 funzione ExampleHandler.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'oggetto ILogger. Il contenitore DI individua un oggetto ILogger con la categoria corretta e lo specifica come argomento del costruttore. Se non esiste ancora alcuna ILogger con tale categoria, il contenitore di inserimento delle dipendenze lo crea automaticamente dal ILoggerFactory nel provider di servizi.
  • Il parametro logger ricevuto nel costruttore è stato usato per la registrazione nella funzione HandleRequest.

ILoggerFactory fornito dall'host

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();

Creare un ILoggerFactory con inserimento delle dipendenze

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 oggetto ILogger<ExampleService> da usare come argomento del costruttore.
  • Richiamato ExampleService.DoSomeWork che usava per ILogger<ExampleService> registrare un messaggio nella console.

Configurare la registrazione

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.

Configurare la registrazione senza 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 log Warning 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 in Logging.LogLevel. Ad esempio, il livello in Debug.LogLevel.Default sostituisce il livello in LogLevel.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

Impostare il livello di log tramite riga di comando, variabili di ambiente e altre configurazioni

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.

Configurare la registrazione con il codice

Per configurare l'accesso al codice, usare l'API ILoggingBuilder. È possibile accedervi da posizioni diverse:

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".

Applicazione delle regole di filtro

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.

Categoria di log

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.

Livello log

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 o Debug 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 livello Trace e Debug in un archivio dati a basso costo per volumi elevati. Prendere in considerazione la limitazione di Trace e Debug a categorie specifiche.
    • La registrazione ai livelli da Warning a Critical 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.
  • In fase di sviluppo:
    • Impostare su Warning.
    • Aggiungere messaggi Trace o Debug durante la risoluzione dei problemi. Per limitare l'output, impostare Trace o Debug solo per le categorie in fase di indagine.

Il codice JSON seguente imposta Logging:Console:LogLevel:Microsoft:Information:

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

ID evento di registrazione

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.

Modello di messaggio di registrazione

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 e RunTime.
  • 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.

Formattazione del modello di messaggio di log

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.

Esempi

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.

Registrare le eccezioni

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.

Livello di log predefinito

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();

Funzione di filtro

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.

Ambiti dei log

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:

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();

Creare log in Main

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>

Evitare l'uso di metodi logger asincroni

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.

Modificare i livelli di log in un'app in esecuzione

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.

Pacchetti NuGet

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:

Per altre informazioni su quali elementi .NET SDK includano riferimenti impliciti ai pacchetti, vedere .NET SDK: tabella per lo spazio dei nomi implicito.

Vedi anche