任何人都可以提供一些关于以下内容为何有效的见解吗?奇怪的问题 - 我知道。
当下面的代码运行时,最终会生成两个日志,每个日志都有一个
UUID
属性,并附加值 First ID
。我不明白为什么这样做会起作用,因为在 LogContext.PushProperty
(即 ContextStackBookmark
)中设置的属性丰富器在写入第二条日志消息之前已被处理。
不要误会我的意思,我观察到的绝对是我正在寻找的行为;但是,我期望看到两条日志消息:第一条带有
UUID
属性,第二条没有。
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
private readonly IServiceProvider _serviceProvider;
public Worker(ILogger<Worker> logger, IServiceProvider serviceProvider)
{
_logger = logger;
_serviceProvider = serviceProvider;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
using (LogContext.PushProperty("UUID", "First ID"))
{
_ = Task.Run(async () =>
{
_logger.LogInformation("Starting task");
await Task.Delay(10_000);
_logger.LogInformation("Finished task");
});
}
}
}
神奇之处在于 AsyncLocal - 它与线程局部变量类似,但有一个重要区别,即它“流向”子任务,无论它们何时或在哪个线程上运行。
详细信息解释如下:https://blog.stephencleary.com/2013/04/implicit-async-context-asynclocal.html。 Serilog 中下一部分的重要提示/注释也存在:
因为引用是共享的,所以不要改变从逻辑调用上下文检索的任何值,这一点很重要。如果需要更改逻辑调用上下文值,请使用 CallContext.LogicalSetData 更新实际值。 您应该只使用不可变类型作为逻辑调用上下文数据值。
而 EncricherStack 正是如此。一个小型的不可变链表实现。
因此,任务将在任务启动时继承父级的“丰富堆栈”,并且稍后在父级中重新分配不会影响任务自己的上下文中看到的 AsyncLocal 值。 (异步模型立即返回启动的任务。)
本例中稍后的重新分配位于 ContextStackBookmark.Dispose。
public void Dispose()
{
// Re-assigns the AsyncLocal value in the parent,
// without affecting any previous enricher stack already
// present on the child task contexts.
Enrichers = _bookmark;
}