.NET Core的日志[1]:采用统一的模式记录日志

记录各种级别的日志是所有应用不可或缺的功能。关于日志记录的实现,我们有太多第三方框架可供选择,比如Log4Net、NLog、Loggr和Serilog
等,当然我们还可以选择微软原生的诊断框架(相关API定义在命名空间“System.Diagnostics”中)实现对日志的记录。.NET
Core提供了独立的日志模型使我们可以采用统一的API来完成针对日志记录的编程,我们同时也可以利用其扩展点对这个模型进行定制,比如可以将上述这些成熟的日志框架整合到我们的应用中。
[ 本文已经同步到《ASP.NET Core框架揭秘》之中]

目录
一、日志模型三要素
二、将日志写入不同的目的地
三、采用依赖注入编程模式创建Logger
四、根据等级过滤日志消息

一、日志模型三要素

日志记录编程主要会涉及到三个核心对象,它们分别是Logger、LoggerFactory和LoggerProvider,这三个对象同时也是.NET

Core日志模型中的核心对象,并通过相应的接口(ILogger、ILoggerFactory和ILoggerProvider)来表示。对于日志模型的这个三个核心对象之间具有如下图所示的关系,我们不难看出,LoggerFactory和LoggerProvider都是Logger的创建者, 而Loggerrovider却注册到LoggerFactory。单单从这个简单的描述来看,我想很多人会觉得这个三个对象之间的关系很“混乱”,混乱的关系主要体现在Logger具有两个不同的创建者。

LoggerProvider和LoggerFactory创建的其实是不同的Logger。LoggerProvider创建的Logger提供真正的日志写入功能,即它的作用就是将提供的日志消息写到对应的目的地(比如文件、数据库等)。LoggerFactory创建的实际上一个“组合式”的Logger,换句话说,这个Logger实际上是对一组Logger的封装,它自身并不提供真正的日志写入功能,而是委托这组内部封装的Logger来写日志。

一个LoggerFactory对象上可以注册多个LoggerProvider对象。在进行日志编程的时候,我们会利用LoggerFactory对象创建Logger来写日志,而这个Logger对象内部封装的Logger则通过注册到LoggerFactory上的这些LoggerProvider来提供。如果我们将上图1所示的关系采用下图的形式来表示,日日志模型中这三个核心要素之间的关系就显得很清楚了。

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

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

我们创建一个空的控制台应用,并在其project.json文件中添加如下四个NuGet包的依赖。其中默认使用的LoggerFactory和由它创建的Logger定义在“Microsoft.Extensions.Logging”这个NuGet包中。而上述的这两个LoggerProvider类型(ConsoleLoggerProvider和DebugLoggerProvider)分别定义在其余两个NuGet包(“Microsoft.Extensions.Logging.Console”和“Microsoft.Extensions.Logging.Debug”)中。除此之外,由于.NET

Core在默认情况下并不支持中文编码,我们不得不程序启动的时候显式注册一个支持中文编码的EncodingProvider,后者定义在NuGet包
“System.Text.Encoding.CodePages”之中,所以我们需要添加这个这NuGet包的依赖。

   1: {
   2:   ...
   3:   "dependencies": {
   4:     ...
   5:     "Microsoft.Extensions.Logging"             : "1.0.0",
   6:     "Microsoft.Extensions.Logging.Console"     : "1.0.0",
   7:     "Microsoft.Extensions.Logging.Debug"       : "1.0.0",
   8:     "System.Text.Encoding.CodePages"           : "4.0.1"
   9:   },
  10:   

日志记录通过如下一段程序来完成。如下面的代码片段所示,我们首先创建一个LoggerFactory对象,并先后通过调用AddProvider方法将一个ConsoleLoggerProvider对象和一个DebugLoggerProvider对象注册到它之上。创建这两个LoggerProvider所调用的构造函数具有一个Func<string,
LogLevel,
bool>类型的参数,该委托对象的两个输入参数分别代表日志消息的类型和等级,布尔类型的返回值决定了创建的Logger是否真的会写入给定的日志消息。由于我们传入的委托对象总是返回True,意味着提供的所有日志均会被这两个LoggerProvider创建的Logger对象写入对应的目的地。

   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(nameof(Program));
  14:  
  15:         int eventId = 3721;
  16:  
  17:         logger.LogInformation(eventId, "升级到最新.NET Core版本({version})", "1.0.0");
  18:         logger.LogWarning(eventId, "并发量接近上限({maximum}) ", 200);
  19:         logger.LogError(eventId, "数据库连接失败(数据库:{Database},用户名:{User})", "TestDb", "sa");
  20:  
  21:     }
  22: }

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

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

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

上面这个实例演示了日志记录采用的基本编程模式:首先创建或者获取一个LoggerFactory并根据需要注册相应的LoggerProvider,然后利用LoggerFactory创建的Logger来记录日志。我们可以直接调用AddProvider方法将指定的LoggerProvider注册到某个LoggerFactory对象上,除此之外,绝大部分LoggerFactory都具有相应的扩展方法使我们可以采用更加简洁的代码来完成针对它们的注册。比如在如下所示的代码片断中,我们可以直接调用针对ILoggerFactory接口的扩展方法AddConsole和AddDebug分别完成针对ConsoleLoggerProvider和DebugLoggerProvider的注册。

   1: ILogger logger = new LoggerFactory()
   2:     .AddConsole()
   3:     .AddDebug()
   4:     .CreateLogger(nameof(Program));

三、采用依赖注入编程模式创建Logger

在我们演示的实例中,我们直接调用构造函数创建了一个LoggerFactory并利用它来创建用于记录日志的Logger,但是在一个ASP.NET

Core应用中,我们总是依赖注入的方式来获取这个LoggerFactory对象。为了演示针对依赖注入的LoggerFactory获取方式,我们首先需要作的是在project.json文件中按照如下的方式添加针对“Microsoft.Extensions.DependencyInjection”这个NuGet包的依赖。

   1: {
   2:   "dependencies": {
   3:     ...
   4:     ""    : "1.0.0",
   5:     "Microsoft.Extensions.Logging"                : "1.0.0",
   6:     "Microsoft.Extensions.Logging.Console"        : "1.0.0",
   7:     "Microsoft.Extensions.Logging.Debug"          : "1.0.0",
   8:   },
   9:   ...
  10: }

所谓采用依赖注入的方式得到用于注册LoggerProvider和创建Logger的LoggerFactory,本质上就是采用调用ServiceProvider的GetService方法得到这个对象。如果希望ServiceProvider能够指定的类型(ILoggerFactory接口)得到我们所需的LoggerFactory,在这之前必须在创建ServiceProvider的ServiceCollection上作相应的服务注册。针对LoggerFactory的注册可以通过调用针对IServiceCollection接口的扩展方法AddLogging来完成。对于我们演示实例中使用的Logger对象,可以利用以依赖注入形式获取的LoggerFactory来创建,如下所示的代码片断体现了这样的编程方式。

   1: ILogger logger = new ServiceCollection()
   2:     .AddLogging()
   3:     .BuildServiceProvider()
   4:     .GetService<ILoggerFactory>()
   5:     .AddConsole()
   6:     .AddDebug()
   7:     .CreateLogger(nameof(Program));

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

由于同一个LoggerFactory上可以注册多个LoggerProvider,所以当我们利用LoggerFactory创建出相应的Logger用它来写入某条日志消息的时候,这条消息实际上会分发给由LoggerProvider提供的所有Logger。其实在很多情况下,我们并不希望每个Logger都去写入分发给它的每条日志消息,而是希望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 = (category, level) => level >= LogLevel.Warning;
   2:  
   3: ILoggerFactory loggerFactory = new LoggerFactory();
   4: loggerFactory.AddProvider(new ConsoleLoggerProvider(filter, false));
   5: loggerFactory.AddProvider(new DebugLoggerProvider(filter));
   6: ILogger logger = loggerFactory.CreateLogger(nameof(Program));

针对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(nameof(Program));

或者

   1: ILogger logger = new ServiceCollection()
   2:     .AddLogging()
   3:     .BuildServiceProvider()
   4:     .GetService<ILoggerFactory>()
   5:     .AddConsole(LogLevel.Warning)
   6:     .AddDebug(LogLevel.Warning)
   7:     .CreateLogger(nameof(Program));

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

 



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

作者:蒋金楠
微信公众账号:大内老A
微博:www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

原文链接

时间: 2024-10-30 21:27:22

.NET Core的日志[1]:采用统一的模式记录日志的相关文章

.NET Core的日志[3]:将日志写入Debug窗口

定义在NuGet包"Microsoft.Extensions.Logging.Debug"中的DebugLogger会直接调用Debug的WriteLine方法来写入分发给它的日志消息.如果需要使用DebugLogger来写日志,我们需要将它的提供者DebugLoggerProvider注册到LoggerFactory上.由于定义在Debug类型中的所有方法都是针对Debug编译模式的,所以在只有针对Debug模式编译的应用中使用DebugLogger才有意义.这里将的"De

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

从微软推出第一个版本的.NET Framework的时候,就在"System.Diagnostics"命名空间中提供了Debug和Trace两个类帮助我们完成针对调试和跟踪信息的日志记录.在.NET Framework 2.0中,微软引入了TraceSource并对跟踪日志系统进行了优化,优化后的跟踪日志系统在.NET Core中又经过了相应的简化..NET Core的日志模型借助TraceSourceLoggerProvider实现对TraceSource的整合,在正式介绍这个Log

.NET Core的日志[2]:将日志输出到控制台

对于一个控制台应用,比如采用控制台应用作为宿主的ASP.NET Core应用,我们可以将记录的日志直接输出到控制台上.针对控制台的Logger是一个类型为ConsoleLogger的对象,ConsoleLogger对应的LoggerProvider类型为ConsoleLoggerProvider,这两个类型都定义在 NuGet包"Microsoft.Extensions.Logging.Console"之中. 本文已经同步到<ASP.NET Core框架揭秘>之中] 目录

.NET Core的日志[4]:将日志写入EventLog

面向Windows的编程人员应该不会对Event Log感到陌生,以至于很多人提到日志,首先想到的就是EventLog.EventLog不仅仅记录了Windows系统自身针对各种事件的日志,我们的应用也可以利用提供的API将日志消息写到EventLog中.与EventLog相关的API都定义在System.Diagnostics.EventLog这个类型中,我们不仅仅可以利用它读取.写入和删除日志,还可以使用它来创建和删除Event Source..NET Core的日志模型利用EventLog

微软呼吁网络搜索采用统一隐私保护标准

腾讯科技讯 7月24日消息,微软日前表示它将采取新的步骤在网络搜索领域和在线广告领域保护消费者的隐私,并且呼吁互联网行业支持它. 微软称,公众对在线广告行业最近的整合感到担心.美国政府管理部门要求互联网行业采取全面的而不是零散的措施来保护隐私.微软的这一举措正是对这些情况做出的反应. 微软首席隐私官员Peter Cullen在采访中称,我们认为,现在是展开整个行业范围内的 对话的时候.当前保护隐私的修补工作和公司如何解释隐私的问题确实令消费者感到困惑. 微软称,它将在其"Live Search&

ASP.NET Core的路由[1]:注册URL模式与HttpHandler的映射关系

ASP.NET Core的路由是通过一个类型为RouterMiddleware的中间件来实现的.如果我们将最终处理HTTP请求的组件称为HttpHandler,那么RouterMiddleware中间件的意义在于实现请求路径与对应HttpHandler之间的映射关系.对于传递给RouterMiddleware中间件的每一个请求,它会通过分析请求URL的模式并选择并提取对应的HttpHandler来处理该请求.除此之外,请求的URL还会携带相应参数,该中间件在进行路由解析过程中还会根据生成相应的路

IE8采用多兼容模式正常显示网页

IE8将具有多种兼容模式.IE平台建筑师Chris Wilson在博客中写到,IE平台的工作是同时提供互操作性(网页在不同浏览器的均能正常工作)和向后兼容性(兼容之前版本的IE浏览器).若是希望IE8继续与目前的数十亿网页兼容,同时也满足使未来数十亿网页开发更加容易的目标,IE8将采用不同的模式显示网页. 他在博客中表示,在过去的6个主要版本的IE浏览器开发中,均采用"不打破现有WEB(规则)"为准则,在IE6中,他们使用DOCTYPE开关去切换不同的"模式",以保

IBM挺进云计算 自家内采用私有云模式

本文讲的是IBM挺进云计算 自家内采用私有云模式,[IT168 资讯]据IBM公司首席信息官Mark Hennessy的说法,IBM目前大刀阔斧的进军新兴私有云市场并不仅仅是针对IBM用户的,蓝色巨人也正在着手在自己的防火墙内部建立几种私有云服务. 自从2007年6月开始担任IBM首席信息官的Hennessy表示,目前他还们没有为在关键领域中大规模接受和应用诸如亚马逊或者Salesforce.com等公有云服务做好准备,不过私有云将帮助IBM公司以更加有效的方式向员工交付研发工具. Hennes

解析云计算与虚拟应用采用的打印模式

虚拟应用技术及私有云计算为企业IT管理人员提供了"集中管控,远程应用"的新方法,它所实施的网络架构几乎支持当前所有的应用程序,轻松实现远程接入的应用模式. 相对于云数据.云计算的虚拟世界,打印却是实实在在物理输出,一直要拿到手里才算完成.虚拟应用能否实在的打印,关乎这个虚拟应用是否完整.为了得到服务端虚拟应用的打印完全像本地应用一样的感觉,需要保证并兼顾通用性和效率. 目前,远程接入及虚拟应用产品大多采用了以下打印模式: 1. 映射打印.基本原理是将客户端打印机映射到服务器,打印时选择