Java 应用日志如何与 Jaeger 的 trace 关联
在 sl4j 的配置文件中可以配置日志的格式,例如 logback 的配置文件如下,可见模板中新增了一段内容[user-id=%X{user-id}]:
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<encoder>
<pattern>%d{HH:mm:ss} [%thread] %-5level %logger{10} [user-id=%X{user-id}] %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
</appender>
再来看一段日志的代码,先调用 MDC.put 方法将一个键值对写入当前线程的诊断上下文 map(diagnostic context map),键名和上面的模板中配置的**%X{user-id}**一模一样:
@GetMapping("/test")
public void test() {
MDC.put("user-id", "user-" + System.currentTimeMillis());
log.info("this is test request");
}
现在把代码运行起来,打印日志看看,如下所示,之前模板中配置的**%X{user-id}**已被替换成了 user-1632122267618,就是代码中
MDC.put 设置的值:
15:17:47 [http-nio-18081-exec-6] INFO c.b.j.c.c.HelloConsumerController [user-id=user-1632122267618] this is test request
以上就是 MDC 的基本功能:对日志模板中的变量进行填充,填充的内容可以用 MDC.put 方法随意设置;
此刻聪明的您应该能猜到 jaeger 官方的方案是如何实现的了,没错,就是借助 MDC 将 trace 信息填充到日志模板中,这样每行日志都有了 trace 信息,咱们在 jaeger web 页面中感兴趣的任何一次 trace,都能找到对应的日志了
关于 Jaeger 的官方方案
Jaeger 的官方方案如下图所示,SDK 已经把 traceId、spanId、sampled 写入当前线程的诊断上下文 map(diagnostic context map),只要日志模板中配置上述三个变量,就会在所有业务日志中输出它们具体的值:
看起来似乎非常简单,那就动手编码试试吧
编码实战
jaeger 与 MDC 的关联只是个小功能,没必要大张旗鼓的新建项目,基于《Jaeger开发入门(java版)》的代码继续开发即可,也就是说修改两个子工程 jaeger-service-consumer 和 jaeger-service-provider 的源码,让它们的业务日志打印出 Jaeger 的 trace 信息
首先从 jaeger-service-provider 工程开始,增加一个标准的 logback 日志配置文件 logback.xml,如下所示,日志模板中已添加了 traceId、spanId、sampled 变量:
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<contextName>logback</contextName>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<encoder>
<pattern>%d{HH:mm:ss} [%thread] %-5level %logger{10} [traceId=%X{traceId} spanId=%X{spanId} sampled=%X{sampled}] %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
</appender>
<root level="info">
<appender-ref ref="console" />
</root>
</configuration>
再去检查配置类,确认 JaegerTracer 实例化时用了 MDCScopeManager 参数,如下所示,咱们在上一章已经这么做了,可以维持不变:
package com.bolingcavalry.jaeger.provider.config;
import io.jaegertracing.internal.MDCScopeManager;
import io.opentracing.contrib.java.spring.jaeger.starter.TracerBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class JaegerConfig {
@Bean
public TracerBuilderCustomizer mdcBuilderCustomizer() {
// 1.8 新特性,函数式接口
return builder -> builder.withScopeManager(new MDCScopeManager.Builder().build());
}
}
接下来是在业务代码中随意加几行打印日志的代码,如下图红框所示:
接下来继续修改 jaeger-service-consumer 子工程,具体步骤与刚才改造 jaeger-service-provider 时一模一样,就不多占用篇幅赘述了,记得在业务代码中随意加几行日志,如下图红框:
评论