Blazor Server中Nlog ScopeContext属性无法向组件及服务传递问题
解决Blazor Server会话级日志作用域自动传递问题
问题根源
Blazor Server中,NLog的ScopeContext依赖AsyncLocal<T>传递上下文,但Blazor的渲染循环、电路处理线程切换会导致异步上下文断裂,初始化时设置的日志属性无法延续到后续组件渲染和Scoped服务调用。同时Scoped服务的日志器首次注入时,上下文属性可能尚未完成设置,导致日志仅显示占位符。
解决方案
1. 实现电路绑定的Scoped日志作用域服务
创建与电路同生命周期的Scoped服务,在构造函数中初始化日志属性,确保整个会话内的日志都能继承这些属性:
public class CircuitLoggingScope : IAsyncDisposable { private readonly IDisposable _scope; public CircuitLoggingScope(AuthenticationStateProvider authProvider) { // 获取用户认证信息(根据实际认证方式调整) var authState = authProvider.GetAuthenticationStateAsync().Result; var user = authState.User; var userId = user.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value ?? "unknown"; var userLogin = user.Identity?.Name ?? "unknown"; var methodGuid = Guid.NewGuid().ToString(); // 使用NLog的MDLC设置会话级日志属性,替代ScopeContext.PushProperties _scope = NLog.MappedDiagnosticsLogicalContext.SetScoped(new Dictionary<string, object> { ["RequestUserId"] = userId, ["RequestUserLogin"] = userLogin, ["RequestMethodGuid"] = methodGuid }); } public ValueTask DisposeAsync() { _scope.Dispose(); return ValueTask.CompletedTask; } }
在Program.cs中注册该服务:
builder.Services.AddScoped<CircuitLoggingScope>(); // 若使用HttpContext获取用户信息,需添加以下注册 builder.Services.AddHttpContextAccessor();
2. 修改LoggingCircuitHandler触发作用域初始化
调整原有的LoggingCircuitHandler,在电路打开时激活Scoped日志作用域,确保属性在会话启动时就生效:
public class LoggingCircuitHandler : CircuitHandler { private readonly IServiceScopeFactory _scopeFactory; public LoggingCircuitHandler(IServiceScopeFactory scopeFactory) { _scopeFactory = scopeFactory; } public override Task OnCircuitOpenedAsync(Circuit circuit, CancellationToken cancellationToken) { // 创建电路专属的服务作用域 using var scope = _scopeFactory.CreateScope(); // 激活日志作用域服务,触发属性设置 scope.ServiceProvider.GetRequiredService<CircuitLoggingScope>(); // 验证日志属性是否生效 var logger = scope.ServiceProvider.GetRequiredService<ILogger<LoggingCircuitHandler>>(); logger.LogInformation("Circuit opened for user {RequestUserLogin}", NLog.MappedDiagnosticsLogicalContext.Get("RequestUserLogin")); return base.OnCircuitOpenedAsync(circuit, cancellationToken); } }
3. 修正NLog配置
在NLog.config中使用mdlc占位符输出属性(与scopeproperty兼容,但更明确对应异步上下文传递的属性):
<target name="file" xsi:type="File" fileName="${basedir}/logs/${shortdate}.log"> <layout xsi:type="JsonLayout"> <attribute name="timestamp" layout="${date:format=o}" /> <attribute name="level" layout="${level}" /> <attribute name="message" layout="${message}" /> <attribute name="userId" layout="${mdlc:item=RequestUserId:whenEmpty=unknown}" /> <attribute name="userLogin" layout="${mdlc:item=RequestUserLogin:whenEmpty=unknown}" /> <attribute name="methodGuid" layout="${mdlc:item=RequestMethodGuid:whenEmpty=unknown}" /> </layout> </target>
4. 组件与Scoped服务日志自动继承属性
现在所有电路内的组件、Scoped服务,只要注入ILogger<T>进行日志输出,都会自动携带会话级的用户属性,无需在每个方法中重复设置。
内容的提问来源于stack exchange,提问作者blane
相关产品推荐
相关产品推荐

