
# 使用OpenTelemetry上报Java应用数据
华为云APM兼容OpenTelemetry协议，支持直接接收通过OpenTelemetry SDK或Agent上报的链路追踪数据。本文介绍如何使用OpenTelemetry方式，将Java语言的应用接入APM，并上报链路追踪指标数据。
#### 示例demo
使用springboot实现一个简单掷骰子应用。
1. 编写业务代码，初始化一个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
   ```
   
   
2. 启动。 
   ```
   java -jar otel-demo-0.0.1-SNAPSHOT.jar & 
   curl http://localhost:8080/rolldice?player=Alex
   ```
   
   
 
#### 使用Java agent自动埋点
OpenTelemetry java agent 支持对一些常用库/框架进行无侵入埋点方式，详情参考自动埋点支持的[库/框架](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/docs/supported-libraries.md)。
1. [获取探针](https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases)。
2. 添加探针启动参数并重启应用。命令格式如下： 
   ```
   java -javaagent:探针安装路径  \
     -Dotel.exporter.otlp.protocol=grpc  \
     -Dotel.exporter.otlp.traces.endpoint=上报地址 \
     -Dotel.exporter.otlp.headers=Authentication=鉴权信息  \
     -Dotel.service.name=应用名称.组件名称.环境名称  \  
     -Dotel.metrics.exporter=none  \
     -Dotel.logs.exporter=none  \
     -jar <yourApp>.jar  &
   curl http://localhost:8080/rolldice?player=Alex
   ```
   
   
   
3. 登录[APM控制台](https://auth.huaweicloud.com/authui/login.html?service=https://console.huaweicloud.com/console/#/login)。
4. 单击左侧![](https://support.huaweicloud.com/bestpractice-apm2/zh-cn_image_0000001542238060.png)，选择"管理与监管\> 应用性能管理 APM"，进入APM服务页面。
5. 在左侧导航栏选择"链路追踪 \> 指标"。
6. 在界面左侧树单击环境，单击"概览"，切换至概览页签，在概览页签可以查看该实例的应用监控数据。详细操作参见[指标](https://support.huaweicloud.com/usermanual-apm2/apm_07_0102.html)。
 
#### 使用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。 
   1. 编写一个工具类，用于配置和创建 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("接入地址") // 接入地址
                          .addHeader("Authentication", "应用鉴权信息") // 应用鉴权信息 
                          .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;
          }
      }
      ```
      
   
   2. 创建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.Context;
      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, span);
              } 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 parentSpan) {
              Span span = OpenTelemetrySupport.getTracer()
                  .spanBuilder("saveResult")
                  .setSpanKind(SpanKind.CLIENT)    // 设置为CLIENT，表示这是对外部服务(如数据库)的调用
                  .setParent(Context.current().with(parentSpan))    // 关联父span，建立调用链路的父子关系，形成完整的分布式追踪树
                  .startSpan(); 
              try (Scope scope = span.makeCurrent()) {
                  span.setAttribute("db.system", "mysql"); // 标识数据库的类型为mysql
                  span.setAttribute("db.statement",
                      "INSERT INTO dice_roll (player_name, dice_point) VALUES (" + player + ", " + dicePoint
                          + ")"); // 标识SQL语句
                  span.setAttribute("db.connection_string",
                      "127.0.0.1:3306:mysql"); // 标识连接的数据库实例
                  //模拟数据库操作耗时
                  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控制台](https://auth.huaweicloud.com/authui/login.html?service=https://console.huaweicloud.com/console/#/login)。
4. 单击左侧![](https://support.huaweicloud.com/bestpractice-apm2/zh-cn_image_0000001542238060.png)，选择"管理与监管\> 应用性能管理 APM"，进入APM服务页面。
5. 在左侧导航栏选择"链路追踪 \> 指标"。
6. 在界面左侧树单击环境，单击"概览"，切换至概览页签，在概览页签可以查看该实例的应用监控数据。详细操作参见[指标](https://support.huaweicloud.com/usermanual-apm2/apm_07_0102.html)。
 
