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

Blazor Server应用中如何在日志中获取用户信息?

在Blazor Server中为ILogger自动添加用户信息的可行方案

核心思路

Blazor Server的每个用户电路对应Scoped生命周期的服务实例,我们可以利用这一特性,通过Scoped服务安全获取用户信息,并自动为日志添加包含用户标识的Scope,无需手动修改每一处日志调用。

分步实现

1. 创建Scoped用户信息服务

该服务负责异步获取当前用户信息,避免在单例服务中直接注入Scoped依赖导致的生命周期冲突:

public class CurrentUserService
{
    private readonly AuthenticationStateProvider _authStateProvider;
    private AuthenticationState? _cachedAuthState;

    public CurrentUserService(AuthenticationStateProvider authStateProvider)
    {
        _authStateProvider = authStateProvider;
    }

    public async Task<string?> GetUserEmailAsync()
    {
        _cachedAuthState ??= await _authStateProvider.GetAuthenticationStateAsync();
        
        return _cachedAuthState.User.Identity?.IsAuthenticated == true 
            ? _cachedAuthState.User.FindFirst(System.Security.Claims.ClaimTypes.Email)?.Value 
            : null;
    }
}

2. 创建Scoped日志Scope管理器

该服务会在作用域启动时自动创建包含用户信息的日志Scope,作用域销毁时自动释放:

public class LogUserScopeManager : IAsyncDisposable
{
    private readonly ILogger<LogUserScopeManager> _logger;
    private readonly CurrentUserService _currentUserService;
    private IDisposable? _activeScope;

    public LogUserScopeManager(ILogger<LogUserScopeManager> logger, CurrentUserService currentUserService)
    {
        _logger = logger;
        _currentUserService = currentUserService;
        _ = InitializeUserScopeAsync();
    }

    private async Task InitializeUserScopeAsync()
    {
        try
        {
            var userEmail = await _currentUserService.GetUserEmailAsync();
            if (!string.IsNullOrWhiteSpace(userEmail))
            {
                _activeScope = _logger.BeginScope(new Dictionary<string, object>
                {
                    ["UserEmail"] = userEmail
                });
            }
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Failed to attach user info to log scope");
        }
    }

    public async ValueTask DisposeAsync()
    {
        _activeScope?.Dispose();
        GC.SuppressFinalize(this);
    }
}

3. 注册服务到DI容器

在Program.cs中添加Scoped服务注册:

builder.Services.AddScoped<CurrentUserService>();
builder.Services.AddScoped<LogUserScopeManager>();

4. 触发Scope初始化

Blazor的Scoped服务不会自动初始化,需在根组件(如MainLayout.razor)中注入LogUserScopeManager,确保每个用户电路启动时创建日志Scope:

@inject LogUserScopeManager LogUserScopeManager

<!-- 原有布局内容 -->

效果验证

所有通过ILogger输出的日志(包括框架日志、第三方组件日志)都会自动携带UserEmail标识,例如在日志输出中会看到:

Information: 00:00:00.000 [UserEmail: user@example.com] 执行用户操作
Error: 00:00:00.000 [UserEmail: user@example.com] 操作触发异常

原方案失败原因说明

  • ITelemetryInitializer/Serilog增强器:这类组件默认是单例生命周期,无法直接注入Scoped的AuthenticationStateProvider,会触发DI生命周期冲突。
  • IHttpContextAccessor:Blazor Server中HttpContext仅在初始连接阶段存在,后续SignalR通信时HttpContext为空,导致用户信息获取不稳定。
  • 提前调用GetAuthenticationStateAsync:在AuthenticationStateProvider完成初始化前调用该方法,会触发GetAuthenticationStateAsync was called before SetAuthenticationState错误。

内容的提问来源于stack exchange,提问作者Dalton Keightley

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 01:17:23