【问题标题】:How can I get the current HttpContext in a SeriLog Sink?如何在 SeriLog Sink 中获取当前的 HttpContext?
【发布时间】:2017-11-02 09:15:34
【问题描述】:

我正在创建自己的 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();

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

我有一个 MVC 站点,使用 Serilog.AspNetCore v2.1.0 针对 .Net 4.6.2 运行时运行 Asp.Net Core 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;
    }
}

【问题讨论】:

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


    【解决方案1】:

    更新 我想你可能想看看这篇文章: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
    • 配置记录器时,注入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();
    

    【讨论】:

    • 谢谢@tsimbalar。在 configure 步骤中,您将contextAccessor 传递给MyEnricher 对象的实例。这没有使用依赖注入,所以如果我在调用 Startup(以及随后的 ConfigureServices)之前在 Program Main 方法中配置日志记录,我将如何获取上下文?
    • 哦,是的,如果您在 Program Main 中,那将无法工作.... mmmm 不确定如何工作
    • 谢谢@tsimbalar。我之前曾尝试过一个中间件解决方案,并取得了不同程度的成功。我希望有更好的方法,但我会重新审视并更新......
    • 我有类似的问题。通过按照这里的建议编写中间件来解决。可能对其他人有用:nuget.org/packages/Serilog.Enrichers.AspNetCore.HttpContext
    【解决方案2】:

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

    在创建 Logger 时不要添加扩充器。您必须在可以访问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&lt;HttpContextToLog&gt;() 调用。

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-09-08
      • 1970-01-01
      • 2016-09-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多