Как я могу получить текущий HttpContext в раковине SeriLog?

Я создаю свой собственный приемник SeriLog, реализующий ILogEventSink используя пример " Построение простого приемника" с целью регистрации некоторой информации из заявлений пользователей. Чтобы получить доступ к HttpContext в Core, я обычно вставляю в экземпляр 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();

Как я могу получить доступ к текущему HttpContext в методе Emit приемника? Или, например, можно создать приемник в DI-фреймворке?!

У меня есть сайт MVC, на котором работает среда Asp.Net Core 2 против среды выполнения.Net 4.6.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;
    }
}

2 ответа

Решение

ОБНОВЛЕНИЕ Я думаю, что вы можете посмотреть эту статью: 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(); 

Эта статья Николаса Блумхардта также может помочь: 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();

Я изо всех сил пытался сделать то же самое, и я наконец нашел правильное решение.

Не добавляйте обогащение при создании 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, так далее.

Другие вопросы по тегам