如何在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
相关产品推荐
相关产品推荐

