如何禁用Serilog记录TaskCanceledException的错误日志?
问题原因与解决方案
适用环境:Asp.Net core 3.1/.Net 6
核心问题1:为什么自定义中间件已经捕获TaskCanceledException,Serilog仍输出Error级别日志
你遇到这个问题主要有3个原因:
- 中间件注册顺序错误。Asp.Net Core中间件是按注册顺序从外到内嵌套执行的,如果你把
app.UseSerilogRequestLogging()(也就是输出错误日志的Serilog.AspNetCore.RequestLoggingMiddleware)注册在你自定义的TaskCanceledMiddleware之前,Serilog的请求日志中间件会处于更外层的管道位置,会在你的中间件处理异常之前就捕获到请求异常标记,后续即使你在内层catch了异常,Serilog仍会按异常状态记录日志。 - 状态码设置不生效。客户端主动断开连接触发TaskCanceledException时,大概率HTTP响应头已经发送给客户端(即
context.Response.HasStarted == true),此时你再修改context.Response.StatusCode是无效的,Kestrel会保留默认的500状态码,Serilog检测到响应状态码为500,就会按Error级别输出日志。 - Serilog默认日志规则未适配。Serilog请求日志中间件默认只要检测到响应状态码>=500、或者请求过程中有未标记为已处理的异常,就会输出Error级别日志,不会自动识别你自定义catch的TaskCanceledException场景。
核心问题2:日志级别显示为EROR而非ERR的原因
这不是特殊日志级别,就是标准的Error级别。Serilog默认的日志输出模板中,级别字段默认采用4字符固定宽度格式化:
- Verbose → VRB
- Debug → DBG
- Information → INF
- Warning → WRN
- Error → EROR(Error为5个字符,取前4位输出)
- Fatal → FTL
你预期的ERR是3字符宽度的缩写,是日志模板配置的格式差异,不是日志系统异常。
完整修复步骤
- 修正中间件注册顺序
把自定义的TaskCanceledMiddleware放到所有中间件的最开头(位于UseSerilogRequestLogging之前注册),保证它处于请求管道最外层:
// 最先注册异常抑制中间件 app.UseMiddleware<TaskCanceledMiddleware>(); // 再注册Serilog请求日志中间件 app.UseSerilogRequestLogging(); // 后续再注册路由、授权、端点等其他中间件 app.UseRouting(); // ... 其他中间件配置 app.UseEndpoints(endpoints => endpoints.MapControllers());
- 优化自定义中间件逻辑
增加响应状态判断、异常标记清除逻辑,避免状态码设置失效、异常残留:
public class TaskCanceledMiddleware { private readonly RequestDelegate _next; private readonly ILogger<TaskCanceledMiddleware> _logger; public TaskCanceledMiddleware(RequestDelegate next, ILogger<TaskCanceledMiddleware> logger) { _next = next; _logger = logger; } public async Task InvokeAsync(HttpContext context) { try { await _next(context); } catch (TaskCanceledException) when (context.RequestAborted.IsCancellationRequested) { _logger.LogWarning("客户端主动取消请求,请求路径:{Path}", context.Request.Path); // 仅在响应未启动时可修改状态码 if (!context.Response.HasStarted) { context.Response.StatusCode = StatusCodes.Status499ClientClosedRequest; } // 清除上下文中的未处理异常标记,避免其他组件识别为未处理异常 var exceptionFeature = context.Features.Get<IExceptionHandlerFeature>(); if (exceptionFeature != null) { context.Features.Set<IExceptionHandlerFeature>(null); } } } }
- 配置Serilog请求日志级别规则
在注册Serilog请求日志中间件时,自定义日志级别判断逻辑,针对客户端取消请求场景降级为Warning级别:
app.UseSerilogRequestLogging(options => { options.GetLevel = (context, elapsedMs, exception) => { // 客户端主动取消的场景统一记Warning,不输出Error if (exception is TaskCanceledException || context.RequestAborted.IsCancellationRequested || context.Response.StatusCode == StatusCodes.Status499ClientClosedRequest) { return LogEventLevel.Warning; } // 其余场景走默认规则 return context.Response.StatusCode >= 500 ? LogEventLevel.Error : context.Response.StatusCode >= 400 ? LogEventLevel.Warning : LogEventLevel.Information; }; });
- (可选)全局过滤TaskCanceledException的Error日志
如果还有零散的相关Error日志输出,可以在Serilog全局配置中增加过滤规则,直接排除这类异常的Error日志:
Log.Logger = new LoggerConfiguration() // 省略你原有的其他配置 .Filter.ByExcluding(logEvent => logEvent.Exception is TaskCanceledException && logEvent.Level == LogEventLevel.Error) .CreateLogger();
内容的提问来源于stack exchange,提问作者ca9163d9
相关产品推荐
相关产品推荐

