利用TraceSource写日志

利用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将不会进行任何实质性的日志记录工作。

如下所示的是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,所以只有不低于这个等级的两条日志才会显示在控制台上。

二、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写日志

时间: 2024-10-10 05:19:11

利用TraceSource写日志的相关文章

利用log4j写日志

1.下载导入包commons-logging.jar和log4j-1.2.9.jar 2.写配置文件log4j.properties,内容如下: log4j.rootCategory=INFO, LOGFILE     //LOGFILE表示将日志写到文件中,如果是CONSOLE则将日志写到控制台 log4j.logger.org.apache.axis2.enterprise=FATAL log4j.logger.de.hunsicker.jalopy.io=FATAL log4j.logge

C# 超高速高性能写日志 代码开源

1.需求 需求很简单,就是在C#开发中高速写日志.比如在高并发,高流量的地方需要写日志.我们知道程序在操作磁盘时是比较耗时的,所以我们把日志写到磁盘上会有一定的时间耗在上面,这些并不是我们想看到的. 2.解决方案 2.1.简单原理说明 使用列队先缓存到内存,然后我们一直有个线程再从列队中写到磁盘上,这样就可以高速高性能的写日志了.因为速度慢的地方我们分离出来了,也就是说程序在把日志扔给列队后,程序的日志部分就算完成了,后面操作磁盘耗时的部分程序是不需要关心的,由另一个线程操作. 俗话说,鱼和熊掌

python 统计时间,写日志

python 统计时间使用time模块,写日志使用logging模块,这两个都是标准模板. 测试socket使用socket模块 # 统计时间 ---------------------- import time start = time.time() end = time.time() stamp = end - start print "耗时", stamp # 日志 ----------------------- import loggingimport datetime cur

NodeJS写日志_Log4js使用详解

今天和大家分享一下NodeJS中写日志的一个常用第三方包:Log4js. 跟随主流Blog特色,先简单介绍下Log4js的基本信息.介绍Log4js之前,需要先说一下Log4***,Log4***是由Apache提供的多平台下多语言下日志书写扩展包,目的很简单就是使日志书写更加方便简洁,同时对不同的业务日志能够进行灵活的分文件记录,同时也包含着详细的等级配置,为之后分级输出,检索,及程序自动解析提供更加便捷的支持(一家之言,非官方描述,领会精神).Log4***有很多语言的实现,比如Log4cp

轻轻松松教你写日志-超级简单

最近在做一个项目,涉及到很多的服务,一步步调试相当麻烦,要在自己电脑上发布很多服务,又要全部开启.很费时间,出现问题,怎么解决最快呢?直接写日志,一步定位哪里出了错. Log4Net库是一个帮助程序员将日志信息输出到各种目标(控制台.文件 数据库等)的工具. Log4Net,相信哪个程序员都用过,但是可能是人家配置好了自己拿过来直接用,所以让自己写还是有点困难,听起来很高大上的样子,其实真的很简单.以前听别人讲,迷迷糊糊,知道那么回事,就是个写日志的.但是真正会用还是在项目中自己真正的实践. 下

C#多线程写日志

由于程序是3层架构的,所有多线程记录日志成了比较棘手的问题,以前还真就没有在意过写日志的问题,认为不过是写文件罢了~~!如今发现原来要实现文件共享,并且能够使多线程同时操作日志还不能相互冲突,真的很麻烦.当然要实现它我首先想到的是在网上搜,结果可能是我搜的不得其法,没发现结果,多数都是用lock,mutx等线程锁或互斥的方式写日志,偶想这样和单线程有啥区别吗?还是没能起到多线程应该有的效率! 后来问朋友,发现个log4net的东西,不过此物依然用到了线程互斥,看了源码发现的! 网络不行,朋友不知

多线程下写日志

鄙人最近遇到了一个奇特的线上事故,记录一下,以备记忆. 鄙人所在的部门负责给公司提供各种基础库,即基础架构部门.最近某别的部门用本部门提供的支持多线程版本的日志库后,出现这样一个奇特的现象:当磁盘被日志写满以后,他们的数据文件的头部被写上了最新的日志!就是说,别的部门的程序的数据文件被日志数据给污染了. 这里先不介绍这个事故的原因.先说下这个日志库的写日志过程,其流程大致如下: step1  如果log的fd为-1,就重新通过C函数open再打开一个log_fd: step2  写log内容,即

写日志函数和持续优化

首先看下面的函数 <span style="font-size:18px;">void writelog(char* file, char* msg) { FILE*fp=NULL; int nDataLen = strlen(msg); fp=fopen(file,"ab+");//只供读取 if(fp!=NULL) { fwrite(msg, nDataLen, 1, fp); } fclose(fp);//关闭文件 }</span> 这

[cocos2dx]利用NDK崩溃日志查找BUG

摘要: 在android上开发c++应用, crash日志都是汇编码, 很难对应到c++代码中去. 通过此文, 你可以定位到程序崩溃时的C++代码, 精确查找问题. 博客: http://www.cnblogs.com/jhzhu 邮箱: [email protected] 作者: 知明所以 时间: 2014-06-20 背景介绍 本文主要内容: 利用android的crash log来对c++开发的android应用进行错误定位. 容易稳定复现的BUG, 一般可以通过断点调试来解决. 如果测试