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

添加至LogContext的属性在中间件日志中丢失的解决方法咨询

问题描述

我使用Serilog将日志记录到数据库,并配置了自定义列,相关JSON配置如下:

{
  "Name": "MSSqlServer",
  "Args": {
    "connectionString": "ApplicationDbContext",
    "tableName": "Logs",
    "autoCreateSqlTable": false,
    "columnOptionsSection": {
      "batchPostingLimit": 10,
      "period": "0.00:00:02",
      "additionalColumns": [
        // 自定义列
      ],
      "timeStamp": {
        "columnName": "TimeStamp",
        "convertToUtc": true
      },
      "level": {
        "columnName": "Level",
        "storeAsEnum": true
      }
    }
  }
},
"Enrich": [ "FromLogContext" ]

由于需要控制器内的数据与逻辑,无法在中间件中添加自定义信息,因此在控制器内将属性添加至LogContext:

[HttpPost("dosomething")]
public IActionResult Compile(string catalog)
{
    Serilog.Context.LogContext.PushProperty(Log.OPERATION_NAME_KEY, Convert.ToInt16(OperationTypes.DoSomething));
    // 业务逻辑处理
    return Ok();
}

同时使用中间件处理异常日志:

public async Task InvokeAsync(HttpContext httpContext)
{
    try
    {
        await _next(httpContext);
    }
    catch (NotFoundException ex) when (LogExeception(ex))
    {
        await HandleNotFoundExceptionAsync(httpContext, ex);
    }
    catch (Exception ex) when (LogExeception(ex))
    {
        await HandleExceptionAsync(httpContext, ex);
    }
}

当前遇到的问题:中间件中记录日志时,LogContext内添加的所有属性都丢失,但主程序日志能正常包含这些属性。尝试过"when trick"方案,没有效果。


解决方案

核心原因

LogContext的属性基于**异步本地存储(AsyncLocal)**实现,控制器内直接调用PushProperty时,属性的作用域仅在控制器方法执行期间有效。当控制器方法执行完毕返回后,作用域自动释放,后续中间件捕获异常时,LogContext已无对应属性。

具体解决步骤

1. 用using包裹PushProperty,延长作用域

将控制器内的PushProperty用using块包裹,确保属性作用域覆盖整个请求流程(包括中间件的异常捕获阶段):

[HttpPost("dosomething")]
public IActionResult Compile(string catalog)
{
    using (LogContext.PushProperty(Log.OPERATION_NAME_KEY, Convert.ToInt16(OperationTypes.DoSomething)))
    {
        // 业务逻辑处理
        return Ok();
    }
}

注:PushProperty返回IDisposable对象,using块会在请求全流程结束后才释放作用域,保证中间件捕获异常时LogContext属性仍有效。

2. 调整中间件日志记录时机

避免在when子句中记录日志——when子句执行时机早于异常捕获,此时控制器的LogContext作用域可能已失效。将日志记录移到异常处理方法内部:

public async Task InvokeAsync(HttpContext httpContext)
{
    try
    {
        await _next(httpContext);
    }
    catch (NotFoundException ex)
    {
        await HandleNotFoundExceptionAsync(httpContext, ex);
    }
    catch (Exception ex)
    {
        await HandleExceptionAsync(httpContext, ex);
    }
}

private Task HandleExceptionAsync(HttpContext httpContext, Exception ex)
{
    // 在此处记录日志,此时LogContext属性仍处于有效作用域内
    Log.Error(ex, "发生未处理异常");
    // 其他异常响应逻辑
    return httpContext.Response.WriteAsync("服务发生错误");
}

3. 确认Serilog配置有效性

确保Enrich: ["FromLogContext"]配置正确无拼写错误,这是Serilog从LogContext提取属性的必要前提。

优化建议:用动作过滤器统一处理

如果多个控制器方法需要添加相同的LogContext属性,可通过动作过滤器统一实现,避免重复代码:

public class OperationNameFilter : IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        // 根据当前动作或参数获取对应的OperationType
        var operationType = OperationTypes.DoSomething;
        LogContext.PushProperty(Log.OPERATION_NAME_KEY, Convert.ToInt16(operationType));
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        // 若未使用using块,可手动释放作用域
        (context.HttpContext.Items["LogScope"] as IDisposable)?.Dispose();
    }
}

在Program.cs中注册过滤器:

builder.Services.AddControllers(options =>
{
    options.Filters.Add<OperationNameFilter>();
});

内容的提问来源于stack exchange,提问作者Enrica Berardi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 04:32:41