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

如何禁用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字符宽度的缩写,是日志模板配置的格式差异,不是日志系统异常。

完整修复步骤

  1. 修正中间件注册顺序
    把自定义的TaskCanceledMiddleware放到所有中间件的最开头(位于UseSerilogRequestLogging之前注册),保证它处于请求管道最外层:
// 最先注册异常抑制中间件
app.UseMiddleware<TaskCanceledMiddleware>();
// 再注册Serilog请求日志中间件
app.UseSerilogRequestLogging();
// 后续再注册路由、授权、端点等其他中间件
app.UseRouting();
// ... 其他中间件配置
app.UseEndpoints(endpoints => endpoints.MapControllers());
  1. 优化自定义中间件逻辑
    增加响应状态判断、异常标记清除逻辑,避免状态码设置失效、异常残留:
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);
            }
        }
    }
}
  1. 配置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;
    };
});
  1. (可选)全局过滤TaskCanceledException的Error日志
    如果还有零散的相关Error日志输出,可以在Serilog全局配置中增加过滤规则,直接排除这类异常的Error日志:
Log.Logger = new LoggerConfiguration()
    // 省略你原有的其他配置
    .Filter.ByExcluding(logEvent => 
        logEvent.Exception is TaskCanceledException 
        && logEvent.Level == LogEventLevel.Error)
    .CreateLogger();

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 14:36:14