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

时间:2017-11-02 09:15:35

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

我正在创建自己的SeriLog接收器,使用Building a Simple Sink示例实现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框架创建的接收器?!

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

更新 - 解决方法

在@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;
    }
}

2 个答案:

答案 0 :(得分:4)

<强>更新 我想你可能想看看这篇文章:http://mylifeforthecode.com/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
  • 配置记录器时,请注入IHttpContextAccessor(通过向IHttpContextAccessor
  • 添加Startup.Configure()类型的参数
  • 将此浓缩器添加到记录器

更丰富的可能看起来像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();

答案 1 :(得分:1)

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

创建Logger时不要添加浓缩器。您将必须在可访问IServiceProvider的中间件中添加扩展程序。关键是LogContext有一种方法Push,可以添加一个浓缩器:

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中,等等。