阻止 .Net 日志记录代码中的 System.Diagnostics 调用与 System.Diagnostics->ILogger 侦听器之间的无限循环

问题描述 投票:0回答:1

我有一个超过 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-Logginghttps://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>

c# asp.net-core .net-core system.diagnostics ilogger
1个回答
0
投票

我通过添加额外的检查解决了这个问题。

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
        };
}
© www.soinside.com 2019 - 2024. All rights reserved.