Add Username into Serilog
You can create a middleware to put required property to LogContext.
public class LogUserNameMiddleware
{
private readonly RequestDelegate next;
public LogUserNameMiddleware(RequestDelegate next)
{
this.next = next;
}
public Task Invoke(HttpContext context)
{
LogContext.PushProperty("UserName", context.User.Identity.Name);
return next(context);
}
}
Also you need to add the following to your logger configuration:
.Enrich.FromLogContext()
In Startup add the middleware LogUserNameMiddleware
, and also note that the middleware should be added after UserAuthentication
, in order to have context.User.Identity
initialized
e.g.
app.UseAuthentication();
app.UseMiddleware<LogUserNameMiddleware>();
An alternative to using middleware is to use an action filter.
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;
using Serilog.Context;
namespace Acme.Widgets.Infrastructure
{
public class LogEnrichmentFilter : IActionFilter
{
private readonly IHttpContextAccessor httpContextAccessor;
public LogEnrichmentFilter(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
public void OnActionExecuting(ActionExecutingContext context)
{
var httpUser = this.httpContextAccessor.HttpContext.User;
if (httpUser.Identity.IsAuthenticated)
{
var appUser = new AppIdentity(httpUser);
LogContext.PushProperty("Username", appUser.Username);
}
else
{
LogContext.PushProperty("Username", "-");
}
}
public void OnActionExecuted(ActionExecutedContext context)
{
// Do nothing
}
}
}
In your Startup.ConfigureServices
you will need to:
- Ensure
IHttpContextAccessor
is added to the IoC container - Add the
LogEnrichmentFilter
to the IoC container, scoped to the request - Register
LogEnrichmentFilter
as a global action filter
Startup.cs
:
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddScoped<LogEnrichmentFilter>();
services.AddMvc(o =>
{
o.Filters.Add<LogEnrichmentFilter>();
});
You should then have the current username in the log context for code that runs in the MVC action invocation pipeline. I imagine the username would be attached to a few more log entries if you used a resource filter instead of an action filter, as they run slightly earlier in the pipeline (I've only just found out about these!)
There is a number of issues with the approach suggested by @Alex Riabov.
- One needs to
Dispose
the pushed property - The
Invoke
method in a middleware is asynchronous, so you can't justreturn next()
, you needawait next()
- The request information is logged by
UseSerilogRequestLogging()
middleware. If the property is popped before it is reached, the property becomes empty.
To fix them, I could suggest the following modifications.
In the middleware:
public async Task Invoke(HttpContext context)
{
using (LogContext.PushProperty("UserName", context.User.Identity.Name ?? "anonymous"))
{
await next(context);
}
}
In Startup.cs
:
appl.UseRouting()
.UseAuthentication()
.UseAuthorization()
.UseMiddleware<SerilogUserNameMiddleware>()
.UseSerilogRequestLogging()
.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapRazorPages();
endpoints.MapHealthChecks("/health");
});
If you are using Serilog.AspNetCore it's very easy to add authentication/user properties.
app.UseSerilogRequestLogging(options =>
{
options.EnrichDiagnosticContext = PushSeriLogProperties;
});
public void PushSeriLogProperties(IDiagnosticContext diagnosticContext, HttpContext httpContext)
{
diagnosticContext.Set("SomePropertyName", httpContext.User...);
}