【问题标题】:Maintaining separate scope context with serilog in asp.net core在 asp.net 核心中使用 serilog 维护单独的范围上下文
【发布时间】:2021-02-24 21:58:21
【问题描述】:

我遇到了ILogger<T> 实例之间共享范围上下文的问题。

下面是我如何在我的 asp.net core 3.1 服务中配置 Serilog 并注入 ILogger<T>。我有一个WithClassContext 扩展,我在每个类的构造函数中调用它,以便将类名作为上下文属性推送。我发现错误的ClassName 值出现在我的日志中。

当我在调试器中检查注入的 ILogger 对象时,我发现以下内容:

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&lt;T&gt; 都具有所有记录器共享的相同_provider 对象。 _provider.CurrentScope 似乎是SerilogLoggerScope 对象的链接列表,_provider.CurrentScope 指向列表中的最后一个节点,_provider.CurrentScope.Parent 是前一个节点。还有一个 CurrentScope._state Dictionary&lt;string, object&gt; 包含属性名称和值。写出作用域上下文时,如果有任何冲突的属性名称,则使用列表中的最后一个SerilogLoggerScope

所以使用下面的例子:

  1. FooService 被创建并推送ClassName

    • CurrentScope._state -&gt; {"ClassName", "FooService"}
  2. FooController 被创建并推送ClassName

    • CurrentScope._state -&gt; {"ClassName", "FooController"}
    • CurrentScope.Parent._state -&gt; {"ClassName", "FooService"}
  3. FooService:CurrentScope 现在与FooController 相同。

  4. 所有类的日志现在推送"ClassName": "FooController"

我原以为通过注入ILogger&lt;T&gt;,范围上下文不会被其他实例共享。我还研究了其他人如何将类名推送到日志记录上下文中,我相信我也在这样做。

程序.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));
    }
}

控制器:

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

【问题讨论】:

    标签: c# .net asp.net-core .net-core serilog


    【解决方案1】:

    根据您的尝试,BeginScope 似乎不是解决问题的正确工具。您要解决的问题是每个日志都需要将 ClassName 作为日志消息的一部分。为此,您可以修改您使用的 outputTemplate 以包含 {SourceContext},然后在您的构造函数内部而不是调用 logger.WithClassContext() 您调用 logger.ForContext&lt;ClassName&gt;()

    请注意,我在以下示例中只修改了本地环境日志记录。

    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 add class name and method to log
    https://github.com/serilog/serilog/issues/968

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-10-17
      • 2022-06-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-20
      相关资源
      最近更新 更多