Serilog是 .NET 中最著名的结构化日志类库

发布时间:2021-07-30 04:11:57

在过去的几年中,结构化日志已经大受欢迎。而Serilog是 .NET 中最著名的结构化日志类库 ,我们提供了这份的精简指南来帮助你快速了解并运用它。


    内容
    设定目标
    认识Serilog
    事件和级别
    触发和收集结构化数据
    为过滤和关联添加事件标记
    大海捞针 [Finding needles in the haystack]
    下一步是什么?
    获得帮助设定目标
    你可能之前已经在项目中使用了Serilog,或者你有一个新的项目希望使用Serilog,又或者你只是对结构化日志记录感兴趣: 那就非常好! 你来对地方了。

然而,更进一步来说,你的目标可能是:


希望在用户之前发现代码中的BUG和错误
为了更快的找到生产环境中的问题
深入的了解系统运行表现
Serilog使用json格式来记录应用程序中的事件,方便我们可以快速的查询日志,关键是可以方便地过滤和查询日志,而不用编写正则表达式。


在本教程中,我们将介绍最关键的几个部分,帮助我们可以在生成环境中提供令人惊叹的诊断能力。[注:原文这句挺拗口]


    认识 Serilog
    那就让我们开始吧!为了更好的理解,你可以先创建一个新的.Net console 项目,可以是netcore或者传统的NETFramework版本。

Serilog 通过NuGet分发,项目包括一个Seirlog核心项目Seirlog和很多接收器sinks(超过100个),这些接收是通过插件的方式来实现将日志写入到各种终端,文件,邮件,数据库或日志服务器


我们将通过使用Serilog和Serilog.Sinks.Console这两个组件开始,在稍后讨论其他选项:


dotnet add package Serilog
dotnet add package Serilog.Sinks.Console


通过nuget安装:
Install-Package Serilog
Install-Package Serilog.Sinks.Console


这是世界上最简单的Serilog配置:


using Serilog;


class Program
{
public static void Main(string[] args)
{
using (var log = new LoggerConfiguration()
.WriteTo.Console()
.CreateLogger())
{
log.Information(“Hello, Serilog!”);
log.Warning(“Goodbye, Serilog.”);
}
}
}
让我们稍微分解一下:


LoggerConfiguration 类提供一个流式接口用于构建一个日志记录管道
WriteTo.Console() 将控制台接收器添加到上述管道中
CreateLogger() 组装并返回一个实现ILogger接口的Logger对象
上述Logger对象同样实现了IDisposable,所以我们可以在using中调用它
最后log.Information()和log.Warning() 触发记录日志
这个日志记录管道是一个可释放的[disposable],这可能会让你有点意外,但是请记住,记录器通常是要写入文件,数据库等等: 很多sinks 必须被完全地关闭掉。尽管这样,也仅仅在应用程序退出前,根logger才需要被释放。而在应用程序中使用logger是不需要关心这些细节的。


你运行了这个程序嘛?这是你看到的效果吧?


Apart from just passing it around everywhere, there are two possibilities. [ 除了在各地传递外,还有两种可能性。] If you’re using an IoC container, you might have components receive an ILogger through dependency injection. [ 如果您使用的是IoC容器,则可能会让组件通过依赖注入来接收ILogger。] Packages like AutofacSerilogIntegration can help with that. [ 像AutofacSerilogIntegration这样的软件包可以提供帮助。]


现在最直接的问题是:我们在应用程序的其他类里面如何获得这个log对象,除了到处传递之外,还有两个办法。


如果你使用IoC容器,你可以组件注入一个ILogger对象来接收,像AutofacSerilogIntegration的包括帮助你实现这种方式。
或者,您可以将Logger对象存储在众所周知的位置; Serilog 已经内容内置了一个静态的Log对象,就像这样:


Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.CreateLogger();


Log.Information(“Hello again, Serilog!”);


Log.CloseAndFlush();
Log类提供所有与ILogger接口相同的方法,这里我们显示调用Log.CloseAndFlush()来关闭它,而不是使用using代码块


你可以使用依赖注入的方式,也可以是静态属性的方式 - 这取决你的个人喜好问题。为了简单起见,我们在本教程中使用了静态Log的方式。


也许,你不是在编写一个控制台应用程序。我们将使用Console应用作为广为人知的示例,但是你一旦完成了本教程,您应该查看目标*台的文档(例如,ASP.NET Core)。


    Event and Level [事件和级别]
    和一些老的日志类库相比(如log4net),在使用Serilog时,你需要做的就是最大改变就是思考日志事件[log events],而不是日志消息[log message],一条事件[event]由以下几个内容组成:

事件发生时的时间戳[timestamp]
描述何时应该捕获事件的级别[level]
记录事件的消息[message]内容]
描述事件的命名属性[properties]
还可能有一个Exception对象
您可以将日志事件格式化为控制台的可读文本,正如我们在第一个示例中看到的那样:


11:33:01 [INF] Hello, Serilog!
或者,您可以将相同的事件格式化为JSON并将其发送到远程日志服务器:


json {"@t":“2017-11-20T11:33:01.22138”,"@m":“Hello, Serilog!”}


在背后,应用程序中的日志语句会创建LogEvent对象,而连接到管道的接收器[sinks]会知道如何记录它们。


Logging levels

Serilog速度很快,但始终构建和记录详细的日志事件会浪费CPU,磁盘和网络资源。为了管理这个,Serilog事件被赋予了多种级别:Debug, Information, Warning 和 Error等。对应的有一个Log.*()方法来对应各个级别。


在开发过程中,可能会打开调试级别的事件:


Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug() // <- Set the minimum level
.WriteTo.Console()
.CreateLogger();

// In a tight loop...
Log.Debug("Processing item {ItemNumber} of {ItemCount}", itemNumber, itemCount);


在生成环境中,通常关闭调试的日志,并将最小的日志级别设置成Information,以便只记录重要的事件,在这篇文档中可以获得有关Serilog Logger Lever的更多信息


Tip: Serilog has special handling for Exception objects; methods like Log.Error() take the exception as the first parameter, e.g. [ 提示:Serilog对Exception对象有特殊的处理;] Log.Error(ex, “Task {TaskName} was canceled”, task). [ Log.Error(例如,“任务{任务名称}被取消”,任务)。] Don’t include exceptions in the log message itself. [ 不要在日志消息本身中包含异常。]
提示:Serilog对Exception对象有特殊的处理; 像方法Log.Error() 将 exception 作为第一个参数,例如Log.Error(ex, “Task {TaskName} was canceled”, task),不要将异常的包括在message消息中


    触发和收集结构化数据
    让我们回到最后一个代码片段:

var itemNumber = 10;
var itemCount = 999;
Log.Debug(“Processing item {ItemNumber} of {ItemCount}”, itemNumber, itemCount);
您是否注意到日志消息中的{ItemNumber}这样的占位符? 这不是一个C#的内插字符串[Interpolated string C# 6.0的新特性],Log.*()方法接收一个消息模板,另外一种.NET格式化字符串,除了支持传统的{0}的方式,还支持{Name}的方式。


这看起来有点奇怪,除非您意识到通过这样做,Serilog可以将这些消息的一部分作为类的属性与人性化的文本一起捕获:


{
“@t”: “2017-11-20T11:33:01.22138”,
“@l”: “Debug”,
“@m”: “Processing item 10 of 999”,
“ItemNumber”: 10,
“ItemCount”: 999
}
我们为什么要这样做?因为通过这种有趣的字段插入方式,并作为属性记录到事件日志中,我们可以立即使用优雅的简单的过滤器来查找事件,就像ItemNumber > 900,而无需通过正则表达式从消息中提取了。


进一步,我们可以使用 @ 结构捕获运算符 来获取不仅仅是*坦的属性值,而是完整的对象:


var user = new { Name = “Nick”, Id = “nblumhardt” };
Log.Information(“Logged on user {@User}”, user);
在这里,user对象被捕获,并生成的JSON中,以便我们可以使用查询来查找事件,如:User.Id = ‘nblumhardt’


{
“@t”: “2017-11-20T11:33:01.22138”,
“@m”: “Logged on user {“Name”: “Nick”, “Id”: “nblumhardt”}”,
“User”: {“Name”: “Nick”, “Id”: “nblumhardt”}
}
生产环境的监控和调试已经非常困难和耗时,而且经常是压力山大的任务:而通过将相关的数据放在手边,Serilog除去了运维操作相关活动的最大难题之一。


Tip: 从Visual Studio Marketplace安装令人惊叹的Serilog Analyzer,可以帮助你检查你的消息模板类型( 注:这个插件还能帮你通过配置代码生成appsetting.json的内容,但是只支持生成一级配置:( )


这实际上有多大的差异取决于你如何收集Serilog的事件。一般来说,日志事件进入文本文件并用grep进行搜索。Serilog也可以记录文本文件,但不能在记事本中执行ItemNumber> 900,因此您需要评估更强大的工具来执行此操作。


写 JSON 格式的日志文件


如果您的需求很简单,您可以将JSON写入日志文件,并使用支持JSON的工具直接查询文件。] [ Serilog的文件接收器[sink]和紧凑的JSON格式化类库[compact JSON formatter]使第一部分变得简单。 让我们重新建一个控制台应用程序,并安装下列软件包:


dotnet add package Serilog
dotnet add package Serilog.Sinks.File
dotnet add package Serilog.Formatting.Compact
在Main函数中插入:


Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.File(new CompactJsonFormatter(), “log.clef”)
.CreateLogger();


var itemCount = 99;
for (var itemNumber = 0; itemNumber < itemCount; ++itemNumber)
Log.Debug(“Processing item {ItemNumber} of {ItemCount}”, itemNumber, itemCount);


Log.CloseAndFlush();
运行这个程序将产生使用Serilog的紧凑格式[compact],在文件log.clef中生成以换行符分隔的JSON流,如果没有使用CompactJsonFormatter,则会创建一个简单饿扁*日志文件。


如果你在文本编辑器中打开文件,你会看到JSON事件,就像我们上面使用的例子。


CLEF-tool 是查询CLEF格式的日志文件的方便的命令行应用程序:(注:貌似只支持windows)


注意第二行的过滤器ItemNumber> 95:毫不费力地在大型日志流中定位事件,就是结构化日志记录的好处吧。


将日志写入日志服务器


将日志事件从多个应用程序发送到中央服务器或日志聚合服务非常方便,而不是试图在生产环境中多个日志进行浑水摸鱼[shuffle log files]


日志服务器通常通过HTTP/S或UDP在网络上接收事件,并为开发人员和操作员工程师提供用户界面,以便在出现问题时搜索和分析日志流。


Serilog接收器[sinks]支持大量的日志服务器,其中许多具有结构化数据支持。


注:这段是广告就不翻译了,读者可以根据实际需求选择自己的日志服务器。


    为过滤和关联标记事件
    我们刚刚看到消息模板如何实现我们传统上认为可以有效搜索和分析的日志“消息”。

结构化日志记录的另一方面是通过某种因果关系或空间关联来识别相关事件集合。事件触发:[Events raised: ]


在处理单个HTTP请求期间
从特定的机器,应用程序,服务或组件
关于单个客户,订单或交易
起因于事件的因果链
Serilog通过enrichment来处理所有这些情况(以及其他情况)。Enrichment只是为事件添加附加属性,而不是源自消息模板的属性


添加[Enriching]特定的属性


最简单的enrichment方法将固定属性值添加到源自日志记录管道的所有事件,可以通过Enrich.WithProperty()方法快速实现


Log.Logger = new LoggerConfiguration()
.Enrich.WithProperty(“Application”, “Demo”)
.WriteTo.Seq(“http://localhost:5341”) //Seq 日志服务器
.CreateLogger();
在LogEvents上,通过enrichment添加的属性看起来与源自消息模板的属性相同


json { “@t”: “2017-11-20T11:33:01.22138”, “@l”: “Debug”, “@m”: “Processing item 10 of 999”, “ItemNumber”: 10, “ItemCount”: 999, “Application”: “Demo” }


丰富特殊的属性


通过创建和使用上下文记录器,可以将属性添加到一个或几个相关事件中,而不是增加具有相同值的所有事件


var orderLog = Log.ForContext(“OrderId”, order.Id);
orderLog.Information(“Looking up product codes”);
// …
orderLog.Information(“Product lookup took {Elapsed} ms”, elapsed.TotalMilliseconds);
在这里,通过orderLog发出的两个事件都会附加一个OrderId属性。
Enrichmen是附加的:如果应用程序属性设置在管道级别,则上面的第二个事件将携带Elapsed(来自消息),OrderId(来自上下文记录器)和Application(来自记录管道)。


丰富消息源类型信息


记录器特定的enrichment一个特例是关于如何使用创建它们的类标记事件


在名为HomeController的类中,使用以下命令创建类型特定的记录器:


private readonly ILogger _log = Log.ForContext();
通过_log发出的事件将携带一个值为MyApp.Controllers.HomeController的SourceContext属性。


充分利用上下文


为了丰富工作单元中所有事件[为所有事件添加特定属性],Serilog提供了LogContext,首先需要使用Enrich.FromLogContext()在LoggerConfiguration级别启用:


Log.Logger = new LoggerConfiguraition()
.Enrich.FromLogContext()
// …
LogContext可以被认为是一堆(key,value)键值对;


using (LogContext.PushProperty(“MessageId”, message.Id))
{
Log.Debug(“Dispatching message of type {MessageType}”, message.GetType());
await handler.HandleAsync(message);
}
关于LogContext有趣的是没有什么需要对象需要传递。在示例代码中,HandleAsync()以及由它调用的任何其他方法的实现可以直接使用Log和ILogger - MessageId属性将自动T并添加LogEvent中。


Tip: LogContext是一个堆栈;推送到堆栈上的属性必须通过释放从PushProperty()返回的对象, ? 上述通过手动使用using块的方式


已经存在的Enricher


所有enrichment API都是使用Enricher的实现Serilog的ILogEventEnricher接口的对象来实现的。


NuGet中为线程细节,机器信息和用户详细信息等内容提供了一些有趣的预先构建的Enricher实现。


Serilog.Enrichers.Thread 通过 Enrich.WithThreadId() 来添加线程ID相关的扩展:


Log.Logger = new LoggerConfiguration()
.Enrich.WithThreadId()
// …
这将为事件附加一个ThreadId属性,以便交错事件可以再次分开。


我们将在下一节中看到一个简单的例子,说明如何编写和插入自己的应用程序专用的Enricher程序。


    大海捞针 Finding needles in the haystack
    如果我们已经知道如何使用Serilog调用消息模板和enrichment结构化日志的两个支柱,那么第三个支柱就是隐式事件类型的概念。

结构化日志适合有效处理大量日志数据。关于大型日志流的一个有趣的观察是,真实产生的事件比编写日志语句代码块时要多的多(注:这算什么发现)


Log.Debug(“Processing item {ItemNumber} of {ItemCount}”, itemNumber, itemCount);
这意味着,尽管生成了许多独特的消息字符串,如"Processing item 31 of 4159",但由此日志记录语句生成的每个事件共享相同的消息模板,即"Processing item {ItemNumber} of {ItemCount}"


Serilog及其许多sinks 利用这一事实从根本上改进了查询和过滤日志事件的体验。如果消息模板与事件一起保存,则下面的过滤器可以立即从嘈杂的日志记录语句中排除成千上万的事件,从而更容易看到否则会被淹没的有趣事件:


@MessageTemplate <> ‘Processing item {ItemNumber} of {ItemCount}’
反转也适用 - 放大事件类型可以从单个日志记录语句中查找所有事件


如何利用此功能取决于您存储和搜索日志的位置。接下来我们会看看细节。


Tip:字符串链接,C#内插字符串,以及其他技术手段来预格式化传递给Serilog的消息内容,会取消此功能。详细请看 How (not) to parameterize Serilog events


隐式事件类型


存储,然后过滤罗嗦的消息模板并不总是理想的。 相反,通常从消息模板创建一个数字哈希值,并将其与事件一起存储:


事件类型 enrichment


日志文件和本地不支持消息模板的日志服务器仍然可以通过在Serilog管道中明确地enricher事件来接收事件类型。


为此,自定义enricher程序将EventType属性附加到每个事件


// Install-Package Murmurhash


class EventTypeEnricher : ILogEventEnricher
{
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
var murmur = MurmurHash.Create32();
var bytes = Encoding.UTF8.GetBytes(logEvent.MessageTemplate.Text);
var hash = murmur.ComputeHash(bytes);
var numericHash = BitConverter.ToUInt32(hash, 0);
var eventType = propertyFactory.CreateProperty(“EventType”, numericHash);
logEvent.AddPropertyIfAbsent(eventType);
}
}
当插入管道时,这会使{EventType}属性可用于sinks


Log.Logger = new LoggerConfiguration()
.Enrich.With()
.WriteTo.Console(outputTemplate:
“{Timestamp:HH:mm:ss} [{EventType:x8} {Level:u3}] {Message:lj}{NewLine}{Exception}”)
.CreateLogger();
WriteTo.Console()的参数是一个Serilog输出模板,描述了如何将日志事件的属性格式化为文本。 大多数基于文本的sinks(包括Serilog.Sinks.File)都可以接受输出模板来指导格式化。


[ 输出如下所示:]


相关文档

  • 迎新年庆元旦绘画图片大全
  • 在家乡的海滩看海鸥
  • 测绘仪器使用方法 关于新技术、新仪器在城市测绘中的应用
  • 女性经期是否能献血 女性献血注意事项和保养法则
  • 宝宝奶量不大频繁堵奶怎么办如何缓解堵奶痛疼
  • 万维网的发展和产生
  • 部分html头部的标签的作用
  • 合同范本300例精选多篇
  • 植物一帆风顺怎么养护
  • 苹果相机夜间模式怎么设置苹果相机夜间模式如何设置
  • 关于公路货物运输的合同范本
  • 我的java,我的梦--开篇
  • 融媒体中心工作总结
  • 遇到喜欢的人已经结婚了怎么办呢
  • 掌握8个小诀窍 让唇彩更出色
  • 快手昵称后面的数字代表什么
  • 高达的NT指什么
  • 一般使用lambda表达式来写Thread的run方法
  • 意林励志读物介绍
  • 百合网相亲自我介绍
  • 餐厅收银辞职信怎么写
  • wireshark抓包工具使用 示例分析TCP三次握手
  • 有没有像盛爱第一夫人一样的小说
  • 记事文含泪的祝福
  • PHP实现留言板功能实例代码
  • 吃燕窝上火吗 炖燕窝加什么好
  • 大卫科波菲尔读书笔记_大卫科波菲尔阅读笔记
  • 橄榄油的作用与营养价值
  • 冰与火的青春所有歌曲
  • 荣耀play内存卡怎么用设置
  • 猜你喜欢

  • 人教版小学三年级语文下册期末试卷集(8份).doc-精品推荐
  • 2018-求职简历里自我评价-推荐word版 (4页)
  • 2018年北师大版七年级下册数学4.2图形的全等课件22张PPT
  • 【参考文档】赚钱好难为话题的600字作文-范文模板 (1页)
  • 三年级上册数学课件-第3单元第4课时 综合实践周长是多少_苏教版(2014秋) (共16张PPT)
  • 【配套K12]三年级英语上册 Unit 3 Can you sing说课稿教案 广东版开心
  • 关岭中机能源有限公司(企业信用报告)- 天眼查
  • 初中英语创新能力培养论文
  • 浅谈城管执法工作中存在问题的思考与措施论文
  • hdu1099
  • linux下利用dd和iso文件制作启动盘方法
  • 第19章糖蛋白、蛋白聚糖和细胞外基质2011-文档资料
  • 济南中大净化设备有限公司(企业信用报告)- 天眼查
  • 2016_2017学年广东广州越秀区初一下学期期末英语试卷
  • 户县摆汤面
  • 模具维修记录 (2)
  • 七年级下册期中考试语文试题共7页
  • Excel在生物统计学双因素无重复方差分析中的应用
  • 固安县重大危险源单位现场核查登记表
  • 编童话故事小学三年级作文指导 2
  • 描写形容爱情长久的句子
  • 贵州嘉晨锋锐电气有限公司(企业信用报告)- 天眼查
  • 广州番禺宇联电子技术有限公司企业信用报告-天眼查
  • 梦见甲板
  • 应届生求职自荐信推荐5篇
  • 在困境中磨炼出美作文
  • 教师研讨会的学习心得感悟
  • 北京奥运会可能产生的负面效应的二维对策分析
  • 基于UML的仓储管理系统的设计与实现
  • 橡胶促进剂CBS废水处理的初步试验
  • 老年癌症患者的心理干预对患者生活质量的影响
  • 上海筑邦测控科技有限公司陕西分公司企业信用报告-天眼查
  • 应聘土建施工员面试注意哪些
  • 孕期检查之血常规检查全解析 怀孕之前一定要做的孕前检查
  • 小学班级管理中存在的问题及优化策略
  • 夏普LCD-46G120A与夏普LCD-46GE220A哪个好
  • 学校关于厉行节约压缩公用经费开支的实施办法
  • 厦门湖里万达广场投资有限公司(企业信用报告)- 天眼查
  • 2011年初级会计资格考试初级会计实务习题-第十一章财务管理基础
  • 2018年机关办公室干部瑟肽旯ぷ鞲鋈俗芙岱段挠氩018年机关办公室年终工作总结范文汇编.doc
  • 大学生面试自我介绍范文
  • 北京瀚琳商贸有限公司(企业信用报告)- 天眼查
  • 电脑版