记录各种级别的日志是所有应用不可或缺的功能。关于日志记录的实现,我们有太多第三方框架可供选择,比如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. Android日志服务 记录日志

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

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

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

  8. 【Log4j】分包,分等级记录日志信息

    在开发中我们经常会将不同包下的日志信息在不同的地方输出,以便于以后出问题能够直接在对应的文件中找到对应的信息! 例如:在spring+SpringMVC+mybatis的框架中,我们经常会将sprin ...

  9. asp.net core 集成 log4net 日志框架

    asp.net core 集成 log4net 日志框架 Intro 在 asp.net core 中有些日志我们可能想输出到数据库或文件或elasticsearch等,如果不自己去实现一个 Logg ...

随机推荐

  1. 使用自定义签名的https的ssl安全问题解决和metro的webservice调用

    最近一直在忙新的项目,每天加班到8点多,都没来写博客了.新的项目遇到了很多问题,现在趁着突然停电来记录下调用https的问题吧. 我们服务主要是,我们调用数据源数据,并且再提供接口供外部数据调用. 我 ...

  2. [Asp.net 开发系列之SignalR篇]专题一:Asp.net SignalR快速入门

    一.前言 之前半年时间感觉自己有点浮躁,导致停顿了半年多的时间没有更新博客,今天重新开始记录博文,希望自己可以找回初心,继续沉淀.由于最近做的项目中用到SignalR技术,所以打算总结下Asp.net ...

  3. 1编写一个Java程序,计算半径为3.0的圆周长和面积并输出结果。2编写一个Java项目,定义包,在包下定义包含main方法的类。

  4. Tomcat详解

    解压缩下载的Tomcat压缩包,呈现的目录结构如下. bin:目录存放一些启动和关闭Tomcat的可执行程序和相关内容.conf:存放关于Tomcat服务器的全局配置.lib:目录存放Tomcat运行 ...

  5. opencv保存选择图像中的区域(二)

    /* * ===================================================================================== * * Filen ...

  6. web浏览器中javascript

    1.异步载入一个js代码function loadasync(url) { var head = document.getElementsByTagName("head")[0]; ...

  7. C#编程实践–产假方案优化版

    前言 既然作为一个踏踏实实学习技术的人,就要有一颗谦卑.虚心和追求卓越的心,我不能一次就写出很完美的代码,但我相信,踏踏实实一步一步的优化,代码就可以变得趋近完美,至少在某一个特定场景下相对完美,这和 ...

  8. sencha touch视频教程

    链接地址:http://v.youku.com/v_show/id_XOTI1MDg1ODQ4.html

  9. 庞玉栋:浅谈seo优化对于网站建设的重要性

    根据最近做SEO优化经验而写  写的也都是我的方法 大神勿喷 SEO:英文Search Engine Optimization缩写而来, 中文意译为搜索引擎优化 如果你连个网站都没有那就点这里:如何拥 ...

  10. dm-verity

    一.Device Mapper: dm-verity是内核子系统的Device Mapper中的一个子模块,所以在介绍dm-verity之前先要介绍一下Device  Mapper的基础知识. Dev ...