我有一个超过 10 年历史的大型 Web 应用程序,我正在逐步升级到 Entity Framework 7、.Net 7 和 ASP.Net Core。我正在偿还这一大笔科技债务。我想(希望)我已经完成了大约 80%...所以只剩下另外 80% 要做:-)。
我刚刚遇到的一个意外迁移步骤是 System.Diagnostics.Trace 无法与 .NetCore/Asp.NetCore 中的现代化 ILogger 日志记录/跟踪基础结构一起使用。我有数百个 System.Diagnostics.Trace 调用,分布在该解决方案涉及的所有项目中,以及业务逻辑类的多个层中。我宁愿避免弄清楚如何通过依赖注入传递 ILogger 对象,并且(我猜这在某些地方是必需的)更改 new/create 调用和构造函数以将其传递进去。
因此,我找到了一种有前途的方法,能够推迟从 System.Diagnostics 迁移到 ILogger。感谢在我之前开辟这条道路的一些人,有一些很棒的想法: https://www.codeproject.com/Articles/5255953/Use-Trace-and-TraceSource-in-NET-Core-Logging 和 https://khalidabuhakmeh.com/logging-trace-output-using-ilogger-in-dotnet-applications
我采用了这种方法,创建了一个 TraceListener,将其添加到 System.Diagnostics.Trace,并将所有日志记录调用发送到 ILogger。
// Set up trace/debug logging.
IServiceCollection serviceCollection = builder.Services.AddLogging(iLoggingBuilder =>
{
iLoggingBuilder
//.ClearProviders()
//.AddConsole()
//.AddEventLog()
//.AddEventSourceLogger()
.AddConfiguration(builder.Configuration.GetSection("Logging"));
});
...
// Make the Asp.Net Core Logging framework listen to what comes from the System.Diagnostics.Trace framework, since that old tracing framework is what our DB+Model project uses.
ILogger<Program> logger = app.Services.GetRequiredService<ILogger<Program>>();
//ILogger<Program> logger = app.Services.GetService<ILoggerFactory>().CreateLogger<Program>();
Trace.Listeners.Clear();
Trace.Listeners.Add(new LoggerTraceListener(logger));
我的 LoggerTraceListener 实现与此处共享的非常相似: https://www.codeproject.com/Articles/5255953/Use-Trace-and-TraceSource-in-NET-Core-Logging
效果很好。除了...在 .Net Core 中的 Microsoft.Extensions.Logging 代码中,日志记录框架调用 System.Diagnostics 来进行自己的日志记录。这会导致无限循环。我对 System.Diagnostics 进行的每次调用都会传递到 ILogger,一旦到达 Microsoft.Extensions.Logging,就会进行另一个 System.Diagnostics 调用,最终返回到我的 TraceListener 中。
我可以用大锤子解决这个问题:在设置 ILoggingBuilder 时添加“.ClearProviders()”。我可以添加回一些默认提供程序。如果我保留“.AddDebug()”,则 Microsoft.Extensions.Logging 的日志记录将停止,因此问题得到解决。但我不想停止所有调试日志记录 - 谁知道 .Net 和 ASP.Net Core 的其他部分使用它。将来我会对一些可能有用的调试日志记录视而不见。我宁愿避免这种情况。 我尝试通过将 Microsoft.Extensions.Logging 的 appsettings.json 日志记录级别设置为“错误”来停止循环,但似乎只过滤写入日志的内容,而不会停止循环。
当 Microsoft.Extensions.Logging 的调用到达 LoggerTraceListener 中的方法时,我该如何阻止这些调用继续进行?是否有另一种方法可以更可靠地解决此问题? 或者我应该接受这样一个事实:我需要将 System.Diagnostics->ILogger 迁移工作添加到我正在咀嚼的一长串技术债务偿还清单中?
更新1: 我尝试了两种新方法,仍然没有成功。一种是尝试 System.Diagnostics TraceFilter。也许我可以过滤掉导致无限循环的日志记录调用?但这并没有成功。由于某种原因,TraceFilter 不会在 Trace.Write 和 WriteLine 调用中被调用。 然后,当调用到达 LoggerTraceListener 时,我尝试查找调用堆栈。这可能会起作用。但这会导致严重的性能问题。对于整个代码库中的每个跟踪调用,我都会遍历调用堆栈......这不是一个好主意。
更新2: 我放弃了尝试在此代码库中保持 System.Diagnostics.Trace 调用不变。我开始注入和传播 ILogger。这很痛苦,特别是其他项目中的许多类无法通过依赖注入来处理(没有可怕的“关注点分离”影响),所以我最终传递了一个带有“错误”的 ILogger 对象
<T>
。
我通过添加额外的检查解决了这个问题。
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Text;
using System.Threading;
using Microsoft.Extensions.Logging;
namespace Core.Hosting.Logging;
public class LoggerTraceListener : TraceListener
{
private readonly ILoggerFactory _loggerFactory;
private readonly ILogger _defaultLogger;
private readonly ConcurrentDictionary<string, ILogger> _loggers = new();
private readonly StringBuilder _builder = new();
private object _lock = new();
private int _processing;
public LoggerTraceListener(ILoggerFactory loggerFactory)
{
_loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
_defaultLogger = loggerFactory.CreateLogger(nameof(LoggerTraceListener));
Filter = new EventTypeFilter(SourceLevels.Information);
}
public override void TraceEvent(
TraceEventCache eventCache,
string source,
TraceEventType eventType,
int id,
string message
)
{
var logger = _loggers.GetOrAdd(
source,
static (s, factory) => factory.CreateLogger(s),
_loggerFactory
);
lock (_lock)
{
Interlocked.CompareExchange(ref _processing, 1, 0);
logger.Log(MapLevel(eventType), message);
Interlocked.Exchange(ref _processing, 0);
}
}
public override void TraceEvent(
TraceEventCache eventCache,
string source,
TraceEventType eventType,
int id,
string format,
params object[] args
)
{
var logger = _loggers.GetOrAdd(
source,
static (s, factory) => factory.CreateLogger(s),
_loggerFactory
);
lock (_lock)
{
Interlocked.CompareExchange(ref _processing, 1, 0);
logger.Log(MapLevel(eventType), format, args ?? Array.Empty<object>());
Interlocked.Exchange(ref _processing, 0);
}
}
public override void Write(string message)
{
_builder.Append(message);
}
public override void WriteLine(string message)
{
if (SkipMessage(message))
return;
if (Interlocked.CompareExchange(ref _processing, 1, 0) == 1)
return;
lock (_lock)
{
_builder.AppendLine(message);
_defaultLogger.LogInformation(_builder.ToString());
_builder.Clear();
Interlocked.Exchange(ref _processing, 0);
}
}
private static bool SkipMessage(string message) =>
string.IsNullOrWhiteSpace(message) ||
message.StartsWith("Microsoft.Hosting.") ||
message.StartsWith("CoreWCF.Channels.");
private LogLevel MapLevel(TraceEventType eventType) =>
eventType switch
{
TraceEventType.Verbose => LogLevel.Debug,
TraceEventType.Information => LogLevel.Information,
TraceEventType.Critical => LogLevel.Critical,
TraceEventType.Error => LogLevel.Error,
TraceEventType.Warning => LogLevel.Warning,
_ => LogLevel.Trace
};
}