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

