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
?
And when you need it you can get it with : HttpContext context = CallContext. LogicalGetData("CurrentContextKey") as HttpContext; I hope that helps.
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.
An ILoggerProvider ILogger is a logger that logs to that specific provider. An ILoggerFactory ILogger is a logger that logs to all registered providers.
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.
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>");
//
});
}
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;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With