LogContext.PushProperty 为什么没出现在日志里
直接调用
LogContext.PushProperty("UserId", 123) 后日志没带这个字段,大概率是因为你没在日志配置中启用上下文支持。Serilog 默认不自动注入 LogContext的属性,必须显式启用——比如用
Enrich.FromLogContext()。
常见错误是只写了
PushProperty,却漏掉 enricher 配置:
Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext() // ⚠️ 必须加这一行
.WriteTo.Console()
.CreateLogger();
没有 Enrich.FromLogContext(),
PushProperty像往水里扔石头,涟漪根本传不到日志输出 该 enricher 是全局生效的,不需要每次写日志都重复配置 它只捕获「当前异步执行流」中的上下文(基于
AsyncLocal<ilogeventenricher></ilogeventenricher>),跨线程或新 Task 会丢失
如何安全地在异步方法中使用 LogContext
LogContext的生命周期绑定到当前
ExecutionContext,但不是所有异步场景都自动流动。尤其在 ASP.NET Core 中,Controller 方法里 push 的属性,在
await后的 lambda 或后台任务里可能已失效。
推荐做法是:在入口处(如中间件、Controller Action)push,且尽量避免在
Task.Run或手动切换同步上下文的地方依赖它: ✅ ASP.NET Core 中,在
UseSerilogRequestLogging中间件前注册
Enrich.FromLogContext(),再配合
LogContext.PushProperty("TraceId", HttpContext.TraceIdentifier)
❌ 不要在 Task.Run(() => { LogContext.PushProperty(...); DoWork(); }) 里 push —— 子线程没有父上下文
⚠️ 若必须跨任务传递,改用显式参数传入,或用 Log.ForContext("UserId", userId) 创建子 logger
PushProperty 和 ForContext 的关键区别
二者都能加属性,但作用域和机制完全不同:
LogContext.PushProperty("Key", value):把属性压入当前逻辑调用栈,所有后续 Log.Information(...)都自动携带(只要没被 pop);适合请求级、事务级统一字段
Log.ForContext("Key", value).Information(...):仅本次日志事件携带,不污染后续日志;适合单条日志的临时补充,比如记录某个计算结果
PushProperty支持多次同名覆盖(后 push 的生效),而
ForContext是链式构造新 logger,不影响原 logger 性能上,
ForContext每次新建 logger 对象有开销;
PushProperty是栈操作,更轻量,但需注意别漏 pop 导致内存泄漏(不过 Serilog 内部用
AsyncLocal管理,通常无需手动 pop)
动态属性值怎么实时求值(比如当前时间戳或随机 ID)
PushProperty默认存的是调用时的值快照。如果想每次日志输出时重新计算(例如生成唯一 ID、取当前毫秒数),得用它的重载版本传入
Func<object></object>:
LogContext.PushProperty("LogTime", () => DateTime.Now.ToString("HH:mm:ss.fff"));
LogContext.PushProperty("RequestId", () => Guid.NewGuid().ToString("N"));
这样每次日志序列化时都会执行函数,拿到最新值。但要注意:
函数体不能抛异常,否则整条日志会静默失败(Serilog 会吞掉异常) 避免在函数里做 IO 或锁操作,否则拖慢日志性能 不要返回null,否则该字段不会出现在日志中(可返回
"null"字符串兜底)
真正难处理的从来不是怎么加属性,而是上下文在异步分叉、线程切换、DI 生命周期边界处的“消失”——这些地方得靠显式传参或重构日志结构来兜底。
