Java应用日志如何与Jaeger的trace关联

本文介绍如何使用Jaeger的MDC功能将业务日志与Jaeger的trace关联,通过简单的改动大幅提升问题定位效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

欢迎访问我的GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos

本篇概览

  • 经过《Jaeger开发入门(java版)》的实战,相信您已经能将自己的应用接入Jaeger,并用来跟踪定位问题了,本文将介绍Jaeger一个小巧而强大的辅助功能,用少量改动大幅度提升定位问题的便利性:将业务日志与Jaeger的trace关联
  • 在正式开始前,咱们先来看一个具体的问题:
  1. 一次web请求可能有多条业务日志(log4j或者logback配置的那种),这和您写代码执行log.info的次数有关,假设有10条,那么十次请求就有一百条业务日志;
  2. 通过jaeger发现这十次请求中有一次耗时特别长,想定位一下具体原因,现在问题来了:一共有100条业务日志,到底哪些是和Jaeger中耗时长的那一次请求有关?
  • 您可能会说:有些业务特征如user-id,咱们可以写入span的tag或者log中,这样通过span查到user-id,再去日志中查找含有此user-id的日志即可,这样确实可以,但未必每条日志都有user-id,所以并非最佳方式
  • 好在Jaeger官方给出了一种简单有效的方案:基于MDC,Jaeger的SDK在日志中注入trace相关的变量

关于MDC

  • 关于sl4j的MDC不是本篇的重点,因此只把本篇用到的特性简单说说即可,经验丰富的您如果对MDC已经了解,请跳过此节
  • 在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>
            <!--%logger{10}表示类名过长时会自动缩写-->
            <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已经把traceIdspanIdsampled写入当前线程的诊断上下文map(diagnostic context map),只要日志模板中配置上述三个变量,就会在所有业务日志中输出它们具体的值:
    在这里插入图片描述
  • 看起来似乎非常简单,那就动手编码试试吧

编码实战

  • jaeger与MDC的关联只是个小功能,没必要大张旗鼓的新建项目,基于《Jaeger开发入门(java版)》的代码继续开发即可,也就是说修改两个子工程jaeger-service-consumerjaeger-service-provider的源码,让它们的业务日志打印出Jaeger的trace信息
  • 首先从jaeger-service-provider工程开始,增加一个标准的logback日志配置文件logback.xml,如下所示,日志模板中已添加了traceIdspanIdsampled变量:
<?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>
            <!--%logger{10}表示类名过长时会自动缩写-->
            <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时一模一样,就不多占用篇幅赘述了,记得在业务代码中随意加几行日志,如下图红框:
    在这里插入图片描述
  • 开发完成,开始验证吧

验证

  • 《Jaeger开发入门(java版)》那样操作,将jaeger-service-consumerjaeger-service-provider编译构建制作成docker镜像
  • 用docker-compose将所有服务启动,然后通过浏览器访问jaeger-service-consumer的服务,多访问几次
  • 打开jaeger的web页面,可以看到多次请求的trace,咱们随机选择一个,鼠标点击下图红框中的圆点:
    在这里插入图片描述
  • 此时会跳转到该trace的详情页,注意页面的url,如下图红框,里面的2037fe105d73f4a5就是traceid:
    在这里插入图片描述
  • 2037fe105d73f4a5搜索jaeger-service-provider的日志,由于应用部署在docker中,咱们要用docker log和grep命令组合来过滤,如下所示,咱们代码写的日志都打印出来了,并且红框中就是traceid等关键信息
    两行
  • 再去查看jaeger-service-consumer的日志,如下图红框,本次请求相关的日志也可以通过traceid搜索到:
    在这里插入图片描述
  • 至此,本篇实战就完成了,Jaeger的web页面上的任何一个trace,现在都能轻易找到与之对应的所有业务日志,这在定位问题时简直是如虎添翼的效果,如果您的系统用了ELK或者EFK来汇总所有分布式服务的日志,那就更高效了

你不孤单,欣宸原创一路相伴

  1. Java系列
  2. Spring系列
  3. Docker系列
  4. kubernetes系列
  5. 数据库+中间件系列
  6. DevOps系列
<think>嗯,用户之前问过关于apm-toolkit-logback-1.x的问题,现在又问了OpenTelemetryAppender属于哪个依赖,以及如何在日志中打印traceId。我需要先确认这两个问题之间的关系,以及用户的需求是否有所变化。 首先,用户之前使用的是SkyWalking的日志集成组件,现在转向OpenTelemetry,可能是因为他们正在迁移到更通用的OpenTelemetry标准,或者他们的技术栈发生了变化。OpenTelemetry是CNCF的一个项目,旨在提供统一的观测数据收集,包括追踪、指标和日志,所以用户可能希望整合不同的可观测性工具。 接下来,用户的问题分为两部分:OpenTelemetryAppender属于哪个依赖,以及如何在日志中打印traceId。我需要明确回答这两个部分,特别是依赖的groupId和artifactId,以及具体的配置步骤。 关于OpenTelemetryAppender,根据知识,这个类通常属于OpenTelemetry的Java日志库,具体来说应该是opentelemetry-log4j-appender或opentelemetry-logback-appender,具体取决于使用的日志框架。用户之前用的是Logback,所以可能指的是Logback的Appender。需要确认正确的依赖名称和版本。 然后,如何配置日志打印traceId。这里需要解释如何在Logback的配置文件中添加OpenTelemetry的Appender,并设置PatternLayout来包含traceId。同时,需要确保上下文正确注入,可能需要使用OpenTelemetry的SDK自动配置或者手动设置。 还要注意,用户可能在使用Spring Boot或其他框架,是否需要额外的配置步骤,比如添加依赖或设置环境变量。另外,是否需要启动OpenTelemetry的代理或者使用自动instrumentation。 可能用户遇到的困难是,在迁移到OpenTelemetry之后,如何保持之前类似SkyWalking的日志集成体验,确保traceId能够自动附加到日志中。需要提供清晰的步骤,避免常见的配置错误,比如依赖版本不匹配,或者Pattern中使用了错误的占位符。 最后,考虑到用户可能有不同的日志框架,比如Log4j2或者Logback,需要明确说明是针对哪个框架的配置。根据用户之前的问题,他们使用的是Logback,所以应针对Logback的配置进行说明,并提供对应的依赖和示例配置。</think>以下是关于 `OpenTelemetryAppender` 和日志打印 `traceId` 的详细指南: --- ### **一、OpenTelemetryAppender 所属依赖** `OpenTelemetryAppender` 是 **OpenTelemetry Java SDK** 生态中的日志集成组件,属于以下依赖: ```xml <!-- Maven 依赖 --> <dependency> <groupId>io.opentelemetry.instrumentation</groupId> <artifactId>opentelemetry-logback-appender-1.0</artifactId> <version>2.3.0</version> <!-- 版本需OpenTelemetry SDK匹配 --> </dependency> ``` - **功能定位**:将Logback日志OpenTelemetry的分布式追踪数据(TraceID/SpanID)自动关联 - **适用场景**:需要将日志OpenTelemetry追踪系统(如Jaeger、Zipkin、Prometheus等)集成的Java应用 --- ### **二、打印TraceID的配置步骤** #### **步骤1:添加依赖** 确保包含以下核心依赖: ```xml <!-- OpenTelemetry SDK --> <dependency> <groupId>io.opentelemetry</groupId> <artifactId>opentelemetry-sdk</artifactId> <version>1.38.0</version> </dependency> <!-- Logback Appender集成 --> <dependency> <groupId>io.opentelemetry.instrumentation</groupId> <artifactId>opentelemetry-logback-appender-1.0</artifactId> <version>2.3.0</version> </dependency> ``` #### **步骤2:配置logback.xml** 修改日志输出格式,注入TraceID: ```xml <configuration> <!-- 添加OpenTelemetry Appender --> <appender name="OTEL" class="io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender"/> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern> %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} [TraceID=%X{trace_id}, SpanID=%X{span_id}] - %msg%n </pattern> </encoder> </appender> <root level="INFO"> <appender-ref ref="STDOUT"/> <appender-ref ref="OTEL"/> <!-- 启用OpenTelemetry日志注入 --> </root> </configuration> ``` #### **步骤3:初始化OpenTelemetry SDK** 在应用启动时初始化(以Jaeger为例): ```java import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporter; import io.opentelemetry.sdk.OpenTelemetrySdk; public class OtelConfig { public static OpenTelemetry init() { JaegerGrpcSpanExporter exporter = JaegerGrpcSpanExporter.builder() .setEndpoint("http://localhost:14250") .build(); SdkTracerProvider tracerProvider = SdkTracerProvider.builder() .addSpanProcessor(BatchSpanProcessor.builder(exporter).build()) .build(); return OpenTelemetrySdk.builder() .setTracerProvider(tracerProvider) .buildAndRegisterGlobal(); } } ``` --- ### **三、验证效果** 日志输出示例: ``` 2024-01-01 14:30:00 [http-nio-8080-exec-1] INFO c.example.Service [TraceID=4d2d3e1f9a7b8c9d0a1b2c3d4e5f6a7b, SpanID=1a2b3c4d5e6f7a8b] - 用户登录成功 ``` 在Jaeger/Zipkin等系统中,可通过TraceID直接关联到对应日志: ![Jaeger Trace日志关联示意图](https://opentelemetry.io/img/docs/jaeger-trace-with-logs.png) --- ### **四、高级配置** #### **1. 自动注入上下文(Agent方式)** 通过Java Agent自动采集追踪数据(无需代码修改): ```bash -javaagent:opentelemetry-javaagent.jar -Dotel.service.name=your-service -Dotel.traces.exporter=jaeger ``` #### **2. 自定义日志字段** 在Pattern中扩展其他OpenTelemetry上下文: ```xml <pattern> [TraceID=%X{trace_id}, SpanID=%X{span_id}, Service=%X{service.name}] </pattern> ``` #### **3. 过滤敏感信息** 通过Logback过滤器控制日志上报: ```xml <appender name="OTEL" class="io.opentelemetry...OpenTelemetryAppender"> <filter class="ch.qos.logback.core.filter.EvaluatorFilter"> <evaluator> <expression>message.contains("password")</expression> </evaluator> <OnMatch>DENY</OnMatch> </filter> </appender> ``` --- ### **五、常见问题排查** | 现象 | 原因 | 解决方案 | |---------------------|-----------------------------|--------------------------| | 日志中无TraceID | 1. SDK未初始化<br>2. Appender未配置 | 检查`OpenTelemetry.init()`是否执行<br>确认logback.xml中引用了OTEL appender | | TraceID格式不正确 | 上下文传播中断 | 检查跨线程/异步任务中是否调用了`Context.current().makeCurrent()` | | 日志上报延迟 | Batch处理器缓存未刷新 | 在SpanProcessor中配置`setScheduleDelay(1000)`(单位:毫秒) | --- 通过以上配置,可实现日志OpenTelemetry追踪数据的深度集成,提升分布式系统的可观测性。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员欣宸

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值