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

关于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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 20:07:56