ASP.NET Core 沉思录 - 结构化日志

在 《ASP.NET Core 沉思录 - Logging 的两种介入方法》中我们介绍了 ASP.NET Core 中日志的基本设计结构。这一次我们来观察日志记录的格式,并进一步考虑如何在应用程序中根据不同的需求选择不同的日志记录形式。

太长不读:直接飞到文章最后 :-D

Microsoft.Extension.Logging 体系下的日志格式

为了便于阅读,我们仍然将 Microsoft.Extension.Logging 的基本设计结构放在这里:

640?wx_fmt=png

奇怪的不合理之处

注:所谓奇怪就是这种不合理是只是从某一种特定视角看的。

对于记录日志而言,虽然一些具体的日志记录目标和记录的格式会有一些联系,但是日志记录的目标和日志记录的格式应该是两件事情。貌似 Microsoft.Extension.Logging 在此处进行了一些抽象。首先,日志具体的记录地点和记录格式全部由具体的 ILoggerProvider 创建的 ILogger 来完成。而对于日志的格式化方法,则使用 ILogger.Log 方法中的委托来完成。该委托中包含了一个 formatter 委托参数。该委托接收需要记录的对象,关联的异常实例并返回日志字符串。我们可以在其中定义自己的格式化逻辑。总结起来感觉是:

  • 特定的 ILoggerProvider 创建将日志记录到特定种类的目的地的日志记录器。例如,ConsoleLogger

  • 指定 ILogger.Log 方法中的 formatter 参数对日志对象进行格式化。

ILogger.Log 方法除了 formatter 之外还包含如下的参数:

  • logLevel:日志的级别。

  • eventId:当前事件的标识。

  • state:日志对象。

  • exception:关联的异常对象。

formatter 参数将使用其中的 state 参数和 exception 参数对日志进行格式化。这样通过替换 formatter 的逻辑就可以更改日志的形式了。例如,使用如下的逻辑就可以将 state 格式化为 JSON 形式:

// Capture output so that we can assert its content
var writer = new StringWriter();
Console.SetOut(writer);

// Normal initialization logic
var serviceCollection = new ServiceCollection();
serviceCollection.AddLogging(
    config => config.SetMinimumLevel(LogLevel.Debug).AddConsole());
ServiceProvider provider = serviceCollection.BuildServiceProvider();
var loggerFactory = provider.GetService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger("category");

// Write log message
logger.Log(
    LogLevel.Information,
    1,
    new {message = "Hello {name}", name = "World"},
    null,
    (state, exception) => JsonConvert.SerializeObject(state));

// This is very important. The console logger using a async processor to consume
// the queued log message.
Thread.Sleep(1000);

Assert.Equal("...(omitted)...", writer.ToString())

如果您尝试了上述范例程序就会感到这个设计好像有问题,而如果联系整个 Extension.Logging 体系则感觉问题就更大了:

  • formatter只是解决了日志 message 部分的格式化问题,而无法影响其他信息的格式化,例如 eventIdlogLevelexception 等。

  • 我们根本不会用到具体的 ILoggerProvider 而是会使用 ILoggerFactory 提供的 Logger 门面。这个 Logger 是一个组合 Logger,也就是它会将 ILogger&lt;&gt;.Log 调用分发出来。但是我们很少使用 ILogger&lt;&gt;.Log 方法,而会使用扩展方法使用 template message 进行日志记录,这意味着所有的子 ILogger 实现都会接到同样的 formatter。自此,不同的目标采用不同的消息格式的理想破灭了。

总结一下就是,日志的记录目标和日志的格式混合了起来。职责区分不清。message 的格式化职责交给了门面扩展方法;而另一部分格式化职责交给了具体的 ILoggerProvider

从另一种视角看的合理之处

我们换一个视角可能就会得到不一样的体验。首先我们更改分析问题的策略。从端到端的角度来思考问题。当我们记录日志的时候希望有哪几类信息呢?日志作为追踪一个事件的依据,应当能够清晰的说明这个事件。那么小学语文老师就告诉过我们,记录一件事情需要有:

  • 时间

  • 地点

  • 人物

  • 起因

  • 经过

  • 结果

如果我们将这些信息归一归类,我们就可以得到这些信息:

  • 物理世界的环境参数:时间

  • 判断事件严重程度的依据:结果

  • 事件过程的上下文参数:地点(例如 URI 或代表某种操作的入口)、人物(例如谁进行的操作)、起因(例如方法调用参数)、经过(例如调用的那个方法)

而要记录这些信息,则可以对应到程序中的以下几种形式的数据:

  • 物理世界的环境参数:例如 DateTimeDateTimeOffset

  • 判断事件严重程度的依据:例如 LogLevel

  • 事件过程上下文的参数:例如事件的类别 CategoryName;一个包含各种各样上下文参数的 object[] 对象;以及对人类友好,能够将这些参数串起来的消息模板。

至此,你能够看到这些参数正是 Microsoft.Extension.Logging 中门面扩展方法中需要你来提供的参数。它本来也没有希望你调用 ILogger.Log 方法。而是希望你调用扩展方法用最舒服的方式达成日志记录的目的。

而作为 ILoggerProvider 开发者,你并不一定必须得接受 formatter 生成的格式化后的日志消息。你可以选择处理每一个传入参数。具体的请参见 FormattedLogValues 类型的源代码。

阶段性总结

  • 日志记录和记叙文一样,只要满足了六要素就可以说清楚一件事情。而记录这六要素的形式正式 Extension.Logging 提供给我们的扩展方法的参数形式。

  • 分析问题从端到端分析是一种非常靠谱的分析方法。可以避免走弯路。

  • ILogger 的扩展方法负责生成日志消息,ILoggerProvider 和负责记录工作的 ILogger 实现负责格式化日志消息并将日志记录到特定的目标上去。

利用 SeriLog 实现灵活的日志记录形式

通过上述分析我们应该能够看到这种设计的合理性。但是不争的事实是 ILoggerProvider 一系包揽两种职能,并没有进一步抽象,有没有人来对日志记录的目标和日志记录的整体格式进行抽象呢?有!那就是被千万人喜爱的 SeriLog。它在 ILoggerProvider 一级抽象了 ITextFormatter 解决了这个问题:

我不会在这里介绍 SeriLog 的具体使用方法。网上教程一大堆大家去搜搜好了。我建议直接去官网。

例如,我可以将日志记录到 Console 中,默认情况下,这种日志的格式是给人看的:

// normal initialization logic
var serviceCollection = new ServiceCollection();
serviceCollection.AddLogging(b =>
{
    Logger seriLogger = new LoggerConfiguration()
        .WriteTo.Console()
        .MinimumLevel.Debug()
        .CreateLogger();

    b.AddSerilog(seriLogger);
});
ServiceProvider provider = serviceCollection.BuildServiceProvider();

// create logger
var loggerFactory = provider.GetService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger("category");

// write log
logger.LogInformation("Hello {name}""world");

此时屏幕上会输出高亮版的,适于阅读的日志,类似这样:

[18:41:27 INF] Hello world

但是如果希望使用其他的格式,则可以通过 ITextFormatter 快速的转换格式:

var serviceCollection = new ServiceCollection();
serviceCollection.AddLogging(b =>
{
    Logger seriLogger = new LoggerConfiguration()
        // PLEASE NOTE that we use JsonFormatter as input paramter
        .WriteTo.Console(new JsonFormatter())
        .MinimumLevel.Debug()
        .CreateLogger();

    b.AddSerilog(seriLogger);
});

ServiceProvider provider = serviceCollection.BuildServiceProvider();

var loggerFactory = provider.GetService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger("category");

logger.LogInformation("Hello {name}""world");

这样就会得到以下的日志:

{
    "Timestamp":"2019-03-24T19:38:54.4833240+08:00",
    "Level":"Information",
    "MessageTemplate":"Hello {name}",
    "Properties":{"name":"world","SourceContext":"category"}
}

如果还需要 formatter 格式化之后的完整消息,可以在创建 JsonFormatter 时指定 new JsonFormatter(renderMessage: true) 这样就会得到包含完整可读消息的结果:

{
    "Timestamp":"2019-03-24T19:44:51.0430260+08:00",
    "Level":"Information",
    "MessageTemplate":"Hello {name}",
    "RenderedMessage":"Hello \"world\"",
    "Properties":{"name":"world","SourceContext":"category"}
}

这样,在实际的操作中。我们可以直接使用 Microsoft.Extension.Logging 默认体系对 ILoggerProvider 进行扩展达到对记录目标和记录格式的控制;也可以将其与 SeriLog 集成。通过 Sink 和 ITextFormatter 组合的方式分别对记录目标和记录格式进行控制。

总结

一图胜千言:

640?wx_fmt=png

图-1 默认 Microsoft.Extension.Logging 类型及信息传递路径

640?wx_fmt=png

图-2 集成 SeriLog 后类型及信息传递路径

如果您觉得本文对您有帮助,也欢迎分享给其他的人。我们一起进步。欢迎关注我的博客(https://clrdaily.com)和微信公众号:

640?wx_fmt=jpeg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/316397.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

为什么我们要做单元测试?(二)

引子当我第一篇博客发布&#xff0c;并被张善友老师的公众号转载之后&#xff0c;在公众号文章和博客园的留言中&#xff0c;许多开发者纷纷表示&#xff0c;单元测试作为企业行为&#xff0c;与实施的技术栈不同&#xff0c;不是开发者个人行为&#xff0c;实施单元测试花费的…

P4159 [SCOI2009] 迷路

P4159 [SCOI2009] 迷路 题意&#xff1a; 该有向图有 n 个节点&#xff0c;节点从 1 至 nn 编号&#xff0c;windy 从节点 1 出发&#xff0c;他必须恰好在 t 时刻到达节点 n。 现在给出该有向图(带边权)&#xff0c;你能告诉 windy 总共有多少种不同的路径吗&#xff1f; …

程序员修神之路--提高网站的吞吐量

点击上方蓝色字体&#xff0c;关注我们菜菜哥&#xff0c;有个事你还得帮我呀呦西&#xff0c;YY妹子&#xff0c;最近天这么热了&#xff0c;你怎么还穿这么多&#xff1f;苦笑一下.....前几天写了几个接口&#xff0c;领导让提高一下接口吞吐量这是你技术提高的大好机会呀可吞…

一份.NET 容器化的调查小结

小编在上个月在微信公众号“dotnet跨平台” 做了一个针对.NET 容器化的调查&#xff1a;.NET Core 容器化调查&#xff0c;参与人数702人&#xff0c;由于软件定义基础设施方兴未艾&#xff0c;编排和自动化领域kubernetes占据了主体地位&#xff0c;在平时的工作中和身边的同学…

P2148 [SDOI2009]ED

P2148 [SDOI2009]E&D 题意&#xff1a; 有2n堆石子&#xff0c;第2k-1堆和第2k堆是一组&#xff0c;现在两个人轮流操作&#xff0c;每次操作任选一组石子&#xff0c;然后将改组中的一堆石子移走&#xff0c;将另一堆式子分割成两堆&#xff0c;形成新的两堆石子&#x…

扒一扒.NET Core的环境配置提供程序

前言很久之前&#xff0c;在玩Docker的时候顺便扒了扒&#xff0c;最近&#xff0c;终于下定决心花了些时间整理并成文&#xff0c;希望能够给大家一些帮助。目录 .NET Core中的配置ASP.NET Core中的配置扒一扒环境变量提供程序为什么是“__”&#xff1f;“__”如何变成了“&…

[HNOI2016] 序列(线段树 + 莫队 + 倍增)

problem luogu-P3246 心路历程卡常历程问题存疑 一直在想莫队的做法。发现左右指针的移动对应一段左/右端点固定的子序列&#xff0c;然后可以一个数代表一段相同的贡献。 就开始求 lsti,nxtilst_i,nxt_ilsti​,nxti​ 了。 仔细想想需要找到 lstlsti<l≤lstilst_{lst_…

《从零开始学ASP.NET CORE MVC》:ASP.NET Core 中的 Main方法(5)

本文出自《从零开始学ASP.NET CORE MVC》推荐文章&#xff1a;ASP.NET Core Web 项目文件ASP.NET Core 中的 Main方法一个开始专心写字的人在ASP.NET Core项目中&#xff0c;我们有一个名为Program.cs的文件。在这个文件中&#xff0c;我们有一个public static void Main&#…

.NET中的状态机库Stateless

标题&#xff1a;.NET中的状态机库Stateless 作者&#xff1a;Lamond Lu 地址&#xff1a;https://www.cnblogs.com/lwqlun/p/10674018.html[1]介绍什么是状态机和状态模式状态机是一种用来进行对象建模的工具&#xff0c;它是一个有向图形&#xff0c;由一组节点和一组相应的转…

.net core webapi 前后端开发分离后的配置和部署

背景&#xff1a;现在越来越多的企业都采用了在开发上前后端分离&#xff0c;前后端开发上的分离有很多种&#xff0c;那么今天&#xff0c;我来分享一下项目中得的前后端分离。B/S Saas 项目&#xff1a;&#xff08;这个项目可以理解成个人中心&#xff0c;当然不止这么点功…

ASP.NET Core使用Jaeger实现分布式追踪

前言最近我们公司的部分.NET Core的项目接入了Jaeger&#xff0c;也算是稍微完善了一下.NET团队的技术栈。至于为什么选择Jaeger而不是Skywalking&#xff0c;这个问题我只能回答&#xff0c;大佬们说了算。前段时间也在CSharpCorner写过一篇类似的介绍Exploring Distributed T…

长沙开发者技术大会暨.NET技术社区成立大会倒数第13天

待你扬帆起航&#xff0c;一起精彩纷呈&#xff01;长沙开发者技术大会暨.NET技术社区成立大会倒数第13天&#xff01;2019年4月21日期待与你相聚在.NET技术社区&#xff01;我们今天会完成海报制作和报表表单&#xff0c;海报内容初步如下所示&#xff1a;活动信息 长沙开发者…

C#并行编程(1):理解并行

什么是并行并行是指两个或者多个事件在同一时刻发生。在程序运行中&#xff0c;并行指多个CPU核心同时执行不同的任务&#xff1b;对于单核心CPU,严格来说是没有程序并行的。并行是为了提高任务执行效率&#xff0c;更快的获取结果。与并发的区别&#xff1a;并发是指两个或者多…

P2163 [SHOI2007]园丁的烦恼(二维数点模板题)

P2163 [SHOI2007]园丁的烦恼 题意&#xff1a; 在一个二维平面内有一些点&#xff0c;给你一个左上角和右下角的点&#xff0c;问这个范围内有多少点 题解&#xff1a; 二维数点模板题 我们设F(a,b)表示以(0,0)为左下角&#xff0c;(a,b)为右上角的矩阵内有多少点 如图不难…

Orleans MultiClient 多个Silo复合客户端

介绍Orleans.MultiClient 是一个 Orleans 复合客户端&#xff0c;只需要简单配置就可以简单高效连接和请求 Orleans 服务。Orleans.MultiClient 可以轻松连接多个不同服务的 Orleans 服务,在请求 Orleans 时会根据请求的接口自动寻找 Orleans 客户端&#xff0c;使用者无需关心…

ASP.NET Core 进程内(InProcess)托管(6)《从零开始学ASP.NET CORE MVC》:

本文出自《从零开始学ASP.NET CORE MVC》推荐文章&#xff1a;ASP.NET Core 中的 Main方法ASP.NET Core 进程内(InProcess)托管在这个视频中我们将讨论在ASP.NET Core中的进程内(InProcess)托管模型什么是Kestrel服务器当一个 ASP.NET Core 应用程序执行的时候&#xff0c;.NET…

约会安排 HDU - 4553

约会安排 HDU - 4553 题意&#xff1a; 题意又丑又长就不叙述了 题解&#xff1a; 这个题一开始理解错了。。。题目相当于是有三种情况占据时间&#xff0c;分别是学习&#xff0c;女神和屌丝&#xff0c;我们用不同的lazy来表示女神和屌丝&#xff0c;根据优先级去更新状态…

ML.NET机器学习、API容器化与Azure DevOps实践(一):简介

打算使用几篇文章介绍一下.NET下的机器学习框架ML.NET的具体应用&#xff0c;包括一些常用的业务场景、算法的选择、模型的训练以及RESTful API的创建、机器学习服务容器化&#xff0c;以及基于Azure DevOps的容器化部署等等相关的内容。如果你从来没有玩过机器学习&#xff0c…

Picture POJ - 1177(矩形周长并))

Picture POJ - 1177 题目&#xff1a; 多个矩阵相交在一起&#xff0c;问新图形的周长是多少 题解&#xff1a; 参考题解 周长分为两部分&#xff1a;横线和竖线 横线计算方法&#xff1a;现在总区间被覆盖的长度和上一次总区间被覆盖的长度之差的绝对值 那么我们只需要从…

聊一聊C# 8.0中的await foreach

很开心今天能与大家一起聊聊C# 8.0中的新特性-Async Streams,一般人通常看到这个词表情是这样.简单说,其实就是C# 8.0中支持await foreach.或者说,C# 8.0中支持异步返回枚举类型async Task<IEnumerable<T>>.好吧,还不懂?Good,这篇文章就是为你写的,看完这篇文章,你…