簡體   English   中英

在 asp.net 內核中使用 serilog 維護單獨的 scope 上下文

[英]Maintaining separate scope context with serilog in asp.net core

我遇到了在ILogger<T>實例之間共享 scope 上下文的問題。

下面是我如何在 asp.net 核心 3.1 服務中配置 Serilog 並注入ILogger<T> 我有一個WithClassContext擴展,我在每個類的構造函數中調用它,以便將 class 名稱作為上下文屬性推送。 我發現錯誤的ClassName值出現在我的日志中。

當我在調試器中檢查注入的 ILogger object 時,我發現以下內容:

ILogger<T> -> Logger<T>
{
  _logger -> Serilog.Extensions.Logger.SerilogLogger
   {
      _logger -> Serilog.Core.Logger
      _provider -> Serilog.Extensions.Logger.SerilogLoggerProvider
      {
         CurrentScope -> Serilog.Extensions.Logger.SerilogLoggerScope
         {
           Parent -> Serilog.Extensions.Logger.SerilogLoggerScope
           _state -> Dictionary<string, object>
         }
      }
   }
}

所以我觀察到的是,每個注入的ILogger<T>都具有所有記錄器共享的相同_provider object。 _provider.CurrentScope似乎是SerilogLoggerScope對象的鏈表, _provider.CurrentScope指向列表中的最后一個節點, _provider.CurrentScope.Parent是前一個節點。 還有一個CurrentScope._state Dictionary<string, object>包含屬性名稱和值。 在寫出 scope 上下文時,如果有任何沖突的屬性名稱,則使用列表中的最后一個SerilogLoggerScope

所以使用下面的例子:

  1. FooService被創建並推送ClassName

    • CurrentScope._state -> {"ClassName", "FooService"}
  2. FooController被創建並推送ClassName

    • CurrentScope._state -> {"ClassName", "FooController"}
    • CurrentScope.Parent._state -> {"ClassName", "FooService"}
  3. FooService: CurrentScope現在和FooController一樣。

  4. 從所有類的日志現在推送"ClassName": "FooController"

我原以為通過注入ILogger<T> ,其他實例不會共享 scope 上下文。 我還研究了其他人如何將 class 名稱推送到日志記錄上下文中,我相信我也在做同樣的事情。

程序.cs:

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .UseSerilog(ConfigureLogger)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
            webBuilder.UseUrls("http://*:6050");
        });


private static void ConfigureLogger(HostBuilderContext ctx, IServiceProvider sp, LoggerConfiguration config)
{
    var shouldFormatElastic = !ctx.HostingEnvironment.EnvironmentName.Equals("local", StringComparison.OrdinalIgnoreCase);
    config.ReadFrom.Configuration(ctx.Configuration)
          .Enrich.FromLogContext()
          .Enrich.WithExceptionDetails();

    if (shouldFormatElastic)
    {
        var logFormatter = new ExceptionAsObjectJsonFormatter(renderMessage: true);
        config.WriteTo.Async(a =>
                                 a.Console(logFormatter, standardErrorFromLevel: LogEventLevel.Error));
    }
    else
    {
        config.WriteTo.Async(a =>
                                 a.Console(
                                     outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Properties:j}{Exception}{NewLine}",
                                     theme: SystemConsoleTheme.Literate));
    }
}

Controller:

[ApiController]
[Route("api/[controller]")]
public class FooController : ControllerBase
{
    private ILogger _logger;
    private readonly IFooService _fooService;

    /// <inheritdoc />
    public FooController(ILogger<FooController> logger, IFooService fooService)
    {
        _logger = logger.WithClassContext();
        _fooService = fooService;
    }
}

服務:

public class FooService : IFooService
{
    private readonly ILogger _logger;

    public FooService(ILogger<IFooService> logger)
    {
        _logger = logger.WithClassContext();
    }

LoggingExtensions.cs

public static class FooLoggingExtensions
{
    public static ILogger Here(this ILogger logger, [CallerMemberName] string memberName = null)
    {
        var state = new Dictionary<string, object>
        {
            {"MemberName", memberName }
        };

        // Don't need to dispose since this scope will last through the call stack.
        logger.BeginScope(state);
        return logger;
    }

    public static ILogger WithClassContext<T>(this ILogger<T> logger)
    {
        var state = new Dictionary<string, object>
        {
            {"ClassName", typeof(T).Name }
        };

        // Don't need to dispose since this scope will last through the call stack.
        logger.BeginScope(state);
        return logger;
    }
}

根據您嘗試執行的操作,BeginScope 似乎不是解決問題的正確工具。 您要解決的問題是每個日志都需要將 ClassName 作為日志消息的一部分。 為此,您可以修改您正在使用的 outputTemplate 以包含{SourceContext} ,然后在您的構造函數內部而不是調用logger.WithClassContext()您調用logger.ForContext<ClassName>()

請注意,我只修改了以下示例中的本地環境日志記錄。

private static void ConfigureLogger(HostBuilderContext ctx, IServiceProvider sp, LoggerConfiguration config)
{
    var shouldFormatElastic = !ctx.HostingEnvironment.EnvironmentName.Equals("local", StringComparison.OrdinalIgnoreCase);
    config.ReadFrom.Configuration(ctx.Configuration)
          .Enrich.FromLogContext()
          .Enrich.WithExceptionDetails();

    if (shouldFormatElastic)
    {
        var logFormatter = new ExceptionAsObjectJsonFormatter(renderMessage: true);
        config.WriteTo.Async(a =>
                                 a.Console(logFormatter, standardErrorFromLevel: LogEventLevel.Error));
    }
    else
    {
        config.WriteTo.Async(a =>
                                 a.Console(
                                     outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {SourceContext} {Message:lj}{NewLine}{Properties:j}{Exception}{NewLine}",
                                     theme: SystemConsoleTheme.Literate));
    }
}
[ApiController]
[Route("api/[controller]")]
public class FooController : ControllerBase
{
    private ILogger _logger;
    private readonly IFooService _fooService;

    /// <inheritdoc />
    public FooController(ILogger<FooController> logger, IFooService fooService)
    {
        _logger = logger.ForContext<FooController>();
        _fooService = fooService;
    }
}

我使用以下網站/博客作為參考來制定這個答案。
https://benfoster.io/blog/serilog-best-practices/#source-context
C# ASP.NET Core Serilog 添加 class 名稱和登錄方法
https://github.com/serilog/serilog/issues/968

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM