65.9K
CodeProject 正在变化。 阅读更多。
Home

在 .NET Core 日志记录中使用 Trace 和 TraceSource

starIconstarIconstarIconstarIconstarIcon

5.00/5 (6投票s)

2020 年 1 月 27 日

CPOL

5分钟阅读

viewsIcon

36148

如何在 .NET Core 日志记录中使用 Trace 和 TraceSource

引言

本文介绍如何在 .NET Core 的组件设计中使用 Trace TraceSource 。如果您有以下需求,本文可能会对您有所帮助:

  1. 您正在为 .NET Framework 和 .NET Core / .NET Standard 同时构建组件,并且您倾向于保留 TraceTraceSource
  2. 您有使用 TraceTraceSource 的第三方组件。
  3. 您正在将一个复杂的 .NET Framework 应用程序迁移到 .NET Core,并且目前不想更改其跟踪和日志记录设计。
  4. 您希望保持跟踪和日志记录的独立性和简洁性。
  5. 并且部署的应用程序不会托管在 Linux 上。

目标读者是对 .NET Framework 编程有丰富经验的程序员,本文讨论的知识对于 .NET Core 3.0 和 .NET Framework 2.0 都是最新的,而 .NET Framework 4.8 是 .NET Framework 的最后一个主要版本。

背景

在 .NET Core 中,默认的跟踪和 日志记录 已升级为 ILogger<T>,并且期望通过 .NET Core 的依赖注入来实例化相应的日志记录器对象。ILogger<T> 可能与 System.Diagnostics.TraceSource 相当,而附加的 ILoggerProvider 对象可能与 System.Diagnostics.TraceListener 相当。

在 .NET Core 中进行复杂业务应用程序编程时,我发现关于在 .NET Core 中使用 TraceTraceSource 的文章/帖子非常少,而且我在 Google 上找到的关于 ILogger<T> 的文章和示例几乎都描述了如何使用直接注入到 ProgramStartupController 中的 ILogger,而我一直在寻找关于如何在远离 Program 项目的组件/程序集中使用 ILogger 的示例或指南。

Logger

代码示例包含多个项目,每个项目代表一个简单的场景和一个技术解决方案。

假设您已经阅读了 .NET Core 和 ASP.NET Core 中的日志记录。日志记录并未包含在 .NET Core 运行时和启动逻辑中,也没有包含在控制台应用程序的脚手架代码中。要使用日志记录,需要安装 Microsoft.Extensions.Logging 包。

然而,仅此包不足以将日志记录到控制台。请使用 Microsoft.Extensions.Logging.Console

此包包含 Microsoft.Extensions.Logging。此外,您通常不会在代码中硬编码开关,而是在配置文件中设置,在 .NET Core 中,通常使用 JSON 配置文件,因此您需要 Microsoft.Extensions.Configuration.Json

LoggerFactory

代码示例:ConsoleAppLoggerFactory

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

namespace ConsoleAppLoggerDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            var configuration = new ConfigurationBuilder()
                                .AddJsonFile("appsettings.json", false, true)
                                .Build();

            using var loggerFactory = LoggerFactory.Create(
                builder =>
                    {
                        builder.AddConfiguration(configuration.GetSection("Logging"));
                        builder.AddConsole();
                    }
            );

            var logger = loggerFactory.CreateLogger<Program>();
            logger.LogInformation("1111logger information"); //settings in appsettings.json filters this out
            logger.LogWarning("2222logger warning");

            var fooLogger = loggerFactory.CreateLogger<FooService>();
            IFooService fooService = new FooService(fooLogger);
            fooService.DoWork();
        }
    }

    public interface IFooService
    {
        void DoWork();
    }

    public class FooService : IFooService
    {
        private readonly ILogger logger;

        public FooService(ILogger<FooService> logger)
        {
            this.logger = logger;
        }

        public void DoWork()
        {
            logger.LogInformation("3333Doing work.");
            logger.LogWarning("4444Something warning");
            logger.LogCritical("5555Something critical");
        }
    }
}

appsettings.json 文件内容如下:

{
    "Logging": {
        "Console": {
            "disableColors": false
        },

        "LogLevel": {
            "Default": "Information",
            "Microsoft": "Information",
            "ConsoleAppLoggerDemo.FooService": "Warning",
            "ConsoleAppLoggerDemo.Program": "warning"
        }
    }
}

提示

在读取 appsettings.json 中的 Logging 部分后,通过 DI,.NET Core 运行时会配置控制台日志记录提供程序以及“Microsoft”和“ConsoleApp1.FooService”等日志记录器。

这对于非常简单的场景已经足够了,但是,在复杂的应用程序中,您可能希望利用 .NET Core 内置的依赖注入,如下所述。

用于注入日志记录器的 ServiceCollection

代码示例:ConsoleAppAddLogging

会创建一个 ILoggerFactory 实例并将其注册到 ServiceCollection 中,然后该工厂会通过以下任一方式创建日志记录器 ILogger<Program>

serviceProvider.GetService<ILoggerFactory>().CreateLogger<Program>()

serviceProvider.GetService<ILogger<Program>>();

FooService 通过 DI 实例化时,定义的日志记录器 ILogger<FooService> 会被实例化并注入。

using Microsoft.Extensions.Logging;
using System;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace ConsoleAppLoggerDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World! from console");

            var configuration = new ConfigurationBuilder()
                                .AddJsonFile("appsettings.json", false, true)
                                .Build();

            using var serviceProvider = new ServiceCollection()
                .AddSingleton<IFooService, FooService>()
                .AddLogging(builder =>
                {
                    builder.AddConfiguration(configuration.GetSection("Logging"));
                    builder.AddConsole();
                })
                .BuildServiceProvider();

            ILogger<Program> logger = serviceProvider.GetService<ILogger<Program>>();
            //logger = serviceProvider.GetService<ILoggerFactory>().CreateLogger<Program>(); 
            // Factory first. This works too.

            IFooService fooService = serviceProvider.GetService<IFooService>();

            logger.LogInformation("1111logger information");
            logger.LogWarning("2222logger warning");

            fooService.DoWork();
        }
    }

    public interface IFooService
    ...
}

Trace 和 TraceSource

System.Diagnostics.TraceSystem.Diagnostics.TraceSource 被设计用于分离跟踪和日志记录,而日志记录是通过附加的 跟踪侦听器 来实现的。

内置跟踪侦听器

代码示例:ConsoleAppTraceListener

在 .NET Framework 上,许多 TraceListener 派生类在 .NET Core 上仍然可用,但像 IisTraceListener 这样的类在 .NET Core 上不可用。

在 .NET Framework 上,应用程序可以通过 app.config 初始化 TraceTraceSource 并实例化跟踪侦听器,这些设置会在应用程序代码执行第一行之前加载。

在 .NET Core 中,您仍然可以使用各种跟踪侦听器,例如 ConsoleTraceListener,但是,由于 .NET Core 默认不会加载配置文件,并且内置配置不关注跟踪侦听器。

            using (var listener = new TextWriterTraceListener("c:\\temp\\mylog.txt"))
            using (var consoleListener = new ConsoleTraceListener())
            {
                Trace.Listeners.Add(listener);
                Trace.Listeners.Add(consoleListener);

因此,您必须在应用程序的启动代码中实例化跟踪侦听器并初始化 TraceTraceSources 对象。这不算太糟糕,但随着时间的推移,越来越多的第三方组件可能与 ILogger<T> 接口进行跟踪和日志记录。考虑到各种因素和权衡,最好在 TraceSourceILogger<T> 之间建立一个桥梁,这样使用 TraceTraceSource 的旧组件就可以将跟踪消息发送到 ILogger<T>

LoggerTraceListener

这是一个辅助的 TraceListener,用于桥接 TraceTraceSourceILogger

代码示例:ConsoleAppTrace

由于 TraceTraceSource 只能通过 Listeners 与日志记录进行交互,因此这里使用 LoggerTraceListener 来监听跟踪并将消息写入 ILogger<T>,最终将跟踪发送到日志记录提供程序。

    public class LoggerTraceListener : TraceListener
    {
        private readonly ILogger logger;

        public LoggerTraceListener(ILogger logger)
        {
            this.logger = logger;
        }

        public override void Write(string message)
        {
            logger.LogInformation(message);
        }

        public override void WriteLine(string message)
        {
            logger.LogInformation(message);
        }

        public override void WriteLine(string message, string category)
        {
            logger.LogInformation(category + ": " + message);
        }

        public override void TraceEvent
               (TraceEventCache eventCache, string source, 
                TraceEventType eventType, int id)
        {
            switch (eventType)
            {
                case TraceEventType.Critical:
                    logger.LogCritical(id, source);
                    break;
                case TraceEventType.Error:
                    logger.LogError(id, source);
                    break;
                case TraceEventType.Warning:
                    logger.LogWarning(id, source);
                    break;
                case TraceEventType.Information:
                    logger.LogInformation(id, source);
                    break;
                case TraceEventType.Verbose:
                    logger.LogTrace(id, source);
                    break;
                case TraceEventType.Start:
                    logger.LogInformation(id, "Start: " + source);
                    break;
                case TraceEventType.Stop:
                    logger.LogInformation(id, "Stop: " + source);
                    break;
                case TraceEventType.Suspend:
                    logger.LogInformation(id, "Suspend: " + source);
                    break;
                case TraceEventType.Resume:
                    logger.LogInformation(id, "Resume: " + source);
                    break;
                case TraceEventType.Transfer:
                    logger.LogInformation(id, "Transfer: " + source);
                    break;
                default:
                    throw new InvalidOperationException("Impossible");
            }
        }

应用程序启动

            using var serviceProvider = new ServiceCollection()
                .AddSingleton<IFooService, FooService>()
                .AddLogging(builder =>
                {
                    builder.AddConfiguration(configuration.GetSection("Logging"));
                    builder.AddConsole();
                })
                .BuildServiceProvider();

            ILogger<Program> logger = serviceProvider.GetService<ILogger<Program>>();
            IFooService fooService = serviceProvider.GetService<IFooService>();


            logger.LogInformation("1111logger information");
            logger.LogWarning("2222logger warning");

            fooService.DoWork();

            using (var listener = new LoggerTraceListener(logger))
            {
                System.Diagnostics.Trace.Listeners.Add(listener);
                TraceSources.Instance.InitLoggerTraceListener(listener);

                TraceLover.DoSomething();
                TraceSourceLover.DoSomething();
            }

现在,TraceTraceSource 可以使用哪些日志记录介质,取决于附加到 ILogger 的日志记录提供程序。

Trace、TraceSource 和日志记录提供程序和谐共处

代码示例:ConsoleappSeriLog

Microsoft 在 .NET Core 中开发了 很少具体的日志记录提供程序,这可能是出于业务考虑和设计。市面上有相当多的第三方日志记录提供程序。

  • NLog
  • Log4net
  • Serilog

还有一篇非常好的文章比较了这三者。

我同意 Serilog 是整体上最好的。

            var configuration = new ConfigurationBuilder()
                                .AddJsonFile("appsettings.json", false, true)
                                .Build();

            Serilog.Log.Logger = new Serilog.LoggerConfiguration()
                                  .Enrich.FromLogContext()
                                  //.WriteTo.Console() I prefer plugging 
                                  // through the config file
                                  .ReadFrom.Configuration(configuration)
                                  .CreateLogger();

            using var serviceProvider = new ServiceCollection()
                .AddLogging(builder => builder.AddSerilog())
                .AddSingleton<IFooService, FooService>()
                .BuildServiceProvider();

            var    logger = serviceProvider.GetService<ILogger<Program>>();
            var    fooService = serviceProvider.GetService<IFooService>();

            try
            {
                Log.Information("Starting up");
                logger.LogInformation("1111logger information");
                logger.LogWarning("2222logger warning");

                fooService.DoWork();

                using (var listener = new LoggerTraceListener(logger))
                {
                    System.Diagnostics.Trace.Listeners.Add(listener);
                    TraceSources.Instance.InitLoggerTraceListener(listener);

                    TraceLover.DoSomething();
                    TraceSourceLover.DoSomething();
                }
            }
            catch (Exception ex)
            {
                Log.Fatal(ex, "Application start-up failed");
            }
            finally
            {
                Log.CloseAndFlush();
            }

appsettings.json 文件内容如下:

{
    "TraceSource": {
        "WebApi": {
            "SourceLevels": "Information"
        },

        "HouseKeeping": { "SourceLevels": "Warning" },

        "DbAudit": {
            "SourceLevels": "Warning"
        }
    },

    "Serilog": {
        "MinimumLevel": {
            "Default": "Debug",
            "Override": {
                "Microsoft": "Information",
                "System": "Warning",
                "ConsoleAppLoggerDemo.FooService": "Warning",
                "ConsoleAppLoggerDemo.Program": "Warning"
            }
        },

        "WriteTo": [
            {
                "Name": "Console"
            },

            {
                "Name": "File",
                "Args": {
                    "path": "%PROGRAMDATA%/my/logs/CloudPosApi_Test.log",
                    "outputTemplate": "{Timestamp:MM-dd HH:mm:ss.fff zzz} 
                           [{Level}] {ThreadId} {Message}{NewLine}{Exception}",
                    "rollingInterval": "Day"
                }
            }
        ]
    }
}

关注点

在 .NET Framework 上,运行时会在应用程序代码执行第一行之前加载 app.config 并将设置应用于一些内置组件。而像 SMTPClientSystem.Diagnostics 组件之类的其他组件会默认读取 app.config

在 .NET Core 中,应用程序程序员有责任通过代码或加载配置文件来配置。

历史

  • 2020 年 1 月 27 日:初始版本
© . All rights reserved.