如何在 SeriLog Sink 中获取当前的 HttpContext?

How can I get the current HttpContext in a SeriLog Sink?

我正在使用 Building a Simple Sink 示例创建自己的实现 ILogEventSink 的 SeriLog 接收器,目的是从用户声明中记录一些信息。要访问 Core 中的 HttpContext,我通常会注入一个 IHttpContextAccessor 的实例,但该示例显示在扩展方法中创建一个接收器实例,例如

public class MySink : ILogEventSink
{
    private readonly IFormatProvider _formatProvider;

    public MySink(IFormatProvider formatProvider)
    {
        _formatProvider = formatProvider;
    }

    public void Emit(LogEvent logEvent)
    {
        // How to get HttpContext here?
    }
}

public static class MySinkExtensions
{
    public static LoggerConfiguration MySink(
              this LoggerSinkConfiguration loggerConfiguration,
              IFormatProvider formatProvider = null)
    {
        return loggerConfiguration.Sink(new MySink(formatProvider));
    }
}

...然后使用水槽...

var log = new LoggerConfiguration()
    .MinimumLevel.Information()
    .WriteTo.MySink()
    .CreateLogger();

如何在sink的Emit方法中获取到当前的HttpContext?或者是否可以让 DI 框架创建接收器?!

我有一个 MVC 站点 运行 Asp.Net 针对 .Net 4.6.2 运行时的 Core 2 框架使用 Serilog.AspNetCore v2.1.0.

更新 - 解决方法

根据@tsimbalar 的指示,我创建了类似于以下代码的中间件。在我的 StartUp.Configure 方法中,我使用 app.UseMiddleware<ClaimsMiddleware>(); 添加它 应用程序身份验证步骤发生后(否则将不会加载声明)。

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

    public ClaimsMiddleware(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 values from claims here
        var myVal = httpContext
                .User
                .Claims
                .Where(x => x.Type == "MyVal")
                .Select(x => x.Value)
                .DefaultIfEmpty(string.Empty)
                .SingleOrDefault();

        using (LogContext.PushProperty("MyVal", myVal))
        {
            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;
    }
}

更新 我想你可能想看看这篇文章:http://mylifeforthecode.github.io/enriching-serilog-output-with-httpcontext-information-in-asp-net-core/

想法是注册一个自定义中间件,它将在请求期间将所有上下文 信息添加到当前LogContext

要使其正常工作,您必须使用

配置您的记录器
Log.Logger = new LoggerConfiguration()
      // snip ....MinimumLevel.Debug()
      .Enrich.FromLogContext()                
      // snip ...
.CreateLogger(); 

Nicholas Blumhardt 的这篇文章也可能有所帮助:https://blog.getseq.net/smart-logging-middleware-for-asp-net-core/


警告 - 以下解决方案在这种情况下不起作用

如果记录器提前注册(在 Program.Main() 中),下面的解决方案将无法工作

首先,如果您想添加附加到已记录事件的额外信息,我相信您想要的是 Enricher

然后你可以:

  • 注册 IHttpContextAccessor into your ServiceCollection (for instance, using AddHttpContextAccessor()) : services.AddHttpContextAccessor();
  • 创建 ILogEventEnricher 的实现,在其构造函数中接受 IHttpContextAccessor
  • 配置记录器时,注入 IHttpContextAccessor(通过将 IHttpContextAccessor 类型的参数添加到 Startup.Configure()
  • 将此增强器添加到您的记录器

enricher 可能看起来像 https://github.com/serilog-web/classic/blob/master/src/SerilogWeb.Classic/Classic/Enrichers/ClaimValueEnricher.cs .

你会配置你的记录器:

var logger = new LoggerConfiguration()
                .EnrichWith(new MyEnricher(contextAccessor))
                .WriteTo.Whatever()
                .CreateLogger();

我一直在努力尝试做同样的事情,我终于找到了一个合适的解决方案。

创建 Logger 时不要添加 enricher。您必须在可以访问 IServiceProvider 的中间件中添加 enricher。关键是LogContext有个方法,Push,可以加一个enricher:

public async Task Invoke(HttpContext httpContext)
{
    IServiceProvider serviceProvider = httpContext.RequestServices;
    using (LogContext.Push(new LogEnricher(serviceProvider))) {
        await _next(httpContext);
    }
}

ConfigureServices 中,我添加了一个 services.AddScoped<HttpContextToLog>() 调用。

然后,我在几个地方填充 HttpContextToLog 对象,像这样访问它:

HttpContextToLog contextToLog = _serviceProvider.GetService<HttpContextToLog>();

Enrich 方法中,在 IActionFilter 中,在 IPageFilter 中,等等