.NET7迁移中System.Diagnostics与ILogger循环调用问题求助
问题根源
无限循环的核心原因是:.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

