结构化日志记录与基本日志记录的优点


110

我们正在构建一个新的应用程序,我想包括结构化日志记录。我理想的设置应该是SerilogC#代码和BunyanJS。fluentd我最初想到的是,这些东西会进入然后可能会涉及到许多事情elasticsearch + kibana。我们已经有一个MySQL数据库,因此在短期内,我对获得Serilog + Bunyan设置和开发人员使用它更感兴趣,我们可以登录MySQL,同时花更多的时间来流利地进行其余工作。

但是,我们经验丰富的编码人员之一更喜欢做类似的事情:log.debug("Disk quota {0} exceeded by user {1}, quota, user);使用log4net然后对MySQL运行select语句,例如:select text from logs where text like "Disk quota";

话虽这么说,哪种方法更好,和/或在选择日志系统类型时我们需要考虑什么?


我同意所做的编辑。我不是在试图向某人证明某些东西,而是在试图了解结构化日志记录和基本日志记录的好处和区别。在我看来,结构化为我们提供了更大的灵活性,尤其是在日志源以及如何显示其数据方面。就我的理解而言,我无法解释为什么基本日志记录和搜索MySQL比结构化日志记录更好/更糟糕。
DTI-Matt

2
@ DTI-Matt serilog的结构化日志记录只是基本的日志记录,只有它会格式化您要打印到其中的对象,您可以通过轻松地重写ToString来完成自己的工作。一个更重要的方面是日志文件的配置和管理,而不是一种将字符串格式化为另一种格式的方法,另一种是性能。如果开发人员想要使用log4net(这是一个很好的日志记录库),那么您选择serilog(看起来很酷)就是那些“寻找问题的解决方案”。
gbjbaanb

@ DTI-Matt从serilog来看,它看起来与log4net非常相似。log4net处理在config上创建结构化日志。您无需搜索日志消息,因为可以配置其他信息并将其写入表中。还配置fluentd log4net的tipstuff.org/2014/05/...
RubberChickenLeader

当心有些傻瓜在这里不理解概念性问题的想法。询问数据库应用程序的方向以尝试掌握其ETL功能v。代码将使您感到严重不满。我想您的问题也将放在砧板上。
user3916597'3

2
@gbjbaanb Serilog在将事件呈现为文本时,其工作原理与log4net相同,但是如果您使用结构化格式存储日志,则会将命名属性与所传递的参数相关联(即,支持不使用正则表达式进行搜索/过滤等)。 )HTH!
Nicholas Blumhardt

Answers:


138

结构化方法有两个基本的进步,如果没有(有时是极端的)额外的努力,就无法使用文本日志来模拟。

活动类型

当您使用log4net编写两个事件时,例如:

log.Debug("Disk quota {0} exceeded by user {1}", 100, "DTI-Matt");
log.Debug("Disk quota {0} exceeded by user {1}", 150, "nblumhardt");

这些将产生类似的文本:

Disk quota 100 exceeded by user DTI-Matt
Disk quota 150 exceeded by user nblumhardt

但是,就机器处理而言,它们只是两行不同的文本。

您可能希望找到所有“磁盘配额已超出”事件,但是like 'Disk quota%'一旦另一个事件发生,查找事件的简单情况就会下降:

Disk quota 100 set for user DTI-Matt

文本记录会丢弃我们最初获得的有关事件源的信息,并且在读取日志时通常必须使用越来越复杂的匹配表达式来重新构造该信息。

相比之下,当您编写以下两个Serilog事件时:

log.Debug("Disk quota {Quota} exceeded by user {Username}", 100, "DTI-Matt");
log.Debug("Disk quota {Quota} exceeded by user {Username}", 150, "nblumhardt");

它们产生与log4net版本相似的文本输出,但是在后台,两个事件都携带了"Disk quota {Quota} exceeded by user {Username}" 消息模板

使用适当的接收器,以后您可以编写查询where MessageTemplate = 'Disk quota {Quota} exceeded by user {Username}'确切获取超出磁盘配额的事件。

在每个日志事件中存储整个消息模板并不总是很方便,因此有些接收器会将消息模板的哈希值哈希成一个数字EventType值(例如0x1234abcd),或者,您可以在日志记录管道中添加一个扩展器以自己完成

它比下面的下一个差异更加微妙,但是在处理大量日志时却具有强大的功能。

结构化数据

再次考虑两个有关磁盘空间使用情况的事件,使用文本日志使用来查询特定用户可能很容易like 'Disk quota' and like 'DTI-Matt'

但是,生产诊断并不总是那么简单。想象一下是否有必要查找超出磁盘配额低于125 MB的事件?

使用Serilog,可以在大多数接收器中使用以下变量来实现:

Quota < 125

从正则表达式构造这种查询可能的,但是它很快就很累人,通常最终只能作为最后的手段。

现在添加一个事件类型:

Quota < 125 and EventType = 0x1234abcd

您开始在这里看到这些功能如何以直接的方式组合在一起,以使带有日志的生产调试感觉像是一流的开发活动。

另一个好处是,也许不是很容易预防,但是一旦将生产调试从regex黑客工具中解放出来,开发人员便开始更加珍惜日志,并在编写日志时更加谨慎。更好的日志->更好质量的应用程序->更加幸福。


4
我喜欢这个答案。写得非常好,由于某种原因我无法解释,这使我一直坐不到位。
jokab

16

当您收集日志进行处理时,使用结构化日志记录可以将其解析为某些数据库和/或稍后在处理后的日志中进行搜索,从而使某些处理更加容易/高效。解析器可以利用已知的结构(例如 JSON,XML,ASN.1等),并使用状态机进行解析,而不是使用正则表达式(相对于正则表达式而言,编译和执行在计算上相对昂贵)。诸如同事建议的那样,对自由格式文本的解析往往依赖于正则表达式,并且依赖于不变的文本。这会使解析自由格式的文本变得非常脆弱(即,解析与代码中的确切文本紧密耦合)。

还考虑搜索/查找的情况,例如

SELECT text FROM logs WHERE text LIKE "Disk quota";

LIKE条件需要与每个text行值进行比较;再次,这在计算上是相对昂贵的,尤其是在使用通配符时:

SELECT text FROM logs WHERE text LIKE "Disk %";

使用结构化日志记录,与磁盘错误相关的日志消息在JSON中可能如下所示:

{ "level": "DEBUG", "user": "username", "error_type": "disk", "text": "Disk quota ... exceeded by user ..." }

这种结构的字段可以很容易地映射到例如 SQL表列名,这意味着查找可以更具体/更细粒度:

SELECT user, text FROM logs WHERE error_type = "disk";

您可以在希望经常搜索/查找其值的列上放置索引,只要您不对LIKE这些列值使用子句即可。您可以将日志消息细分为特定类别的内容越多,查找的对象就越有针对性。例如,除了error_type上面示例中的字段/列之外,您甚至可以设置为be "error_category": "disk", "error_type": "quota"或诸如此类。

结构越多,你有你的日志消息,更您解析/检索系统(如fluentdelasticsearchkibana),可以利用该结构,并以更快的速度和更低的CPU /内存执行任务。

希望这可以帮助!


1
+1想补充一点,这不仅与速度和效率有关。使用结构化日志记录和“结构化查询”时,搜索结果的相关性将更高。如果不进行这种搜索,那么在不同上下文中出现的任何单词都会给您带来大量无关的点击。
Marjan Venema

1
我也+1,我认为这很重要。在下面添加了稍微不同的表述,以扩展事件类型的情况。
Nicholas Blumhardt

8

当您的应用每天创建数百条日志消息时,您将不会从结构化日志中获得太多好处。当您每秒收到数百条来自许多已部署应用程序的日志消息时,您肯定会。

相关的是,日志消息最终存储在ELK堆栈中的设置也适用于扩展规模,在这种情况下,登录SQL成为瓶颈。

我已经看到使用SQL select .. like和正则表达式将“基本日志记录和搜索”的设置推到了极限,在这种情况下,它会崩溃-存在误报,遗漏,可怕的过滤器代码以及难以维护的knwon错误,而且没人想碰,不遵循过滤器假设的新日志消息,不愿触摸代码中的日志记录语句,以免破坏报告,等等。

因此,出现了一些软件包来更好地解决此问题。有Seri​​log,我听说NLog团队正在研究它,我们StructuredLogging.Json为Nlog撰写了文章,我还看到新的ASP.Net核心日志记录抽象 “使日志记录提供程序可以实现...结构化日志记录”。

使用StructuredLogging的示例。您可以这样登录到NLog记录器:

logger.ExtendedError("Order send failed", new { OrderId = 1234, RestaurantId = 4567 } );

该结构化数据将发送至kibana。该值1234存储在OrderId日志条目的字段中。然后,您可以使用kibana查询语法搜索所有日志条目,例如@LogType:nlog AND Level:Error AND OrderId:1234

MessageOrderId现在只是一个可以搜索的精确或不精确的匹配,而你需要,和汇总数场。这是强大而灵活的。

根据StructuredLogging最佳实践

每次记录的消息应该相同。它应该是一个常量字符串,而不是格式化为包含ID或数量之类的数据值的字符串。然后很容易搜索。

记录的消息应该是不同的,即与无关日志语句产生的消息不同。然后搜索也不匹配无关的事物。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.