Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Access the current HttpContext from a ILogger

In ASP.NET Core 1.0, I have a custom implementation of the ILoggerProvider and ILogger interfaces. I would like to be able to access the HttpContext from the Log method.

It seems I need to inject an IHttpContextAccessor into the custom ILogger, but can't find how to do that. The ILoggerProvider object is created at startup, and the CreateLogger method doesn't allow for dependency injection.

Is there a simple way to use dependency injection with ILogger?

like image 338
Flavien Avatar asked Jul 14 '16 01:07

Flavien


People also ask

How do I find HttpContext current?

And when you need it you can get it with : HttpContext context = CallContext. LogicalGetData("CurrentContextKey") as HttpContext; I hope that helps.

What is HttpContext?

HttpContext is an object that wraps all http related information into one place. HttpContext. Current is a context that has been created during the active request. Here is the list of some data that you can obtain from it.

What is the difference between ILogger and ILoggerFactory?

An ILoggerProvider ILogger is a logger that logs to that specific provider. An ILoggerFactory ILogger is a logger that logs to all registered providers.

What is HttpContext MVC?

HttpContext is a type which has a static Current property that you're using to get the current context. There isn't a System. Web. Mvc. HttpContext type.


2 Answers

Here is an example

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>());
        // 
    }

Custom Logger:

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

Although above way works, i would prefer to implement custom IRequestLogger instead of injecting HttpContextAccessor. Implementation is like below(it is not tested):

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);
    }
}

And simple usage:

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>");
         //
     });
}
like image 129
adem caglin Avatar answered Sep 21 '22 20:09

adem caglin


I haven't tested this code, but I believe the approach will be something like the following.

In your Startup.cs class, register a HttpContextAccessor by adding the following line to the ConfigureServices method:

services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();

Then, add an additional parameter for the IHttpContextAccessor httpContextAccessor to the Configure method (still inside Startup.cs), something like:

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

Inside this Configure method, you can add now invoke an extension method on the logger factory to create your custom log provider, something like:

loggerFactory.AddMyCustomProvider(httpContextAccessor);

where the extension method (that you need to create) will be something like:

public static class MyCustomLoggerExtensions
  {
    public static ILoggerFactory AddMyCustomProvider(
        this ILoggerFactory factory, 
        IHttpContextAccessor httpContextAccessor)
    {
      factory.AddProvider(new MyCustomLoggerProvider(httpContextAccessor));
      return factory;
    }
  }
like image 26
Chris Melinn Avatar answered Sep 25 '22 20:09

Chris Melinn