.NET Core中Serilog中间件未处理异常日志丢失UserId的原因
我编写了一个小型Serilog中间件,期望从HttpContext中捕获UserId并写入日志文件。该中间件在普通日志中可正常工作,但记录未处理异常时无法携带UserId。此场景基于.NET Core框架,请问原因是什么?
代码如下:
public class SerilogUserIdLogger { readonly RequestDelegate _next; public SerilogUserIdLogger(RequestDelegate next) { if (next == null) throw new ArgumentNullException(nameof(next)); _next = next; } public async Task Invoke(HttpContext httpContext) { if (httpContext.User != null && httpContext.User.Identity != null && httpContext.User.Identity.IsAuthenticated) { var userId = httpContext.User.Claims?.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value; using (LogContext.PushProperty("UserId", userId)) { await _next(httpContext); } } else { await _next(httpContext); } } }
中间件注册代码:
app.UseMiddleware<SerilogUserIdLogger>();
原因分析
问题核心在于未处理异常的捕获时机与LogContext作用域的生命周期不匹配。你的中间件通过using (LogContext.PushProperty(...))创建的作用域,会在await _next(httpContext)执行完成后立即释放。如果下游中间件或业务代码抛出未处理异常,该异常通常会被上层的全局异常处理组件(比如UseExceptionHandler或Serilog的UseSerilogRequestLogging)捕获,此时你的LogContext作用域已经结束,导致UserId属性无法关联到异常日志中。
简单来说:异常发生在你的中间件作用域之外,所以日志无法获取到之前Push的UserId属性。
解决方案
方案1:调整中间件注册顺序
将你的SerilogUserIdLogger中间件优先于异常处理/请求日志中间件注册,确保异常发生时,LogContext的作用域仍处于有效状态。
修改注册代码:
// 先注册UserId中间件,确保作用域覆盖后续所有中间件 app.UseMiddleware<SerilogUserIdLogger>(); // 再注册异常处理、请求日志等中间件 app.UseExceptionHandler("/Error"); app.UseSerilogRequestLogging();
方案2:在中间件内部捕获异常并保留作用域
修改中间件的Invoke方法,在LogContext作用域内手动捕获异常并记录日志,确保UserId属性能被关联:
public async Task Invoke(HttpContext httpContext) { var userId = httpContext.User?.Identity?.IsAuthenticated == true ? httpContext.User.Claims?.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value : "Anonymous"; using (LogContext.PushProperty("UserId", userId)) { try { await _next(httpContext); } catch (Exception ex) { // 在LogContext作用域内记录异常,确保UserId被写入 Log.Error(ex, "请求处理过程中发生未处理异常"); // 重新抛出异常,让上层中间件继续处理(如返回错误页面) throw; } } }
关键前提
无论采用哪种方案,都必须确保Serilog配置中启用了Enrich.FromLogContext(),否则LogContext中的属性不会被添加到日志事件中:
Log.Logger = new LoggerConfiguration() .Enrich.FromLogContext() // 必须启用此配置 .WriteTo.File("logs/log-.txt", rollingInterval: RollingInterval.Day) .CreateLogger();
内容的提问来源于stack exchange,提问作者NullReference

