【问题标题】:Add Username into Serilog将用户名添加到 Serilog
【发布时间】:2018-12-18 01:26:51
【问题描述】:

我在 program.cs

中有这个 Serilog 配置
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();
    }

现在我想将HttpContext.Current.User.Identity.Name 添加到所有日志消息中。

我尝试按照文档 https://github.com/serilog/serilog/wiki/Configuration-Basics#enrichers 创建新的 Enrich 类

class UsernameEnricher : ILogEventEnricher
    {
        public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory, HttpContext httpContext)
        {
            logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty(
                    "Username", httpContext.User.Identity.Name));
        }
    }

但是与不知道HttpContextILogEventEnricher有冲突。

我也尝试安装包含 Username Enricher 的 Nuget 包 Serilog.Web.Classic,但目标框架 .Net Framework 和 .Net Core 之间存在冲突,因此我无法使用此插件。

有什么想法吗?

【问题讨论】:

    标签: c# asp.net-core logging serilog


    【解决方案1】:

    您可以创建一个中间件将所需的属性放入 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);
        }
    }
    

    您还需要将以下内容添加到您的记录器配置中:

    .Enrich.FromLogContext()
    

    在Startup中添加中间件LogUserNameMiddleware,还要注意中间件要放在UserAuthentication之后,才能初始化context.User.Identity

    例如

        app.UseAuthentication();     
    
        app.UseMiddleware<LogUserNameMiddleware>();
    

    【讨论】:

    • 成功了,你是我的英雄! :)) (还必须在startup.cs 中加载中间件app.UseMiddleware&lt;LogUserNameMiddleware&gt;();
    • 我试过了,但它似乎不起作用 - 中间件总是在身份验证处理程序之前调用 ,因此它没有用户。我在调用app.UseAuthentication() 后尝试对它进行评估,但结果相同。有什么想法吗?
    • @JianYA 回复贴,希望对你有帮助!
    • app.UseMiddleware&lt;LogUserNameMiddleware&gt;() 最好放在app.UseMvc() 前面的那一行,确保前面有任何身份验证中间件。
    • 这种方法存在很多问题。
    【解决方案2】:

    如果您使用Serilog.AspNetCore,添加身份验证/用户属性非常容易。

        app.UseSerilogRequestLogging(options =>
        {
             options.EnrichDiagnosticContext = PushSeriLogProperties;
        });
    
    
    
        public void PushSeriLogProperties(IDiagnosticContext diagnosticContext, HttpContext httpContext)
        {
                diagnosticContext.Set("SomePropertyName", httpContext.User...);
        }
    

    【讨论】:

    • 这个答案真的很小,代码sn-p对我帮助很大。可能比其他答案更好
    • 这只会将“SomePropertyName”添加到请求日志条目中。如何将此属性添加到我写入 serilog 的所有日志条目中? PushSeriLogProperties 方法不会被其他日志调用,并且不带有此属性。
    • 问题是关于向 Serilog 添加用户属性。用户属性与请求相关联。如果您想添加独立于 httpcontext 的其他属性,请查看github.com/serilog/serilog/wiki/Enrichment
    【解决方案3】:

    使用中间件的另一种方法是使用操作过滤器。

    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
            }
        }
    }
    

    在您的Startup.ConfigureServices 中,您需要:

    1. 确保将IHttpContextAccessor 添加到 IoC 容器中
    2. LogEnrichmentFilter 添加到 IoC 容器中,范围为请求
    3. LogEnrichmentFilter注册为全局操作过滤器

    Startup.cs:

    services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    services.AddScoped<LogEnrichmentFilter>();
    
    services.AddMvc(o =>
    {
        o.Filters.Add<LogEnrichmentFilter>();
    });
    

    您应该在MVC action invocation pipeline 中运行的代码的日志上下文中拥有当前用户名。我想如果您使用 resource filter 而不是操作过滤器,用户名将附加到更多日志条目,因为它们在管道中运行得稍早(我才刚刚发现这些!)

    【讨论】:

    • 您好!谢谢您的回答。当用户登录时,这是如何工作的?到目前为止,似乎没有人可以检测到用户第一次登录的时间,只能检测到之后的后续请求。
    • @JianYA 它只会在授权后将用户名添加到日志上下文中。如果我想在实际登录过程中记录某些内容,我会在负责的控制器/处理程序/服务中单独处理。
    • 我明白了。谢谢!
    • 上面代码中的“AppIdentity”方法是什么?
    • @spankymac 我没有在这里包含它,但这对我展示的内容并不重要 - AppIdentity 只是扩展 ClaimsIdentity 以提供一些方便的属性来访问声明值
    【解决方案4】:

    @Alex Riabov 建议的方法存在许多问题。

    1. 需要Dispose推送的属性
    2. 中间件中的Invoke方法是异步的,所以你不能只是return next(),你需要await next()
    3. 请求信息由UseSerilogRequestLogging()中间件记录。如果该属性在到达之前被弹出,则该属性变为空。

    要修复它们,我可以建议进行以下修改。

    在中间件中:

    public async Task Invoke(HttpContext context)
    {
        using (LogContext.PushProperty("UserName", context.User.Identity.Name ?? "anonymous"))
        {
            await next(context);
        }
    }
    

    Startup.cs:

    appl.UseRouting()
        .UseAuthentication()
        .UseAuthorization()
        .UseMiddleware<SerilogUserNameMiddleware>()
        .UseSerilogRequestLogging()
        .UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
            endpoints.MapRazorPages();
            endpoints.MapHealthChecks("/health");
        });
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-02-15
      • 2016-04-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-11-06
      相关资源
      最近更新 更多