2月,在锁定之前,我在ConFoo上发表了我的Fast log演讲。 最后,我有一个有趣的问题,由于时间紧迫,我不得不缩短。 这篇博客文章旨在描述演讲的相关要点,问题以及一些可能的答案。
具有更快日志的一项改进
在本演讲中,我重点介绍了提高日志速度的不同方法。 如今,大多数日志都汇总到一个地方,以备后用。 例如,一种广泛的体系结构是基于弹性堆栈的:
尽管此设计有效,但它具有许多组件。 特别是,Logstash在这里可以解析JSON格式的原始日志行,以便Elasticsearch可以分别索引每个字段。
2020-02-29 13:56:54.906 INFO 1 --- [ main] c.f.f.Main : Informative message
{
"date": [[ "20-02-29" ]],
"MONTHDAY": [[ "29" ]],
"MONTHNUM": [[ "02" ]],
"YEAR": [[ "20" ]],
"time": [[ "13:56:54.906" ]],
"HOUR": [[ "13" ]],
"MINUTE": [[ "56" ]],
"SECOND": [[ "54.906" ]],
"level": [[ "INFO" ]],
"threadName": [[ "main" ]],
"class": [[ "c.f.f.Main" ]],
"message":
[[ "Informative message" ]]
}
一种改进是使用一种模式配置日志记录框架,以便它直接写入JSON格式的单行日志。 下面显示了如何使用Logback做到这一点:
<configuration>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>/var/log/sample.log</file>
<encoder>
<pattern>{ "date": "%d{dd-MM-yy}", "time": "%d{HH:mm:ss.SSS}", "thread": "%thread", "level": "%-5level", "className": "%logger{36}", "message": "%msg" }%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="FILE" />
</root>
</configuration>
现在的输出如下:
{ "date": "29-02-20", "time": "23:13:53.640", "thread": "main", "level": "INFO", "className": "c.f.f.Main", "message": "Informative message" }
这允许将日志直接发送到Elasticsearch,从而通过完全删除Logstash来简化架构:
添加其他元数据
现在,假设企业出于日志目的还需要除日志消息之外的其他字段。 使用简单的字符串消息,这就像获取对值的引用并将其添加到字段中一样容易:
logger.info("Client-Id -> {} | Informative message",client.getId());
在正确的日志级别下执行时,将输出所需的消息:
2020-02-29 13:56:54.906 INFO 1 --- [ main] ch.frankel.fastlogs.Main : Client-Id -> XYZ | Informative message
使用Logstash,可以改善groking模式,使其成功解析Client-Id
值并将其作为专用字段发送到Elasticsearch。 不幸的是,上面显示的JSON格式的日志选项不允许这样做:
{ "date": "29-02-20", "time": "23:13:53.640", "thread": "main", "level": "INFO", "className": "ch.frankel.fastlogs.Main", "message": "Client-Id -> XYZ | Informative message" }
注意, Client-Id
与分隔符一起是message
一部分。 可以将其发送到Elasticsearch并使用自定义ingester解析消息。 但是,这将对每个索引的消息花费额外的处理时间。 回到第一个方框和Logstash吗?
将诊断上下文进行救援
SLF4J和类似的框架允许通过使用MDC添加其他元数据。 简而言之, MDC是绑定到特定线程的哈希映射上下文持有者 。
可以利用MDC解决此问题。 这是一个示例代码片段:
MDC.put("client-id",client.getId());
logger.info("Informative message");
MDC.remove("client-id");
请注意,我们在日志记录语句之前明确设置了它,并在之后删除了它。 根据上下文,使用servlet过滤器自动执行操作以避免错误是有益的。 例如,Logback提供了现成的MDC筛选器来设置与请求相关的数据。
让我们稍微更新一下模式以使用MDC:
<pattern> { "date": "%d{dd-MM-yy}", "time": "%d{HH:mm:ss.SSS}", "thread": "%thread", "level": "%-5level", "className": "%logger{36}", "message": "%msg", "Client-Id": "%X{client-id}" }%n </pattern>
注意%X{key}
语法。 这样,我们可以保持我们简单的无Logstash架构,并仍然获得所需的JSON输出:
{ "date": "29-02-20", "time": "23:13:53.640", "thread": "main", "level": "INFO", "className": "ch.frankel.fastlogs.Main", "message": "Informative message", "Client-Id": "XYZ" }
结论
通过消除对日志的解析,可以使日志变得更快。 为此,可以直接输出单行JSON消息。 当需要其他元数据时,只需使用MDC和适合日志记录实现的日志记录模式。