当使用Serilog和Asp.Net Core时,将用户添加到日志上下文中

17

我正在尝试将Serilog与我的ASP.Net Core 1.0项目一起使用。 我似乎无法将当前登录的用户添加到已记录的属性中。

有人解决了这个问题吗?

我尝试过以下方法:

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

我通过添加以下内容在Startup.cs中触发它:

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

但这总是以“匿名”作为用户名结束。

提前致谢

Søren Rokkedal


你尝试在身份验证后将这个添加到你的管道中了吗? - Brad
不确定您在这方面的意思。 - Søren Rokkedal
在调用 app.UseAuthentication(...); 或其他身份验证方法之后调用 app.EnrichLogger();。您在用户进行身份验证之前过早地将记录器注入到管道中,因此它始终是匿名的。 - Brad
在调用丰富日志记录器之前,我使用了app.UseIdentity()。 - Søren Rokkedal
你的应用程序其他地方能否看到填充的 httpContext.User.Identity.Name?并且你是使用的Active Directory认证还是其他什么方式? - Kevin Kalitowski
1
@SørenRokkedal.. 感谢您的帖子,虽然这是一个旧帖子,但您最终解决了吗?谢谢。 - Gavin Sutherland
3个回答

9
我只需要用几行代码就能获取已验证的Active Directory用户。我对Core身份验证,尤其是声明不是很有经验,但也许这可以帮助你或者至少帮助其他遇到类似问题但使用AD的人。
关键行是Enrich.FromLogContext()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();
                });
    }
}

通过IIS/Kestrel进行AD身份验证,web.config需要添加以下 forwardWindowsAuthToken 设置:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <aspNetCore ... forwardWindowsAuthToken="true" />
  </system.webServer>
</configuration>

5
您的中间件应该是没有问题的,但是配置中间件的顺序很重要。您的EnrichLogger中间件是第一个运行的。这意味着它在身份验证中间件之前运行。将app.EnrichLogger调用移到添加身份验证中间件下面(可能是app.UseAuthentication)。这样,当EnrichLogger中间件运行时,HttpContext.User属性将被正确设置。
更新:
实际上,即使将此中间件移动到身份验证中间件下面,可能还不足以解决问题。似乎标识在MVC中间件中设置(至少在某些配置中)。这意味着在中间件中无法访问用户标识,直到控制器操作执行完毕(通过将其移至MVC中间件之后)。但是这对于日志来说为时已晚。
相反,您可以使用MVC过滤器将用户信息添加到日志上下文中。例如,您可以创建如下过滤器:
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)
    {
    }
}

然后,您可以使用 DI 全局应用过滤器。在 Services.cs 文件中:

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

你对中间件方法提出了一些有价值的观点,但是使用这种方法时用户名也不是随处可用的 - 例如,在 Microsoft.AspNetCore.HostingMicrosoft.AspNetCore.Mvc.ViewFeaturesMicrosoft.AspNetCore.Routing.EndpointMiddleware 等地方都没有记录。 - Cocowalla

3
您需要在如下代码块中调用_next
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);
    }
}

1
谢谢回复。我相信原始帖子包括 await _next(httpContext)。实施建议的解决方案并没有使它工作。每次调用 Invoke 方法时,httpContext.User.Identity.IsAuthenticated 都为 false。有什么想法吗? - Søren Rokkedal
谢谢您的跟进。那这个不确定。您的LoggerConfiguration上是否设置了 Enrich.FromLogContext() - Nicholas Blumhardt
是的,我已经配置好了。 - Søren Rokkedal

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接