在 .NET Core 日志记录中使用 Trace 和 TraceSource
如何在 .NET Core 日志记录中使用 Trace 和 TraceSource
引言
本文介绍如何在 .NET Core 的组件设计中使用 Trace 和 TraceSource 。如果您有以下需求,本文可能会对您有所帮助:
- 您正在为 .NET Framework 和 .NET Core / .NET Standard 同时构建组件,并且您倾向于保留
Trace
和TraceSource
。 - 您有使用
Trace
和TraceSource
的第三方组件。 - 您正在将一个复杂的 .NET Framework 应用程序迁移到 .NET Core,并且目前不想更改其跟踪和日志记录设计。
- 您希望保持跟踪和日志记录的独立性和简洁性。
- 并且部署的应用程序不会托管在 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 中使用 Trace
和 TraceSource
的文章/帖子非常少,而且我在 Google 上找到的关于 ILogger<T>
的文章和示例几乎都描述了如何使用直接注入到 Program
、Startup
和 Controller
中的 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.Trace 和 System.Diagnostics.TraceSource 被设计用于分离跟踪和日志记录,而日志记录是通过附加的 跟踪侦听器 来实现的。
内置跟踪侦听器
代码示例:ConsoleAppTraceListener
在 .NET Framework 上,许多 TraceListener
派生类在 .NET Core 上仍然可用,但像 IisTraceListener
这样的类在 .NET Core 上不可用。
在 .NET Framework 上,应用程序可以通过 app.config 初始化 Trace
和 TraceSource
并实例化跟踪侦听器,这些设置会在应用程序代码执行第一行之前加载。
在 .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);
因此,您必须在应用程序的启动代码中实例化跟踪侦听器并初始化 Trace
和 TraceSources
对象。这不算太糟糕,但随着时间的推移,越来越多的第三方组件可能与 ILogger<T>
接口进行跟踪和日志记录。考虑到各种因素和权衡,最好在 TraceSource
和 ILogger<T>
之间建立一个桥梁,这样使用 Trace
和 TraceSource
的旧组件就可以将跟踪消息发送到 ILogger<T>
。
LoggerTraceListener
这是一个辅助的 TraceListener
,用于桥接 Trace
和 TraceSource
到 ILogger
。
代码示例:ConsoleAppTrace
由于 Trace
和 TraceSource
只能通过 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();
}
现在,Trace
和 TraceSource
可以使用哪些日志记录介质,取决于附加到 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 并将设置应用于一些内置组件。而像 SMTPClient
和 System.Diagnostics
组件之类的其他组件会默认读取 app.config。
在 .NET Core 中,应用程序程序员有责任通过代码或加载配置文件来配置。
历史
- 2020 年 1 月 27 日:初始版本