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:

  1. Ensure IHttpContextAccessor is added to the IoC container
  2. Add the LogEnrichmentFilter to the IoC container, scoped to the request
  3. 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.

  1. One needs to Dispose the pushed property
  2. The Invoke method in a middleware is asynchronous, so you can't just return next(), you need await next()
  3. 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...);
    }