更新时间:2025-10-27 GMT+08:00
分享

Java语言接入

华为云APM兼容OpenTelemetry协议,支持直接接收通过OpenTelemetry SDK或Agent上报的链路追踪数据。本文将介绍如何通过OpenTelemetry Java Agent实现无侵入式埋点,或通过SDK进行手动埋点,并将链路数据对接到APM。

示例demo

使用springboot实现一个简单掷骰子应用。

编写业务代码

初始化一个springboot项目。

创建RollDice.java文件,内容如下

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Optional;
import java.util.concurrent.ThreadLocalRandom;

@RestController
public class RollDice {

    @GetMapping("/rolldice")
    public String rollDiceAndSave(@RequestParam("player") Optional<String> player) {
        int result = 0;
        //掷骰子
        try {
            Thread.sleep(100);
            result = ThreadLocalRandom.current().nextInt(1, 7);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        saveResult(player.orElse("Anonymous player"), result);

        return Integer.toString(result);
    }

    private void saveResult(String player, int dicePoint) {
        //模拟数据库操作耗时
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

application.properties 内容如下

server.port=8080

启动

java -jar otel-demo-0.0.1-SNAPSHOT.jar & curl http://localhost:8080/rolldice?player=Alex

操作步骤

  1. 登录APM控制台
  2. 单击左侧,选择“管理与监管 > 应用性能管理 APM”,进入APM服务页面。
  3. 在左侧导航栏中选择“应用监控 > 应用列表”。
  4. 单击“接入应用”,进入接入应用页面。
  5. 选择“区域”和“应用”。单击“创建应用”,弹出“创建应用”弹窗,可以具体操作参见创建应用
  6. “接入方式”选择OpenTelemetry。
  7. “服务端语言”选择Java。
  8. 数据接入,相关参数与操作步骤如下。

    表1 参数说明

    参数

    说明

    是否必填

    探针安装路径

    opentelemetry-agent-path,即:探针下载后存放的路径。

    必填

    应用名称

    应用显示的名称。一个应用代表一个逻辑单元,是一个全局概念,各个region都可以看到相同的应用信息,比如一个租户下面比较独立的功能模块可以定义为一个应用。

    必填

    组件名称

    组件名称,代表一个组件,需要使用英文字符开头。同一个应用下,组件名称不能重复。一个组件可以包含多个环境。不能重复,如果要重复,使用instanceName区分。

    必填

    环境名称

    环境名称,代表一个应用在一个地方的部署。一个应用程序根据配置不同可以部署多个环境,比如测试环境,现网环境。每个环境都在一个region部署,具有唯一的region属性。该参数可以为空,代表默认环境。

    选填

    用户应用

    用户所属的应用名称。

    必填

    使用Java agent自动埋点

    OpenTelemetry java agent 支持对一些常用库/框架进行无侵入埋点方式,详情参考自动埋点支持的库/框架

    1. 获取探针
    2. 添加探针启动参数并重启应用。
      添加启动命令并重启应用,命令格式如下:
      java -javaagent:< 探针安装路径 >  \
        -Dotel.exporter.otlp.protocol=grpc  \
        -Dotel.exporter.otlp.traces.endpoint=http://xxx.xxx.xx.xxx:4317 \
        -Dotel.exporter.otlp.headers=Authentication=<token>  \
        -Dotel.service.name=<应用名称.组件名称.环境名称>  \  
        -Dotel.metrics.exporter=none  \
        -Dotel.logs.exporter=none  \
        -jar <yourApp>.jar  &
      
      curl http://localhost:8080/rolldice?player=Alex
    3. 在APM控制台链路追踪中查看应用的监控数据。详细操作参见指标

    使用Java SDK手动埋点

    当自动埋点不满足业务场景,或者需要自定义业务埋点逻辑时,可以引入OpenTelemetry SDK 进行手动埋点。

    1. 添加Maven依赖。

      在pom.xml中引入OpenTelemetry相关依赖

      <dependencies>
              <dependency>
                  <groupId>io.opentelemetry</groupId>
                  <artifactId>opentelemetry-api</artifactId>
              </dependency>
              <dependency>
                  <groupId>io.opentelemetry</groupId>
                  <artifactId>opentelemetry-sdk-trace</artifactId>
              </dependency>
              <dependency>
                  <groupId>io.opentelemetry</groupId>
                  <artifactId>opentelemetry-exporter-otlp</artifactId>
              </dependency>
              <dependency>
                  <groupId>io.opentelemetry</groupId>
                  <artifactId>opentelemetry-sdk</artifactId>
              </dependency>
              <dependency>
                  <groupId>io.opentelemetry</groupId>
                  <artifactId>opentelemetry-semconv</artifactId>
                  <version>1.30.0-alpha</version>
              </dependency>
          </dependencies>
          <dependencyManagement>
              <dependencies>
                  <dependency>
                      <groupId>io.opentelemetry</groupId>
                      <artifactId>opentelemetry-bom</artifactId>
                      <version>1.30.0</version>
                      <type>pom</type>
                      <scope>import</scope>
                  </dependency>
              </dependencies>
          </dependencyManagement>
    2. 初始化 OpenTelemetry SDK。

      编写一个工具类,用于配置和创建 OpenTelemetry实例

      import io.opentelemetry.api.OpenTelemetry;
      import io.opentelemetry.api.common.Attributes;
      import io.opentelemetry.api.trace.Tracer;
      import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
      import io.opentelemetry.context.propagation.ContextPropagators;
      import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
      import io.opentelemetry.sdk.OpenTelemetrySdk;
      import io.opentelemetry.sdk.resources.Resource;
      import io.opentelemetry.sdk.trace.SdkTracerProvider;
      import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
      import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
      
      public class OpenTelemetrySupport {
      
          static {
              // 获取OpenTelemetry Tracer
              Resource resource = Resource.getDefault()
                  .merge(Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, "<应用名称.组件名称.环境名称>", // 应用名称需在APM链路追踪创建一个opentelemetry的应用,组件名称和环境名称任意填写
                      ResourceAttributes.SERVICE_VERSION, "1.0.0", // 版本号。
                      ResourceAttributes.DEPLOYMENT_ENVIRONMENT, "test", // 部署环境,任意填写字符串。
                      ResourceAttributes.HOST_NAME, "127.0.0.1" // 请将 ${host-name} 替换为您的主机名,也任意填写字符串。
                  )));
      
              SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder()
                  .addSpanProcessor(BatchSpanProcessor.builder(
                      OtlpGrpcSpanExporter.builder().setEndpoint("<endpoint>") // 接入地址
                          .addHeader("Authentication", "<token>") // 应用鉴权信息 
                          .build()).build())
                  .setResource(resource)
                  .build();
      
              OpenTelemetry openTelemetry = OpenTelemetrySdk.builder()
                  .setTracerProvider(sdkTracerProvider)
                  .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance()))
                  .buildAndRegisterGlobal();
      
              tracer = openTelemetry.getTracer("OpenTelemetry Tracer", "1.0.0");
          }
      
          private static Tracer tracer;
      
          public static Tracer getTracer() {
              return tracer;
          }
      }

      创建Span

      import io.opentelemetry.api.common.Attributes;
      import io.opentelemetry.api.trace.Span;
      import io.opentelemetry.api.trace.SpanKind;
      import io.opentelemetry.api.trace.StatusCode;
      import io.opentelemetry.context.Scope;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.RequestParam;
      import org.springframework.web.bind.annotation.RestController;
      
      import java.util.Optional;
      import java.util.concurrent.ThreadLocalRandom;
      
      @RestController
      public class RollDice {
      
          @GetMapping("/rolldice")
          public String rollDiceAndSave(@RequestParam("player") Optional<String> player) {
              int result = 0;
              Span span = OpenTelemetrySupport.getTracer().spanBuilder("rolldice").setSpanKind(SpanKind.SERVER).startSpan();
              try (Scope scope = span.makeCurrent()) {
                  span.setStatus(StatusCode.OK);
                  span.setAttribute("dice.player", player.orElse("Anonymous player"));
                  span.addEvent("ev",
                      Attributes.builder().put("event1", "value1").build());  // 调用链的span详情页面上会显示后面的event1和value1
                  span.setAttribute("http.target", "rolldice");   // 作为server类型,必须设置该字段
                  span.setAttribute("http.method", "GET");  // 作为server类型,必须设置该字段
                  span.setAttribute("http.route", "{path}/**");  // 可选字段
                  span.setAttribute("http.status_code", 200);  // 可选字段
                  Attributes attributes = Attributes.builder()
                      .put("exception.type", "NullPoint")
                      .put("exception.message", "I am exception message")
                      .build();   // 可选字段,展示在调用链的span详情页面上
                  span.addEvent("exception", attributes);
                  //掷骰子
                  try {
                      Thread.sleep(100);
                      result = ThreadLocalRandom.current().nextInt(1, 7);
                  } catch (InterruptedException e) {
                      Thread.currentThread().interrupt();
                  }
                  saveResult(player.orElse("Anonymous player"), result);
              } catch (Throwable t) {
                  span.setStatus(StatusCode.ERROR, "handle parent span error");
              } finally {
                  span.end();
              }
              return Integer.toString(result);
          }
      
          private void saveResult(String player, int dicePoint) {
              Span span = OpenTelemetrySupport.getTracer()
                  .spanBuilder("saveResult")
                  .setSpanKind(SpanKind.CLIENT)
                  .startSpan();  // 数据库调用类型的span,必须设置为client类型,setParent这一步用于设置父span,标识父子span的关系,用以构造span的树桩结构
      
              try (Scope scope = span.makeCurrent()) {
                  span.setAttribute("db.system", "mysql"); // 数据库调用类型的span,必须设置为db.system类型,用来标识数据库的类型
                  span.setAttribute("db.statement",
                      "INSERT INTO dice_roll (player_name, dice_point) VALUES (" + player + ", " + dicePoint
                          + ")"); // 数据库调用类型的span,必须设置为db.statement,用来标识数据库的SQL语句
                  span.setAttribute("db.connection_string",
                      "127.0.0.1:3306:mysql"); // 数据库调用类型的span,必须设置为db.connection_string,用来标识数据库的连接对象
      
                  //模拟数据库操作耗时
                  try {
                      Thread.sleep(100);
                  } catch (InterruptedException e) {
                      Thread.currentThread().interrupt();
                  }
              } catch (Throwable t) {
                  span.recordException(t);
                  span.setStatus(StatusCode.ERROR, "handle child span error");
              } finally {
                  span.end();
              }
          }
      }
    3. 在APM控制台链路追踪中查看应用的监控数据。详细操作参见指标

相关文档