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

Serilog实现单请求单日志时调用FileSink构造函数报Obsolete错误

问题解决方案

不要自定义实现依赖内部FileSink的滚动文件接收器,官方标记内部构造函数过时的核心原因是文件写入逻辑会持续迭代,直接依赖内部类型会在版本升级时出现兼容性问题。正确实现单请求单日志可以通过请求级子Logger+上下文实例绑定+自定义文件路径的方式实现,全程使用公开API,不需要修改内部Sink逻辑。

具体实现步骤

  • 第一步:生成符合规则的请求专属日志文件名
    在HTTP请求管道最前置的自定义中间件中,用线程安全的结构按日期维护当天的请求计数器:当天第一个请求序号为0,对应不带后缀的文件,后续请求序号依次递增,文件名匹配规则如下:

    当天第1个请求:debug20220713.log、error20220713.log
    当天第2个请求:debug20220713_001.log、error20220713_001.log
    当天第N个请求:debug20220713_{N-1:000}.log、error20220713_{N-1:000}.log

    计数器实现参考代码:

    // 静态线程安全的每日计数器存储,单实例部署直接用即可
    private static readonly ConcurrentDictionary<string, int> _dailyRequestCounter = new();
    
    // 中间件逻辑内生成当前请求的文件路径
    var currentDate = DateTime.Now.ToString("yyyyMMdd");
    var requestSeq = _dailyRequestCounter.AddOrUpdate(currentDate, 0, (_, oldVal) => oldVal + 1);
    var debugLogPath = requestSeq == 0 
        ? $"debug{currentDate}.log" 
        : $"debug{currentDate}_{requestSeq:000}.log";
    var errorLogPath = requestSeq == 0 
        ? $"error{currentDate}.log" 
        : $"error{currentDate}_{requestSeq:000}.log";
    

    如果是多实例部署场景,把计数器替换为Redis等分布式缓存存储即可,避免多实例生成重复序号。

  • 第二步:为当前请求创建独立的子Logger实例
    不要用全局静态Logger写请求级日志,拿到当前请求的专属日志路径后,单独创建子Logger,配置规则和全局Logger保持一致,仅替换文件写入路径为当前请求的专属路径:

    var requestLogger = new LoggerConfiguration()
        .Enrich.WithExceptionDetails()
        .Enrich.FromLogContext()
        .WriteTo.Async(y =>
            y.Logger(m =>
            {
                m.WriteTo.File(
                    new ExpressionTemplate(jsonErrorTemplate),
                    errorLogPath,
                    LogEventLevel.Warning);
                m.Filter.ByIncludingOnly(e => e.Level == LogEventLevel.Warning || e.Level == LogEventLevel.Error);
            })
        )
        .WriteTo.Async(x =>
            x.Logger(l =>
            {
                l.WriteTo.File(new ExpressionTemplate(jsonLogTemplate),
                    debugLogPath,
                    LogEventLevel.Information);
                l.Filter.ByIncludingOnly(e => e.Level == LogEventLevel.Information);
            }))
        .CreateLogger();
    
  • 第三步:绑定请求级Logger到当前请求上下文
    把创建好的requestLogger存入HttpContext.Items或者当前请求作用域的依赖注入容器中,当前请求生命周期内所有需要写日志的逻辑,都从请求上下文获取这个专属Logger实例写入,不要调用全局Log.Logger写日志,避免日志串到其他文件。

  • 第四步:请求结束时释放Logger资源
    在中间件的响应收尾逻辑(哪怕请求触发异常)中调用requestLogger.Dispose(),会自动触发异步Sink的缓冲区刷新,把所有缓存的日志内容完整写入对应文件,不会出现日志丢失。

方案优势

整个实现全程通过公开的WriteTo.File()扩展方法配置,没有直接引用FileSink等非公开类型,后续Serilog版本升级不会出现兼容性问题;同时每个Logger实例只处理对应单个请求的日志,天然实现日志隔离,不需要自己实现复杂的文件流管理、滚动切换逻辑,稳定性更高。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 11:15:42