ASP.NET 5中如何实现NLog MDLC值从子异步上下文到父上下文的传递
MDLC 子异步上下文值向上同步解决方案
核心问题本质
NLog的MappedDiagnosticsLogicalContext(MDLC)基于.NET的AsyncLocal<T>实现,其默认采用写时复制(Copy-on-Write)机制:子异步上下文对值类型的修改、或者对引用类型的整体替换,都只会作用于子上下文本身,不会向上同步到父上下文,这就是异步方法内设置MDLC不生效的根本原因。
方案1:异步方法返回参数后在顶层上下文写入(最推荐,无副作用)
该方案完全符合AsyncLocal的设计逻辑,改造成本极低:
- 调整控制器中调用的异步参数校验/转译方法,将需要写入日志的参数通过返回值、元组或自定义DTO返回,不要在异步方法内部直接设置MDLC
- 控制器中
await异步方法拿到返回结果后,在当前顶层请求上下文(与过滤器属于同一个AsyncLocal作用域)调用MappedDiagnosticsLogicalContext.Set()写入值,过滤器执行结束日志时可直接读取到该值
示例代码:
// 异步参数转译方法定义 public async Task<(bool IsValid, long? UserId, string UserTag)> TranslateParamsAsync(InputDto input) { // 数据库查询、参数校验逻辑 var user = await _userRepository.GetByIdAsync(input.UserId); return (user != null, user?.Id, user?.Tag); } // 控制器内调用逻辑 public async Task<IActionResult> BusinessApi(InputDto input) { var paramResult = await TranslateParamsAsync(input); if (!paramResult.IsValid) return BadRequest(); // 在顶层请求上下文写入MDLC MappedDiagnosticsLogicalContext.Set("UserId", paramResult.UserId); MappedDiagnosticsLogicalContext.Set("UserTag", paramResult.UserTag); // 后续业务逻辑执行 }
方案2:引用类型容器包裹值(适用方法返回值不便调整的场景)
利用AsyncLocal只会复制引用类型的指针、不会复制引用指向的实际对象的特性,实现跨上下文值同步:
- 定义通用的引用类型容器类,用于存放需要动态设置的日志参数
- 过滤器初始化MDLC阶段,就将空的容器实例写入MDLC
- 任意子异步上下文内只需修改容器实例的内部属性,所有上下文都可读取到最新值
- 过滤器结束日志记录时,直接读取容器内的属性值即可
示例代码:
// 通用日志参数容器定义 public class LogPropertyContainer<T> { public T Value { get; set; } } // 过滤器初始化逻辑 public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { // 初始化公共参数的同时,预创建需要动态填充的参数容器 MappedDiagnosticsLogicalContext.Set("UserIdContainer", new LogPropertyContainer<long?>()); MappedDiagnosticsLogicalContext.Set("UserTagContainer", new LogPropertyContainer<string>()); // 执行后续业务逻辑 var result = await next(); // 结束日志读取参数 var userId = ((LogPropertyContainer<long?>)MappedDiagnosticsLogicalContext.Get("UserIdContainer"))?.Value; var userTag = ((LogPropertyContainer<string>)MappedDiagnosticsLogicalContext.Get("UserTagContainer"))?.Value; _logger.LogInformation("请求处理完成,用户ID:{UserId} 用户标签:{UserTag}", userId, userTag); } // 子异步方法内修改参数值 public async Task TranslateParamsAsync(InputDto input) { var user = await _userRepository.GetByIdAsync(input.UserId); // 直接修改容器内的属性值,无需修改MDLC的键指向 var userIdContainer = (LogPropertyContainer<long?>)MappedDiagnosticsLogicalContext.Get("UserIdContainer"); if (userIdContainer != null) userIdContainer.Value = user?.Id; }
单个请求内不存在并发写操作,无需为容器加锁,不存在线程安全问题。
方案3:手动同步AsyncLocal上下文(不推荐)
如果必须在子异步上下文直接修改MDLC根值,可以在请求入口捕获顶层AsyncLocal上下文,子上下文修改后手动同步回父上下文,该方案容易出现上下文泄漏,仅在极端场景下使用。
内容的提问来源于stack exchange,提问作者Marcanpilami
相关产品推荐
相关产品推荐

