.NET 中的高效能記錄

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

相較於記錄器擴充方法,LoggerMessage 提供下列效能優勢:

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

重要

您可以使用 .NET 6. 或更新版本中的 LoggerMessage 屬性,而不是使用 LoggerMessage 類別來建立高效能記錄。 LoggerMessageAttribute 提供來源產生記錄支援,其設計目的是為現代化 .NET 應用程式,提供高度可用且高效能的記錄解決方案。 如需詳細資訊,請參閱編譯時間記錄來源產生 (.NET 基本概念)

樣本應用程式示範 LoggerMessage 具有優先順序佇列處理背景工作角色服務的功能。 應用程式會依優先順序處理工作項目。 在進行這些作業時,將會使用 LoggerMessage 模式來產生記錄訊息。

提示

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

定義記錄器訊息

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

提供給 Define 方法的字串是範本,而不是內插字串。 預留位置會依照指定類型的順序填入。 範本中的預留位置名稱應該是描述性名稱,而且在範本之間應該保持一致。 它們將作為結構化記錄資料內的屬性名稱。 建議您針對預留位置名稱使用 Pascal 大小寫。 例如,{Item}{DateTime}

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

private static readonly Action<ILogger, Exception> s_failedToProcessWorkItem;

針對 Action,請指定:

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

當工作項目取消佇列處理時,背景工作服務應用程式會設定:

  • 將記錄層級設定為 LogLevel.Critical
  • 將事件識別碼設定為含有 FailedToProcessWorkItem 方法名稱的 13
  • 將訊息範本 (具名格式字串) 設定為字串。
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

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

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 方法,定義要套用至一系列記錄訊息的記錄範圍。 在 appsettings.json 的主控台記錄器區段中啟用 IncludeScopes

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

若要建立記錄範圍,請新增欄位以保留該範圍的 Func<TResult> 委派。 範例應用程式會建立稱為 s_processingWorkScope (Internal/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...

記錄層級受防護最佳化

若要進行另一個效能最佳化,請在對應 Log* 方法的調用之前,使用 ILogger.IsEnabled(LogLevel) 檢查 LogLevel。 未針對指定的 LogLevel 設定記錄時,下列陳述式為 true:

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

如需詳細資訊,請參閱:

另請參閱