日志记录功能在开发中很常用,可以记录程序运行的细节,也可以记录用户的行为。在之前开发时我一般都是用自己写的小工具来记录日志,输出目标包含控制台、文本文件、数据库,一般都是创建全局的Logger,在需要记录日志的地方调用相应的Logger输出至相应目标。遇到输出目标多了有时候也感觉挺麻烦的,不过也还能接受。开始学习.NetCore后接触到了日志记录框架(Logging组件),虽然完全可以用之前的方式记录日志,不过应该使用更通用的方式,把日志记录和具体的输出目标解耦。所以学习了.NetCore中Logging组件,并尝试实现了自定义的LoggerProvider,以及在.NetCore的Logging框架中使用现有完善的第三方日志记录工具NLog。写一篇博客作为学习记录,同时也希望对有这方面需求的园友有所帮助。
正如在上面部分写到的那样,当日志输出的目标多起来后,写日志就会变得麻烦。仔细想一下,日志输出这个动作是不变的,变的只是不同的输出目标(控制台、文本文件、数据库等),所以可以把日志记录这个动作抽象出来,日志记录器包含多个可输出目标,当我们调用Log方法写日志时,由Log方法依次调用Logger中的XxxLogger,把日志写到具体的目标上。过程如下图所示:
那么如何创建出这样的一个Logger呢,我们可以创建一个工厂叫LoggerFactory用来生产Logger,Logger中包还含了ConsoleLogger、FileLogger等,这些XxxLogger可以通过XxxLoggerProvider创建。进一步的,可以把Logger、LoggerFactory和LoggerProvider的行为抽象为接口ILogger、ILoggerFactory、ILoggerProvider。
其中:
ILogger中的Log()方法可以记录日志;
ILoggerProvider可以创建ILogger,用于向特定的目标写入日志;
ILoggerFactory可以添加多个IloggerProvider,并可以创建我们最终使用的ILogger;
下图为使用LoggerFactory中使用AddProvider方法添加ILoggerProvider:
下图为LoggerFactory中使用CreateLogger方法创建Logger:
下图为Logger的构造函数,使用传入的LoggerFactory中的providers,依次调用其中的ILoggerProvider来创建XxxLogger。
这里需要特别说明一下,ILoggerFactory和ILoggerProvider都产生ILogger,看上去让人迷惑,但实际上这两种ILogger的实现细节是不一样的,不同的实现中Log()方法的意义不同。
对于ILoggerFactory产生的是Logger类型(也就是我们最终使用的Logger),其Log()方法是依次调用Logger中包含的_loggers数组中的ILogger。
而ILoggerProvider产生的为各类不同的XxxLogger(也就是上面说的Logger中的_loggers数组包含的如ConsoleLogger、DebugLogger),其Log()方法是把日志写到具体的目标上去。下图为ConsoleLogger的Log()方法:
在有时候我们可能不希望某些日志被写入到所有的目标上。例如只想把某些特定的日志写入数据库。这时可以在XxxdLoggerProvider构造函数中传入
形式的委托,当返回true时写入日志,返回false则不写入日志。
此外针对不同的LoggerProvider有不同的配置方式,这里就不一一说明了。
前面我们看到了,Logger用name来标识其唯一性。在日志记录时我们很多情况下都希望记录日志产生时所在的命名空间和类型,因此使用完整的类型名称来作为Logger的name既保证了唯一性又记录了日志产生时所在的命名空间和类型是一个很好的选择。当创建Logger<T>对象时,实际上就是创建了一个用T的完整类型名称作为name的Logger并进行了包装,把Logger<T>的Log方法原封不动传入了创建的Logger的Log方法。
这样一来像NLog这样基于name的路由也很容易集成了。
ILoggerFactory默认就已经被添加到IServiceCollection容器中了,我们只需要添加需要的ILoggerProvider即可。为了让代码更简洁更具备自解释的能力,Logging组件还给ILoggerFactory添加了扩展方法,例如只要使用以下代码
就可以完成ConsoleLoggerProvider和DebugLoggerProvider的添加。
此外对Logger复杂的Log方法也进行了封装(LogTrace、LogDebug、LogError等等),以满足不同需求。
在使用Logger时可以通过依赖注入的方式获取Logger,可以有两种方法获取:
以及
这两种方法没有本质区别,如下图所示CreateLogger<T>方法也是调用Logger<T>构造函数来创建Logger<T>的。
所以只要根据喜好选择就行。
.NetCore的Logging组件提供了日志记录的框架,只要实现了ILoggerProvider接口的日志记录工具都可以集成到Logger中,这极大方便了成熟的第三方日志记录工具的集成。通过Logging组件,把日志记录逻辑和具体的记录行为解耦了,可以任意更换日志记录工具而不需要修改日志记录逻辑,同样的,只要实现了框架的接口,不同日志记录工具也可以混用。所以虽然.NetCore本身只实现了Console、Debug等几个有限的Logger,但借助于丰富的第三方日志记录工具,我们有非常多的选择。即使需求极其奇葩,只要实现框架中的接口,我们很容易集成自己写的日志记录工具。下一篇将以NLog为例说一下第三方日志记录工具的集成,此外还将编写和集成一个自己写的Logger。
更多内容欢迎访问我的博客:http://www.durow.vip