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

.NET7迁移中System.Diagnostics与ILogger循环调用问题求助

解决System.Diagnostics.Trace转ILogger时的无限循环问题(兼容.NET 7/ASP.NET Core)

问题根源

无限循环的核心原因是:.NET Core默认的DebugLogger内部会调用System.Diagnostics.Debug.WriteLine,而Debug与Trace共享同一套监听链。当你把Trace日志转发到ILogger后,ILogger的Debug日志又会触发Debug调用,进而回到你的TraceListener,形成闭环。

可行解决方案

方案1:替换默认DebugLogger,避免触发Trace/Debug调用

默认的DebugLogger会调用System.Diagnostics,我们可以自定义一个不依赖Trace/Debug的Debug日志提供者,替换掉默认实现,既保留Debug日志输出,又切断循环链路。

自定义Debug日志提供者代码:

public class NonTraceDebugLoggerProvider : ILoggerProvider
{
    public ILogger CreateLogger(string categoryName)
    {
        return new NonTraceDebugLogger(categoryName);
    }

    public void Dispose() { }

    private class NonTraceDebugLogger : ILogger
    {
        private readonly string _categoryName;

        public NonTraceDebugLogger(string categoryName)
        {
            _categoryName = categoryName;
        }

        public IDisposable BeginScope<TState>(TState state) => NullScope.Instance;

        public bool IsEnabled(LogLevel logLevel)
        {
            return logLevel >= LogLevel.Debug;
        }

        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
        {
            if (!IsEnabled(logLevel)) return;

            var logMessage = formatter(state, exception);
            // 直接用Console输出,不会触发Trace/Debug
            Console.WriteLine($"[{DateTime.Now:HH:mm:ss} {logLevel,-10}] {_categoryName}: {logMessage}");
            exception?.ConsoleWriteLine();
        }
    }

    private class NullScope : IDisposable
    {
        public static NullScope Instance = new();
        public void Dispose() { }
    }
}

// 扩展方法简化异常输出
internal static class ExceptionExtensions
{
    public static void ConsoleWriteLine(this Exception ex)
    {
        Console.WriteLine($"Exception: {ex.Message}\n{ex.StackTrace}");
        ex.InnerException?.ConsoleWriteLine();
    }
}

在Program.cs中配置日志:

var builder = WebApplication.CreateBuilder(args);

// 清除默认日志提供者,避免默认DebugLogger触发Trace
builder.Logging.ClearProviders();
// 添加自定义Debug日志提供者
builder.Logging.AddProvider(new NonTraceDebugLoggerProvider());
// 添加其他需要的日志提供者(如Console、File等)
builder.Logging.AddConsole();

// 注册你的TraceListener,将Trace转发到ILogger
var loggerFactory = builder.Services.BuildServiceProvider().GetRequiredService<ILoggerFactory>();
System.Diagnostics.Trace.Listeners.Add(new LoggerTraceListener(loggerFactory));

var app = builder.Build();
// ... 后续中间件配置

方案2:在TraceListener中轻量过滤循环调用

如果不想改动日志提供者配置,可以在自定义TraceListener中过滤来自Microsoft.Extensions.Logging命名空间的调用。只检查调用栈的前3层,性能开销可以忽略。

修改后的TraceListener代码:

public class LoggerTraceListener : TraceListener
{
    private readonly ILogger _logger;
    // 需要拦截的命名空间
    private readonly HashSet<string> _blockedNamespaces = new()
    {
        "Microsoft.Extensions.Logging",
        "System.Diagnostics"
    };

    public LoggerTraceListener(ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger("TraceForwarder");
    }

    public override void Write(string message)
    {
        if (IsLoopCall()) return;
        _logger.LogTrace(message);
    }

    public override void WriteLine(string message)
    {
        if (IsLoopCall()) return;
        _logger.LogTrace(message);
    }

    private bool IsLoopCall()
    {
        // 仅检查前3层调用栈,避免性能损耗
        var stackTrace = new StackTrace(2, false);
        for (int i = 0; i < Math.Min(stackTrace.FrameCount, 3); i++)
        {
            var method = stackTrace.GetFrame(i)?.GetMethod();
            if (method?.DeclaringType?.Namespace != null && _blockedNamespaces.Contains(method.DeclaringType.Namespace))
            {
                return true;
            }
        }
        return false;
    }
}

方案3:静态日志类过渡,逐步替换Trace调用

如果计划长期迁移,完全移除System.Diagnostics.Trace依赖,可以定义一个静态日志类,通过初始化注入ILogger,无需修改现有类的构造函数,逐步替换原有Trace调用。

静态日志类代码:

public static class TraceLogger
{
    private static ILogger _logger;

    // 在程序启动时初始化
    public static void Initialize(ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger("TraceLogger");
    }

    [Conditional("TRACE")]
    public static void Trace(string message)
    {
        _logger?.LogTrace(message) ?? System.Diagnostics.Trace.WriteLine(message);
    }

    [Conditional("TRACE")]
    public static void Trace(string format, params object[] args)
    {
        if (_logger != null)
        {
            _logger.LogTrace(format, args);
        }
        else
        {
            System.Diagnostics.Trace.WriteLine(string.Format(format, args));
        }
    }

    // 根据需要添加其他Trace重载方法
}

在Program.cs中初始化:

var app = builder.Build();
TraceLogger.Initialize(app.Services.GetRequiredService<ILoggerFactory>());

之后逐步将项目中System.Diagnostics.Trace.WriteLine(...)替换为TraceLogger.Trace(...),最终可以完全移除System.Diagnostics.Trace的依赖,同时避免DI传递ILogger的麻烦。

方案选择建议

  • 若需快速解决循环问题且保留Debug日志,优先选方案1;
  • 若不想改动日志系统配置,仅调整TraceListener,选方案2;
  • 若计划长期迁移,彻底替换Trace调用,选方案3。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 23:50:25