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

11

我正在创建自己的SeriLog接收器,使用构建简单接收器示例实现ILogEventSink,旨在记录来自用户声明的一些信息。要在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();

在 Emit 方法中如何获取当前 HttpContext 的访问权限?或者说,例如是否可以通过 DI 框架创建 sink?
我有一个运行 Asp.Net Core 2 框架、使用 .Net 4.6.2 运行时和 Serilog.AspNetCore v2.1.0 的 MVC 站点。
更新 - 解决方法
在 @tsimbalar 的提示下,我创建了类似以下代码的中间件。在我的 StartUp.Configure 方法中,我使用 app.UseMiddleware(); 在应用程序身份验证步骤之后添加它(否则将不会加载任何声明)。
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;
    }
}
2个回答

6

更新 我认为你可能想要查看这篇文章: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 注册到您的 ServiceCollection (例如,使用 AddHttpContextAccessor()): services.AddHttpContextAccessor();
  • 创建一个实现了ILogEventEnricher接口的类,并在其构造函数中接受IHttpContextAccessor
  • 配置您的日志记录器时,在Startup.Configure()中添加类型为IHttpContextAccessor的参数注入IHttpContextAccessor
  • 将此Enricher添加到记录器中

这个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();

感谢@tsimbalar。在配置步骤中,您正在将contextAccessor传递给MyEnricher对象的实例。这不是使用依赖注入,那么如果我在程序主方法中配置日志记录,而在启动(以及ConfigureServices)调用之前,我将如何获取上下文呢? - Gavin Sutherland
哦,如果你在程序主函数中,那是行不通的...嗯,不确定那该怎么办。 - tsimbalar
谢谢@tsimbalar。我之前尝试过使用中间件解决方案,但效果参差不齐。我希望有更好的方法,但我会重新审视并更新... - Gavin Sutherland
我遇到了类似的问题。按照这里建议的方式编写中间件后解决了。也许对其他人有用:https://www.nuget.org/packages/Serilog.Enrichers.AspNetCore.HttpContext - Ihar Yakimush

4

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

创建Logger时不要添加enricher。您需要在中间件中添加enricher,以便可以访问IServiceProvider。关键是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方法中,在IActionFilterIPageFilter等过滤器中。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接