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

.NET Core中Serilog中间件未处理异常日志丢失UserId的原因

问题:.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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 20:11:46