You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 18:53:09