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

如何在Blazor Server中使用ILogger.BeginScope实现全局日志上下文增强?

Blazor Server中通过ILogger.BeginScope自动注入日志上下文信息

在标准ASP.NET Core请求/响应架构中,我们可以通过Middleware结合ILogger.BeginScope,让所有日志自动携带请求上下文信息(如登录用户名、当前编辑表单ID)。但Blazor Server基于WebSocket长连接,初始连接后后续的SignalR消息不会再经过请求管道,传统Middleware方式完全失效。

以下是适配Blazor Server场景的解决方案:

核心思路

Blazor Server中每个用户的SignalR连接对应一个Scoped生命周期作用域,我们可以利用根组件的生命周期,在连接建立时创建全局日志Scope,并通过Scoped服务维护这个Scope的生命周期。

实现步骤

1. 创建Scoped日志增强服务

这个服务负责创建和管理日志Scope,确保在用户会话期间所有日志都能访问上下文信息:

public class BlazorLogEnricher : IDisposable
{
    private readonly ILogger<BlazorLogEnricher> _logger;
    private IDisposable? _currentScope;

    public BlazorLogEnricher(ILogger<BlazorLogEnricher> logger)
    {
        _logger = logger;
    }

    // 初始化日志上下文
    public void Initialize(HttpContext httpContext, string formId)
    {
        var scopeProperties = new Dictionary<string, object>
        {
            ["UserName"] = httpContext.User?.Identity?.Name ?? "Anonymous",
            ["FormId"] = formId
        };
        _currentScope = _logger.BeginScope(scopeProperties);
    }

    // 动态更新FormId(如果用户切换表单)
    public void UpdateFormId(string newFormId, HttpContext httpContext)
    {
        _currentScope?.Dispose();
        Initialize(httpContext, newFormId);
    }

    public void Dispose()
    {
        _currentScope?.Dispose();
    }
}

2. 注册服务

在Program.cs中将服务注册为Scoped:

var builder = WebApplication.CreateBuilder(args);

// 添加HttpContextAccessor(用于获取用户上下文)
builder.Services.AddHttpContextAccessor();
// 注册日志增强服务
builder.Services.AddScoped<BlazorLogEnricher>();
// 注册你的IMyForm服务(示例中获取FormId的服务)
builder.Services.AddScoped<IMyForm, MyFormImplementation>();

// 其他Blazor服务注册
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();

var app = builder.Build();

// 管道配置(注意无需添加原有的Middleware)
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.MapBlazorHub();
app.MapFallbackToPage("/_Host");

app.Run();

3. 在根组件中初始化Scope

Blazor的根组件(通常是MainLayout.razor)会在用户连接建立时初始化,我们在这里触发日志Scope的创建:

@inject IHttpContextAccessor HttpContextAccessor
@inject BlazorLogEnricher LogEnricher
@inject IMyForm MyForm
@implements IDisposable

<div class="page">
    <div class="sidebar">
        <NavMenu />
    </div>

    <main>
        <div class="top-row px-4">
            <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
        </div>

        <article class="content px-4">
            @Body
        </article>
    </main>
</div>

@code {
    protected override void OnInitialized()
    {
        var httpContext = HttpContextAccessor.HttpContext;
        if (httpContext != null)
        {
            LogEnricher.Initialize(httpContext, MyForm.FormId);
        }
    }

    public void Dispose()
    {
        LogEnricher.Dispose();
    }
}

4. 验证日志输出

在任意组件中写入日志:

@inject ILogger<Counter> Logger

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
        Logger.LogInformation("Count incremented to {Count}", currentCount);
    }
}

此时日志会自动携带UserName和FormId字段。

动态更新上下文信息

如果用户在会话中切换表单,只需调用BlazorLogEnricher的UpdateFormId方法即可更新日志上下文:

// 在表单切换组件中
@inject BlazorLogEnricher LogEnricher
@inject IHttpContextAccessor HttpContextAccessor

<button @onclick="SwitchForm">切换表单</button>

@code {
    private void SwitchForm()
    {
        var newFormId = "NEW_FORM_123";
        LogEnricher.UpdateFormId(newFormId, HttpContextAccessor.HttpContext);
        // 后续日志会使用新的FormId
    }
}

内容的提问来源于stack exchange,提问作者Alexandre Jobin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 07:20:04