Add Username into Serilog
Solution 1
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>();
Solution 2
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...);
}
Solution 3
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!)
Solution 4
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");
});
Solution 5
Simply you can achieve it in two steps
1- Create an Enricher that can access services.
using Microsoft.AspNetCore.Http;
using Serilog.Core;
using Serilog.Events;
using System.Security.Claims;
namespace CoolProject.Logging.Enricher;
public class UserEnricher : ILogEventEnricher
{
private readonly IHttpContextAccessor _httpContextAccessor;
public UserEnricher() : this(new HttpContextAccessor())
{
}
//Dependency injection can be used to retrieve any service required to get a user or any data.
//Here, I easily get data from HTTPContext
public UserEnricher(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty(
"UserId", _httpContextAccessor.HttpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier) ?? "anonymous"));
}
}
2-Use With to include your UserEnricher.
loggerConfiguration.Enrich.FromLogContext()
.MinimumLevel.Is(level)
.Enrich.With<UserEnricher>()
It only requires two steps to add the user enricher, but I will also add my driver code. Don't forget to inject IHttpContextAccessor!
public static IHostBuilder UseLogging(this IHostBuilder webHostBuilder, string applicationName = null)
=> webHostBuilder.UseSerilog((context ,loggerConfiguration) =>
{
var logOptions = context.Configuration.GetSection("logging");
var serilogOptions = logOptions.GetSection("serilog").Get<SerilogOptions>();
if (!Enum.TryParse<LogEventLevel>(serilogOptions.Level, true, out var level))
{
level = LogEventLevel.Error;
}
loggerConfiguration.Enrich.FromLogContext()
.MinimumLevel.Is(level)
.Enrich.With<UserEnricher>()
.Enrich.WithProperty("Environment", context.HostingEnvironment.EnvironmentName)
.Enrich.WithProperty("ApplicationName", applicationName);
loggerConfiguration.WriteTo.Console(outputTemplate: "{Timestamp:HH:mm:ss} [{Level}] {Environment} {ApplicationName} {UserId} {Message:lj}{NewLine}{Exception}");
});
Related videos on Youtube
Muflix
Updated on March 07, 2022Comments
-
Muflix about 2 years
I have this Serilog configuration in program.cs
public class Program { public static IConfiguration Configuration { get; } = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true) .Build(); public static void Main(string[] args) { Log.Logger = new LoggerConfiguration() .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) .MinimumLevel.Override("System", LogEventLevel.Warning) .WriteTo.MSSqlServer(Configuration.GetConnectionString("DefaultConnection"), "dbo.Log") .Enrich.WithThreadId() .Enrich.WithProperty("Version", "1.0.0") .CreateLogger(); try { BuildWebHost(args).Run(); } catch (Exception ex) { Log.Fatal(ex, "Host terminated unexpectedly"); } finally { Log.CloseAndFlush(); } } public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>() .UseSerilog() .Build(); }
Now i want to add
HttpContext.Current.User.Identity.Name
into all log messages.I tried to create new Enrich class following documentation https://github.com/serilog/serilog/wiki/Configuration-Basics#enrichers
class UsernameEnricher : ILogEventEnricher { public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory, HttpContext httpContext) { logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty( "Username", httpContext.User.Identity.Name)); } }
But there is conflict with ILogEventEnricher which does not know HttpContext.
I also tried to install Nuget package Serilog.Web.Classic which contains Username Enricher, but there is conflict between target framework .Net Framework and .Net Core therefore i cannot use this plugin.
Any idea ?
-
Muflix almost 6 yearsIt works, you are my hero ! :)) (Also the middleware has to be loaded
app.UseMiddleware<LogUserNameMiddleware>();
instartup.cs
) -
Cocowalla over 5 yearsI tried this, but it doesn't seem to work - the middleware is always called before the authentication handler, so it doesn't have a user. I tried assing it after the call to
app.UseAuthentication()
, but with the same result. Any ideas? -
Muflix over 5 yearsCocowalla: I do not use Authentication middleware in my scenario :/
-
Cocowalla over 5 years@JianYA I did, by using an action filter instead of middleware. I'm on mobile now, it I'll post an answer here later if I remember!
-
JianYA over 5 years@Cocowalla Thank you!
-
Cocowalla over 5 years@JianYA answer posted, hope it helps!
-
JianYA over 5 yearsHello! Thank you for your answer. How does this work when the user is logging in? As of now it doesn't seem like anyone can detect when the user first logs in, only the subsequent requests after.
-
Cocowalla over 5 years@JianYA It will only add the username to the log context after authorisation. If I wanted to log something during the actual signin process, I'd handle that separately, in the controller/handler/service that was responsible.
-
JianYA over 5 yearsI see. Thank you!
-
spankymac over 4 yearsWhat is the 'AppIdentity' method in your code above?
-
Cocowalla over 4 years@spankymac I didn't include it here, but it's not important to what I'm demonstrating -
AppIdentity
simply extendsClaimsIdentity
to provide some convenience properties for accessing claim values -
Josh Brown over 4 years
app.UseMiddleware<LogUserNameMiddleware>()
may be best placed as the line precedingapp.UseMvc()
, make sure that any authentication middleware is before it. -
Mike almost 4 yearsThere is a number of issues with this approach.
-
Stephan almost 4 yearsThis answer really small code snippet helped me greatly. Possibly even better then other answers
-
Himal Patel over 3 yearsThis would add the "SomePropertyName" only to the Request Log Entry. How can I add this property to all the log entries that i write to serilog ? PushSeriLogProperties method isn't getting called for other logs and doesn't carry this property.
-
flux over 3 yearsThe question was about adding a user property to Serilog. User properties are associated with a request. If you want to add other properties independent of the httpcontext look at github.com/serilog/serilog/wiki/Enrichment.