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提供了两个独立的缓存框架,一个是针对本地内存的缓存,另一个是针对分布式存储的缓存.前者可以在不经过序列化的情况下直接将对象存储在应用程序进程的内存中,后者则需要将对象序列化成字节数组并存储到一 ...
随机推荐
- 比 WSL2 更香的是 Docker for windows!
今天给大家推荐一个软件 -- "Docker for windows": 如果你对WSL2,还不熟悉,可以关注公众号或小程序看看我之前推送过的两篇文章. Docker for wi ...
- C++多线程之互斥锁和超时锁
#include<iostream> #include<thread> #include<mutex> using namespace std; mutex mu; ...
- AOP操作-准备工作
AOP操作(准备) 1,Spring 框架中一般基于 AspectJ 实现AOP操作 (1)什么是 AspectJ *AspectJ 不是 Spring 组成部分,独立AOP框架,一般把 Aspect ...
- Vue3 框架基础随笔 (一)
VUE框架基础部分随笔 Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架. Vue可以使用简单的代码实现一个单页面应用. 基本格式 Vue通过模板语法来声明式的将数 ...
- springboot 修改关闭banner的方法
一.修改banner. 1.1 替换banner: 需要在resources(classpath)目录中创建文件 banner.txt 1.2 上图 banner.txt 里面可以使用文字,也可以 ...
- RealFormer: 残差式 Attention 层的Transformer 模型
原创作者 | 疯狂的Max 01 背景及动机 Transformer是目前NLP预训练模型的基础模型框架,对Transformer模型结构的改进是当前NLP领域主流的研究方向. Transformer ...
- SpringDataJpa打印Sql详情(含参数)
Spring Data Jpa打印Sql详情(带sql参数) 这里使用的是 log4jdbc,yml配置文件里的数据源配置也要做相应的修改 pom文件引入 <dependency> < ...
- docker平时使用异常记录
GPU主机重启后,启动使用GPU的容器报错 docker: Error response from daemon: Unknown runtime specified nvidia. 解决办法:修改/ ...
- 基于Hexo的博客管理恢复
若重装电脑或更换电脑后 该如何恢复博客的管理? 1.确保之前博客源代码文件夹及文件保存在公库或私库中 例如: 我这里采用的是闭源存放方案,故为私库 这是源码文件样式 2.在新电脑上重新安装git,no ...
- 【第十八期】分享一个网易go面经
自我介绍 未来的主要方向 介绍下之前的项目用到的优化点.难点 为什么不要大量使用goroutine gpm模型 go里面goroutine创建数量有限制吗? 线程和协程有什么区别 golang支持哪些 ...