定义在NuGet包“Microsoft.Extensions.Logging.Debug”中的DebugLogger会直接调用Debug的WriteLine方法来写入分发给它的日志消息。如果需要使用DebugLogger来写日志,我们需要将它的提供者DebugLoggerProvider注册到LoggerFactory上。由于定义在Debug类型中的所有方法都是针对Debug编译模式的,所以在只有针对Debug模式编译的应用中使用DebugLogger才有意义。这里将的“Debug编译模式”涉及到一个叫做“条件编译”的话题。 本文已经同步到《ASP.NET Core框架揭秘》之中]

目录
一、Debug类型与条件编译
二、DebugLogger
三、DebugLoggerProvider

一、Debug类型与条件编译

DebugLogger适用于.NET Framework和.NET Core应用,我们说DebugLogger最终是通过调用Debug类型的静态方法WriteLine来写入分发给它的日志消息,但是使用的这个Debug类型在.NET Framework和.NET Core应用下其实是两个完全不同的类型。针对.NET Framework的Debug类型定义在程序集“System.dll”下,而针对.NET Core的Debug类型则承载于“System.Diagnostics.Debug”这个NuGet包中,这两个Debug方法具有不同的API定义。

这两个Debug类型针对日志的写入机制也不尽相同,针对.NET Framework的Debug类型定会利用注册到Debug.Listeners属性TraceListener来写日志,默认注册的DefaultTraceListener会通过调用Win32函数OutputDebugString将格式化的日志消息输出给Debug监视器(Debug Monitor)。对于针对针对.NET Core的Debug类型来说,它针对不同的平台具有不同的实现,针对Windows平台下日志消息依然是通过调用OutputDebugString这Win32函数来写入的。

虽然两个Debug类型在API定义和写入日志的实现都不同,但是对于被DebugLogger用来写日志的WriteLine方法来说,它们都具有如下所示的定义方式。该方法具有两个参数,分别代表写入日志的文本消息和类型。我们可以看到这个方法上标注了一个类型为ConditionalAttribute的特性,它具有一个值为“DEBUG”的参数。这个ConditionalAttribute特性就与我们所说的“条件编译”有关。

   1: public static class Debug

   2: {

   3:     [Conditional("DEBUG")]

   4:     public static void WriteLine(string message,string category);

   5: }

所谓的“条件编译”,就是说编译器在进行编译的时候会根据指定的条件来过滤参与编译的源代码,这个源代码过滤条件是在编译时指定的符号化的字符串,我们称它为“条件编译符(Conditional Compilation Symbol)”,上面代码片段中作为ConditionalAttribute特性参数的“DEBUG”就是条件编译符。如果我们使用Visual Studio作为IDE,我们可以利用它以可视化的方式来为某个的项目设置一个或者多个就是条件编译符。我们只需要右击某个项目并在弹出的上下文菜单中选择“属性(Properties)”,然后按照如下图所示方式在显示的项目属性窗口中选择“生成(Build)”选项卡。

如图8所示,我们可以定义任意字符串作为条件编译符(比如“UAT”,“SIT”)。除此之外,Visual Studio还为我们预设了“DEBUG”和“TRACE”这两个常用的条件编译符,如果需要我们只需要选择相应的复选框(“Define DEBUG/TRACE constant”)即可。我们通过这种方法设置的条件编译符最终会作为编译选项以如下的方式写入到project.json文件中,具体的配置项目为“buildOptions/define”,换句话说,我们完全可以直接编辑project.json文件的方式来定义条件编译符。

   1: {

   2:   ...

   3:   "buildOptions": {

   4:     "define": [ "DEBUG", "TRACE", "UAT, SIT" ]

   5:   }

   6: }

从某种意义来说,条件编译符实际上是为应用定义相应的“部署场景”,比如我们在上边定义的条件编译符“UAT”和“SIT”就是针对两种不同类型(用户接收测试和系统集成测试)的测试部署场景。如果我们需要编写针对具有某种部署场景的程序,可以采用预编译指令“#if/#endif”来实现。如果编译器在编译如下一段代码的时候,只有指定的条件编译符包含“DEBUG”的情况下,调用WriteDebug方法的这段代码才会参与编译,否则这段代码将直接被忽略。

   1: #if DEBUG

   2:     WriteDebug(message);

   3: #endif

完全采用预编译指令“#if/#endif”来编写针对具体某个条件编译符的代码其实是很繁琐的。如果某个方法总是针对具体某个条件编译符,我们可以直接在这样的方法上标注一个ConditionalAttribute特性,并将对应的条件编译符作为其参数即可。比如上面这个WriteDebug方法就可以采用如下的方式来定义,它可以作为一个普通的方法来调用,而无需再使用任何预编译指令。

   1: [Conditional(“DEBUG”)]

   2: public static void WriteBug(string message);

编译器在编译我们的程序的时候,如果程序中调用了某个标注了ConditionalAttribute特性的方法并且指定的条件编译符与当前不一致,针对该方法调用的代码将被自动忽略。定义在Debug类型上的WriteLine方法上就标注了这么一个ConditionalAttribute特性,指定的编译符为“DEBUG”,大家应该知道为什么DebugLogger为什么只有针对Debug模式编译生成的应用才后意义了吧。

二、DebugLogger

在了解了Debug类型和条件编译的背景知识后,我们来正式认识一下DebugLogger类型。我们采用如下一段现对简介的代码模拟了DebugLogger的定义。当我们调用构造函数创建一个DebugLogger对象的时候需要指定Logger的名称和进行日志过滤的Func<string, LogLevel, bool>对象,后者是可选的。DebugLogger调用Debug的WriteLine方法来进行日志写入体现在它的Log方法中,写入的日志消息将DebugLogger的名称作为日志类型。

   1: public class DebugLogger : ILogger

   2: {

   3:     private readonly Func<string, LogLevel, bool> _filter;

   4:     private readonly string _name;

   5:  

   6:     public DebugLogger(string name, Func<string, LogLevel, bool> filter)

   7:     {

   8:         _name = string.IsNullOrEmpty(name) ? "DebugLogger" : name;

   9:         _filter = filter?? ((cate, level) => true);

  10:     }

  11:  

  12:     public DebugLogger(string name) : this(name, null)

  13:     {}

  14:  

  15:  

  16:     public IDisposable BeginScope<TState>(TState state)

  17:     {

  18:         return NoopDisposable.Instance;

  19:     }

  20:  

  21:     public bool IsEnabled(LogLevel logLevel)

  22:     {

  23:         return Debugger.IsAttached && _filter(_name, logLevel);

  24:     }

  25:  

  26:     public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)

  27:     {

  28:         if (this.IsEnabled(logLevel))

  29:         {

  30:             string message = formatter(state, exception);

  31:             message = $"{logLevel}: {message}";

  32:             if (exception != null)

  33:             {

  34:                 message = $"{message}{Environment.NewLine}{Environment.NewLine}{exception}";

  35:             }

  36:             Debug.WriteLine(message, _name);

  37:         }

  38:     }

  39:  

  40:     private class NoopDisposable : IDisposable

  41:     {

  42:         public static DebugLogger.NoopDisposable Instance = new DebugLogger.NoopDisposable();

  43:         public void Dispose()

  44:         {}

  45:     }

  46: }

上面这段代码和体现了DebugLogger进行日志记录的一些细节特性:

  • 如果调用构造函数指定的名称为Null或者是一个空字符串,创建的DebugLogger对象将使用它的类型名(“DebugLogger”)来命名。如果作为日志过滤器的Func<string, LogLevel, bool>对象没有显式指定,意味着不需要对日志进行过滤。

  • DebugLogger并不支持日志上下文,所以它的BeginScope<TState>方法返回的NoopDisposable对象并承载任何上下文信息,这也是DebugLogger的构造函数不像ConsoleLogger一样具有一个includeScope参数的原因。
  • DebugLogger的IsEanbled方法不仅仅利用构造时指定的作为日志过滤器的Func<string, LogLevel, bool>对象来决定是否真正写入日志,还需要考虑调试器是否附加到当前进程(Debugger.IsAttached),只有这个两个条件都满足的情况下,这个方法才会返回True。
  • DebugLogger的Log方法在真正写入日志的过程中,它会利用指定的作为格式化器的Func<TState, Exception, string>对象将承载原始日志信息的对象和异常(对应参数state和exception)格式成一个完整的字符串作为最终写入的日志消息。但是在指定的Exception对象不为Null的情况下,它又会在这个格式好的日志消息上附加上异常信息,这其实是不太合理的。

三、DebugLoggerProvider

DebugLogger对应的LoggerProvider是一个DebugLoggerProvider对象。如下面的代码片段所示,DebugLoggerProvider提供DebugLogger的逻辑非常简单,它只需要在实现的CreateLogger方法中调用构造函数创建并返回一个DebugLogger对象即可,提供的作为日志过滤器的Func<string, LogLevel, bool>对象在自身的构造函数中由对应的参数指定。

   1: public class DebugLoggerProvider : ILoggerProvider, IDisposable

   2: {

   3:     private readonly Func<string, LogLevel, bool> _filter;

   4:     public DebugLoggerProvider(Func<string, LogLevel, bool> filter)

   5:     {

   6:         _filter = filter;

   7:     }

   8:  

   9:     public ILogger CreateLogger(string name)

  10:     {

  11:         return new DebugLogger(name, _filter);

  12:     }

  13:  

  14:     public void Dispose()

  15:     {}

  16: }

针对DebugLoggerProvider的注册可以通过如下三个针对ILoggerFactory接口的扩展方法AddDebug来完成。我们调用这些方法时可以为注册的DebugLoggerProvider指定作为日志过滤器的Func<string, LogLevel, bool>对象,也可以指定一个最低的日志等级。如果这两者都没有指定,从给出的代码片段可以看出该方法会默认将Information作为最低日志等级。也就是说,当我们调用AddDebug方法时如果没有指定任何日志过滤条件,等级为Debug的日志消息并不会被记录下来,这一点也是我们个人觉得不太合理的地方。

   1: public static class DebugLoggerFactoryExtensions

   2: {

   3:     public static ILoggerFactory AddDebug(this ILoggerFactory factory)

   4:     {

   5:         return factory.AddDebug(LogLevel.Information);

   6:     }

   7:  

   8:     public static ILoggerFactory AddDebug(this ILoggerFactory factory, LogLevel minLevel)

   9:     {

  10:         return factory.AddDebug((cate, level) => level >= minLevel);

  11:     }

  12:  

  13:     public static ILoggerFactory AddDebug(this ILoggerFactory factory, Func<string, LogLevel, bool> filter)

  14:     {

  15:         factory.AddProvider(new DebugLoggerProvider(filter));

  16:         return factory;

  17:     }

  18: }

接下来我们通过一个简单的实例来演示针对DebugLogger的日志记录。我们创建一个空的控制台应用,在添加必要的依赖之后,我们在Main方法中编写了如下一段程序。如下面的代码片段所示,我们采用依赖注入的方式创建了一个LoggerFactory,并调用AddDebug方法完成了针对DebugLoggerProvider的注册。在利用LoggerFactory创建出Logger对象之后,我们利用后者记录了三条日志消息。

   1: public class Program

   2: {

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

   4:     {

   5:         ILogger logger = new ServiceCollection()

   6:             .AddLogging()

   7:             .BuildServiceProvider()

   8:             .GetService<ILoggerFactory>()

   9:             .AddDebug()

  10:             .CreateLogger<Program>();

  11:  

  12:         logger.LogDebug("这是一个等级为Debug的日志");

  13:         logger.LogInformation("这是一个等级为Information的日志");

  14:         logger.Log(LogLevel.Error, 3721, "这是一个等级为Error的日志",new FileNotFoundException("目标文件不存在"),

  15:         (state, exception) => $"{state}{Environment.NewLine}{exception}");

  16:     }

  17: }

记录的三条日志具有不同的等级(分别为Debug、Information和Error)。第三条日志的记录是调用Logger对象的Log方法实现的,我们在调用该方法时指定了所有的承载日志消息所有的信息(日志等级、事件ID、日志原始消息和异常)和作为格式化器的Func<TState, Exception, string>对象。值得一提是作为格式化器的这个委托对象已经考虑到了针对异常消息的格式化。

现在直接利用Visual Studio在Debug模式下编译并运行这个程序,我们会在输出窗口中看到写入的日志。如下图所示,Visual Studio的输出窗口只显示了两条等级分别为Information和Error的日志,等级为Debug的日志并没有被记录下来。对于记录的第二条日志,我们发现异常的信息被重复记录,前者是的内容是源于我们指定的格式化器,后者则是DebugConsoleLogger的Log方法自行附加上去的。


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

.NET Core的日志[3]:将日志写入Debug窗口的更多相关文章

  1. 将日志写入Debug窗口

    定义在NuGet包“Microsoft.Extensions.Logging.Debug”中的DebugLogger会直接调用Debug的WriteLine方法来写入分发给它的日志消息.如果需要使用D ...

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

    面向Windows的编程人员应该不会对Event Log感到陌生,以至于很多人提到日志,首先想到的就是EventLog.EventLog不仅仅记录了Windows系统自身针对各种事件的日志,我们的应用 ...

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

    对于一个控制台应用,比如采用控制台应用作为宿主的ASP.NET Core应用,我们可以将记录的日志直接输出到控制台上.针对控制台的Logger是一个类型为ConsoleLogger的对象,Consol ...

  4. Net Core平台灵活简单的日志记录框架NLog+SqlServer初体验

    Net Core平台灵活简单的日志记录框架NLog+SqlServer初体验 前几天分享的"[Net Core平台灵活简单的日志记录框架NLog+Mysql组合初体验][http://www ...

  5. Net Core平台灵活简单的日志记录框架NLog+Mysql组合初体验

    Net Core平台灵活简单的日志记录框架NLog初体验 前几天分享的"[Net Core集成Exceptionless分布式日志功能以及全局异常过滤][https://www.cnblog ...

  6. asp.net core mcroservices 架构之 分布式日志(一)

    一 简介 无论是微服务还是其他任何分布式系统,都需要一个统一处理日志的系统,这个系统 必须有收集,索引,分析查询的功能.asp .net core自己的日志是同步方式的,正如文档所言: 所以必须自己提 ...

  7. .net core使用ocelot---第三篇 日志记录

    简介 .net core使用ocelot---第一篇 简单使用 .net core使用ocelot---第二篇 身份验证使用 上篇介绍使用asp.net core 创建API网关.本文将介绍Ocelo ...

  8. .Net Core IIS下无Log4Net日志输出,命令行下却有(dotnet运行)

    .Net Core IIS下无Log4Net日志输出,命令行下却有(dotnet运行) 遇到个诡异的问题,项目发布并寄宿到 IIS上后,Log4Net没有日志输出 1.原因分析 这不应该啊,所有的配置 ...

  9. .NET Core + K8S + Loki 玩转日志聚合

    1. Intro 最近在了解日志聚合系统,正好前几天看到一篇文章<用了日志系统新贵Loki,ELK突然不香了!>,所以就决定动手体验一下.本文就带大家快速了解下Loki,并简单介绍.NET ...

随机推荐

  1. Python编码记录

    字节流和字符串 当使用Python定义一个字符串时,实际会存储一个字节串: "abc"--[97][98][99] python2.x默认会把所有的字符串当做ASCII码来对待,但 ...

  2. ASP.NET_各个币种之间的汇率转换(实时)使用Yahoo汇率。

    近期开发支付平台的时候有运用到各国的实时汇率之间的转换问题,于是在往上找了很多相关资料,以下就是一些参考网址: 1.提供API接口的网站:https://www.showapi.com:这个网站有提供 ...

  3. 谈谈如何使用Netty开发实现高性能的RPC服务器

    RPC(Remote Procedure Call Protocol)远程过程调用协议,它是一种通过网络,从远程计算机程序上请求服务,而不必了解底层网络技术的协议.说的再直白一点,就是客户端在不必知道 ...

  4. Socket聊天程序——服务端

    写在前面: 昨天在博客记录自己抽空写的一个Socket聊天程序的初始设计,那是这个程序的整体设计,为了完整性,今天把服务端的设计细化记录一下,首页贴出Socket聊天程序的服务端大体设计图,如下图: ...

  5. ABP框架 - Swagger UI 集成

    文档目录 本节内容: 简介 Asp.net Core 安装 安装Nuget包 配置 测试 Asp.net 5.x 安装 安装Nuget包 配置 测试 简介 来自它的网页:“...使用一个Swagger ...

  6. ASP.NET Web API 跨域访问(CORS)

    一.客户端用JSONP请求数据 如果你想用JSONP来获得跨域的数据,WebAPI本身是不支持javascript的callback的,它返回的JSON是这样的: {"YourSignatu ...

  7. 6.DNS公司PC访问外网的设置 + 主DNS服务器和辅助DNS服务器的配置

    网站部署之~Windows Server | 本地部署 http://www.cnblogs.com/dunitian/p/4822808.html#iis DNS服务器部署不清楚的可以看上一篇:ht ...

  8. .net 分布式架构之配置中心

    开源QQ群: .net 开源基础服务  238543768 开源地址: http://git.oschina.net/chejiangyi/Dyd.BaseService.ConfigManager ...

  9. 用scikit-learn学习BIRCH聚类

    在BIRCH聚类算法原理中,我们对BIRCH聚类算法的原理做了总结,本文就对scikit-learn中BIRCH算法的使用做一个总结. 1. scikit-learn之BIRCH类 在scikit-l ...

  10. 邮件中嵌入html中要注意的样式

    工作中常会有需求向用户发送邮件,需要前端工程师来制作html格式的邮件,但是由于邮件客户端对样式的支持有限,要兼容很多种浏览器需要注意很多原则: 1.邮件使用table+css布局 2.邮件主要部分在 ...