HttpContext from an Enricher or a Middleware
See original GitHub issueHi everyone,
I’m using .Net Core 3.1 and I’ve read a lot on how to enrich Serilog with HttpContext information. I’ve found more than one way to do it and i would like to have your opinion.
Suppose that I would like to enrich with the logged username. I must have access to the HttpContext. Here’s the 2 ways that I’ve found:
- Create a new class UserNameEnricher that receive IHttpContextAccessor in the constructor and add it to the Serilog configuration
- Create a new class SerilogUserNameMiddleware and add it to the pipeline in the Configure function
UserNameEnricher
public static class UserNameLoggerConfigurationExtensions
{
public static LoggerConfiguration WithUserName(this LoggerEnrichmentConfiguration enrichmentConfiguration)
{
if (enrichmentConfiguration == null) throw new ArgumentNullException(nameof(enrichmentConfiguration));
return enrichmentConfiguration.With<UserNameEnricher>();
}
}
public class UserNameEnricher : ILogEventEnricher
{
private readonly IHttpContextAccessor _contextAccessor;
public UserNameEnricher() : this(new HttpContextAccessor())
{
}
public UserNameEnricher(IHttpContextAccessor contextAccessor)
{
_contextAccessor = contextAccessor;
}
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
var userName = _contextAccessor.HttpContext?.User?.Identity?.Name;
var property = propertyFactory.CreateProperty("UserName", userName);
logEvent.AddOrUpdateProperty(property);
}
}
And then, add it to Serilog
public class Program
{
public static int Main(string[] args)
{
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables()
.Build();
// Configure logger
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration)
.Enrich.FromLogContext()
.Enrich.WithUserName()
.CreateLogger();
try
{
Log.Information("Configuring host...");
var host = CreateHostBuilder(args).Build();
Log.Information("Starting host...");
host.Run();
Log.Information("Program terminated successfully!");
return 0;
}
catch (Exception ex)
{
Log.Fatal(ex, "Program terminated unexpectedly!");
return 1;
}
finally
{
Log.CloseAndFlush();
}
}
private static IHostBuilder CreateHostBuilder(string[] args)
{
var builder = Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
webBuilder.UseSerilog();
});
return builder;
}
}
SerilogUserNameMiddleware
public class SerilogUserNameMiddleware
{
readonly RequestDelegate _next;
public SerilogUserNameMiddleware(RequestDelegate next)
{
if (next == null)
{
throw new ArgumentNullException(nameof(next));
}
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
if (httpContext == null)
{
throw new ArgumentNullException(nameof(httpContext));
}
var userName = httpContext.User?.Identity?.Name;
// Push the user name into the log context so that it is included in all log entries
using (LogContext.PushProperty("UserName", userName))
{
await _next(httpContext);
}
}
}
And add it to the pipeline
public void Configure(IApplicationBuilder app)
{
app.UseStaticFiles();
app.UseRouting();
app.UseMiddleware<SerilogUserNameMiddleware>();
app.UseCookiePolicy();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
So what is the best way to implement the Enricher if we must consider speed, memory allocation, etc. My goal is to add all the HttpRequest information available for the log (headers, querystring, etc).
My opinion would be to use the Enricher since that its seems to be cleaner than the middleware. Also, the code is only executed if the code use the logger. With the middleware, it is executed everytime. But since that many user propose the middleware, i’m a little bit confused.
Issue Analytics
- State:
- Created 4 years ago
- Comments:16 (8 by maintainers)
I had problems getting the approach with
ILogEventEnricher
to work, but later found that you need to add a call toAddHttpContextAccessor()
inConfigureServices
to be able to get the correct instance ofIHttpContextAccessor
. Otherwise it will use the one created in the default constructor which will always haveHttpContext = null
.See: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-context?view=aspnetcore-5.0#use-httpcontext-from-custom-components
I’ve made an HttpRequestEnricherMiddleware because I can use async method more easly to read the request body. By using the Serilog Enricher, I had to read it async but synchronise it afterward and I think it is not recommended. But when I read the log, i’m getting a
$type": "HttpContextInfo
. Is it possible to get ride of it?