.NET Core的日志[5]:利用TraceSource写日志

简介:

从微软推出第一个版本的.NET Framework的时候,就在“System.Diagnostics”命名空间中提供了Debug和Trace两个类帮助我们完成针对调试和跟踪信息的日志记录。在.NET Framework 2.0中,微软引入了TraceSource并对跟踪日志系统进行了优化,优化后的跟踪日志系统在.NET Core中又经过了相应的简化。.NET Core的日志模型借助TraceSourceLoggerProvider实现对TraceSource的整合,在正式介绍这个Logger之前,我们先来认识一下TraceSource跟踪日志系统中的三个核心对象。[ 本文已经同步到《ASP.NET Core框架揭秘》之中]

目录
一、基于TraceSource的追踪日志系统
二、TraceSourceLogger
三、TraceSourceLoggerProvider

一、基于TraceSource的追踪日志系统

对于这个基于TraceSource的跟踪日志系统来说,除了TraceSource之外,它还具有额外连个核心的对象,它们分别是TraceListener和SourceSwitch,三者之间的关系如下图所示。日志消息的写入实现在TraceListener上,我们可以将一组TraceListener注册到某个TraceSource之上。当我们利用TraceSource记录某条跟踪日志时,日志消息会分发给注册的每一个TraceListener并由它们将日志消息写到对应的目的地。每个TraceSource都具有一个SourceSwitch,后者起到了日志过滤的作用。具体来说,SourceSwitch定义了相应的过滤条件来帮助TraceSource决定是否应该将跟踪日志分发给TraceListener,如果指定的日志消息不满足过滤条件,TraceSource将不会进行任何实质性的日志记录工作。

11

如下所示的是TraceSource的定义。每一个TraceSource都具有一个名称,它一般代表写入跟踪日志的应用程序、服务或者组件的名称。我们可以调用它的三组Trace方法(TraceData、TraceEvent和TraceInformation)来记录跟踪日志。由于这些方法都标注了一个ConditionaleAttribute特性并将条件编译符“TRACE”,所以针对这些方法的调用只有在针对Trace模式编译的应用中才是有效的。

   1: public class TraceSource
   2: {
   3:     public TraceListenerCollection Listeners { get; }
   4:     public string             Name { get; }
   5:     public SourceSwitch         Switch { get; set; }
   6:  
   7:     public TraceSource(string name);
   8:     public TraceSource(string name, SourceLevels defaultLevel);
   9:    
  10:     [Conditional("TRACE")]
  11:     public void TraceData(TraceEventType eventType, int id, object data);
  12:     [Conditional("TRACE")]
  13:     public void TraceData(TraceEventType eventType, int id, params object[] data);
  14:  
  15:     [Conditional("TRACE")]
  16:     public void TraceEvent(TraceEventType eventType, int id);
  17:     [Conditional("TRACE")]
  18:     public void TraceEvent(TraceEventType eventType, int id, string message);
  19:     [Conditional("TRACE")]
  20:      public void TraceEvent(TraceEventType eventType, int id, string format, params object[] args);
  21:  
  22:     [Conditional("TRACE")]
  23:     public void TraceInformation(string message);
  24:     [Conditional("TRACE")]
  25:     public void TraceInformation(string format, params object[] args);  
  26: }

通过TraceData、TraceEvent和TraceInformation这三个方法记录的跟踪日志都具有一个通过枚举类型TraceEventType表示的事件类型,它相当于前面提到的日志等级。TraceEventType的这些枚举项的值越小意味着等级越高,定义日志等级的LogLevel则于此相反。在调用TraceData和TraceEvent方法时,我们需要显式地为写入的跟踪日志指定事件类型,而TraceInformation方法则默认使用Information类型。

   1: public enum TraceEventType
   2: {
   3:     Critical         = 1,
   4:     Error            = 2,
   5:     Warning          = 4,
   6:     Information      = 8,
   7:     Verbose          = 16,
   8: }

与TraceEventType枚举对应的还具有另一个名为SourceLevels的枚举,除了包含五种具体事件类型之外,还具有额外两个选项All和Off,该枚举对象被SourceSwitch用来过滤日志。在调用构造函数创建TraceSource的时候,我们可以指定一个SourceLevels枚举值作为默认的等级。如果这个等级未作显式设置,创建的TraceSource采用的等级为Off,这意味着默认情况下针对追踪日志的记录是禁止的。

   1: [Flags]
   2: public enum SourceLevels
   3: {
   4:     All             = -1,
   5:     Off             = 0,
   6:     Critical        = 1,
   7:     Error           = 3,
   8:     Warning         = 7
   9:     Information     = 15,
  10:     Verbose         = 31
  11: }

我们创建的TraceSource是指定(或者默认设置)的表示日志等级的SourceLevels枚举会用来创建一个具有如下定义的SourceSwitch对象,TraceSource的Switch属性返回的就是这么一个对象。顾名思义,SourceSwitch是一个开关,它利用ShouldTrace方法决定了针对某种类型的跟踪日志的写入操作是应该开启还是关闭。如下面的代码片段所示,ShouldTrace方法返回的结果是根据通过Level属性返回的跟踪日志等级计算出来的,表示跟踪日志等级的SourceLevels枚举正是最初正是由TraceSource在初始化时提供的。

   1: public class SourceSwitch : Switch
   2: {
   3:     public SourceLevels Level {get;set;}
   4:  
   5:     public SourceSwitch(string name);
   6:     public SourceSwitch(string displayName, string defaultSwitchValue);
   7:  
   8:     public bool ShouldTrace(TraceEventType eventType)
   9:     {
  10:         return ((base.SwitchSetting & eventType) > 0);
  11:     }    
  12: }

TraceSource对象自身并不负责针对跟踪日志的写入,它仅仅将日志的写入请求分发给注册的TraceListener并委托它们来完成写日志的功能。这些注册到TraceSource上的TraceListenter被保存到由它的Listeners属性返回的集合对象中。所有的TraceListener都拍生于如下这个抽象的TraceListener类型,它定义了如下两组TraceData和TraceEvent方法。当我们调用TraceSource的TraceData、TraceEvent和TraceInformation方法时,如果通过SourceSwitch判断应该开启针对当前跟踪日志的写入功能,那么注册的TraceListener的TraceData或者TraceEvent方法将会被调用。

   1: public abstract class TraceListener : IDisposable
   2: {
   3:     ...
   4:     public virtual void TraceData(TraceEventCache eventCache, string source, TraceEventType eventType, int id, object data);
   5:     public virtual void TraceData(TraceEventCache eventCache, string source, TraceEventType eventType, int id, params object[] data);
   6:  
   7:     public virtual void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id);
   8:     public virtual void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string message);
   9:     public virtual void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string format, params object[] args);
  10: }

接下来我们通过一个简单的控制台应用来演示如何创建一个TraceSource并使用它来记录追踪日志。由于TraceSource定义在“System.Diagnostics.TraceSource”这个NuGet包中,我们需要在project.json文件中需要按照如下的方式添加针对这个NuGet包的依赖。和前面演示的实例一样,为了提供针对中文编码的支持,我们不得不添加针对“System.Text.Encoding.CodePages”这个NuGet包的依赖。

   1: {
   2:   ...
   3:   "dependencies": {
   4:     "System.Diagnostics.TraceSource": "4.0.0",    
   5:     "System.Text.Encoding.CodePages": "4.0.1"
   6:   }
   7: }

由于TraceSource总是利用注册在它上面的TraceListener来完成写日志的工作,所以我们按照如下的方式自定义了ConsoleTraceListener。顾名思义,ConsoleTraceListener旨在将分发给它的追踪日志输出到控制台上。如下面的代码片段所示,这个ConsoleTraceListener仅仅重写了Write和WriteLine方法,它们调用定义在Console类型上的同名方法将格式化好的日志消息输出到控制台上。

   1: public class ConsoleTraceListener : TraceListener
   2: {
   3:     public override void Write(string message) => Console.Write(message);
   4:     public override void WriteLine(string message) => Console.WriteLine(message);
   5: }

我们在作为程序入口的Main方法中创建了一个TraceSource对象。在调用构造函数的时候,除了指定TraceSource的名称(“Program”)之外,我们还设置了一个默认的追踪日志等级(Warning)。接下来我们创建了一个ConsoleTraceListener对象并将其注册到TraceSource对象上。在此之后,我们调用TraceSource的TraceEvent方法记录了三条追踪日志,它们采用的追踪事件类型分别是Information、Warining和Error。

   1: public class Program
   2: {
   3:     public static void Main(string[] args)
   4:     {
   5:         //注册EncodingProvider实现对中文编码的支持
   6:         Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
   7:  
   8:         TraceSource traceSource = new TraceSource(nameof(Program), SourceLevels.Warning);
   9:         traceSource.Listeners.Add(new ConsoleTraceListener());
  10:  
  11:         int eventId = 3721;
  12:         traceSource.TraceEvent(TraceEventType.Information, eventId, "升级到最新.NET Core版本({0})", "1.0.0");
  13:         traceSource.TraceEvent(TraceEventType.Warning, eventId, "并发量接近上限({0}) ", 200);
  14:         traceSource.TraceEvent(TraceEventType.Error, eventId, "数据库连接失败(数据库:{0},用户名:{1})", "TestDb", "sa");
  15:     }
  16: }

该程序运行之后,我们利用TraceSource记录的追踪日志将会被注册的ConsoleTraceListener按照如下图所示的形式输出到控制台上。由于我们在创建TraceSource的时候指定了一个默认的追踪日志等级Warning,所以只有不低于这个等级的两条日志才会显示在控制台上。

12

二、TraceSourceLogger

.NET Core的日志模型利用一个定义在NuGet包“Microsoft.Extensions.Logging.TraceSource”中的TraceSourceLogger类型实现与TraceSource跟踪日志系统的整合。从如下面的代码片段我们不难看出,一个TraceSourceLogger对象实际上就是对一个TraceSource对象的封装,在实现的Log<State>方法中,它会调用TraceSource的TraceEvent方法来完成针对日志消息的写入工作。

   1: public class TraceSourceLogger : ILogger
   2: {
   3:     public TraceSourceLogger(TraceSource traceSource);
   4:     public IDisposable BeginScope<TState>(TState state);
   5:     public bool IsEnabled(LogLevel logLevel);
   6:     public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter);
   7: }

当我们调用TraceSource的TraceEvent方法来写追踪日志的时候,需要指定追踪日志的事件类型,该类型由提供的日志等级来决定,下表展示了日志等级与跟踪事件类型之间的映射关系很简单。 由于TraceSource通过调用其SourceSwitch的ShouldTrace方法来决定是否真正需要写入当前分发的追踪日志消息,所以当TraceSourceLogger的IsEnabled方法被调用的时候,它也会按照这样的映射关系将指定的日志等级转换成追踪事件类型,并将其作为参数调用这个ShouldTrace方法,这个方法的返回值就是IsEnabled方法的返回值。

日志等级

跟踪事件类型

Trace

Verbose

Debug

Verbose

Information

Information

Warning

Warning

Error

Error

Critical

Critical

TraceSourceLogger的BeginScope<TState>方法会返回一个TraceSourceScope对象,虽然这是一个共有的类型,但是这个对象并不做任何作用域的控制,其自身也不携带任何关于当前日志上下文的信息,所以TraceSourceLogger和前面介绍的DebugLogger和EventLogLogger一样,其实都不提供针对日志上下文的支持。

三、TraceSourceLoggerProvider

TraceSourceLogger对应的LoggerProvider类型为TraceSourceLoggerProvider。如下面的代码片段所示,当我们创建一个TraceSourceLoggerProvider对象时需要提供一个SourceSwitch和TraceListener对象(可选)。在实现的CreateLogger方法中,TraceSourceLoggerProvider会根据指定的名称创建一个TraceSource对象,它将采用初始化时指定的SourceSwitch,预先指定的TraceListener也会注册到这个TraceSource对象上,CreateLogger方法最终返回的将是根据这个TraceSource创建的TraceSourceLogger。

   1: public class TraceSourceLoggerProvider : ILoggerProvider
   2: {   
   3:     public TraceSourceLoggerProvider(SourceSwitch rootSourceSwitch);
   4:     public TraceSourceLoggerProvider(SourceSwitch rootSourceSwitch, TraceListener rootTraceListener);
   5:  
   6:     public ILogger CreateLogger(string name);
   7:     public void Dispose();   
   8: }

值得一提的是TraceSourceLoggerProvider并不会在CreateLogger方法中频繁地创建TraceSource对象,而是选择将创建的TraceSource会根据指定的名称被缓存起来。所以当CreateLogger方法被调用的时候,TraceSourceLoggerProvider会根据指定的名称查看缓存中是否存在一个现成的TraceSource,如果存在则直接根据它创建返回的TraceSourceLogger。只有在确定同名的TraceSource不曾创建的情况下,新的TraceSource才会被真正创建出来。我们可以调用如下两个扩展方法AddTraceSource根据指定的SourceSwitch(或者它的名称)和TraceListener来创建TraceSourceLoggerProvider并将其注册到指定的LoggerFactory上。

   1: public static class TraceSourceFactoryExtensions
   2: {
   3:     public static ILoggerFactory AddTraceSource(this ILoggerFactory factory, SourceSwitch sourceSwitch, TraceListener listener);
   4:     public static ILoggerFactory AddTraceSource(this ILoggerFactory factory, string switchName, TraceListener listener);
   5: }

接下来我们通过一个简单的实例来演示针对DebugLogger的日志记录。我们创建一个空的控制台应用,在添加必要的依赖之后,我们在Main方法中编写了如下一段程序。如下面的代码片段所示,我们采用依赖注入的方式创建了一个LoggerFactory,并调用扩展方法AddTraceSource方法创建并注册了一个TraceSourceLoggerProvider对象。在利用LoggerFactory创建出Logger对象之后,我们利用后者记录了三条日志消息。

   1: public class Program
   2: {
   3:     public static void Main(string[] args)
   4:     {
   5:         //注册EncodingProvider实现对中文编码的支持
   6:         Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
   7:  
   8:         ILogger logger = new ServiceCollection()
   9:                 .AddLogging()
  10:                 .BuildServiceProvider()
  11:                 .GetService<ILoggerFactory>()
  12:                 .AddTraceSource(new SourceSwitch(nameof(Program), "Warning"), new ConsoleTraceListener())
  13:                 .CreateLogger<Program>();
  14:  
  15:  
  16:         int eventId = 3721;
  17:  
  18:         logger.LogInformation(eventId, "升级到最新.NET Core版本({version})", "1.0.0");
  19:         logger.LogWarning(eventId, "并发量接近上限({maximum}) ", 200);
  20:         logger.LogError(eventId, "数据库连接失败(数据库:{Database},用户名:{User})", "TestDb", "sa");
  21:     }
  22: }

我们在调用扩展方法AddTraceSource创建并注册TraceSourceLoggerProvider是指定了一个针对Warning等级的SourceSwitch,而指定的TraceListener是一个自定义的ConsoleTraceListener,所以只有两条等级不低于Warning的日志消息会被这个ConsoleTraceListener按照上图所示的形式输出到控制台上。


.NET Core的日志[1]:采用统一的模式记录日志
.NET Core的日志[2]:将日志写入控制台
.NET Core的日志[3]:将日志写入Debug窗口
.NET Core的日志[4]:利用EventLog写日志
.NET Core的日志[5]:利用TraceSource写日志

作者:蒋金楠
微信公众账号:大内老A
微博: www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号 蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
相关实践学习
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
相关文章
|
6月前
|
开发框架 .NET C#
ASP.NET Core Blazor 路由配置和导航
大家好,我是码农刚子。本文系统介绍Blazor单页应用的路由机制,涵盖基础配置、路由参数、编程式导航及高级功能。通过@page指令定义路由,支持参数约束、可选参数与通配符捕获,结合NavigationManager实现页面跳转与参数传递,并演示用户管理、产品展示等典型场景,全面掌握Blazor路由从入门到实战的完整方案。
546 6
|
JSON 安全 API
.net 自定义日志类
在.NET中,创建自定义日志类有助于更好地管理日志信息。示例展示了如何创建、配置和使用日志记录功能,包括写入日志文件、设置日志级别、格式化消息等。注意事项涵盖时间戳、日志级别、JSON序列化、线程安全、日志格式、文件处理及示例使用。请根据需求调整代码。
258 13
|
开发框架 算法 中间件
ASP.NET Core 中的速率限制中间件
在ASP.NET Core中,速率限制中间件用于控制客户端请求速率,防止服务器过载并提高安全性。通过`AddRateLimiter`注册服务,并配置不同策略如固定窗口、滑动窗口、令牌桶和并发限制。这些策略可在全局、控制器或动作级别应用,支持自定义响应处理。使用中间件`UseRateLimiter`启用限流功能,并可通过属性禁用特定控制器或动作的限流。这有助于有效保护API免受滥用和过载。 欢迎关注我的公众号:Net分享 (239字符)
373 1
|
开发框架 .NET 开发者
简化 ASP.NET Core 依赖注入(DI)注册-Scrutor
Scrutor 是一个简化 ASP.NET Core 应用程序中依赖注入(DI)注册过程的开源库,支持自动扫描和注册服务。通过简单的配置,开发者可以轻松地从指定程序集中筛选、注册服务,并设置其生命周期,同时支持服务装饰等高级功能。适用于大型项目,提高代码的可维护性和简洁性。仓库地址:&lt;https://github.com/khellang/Scrutor&gt;
537 5
|
开发框架 缓存 .NET
GraphQL 与 ASP.NET Core 集成:从入门到精通
本文详细介绍了如何在ASP.NET Core中集成GraphQL,包括安装必要的NuGet包、创建GraphQL Schema、配置GraphQL服务等步骤。同时,文章还探讨了常见问题及其解决方法,如处理复杂查询、错误处理、性能优化和实现认证授权等,旨在帮助开发者构建灵活且高效的API。
439 3
|
开发框架 .NET C#
在 ASP.NET Core 中创建 gRPC 客户端和服务器
本文介绍了如何使用 gRPC 框架搭建一个简单的“Hello World”示例。首先创建了一个名为 GrpcDemo 的解决方案,其中包含一个 gRPC 服务端项目 GrpcServer 和一个客户端项目 GrpcClient。服务端通过定义 `greeter.proto` 文件中的服务和消息类型,实现了一个简单的问候服务 `GreeterService`。客户端则通过 gRPC 客户端库连接到服务端并调用其 `SayHello` 方法,展示了 gRPC 在 C# 中的基本使用方法。
414 5
在 ASP.NET Core 中创建 gRPC 客户端和服务器
|
开发框架 JavaScript 前端开发
一个适用于 ASP.NET Core 的轻量级插件框架
一个适用于 ASP.NET Core 的轻量级插件框架
286 0
|
11月前
|
监控 容灾 算法
阿里云 SLS 多云日志接入最佳实践:链路、成本与高可用性优化
本文探讨了如何高效、经济且可靠地将海外应用与基础设施日志统一采集至阿里云日志服务(SLS),解决全球化业务扩展中的关键挑战。重点介绍了高性能日志采集Agent(iLogtail/LoongCollector)在海外场景的应用,推荐使用LoongCollector以获得更优的稳定性和网络容错能力。同时分析了多种网络接入方案,包括公网直连、全球加速优化、阿里云内网及专线/CEN/VPN接入等,并提供了成本优化策略和多目标发送配置指导,帮助企业构建稳定、低成本、高可用的全球日志系统。
1072 54
|
监控 Java 应用服务中间件
Tomcat log日志解析
理解和解析Tomcat日志文件对于诊断和解决Web应用中的问题至关重要。通过分析 `catalina.out`、`localhost.log`、`localhost_access_log.*.txt`、`manager.log`和 `host-manager.log`等日志文件,可以快速定位和解决问题,确保Tomcat服务器的稳定运行。掌握这些日志解析技巧,可以显著提高运维和开发效率。
1484 13
下一篇
开通oss服务