将用户名添加到Serilog中

Muf*_*lix 7 logging serilog asp.net-core

我在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();
    }
Run Code Online (Sandbox Code Playgroud)

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

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

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

但是与ILogEventEnricher存在冲突,它不知道HttpContext.

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

任何的想法 ?

flu*_*lux 13

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

    app.UseSerilogRequestLogging(options =>
    {
         options.EnrichDiagnosticContext = PushSeriLogProperties;
    });



    public void PushSeriLogProperties(IDiagnosticContext diagnosticContext, HttpContext httpContext)
    {
            diagnosticContext.Set("SomePropertyName", httpContext.User...);
    }
Run Code Online (Sandbox Code Playgroud)

  • 这只会将“SomePropertyName”添加到请求日志条目中。如何将此属性添加到我写入 serilog 的所有日志条目中?PushSeriLogProperties 方法不会被其他日志调用,并且不携带此属性。 (6认同)

Ale*_*bov 12

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

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

.Enrich.FromLogContext()
Run Code Online (Sandbox Code Playgroud)

  • 它有效,您是我的英雄!:))(还必须在startup.cs中的app.UseMiddleware &lt;LogUserNameMiddleware&gt;();中加载中间件) (2认同)
  • 我尝试了这个,但它似乎不起作用 - 中间件总是在身份验证处理程序之前调用,因此它没有用户。我尝试在调用“app.UseAuthentication()”后对其进行评估,但结果相同。有任何想法吗? (2认同)
  • `app.UseMiddleware&lt;LogUserNameMiddleware&gt;()` 最好放置在 `app.UseMvc()` 之前的行,确保任何身份验证中间件都位于其之前。 (2认同)
  • 这种方法存在许多问题。 (2认同)

She*_*ari 12

只需两步即可实现

1- 创建一个可以访问服务的 Enricher。

using Microsoft.AspNetCore.Http;
using Serilog.Core;
using Serilog.Events;
using System.Security.Claims;

namespace CoolProject.Logging.Enricher;
public class UserEnricher : ILogEventEnricher
{
private readonly IHttpContextAccessor _httpContextAccessor;

public UserEnricher() : this(new HttpContextAccessor())
{
}

//Dependency injection can be used to retrieve any service required to get a user or any data.
//Here, I easily get data from HTTPContext
public UserEnricher(IHttpContextAccessor httpContextAccessor)
{
    _httpContextAccessor = httpContextAccessor;
}

public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
    logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty(
            "UserId", _httpContextAccessor.HttpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier) ?? "anonymous"));
}
}
Run Code Online (Sandbox Code Playgroud)

2-使用With来包含您的 UserEnricher。

loggerConfiguration.Enrich.FromLogContext()
            .MinimumLevel.Is(level)
            .Enrich.With<UserEnricher>()
Run Code Online (Sandbox Code Playgroud)

添加用户丰富器只需要两个步骤,但我还将添加我的驱动程序代码。不要忘记注入 IHttpContextAccessor!

 public static IHostBuilder UseLogging(this IHostBuilder webHostBuilder, string applicationName = null)
    => webHostBuilder.UseSerilog((context ,loggerConfiguration) =>
    {
        var logOptions = context.Configuration.GetSection("logging");
        var serilogOptions = logOptions.GetSection("serilog").Get<SerilogOptions>();
        if (!Enum.TryParse<LogEventLevel>(serilogOptions.Level, true, out var level))
        {
            level = LogEventLevel.Error;
        }

        loggerConfiguration.Enrich.FromLogContext()
            .MinimumLevel.Is(level)
            .Enrich.With<UserEnricher>()
            .Enrich.WithProperty("Environment", context.HostingEnvironment.EnvironmentName)
            .Enrich.WithProperty("ApplicationName", applicationName);
        loggerConfiguration.WriteTo.Console(outputTemplate: "{Timestamp:HH:mm:ss} [{Level}]  {Environment} {ApplicationName} {UserId} {Message:lj}{NewLine}{Exception}");

    });
Run Code Online (Sandbox Code Playgroud)

  • 这是最干净、最合适、最新的答案。 (3认同)

Coc*_*lla 10

使用中间件的替代方法是使用操作过滤器。

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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在你的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>();
});
Run Code Online (Sandbox Code Playgroud)

然后,您应该在MVC 操作调用管道中运行的代码的日志上下文中具有当前用户名。我想如果您使用资源过滤器而不是操作过滤器,用户名会附加到更多的日志条目,因为它们在管道中运行得稍早一些(我刚刚发现了这些!)


Mik*_*ike 6

@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);
    }
}
Run Code Online (Sandbox Code Playgroud)

Startup.cs

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