Registrazione a prestazioni elevate con LoggerMessage in ASP.NET CoreHigh-performance logging with LoggerMessage in ASP.NET Core

Di Luke LathamBy Luke Latham

Le funzionalità di LoggerMessage creano delegati inseribili nella cache che richiedono un numero minore di allocazioni di oggetti e riducono il sovraccarico di calcolo rispetto ai metodi di estensione del logger, ad esempio LogInformation e LogDebug.LoggerMessage features create cacheable delegates that require fewer object allocations and reduced computational overhead compared to logger extension methods, such as LogInformation and LogDebug. Per gli scenari di registrazione a prestazioni elevate, usare LoggerMessage.For high-performance logging scenarios, use the LoggerMessage pattern.

LoggerMessage offre i seguenti vantaggi in termini di prestazioni rispetto ai metodi di estensione del logger:LoggerMessage provides the following performance advantages over Logger extension methods:

  • I metodi di estensione del logger richiedono una "conversione boxing" dei tipi di valori, ad esempio int, in object.Logger extension methods require "boxing" (converting) value types, such as int, into object. LoggerMessage evita la conversione boxing usando campi Action statici e metodi di estensione con parametri fortemente tipizzati.The LoggerMessage pattern avoids boxing by using static Action fields and extension methods with strongly-typed parameters.
  • I metodi di estensione del logger devono analizzare il modello di messaggio (stringa di formato denominata) ogni volta che viene scritto un messaggio del log.Logger extension methods must parse the message template (named format string) every time a log message is written. Solo LoggerMessage richiede una sola analisi del modello durante la definizione del messaggio.LoggerMessage only requires parsing a template once when the message is defined.

Visualizzare o scaricare il codice di esempio (procedura per il download)View or download sample code (how to download)

L'app di esempio illustra le funzionalità di LoggerMessage con un sistema di verifica delle offerte di base.The sample app demonstrates LoggerMessage features with a basic quote tracking system. L'app aggiunge ed elimina le offerte usando un database in memoria.The app adds and deletes quotes using an in-memory database. Durante l'esecuzione di queste operazioni, i messaggi del log vengono generati usando LoggerMessage.As these operations occur, log messages are generated using the LoggerMessage pattern.

LoggerMessage.DefineLoggerMessage.Define

Define(LogLevel, EventId, String) crea un delegato Action per la registrazione di un messaggio.Define(LogLevel, EventId, String) creates an Action delegate for logging a message. Gli overload Define permettono il passaggio di un massimo di sei parametri di tipo in una stringa di formato denominata (modello).Define overloads permit passing up to six type parameters to a named format string (template).

La stringa specificata nel metodo Define è un modello e non una stringa interpolata.The string provided to the Define method is a template and not an interpolated string. I segnaposto vengono inseriti nell'ordine in cui sono specificati i tipi.Placeholders are filled in the order that the types are specified. I nomi dei segnaposto nel modello devono essere descrittivi e coerenti tra i modelli.Placeholder names in the template should be descriptive and consistent across templates. Vengono usati come nomi di proprietà all'interno dei dati di log strutturati.They serve as property names within structured log data. Per i nomi dei segnaposto è consigliabile usare la convenzione Pascal.We recommend Pascal casing for placeholder names. Ad esempio, {Count}, {FirstName}.For example, {Count}, {FirstName}.

Ogni messaggio di log è un elemento Action contenuto in un campo statico creato da LoggerMessage.Define.Each log message is an Action held in a static field created by LoggerMessage.Define. Ad esempio, l'app di esempio crea un campo per descrivere un messaggio di log per una richiesta GET per la pagina di indice (Internal/LoggerExtensions.cs):For example, the sample app creates a field to describe a log message for a GET request for the Index page (Internal/LoggerExtensions.cs):

private static readonly Action<ILogger, Exception> _indexPageRequested;

Per Action, specificare:For the Action, specify:

  • Il livello del log.The log level.
  • Un identificatore di evento univoco (EventId) con il nome del metodo di estensione statico.A unique event identifier (EventId) with the name of the static extension method.
  • Il modello di messaggio (stringa di formato denominata).The message template (named format string).

Una richiesta per la pagina di indice dell'app di esempio imposta:A request for the Index page of the sample app sets the:

  • Il livello del log su Information.Log level to Information.
  • L'ID evento su 1 con il nome del metodo IndexPageRequested.Event id to 1 with the name of the IndexPageRequested method.
  • Il modello di messaggio (stringa di formato denominata) su una stringa.Message template (named format string) to a string.
_indexPageRequested = LoggerMessage.Define(
    LogLevel.Information, 
    new EventId(1, nameof(IndexPageRequested)), 
    "GET request for Index page");

Negli archivi di log strutturati è possibile che venga usato il nome evento quando viene specificato con l'ID evento per arricchire la registrazione.Structured logging stores may use the event name when it's supplied with the event id to enrich logging. Ad esempio, Serilog usa il nome evento.For example, Serilog uses the event name.

Action viene richiamato attraverso un metodo di estensione fortemente tipizzato.The Action is invoked through a strongly-typed extension method. Il metodo IndexPageRequested registra un messaggio per la richiesta GET di una pagina di indice nell'app di esempio:The IndexPageRequested method logs a message for an Index page GET request in the sample app:

public static void IndexPageRequested(this ILogger logger)
{
    _indexPageRequested(logger, null);
}

IndexPageRequested viene chiamato nel logger nel metodo OnGetAsync in Pages/Index.cshtml.cs:IndexPageRequested is called on the logger in the OnGetAsync method in Pages/Index.cshtml.cs:

public async Task OnGetAsync()
{
    _logger.IndexPageRequested();

    Quotes = await _db.Quotes.AsNoTracking().ToListAsync();
}

Esaminare l'output della console dell'app:Inspect the app's console output:

info: LoggerMessageSample.Pages.IndexModel[1]
      => RequestId:0HL90M6E7PHK4:00000001 RequestPath:/ => /Index
      GET request for Index page

Per passare i parametri in un messaggio di log, definire un massimo di sei tipi durante la creazione del campo statico.To pass parameters to a log message, define up to six types when creating the static field. L'app di esempio registra una stringa quando viene aggiunta un'offerta definendo un tipo string per il campo Action:The sample app logs a string when adding a quote by defining a string type for the Action field:

private static readonly Action<ILogger, string, Exception> _quoteAdded;

Il modello di messaggio di log del delegato riceve i valori dei segnaposto dai tipi specificati.The delegate's log message template receives its placeholder values from the types provided. L'app di esempio definisce un delegato per l'aggiunta di un'offerta dove il parametro dell'offerta è un elemento string:The sample app defines a delegate for adding a quote where the quote parameter is a string:

_quoteAdded = LoggerMessage.Define<string>(
    LogLevel.Information, 
    new EventId(2, nameof(QuoteAdded)), 
    "Quote added (Quote = '{Quote}')");

Il metodo di estensione statico per l'aggiunta di un'offerta, QuoteAdded, riceve il valore dell'argomento dell'offerta e lo passa al delegato Action:The static extension method for adding a quote, QuoteAdded, receives the quote argument value and passes it to the Action delegate:

public static void QuoteAdded(this ILogger logger, string quote)
{
    _quoteAdded(logger, quote, null);
}

Nel modello della pagina di indice (Pages/Index.cshtml.cs) viene chiamato QuoteAdded per registrare il messaggio:In the Index page's page model (Pages/Index.cshtml.cs), QuoteAdded is called to log the message:

public async Task<IActionResult> OnPostAddQuoteAsync()
{
    _db.Quotes.Add(Quote);
    await _db.SaveChangesAsync();

    _logger.QuoteAdded(Quote.Text);

    return RedirectToPage();
}

Esaminare l'output della console dell'app:Inspect the app's console output:

info: LoggerMessageSample.Pages.IndexModel[2]
      => RequestId:0HL90M6E7PHK5:0000000A RequestPath:/ => /Index
      Quote added (Quote = 'You can avoid reality, but you cannot avoid the 
          consequences of avoiding reality. - Ayn Rand')

L'app di esempio implementa un modello try–catch per l'eliminazione dell'offerta.The sample app implements a try–catch pattern for quote deletion. Per un'operazione di eliminazione con esito positivo, viene registrato un messaggio informativo.An informational message is logged for a successful delete operation. Per un'operazione di eliminazione con la generazione di un'eccezione, viene registrato un messaggio di errore.An error message is logged for a delete operation when an exception is thrown. Il messaggio di log per un'operazione di eliminazione con esito negativo include l'analisi dello stack dell'eccezione (Internal/LoggerExtensions.cs):The log message for the unsuccessful delete operation includes the exception stack trace (Internal/LoggerExtensions.cs):

private static readonly Action<ILogger, string, int, Exception> _quoteDeleted;
private static readonly Action<ILogger, int, Exception> _quoteDeleteFailed;
_quoteDeleted = LoggerMessage.Define<string, int>(
    LogLevel.Information, 
    new EventId(4, nameof(QuoteDeleted)), 
    "Quote deleted (Quote = '{Quote}' Id = {Id})");

_quoteDeleteFailed = LoggerMessage.Define<int>(
    LogLevel.Error, 
    new EventId(5, nameof(QuoteDeleteFailed)), 
    "Quote delete failed (Id = {Id})");

Si noti come l'eccezione viene passata al delegato in QuoteDeleteFailed:Note how the exception is passed to the delegate in QuoteDeleteFailed:

public static void QuoteDeleted(this ILogger logger, string quote, int id)
{
    _quoteDeleted(logger, quote, id, null);
}

public static void QuoteDeleteFailed(this ILogger logger, int id, Exception ex)
{
    _quoteDeleteFailed(logger, id, ex);
}

Nel modello di pagina per la pagina di indice l'eliminazione di un'offerta con esito positivo chiama il metodo QuoteDeleted nel logger.In the page model for the Index page, a successful quote deletion calls the QuoteDeleted method on the logger. Quando non viene trovata un'offerta per l'eliminazione, viene generata un'eccezione ArgumentNullException.When a quote isn't found for deletion, an ArgumentNullException is thrown. L'eccezione viene intercettata dall'istruzione try–catch e registrata chiamando il metodo QuoteDeleteFailed nel logger nel blocco catch (Pages/Index.cshtml.cs):The exception is trapped by the try–catch statement and logged by calling the QuoteDeleteFailed method on the logger in the catch block (Pages/Index.cshtml.cs):

public async Task<IActionResult> OnPostDeleteQuoteAsync(int id)
{
    try
    {
        var quote = await _db.Quotes.FindAsync(id);
        _db.Quotes.Remove(quote);
        await _db.SaveChangesAsync();

        _logger.QuoteDeleted(quote.Text, id);
    }
    catch (NullReferenceException ex)
    {
        _logger.QuoteDeleteFailed(id, ex);
    }

    return RedirectToPage();
}

Quando un'offerta viene eliminata, esaminare l'output della console dell'app:When a quote is successfully deleted, inspect the app's console output:

info: LoggerMessageSample.Pages.IndexModel[4]
      => RequestId:0HL90M6E7PHK5:00000016 RequestPath:/ => /Index
      Quote deleted (Quote = 'You can avoid reality, but you cannot avoid the 
          consequences of avoiding reality. - Ayn Rand' Id = 1)

Quando l'eliminazione di un'offerta ha esito negativo, esaminare l'output della console dell'app.When quote deletion fails, inspect the app's console output. Si noti che l'eccezione è inclusa nel messaggio di log:Note that the exception is included in the log message:

LoggerMessageSample.Pages.IndexModel: Error: Quote delete failed (Id = 999)

System.NullReferenceException: Object reference not set to an instance of an object.
   at lambda_method(Closure , ValueBuffer )
   at System.Linq.Enumerable.SelectEnumerableIterator`2.MoveNext()
   at Microsoft.EntityFrameworkCore.InMemory.Query.Internal.InMemoryShapedQueryCompilingExpressionVisitor.AsyncQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync()
   at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.SingleOrDefaultAsync[TSource](IAsyncEnumerable`1 asyncEnumerable, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.SingleOrDefaultAsync[TSource](IAsyncEnumerable`1 asyncEnumerable, CancellationToken cancellationToken)
   at LoggerMessageSample.Pages.IndexModel.OnPostDeleteQuoteAsync(Int32 id) in c:\Users\guard\Documents\GitHub\Docs\aspnetcore\fundamentals\logging\loggermessage\samples\3.x\LoggerMessageSample\Pages\Index.cshtml.cs:line 77

LoggerMessage.DefineScopeLoggerMessage.DefineScope

DefineScope(String) crea un delegato Func<TResult> per definire un ambito del log.DefineScope(String) creates a Func<TResult> delegate for defining a log scope. Gli overload DefineScope permettono il passaggio di un massimo di tre parametri di tipo in una stringa di formato denominata (modello).DefineScope overloads permit passing up to three type parameters to a named format string (template).

Come avviene per il metodo Define, la stringa specificata nel metodo DefineScope è un modello e non una stringa interpolata.As is the case with the Define method, the string provided to the DefineScope method is a template and not an interpolated string. I segnaposto vengono inseriti nell'ordine in cui sono specificati i tipi.Placeholders are filled in the order that the types are specified. I nomi dei segnaposto nel modello devono essere descrittivi e coerenti tra i modelli.Placeholder names in the template should be descriptive and consistent across templates. Vengono usati come nomi di proprietà all'interno dei dati di log strutturati.They serve as property names within structured log data. Per i nomi dei segnaposto è consigliabile usare la convenzione Pascal.We recommend Pascal casing for placeholder names. Ad esempio, {Count}, {FirstName}.For example, {Count}, {FirstName}.

Definire un ambito del log da applicare a una serie di messaggi di log usando il metodo DefineScope.Define a log scope to apply to a series of log messages using the DefineScope method.

L'app di esempio include un pulsante Clear All (Cancella tutto) per eliminare tutte le offerte nel database.The sample app has a Clear All button for deleting all of the quotes in the database. Le offerte vengono eliminate rimuovendole una alla volta.The quotes are deleted by removing them one at a time. Ogni volta che viene eliminata un'offerta, viene chiamato il metodo QuoteDeleted nel logger.Each time a quote is deleted, the QuoteDeleted method is called on the logger. Viene aggiunto un ambito di log a questi messaggi di log.A log scope is added to these log messages.

Abilitare IncludeScopes nella sezione del logger di console di appsettings.json:Enable IncludeScopes in the console logger section of appsettings.json:

{
  "Logging": {
    "Console": {
      "IncludeScopes": true
    },
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

Per creare un ambito di log, aggiungere un campo per inserire un delegato Func<TResult> per l'ambito.To create a log scope, add a field to hold a Func<TResult> delegate for the scope. L'app di esempio crea un campo denominato _allQuotesDeletedScope (Internal/LoggerExtensions.cs):The sample app creates a field called _allQuotesDeletedScope (Internal/LoggerExtensions.cs):

private static Func<ILogger, int, IDisposable> _allQuotesDeletedScope;

Usare DefineScope per creare il delegato.Use DefineScope to create the delegate. È possibile specificare un massimo di tre tipi da usare come argomenti di modello quando viene richiamato il delegato.Up to three types can be specified for use as template arguments when the delegate is invoked. L'app di esempio usa un modello di messaggio che include il numero di offerte eliminate (un tipo int):The sample app uses a message template that includes the number of deleted quotes (an int type):

_allQuotesDeletedScope = 
    LoggerMessage.DefineScope<int>("All quotes deleted (Count = {Count})");

Specificare un metodo di estensione statico per il messaggio di log.Provide a static extension method for the log message. Includere i parametri di tipo per le proprietà denominate visualizzate nel modello di messaggio.Include any type parameters for named properties that appear in the message template. L'app di esempio accetta un numero count di offerte da eliminare e restituisce _allQuotesDeletedScope:The sample app takes in a count of quotes to delete and returns _allQuotesDeletedScope:

public static IDisposable AllQuotesDeletedScope(
    this ILogger logger, int count)
{
    return _allQuotesDeletedScope(logger, count);
}

L'ambito esegue il wrapping delle chiamate di estensione di registrazione in un blocco using:The scope wraps the logging extension calls in a using block:

public async Task<IActionResult> OnPostDeleteAllQuotesAsync()
{
    var quoteCount = await _db.Quotes.CountAsync();

    using (_logger.AllQuotesDeletedScope(quoteCount))
    {
        foreach (Quote quote in _db.Quotes)
        {
            _db.Quotes.Remove(quote);

            _logger.QuoteDeleted(quote.Text, quote.Id);
        }
        await _db.SaveChangesAsync();
    }

    return RedirectToPage();
}

Esaminare i messaggi di log nell'output della console dell'app.Inspect the log messages in the app's console output. Il risultato seguente mostra tre offerte eliminate con il messaggio di ambito di log incluso:The following result shows three quotes deleted with the log scope message included:

info: LoggerMessageSample.Pages.IndexModel[4]
      => RequestId:0HL90M6E7PHK5:0000002E RequestPath:/ => /Index => 
          All quotes deleted (Count = 3)
      Quote deleted (Quote = 'Quote 1' Id = 2)
info: LoggerMessageSample.Pages.IndexModel[4]
      => RequestId:0HL90M6E7PHK5:0000002E RequestPath:/ => /Index => 
          All quotes deleted (Count = 3)
      Quote deleted (Quote = 'Quote 2' Id = 3)
info: LoggerMessageSample.Pages.IndexModel[4]
      => RequestId:0HL90M6E7PHK5:0000002E RequestPath:/ => /Index => 
          All quotes deleted (Count = 3)
      Quote deleted (Quote = 'Quote 3' Id = 4)

Le funzionalità di LoggerMessage creano delegati inseribili nella cache che richiedono un numero minore di allocazioni di oggetti e riducono il sovraccarico di calcolo rispetto ai metodi di estensione del logger, ad esempio LogInformation e LogDebug.LoggerMessage features create cacheable delegates that require fewer object allocations and reduced computational overhead compared to logger extension methods, such as LogInformation and LogDebug. Per gli scenari di registrazione a prestazioni elevate, usare LoggerMessage.For high-performance logging scenarios, use the LoggerMessage pattern.

LoggerMessage offre i seguenti vantaggi in termini di prestazioni rispetto ai metodi di estensione del logger:LoggerMessage provides the following performance advantages over Logger extension methods:

  • I metodi di estensione del logger richiedono una "conversione boxing" dei tipi di valori, ad esempio int, in object.Logger extension methods require "boxing" (converting) value types, such as int, into object. LoggerMessage evita la conversione boxing usando campi Action statici e metodi di estensione con parametri fortemente tipizzati.The LoggerMessage pattern avoids boxing by using static Action fields and extension methods with strongly-typed parameters.
  • I metodi di estensione del logger devono analizzare il modello di messaggio (stringa di formato denominata) ogni volta che viene scritto un messaggio del log.Logger extension methods must parse the message template (named format string) every time a log message is written. Solo LoggerMessage richiede una sola analisi del modello durante la definizione del messaggio.LoggerMessage only requires parsing a template once when the message is defined.

Visualizzare o scaricare il codice di esempio (procedura per il download)View or download sample code (how to download)

L'app di esempio illustra le funzionalità di LoggerMessage con un sistema di verifica delle offerte di base.The sample app demonstrates LoggerMessage features with a basic quote tracking system. L'app aggiunge ed elimina le offerte usando un database in memoria.The app adds and deletes quotes using an in-memory database. Durante l'esecuzione di queste operazioni, i messaggi del log vengono generati usando LoggerMessage.As these operations occur, log messages are generated using the LoggerMessage pattern.

LoggerMessage.DefineLoggerMessage.Define

Define(LogLevel, EventId, String) crea un delegato Action per la registrazione di un messaggio.Define(LogLevel, EventId, String) creates an Action delegate for logging a message. Gli overload Define permettono il passaggio di un massimo di sei parametri di tipo in una stringa di formato denominata (modello).Define overloads permit passing up to six type parameters to a named format string (template).

La stringa specificata nel metodo Define è un modello e non una stringa interpolata.The string provided to the Define method is a template and not an interpolated string. I segnaposto vengono inseriti nell'ordine in cui sono specificati i tipi.Placeholders are filled in the order that the types are specified. I nomi dei segnaposto nel modello devono essere descrittivi e coerenti tra i modelli.Placeholder names in the template should be descriptive and consistent across templates. Vengono usati come nomi di proprietà all'interno dei dati di log strutturati.They serve as property names within structured log data. Per i nomi dei segnaposto è consigliabile usare la convenzione Pascal.We recommend Pascal casing for placeholder names. Ad esempio, {Count}, {FirstName}.For example, {Count}, {FirstName}.

Ogni messaggio di log è un elemento Action contenuto in un campo statico creato da LoggerMessage.Define.Each log message is an Action held in a static field created by LoggerMessage.Define. Ad esempio, l'app di esempio crea un campo per descrivere un messaggio di log per una richiesta GET per la pagina di indice (Internal/LoggerExtensions.cs):For example, the sample app creates a field to describe a log message for a GET request for the Index page (Internal/LoggerExtensions.cs):

private static readonly Action<ILogger, Exception> _indexPageRequested;

Per Action, specificare:For the Action, specify:

  • Il livello del log.The log level.
  • Un identificatore di evento univoco (EventId) con il nome del metodo di estensione statico.A unique event identifier (EventId) with the name of the static extension method.
  • Il modello di messaggio (stringa di formato denominata).The message template (named format string).

Una richiesta per la pagina di indice dell'app di esempio imposta:A request for the Index page of the sample app sets the:

  • Il livello del log su Information.Log level to Information.
  • L'ID evento su 1 con il nome del metodo IndexPageRequested.Event id to 1 with the name of the IndexPageRequested method.
  • Il modello di messaggio (stringa di formato denominata) su una stringa.Message template (named format string) to a string.
_indexPageRequested = LoggerMessage.Define(
    LogLevel.Information, 
    new EventId(1, nameof(IndexPageRequested)), 
    "GET request for Index page");

Negli archivi di log strutturati è possibile che venga usato il nome evento quando viene specificato con l'ID evento per arricchire la registrazione.Structured logging stores may use the event name when it's supplied with the event id to enrich logging. Ad esempio, Serilog usa il nome evento.For example, Serilog uses the event name.

Action viene richiamato attraverso un metodo di estensione fortemente tipizzato.The Action is invoked through a strongly-typed extension method. Il metodo IndexPageRequested registra un messaggio per la richiesta GET di una pagina di indice nell'app di esempio:The IndexPageRequested method logs a message for an Index page GET request in the sample app:

public static void IndexPageRequested(this ILogger logger)
{
    _indexPageRequested(logger, null);
}

IndexPageRequested viene chiamato nel logger nel metodo OnGetAsync in Pages/Index.cshtml.cs:IndexPageRequested is called on the logger in the OnGetAsync method in Pages/Index.cshtml.cs:

public async Task OnGetAsync()
{
    _logger.IndexPageRequested();

    Quotes = await _db.Quotes.AsNoTracking().ToListAsync();
}

Esaminare l'output della console dell'app:Inspect the app's console output:

info: LoggerMessageSample.Pages.IndexModel[1]
      => RequestId:0HL90M6E7PHK4:00000001 RequestPath:/ => /Index
      GET request for Index page

Per passare i parametri in un messaggio di log, definire un massimo di sei tipi durante la creazione del campo statico.To pass parameters to a log message, define up to six types when creating the static field. L'app di esempio registra una stringa quando viene aggiunta un'offerta definendo un tipo string per il campo Action:The sample app logs a string when adding a quote by defining a string type for the Action field:

private static readonly Action<ILogger, string, Exception> _quoteAdded;

Il modello di messaggio di log del delegato riceve i valori dei segnaposto dai tipi specificati.The delegate's log message template receives its placeholder values from the types provided. L'app di esempio definisce un delegato per l'aggiunta di un'offerta dove il parametro dell'offerta è un elemento string:The sample app defines a delegate for adding a quote where the quote parameter is a string:

_quoteAdded = LoggerMessage.Define<string>(
    LogLevel.Information, 
    new EventId(2, nameof(QuoteAdded)), 
    "Quote added (Quote = '{Quote}')");

Il metodo di estensione statico per l'aggiunta di un'offerta, QuoteAdded, riceve il valore dell'argomento dell'offerta e lo passa al delegato Action:The static extension method for adding a quote, QuoteAdded, receives the quote argument value and passes it to the Action delegate:

public static void QuoteAdded(this ILogger logger, string quote)
{
    _quoteAdded(logger, quote, null);
}

Nel modello della pagina di indice (Pages/Index.cshtml.cs) viene chiamato QuoteAdded per registrare il messaggio:In the Index page's page model (Pages/Index.cshtml.cs), QuoteAdded is called to log the message:

public async Task<IActionResult> OnPostAddQuoteAsync()
{
    _db.Quotes.Add(Quote);
    await _db.SaveChangesAsync();

    _logger.QuoteAdded(Quote.Text);

    return RedirectToPage();
}

Esaminare l'output della console dell'app:Inspect the app's console output:

info: LoggerMessageSample.Pages.IndexModel[2]
      => RequestId:0HL90M6E7PHK5:0000000A RequestPath:/ => /Index
      Quote added (Quote = 'You can avoid reality, but you cannot avoid the 
          consequences of avoiding reality. - Ayn Rand')

L'app di esempio implementa un modello try–catch per l'eliminazione dell'offerta.The sample app implements a try–catch pattern for quote deletion. Per un'operazione di eliminazione con esito positivo, viene registrato un messaggio informativo.An informational message is logged for a successful delete operation. Per un'operazione di eliminazione con la generazione di un'eccezione, viene registrato un messaggio di errore.An error message is logged for a delete operation when an exception is thrown. Il messaggio di log per un'operazione di eliminazione con esito negativo include l'analisi dello stack dell'eccezione (Internal/LoggerExtensions.cs):The log message for the unsuccessful delete operation includes the exception stack trace (Internal/LoggerExtensions.cs):

private static readonly Action<ILogger, string, int, Exception> _quoteDeleted;
private static readonly Action<ILogger, int, Exception> _quoteDeleteFailed;
_quoteDeleted = LoggerMessage.Define<string, int>(
    LogLevel.Information, 
    new EventId(4, nameof(QuoteDeleted)), 
    "Quote deleted (Quote = '{Quote}' Id = {Id})");

_quoteDeleteFailed = LoggerMessage.Define<int>(
    LogLevel.Error, 
    new EventId(5, nameof(QuoteDeleteFailed)), 
    "Quote delete failed (Id = {Id})");

Si noti come l'eccezione viene passata al delegato in QuoteDeleteFailed:Note how the exception is passed to the delegate in QuoteDeleteFailed:

public static void QuoteDeleted(this ILogger logger, string quote, int id)
{
    _quoteDeleted(logger, quote, id, null);
}

public static void QuoteDeleteFailed(this ILogger logger, int id, Exception ex)
{
    _quoteDeleteFailed(logger, id, ex);
}

Nel modello di pagina per la pagina di indice l'eliminazione di un'offerta con esito positivo chiama il metodo QuoteDeleted nel logger.In the page model for the Index page, a successful quote deletion calls the QuoteDeleted method on the logger. Quando non viene trovata un'offerta per l'eliminazione, viene generata un'eccezione ArgumentNullException.When a quote isn't found for deletion, an ArgumentNullException is thrown. L'eccezione viene intercettata dall'istruzione try–catch e registrata chiamando il metodo QuoteDeleteFailed nel logger nel blocco catch (Pages/Index.cshtml.cs):The exception is trapped by the try–catch statement and logged by calling the QuoteDeleteFailed method on the logger in the catch block (Pages/Index.cshtml.cs):

public async Task<IActionResult> OnPostDeleteQuoteAsync(int id)
{
    var quote = await _db.Quotes.FindAsync(id);

    // DO NOT use this approach in production code!
    // You should check quote to see if it's null before removing 
    // it and saving changes to the database. A try-catch is used 
    // here for demonstration purposes of LoggerMessage features.
    try
    {
        _db.Quotes.Remove(quote);
        await _db.SaveChangesAsync();

        _logger.QuoteDeleted(quote.Text, id);
    }
    catch (ArgumentNullException ex)
    {
        _logger.QuoteDeleteFailed(id, ex);
    }

    return RedirectToPage();
}

Quando un'offerta viene eliminata, esaminare l'output della console dell'app:When a quote is successfully deleted, inspect the app's console output:

info: LoggerMessageSample.Pages.IndexModel[4]
      => RequestId:0HL90M6E7PHK5:00000016 RequestPath:/ => /Index
      Quote deleted (Quote = 'You can avoid reality, but you cannot avoid the 
          consequences of avoiding reality. - Ayn Rand' Id = 1)

Quando l'eliminazione di un'offerta ha esito negativo, esaminare l'output della console dell'app.When quote deletion fails, inspect the app's console output. Si noti che l'eccezione è inclusa nel messaggio di log:Note that the exception is included in the log message:

fail: LoggerMessageSample.Pages.IndexModel[5]
      => RequestId:0HL90M6E7PHK5:00000010 RequestPath:/ => /Index
      Quote delete failed (Id = 999)
System.ArgumentNullException: Value cannot be null.
Parameter name: entity
   at Microsoft.EntityFrameworkCore.Utilities.Check.NotNull[T]
       (T value, String parameterName)
   at Microsoft.EntityFrameworkCore.DbContext.Remove[TEntity](TEntity entity)
   at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.Remove(TEntity entity)
   at LoggerMessageSample.Pages.IndexModel.<OnPostDeleteQuoteAsync>d__14.MoveNext() 
      in <PATH>\sample\Pages\Index.cshtml.cs:line 87

LoggerMessage.DefineScopeLoggerMessage.DefineScope

DefineScope(String) crea un delegato Func<TResult> per definire un ambito del log.DefineScope(String) creates a Func<TResult> delegate for defining a log scope. Gli overload DefineScope permettono il passaggio di un massimo di tre parametri di tipo in una stringa di formato denominata (modello).DefineScope overloads permit passing up to three type parameters to a named format string (template).

Come avviene per il metodo Define, la stringa specificata nel metodo DefineScope è un modello e non una stringa interpolata.As is the case with the Define method, the string provided to the DefineScope method is a template and not an interpolated string. I segnaposto vengono inseriti nell'ordine in cui sono specificati i tipi.Placeholders are filled in the order that the types are specified. I nomi dei segnaposto nel modello devono essere descrittivi e coerenti tra i modelli.Placeholder names in the template should be descriptive and consistent across templates. Vengono usati come nomi di proprietà all'interno dei dati di log strutturati.They serve as property names within structured log data. Per i nomi dei segnaposto è consigliabile usare la convenzione Pascal.We recommend Pascal casing for placeholder names. Ad esempio, {Count}, {FirstName}.For example, {Count}, {FirstName}.

Definire un ambito del log da applicare a una serie di messaggi di log usando il metodo DefineScope.Define a log scope to apply to a series of log messages using the DefineScope method.

L'app di esempio include un pulsante Clear All (Cancella tutto) per eliminare tutte le offerte nel database.The sample app has a Clear All button for deleting all of the quotes in the database. Le offerte vengono eliminate rimuovendole una alla volta.The quotes are deleted by removing them one at a time. Ogni volta che viene eliminata un'offerta, viene chiamato il metodo QuoteDeleted nel logger.Each time a quote is deleted, the QuoteDeleted method is called on the logger. Viene aggiunto un ambito di log a questi messaggi di log.A log scope is added to these log messages.

Abilitare IncludeScopes nella sezione del logger di console di appsettings.json:Enable IncludeScopes in the console logger section of appsettings.json:

{
  "Logging": {
    "Console": {
      "IncludeScopes": true
    },
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*"
}

Per creare un ambito di log, aggiungere un campo per inserire un delegato Func<TResult> per l'ambito.To create a log scope, add a field to hold a Func<TResult> delegate for the scope. L'app di esempio crea un campo denominato _allQuotesDeletedScope (Internal/LoggerExtensions.cs):The sample app creates a field called _allQuotesDeletedScope (Internal/LoggerExtensions.cs):

private static Func<ILogger, int, IDisposable> _allQuotesDeletedScope;

Usare DefineScope per creare il delegato.Use DefineScope to create the delegate. È possibile specificare un massimo di tre tipi da usare come argomenti di modello quando viene richiamato il delegato.Up to three types can be specified for use as template arguments when the delegate is invoked. L'app di esempio usa un modello di messaggio che include il numero di offerte eliminate (un tipo int):The sample app uses a message template that includes the number of deleted quotes (an int type):

_allQuotesDeletedScope = 
    LoggerMessage.DefineScope<int>("All quotes deleted (Count = {Count})");

Specificare un metodo di estensione statico per il messaggio di log.Provide a static extension method for the log message. Includere i parametri di tipo per le proprietà denominate visualizzate nel modello di messaggio.Include any type parameters for named properties that appear in the message template. L'app di esempio accetta un numero count di offerte da eliminare e restituisce _allQuotesDeletedScope:The sample app takes in a count of quotes to delete and returns _allQuotesDeletedScope:

public static IDisposable AllQuotesDeletedScope(
    this ILogger logger, int count)
{
    return _allQuotesDeletedScope(logger, count);
}

L'ambito esegue il wrapping delle chiamate di estensione di registrazione in un blocco using:The scope wraps the logging extension calls in a using block:

public async Task<IActionResult> OnPostDeleteAllQuotesAsync()
{
    var quoteCount = await _db.Quotes.CountAsync();

    using (_logger.AllQuotesDeletedScope(quoteCount))
    {
        foreach (Quote quote in _db.Quotes)
        {
            _db.Quotes.Remove(quote);

            _logger.QuoteDeleted(quote.Text, quote.Id);
        }
        await _db.SaveChangesAsync();
    }

    return RedirectToPage();
}

Esaminare i messaggi di log nell'output della console dell'app.Inspect the log messages in the app's console output. Il risultato seguente mostra tre offerte eliminate con il messaggio di ambito di log incluso:The following result shows three quotes deleted with the log scope message included:

info: LoggerMessageSample.Pages.IndexModel[4]
      => RequestId:0HL90M6E7PHK5:0000002E RequestPath:/ => /Index => 
          All quotes deleted (Count = 3)
      Quote deleted (Quote = 'Quote 1' Id = 2)
info: LoggerMessageSample.Pages.IndexModel[4]
      => RequestId:0HL90M6E7PHK5:0000002E RequestPath:/ => /Index => 
          All quotes deleted (Count = 3)
      Quote deleted (Quote = 'Quote 2' Id = 3)
info: LoggerMessageSample.Pages.IndexModel[4]
      => RequestId:0HL90M6E7PHK5:0000002E RequestPath:/ => /Index => 
          All quotes deleted (Count = 3)
      Quote deleted (Quote = 'Quote 3' Id = 4)

Risorse aggiuntiveAdditional resources