通过“LogContext.PushProperty”为长时间运行的异步操作确定 Serilog 上下文的范围

问题描述 投票:0回答:1

任何人都可以提供一些关于以下内容为何有效的见解吗?奇怪的问题 - 我知道。

当下面的代码运行时,最终会生成两个日志,每个日志都有一个

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");
            });
        }
    }
}
serilog
1个回答
0
投票

神奇之处在于 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;
   }
© www.soinside.com 2019 - 2024. All rights reserved.