.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将不会进行任何实质性的日志记录工作。
如下所示的是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写日志
.NET Core的日志[5]:利用TraceSource写日志的更多相关文章
- 利用TraceSource写日志
利用TraceSource写日志 从微软推出第一个版本的.NET Framework的时候,就在“System.Diagnostics”命名空间中提供了Debug和Trace两个类帮助我们完成针对调试 ...
- Delphi 写日志的类
unit uProgLog; interface uses Windows, SysUtils, SyncObjs; const C_LOG_LEVEL_TRACE = $; C_LOG_LEVEL_ ...
- C# 超高速高性能写日志 代码开源
1.需求 需求很简单,就是在C#开发中高速写日志.比如在高并发,高流量的地方需要写日志.我们知道程序在操作磁盘时是比较耗时的,所以我们把日志写到磁盘上会有一定的时间耗在上面,这些并不是我们想看到的. ...
- [转]C# 超高速高性能写日志 代码开源
1.需求 需求很简单,就是在C#开发中高速写日志.比如在高并发,高流量的地方需要写日志.我们知道程序在操作磁盘时是比较耗时的,所以我们把日志写到磁盘上会有一定的时间耗在上面,这些并不是我们想看到的 ...
- [转]ASP.NET Core 开发-Logging 使用NLog 写日志文件
本文转自:http://www.cnblogs.com/Leo_wl/p/5561812.html ASP.NET Core 开发-Logging 使用NLog 写日志文件. NLog 可以适用于 . ...
- ASP.NET Core 开发-Logging 使用NLog 写日志文件
ASP.NET Core 开发-Logging 使用NLog 写日志文件. NLog 可以适用于 .NET Core 和 ASP.NET Core . ASP.NET Core已经内置了日志支持,可以 ...
- .Net Core 实践 - 使用log4net记录日志(3)— log4net向ElasticSearch写日志
demo地址:https://github.com/PuzzledAlien/log4net_demo/tree/master/DotNetCoreConsole_V3 Windows 10 安装部署 ...
- 收藏收藏:时隔一年,你关注的打造一个实用的TXT文本操作及日志框架,我们开源了,不再为程序写日志发愁(也支持.net core哦)
记得做这个框架是在2018年刚接触.net core的时候,那个时候为了能够专心的研究我开始不写博客了,但是学有所成并在公司运用了近一年的时间了,决定回来和各位分享我们所掌握的那星星点点的知识,希望可 ...
- ASP.NET Core应用中如何记录和查看日志
日志记录不仅对于我们开发的应用,还是对于ASP.NET Core框架功能都是一项非常重要的功能特性.我们知道ASP.NET Core使用的是一个极具扩展性的日志系统,该系统由Logger.Logger ...
随机推荐
- js学习笔记:操作iframe
iframe可以说是比较老得话题了,而且网上也基本上在说少用iframe,其原因大致为:堵塞页面加载.安全问题.兼容性问题.搜索引擎抓取不到等等,不过相对于这些缺点,iframe的优点更牛,跨域请求. ...
- Spring之初体验
Spring之初体验 Spring是一个轻量级的Java Web开发框架,以IoC(Inverse of Control 控制反转)和 ...
- Solr高级查询Facet
一.什么是facet solr种以导航为目的的查询结果成为facet,在用户查询的结果上根据分类增加了count信息,然后用户根据count信息做进一步搜索. facet主要用于导航实现渐进式精确搜索 ...
- C# 自定义控件VS用户控件
1 自定义控件与用户控件区别 WinForm中, 用户控件(User Control):继承自 UserControl,主要用于开发 Container 控件,Container控件可以添加其他Con ...
- APP多版本共存,服务端如何兼容?
做过APP产品的技术人员都知道,APP应用属于一种C/S架构的,所以在做多版本兼容,升级等处理则比较麻烦,不像web应用那么容易.下面将带大家分析几种常见的情况和应对方式: 小改动或者新加功能的 这种 ...
- VC中的MFC到底是什么?
1. 微软基础类库(英语:Microsoft Foundation Classes,简称MFC)是一个微软公司提供的类库(class libraries),以C++类的形式封装了Windows API ...
- BPM始终服务于人,落脚于人
数字经济时代下,云计算.大数据.移动互联已经成为当下企业必须采取的武装力量.随着互联网+.中国制造2025.工业4.0等国家战略的引导与支持,无数的企业在这场数字化浪潮中使尽浑身解数,想要抓住机遇奋力 ...
- H3 BPM社区:流程开发者的学习交流平台
企业上市有上市流程,融资扩充有融资流程,项目招投标有招投标流程,部门领导选拔有晋升流程,员工请假休假有请假流程,早起上班梳洗有符合自己习惯的流程--生活处处是流程,流程无处不在.但从信息化建设来说,企 ...
- Android 死锁和重入锁
死锁的定义: 1.一般的死锁 一般的死锁是指多个线程的执行必须同时拥有多个资源,由于不同的线程需要的资源被不同的线程占用,最终导致僵持的状态,这就是一般死锁的定义. package com.cxt.t ...
- js动态加载css和js
之前写了一个工具类点此链接里面含有这段代码,感觉用处挺多,特意提出来 var loadUtil = { /* * 方法说明:[动态加载js文件css文件] * 使用方法:loadUtil.loadjs ...