关于Serilog实现错误触发时转储队列Debug日志的问询
实现异常触发时输出关联Debug日志的方案
这个需求完全可以实现,核心思路是利用Serilog的扩展能力,结合CorrelationId做请求上下文关联,把Debug日志临时缓存起来,直到出现Error时再一次性输出。下面是具体的实现步骤和代码:
1. 实现线程安全的Debug日志缓存
首先需要一个容器来存储每个请求(通过CorrelationId标识)的Debug日志,同时要考虑并发场景和内存清理,避免内存泄漏:
public static class DebugLogCache { // 存储结构:CorrelationId -> (日志列表, 创建时间) private static readonly ConcurrentDictionary<string, (List<LogEvent> Logs, DateTime CreatedAt)> _cache = new(); private static readonly Timer _cleanupTimer; static DebugLogCache() { // 启动定时清理任务,每小时清理一次30分钟前的缓存 _cleanupTimer = new Timer(CleanupExpiredLogs, null, TimeSpan.Zero, TimeSpan.FromHours(1)); } private static void CleanupExpiredLogs(object state) { var cutoffTime = DateTime.UtcNow.AddMinutes(-30); var expiredKeys = _cache.Where(kv => kv.Value.CreatedAt < cutoffTime).Select(kv => kv.Key).ToList(); foreach (var key in expiredKeys) { _cache.TryRemove(key, out _); } } // 添加Debug日志到缓存 public static void AddLog(string correlationId, LogEvent logEvent) { if (string.IsNullOrEmpty(correlationId)) return; _cache.AddOrUpdate( correlationId, _ => (new List<LogEvent> { logEvent }, DateTime.UtcNow), (_, existing) => { existing.Logs.Add(logEvent); return existing; }); } // 获取并清空指定CorrelationId的缓存日志 public static List<LogEvent> GetAndClearLogs(string correlationId) { if (_cache.TryRemove(correlationId, out var entry)) return entry.Logs; return new List<LogEvent>(); } }
2. 自定义Serilog Sink处理日志逻辑
这个Sink会拦截所有日志,根据级别决定是缓存Debug日志,还是在Error发生时先输出缓存再输出Error:
public class ConditionalDebugSink : ILogEventSink { private readonly ILogEventSink _targetSink; // 接收实际输出的Sink(比如Console、File等) public ConditionalDebugSink(ILogEventSink targetSink) { _targetSink = targetSink; } public void Emit(LogEvent logEvent) { // 从日志属性中提取CorrelationId if (!logEvent.Properties.TryGetValue("CorrelationId", out var correlationIdValue)) return; var correlationId = correlationIdValue.ToString().Trim('"'); switch (logEvent.Level) { case LogEventLevel.Debug: // Debug日志先缓存,不立即输出 DebugLogCache.AddLog(correlationId, logEvent); break; case LogEventLevel.Error: case LogEventLevel.Fatal: // 出现错误时,先输出缓存的所有Debug日志,再输出当前错误日志 var cachedLogs = DebugLogCache.GetAndClearLogs(correlationId); foreach (var log in cachedLogs) { _targetSink.Emit(log); } _targetSink.Emit(logEvent); break; default: // Information及以上级别(非错误)直接正常输出 _targetSink.Emit(logEvent); break; } } }
3. 配置Serilog Logger
在配置时,需要让Debug日志只进入我们的自定义Sink,全局保持Information级别,避免正常情况下输出Debug日志:
Log.Logger = new LoggerConfiguration() .Enrich.WithCorrelationId() // 确保日志带上CorrelationId .MinimumLevel.Information() // 全局最低日志级别设为Information .WriteTo.Logger(lc => lc .MinimumLevel.Debug() // 这个分支专门接收Debug级别日志 .WriteTo.Sink(new ConditionalDebugSink(new ConsoleSink(new ConsoleFormatter())))) // 替换成你实际用的输出Sink(比如FileSink) // 这里可以添加其他常规配置,比如输出到文件、Seq等 .CreateLogger();
4. 注意事项
- 确保CorrelationId在整个请求生命周期中正确传递,比如在ASP.NET Core中可以通过中间件生成并设置CorrelationId,保证每个请求的日志都带有唯一标识。
- 可以根据实际业务场景调整缓存过期时间,比如请求响应时间较长的话,可适当延长过期时长。
- 如果使用其他日志输出目标(如Seq、Elasticsearch),只需把
ConsoleSink替换为对应的Sink即可。
内容的提问来源于stack exchange,提问作者Marco
相关产品推荐
相关产品推荐

