Serilog中LogContext.PushProperty销毁后长耗时异步任务日志仍携带属性的原因
现象背后的核心原因
你观察到的现象是.NET异步上下文流动机制 + Serilog LogContext的底层实现共同导致的,具体逻辑如下:
- Serilog的
LogContext是基于**AsyncLocal<T>**存储上下文属性的,AsyncLocal的设计目的就是让上下文属性可以跟随异步代码的执行流程跨线程传递。 - 调用
Task.Run的瞬间,.NET runtime会自动捕获当前线程的ExecutionContext,其中就包含了此时LogContext里存储的UUID=First ID属性,这个捕获动作发生在using块内部,早于外层using块的释放逻辑。 - 外层using块结束、
ContextStackBookmark被释放时,只会清空当前执行ExecuteAsync方法的原始线程的LogContext属性,已经被Task.Run捕获走的上下文副本完全不受影响。 Task.Run委托内的所有代码,不管是await前的Starting task日志,还是awaitTask.Delay后在线程池线程上执行的Finished task日志,全程都运行在之前已经捕获到的、带有UUID属性的上下文里,所以两条日志都会携带该属性。
如果你想要实现「只有第一条日志携带UUID、第二条不携带」的效果,可以在await之后手动弹出属性:
_ = Task.Run(async () => { _logger.LogInformation("Starting task"); using (LogContext.PopProperty("UUID")) { await Task.Delay(10_000); _logger.LogInformation("Finished task"); } });
如果你希望Task.Run的委托完全不继承外层的LogContext,可以在调用Task.Run前禁止上下文流动:
using (LogContext.PushProperty("UUID", "First ID")) using (ExecutionContext.SuppressFlow()) { _ = Task.Run(async () => { // 两条日志都不会携带UUID属性 _logger.LogInformation("Starting task"); await Task.Delay(10_000); _logger.LogInformation("Finished task"); }); }
内容的提问来源于stack exchange,提问作者Colin Sim
相关产品推荐
相关产品推荐

