共用方式為


.NET 中的高效能日志

類別 LoggerMessage 會公開功能來建立可快取委派,相較於 記錄器擴充方法,這些委派需要較少的物件配置和減少計算額外負荷,例如 LogInformationLogDebug。 對於高效能記錄的案例,請使用 LoggerMessage 模式。

LoggerMessage 提供以下效能優勢,相較於日誌擴展方法:

  • 記錄器擴充方法需要 "boxing" (轉換) 實值型別,例如將 int 轉換為 objectLoggerMessage 模式可使用靜態 Action 欄位和擴充方法搭配強型別參數來避免 boxing。
  • 記錄器擴充方法在每次寫入記錄訊息時,都必須剖析訊息範本 (具名格式字串)。 LoggerMessage 只需在定義訊息時剖析範本一次。

這很重要

與其使用LoggerMessage類別來創建高效能日誌,您可以在 .NET 6 和更新版本中使用LoggerMessage屬性。 提供 LoggerMessageAttribute 來源產生記錄支援,其設計目的是為新式 .NET 應用程式提供高度可用且高效能的記錄解決方案。 如需詳細資訊,請參閱 編譯時期記錄來源產生 (.NET 基礎)

範例應用程式示範 LoggerMessage 的功能,含有優先順序佇列處理工人的服務。 應用程式會依優先順序處理工作專案。 當這些作業發生時,會使用 LoggerMessage 模式產生記錄訊息。

小提示

所有記錄範例原始程式碼都可在範例瀏覽器中下載。 如需詳細資訊,請參閱 瀏覽程式碼範例: 在 .NET 中記錄

定義記錄器訊息

使用 Define(LogLevel、EventId、String) 來建立 Action 用於記錄訊息的委派。 Define 多載允許將最多六個類型參數傳遞至具名格式字串(範本)。

提供給 方法的 Define 字串是範本,而不是插補字串。 佔位符會按照指定類型的順序填入。 範本中的佔位元名稱應具有描述性,並且在不同範本間保持一致。 它們可作為結構化記錄數據內的屬性名稱。 我們建議針對佔位元名稱使用 Pascal大小寫 。 例如,{Item}{DateTime}

每個記錄訊息都會 Action 保留在 LoggerMessage.Define所建立的靜態字段中。 例如,範例應用程式會建立欄位來描述處理工作項目的記錄訊息:

private static readonly Action<ILogger, Exception> s_failedToProcessWorkItem;

針對Action指定:

  • 記錄層級。
  • 具有靜態擴充方法名稱的唯一事件標識碼 (EventId)。
  • 訊息模板(具名稱的格式字串)。

當工作專案從佇列中移出以進行處理時,工作服務應用程式會設定:

  • 記錄層級至 LogLevel.Critical
  • 事件標識碼13FailedToProcessWorkItem方法的名稱。
  • 訊息範本(具名格式字串)轉換成字串。
s_failedToProcessWorkItem = LoggerMessage.Define(
    LogLevel.Critical,
    new EventId(13, nameof(FailedToProcessWorkItem)),
    "Epic failure processing item!");

方法 LoggerMessage.Define 可用來設定及定義 Action 代表記錄訊息的委派。

結構化日誌存儲可能會在提供事件標識碼的情況下使用事件名稱來豐富日誌內容。 例如, Serilog 會使用事件名稱。

Action 是透過強類型擴充方法被調用。 方法 PriorityItemProcessed 會在每次處理工作項目時記錄訊息。 FailedToProcessWorkItem 會在發生例外狀況時呼叫 :

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

檢查應用程式的主控台輸出:

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

若要將參數傳遞至記錄訊息,請在建立靜態字段時定義最多六種類型。 範例應用程式會藉由定義 WorkItem 欄位的類型 Action 來記錄處理專案時的工作項目詳細資料:

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

委派的記錄訊息範本會從所提供的型別接收佔位符的值。 範例應用程式針對新增工作項目定義了一個委派,而該工作項目的參數為 WorkItem

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

記錄工作專案正在被處理的靜態擴充方法, PriorityItemProcessed 會接收工作專案的引數值,並將它傳給 Action 委派函式:

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

在工作者服務的方法中 ExecuteAsync ,會呼叫 PriorityItemProcessed 來記錄訊息:

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

檢查應用程式的主控台輸出:

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

定義記錄器訊息範圍

DefineScope(string) 方法會Func<TResult>建立委派來定義記錄範圍DefineScope 多載允許將最多六個類型參數傳遞至具名格式字串(範本)。

Define 方法也是如此,提供給 DefineScope 方法的字串是模板,而不是插值字串。 佔位符會按照指定類型的順序填入。 範本中的佔位元名稱應具有描述性,並且在不同範本間保持一致。 它們可作為結構化記錄數據內的屬性名稱。 我們建議針對佔位元名稱使用 Pascal大小寫 。 例如,{Item}{DateTime}

定義記錄範圍以使用DefineScope方法套用至一系列記錄訊息。 在 IncludeScopes的主控台紀錄器區段中開啟

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

若要建立記錄範圍,請新增字段來保存 Func<TResult> 範圍的委派。 範例應用程式會建立名為 s_processingWorkScope 的欄位(內部/LoggerExtensions.cs):

private static readonly Func<ILogger, DateTime, IDisposable?> s_processingWorkScope;

使用 DefineScope 來創建委任。 叫用委派時,最多可以指定六個型別做為樣板自變數。 範例應用程式會使用訊息範本,其中包含處理開始的日期時間:

s_processingWorkScope =
    LoggerMessage.DefineScope<DateTime>(
        "Processing scope, started at: {DateTime}");

提供記錄訊息的靜態擴充方法。 包含出現在訊息範本中之具名屬性的任何類型參數。 範例應用程式會採用 DateTime ,以取得自訂時間戳來記錄並傳 _processingWorkScope回 :

public static IDisposable? ProcessingWorkScope(
    this ILogger logger, DateTime time) =>
    s_processingWorkScope(logger, time);

範圍會將記錄延伸模組呼叫包裝在 using 區塊中:

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

檢查應用程式主控台輸出中的記錄訊息。 下列結果顯示記錄檔訊息的優先順序,其中包含記錄範圍訊息:

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'
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: D:\source\repos\dotnet-docs\docs\core\extensions\snippets\logging\worker-service-options
info: WorkerServiceOptions.Example.Worker[1]
      => Processing scope, started at: 04/11/2024 11:27:52
      Processing priority item: Priority-High (dbad6558-60cd-4eb1-8531-231e90081f62): 'Validate collection'
info: WorkerServiceOptions.Example.Worker[1]
      => Processing scope, started at: 04/11/2024 11:27:52
      Processing priority item: Priority-Medium (1eabe213-dc64-4e3a-9920-f67fe1dfb0f6): 'Propagate selections'
info: WorkerServiceOptions.Example.Worker[1]
      => Processing scope, started at: 04/11/2024 11:27:52
      Processing priority item: Priority-Medium (1142688d-d4dc-4f78-95c5-04ec01cbfac7): 'Enter pooling [contention]'
info: WorkerServiceOptions.Example.Worker[1]
      => Processing scope, started at: 04/11/2024 11:27:52
      Processing priority item: Priority-Low (e85e0c4d-0840-476e-b8b0-22505c08e913): 'Health check network'
info: WorkerServiceOptions.Example.Worker[1]
      => Processing scope, started at: 04/11/2024 11:27:52
      Processing priority item: Priority-Deferred (07571363-d559-4e72-bc33-cd8398348786): 'Ping weather service'
info: WorkerServiceOptions.Example.Worker[1]
      => Processing scope, started at: 04/11/2024 11:27:52
      Processing priority item: Priority-Deferred (2bf74f2f-0198-4831-8138-03368e60bd6b): 'Set process state'
info: Microsoft.Hosting.Lifetime[0]
      Application is shutting down...

記錄層級受防護優化

另一個效能優化方法是,在調用對應的LogLevel方法之前,使用ILogger.IsEnabled(LogLevel)Log*來進行檢查。 未針對指定的 LogLevel 設定日誌時,下列敘述為真:

  • ILogger.Log 未被呼叫。
  • 避免表示 object[] 參數的配置。
  • 避免實值型別 Boxing。

如需詳細資訊,請參閱:

另請參閱