Sdílet prostřednictvím


Vysoce výkonné protokolování v .NET

V případě vysoce výkonného protokolování ve .NET 6 a novějších verzích použijte LoggerMessageAttributegenerování zdrojového kódu při kompilaci. Tento přístup poskytuje nejlepší výkon vypuštěním boxingu, dočasných přidělení a zpracování šablon zpráv za běhu.

Protokolování vygenerované zdrojem poskytuje následující výhody výkonu oproti metodám rozšíření protokolovacího nástroje, jako jsou například LogInformation a LogDebug.

  • Eliminuje boxování: Metody rozšíření loggeru vyžadují převod hodnotových typů, například int, do object. Protokolování generované zdrojem zabraňuje boxování pomocí silně typovaných parametrů.
  • Parsuje šablony v době kompilace: Metody rozšíření protokolovacího nástroje musí parsovat šablonu zprávy (pojmenovaný formátovací řetězec) při každém záznamu logu. Protokolování generované zdrojem analyzuje šablony pouze jednou během doby kompilace.
  • Snižuje přidělení: Zdrojový generátor vytvoří optimalizovaný kód, který minimalizuje přidělení objektů a dočasné využití paměti.

Ukázková aplikace demonstruje vysoce výkonné protokolování pomocí pracovní služby pro zpracování prioritní fronty. Aplikace zpracovává pracovní položky v pořadí priority. Jak k těmto operacím dochází, zprávy protokolu se generují pomocí protokolování generovaného zdrojem.

Návod

Veškerý zdrojový kód příkladu protokolování je k dispozici v prohlížeči ukázek ke stažení. Další informace najdete v tématu Procházení ukázek kódu: Protokolování v .NET.

Definování zpráv protokolovacího nástroje pomocí generování zdroje

Chcete-li vytvořit zprávy protokolu s vysokým výkonem v rozhraní .NET 6 a novější, definujte metody partial zdobené LoggerMessageAttribute. Zdrojový generátor vytvoří implementaci v době kompilace.

Základní metoda protokolování

Pro jednoduchou zprávu protokolu definujte částečnou metodu s atributem, který určuje ID události, úroveň protokolu a šablonu zprávy:

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

Šablona zprávy používá zástupné symboly, které se vyplňují pomocí parametrů metody. Zástupné názvy by měly být v různých šablonách popisné a konzistentní. Slouží jako názvy vlastností v rámci strukturovaných dat protokolu. Doporučujeme používat Pascal casing pro zástupné názvy. Například , {Item}{DateTime}.

Zavolejte metodu logování ze svého kódu. Například když během zpracování pracovní položky dojde k výjimce:

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

Tento kód vytvoří výstup konzoly, například:

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

Logování s parametry

Pokud chcete předat parametry do zprávy protokolu, přidejte je jako parametry metody. Názvy parametrů odpovídají zástupným symbolům v šabloně zprávy:

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

Volejte metodu s hodnotami protokolovacího nástroje a parametrů:

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

Tento kód vytvoří výstup konzoly, například:

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

Úložiště strukturovaného protokolování můžou k obohacení protokolování použít název události, pokud je součástí ID události. Například Serilog používá název události.

Definování rozsahu zpráv protokolovacího modulu pomocí generování zdroje

Obory protokolu můžete definovat tak, aby zabalily řadu zpráv protokolu s dalším kontextem. Pomocí protokolování generovaného zdrojem zkombinujete LoggerMessageAttribute metody se standardní ILogger.BeginScope metodou.

V části nástroje pro protokolování konzoly IncludeScopespovolte:

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

Vytvořte metody protokolování generované zdrojem a zabalte je do oboru pomocí 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);
}

Použijte metodu protokolování v rozsahu v kódu aplikace:

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

Zkontrolujte zprávy protokolu ve výstupu konzoly aplikace. Následující výsledek ukazuje upřednostněné řazení zpráv protokolu, včetně zpráv v rozsahu protokolu:

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'

Starší verze přístupu: LoggerMessage.Define (pro .NET Framework a .NET Core 3.1)

Před zavedením protokolování generovaného zdrojem v rozhraní .NET 6 bylo doporučeným přístupem k protokolování s vysokým výkonem použít metodu LoggerMessage.Define k vytvoření delegátů s možností ukládání do mezipaměti. I když je tento přístup stále podporovaný kvůli zpětné kompatibilitě, měl by nový kód místo toho používat protokolování LoggerMessageAttribute generované zdrojem.

Třída LoggerMessage zveřejňuje funkce pro vytváření delegátů s možností ukládání do mezipaměti, které vyžadují méně přidělení objektů a nižší výpočetní režii v porovnání s metodami rozšíření protokolovacího nástroje, jako LogInformation jsou a LogDebug. LoggerMessage poskytuje následující výhody výkonu oproti metodám rozšíření protokolovacího nástroje:

  • Metody rozšíření protokolovacího nástroje vyžadují "boxing" (převod) hodnotové typy, například int, do object. Vzor LoggerMessage se vyhne boxování pomocí statických Action polí a rozšiřujících metod se silnými parametry.
  • Metody rozšíření protokolovacího nástroje musí analyzovat šablonu zprávy (pojmenovaný formát řetězec) při každém zápisu zprávy protokolu. LoggerMessage Při definování zprávy vyžaduje pouze jednou parsování šablony.

Poznámka:

Pokud udržujete kód, který používá LoggerMessage.Define, zvažte migraci na protokolování generovaného zdrojem. Pro aplikace .NET Framework nebo .NET Core 3.1 pokračujte v používání LoggerMessage.Define.

Definujte zprávu protokolu

K vytvoření delegáta pro protokolování zprávy použijte Action). Define přetížení umožňují předat až šest parametrů typu do pojmenovaného řetězce formátu (šablony).

Řetězec poskytnutý metodě Define je šablona, nikoli interpolovaný řetězec. Zástupné symboly se vyplní v pořadí, v jakém jsou zadané typy. Zástupné názvy v šabloně by měly být popisné a konzistentní napříč šablonami. Slouží jako názvy vlastností v rámci strukturovaných dat protokolu. Pro zástupné názvy doporučujeme používat PascalCase. Například , {Item}{DateTime}.

Každá zpráva protokolu je uložena Action ve statickém poli vytvořeném pomocí LoggerMessage.Define. Ukázková aplikace například vytvoří pole pro popis zprávy protokolu pro zpracování pracovních položek:

private static readonly Action<ILogger, Exception> s_failedToProcessWorkItem;

V poli Actionzadejte:

  • Úroveň protokolování
  • Jedinečný identifikátor události (EventId) s názvem metody statického rozšíření.
  • Šablona zprávy (pojmenovaný formátovací řetězec)

Vzhledem k tomu, že pracovní položky jsou vyřazeny z fronty pro zpracování, služba pro pracovní úkony nastaví:

  • Úroveň protokolu na LogLevel.Critical.
  • ID události pro 13 s názvem FailedToProcessWorkItem metody.
  • Převede šablonu zprávy, definovanou pojmenovaným formátem, na řetězec.
s_failedToProcessWorkItem = LoggerMessage.Define(
    LogLevel.Critical,
    new EventId(13, nameof(FailedToProcessWorkItem)),
    "Epic failure processing item!");

Metoda LoggerMessage.Define se používá ke konfiguraci a definování delegáta Action , který představuje zprávu protokolu.

Úložiště strukturovaného protokolování můžou k obohacení protokolování použít název události, pokud je součástí ID události. Například Serilog používá název události.

Action je volána prostřednictvím silně typované metody rozšíření. Metoda PriorityItemProcessed zaznamená zprávu při každém zpracování pracovní položky. FailedToProcessWorkItem je volána, pokud a když dojde k výjimce:

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

Zkontrolujte výstup konzoly aplikace:

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

Pokud chcete předat parametry do zprávy protokolu, definujte při vytváření statického pole až šest typů. Ukázková aplikace zaznamená podrobnosti pracovní položky při zpracování položek definováním WorkItem typu pole Action :

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

Šablona zprávy protokolu delegáta obdrží zástupné hodnoty z zadaných typů. Ukázková aplikace definuje delegáta pro přidání pracovní položky, kde je WorkItemparametr položky:

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

Statická metoda rozšíření pro protokolování, že pracovní položka je zpracovávána, PriorityItemProcessedobdrží hodnotu argumentu pracovní položky a předá ji delegátu Action :

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

V metodě ExecuteAsync pracovní služby PriorityItemProcessed je volána k protokolování zprávy:

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

Zkontrolujte výstup konzoly aplikace:

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

Strážené optimalizace na úrovni protokolů

Výkon můžete optimalizovat tak, že před vyvoláním odpovídající LogLevel metody zkontrolujete ILogger.IsEnabled(LogLevel) pomocí Log* příkazu. Pokud není pro dané LogLevel nakonfigurováno protokolování, ILogger.Log není voláno. Kromě toho se vyhne krabicování a alokaci typů hodnot object[] (pro reprezentaci parametrů).

Další informace najdete tady:

Viz také