记录各种级别的日志是所有应用不可或缺的功能。关于日志记录的实现,我们有太多第三方框架可供选择,比如Log4Net、NLog、Loggr和Serilog 等,当然我们还可以选择微软原生的诊断机制(相关API定义在命名空间“System.Diagnostics”中)实现对日志的记录。.NET Core提供了独立的日志模型使我们可以采用统一的API来完成针对日志记录的编程,我们同时也可以利用其扩展点对这个模型进行定制,比如可以将上述这些成熟的日志框架整合到我们的应用中。本系列文章旨在从设计和实现的角度对.NET Core提供的日志模型进行深入剖析,不过在这之前我们必须对由它提供的日志记录编程模式具有一个大体的认识,接下来我们会采用实例的形式来演示如何相应等级的日志并最终将其写入到我们期望的目的地中。

目录
一、日志模型三要素
二、将日志写入不同的目的地
三、依赖注入
四、根据等级过滤日志消息
五、利用TraceSource记录日志
    直接利用TraceSource记录追踪日志
    利用TraceSourceLoggerProvider记录追踪日志

一、日志模型三要素

日志记录编程主要会涉及到三个核心对象,它们分别是Logger、LoggerFactory和LoggerProvider,这三个对象同时也是.NET Core日志模型中的核心对象,并通过相应的接口(ILogger、ILoggerFactory和ILoggerProvider)来体现。右图所示的UML揭示了日志模型的这三个核心对象之间的关系。

在进行日志记录编程时,我们直接调用Logger对象相应的方法写入日志,LoggerFactory是创建Logger对象的工厂。由LoggerFactory创建的Logger并不真正实现对日志的写入操作,真正将日志写入相应目的地的Logger是通过相应的LoggerProvider提供的,前者是对后者的封装,它将日志记录请求委托给后者来完成。

具体来说,在通过LoggerFactory创建Logger之前,我们会根据需求将一个或者多个LoggerProvider注册到LoggerFactory之上。比如,如果我们需要将日志记录到EventLog中,我们会注册一个EventLogLoggerProvider,后者会提供一个EventLogLogger对象来实现针对EventLog的日志记录。当我们利用LoggerFactory创建Logger对象时,它会利用注册其上的所有LoggerProvider创建一组具有真正日志写入功能的Logger对象,并采用“组合(Composition)”模式利用这个Logger列表创建并返回一个Logger对象。

综上所述,LoggerFactory创建的Logger仅仅是一个“壳”,在它内部封装了一个或者多个具有真正日志写入功能的Logger对象。当我们调用前者实施日志记录操作时,它会遍历被封装的Logger对象列表,并委托它们将日志写入到相应的目的地。

二、将日志写入不同的目的地

接下来我们通过一个简单的实例来演示如何将具有不同等级的日志写入两种不同的目的地,其中一种是直接将格式化的日志消息输出到当前控制台,另一种则是将日志写入Debug输出窗口(相当于直接调用Debug.WriteLine方法),针对这两种日志目的地的Logger分别通过ConsoleLoggerProvider和DebugLoggerProvider来提供。

我们创建一个空的.NET Core控制台应用,并在其project.json文件中添加如下三个NuGet包的依赖,其中默认使用的LoggerFactory和由它创建的Logger定义在“Microsoft.Extensions.Logging”之中,而上述的ConsoleLoggerProvider和DebugLoggerProvider则分别由其余两个NuGet包来提供。由于在默认情况下 ,.NET Core并不支持中文编码,我们需要显式注册一个名为的针对相应的EncodingProvider,后者定义在NuGet包 “System.Text.Encoding.CodePages”之中,所以我们需要添加这个这NuGet包的依赖。

   1: {

   2:   

   3:   "dependencies": {

   4:     ...

   5:     "Microsoft.Extensions.Logging"            : "1.0.0-rc2-final",

   6:     "Microsoft.Extensions.Logging.Console"    : "1.0.0-rc2-final",

   7:     "Microsoft.Extensions.Logging.Debug"      : "1.0.0-rc2-final",

   8:  

   9:     "System.Text.Encoding.CodePages"          : "4.0.1-rc2-24027"

  10:   },

  11:   ...

  12: }

我们在入口的Main方法中编写如下一段程序。我们首先创建一个LoggerFactory对象,并先后通过调用AddProvider方法在它上面注册一个ConsoleLoggerProvider对象和DebugLoggerProvider对象。创建它们调用的构造函数具有一个Func<string, LogLevel, bool>类型的参数旨在对日志消息进行写入前过滤(针对日子类型和等级),由于我们传入的委托对象总是返回True,意味着提供的所有日志均会被写入。

   1: public class Program

   2: {

   3:     public static void Main(string[] args)

   4:     {

   5:        //注册EncodingProvider实现对中文编码的支持

   6:         Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

   7:  

   8:         Func<string, LogLevel, bool> filter = (category, level) => true;

   9:  

  10:         ILoggerFactory loggerFactory = new LoggerFactory();

  11:         loggerFactory.AddProvider(new ConsoleLoggerProvider(filter,false));

  12:         loggerFactory.AddProvider(new DebugLoggerProvider(filter));

  13:         ILogger logger = loggerFactory.CreateLogger("App");

  14:  

  15:         int eventId = 3721;

  16:  

  17:         logger.LogInformation(eventId, "升级到最新版本({version})", "1.0.0.rc2");

  18:         logger.LogWarning(eventId, "并发量接近上限({maximum}) ", 200);

  19:         logger.LogError(eventId, "数据库连接失败(数据库:{Database},用户名:{User})", "TestDb", "sa");

  20:  

  21:         Console.Read();

  22:     }

  23: }

我们通过指定日志类型(“App”)调用LoggerFactory对象的CreateLogger方法创建一个Logger对象,并先后调用其LogInformation、LogWarning和LogError方法记录三条日志,这三个方法决定了写入日志的等级(Information、Warning和Error)。我们在调用这三个方法的时候指定了一个表示日志记录事件ID的整数(3721),以及具有占位符(“{version}”、“{maximum}”、“{Database}”和“{User}”)的消息模板和替换这些占位符的参数。

由于ConsoleLoggerProvider被事先注册到创建Logger的LoggerFactory上,所以当我们执行这个实例程序之后,三条日志消息会直接按照如下的形式打印到控制台上。我们可以看出格式化的日志消息不仅仅包含我们指定的消息内容,日志的等级、类型和事件ID同样包含其中。

   1: info: App[3721]

   2:       升级到最新版本(1.0.0.rc2)

   3: warn: App[3721]

   4:       并发量接近上限(200)

   5: fail: App[3721]

   6:       数据库连接失败(数据库:TestDb,用户名:sa)

由于LoggerFactory上还注册了另一个DebugLoggerProvider对象,由它创建的Logger会直接调用Debug.WriteLine方法写入格式化的日志消息。所以当我们以Debug模式编译并执行该程序时,Visual Studio的输出窗口会以右图所示的形式呈现出格式化的日志消息。

上面这个实例演示了日志记录采用的基本变成模式,即创建/获取LoggerFactory并注册相应的LoggerProvider,然后利用LoggerFactory创建Logger,并最终利用Logger记录日志。LoggerProvider的注册除了可以直接调用LoggerFactory的AddProvider方法来完成之外,对于预定义的LoggerProvider,我们还可以调用相应的扩展方法来将它们注册到指定的LoggerFactory上。比如在如下所示的代码片断中,我们直接调用针对ILoggerFactory接口的扩展方法AddConsole和AddDebug分别注册一个ConsoleLoggerProvider和DebugLoggerProvider。

   1: ILogger logger = new LoggerFactory()

   2:     .AddConsole()

   3:     .AddDebug()

   4:     .CreateLogger("App");

三、依赖注入

在我们演示的实例中,我们直接调用构造函数创建了一个LoggerFactory并利用它来创建用于记录日志的Logger,在一个.NET Core应用中,LoggerFactory会以依赖注入的方式注册到ServiceProvider之中。如果我们需要采用依赖注入的方式来获取注册的LoggerFactory,我们需要在project.json文件中添加针对“Microsoft.Extensions.DependencyInjection”这个NuGet包的依赖。

   1: {

   2:   "dependencies": {

   3:     ...

   4:     "Microsoft.Extensions.DependencyInjection"    : "1.0.0-rc2-final",

   5:     "Microsoft.Extensions.Logging"                : "1.0.0-rc2-final",

   6:     "Microsoft.Extensions.Logging.Console"        : "1.0.0-rc2-final",

   7:     "Microsoft.Extensions.Logging.Debug"          : "1.0.0-rc2-final",

   8:   },

   9:   ...

  10: }

针对LoggerFactory的注册可以通过调用针对IServiceCollection接口的扩展方法AddLogging来完成。当我们调用这个方法的时候,它会创建一个LoggerFactory对象并以Singleton模式注册到指定的ServiceCollection之上。对于我们演示实例中使用的Logger对象,可以利用以依赖注入形式获取的LoggerFactory来创建,如下所示的代码片断体现了这样的编程方式。

   1: ILogger logger = new ServiceCollection()

   2:     .AddLogging()

   3:     .BuildServiceProvider()

   4:     .GetService<ILoggerFactory>()

   5:     .AddConsole()

   6:     .AddDebug()

   7:     .CreateLogger("App");

四、根据等级过滤日志消息

对于通过某个LoggerProvider提供的Logger,它并总是会将提供给它的日志消息写入对应的目的地,它可以根据提供的过滤条件忽略无需写入的日志消息,针对日志等级是我们普遍采用的日志过滤策略。日志等级通过具有如下定义的枚举LogLevel来表示,枚举项的值决定了等级的高低,值越大,等级越高;等级越高,越需要记录。

   1: public enum LogLevel

   2: {

   3:     Trace         = 0,

   4:     Debug         = 1,

   5:     Information   = 2,

   6:     Warning       = 3,

   7:     Error         = 4,

   8:     Critical      = 5,

   9:     None          = 6

  10: }

在前面介绍ConsoleLoggerProvider和DebugLoggerProvider的时候,我们提到可以在调用构造函数时可以传入一个Func<string, LogLevel, bool>类型的参数来指定日志过滤条件。对于我们实例中写入的三条日志,它们的等级由低到高分别是Information、Warning和Error,如果我们选择只写入等级高于或等于Warning的日志,可以采用如下的方式来创建对应的Logger。

   1: Func<string, LogLevel, bool> filter = 

   2:     (category, level) => level >= LogLevel.Warning;

   3:  

   4: ILoggerFactory loggerFactory = new LoggerFactory();

   5: loggerFactory.AddProvider(new ConsoleLoggerProvider(filter, false));

   6: loggerFactory.AddProvider(new DebugLoggerProvider(filter));

   7: ILogger logger = loggerFactory.CreateLogger("App");

针对ILoggerFactory接口的扩展方法AddConsole和AddDebug同样提供的相应的重载使我们可以通过传入的Func<string, LogLevel, bool>类型的参数来提供日志过滤条件。除此之外,我们还可以直接指定一个类型为LogLevel的参数来指定过滤日志采用的最低等级。我们演示实例中的使用的Logger可以按照如下两种方式来创建。

   1: ILogger logger = new ServiceCollection()

   2:     .AddLogging()

   3:     .BuildServiceProvider()

   4:     .GetService<ILoggerFactory>()

   5:  

   6:     .AddConsole((c,l)=>l>= LogLevel.Warning)

   7:     .AddDebug((c, l) => l >= LogLevel.Warning)

   8:     .CreateLogger("App");

或者

   1: ILogger logger = new ServiceCollection()

   2:     .AddLogging()

   3:     .BuildServiceProvider()

   4:     .GetService<ILoggerFactory>()

   5:     .AddConsole(LogLevel.Warning)

   6:     .AddDebug(LogLevel.Warning)

   7:     .CreateLogger("App");

由于注册到LoggerFactory上的ConsoleLoggerProvider和DebugLoggerProvider都采用了上述的日志过滤条件,所有由它们提供Logger都只会写入等级为Warning和Error的两条日志,至于等级为Information的那条则会自动忽略掉。所以我们的程序执行之后会在控制台上打印出如下所示的日志消息。

   1: warn: App[3721]

   2:       并发量接近上限(200)

   3: fail: App[3721]

   4:       数据库连接失败(数据库:TestDb,用户名:sa)

五、利用TraceSource记录日志

从微软推出第一个版本的.NET Framework的时候,就在“System.Diagnostics”命名空间中提供了Debug和Trace两个类帮助我们完成针对调试和追踪信息的日志记录。在.NET Framework 2.0种,增强的追踪日志功能实现在新引入的TraceSource类型中,并成为我们的首选。.NET Core的日志模型借助TraceSourceLoggerProvider实现对TraceSource的整合。

直接利用TraceSource记录追踪日志

.NET Core 中的TraceSource以及相关类型定义在NuGet包“System.Diagnostics.TraceSource”,如果我们需要直接使用TraceSource来记录日志,应用所在的Project.json文件中需要按照如下的方式添加针对这个NuGet包的依赖。

   1: {

   2:   "dependencies": {

   3:     ...

   4:     "System.Diagnostics.TraceSource": "4.0.0-rc2-24027"    

   5:   },

   6: }

不论采用Debug和Trace还是TraceSource,追踪日志最终都是通过注册的TraceListener被写入相应的目的地。在“System.Diagnostics”命名空间中提供了若干预定义的TraceListener,我们也可以自由地创建自定义的TraceListener。如下面的代码片断所示,我们通过继承抽象基类TraceListener自定义了一个ConsoleTranceListener类,它通过重写的Write和WriteLine方法将格式化的追踪消息输出到当前控制台。

   1: public class ConsoleTraceListener : TraceListener

   2: {

   3:     public override void Write(string message)

   4:     {

   5:         Console.Write(message);

   6:     }

   7:  

   8:     public override void WriteLine(string message)

   9:     {

  10:         Console.WriteLine(message);

  11:     }

  12: }

我们可以直接利用TraceSource记录上面实例演示的三条日志。如下面的代码片断所示,我们通过指定名称(“App”)创建了一个TraceSource对象,然后在它的TraceListener列表中注册了一个ConsoleTraceListener对象。我们为这个TraceSource指定了一个开关(一个SourceSwitch对象)让它仅仅记录等级高于Warning的追踪日志。我们调用TraceSource的TraceEvent方法实现针对不同等级(Information、Warning和Error)的三条追踪日志的记录。

   1: public class Program

   2: {

   3:     public static void Main(string[] args)

   4:     {

   5:         TraceSource traceSource = new TraceSource("App");

   6:         traceSource.Listeners.Add(new ConsoleTraceListener());

   7:         traceSource.Switch = new SourceSwitch("LogWarningOrAbove", "Warning");

   8:  

   9:         int eventId = 3721;

  10:         traceSource.TraceEvent(TraceEventType.Information, eventId, "升级到最新版本({0})", "1.0.0.rc2");

  11:         traceSource.TraceEvent(TraceEventType.Warning, eventId, "并发量接近上限({0}) ", 200);

  12:         traceSource.TraceEvent(TraceEventType.Error, eventId, "数据库连接失败(数据库:{0},用户名:{1})", "TestDb", "sa");

  13:     }

  14: }

当我们执行该程序之后,满足TraceSource过滤条件的两条追踪日志(即等级分别为Warning和Error的两条追踪日志)将会通过注册的ConsoleTraceListner写入当前控制台,具体的内容如下所示。由于一个DefaultTraceListener对象会自动注册到TraceSource之上,在它的Write或者WriteLine方法中会调用Win32函数OutputDebugString或者Debugger.Log方法,所以如果我们采用Debug模式编译我们的程序,当程序运行后会在Visual Studio的输出窗口中看到这两条日志消息。

   1: App Warning: 3721 : 并发量接近上限(200) 

   2: App Error: 3721 : 数据库连接失败(数据库:TestDb,用户名:sa) 

利用TraceSourceLoggerProvider记录追踪日志

NET Core的日志模型借助TraceSourceLoggerProvider实现对TraceSource的整合。具体来说,由于TraceSourceLoggerProvider提供的Logger对象实际上是对一个TraceSource的封装,对于提供给Logger的日志消息,后者会借助注册到TraceSource上面的TraceListener来完成对日志消息的写入工作。由于TraceSourceLoggerProvider定义在NuGet包“Microsoft.Extensions.Logging.TraceSource”,我们需要按照如下的方式将针对它的依赖定义在project.json中。

   1: {

   2:   "dependencies": {

   3:     "Microsoft.Extensions.DependencyInjection"    : "1.0.0-rc2-final",

   4:     "Microsoft.Extensions.Logging"                : "1.0.0-rc2-final",

   5:     "Microsoft.Extensions.Logging.TraceSource"    : "1.0.0-rc2-final"

   6:   },

   7:   ...

   8: }

如果采用要利用日志模型标准的编程方式来记录日志,我们可以按照如下的方式来创建对应的Logger对象。如下面的代码片断所示,我们创建一个TraceSourceLoggerProvider对象并调用AddProvider方法将其注册到LoggerFactory对象上。创建TraceSourceLoggerProvider的构造函数接受两个参数,前者是一个SourceSwitch对象,用于过滤等级低于Warning的日志消息,后者则是我们自定义的ConsoleTraceListener对象。

   1: ILoggerFactory loggerFactory = new ServiceCollection()

   2:     .AddLogging()

   3:     .BuildServiceProvider()

   4:     .GetService<ILoggerFactory>();

   5:  

   6: SourceSwitch sourceSwitcher = new SourceSwitch("LogWarningOrAbove", "Warning");

   7: loggerFactory.AddProvider(new TraceSourceLoggerProvider(sourceSwitcher, new ConsoleTraceListener()));

   8:  

   9: ILogger logger = loggerFactory.CreateLogger("App");

我们可以调用针对ILoggerFactory的扩展方法AddTraceSource来实现对TraceSourceLoggerProvider的注册,该方法具有与TraceSourceLoggerProvider构造函数相同的参数列表。如下所示的代码片断通过调用这个扩展方法以更加精简的方式创建了日志记录所需的Logger对象。

   1: ILogger logger = new ServiceCollection()

   2:     .AddLogging()

   3:     .BuildServiceProvider()

   4:     .GetService<ILoggerFactory>()

   5:     .AddTraceSource(new SourceSwitch("LogWarningOrAbove", "Warning"), new ConsoleTraceListener())

   6:     .CreateLogger("App");

.NET Core下的日志(1):记录日志信息的更多相关文章

  1. .NET Core下的日志(2):日志模型详解

    NET Core的日志模型主要由三个核心对象构成,它们分别是Logger.LoggerProvider和LoggerFactory.总的来说,LoggerProvider提供一个具体的Logger对象 ...

  2. .NET Core下的日志(3):如何将日志消息输出到控制台上

    当我们利用LoggerFactory创建一个Logger对象并利用它来实现日志记录,这个过程会产生一个日志消息,日志消息的流向取决于注册到LoggerFactory之上的LoggerProvider. ...

  3. 探索ASP.Net Core 3.0系列六:ASP.NET Core 3.0新特性启动信息中的结构化日志

    前言:在本文中,我将聊聊在ASP.NET Core 3.0中细小的变化——启动时记录消息的方式进行小的更改. 现在,ASP.NET Core不再将消息直接记录到控制台,而是正确使用了logging 基 ...

  4. LINUX下查看日志信息

    Linux下grep显示多行信息标准unix/linux下的grep通过以下参数控制上下文 grep -C 5 foo file 显示file文件中匹配foo字串那行以及上下5行 例如 grep -C ...

  5. ASP.NET Core 异常处理与日志记录

    1. ASP.NET Core 异常处理与日志记录 1.1. 异常处理 1.1.1. 异常产生的原因及处理 1.1.2. ASP.NET Core中启动开发人员异常页面 1.2. 日志记录 1.2.1 ...

  6. .NET Core 中的日志与分布式链路追踪

    目录 .NET Core 中的日志与分布式链路追踪 .NET Core 中的日志 控制台输出 非侵入式日志 Microsoft.Extensions.Logging ILoggerFactory IL ...

  7. .net core Blazor+自定义日志提供器实现实时日志查看器

    场景 我们经常远程连接服务器去查看日志,比较麻烦,如果直接访问项目的某个页面就能实时查看日志就比较奈斯了,花了1天研究了下.net core 日志的原理,结合blazor实现了基本效果. 实现原理 自 ...

  8. Android日志服务 记录日志

    转: http://easion-zms.iteye.com/blog/981568 import java.io.BufferedReader; import java.io.File; impor ...

  9. Linux下重要日志文件及查看方式

    http://os.51cto.com/art/201108/282184_all.htm   1.Linux下重要日志文件介绍 /var/log/boot.log 该文件记录了系统在引导过程中发生的 ...

随机推荐

  1. shell简介

    Shell作为命令语言,它交互式地解释和执行用户输入的命令:作为程序设计语言,它定义了各种变量和参数,并提供了许多在高级语言中才具有的控制结构,包括循环和分支. shell使用的熟练程度反映了用户对U ...

  2. SQL Server-表表达式基础回顾(二十四)

    前言 从这一节开始我们开始进入表表达式章节的学习,Microsoft SQL Server支持4种类型的表表达式:派生表.公用表表达式(CTE).视图.内嵌表值函数(TVF).简短的内容,深入的理解, ...

  3. 关于Android避免按钮重复点击事件

    最近测试人员测试我们的APP的时候,喜欢快速点击某个按钮,出现一个页面出现多次,测试人员能不能禁止这样.我自己点击了几下,确实存在这个问题,也感觉用户体验不太好.于是乎后来我搜了下加一个方法放在我们U ...

  4. zookeeper源码分析之一服务端启动过程

    zookeeper简介 zookeeper是为分布式应用提供分布式协作服务的开源软件.它提供了一组简单的原子操作,分布式应用可以基于这些原子操作来实现更高层次的同步服务,配置维护,组管理和命名.zoo ...

  5. Java定时任务的常用实现

    Java的定时任务有以下几种常用的实现方式: 1)Timer 2)ScheduledThreadPoolExecutor 3)Spring中集成Cron Quartz 接下来依次介绍这几类具体实现的方 ...

  6. ecshop验证码

    <?php //仿制ecshop验证码(四位大写字母和数字.背景) //处理码值(四位大写字母和数字组成) //所有的可能的字符集合 $chars = 'ABCDEFGHIJKLMNOPQRST ...

  7. IT持续集成之质量管理

    研发工具生态 质量相关工作 一次编译产出测试包与上线包 !从源头保证版本的⼀一致性!代码质量控制! 全⽅方位的⾃自动化测试体系保证! 提测冒烟效率! 全⾃自动上线流程杜绝⼈人⼯工犯错! 生产环境应⽤用 ...

  8. Windos环境用Nginx配置反向代理和负载均衡

    Windos环境用Nginx配置反向代理和负载均衡 引言:在前后端分离架构下,难免会遇到跨域问题.目前的解决方案大致有JSONP,反向代理,CORS这三种方式.JSONP兼容性良好,最大的缺点是只支持 ...

  9. Spark-shell和Spark-Submit的使用

    Spark-shell有两种使用方式: 1:直接Spark-shell 会启动一个SparkSubmit进程来模拟Spark运行环境,是一个单机版的. 2:Spark-shell --master S ...

  10. Express 教程 01 - 入门教程之经典的Hello World

    目录: 前言 一.Express?纳尼?! 二.开始前的准备工作 三.测试安装之经典的Hello World 四.使用express(1)来生成一个应用程序 五.说明 前言: 本篇文章是建立在Node ...