Delen via


Logboekregistratie met hoge prestaties in .NET

Voor scenario's voor logboekregistratie met hoge prestaties in .NET 6 en latere versies gebruikt u de LoggerMessageAttribute functie voor het genereren van compileertijdbronnen. Deze aanpak biedt de beste prestaties door boxing, tijdelijke toewijzingen en het parseren van berichtsjablonen tijdens de runtime te elimineren.

Door bron gegenereerde logboekregistratie biedt de volgende prestatievoordelen ten opzichte van extensiemethoden voor logboekregistratie, zoalsLogInformation:LogDebug

  • Elimineert boksen: Logger extensiemethoden vereisen 'boxing' (het converteren van) waardetypen, zoals int, in object. Door de bron gegenereerde logboekregistratie voorkomt boksen met behulp van sterk getypte parameters.
  • Parseert sjablonen tijdens het compileren: Extensiemethoden voor logboekregistratie moeten de berichtsjabloon (benoemde notatietekenreeks) parseren telkens wanneer een logboekbericht wordt geschreven. Door de bron gegenereerde logboekregistratie parseert sjablonen eenmaal tijdens het compileren.
  • Vermindert toewijzingen: De brongenerator maakt geoptimaliseerde code waarmee objecttoewijzingen en tijdelijk geheugengebruik worden geminimaliseerd.

De voorbeeld-app demonstreert hoog-prestatie logfuncties met een verwerkingsservice voor prioriteitswachtrijen. De app verwerkt werkitems in volgorde van prioriteit. Wanneer deze bewerkingen plaatsvinden, worden logboekberichten gegenereerd met behulp van door de bron gegenereerde logboekregistratie.

Aanbeveling

Alle voorbeeldbroncode voor logboekregistratie is beschikbaar in de voorbeeldenbrowser om te downloaden. Zie Blader door codevoorbeelden: Logboekregistratie in .NET voor meer informatie.

Logboekberichten definiëren met brongeneratie

Als u logboekberichten met hoge prestaties wilt maken in .NET 6 en hoger, definieert u partial methoden die zijn ingericht met LoggerMessageAttribute. De brongenerator maakt de implementatie tijdens het compileren.

Eenvoudige methode voor logboekregistratie

Definieer voor een eenvoudig logboekbericht een gedeeltelijke methode met het kenmerk dat de gebeurtenis-id, het logboekniveau en de berichtsjabloon opgeeft:

public static partial class Log
{
    [LoggerMessage(
        EventId = 13,
        Level = LogLevel.Critical,
        Message = "Epic failure processing item!")]
    public static partial void FailedToProcessWorkItem(
        ILogger logger, Exception ex);
}

De berichtsjabloon maakt gebruik van tijdelijke aanduidingen die worden ingevuld door methodeparameters. Namen van tijdelijke aanduidingen moeten beschrijvend en consistent zijn voor alle sjablonen. Ze fungeren als eigenschapsnamen binnen gestructureerde logboekgegevens. We raden Pascal casing aan voor tijdelijke aanduidingen. Bijvoorbeeld, {Item}. {DateTime}

Roep de logboekregistratiemethode aan vanuit uw code. Wanneer er bijvoorbeeld een uitzondering optreedt tijdens de verwerking van werkitems:

try
{
    // Process work item.
}
catch (Exception ex)
{
    Log.FailedToProcessWorkItem(logger, ex);
}

Deze code produceert console-uitvoer zoals:

crit: WorkerServiceOptions.Example.Worker[13]
      Epic failure processing item!
      System.Exception: Failed to verify communications.

Logboekregistratie met parameters

Als u parameters wilt doorgeven aan een logboekbericht, voegt u deze toe als methodeparameters. De parameternamen komen overeen met de tijdelijke aanduidingen in de berichtsjabloon:

public static partial class Log
{
    [LoggerMessage(
        EventId = 1,
        Level = LogLevel.Information,
        Message = "Processing priority item: {Item}")]
    public static partial void PriorityItemProcessed(
        ILogger logger, WorkItem item);
}

Roep de methode aan met de logger- en parameterwaarden:

var workItem = queue.Dequeue();
Log.PriorityItemProcessed(logger, workItem);

Deze code produceert console-uitvoer zoals:

info: WorkerServiceOptions.Example.Worker[1]
      Processing priority item: Priority-Extreme (50db062a-9732-4418-936d-110549ad79e4): 'Verify communications'

Gestructureerde logarchieven kunnen de naam van de gebeurtenis gebruiken, wanneer deze samen met de gebeurtenis-id wordt geleverd, om logboekregistratie te verrijken. Serilog gebruikt bijvoorbeeld de gebeurtenisnaam.

Logboekberichtbereik definiëren met brongeneratie

U kunt logboekbereiken definiëren om een reeks logboekberichten in te pakken met aanvullende context. Met door de bron gegenereerde logboekregistratie combineert u de LoggerMessageAttribute methoden met de standaardmethode ILogger.BeginScope .

Inschakelen IncludeScopes in de sectie consolelogger van appsettings.json:

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

Maak door de bron gegenereerde logboekregistratiemethoden en verpakt deze in een bereik met behulp van BeginScope:

public static partial class Log
{
    [LoggerMessage(
        EventId = 1,
        Level = LogLevel.Information,
        Message = "Processing priority item: {Item}")]
    public static partial void PriorityItemProcessed(
        ILogger logger, WorkItem item);
}

Gebruik de logmethode binnen een scope in uw toepassingscode:

using (_logger.BeginScope("Processing scope, started at: {DateTime}", DateTime.Now))
{
    Log.PriorityItemProcessed(_logger, workItem);
}

Controleer de logboekberichten in de console-uitvoer van de app. Het volgende resultaat toont prioriteitsvolgorde van logboekberichten, waarbij het logboekbereikbericht is opgenomen:

info: WorkerServiceOptions.Example.Worker[1]
      => Processing scope, started at: 04/11/2024 11:27:52
      Processing priority item: Priority-Extreme (7d153ef9-8894-4282-836a-8e5e38319fb3): 'Verify communications'

Verouderde benadering: LoggerMessage.Define (voor .NET Framework en .NET Core 3.1)

Voordat door de bron gegenereerde logboekregistratie werd geïntroduceerd in .NET 6, was de aanbevolen benadering voor logboekregistratie met hoge prestaties de methode te gebruiken voor het LoggerMessage.Define maken van gedelegeerden die in de cache kunnen worden opgeslagen. Hoewel deze benadering nog steeds wordt ondersteund voor compatibiliteit met eerdere versies, moet nieuwe code in plaats daarvan gebruikmaken van door de bron gegenereerde logboekregistratie LoggerMessageAttribute .

De LoggerMessage klasse biedt functionaliteit voor het maken van in cache opgeslagen gedelegeerden waarvoor minder objecttoewijzingen en minder rekenoverhead nodig zijn in vergelijking met methoden voor logboekuitbreiding, zoals LogInformation en LogDebug. LoggerMessage biedt de volgende prestatievoordelen ten opzichte van extensiemethoden voor logging:

  • Voor extensiemethoden voor logboekregistratie zijn waardetypen voor boksen (converteren) vereist, zoals int, in object. Het LoggerMessage patroon voorkomt boksen met behulp van statische Action velden en extensiemethoden met sterk getypte parameters.
  • Extensiemethoden voor logboekregistratie moeten de berichtsjabloon (benoemde notatietekenreeks) parseren telkens wanneer een logboekbericht wordt geschreven. LoggerMessage parseren van een sjabloon is slechts eenmaal vereist wanneer het bericht is gedefinieerd.

Opmerking

Als u code onderhoudt die gebruikmaakt LoggerMessage.Define, kunt u overwegen om te migreren naar door de bron gegenereerde logboekregistratie. Voor .NET Framework- of .NET Core 3.1-toepassingen kunt u doorgaan met het gebruik van LoggerMessage.Define.

Een logboekregistratiebericht definiëren

Gebruik Define(LogLevel, EventId, String) om een Action gemachtigde te maken voor het vastleggen van een bericht. Define overloads staan toe om maximaal zes typeparameters door te geven aan een benoemde formattekenreeks (sjabloon).

De tekenreeks die aan de Define methode wordt verstrekt, is een sjabloon en geen geïnterpoleerde tekenreeks. Plaatsaanduidingen worden ingevuld in de volgorde waarin de types zijn opgegeven. Namen van tijdelijke aanduidingen in de sjabloon moeten beschrijvend en consistent zijn voor alle sjablonen. Ze fungeren als eigenschapsnamen binnen gestructureerde logboekgegevens. We raden Pascal casing aan voor tijdelijke aanduidingsnamen. Bijvoorbeeld, {Item}. {DateTime}

Elk logboekbericht is een Action vastgehouden in een statisch veld dat is gemaakt door LoggerMessage.Define. De voorbeeld-app maakt bijvoorbeeld een veld om een logboekbericht te beschrijven voor de verwerking van werkitems:

private static readonly Action<ILogger, Exception> s_failedToProcessWorkItem;

Voor de Action opgeven:

  • Het logboekniveau.
  • Een unieke gebeurtenis-id (EventId) met de naam van de statische extensiemethode.
  • De berichtsjabloon (tekenreeks met benoemde notatie).

Wanneer werkitems uit de wachtrij worden gehaald voor verwerking, stelt de werkservice-app het volgende in:

  • Het logniveau instellen op LogLevel.Critical.
  • Gebeurtenis-id naar 13 met de naam van de FailedToProcessWorkItem methode.
  • Berichtsjabloon (benoemde formaattekenreeks) naar een tekenreeks.
s_failedToProcessWorkItem = LoggerMessage.Define(
    LogLevel.Critical,
    new EventId(13, nameof(FailedToProcessWorkItem)),
    "Epic failure processing item!");

De LoggerMessage.Define methode wordt gebruikt voor het configureren en definiëren van een Action gemachtigde, die een logboekbericht vertegenwoordigt.

Gestructureerde logboeken kunnen de naam van de gebeurtenis gebruiken indien voorzien van de gebeurtenis-id om de logboekregistratie te verrijken. Serilog gebruikt bijvoorbeeld de gebeurtenisnaam.

De Action wordt aangeroepen via een sterk getypte extensiemethode. Met de PriorityItemProcessed methode wordt een bericht geregistreerd telkens wanneer een werkitem wordt verwerkt. FailedToProcessWorkItem wordt aangeroepen als en wanneer er een uitzondering optreedt:

protected override async Task ExecuteAsync(
    CancellationToken stoppingToken)
{
    using (IDisposable? scope = logger.ProcessingWorkScope(DateTime.Now))
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                WorkItem? nextItem = priorityQueue.ProcessNextHighestPriority();

                if (nextItem is not null)
                {
                    logger.PriorityItemProcessed(nextItem);
                }
            }
            catch (Exception ex)
            {
                logger.FailedToProcessWorkItem(ex);
            }

            await Task.Delay(1_000, stoppingToken);
        }
    }
}

Controleer de console-uitvoer van de app:

crit: WorkerServiceOptions.Example.Worker[13]
      Epic failure processing item!
      System.Exception: Failed to verify communications.
         at WorkerServiceOptions.Example.Worker.ExecuteAsync(CancellationToken stoppingToken) in
         ..\Worker.cs:line 27

Als u parameters wilt doorgeven aan een logboekbericht, definieert u maximaal zes typen bij het maken van het statische veld. De voorbeeld-app registreert de details van het werkitem bij het verwerken van items door een WorkItem type voor het Action veld te definiëren:

private static readonly Action<ILogger, WorkItem, Exception> s_processingPriorityItem;

De sjabloon voor logboekberichten van de gemachtigde ontvangt zijn placeholderwaarden van de gespecificeerde types. De voorbeeld-app definieert een gemachtigde voor het toevoegen van een werkitem waarbij de itemparameter een WorkItem:

s_processingPriorityItem = LoggerMessage.Define<WorkItem>(
    LogLevel.Information,
    new EventId(1, nameof(PriorityItemProcessed)),
    "Processing priority item: {Item}");

De statische extensiemethode voor logboekregistratie waarbij een werkitem wordt verwerkt, PriorityItemProcessed ontvangt de waarde van het werkitem en geeft deze door aan de Action delegate:

public static void PriorityItemProcessed(
    this ILogger logger, WorkItem workItem) =>
    s_processingPriorityItem(logger, workItem, default!);

In de methode ExecuteAsync van de werkrolservice wordt PriorityItemProcessed aangeroepen om het bericht te loggen.

protected override async Task ExecuteAsync(
    CancellationToken stoppingToken)
{
    using (IDisposable? scope = logger.ProcessingWorkScope(DateTime.Now))
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                WorkItem? nextItem = priorityQueue.ProcessNextHighestPriority();

                if (nextItem is not null)
                {
                    logger.PriorityItemProcessed(nextItem);
                }
            }
            catch (Exception ex)
            {
                logger.FailedToProcessWorkItem(ex);
            }

            await Task.Delay(1_000, stoppingToken);
        }
    }
}

Controleer de console-uitvoer van de app:

info: WorkerServiceOptions.Example.Worker[1]
      Processing priority item: Priority-Extreme (50db062a-9732-4418-936d-110549ad79e4): 'Verify communications'

Beveiligde optimalisaties op logboekniveau

U kunt de prestaties optimaliseren door de LogLevel te controleren met ILogger.IsEnabled(LogLevel) voordat u de Log*-methode aanroept. Wanneer logboekregistratie niet is geconfigureerd voor de opgegeven LogLevel, wordt ILogger.Log niet aangeroepen. Bovendien worden boksen van waardetypen en een toewijzing van object[] (om de parameters weer te geven) vermeden.

Voor meer informatie, zie:

Zie ook