18

I'm trying to use Serilog together with my ASP.Net Core 1.0 project. I just can't seem to get the current logged in user added to properties logged.

Has anyone figure this out yet?

I have tried this:

using System.Threading.Tasks;
using Serilog.Context;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using System.Security.Claims;
using xxx.Models;

namespace xxx.Utils
{
    public class EnrichSerilogContextMiddleware
    {
        private readonly RequestDelegate _next;
        public EnrichSerilogContextMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task Invoke(HttpContext httpContext)
        {

            var username = httpContext.User.Identity.Name;
            if (httpContext.User.Identity.IsAuthenticated)
            {
                var userFullName = (((ClaimsIdentity)httpContext.User.Identity).FindFirst(Member.FullnameClaimName).Value);
                var userName = "[email protected]";
                LoggerEnricher.AddEntryPointContext(userFullName, userName);
            }
            else
            {
                LoggerEnricher.AddEntryPointContext();
            }


            await _next(httpContext);
        }
    }

    public static class LoggerEnricher

    {
        public static void AddEntryPointContext(string userFullName = null, string username = null)
        {
            if (!string.IsNullOrWhiteSpace(username) || !string.IsNullOrWhiteSpace(userFullName))
            {
                LogContext.PushProperty("Username", username);
                LogContext.PushProperty("UserFullename", userFullName);
            }
            else
            {
                LogContext.PushProperty("Username", "Anonymous");
            }

        }

        public static void EnrichLogger(this IApplicationBuilder app)
        {
            app.UseMiddleware<EnrichSerilogContextMiddleware>();
        }
    }
}

I trigger this in Startup.cs by adding:

  public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole(Configuration.GetSection("Logging"));
        loggerFactory.AddDebug();
        loggerFactory.AddSerilog();
        app.EnrichLogger();
        ...
    }

But this always ends up with an "Anonymous" as the Username.

Thanks in advance

Søren Rokkedal

6
  • Have you tried adding this to your pipeline after authentication? Commented Oct 6, 2016 at 22:36
  • Not sure what You mean with this Commented Oct 10, 2016 at 14:27
  • I mean call app.EnrichLogger(); after the call to app.UseAuthentication(....);, or whatever your authentication method is. You're injecting the logger too early in the pipeline before the user has been authenticated so it will always be Anonymous. Commented Oct 11, 2016 at 11:34
  • I have app.UseIdentity() before my call to enrich the logger Commented Oct 24, 2016 at 14:25
  • 1
    @SørenRokkedal .. appreciate this is an old post but did you ever figure this out? Thanks Commented Oct 30, 2017 at 16:54

3 Answers 3

9

I was able to get the authenticated Active Directory user with just a few lines of code. I'm not very experienced with Core authentication, claims in particular, but perhaps this will get you on your way or at a minimum help others that come along with a similar problem to yours but with AD.

The key lines are Enrich.FromLogContext() and app.Use(async...

public class Startup
{
    public IConfigurationRoot Configuration { get; }

    public Startup(IHostingEnvironment env)
    {
        Log.Logger = new LoggerConfiguration()
                   .Enrich.FromLogContext() // Populates a 'User' property on every log entry
                   .WriteTo.MSSqlServer(Configuration.GetConnectionString("MyDatabase"), "Logs")
                   .CreateLogger();
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.WithFilter(new FilterLoggerSettings
                                 {
                                     { "Default", LogLevel.Information },
                                     { "Microsoft", LogLevel.Warning },
                                     { "System", LogLevel.Warning }
                                 })
                     .AddSerilog();

        app.Use(async (httpContext, next) =>
                {
                    var userName = httpContext.User.Identity.IsAuthenticated ? httpContext.User.Identity.Name : "unknown";
                    LogContext.PushProperty("User", !String.IsNullOrWhiteSpace(userName) ? userName : "unknown");
                    await next.Invoke();
                });
    }
}

For AD Authentication via IIS/Kestrel the web.config requires a forwardWindowsAuthToken setting as follows:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <aspNetCore ... forwardWindowsAuthToken="true" />
  </system.webServer>
</configuration>
Sign up to request clarification or add additional context in comments.

Comments

6

Your middleware is probably fine. But the order in which you configure the middleware is important. Your EnrichLogger middleware is the very first one. That means it runs before the authentication middleware. Move the app.EnrichLogger call to just below where you add the authentication middleware (probably app.UseAuthentication). This way, the HttpContext.User property will be properly set when your EnrichLogger middleware runs.

Update

Actually, even moving this middleware below the authentication middleware might not be enough. It seems that the identity may be set (at least in some configurations) within the MVC middleware. This means that you can't access the user identity from middleware until after your controller actions have executed (by moving it down after the MVC middleware). But this will be too late to be any use in your logs.

Instead, you may have to use an MVC filter to add the user information to the log context. For example, you might create a filter like this:

public class LogEnrichmentFilter : IActionFilter
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public LogEnrichmentFilter(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    public void OnActionExecuting(ActionExecutingContext context)
    {
        var httpContext = _httpContextAccessor.HttpContext;
        if (httpContext.User.Identity.IsAuthenticated)
        {
            LogContext.PushProperty("Username", httpContext.User.Identity.Name);
        }
        else
        {
            LogContext.PushProperty("Username", "Anonymous");
        }
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
    }
}

You could then apply your filter globally using DI. In your Services.cs file:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddScoped<LogEnrichmentFilter>();
        services.AddMvc(o =>
        {
            o.Filters.Add<LogEnrichmentFilter>();
        });
        ...
    }

1 Comment

You make some valid points about the middleware approach, but the username is not available everywhere with this approach too - for example, it isn't logged for Microsoft.AspNetCore.Hosting, Microsoft.AspNetCore.Mvc.ViewFeatures, Microsoft.AspNetCore.Routing.EndpointMiddleware and many others
3

You need to invoke _next in a block like so:

public async Task Invoke(HttpContext httpContext)
{
    if (httpContext.User.Identity.IsAuthenticated)
    {
        var userFullName = (((ClaimsIdentity)httpContext.User.Identity).FindFirst(Member.FullnameClaimName).Value);
        var userName = "[email protected]";

        using (LogContext.PushProperty("Username", userName))
        using (LogContext.PushProperty("UserFullName", userFullName))
        {
            await _next(httpContext);
        }
    }
    else
    {
        await _next(httpContext);
    }
}

3 Comments

Thanks for the reply. I believe the original posting included the await _next(httpContext). Implementing the suggested solution did not make it work. Every time the Invoke method is called, the httpContext.User.Identity.IsAuthenticated is false. Any ideas?
Thanks for the follow-up. Not sure about this one then. Do you have Enrich.FromLogContext() set on your LoggerConfiguration?
Yes, I have configure this

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.