针对控制台的ILogger实现类型为ConsoleLogger,对应的ILoggerProvider实现类型为ConsoleLoggerProvider,这两个类型都定义在 NuGet包“Microsoft.Extensions.Logging.Console”中。ConsoleLogger要将一条日志输出到控制台上,首选要解决的是格式化的问题,具体来说是如何将日志消息的内容荷载和元数据(类别、等级和事件ID等)格式化成呈现在控制台上的文本。针对日志的格式化由ConsoleFormatter对象来完成。(本篇提供的实例已经汇总到《ASP.NET Core 6框架揭秘-实例演示版》)

[S901]SimpleConsoleFormatter格式化器(源代码

[S902]SystemdConsoleFormatter格式化器(源代码

[S903]JsonConsoleFormatter格式化器(源代码

[S904]改变ConsoleLogger的标准输出和错误输出(源代码

[S905]自定义控制台日志的格式化器(源代码

[S901]SimpleConsoleFormatter格式化器

下面了代码演示了如何使用SimpleConsoleFormatter来格式化控制台输出的日志。我们利用命令行参数控制是否采用单行文本输出和着色方案。在调用ILoggingBuiler接口的AddConsole扩展方法对ConsoleLoggerProvider对象进行注册之后,我们接着调用它的AddSimpleConsole扩展方法将SimpleConsoleFormatter作为格式化器,并利用作为参数的Action<SimpleConsoleFormatterOptions>委托根据命令行参数的对SimpleConsoleFormatterOptions配置选项进行了相应设置(S901)。

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Console; var configuration = new ConfigurationBuilder()
.AddCommandLine(args)
.Build();
var singleLine = configuration.GetSection("singleLine").Get<bool>();
var colorBebavior = configuration.GetSection("color").Get<LoggerColorBehavior>(); var logger = LoggerFactory.Create(builder => builder
.AddConsole()
.AddSimpleConsole(options =>
{
options.SingleLine = singleLine;
options.ColorBehavior = colorBebavior;
}))
.CreateLogger<Program>();
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));
Console.Read();

我们已命名行的方式三次启动演示程序,并利用参数(singleLine=true, color=Disabled)控制对应的格式化行为。从图1所示的结果可以看出日志输出的格式是与我们指定的命名行参数是匹配的。


图1 基于SimpleConsoleFormatter的格式化

[S902]SystemdConsoleFormatter格式化器

我们使用如下这个实例来演示针对SystemdConsoleFormatter的格式化。如代码片段所示,我们利用命令行参数“includeScopes”来决定是否支持日志范围。在调用ILoggingBuilder接口的AddConsole扩展方法ConsoleLoggerProvider对象进行注册之后,我们接着调用了该接口的AddSystemdConsole扩展方法对SystemdConsoleFormatter及其配置选项进行注册。为了输出所有等级的日志,我们将最低日志等级设置为Trace。为了体现针对异常信息的输出,我们在调用Log方法是传入了一个Exception对象。

using Microsoft.Extensions.Logging;

var includeScopes = args.Contains("includeScopes");
var logger = LoggerFactory.Create(builder => builder
.SetMinimumLevel(LogLevel.Trace)
.AddConsole()
.AddSystemdConsole(options => options.IncludeScopes = includeScopes))
.CreateLogger<Program>();
var levels = (LogLevel[])Enum.GetValues(typeof(LogLevel));
levels = levels.Where(it => it != LogLevel.None).ToArray();
var eventId = 1;
Array.ForEach(levels, Log);
Console.Read(); void Log(LogLevel logLevel)
{
using (logger.BeginScope("Foo"))
{
using (logger.BeginScope("Bar"))
{
using (logger.BeginScope("Baz"))
{
logger.Log(logLevel, eventId++, new Exception("Error..."), "This is a/an {0} log message.", logLevel);
}
}
}
}

我们采用命令行的方式两次启动演示程序,第一次采用默认配置,第二次利用命令行参数“includeScopes”开启针对日志范围的支持。从图2所示的输出结果可以看出六条日志均以单条文本的形式输出到控制台上,对应的日志等级(Trace、Debug、Information、Warning、Error和Critical)均被转换成Syslog日志等级(7、7、6、4、3和2)。


图2 基于SystemdConsoleFormatter的格式化

[S903]JsonConsoleFormatter格式化器

我们对上面的演示程序略加改动,将针对ILoggingBuilder接口的AddSystemdConsole扩展方法的调用替换成调用AddJsonConsole扩展方法,后者帮助我们完成针对JsonConsoleFormatter及其配置选项的注册。为了减少控制台上输出的内容,我们移除了针对最低日志等级的设置。

using Microsoft.Extensions.Logging;

var includeScopes = args.Contains("includeScopes");

var logger = LoggerFactory
.Create(builder => builder
.AddConsole()
.AddJsonConsole(options => options.IncludeScopes = includeScopes))
.CreateLogger<Program>();
var levels = (LogLevel[])Enum.GetValues(typeof(LogLevel));
levels = levels.Where(it => it != LogLevel.None).ToArray();
var eventId = 1;
Array.ForEach(levels, Log); Console.Read(); void Log(LogLevel logLevel)
{
using (logger.BeginScope("Foo"))
{
using (logger.BeginScope("Bar"))
{
using (logger.BeginScope("Baz"))
{
logger.Log(logLevel, eventId++, new Exception("Error..."), "This is a/an {0} log message.", logLevel);
}
}
}
}

我们依然采用上面的方式两次执行改动后的程序。在默认以及开启日志范围的情况下,控制台分别具有图3所示的输出。可以看出输出的内容不仅包含参数填充生成完整内容,还包含原始的模板。日志范围的路径是以数组的方式输出的。


图3 基于JsonConsoleFormatter的格式化

[S904]改变ConsoleLogger的标准输出和错误输出

ConsoleLogger具有“标准输出”和“错误输出”两个输出渠道,分别对应着Console类型的静态属性Out和Error返回的TextWriter对象。对于不高于LogToStandardErrorThreshold设定等级的日志会采用标准输出,高于或者等于该等级的日志则采用错误输出。由于LogToStandardErrorThreshold属性的默认值为None,所以任何等级的日志都被写入标准输出。如下的代码片段演示了如何通过设置这个属性改变不同等级日志的输出渠道。

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Console; using (var @out = new StreamWriter("out.log") { AutoFlush = true })
using (var error = new StreamWriter("error.log") { AutoFlush = true })
{
Console.SetOut(@out);
Console.SetError(error); var logger = LoggerFactory.Create(builder => builder
.SetMinimumLevel(LogLevel.Trace)
.AddConsole(options =>options.LogToStandardErrorThreshold = LogLevel.Error)
.AddSimpleConsole(options =>options.ColorBehavior = LoggerColorBehavior.Disabled))
.CreateLogger<Program>(); var levels = (LogLevel[])Enum.GetValues(typeof(LogLevel));
levels = levels.Where(it => it != LogLevel.None).ToArray();
var eventId = 1;
Array.ForEach(levels, Log);
Console.Read(); void Log(LogLevel logLevel) => logger.Log(logLevel, eventId++, "This is a/an {0} log message.", logLevel);
}

如上面的代码片段所示,我们创建了两个针对本地文件(out.log和error.log)的StreamWriter,并通过调用Console类型的静态方法SetOut和SetError将其设置为控制台的标准输出和错误输出。在针对ILogingBuilder接口的AddConsole扩展方法调用中,我们将配置选项ConsoleLoggerOptions的LogToStandardErrorThreshold属性设置为Error,并调用SetMinimumLevel方法将最低日志等级设置为Trace。我们还调用AddSimpleConsole扩展方法禁用作色方案。当程序运行之后,针对具有不同等级的六条日志,四条不高于Error的日志被输出到如图4所示的out.log中,另外两条则作为错误日志被输出到error.log中,控制台上将不会有任何输出内容。


图4 标准输入和错误输出

[S905]自定义控制台日志的格式化器

为了能够更加灵活地控制日志在控制台上的输出格式,我们自定义了如下这个格式化器类型。如代码片段所示,这个名为TemplatedConsoleFormatter会按照指定的模板来格式化输出的日志内容,它使用的配置选项类型为TemplatedConsoleFormatterOptions,日志格式模板就体现在它的Template属性上。

public class TemplatedConsoleFormatter : ConsoleFormatter
{
private readonly bool _includeScopes;
private readonly string _tempalte; public TemplatedConsoleFormatter(IOptions<TemplatedConsoleFormatterOptions> options)
: base("templated")
{
_includeScopes = options.Value.IncludeScopes;
_tempalte = options.Value?.Template?? "[{LogLevel}]{Category}/{EventId}:{Message}\n{Scopes}\n";
} public override void Write<TState>(in LogEntry<TState> logEntry,
IExternalScopeProvider scopeProvider, TextWriter textWriter)
{
var builder = new StringBuilder(_tempalte);
builder.Replace("{Category}", logEntry.Category);
builder.Replace("{EventId}", logEntry.EventId.ToString());
builder.Replace("{LogLevel}", logEntry.LogLevel.ToString());
builder.Replace("{Message}", logEntry.Formatter?.Invoke(logEntry.State, logEntry.Exception)); if (_includeScopes && scopeProvider != null)
{
var buidler2 = new StringBuilder();
var writer = new StringWriter(buidler2);
scopeProvider.ForEachScope(WriteScope, writer); void WriteScope(object? scope, StringWriter state)
{
writer.Write("=>" + scope);
} builder.Replace("{Scopes}", buidler2.ToString());
}
textWriter.Write(builder);
}
} public class TemplatedConsoleFormatterOptions: ConsoleFormatterOptions
{
public string? Template { get; set; }
}

我们将TemplatedConsoleFormatter的别名指定为“templated”,格式模板利用占位符“{Category}”、“{EventId}”、“{LogLevel}”、“{Message}”和“{Scopes}”表示日志类别、事件ID、等级、消息和范围信息。 “[{LogLevel}]{Category}/{EventId}:{Message}\n{Scopes}\n”是我们提供的默认模板。现在我们采用如下这个名为appsettings.json的JSON文件来提供所有的配置。

{
"Logging": {
"Console": {
"FormatterName": "templated",
"LogToStandardErrorThreshold": "Error",
"FormatterOptions": {
"IncludeScopes": true,
"UseUtcTimestamp": true,
"Template": "[{LogLevel}]{Category}:{Message}\n"
}
}
}
}

如上面的代码片段所示,我们将控制台日志输出的相关设置定义在“Logging:Console”配置节中,并定义了格式化器名称(“templated”)、错误日志最低等级(“Error”)。“FormatterOptions”配置节的内容将最终绑定到TemplatedConsoleFormatterOptions配置选项上。在如下所示的演示程序中,我们加载这个配置文件并提取代表“Logging”配置节的IConfigguration对象,我们将这个对象作为参数调用ILoggingBuilder接口的AddConfiguration扩展方法进行了注册。

using App;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging; var logger = LoggerFactory.Create(Configure).CreateLogger<Program>(); var levels = (LogLevel[])Enum.GetValues(typeof(LogLevel));
levels = levels.Where(it => it != LogLevel.None).ToArray();
var eventId = 1;
Array.ForEach(levels, Log);
Console.Read(); void Configure(ILoggingBuilder builder)
{
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build()
.GetSection("Logging");
builder
.AddConfiguration(configuration)
.AddConsole()
.AddConsoleFormatter<TemplatedConsoleFormatter,TemplatedConsoleFormatterOptions>();
} void Log(LogLevel logLevel) => logger.Log(logLevel, eventId++, "This is a/an {0} log message.", logLevel);

在调用ILoggingBuilder接口的AddConsole扩展方法对ConsoleLoggerProvider进行注册之后,我们调用了它的AddConsoleFormatter<TemplatedConsoleFormatter,TemplatedConsoleFormatterOptions>方法,该方法将利用配置来绑定注册格式化器对应的TemplatedConsoleFormatterOptions配置选项。演示程序启动之后,每一条日志将按照配置中提供的模板进行格式化,并以图5所示的形式输出到控制台上。


图5 基于TemplatedConsoleFormatter的格式化

ASP.NET Core 6框架揭秘实例演示[15]:针对控制台的日志输出的更多相关文章

  1. ASP.NET Core 6框架揭秘实例演示[07]:文件系统

    ASP.NET Core应用具有很多读取文件的场景,如读取配置文件.静态Web资源文件(如CSS.JavaScript和图片文件等).MVC应用的视图文件,以及直接编译到程序集中的内嵌资源文件.这些文 ...

  2. ASP.NET Core 6框架揭秘实例演示[08]:配置的基本编程模式

    .NET的配置支持多样化的数据源,我们可以采用内存的变量.环境变量.命令行参数.以及各种格式的配置文件作为配置的数据来源.在对配置系统进行系统介绍之前,我们通过几个简单的实例演示一下如何将具有不同来源 ...

  3. ASP.NET Core 6框架揭秘实例演示[09]:配置绑定

    我们倾向于将IConfiguration对象转换成一个具体的对象,以面向对象的方式来使用配置,我们将这个转换过程称为配置绑定.除了将配置树叶子节点配置节的绑定为某种标量对象外,我们还可以直接将一个配置 ...

  4. ASP.NET Core 6框架揭秘实例演示[10]:Options基本编程模式

    依赖注入使我们可以将依赖的功能定义成服务,最终以一种松耦合的形式注入消费该功能的组件或者服务中.除了可以采用依赖注入的形式消费承载某种功能的服务,还可以采用相同的方式消费承载配置数据的Options对 ...

  5. ASP.NET Core 6框架揭秘实例演示[11]:诊断跟踪的几种基本编程方式

    在整个软件开发维护生命周期内,最难的不是如何将软件系统开发出来,而是在系统上线之后及时解决遇到的问题.一个好的程序员能够在系统出现问题之后马上定位错误的根源并找到正确的解决方案,一个更好的程序员能够根 ...

  6. ASP.NET Core 6框架揭秘实例演示[12]:诊断跟踪的进阶用法

    一个好的程序员能够在系统出现问题之后马上定位错误的根源并找到正确的解决方案,一个更好的程序员能够根据当前的运行状态预知未来可能发生的问题,并将问题扼杀在摇篮中.诊断跟踪能够帮助我们有效地纠错和排错&l ...

  7. ASP.NET Core 6框架揭秘实例演示[13]:日志的基本编程模式[上篇]

    <诊断跟踪的几种基本编程方式>介绍了四种常用的诊断日志框架.其实除了微软提供的这些日志框架,还有很多第三方日志框架可供我们选择,比如Log4Net.NLog和Serilog 等.虽然这些框 ...

  8. ASP.NET Core 6框架揭秘实例演示[14]:日志的进阶用法

    为了对各种日志框架进行整合,微软创建了一个用来提供统一的日志编程模式的日志框架.<日志的基本编程模式>以实例演示的方式介绍了日志的基本编程模式,现在我们来补充几种"进阶" ...

  9. ASP.NET Core 6框架揭秘实例演示[16]:内存缓存与分布式缓存的使用

    .NET提供了两个独立的缓存框架,一个是针对本地内存的缓存,另一个是针对分布式存储的缓存.前者可以在不经过序列化的情况下直接将对象存储在应用程序进程的内存中,后者则需要将对象序列化成字节数组并存储到一 ...

随机推荐

  1. 如何完整删除DISK DRILL

    前两天装了DISK DRILL 右上角出现一个温度提示的图标  现在把DISK DRILL卸载了  但右上角的温度提示图标仍然存在  请问如何删除? 打开系统偏好设置----用户与群----管理员(点 ...

  2. JVM学习九-(复习)HotSpot 垃圾收集器

    HotSpot 虚拟机提供了多种垃圾收集器,每种收集器都有各自的特点,虽然我们要对各个收集器进行比较,但并非为了挑选出一个最好的收集器.我们选择的只是对具体应用最合适的收集器. 新生代垃圾收集器 Se ...

  3. Java中静态变量与非静态变量的区别

    感谢大佬:https://www.cnblogs.com/liuhuijie/p/9175167.html ①java类的成员变量有俩种: 一种是被static关键字修饰的变量,叫类变量或者静态变量 ...

  4. 简介GitHub的使用方法--管理个人代码

    转自 http://blog.csdn.net/tengyeyijiu/article/details/46446283git是一个分布式版本控制系统,最初由linus torvalds编写,用作Li ...

  5. 2022寒假集训day6

    day6上午还是做四道题T1区域[上机练习]1.编程计算由"*"号围成的下列图形的面积.面积计算方法是统计*号所围成的闭合曲线中水平线和垂直线交点的数目.如下图所示,在 10*10 ...

  6. gitlab登录时出现402的错误

    当登录gitlab时出现402的错误提示,可进行以下操作 重新设置一下系统时间 在new project 时若还出现同样的402问题,则清除一下浏览器的cookie即可

  7. NOIP2021T1报数——黄蓝紫黑的神奇梯度

    7A3T 点击查看代码 #include<iostream> #include<cstdio> #include<cmath> #include<algori ...

  8. Solution -「CF 1392G」Omkar and Pies

    \(\mathcal{Description}\)   Link.   给定两个长度为 \(K\) 的 \(01\) 串 \(S,T\) 和 \(n\) 组操作 \((a_i,b_i)\),意义为交换 ...

  9. 自学linux(常用命令)STEP3

    tty tty 可以查看当前处于哪一个系统中. 比如我在图形化界面输入 tty: alt+ctrl+F3切换到命令行: linux命令 linux命令,一般都是 命令+选项+参数,这种格式,为了防止选 ...

  10. 后台运行程序-服务器、python

    0前言 最近遇到一个需求,我有一个很小的python程序,需要一直在服务器器上跑,但是我不想一直开着浏览器或者Xshell 7,所以记录一下怎么解决的. 用到的指令是nohup 具体代码就两行 sou ...