扩展Serilog日志记录器以自动包含用户UserId
解决方案
方法一:Serilog自定义Enricher(推荐)
Serilog支持通过Enricher为所有日志事件自动添加属性,这是最简洁的方案,无需修改现有ILogger调用代码,完全符合结构化日志最佳实践。
1. 实现ILogEventEnricher
创建自定义Enricher,从IUserContextService获取UserId并添加到日志事件:
public class UserIdEnricher : ILogEventEnricher { private readonly IUserContextService _userContextService; public UserIdEnricher(IUserContextService userContextService) { _userContextService = userContextService; } public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) { var userId = _userContextService.GetUserDetails().Id; if (!string.IsNullOrEmpty(userId)) { // 仅当UserId存在时添加属性 logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("UserId", userId)); } } }
2. 注册Enricher到Serilog配置
在Program.cs中配置Serilog时,注入自定义Enricher:
var builder = WebApplication.CreateBuilder(args); // 先注册基础服务 builder.Services.AddHttpContextAccessor(); builder.Services.AddScoped<IUserContextService, UserContextService>(); // 配置Serilog builder.Host.UseSerilog((context, services, config) => config .ReadFrom.Configuration(context.Configuration) .Enrich.FromLogContext() // 从DI容器获取自定义Enricher实例 .Enrich.With(services.GetRequiredService<UserIdEnricher>()) // 控制台输出模板中包含Properties,方便查看UserId .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}")); // 后续API配置... var app = builder.Build(); app.Run();
配置完成后,所有日志会自动带上UserId属性(用户已登录时生效),无需手动在日志消息中传入{userId},日志输出的属性集合中会包含该字段(如控制台输出的{Properties:j}部分会显示"UserId":"xxx")。
方法二:封装ILogger扩展方法
若不想修改Serilog全局配置,可封装扩展方法,让日志调用时自动附加UserId:
1. 编写扩展方法
public static class LoggerUserIdExtensions { public static void LogWarningWithUserId(this ILogger logger, IUserContextService userContextService, string message, params object[] args) { var userId = userContextService.GetUserDetails().Id; // 将UserId作为结构化日志属性传入 logger.LogWarning("{Message}, UserId: {UserId}", message, userId, args); } // 同理扩展其他日志级别 public static void LogInformationWithUserId(this ILogger logger, IUserContextService userContextService, string message, params object[] args) { var userId = userContextService.GetUserDetails().Id; logger.LogInformation("{Message}, UserId: {UserId}", message, userId, args); } }
2. 使用扩展方法
public void DoSomething() { // 直接调用扩展方法,无需手动获取UserId _logger.LogWarningWithUserId(_userContextService, "my warning message"); }
方法三:Serilog LogContext(特定场景使用)
如果仅需要在局部代码块中添加UserId,可使用LogContext.PushProperty临时注入属性:
using (LogContext.PushProperty("UserId", _userContextService.GetUserDetails().Id)) { _logger.LogWarning("my warning message"); // 该代码块内的所有日志都会自动带上UserId属性 }
注意:需确保Serilog配置中启用了.Enrich.FromLogContext()才会生效。
总结
优先选择方法一(自定义Enricher),它无需修改现有业务代码,全局统一添加UserId属性,便于日志分析工具提取字段,是最符合Serilog设计理念的方案。
内容的提问来源于stack exchange,提问作者James Andrew Smith
相关产品推荐
相关产品推荐

