添加至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

