Add user to the log context when using Serilog and Asp.Net Core

旧城冷巷雨未停 提交于 2019-12-01 14:50:59

问题


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 = "anyone@gmail.com";
                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


回答1:


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>



回答2:


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 = "anyone@gmail.com";

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



回答3:


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>();
        });
        ...
    }



回答4:


Use the .Enrich.WithEnvironmentUserName() method on the LoggerConfiguration object, eg:

public static IWebHost BuildWebHost(string[] args) =>
  WebHost.CreateDefaultBuilder(args)
    .ConfigureLogging((hostingContext, config) =>
    {
      config.ClearProviders();
    })
    .UseStartup<Startup>()             
    .UseSerilog((ctx, cfg) =>
    {
      cfg.ReadFrom.Configuration(ctx.Configuration)
        .Enrich.WithEnvironmentUserName()
    })
    .Build();

I also have the following setting in web.config, although I haven't proved if it is required on all IIS servers or not:

<system.webServer><aspNetCore ... forwardWindowsAuthToken="true"> ...

Taken from here: Logging in ASP NET Core with Serilog



来源:https://stackoverflow.com/questions/39896465/add-user-to-the-log-context-when-using-serilog-and-asp-net-core

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!