我们正在构建一个新的应用程序,我想包括结构化日志记录。我理想的设置是将C#代码设置为Serilog,将JS设置为Bunyan。这些将输入fluentd,然后可以处理任何事情,我最初是在考虑elasticsearch + kibana。我们已经有一个MySQL数据库,因此短期内我对获得Serilog + Bunyan设置和开发人员使用它更感兴趣,我们可以登录MySQL,同时花一些时间来流利地使用其余部分。 br />
但是,我们经验丰富的编码人员之一宁愿只执行以下操作:使用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 serilog的结构化日志记录只是基本的日志记录,只有它会格式化您要打印到其中的对象-您可以通过轻松地重写ToString来自己完成操作。一个更重要的方面是日志文件的配置和管理,而不是一种将字符串格式化为另一种的方式,而另一种则是性能。如果开发人员希望使用log4net(这是一个很好的日志记录库),那么您选择serilog(看起来很酷)就是那些“寻找问题的解决方案”。

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

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

#1 楼

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

事件类型

当您编写两个使用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");


它们产生相似的文本输出到lo g4net版本,但在幕后,两个事件都携带"Disk quota {Quota} exceeded by user {Username}"消息模板。

使用适当的接收器,以后可以编写查询where MessageTemplate = 'Disk quota {Quota} exceeded by user {Username}'并确切获取超出磁盘配额的事件。 br />
在每个日志事件中存储整个消息模板并不总是很方便,因此有些接收器会将消息模板散列为数字EventType值(例如0x1234abcd),或者,您可以在日志记录管道中添加增强器以自己完成此操作。

比下面的下一个差异要微妙,但是在处理大量日志时功能强大。

结构化数据

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

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

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

Quota < 125


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

现在向其中添加一个事件类型:

Quota < 125 and EventType = 0x1234abcd


您开始在这里看到这些功能的方式

另一个好处是,也许不是很容易预防,但是一旦将生产调试从工作中解放出来,这又是一个好处。在regex骇客之地,开发人员开始更加重视日志,并在编写日志时多加注意和考虑。更好的日志->更高质量的应用程序->更加幸福。

评论


我喜欢这个答案。写得非常好,由于某种原因我无法解释,这使我无法自拔。

– jokab
18年1月1日,1:15

#2 楼

当您收集日志进行处理时,无论是解析到某些数据库中和/或稍后搜索处理过的日志,使用结构化日志都会使某些处理更加容易/高效。解析器可以利用已知结构(例如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字段/列外,您甚至可以将其设置为"error_category": "disk", "error_type": "quota"或类似的名称。

日志消息中的结构越多,解析/搜索系统就越多(例如fluentdelasticsearchkibana)可以利用该结构,并以更高的速度和更少的CPU /内存执行其任务。

希望这会有所帮助!

评论


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

– Marjan Venema
16 Mar 10 '16 at 7:31



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

–尼古拉斯·布鲁姆哈特(Nicholas Blumhardt)
16-3-13的3:53

#3 楼

当您的应用每天创建数百条日志消息时,您将不会从结构化日志中获得太多好处。当您每秒从许多不同的已部署应用程序发出几百条日志消息时,您肯定会做到这一点。
相关的,日志消息最终存储在ELK堆栈中的设置也适合于将日志记录到SQL成为瓶颈的情况。
我已经看到使用SQL select .. like建立的“基本日志记录和搜索”设置,而正则表达式则被推到了极限,以至于无法解决-存在误报,遗漏,可怕的过滤器代码以及难以维护的knwon错误,而且没有-人们想触摸,不遵循过滤器假设的新日志消息,不愿触摸代码中的日志记录语句以免破坏报告等。
因此,出现了一些软件包来更好地解决此问题。 。有Seri​​log,听说NLog团队正在研究它,我们为Nlog编写了StructuredLogging.Json,我还看到新的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:1234Message只是OrderId和q4312079q的所有日志条目都可以根据需要搜索完全匹配或不完全匹配的字段,也可以合计计数。这是强大而灵活的。
从StructuredLogging最佳实践开始:

每次记录的消息应该相同。它应该是一个
常量字符串,而不是格式化为包含诸如
id或数量之类的数据值的字符串。这样就很容易搜索。
记录的消息
应该是不同的,即与
不相关的日志语句产生的消息不同。然后搜索不匹配
无关的事物。