Serilog 的 Enricher 是用于全局自动为日志添加上下文字段的机制,如线程ID、请求ID、用户名等,避免手动拼接或误用 LogContext;内置 Enricher 通过 .Enrich.WithXxx() 注册,自定义需实现 ILogEventEnricher 并安全访问 HttpContext;须注意性能陷阱,禁止 IO、锁及频繁分配内存。

什么是 Serilog 的 Enricher(富化器)
Serilog 里的 Enricher 是专门用来给每条日志自动附加额外字段的机制,不是靠每次写 Log.Information("msg", prop) 手动传参,而是全局、统一、可复用地注入上下文。比如你想让所有日志都带上线程 ID、请求 ID、用户名或环境名,就该用它。
常见错误是试图在日志语句里硬拼字符串(如 "[User:xxx] " + msg),这破坏结构化日志,也漏掉异常堆栈里的上下文;另一种错误是滥用 LogContext.PushProperty,它只对当前作用域有效,且容易因忘记 Pop 或异步切换导致上下文丢失。
内置 Enricher 怎么用:从 WithThreadId 到 WithMachineName
Serilog 自带一批开箱即用的 Enricher,全在 Serilog.Enrichers.* 包里,需单独安装(如 Serilog.Enrichers.Thread)。注册方式统一走 .Enrich.WithXxx() 链式调用:
Log.Logger = new LoggerConfiguration()
.Enrich.WithThreadId() // 注入 ThreadId 字段
.Enrich.WithMachineName() // 注入 MachineName
.Enrich.WithEnvironmentUserName() // 注入当前 Windows 用户名(仅 Windows)
.WriteTo.Console()
.CreateLogger();
注意:WithEnvironmentUserName 在 Linux/macOS 上会写空字符串;WithThreadId 在线程池线程中可能重复(因为线程复用),若需唯一性,应改用 WithCorrelationId 或自定义请求级 ID。
自定义 Enricher:如何注入 HTTP 请求 ID 和用户信息
ASP.NET Core 场景下最常需要的是请求 ID 和当前登录用户,但这些不在 Serilog 内置范围里——得自己写 IEnricher 实现。关键点是:不能直接访问 HttpContext,因为 IEnricher 是无状态的,必须配合依赖注入和作用域生命周期。
推荐做法是用 ILogEventEnricher + IHttpContextAccessor,并在 Program.cs 中注册为 Scoped:
- 安装
Microsoft.AspNetCore.Http和确保已配置services.AddHttpContextAccessor() - 实现类里通过构造函数接收
IHttpContextAccessor,在Enrich方法中安全读取HttpContext?.TraceIdentifier或User.Identity.Name - 务必判空:HttpContext 可能为 null(如后台任务、HostedService 中)
示例字段注入逻辑:
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
var httpContext = _httpContextAccessor.HttpContext;
if (httpContext != null)
{
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("RequestId", httpContext.TraceIdentifier));
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("UserName", httpContext.User?.Identity?.Name ?? "-"));
}
}
Enricher 的执行时机和性能陷阱
Enricher 在每条日志被写入前执行,所以任何耗时操作(如读数据库、调远程 API、序列化大对象)都会拖慢整个日志链路。Serilog 不做并发保护,也不缓存结果,因此:
- 避免在
Enrich方法里做 IO 或锁操作 - 不要在 Enricher 里 new 大对象或分配大量内存(尤其高频日志场景)
- 如果要注入配置值(如
AppSettings["Env"]),应在构造函数里一次性读取并缓存,而不是每次调用都去查IConfiguration - 异步 Enricher 不存在——Serilog 日志管道是同步的,
asyncEnricher 会被忽略或引发异常
最易被忽略的是:Enricher 对 Log.Error(ex, msg) 同样生效,但异常对象本身不会被 Enricher 修改;如果你发现 Exception 字段里没包含你加的上下文,那是正常的——上下文只加在日志事件元数据里,异常堆栈仍是原始内容。










