从 ILogger 访问当前的 HttpContext

Access the current HttpContext from a ILogger

在 ASP.NET Core 1.0 中,我有一个自定义实现的 ILoggerProviderILogger 接口。我希望能够从 Log 方法访问 HttpContext。

我似乎需要将 IHttpContextAccessor 注入到自定义 ILogger 中,但找不到具体操作方法。 ILoggerProvider对象是在启动时创建的,CreateLogger方法不允许依赖注入。

有没有简单的方法来使用 ILogger 的依赖注入?

这是一个例子

Startup.cs

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    }

    public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory, IServiceProvider serviceProvider)
    {
        loggerFactory.AddCustomLogger(serviceProvider.GetService<IHttpContextAccessor>());
        // 
    }

自定义记录器:

public class CustomLogProvider : ILoggerProvider
{

    private readonly Func<string, LogLevel, bool> _filter;
    private readonly IHttpContextAccessor _accessor;

    public CustomLogProvider(Func<string, LogLevel, bool> filter, IHttpContextAccessor accessor)
    {
        _filter = filter;
        _accessor = accessor;
    }

    public ILogger CreateLogger(string categoryName)
    {
        return new CustomLogger(categoryName, _filter, _accessor);
    }

    public void Dispose()
    {
    }
}

public class CustomLogger : ILogger
{
    private string _categoryName;
    private Func<string, LogLevel, bool> _filter;
    private readonly IHttpContextAccessor _accessor;

    public CustomLogger(string categoryName, Func<string, LogLevel, bool> filter, IHttpContextAccessor accessor)
    {
        _categoryName = categoryName;
        _filter = filter;
        _accessor = accessor;
    }

    public IDisposable BeginScope<TState>(TState state)
    {
        return null;
    }

    public bool IsEnabled(LogLevel logLevel)
    {
        return (_filter == null || _filter(_categoryName, logLevel));
    }

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
    {
        if (!IsEnabled(logLevel))
        {
            return;
        }

        if (formatter == null)
        {
            throw new ArgumentNullException(nameof(formatter));
        }

        var message = formatter(state, exception);

        if (string.IsNullOrEmpty(message))
        {
            return;
        }

        message = $"{ logLevel }: {message}";

        if (exception != null)
        {
            message += Environment.NewLine + Environment.NewLine + exception.ToString();
        }
        if(_accessor.HttpContext != null) // you should check HttpContext 
        {
            message += Environment.NewLine + Environment.NewLine + _accessor.HttpContext.Request.Path;
        }
        // your implementation

    }

}
public static class CustomLoggerExtensions
{
    public static ILoggerFactory AddCustomLogger(this ILoggerFactory factory, IHttpContextAccessor accessor,
                                          Func<string, LogLevel, bool> filter = null)
    {
        factory.AddProvider(new CustomLogProvider(filter, accessor));
        return factory;
    }
}

虽然 上面的方法可行,但我更愿意实现自定义 IRequestLogger 而不是注入 HttpContextAccessor。实现如下(未测试):

public interface IRequestLogger<T>
{
    void Log(LogLevel logLevel, EventId eventId, string message); // you can change this
}

public class RequestLogger<T> : IRequestLogger<T>
{
    private readonly IHttpContextAccessor _accessor;
    private readonly ILogger _logger;
    public RequestLogger(ILogger<T> logger, IHttpContextAccessor accessor)
    {
        _accessor = accessor;
        _logger = logger;
    }

    public void Log(LogLevel logLevel, EventId eventId, string message)
    {
        Func<object, Exception, string> _messageFormatter = (object state, Exception error) =>
        {
            return state.ToString();
        };
        _logger.Log(LogLevel.Critical, 0, new FormattedLogValues(message), null, _messageFormatter);
    }
}

简单用法:

public class LogType
{

}
public void ConfigureServices(IServiceCollection services)
{
     services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
     services.AddSingleton(typeof(IRequestLogger<>), typeof(RequestLogger<>));
}

public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
     loggerFactory.AddConsole(true);
     app.Run(async (context) =>
     {
         var requestLogger = context.RequestServices.GetService<IRequestLogger<LogType>>();
         requestLogger.Log(LogLevel.Critical, 11, "<message>");
         //
     });
}

我没有测试过这段代码,但我相信该方法将类似于以下内容。

在您的 Startup.cs class 中,通过将以下行添加到 ConfigureServices 方法来注册 HttpContextAccessor:

services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();

然后,将 IHttpContextAccessor httpContextAccessor 的附加参数添加到 Configure 方法(仍在 Startup.cs 中),例如:

public void Configure(
                      IApplicationBuilder app, 
                      IHostingEnvironment env, 
                      ILoggerFactory loggerFactory, 
                      IHttpContextAccessor httpContextAccessor)

在此 Configure 方法中,您现在可以添加调用记录器工厂的扩展方法来创建您的自定义日志提供程序,例如:

loggerFactory.AddMyCustomProvider(httpContextAccessor);

其中(您需要创建的)扩展方法类似于:

public static class MyCustomLoggerExtensions
  {
    public static ILoggerFactory AddMyCustomProvider(
        this ILoggerFactory factory, 
        IHttpContextAccessor httpContextAccessor)
    {
      factory.AddProvider(new MyCustomLoggerProvider(httpContextAccessor));
      return factory;
    }
  }