如何在不添加其他上下文属性的情况下将自定义属性推送到 Serilog 的 LogContext?

Con*_*ver 7 serilog asp.net-core-webapi

在 .Net Core 2.2 API 中,我尝试使用 Serilog 记录到 SQL Server,以利用其结构化日志记录功能。除了标准字段(Id、Timestamp、Level、Message、MessageTemplate、LogEvent)之外,我还需要在每个日志条目中捕获用户名和 IP 地址。我看过这篇文章,但我想在一个地方做这件事,这样开发人员就不必在每个日志语句中手动添加它。我的Startup.csctor 中有以下代码段:

    public Startup(IConfiguration configuration)
    {
        _configuration = configuration;

        Log.Logger = new LoggerConfiguration()
            .ReadFrom.Configuration(configuration)
            .Enrich.FromLogContext()
            .Enrich.With<SerilogContextEnricher>()
            .CreateLogger();
    }
Run Code Online (Sandbox Code Playgroud)

.Enrich.FromLogContext()调用使静态 LogContext 可供我在自定义中间件中添加自定义属性(用户名和 IP 地址),如下所示:

    public class SerilogMiddleware
    {
        private static readonly ILogger Log = Serilog.Log.ForContext<SerilogMiddleware>();
        private readonly RequestDelegate _next;

        public SerilogMiddleware(RequestDelegate next)
        {
            this._next = next ?? throw new ArgumentNullException(nameof(next));
        }

        public async Task Invoke(HttpContext httpContext)
        {
            if (httpContext == null) throw new ArgumentNullException(nameof(httpContext));

            // Get user info
            var user = httpContext.User.FindFirst(ClaimTypes.Name)?.Value ?? "anonymous";

            // Get client info
            var client = httpContext.Connection.RemoteIpAddress.ToString() ?? "unknown";

            // enrich LogContext with user info
            using (LogContext.PushProperty("User", user))
            using (LogContext.PushProperty("Client", client))
            {
                try
                {
                    await _next(httpContext);
                }

                // Never caught, because LogException() returns false.
                catch (Exception ex) when (LogException(httpContext, ex)) { }
            }
        }

        private static bool LogException(HttpContext httpContext, Exception ex)
        {
            var logForContext = Log.ForContext("StackTrace", ex.StackTrace);

            logForContext.Error(ex, ex.Message);

            return false;
        }
    }
Run Code Online (Sandbox Code Playgroud)

但是,该.Enrich.FromLogContext()调用还会向 LogEvent 字段添加 ActionId、ActionName、RequestId、RequestPath 和 CorrelationId 属性。我不想用这 5 个属性来膨胀我的日志表。我当前的解决方案是使用以下自定义丰富器来丰富我的记录器,从 EventLog 中删除这些属性。

    public class SerilogContextEnricher : ILogEventEnricher
    {
        public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
        {
            logEvent.RemovePropertyIfPresent("ActionId");
            logEvent.RemovePropertyIfPresent("ActionName");
            logEvent.RemovePropertyIfPresent("RequestId");
            logEvent.RemovePropertyIfPresent("RequestPath");
            logEvent.RemovePropertyIfPresent("CorrelationId");
        }
    }
Run Code Online (Sandbox Code Playgroud)

这一切都很好,但最初将这些属性添加到记录器然后删除它们似乎很愚蠢。有没有人知道一种将自定义属性普遍推送到所有日志条目的方法,而无需添加和删除不需要的属性?

bre*_*ret 4

Serilog 允许您将依赖注入 (DI) 中的服务注入到日志记录管道中。您可以实现一个自定义的ILogEventEnricher注入IHttpContextAccessor来解析当前的 HTTP 上下文:

class HttpContextLogEventEnricher : ILogEventEnricher
{
  private readonly IHttpContextAccessor _contextAccessor;

  HttpContextLogEventEnricher(IHttpContextAccessor contextAccessor)
  {
    _contextAccessor = contextAccessor;
  }

  public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
  {
    // HttpContext may be null if there is no active context
    var userName = _contextAccessor.HttpContext?.User.Identity.Name;
    
    if (!string.IsNullOrEmpty(userName))
    {
      var userNameProperty = propertyFactory.CreateProperty("UserName", userName);
      logEvent.AddPropertyIfAbsent(userNameProperty);
    }
  }
}

Run Code Online (Sandbox Code Playgroud)

使用依赖注入和 HttpContextAccessor 注册您的客户丰富器。使用 Microsoft DI 容器:

services.AddHttpContextAccessor();
services.AddTransient<ILogEventEnricher, HttpContextLogEventEnricher>();
Run Code Online (Sandbox Code Playgroud)

最后,在您的 Serilog 配置中使用该ReadFrom.Services()方法。ILogEventEnrichersSerilog 将使用它在服务提供商中找到的所有内容:

Host.CreateDefaultBuilder().UseSerilog(
  (hostBuilderContext, serviceProvider, loggerConfiguration) =>
    {
      loggerConfiguration.ReadFrom.Services(serviceProvider)
    }
Run Code Online (Sandbox Code Playgroud)

注意:该ReadFrom.Services()扩展是 NuGet 包的一部分Serilog.Extensions.Hosting(包含在Serilog.AspNetCore包中)。