ASP.NET Core 6框架揭秘实例演示[14]:日志的进阶用法
为了对各种日志框架进行整合,微软创建了一个用来提供统一的日志编程模式的日志框架。《日志的基本编程模式》以实例演示的方式介绍了日志的基本编程模式,现在我们来补充几种“进阶”用法。(本篇提供的实例已经汇总到《ASP.NET Core 6框架揭秘-实例演示版》)
[S808]利用配置定义日志过滤规则(源代码)
[S809]利用日志范围输出调用链(源代码)
[S810]LoggerMessage的应用(源代码)
[S812]基于Activity的日志范围(源代码)
[S808]利用配置定义日志过滤规则
通过Func<string, string, LogLevel, bool>对象表示的日志过滤规还可以采用配置的形式来定义。以配置的形式定义的过滤规则最终都体现为对最低等级的设置,设定的这个最低日志等级可以是一个全局的默认设置,也可以专门针对某个日志类别或者ILoggerProvider类型。下面演示针对配置形式的日志过滤规则。我们先创建一个名为logging.json的文件,并在其中定义如下这段配置,然后将“Copy to Output Directory”的属性设置为“Copy Always”。这段配置定义了两组日志过滤规则,第一组是默认规则,第二组则是专门为ConsoleLoggerProvider(别名为Console)定义的过滤规则。
{
"LogLevel": {
"Default" : "Error",
"Foo" : "Debug"
},
"Console": {
"LogLevel": {
"Default" : "Information",
"Foo" : "Warning",
"Bar" : "Error"
}
}
}
以配置形式定义的日志过滤规则最终会落实到对最低日志等级的设置上,其中Default表示默认设置,其他的则是针对具体日志类别的设置。上面定义的这段配置体现的过滤规则如下:对于ConsoleLoggerProvider来说,在默认情况下只有等级不低于Information的日志事件会被输出,而对日志类别“Foo”和“Bar”来说,对应的最低日志等级分别为Warning和Error。对于其他ILoggerProvider类型来说,如果日志类别为“Foo”,那么只有等级不低于Debug的日志才会被输出,其他日志类别则采用默认的等级Error。
为了检验最终是否会采用配置定义的规则对日志消息进行过滤,我们根据配置文件生成对应的IConfiguration对象,然后采用依赖注入的方式创建一个ILoggerFactory对象。我们将IConfiguration对象作为参数调用ILoggingBuilder接口的AddConfiguration扩展方法将配置承载的过滤规则应用到配置模型上。我们最终采用不同的类别(“Foo”、“Bar”和“Baz”)创建了三个ILogger对象,并利用它们记录了六条具有不同等级的日志。
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("logging.json")
.Build(); var loggerFactory = new ServiceCollection()
.AddLogging(builder => builder
.AddConfiguration(configuration)
.AddConsole()
.AddDebug())
.BuildServiceProvider()
.GetRequiredService<ILoggerFactory>(); Log(loggerFactory, "Foo");
Log(loggerFactory, "Bar");
Log(loggerFactory, "Baz"); Console.Read(); static void Log(ILoggerFactory loggerFactory, string category)
{
var logger = loggerFactory.CreateLogger(category);
var levels = (LogLevel[])Enum.GetValues(typeof(LogLevel));
levels = levels.Where(it => it != LogLevel.None).ToArray();
var eventId = 1;
Array.ForEach(levels, level => logger.Log(level, eventId++, "This is a/an {0} log message.", level));
由于我们注册了两个不同的ILoggerProvider类型,创建了三种基于不同日志类别的ILogger对象,所以这里面涉及分发的36条日志消息。而图1是程序执行(以Debug模式进行编译)之后控制台和Visual Studio调试输出窗口的输出结果。

图1 针对配置文件的日志过滤
[S809]利用日志范围输出调用链
日志可以为针对某种目的(如纠错查错、系统优化和安全审核等)而进行的分析提供原始数据,所以孤立存在的一条日志消息对数据分析往往毫无用处,很多问题只有将多条相关的日志消息综合起来分析才能找到答案。日志框架为此引入了日志范围(Log Scope)的概念。所谓的日志范围是为日志记录创建的一个具有唯一标识的上下文,如果注册的ILoggerProvider对象支持这个特性,那么它提供的ILogger对象会感知到当前日志范围的存在,此时它可以将上下文信息一并记录下来。
在接下来演示的实例中,我们将一个包含多个处理步骤的事务作为日志范围,并将各个步骤的执行耗时记录下来。如下面的代码片段所示,我们利用依赖注入容器创建一个ILogger对象。在调用AddConsole扩展方法注册ConsoleLoggerProvider对象之后,我们接着调用AddSimpleConsole扩展方法为它注册了一个简单的格式化器,该方法接受一个Action<SimpleConsoleFormatterOptions>对象作为参数来对格式化器配置选项进行设置。我们利用传入的这个委托将配置选项的IncludeScopes属性设置为True。
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.Diagnostics; var logger = new ServiceCollection()
.AddLogging(builder => builder
.AddConsole()
.AddSimpleConsole(options => options.IncludeScopes = true))
.BuildServiceProvider()
.GetRequiredService<ILogger<Program>>(); using (logger.BeginScope($"Foobar Transaction[{Guid.NewGuid()}]"))
{
var stopwatch = Stopwatch.StartNew();
await Task.Delay(500);
logger.LogInformation("Operation foo completes at {0}", stopwatch.Elapsed); await Task.Delay(300);
logger.LogInformation("Operation bar completes at {0}", stopwatch.Elapsed); await Task.Delay(800);
logger.LogInformation("Operation baz completes at {0}", stopwatch.Elapsed);
}
Console.Read();
日志范围是通过调用ILogger对象的BeginScope方法创建的,我们在调用这个方法时指定一个携带请求ID的字符串来描述并标识创建日志范围。创建的日志范围上下文体现为一个IDisposable对象,范围因Dispose方法的调用而终结。对于支持日志范围的ILoggerProvider对象来说,它提供的ILogger对象自身能够感知到当前上下文的存在,所以我们演示程序并不需要作额外的修改。在我们演示的程序中,执行的事务包含三个操作(Foo、Bar和Baz)。我们将事务开始的那一刻作为基准,记录每个操作完成的时间。该程序启动后会将日志以图2所示的形式输出到控制台上,可以看出包含事务ID的日志范围上下文描述信息一并被记录下来。如果日志最终被写入海量存储中,只要知道请求ID,我们就能将相关的日志提取出来并利用它们构建出该请求的调用链。

图2 记录日志范围上下文
[S810]LoggerMessage的应用
前面演示的程序总是指定一个包含占位符(“{数字}”或者“{文本}”)的消息模板作为参数调用ILogger对象的Log方法来记录日志,所以该方法每次都需要对提供的消息模板进行解析。如果每次提供的都是相同的消息模板,那么这种对消息模板的重复解析就会显得多余。如果应用对性能要求比较高,这绝不是一种好的编程方式。为了解决这个问题,日志框架提供了一个名为LoggerMessage的静态类型,我们可以利用它根据某个具体的消息模板创建一个委托来记录日志。在如下所示的演示程序中,我们利用日志将针对FoobarAsync方法的“调用现场”记录先来,具体记录的内容包括输入参数、返回值和执行耗时。如代码片段所示,我们根据FoobarAsync的定义调用LoggerMessage类型的静态Define方法创建了一个Action<ILogger, int, long, double, TimeSpan, Exception>类型的委托对象来记录日志。
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.Diagnostics; var random = new Random();
var template = "Method FoobarAsync is invoked." +
"\n\t\tArguments: foo={foo}, bar={bar}" +
"\n\t\tReturn value: {returnValue}" +
"\n\t\tTime:{time}";
var log = LoggerMessage.Define<int, long, double, TimeSpan>(
logLevel: LogLevel.Trace,
eventId: 3721,
formatString: template);
var logger = new ServiceCollection()
.AddLogging(builder => builder
.SetMinimumLevel(LogLevel.Trace)
.AddConsole())
.BuildServiceProvider()
.GetRequiredService<ILoggerFactory>()
.CreateLogger("App.Program"); await FoobarAsync(random.Next(), random.Next());
await FoobarAsync(random.Next(), random.Next());
Console.Read(); async Task<double> FoobarAsync(int foo, long bar)
{
var stopwatch = Stopwatch.StartNew();
await Task.Delay(random.Next(100, 900));
var result = random.Next();
log(logger, foo, bar, result, stopwatch.Elapsed, null);
return result;
}
在调用Define方法构建对应的委托对象时,我们指定了日志等级(Information)、EventId(3721)和日志消息模板。我们在FoobarAsync中利用创建的这个委托对象将当前方法的参数、返回值和执行时间通过日志记录下来。FoobarAsync方法总共被调用了两次,所以程序执行后在控制台上输出的两组数据如图3所示。

图3 利用LoggerMessage记录日志
[S812]基于Activity的日志范围
S809开篇演示了通过调用ILogger的BeginScope<TState>方法的构建日志范围的方式,我们接下来演示一下基于Activity的日志范围构建。如下面的代码片段所示,在调用IServiceCollection接口的AddLogging扩展方法时,我们调用了ILoggingBuilder的Configure方法对LoggerFactoryOptions配置选项的ActivityTrackingOptions属性进行了设置,其目的在于Activity中提取TraceId、SpanId和ParentId来描述跟踪操作。
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.Diagnostics; var logger = new ServiceCollection()
.AddLogging(builder => builder
.Configure(options => options.ActivityTrackingOptions = ActivityTrackingOptions.TraceId | ActivityTrackingOptions.SpanId | ActivityTrackingOptions.ParentId)
.AddConsole()
.AddSimpleConsole(options => options.IncludeScopes = true))
.BuildServiceProvider()
.GetRequiredService<ILogger<Program >>(); ActivitySource.AddActivityListener(
new ActivityListener { ShouldListenTo = _ => true, Sample = Sample });
var source = new ActivitySource("App");
using (source.StartActivity("Foo"))
{
logger.Log(LogLevel.Information, "This is a log written in scope Foo.");
using (source.StartActivity("Bar"))
{
logger.Log(LogLevel.Information, "This is a log written in scope Bar.");
using (source.StartActivity("Baz"))
{
logger.Log(LogLevel.Information, "This is a log written in scope Baz.");
}
}
} Console.Read(); static ActivitySamplingResult Sample(ref ActivityCreationOptions<ActivityContext> options)=> ActivitySamplingResult.AllData;
我们利用调用ActivitySource对象的StartActivity方法来创建和启动代表跟踪操作的Activity对象。我们知道只有具有匹配的ActivityListener,并且采样结果不为None的情况下,ActivitySource才会真正将Activity对象创建出来,所以我们人为注册了一个ActivityListener对象。程序运行之后,携带范围信息(调用堆栈信息)的日志会以图4的形式输出到控制台上。

图4 基于Activty的日志范围
ASP.NET Core 6框架揭秘实例演示[14]:日志的进阶用法的更多相关文章
- ASP.NET Core 6框架揭秘实例演示[07]:文件系统
ASP.NET Core应用具有很多读取文件的场景,如读取配置文件.静态Web资源文件(如CSS.JavaScript和图片文件等).MVC应用的视图文件,以及直接编译到程序集中的内嵌资源文件.这些文 ...
- ASP.NET Core 6框架揭秘实例演示[08]:配置的基本编程模式
.NET的配置支持多样化的数据源,我们可以采用内存的变量.环境变量.命令行参数.以及各种格式的配置文件作为配置的数据来源.在对配置系统进行系统介绍之前,我们通过几个简单的实例演示一下如何将具有不同来源 ...
- ASP.NET Core 6框架揭秘实例演示[09]:配置绑定
我们倾向于将IConfiguration对象转换成一个具体的对象,以面向对象的方式来使用配置,我们将这个转换过程称为配置绑定.除了将配置树叶子节点配置节的绑定为某种标量对象外,我们还可以直接将一个配置 ...
- ASP.NET Core 6框架揭秘实例演示[10]:Options基本编程模式
依赖注入使我们可以将依赖的功能定义成服务,最终以一种松耦合的形式注入消费该功能的组件或者服务中.除了可以采用依赖注入的形式消费承载某种功能的服务,还可以采用相同的方式消费承载配置数据的Options对 ...
- ASP.NET Core 6框架揭秘实例演示[11]:诊断跟踪的几种基本编程方式
在整个软件开发维护生命周期内,最难的不是如何将软件系统开发出来,而是在系统上线之后及时解决遇到的问题.一个好的程序员能够在系统出现问题之后马上定位错误的根源并找到正确的解决方案,一个更好的程序员能够根 ...
- ASP.NET Core 6框架揭秘实例演示[12]:诊断跟踪的进阶用法
一个好的程序员能够在系统出现问题之后马上定位错误的根源并找到正确的解决方案,一个更好的程序员能够根据当前的运行状态预知未来可能发生的问题,并将问题扼杀在摇篮中.诊断跟踪能够帮助我们有效地纠错和排错&l ...
- ASP.NET Core 6框架揭秘实例演示[13]:日志的基本编程模式[上篇]
<诊断跟踪的几种基本编程方式>介绍了四种常用的诊断日志框架.其实除了微软提供的这些日志框架,还有很多第三方日志框架可供我们选择,比如Log4Net.NLog和Serilog 等.虽然这些框 ...
- ASP.NET Core 6框架揭秘实例演示[15]:针对控制台的日志输出
针对控制台的ILogger实现类型为ConsoleLogger,对应的ILoggerProvider实现类型为ConsoleLoggerProvider,这两个类型都定义在 NuGet包"M ...
- ASP.NET Core 6框架揭秘实例演示[16]:内存缓存与分布式缓存的使用
.NET提供了两个独立的缓存框架,一个是针对本地内存的缓存,另一个是针对分布式存储的缓存.前者可以在不经过序列化的情况下直接将对象存储在应用程序进程的内存中,后者则需要将对象序列化成字节数组并存储到一 ...
随机推荐
- IPV4地址,子网掩码,子网划分
转载自https://blog.csdn.net/qq_45108415/article/details/114179407
- Centos配置yum本地源最简单的办法
有关centos配置yum本地源的方法 一.前提 先连接镜像 然后在命令行输入如下命令 mount /dev/sr0 /mnt cd /etc/yum.repos.d/ ls 之后会看到如下的界面 二 ...
- SQL解析器详解
1.概述 最近,有同学留言关于SQL解析器方面的问题,今天笔者就为大家分享一下SQL解析器方便的一些内容. 2.内容 2.1 SQL解析器是什么? SQL解析与优化是属于编辑器方面的知识,与C语言这类 ...
- Vue之性能调优
打包优化 1. 屏蔽 sourceMap sourceMap作用:项目打包后,代码都是经过压缩加密的,如果运行时报错,输出的错误信息无法准确得知是哪里的代码报错. 在config目录的index.js ...
- 【第十四期】高德go面经
自我介绍 选一个比较熟悉的项目讲讲 筛选日志的时候,日志格式是不一样的,你们是如何处理的? 处理日志的时候如果日志量比较大会堆积吗?怎么处理的? 日志落盘到机器上,是如何采集的? 采集服务有问题的话可 ...
- MySQL 新增表分区很慢,转移大表数据
问题: MySQL (version 5.7.26) 数据库有一批表 xxx_yyy,由于评估的数据量可能比较大,因此每张表都设置了表分区,把每个月的数据保存在单独的分区里. 那么如果每年年末,没有提 ...
- java的四种引用:强软弱虚
简介 在JDK 1.2以前的版本中,若一个对象不被任何变量引用,那么程序就无法再使用这个对象.也就是说,只有对象处于(reachable)可达状态,程序才能使用它. 从JDK 1.2版本开始,对象的引 ...
- Nginx 路由转发配置(转)
Nginx 路由转发配置笔记 由于预算有限,只有一台服务器,想要玩的东西不少,所以这个台服务器上会提供多重服务,因此涉及到的nginx转发就必有重要了 由nginx做请求代理,提供多种服务 php搭建 ...
- js null和{}区别
{}是一个不完全空的对象,因为他的原型链上还有Object呢,而null就是完全空的对象,啥也没有,原型链也没有,所以null instanceof Object === false;[]就更不用说了 ...
- js获取 url?后面的参数取值
function GetRequest() { var url = location.search; //获取url中"?"符后的字串 var theRequest ...