在使用链路追踪控制台追踪应用的链路数据之前,需要将应用数据上报至链路追踪。本文介绍如何为应用埋点并上报链路数据。

前提条件

获取接入点信息
  1. 登录ARMS控制台,在左侧导航栏选择应用监控 > 应用列表
  2. 应用列表页面顶部选择目标地域,然后单击目标链路追踪应用名称。
    说明 语言列显示Java图标图标的应用为接入应用监控的应用,显示-图标的应用为接入链路追踪的应用。
  3. 在弹出的页面中,单击左侧导航栏上方的返回图标图标,返回上一层。
  4. 在左侧导航栏单击集群配置,然后在右侧页面单击接入点信息页签。
  5. 在页面顶部选择需要接入的地域,然后在集群信息区域打开显示Token开关。
  6. 客户端采集工具区域单击OpenTelemetry
    在下方表格的相关信息列中,获取接入点信息。OT接入点信息
    说明
    • 对于ACK集群应用,建议通过ARMS OpenTelemetry Collector上报应用数据。ARMS OpenTelemetry Collector支持本地集群内的链路采样与指标无损统计,在降低链路传输、存储成本的同时,不影响监控或告警指标的准确性。更多信息,请参见ARMS OpenTelemetry Collector
    • 如果应用部署于阿里云生产环境,则选择私网接入点,否则选择公网接入点。

示例Demo

示例代码仓库地址:java-opentelemetry-demo

方法一:使用OpenTelemetry Java Agent自动埋点

OpenTelemetry Java Agent提供了无侵入的接入方式,支持数十种Java框架自动上传Trace数据,详细的Java框架列表,请参见Supported Libraries

  1. 下载Java Agent
  2. 通过修改Java启动的VM参数上报链路数据。
    -javaagent:/path/to/opentelemetry-javaagent.jar    //请将路径修改为您文件下载的实际地址。
    -Dotel.resource.attributes=service.name=<appName>     //<appName> 为应用名。
    -Dotel.exporter.otlp.headers=Authentication=<token>
    -Dotel.exporter.otlp.endpoint=<endpoint>
    • 如果您选择直接上报数据,请将<token>替换成从前提条件中获取的Token,将<endpoint>替换成对应地域的Endpoint。

      例如:

      -javaagent:/Users/carpela/Downloads/opentelemetry-javaagent.jar
      -Dotel.resource.attributes=service.name=ot-java-agent-sample
      -Dotel.exporter.otlp.headers=Authentication=b590xxxxuqs@3a75d95xxxxx9b_b59xxxxguqs@53dxxxx2afe8301
      -Dotel.exporter.otlp.endpoint=http://tracing-analysis-dc-bj:8090
    • 如果您选择使用OpenTelemetry Collector转发,则需删除-Dotel.exporter.otlp.headers=Authentication=<token>并修改<endpoint>为您本地部署的服务地址。

方法二:使用OpenTelemetry Java SDK手动埋点

OpenTelemetry Java SDK是OpenTelemetry Java Agent实现的基础,同时提供了丰富的自定义能力。当OpenTelemetry Java Agent的埋点不满足您的场景或者需要增加一些自定义业务埋点时,可以使用以下方式接入。

  1. 引入Maven POM依赖。
    <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.23.0-alpha</version>
      </dependency>
    </dependencies>
    
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>io.opentelemetry</groupId>
                <artifactId>opentelemetry-bom</artifactId>
                <version>1.23.0</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
  2. 获取OpenTelemetry Tracer。
    • <logical-service-name>为服务名,<host-name>为主机名,请根据您的实际场景配置。
    • 如果您选择直接上报数据,请将以下代码中的<token>替换成前提条件中获取的Token,将<endpoint>替换成对应地域的Endpoint。
    package com.alibaba.arms.brightroar.console.util;
    
    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, "<logical-service-name>",
                            ResourceAttributes.HOST_NAME, "<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("<your_tracer_name>", "1.0.0");
        }
    
        private static Tracer tracer;
    
        public static Tracer getTracer() {
            return tracer;
        }
    
    }
  3. 参考以下内容修改Controller代码和Service代码。
    • Controller代码:
      package com.alibaba.arms.brightroar.console.controller;
      
      import com.alibaba.arms.brightroar.console.service.UserService;
      import com.alibaba.arms.brightroar.console.util.OpenTelemetrySupport;
      import io.opentelemetry.api.GlobalOpenTelemetry;
      import io.opentelemetry.api.OpenTelemetry;
      import io.opentelemetry.api.trace.Span;
      import io.opentelemetry.api.trace.StatusCode;
      import io.opentelemetry.api.trace.Tracer;
      import io.opentelemetry.context.Context;
      import io.opentelemetry.context.Scope;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RestController;
      
      import java.util.concurrent.ExecutorService;
      import java.util.concurrent.Executors;
      
      /**
       * 参考文档:
       * 1. https://opentelemetry.io/docs/java/manual_instrumentation/
       */
      @RestController
      @RequestMapping("/user")
      public class UserController {
      
          @Autowired
          private UserService userService;
      
          private ExecutorService es = Executors.newFixedThreadPool(5);
      
          private void biz() {
              Tracer tracer = OpenTelemetrySupport.getTracer();
              Span span = tracer.spanBuilder("biz (manual)")
                      .setParent(Context.current().with(Span.current())) // 可选,自动设置
                      .startSpan();
      
              try (Scope scope = span.makeCurrent()) {
                  span.setAttribute("biz-id", "111");
      
                  es.submit(new Runnable() {
                      @Override
                      public void run() {
                          Span asyncSpan = tracer.spanBuilder("async")
                                  .setParent(Context.current().with(span))
                                  .startSpan();
                          try {
                              Thread.sleep(1000L); // some async jobs
                          } catch (Throwable e) {
                          }
                          asyncSpan.end();
                      }
                  });
      
                  Thread.sleep(1000); // fake biz logic
                  System.out.println("biz done");
                  OpenTelemetry openTelemetry = GlobalOpenTelemetry.get();
                  openTelemetry.getPropagators();
              } catch (Throwable t) {
                  span.setStatus(StatusCode.ERROR, "handle biz error");
              } finally {
                  span.end();
              }
          }
      
          private void child(String userType) {
              Span span = OpenTelemetrySupport.getTracer().spanBuilder("child span").startSpan();
              try (Scope scope = span.makeCurrent()) {
                  span.setAttribute("user.type", userType);
                  System.out.println(userType);
                  biz();
              } catch (Throwable t) {
                  span.setStatus(StatusCode.ERROR, "handle child span error");
              } finally {
                  span.end();
              }
          }
      
          @RequestMapping("/async")
          public String async() {
              System.out.println("UserController.async -- " + Thread.currentThread().getId());
              Span span = OpenTelemetrySupport.getTracer().spanBuilder("parent span").startSpan();
              span.setAttribute("user.id", "123456");
              try (Scope scope = span.makeCurrent()) {
                  userService.async();
                  child("vip");
              } catch (Throwable t) {
                  span.setStatus(StatusCode.ERROR, "handle parent span error");
              } finally {
                  span.end();
              }
              return "async";
          }
      
      }
         
    • Service代码:
      package com.alibaba.arms.brightroar.console.service;
      
      import org.springframework.scheduling.annotation.Async;
      import org.springframework.stereotype.Service;
      
      @Service
      public class UserService {
      
          @Async
          public void async() {
              System.out.println("UserService.async -- " + Thread.currentThread().getId());
              System.out.println("my name is async");
              System.out.println("UserService.async -- ");
          }
      }
  4. 启动应用。
    登录ARMS控制台后,在应用监控 > 应用列表页面选择目标应用,查看链路数据。
    说明 语言列显示Java图标图标的应用为接入应用监控的应用,显示-图标的应用为接入链路追踪的应用。

方法三:同时使用Java Agent和Java SDK埋点

您可以在使用Java Agent获得自动埋点能力的同时,使用Java SDK添加自定义业务埋点。

  1. 下载Java Agent
  2. 方法二的Maven依赖基础上新增以下依赖。
    <dependency>
        <groupId>io.opentelemetry</groupId>
        <artifactId>opentelemetry-extension-annotations</artifactId>
    </dependency>
    <dependency>
        <groupId>io.opentelemetry</groupId>
        <artifactId>opentelemetry-sdk-extension-autoconfigure</artifactId>
        <version>1.23.0-alpha</version>
    </dependency>
    说明 其中opentelemetry-sdk-extension-autoconfigure完成了SDK的自动配置,将Java Agent的配置传递到Java SDK中。
    展开查看完整的Maven POM依赖
    <dependencies>
        <dependency>
            <groupId>com.alibaba.arms</groupId>
            <artifactId>brightroar-dao</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>
        <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-extension-annotations</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.23.0-alpha</version>
        </dependency>
        <dependency>
            <groupId>io.opentelemetry</groupId>
            <artifactId>opentelemetry-sdk-extension-autoconfigure</artifactId>
            <version>1.23.0-alpha</version>
        </dependency>
    </dependencies>
    
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>io.opentelemetry</groupId>
                <artifactId>opentelemetry-bom</artifactId>
                <version>1.23.0</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
  3. 获取OpenTelemetry Tracer。
    同时使用Java Agent和Java SDK埋点时,无需再使用方法二中的OpenTelemetrySupport类获取Tracer。
    OpenTelemetry openTelemetry = GlobalOpenTelemetry.get();
    Tracer tracer = openTelemetry.getTracer("instrumentation-library-name", "1.0.0");
  4. 参考以下内容修改Controller代码和Service代码。
    • Controller代码如下,建议使用代码中的第一种和第二种方式。
      package com.alibaba.arms.brightroar.console.controller;
      
      import com.alibaba.arms.brightroar.console.service.UserService;
      import io.opentelemetry.api.GlobalOpenTelemetry;
      import io.opentelemetry.api.OpenTelemetry;
      import io.opentelemetry.api.trace.Span;
      import io.opentelemetry.api.trace.StatusCode;
      import io.opentelemetry.api.trace.Tracer;
      import io.opentelemetry.context.Context;
      import io.opentelemetry.context.Scope;
      import io.opentelemetry.extension.annotations.SpanAttribute;
      import io.opentelemetry.extension.annotations.WithSpan;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RestController;
      
      import java.util.concurrent.ExecutorService;
      import java.util.concurrent.Executors;
      
      /**
      * 参考文档:
      * 1. https://opentelemetry.io/docs/java/manual_instrumentation/
      */
      @RestController
          @RequestMapping("/user")
          public class UserController {
      
              @Autowired
              private UserService userService;
      
              private ExecutorService es = Executors.newFixedThreadPool(5);
      
              // 第三种:获得 Tracer 纯手工埋点
              private void biz() {
                  Tracer tracer = GlobalOpenTelemetry.get().getTracer("tracer");
                  Span span = tracer.spanBuilder("biz (manual)")
                      .setParent(Context.current().with(Span.current())) // 可选,自动设置
                      .startSpan();
      
                  try (Scope scope = span.makeCurrent()) {
                      span.setAttribute("biz-id", "111");
      
                      es.submit(new Runnable() {
                          @Override
                          public void run() {
                              Span asyncSpan = tracer.spanBuilder("async")
                                  .setParent(Context.current().with(span))
                                  .startSpan();
                              try {
                                  Thread.sleep(1000L); // some async jobs
                              } catch (Throwable e) {
                              }
                              asyncSpan.end();
                          }
                      });
      
                      Thread.sleep(1000); // fake biz logic
                      System.out.println("biz done");
                      OpenTelemetry openTelemetry = GlobalOpenTelemetry.get();
                      openTelemetry.getPropagators();
                  } catch (Throwable t) {
                      span.setStatus(StatusCode.ERROR, "handle biz error");
                  } finally {
                      span.end();
                  }
              }
      
              // 第二种:通过注解创建埋点
              @WithSpan
              private void child(@SpanAttribute("user.type") String userType) {
                  System.out.println(userType);
                  biz();
              }
      
              // 第一种:自动埋点,基于 API 手工添加信息
              @RequestMapping("/async")
              public String async() {
                  System.out.println("UserController.async -- " + Thread.currentThread().getId());
                  Span span = Span.current();
                  span.setAttribute("user.id", "123456");
                  userService.async();
                  child("vip");
                  return "async";
              }
      
          }
                                      
    • Service代码:
      package com.alibaba.arms.brightroar.console.service;
      
      import org.springframework.scheduling.annotation.Async;
      import org.springframework.stereotype.Service;
      
      @Service
      public class UserService {
      
          @Async
          public void async() {
              System.out.println("UserService.async -- " + Thread.currentThread().getId());
              System.out.println("my name is async");
              System.out.println("UserService.async -- ");
          }
      }
  5. 通过修改Java启动的VM参数上报链路数据。
    -javaagent:/path/to/opentelemetry-javaagent.jar    //请将路径修改为您文件下载的实际地址。
    -Dotel.resource.attributes=service.name=<appName>     //<appName> 为应用名。
    -Dotel.exporter.otlp.headers=Authentication=<token>
    -Dotel.exporter.otlp.endpoint=<endpoint>
    • 如果您选择直接上报数据,请将<token>替换成从前提条件中获取的Token,将<endpoint>替换成对应地域的Endpoint。

      例如:

      -javaagent:/Users/carpela/Downloads/opentelemetry-javaagent.jar
      -Dotel.resource.attributes=service.name=ot-java-agent-sample
      -Dotel.exporter.otlp.headers=Authentication=b590xxxxuqs@3a75d95xxxxx9b_b59xxxxguqs@53dxxxx2afe8301
      -Dotel.exporter.otlp.endpoint=http://tracing-analysis-dc-bj:8090
    • 如果您选择使用OpenTelemetry Collector转发,则需删除-Dotel.exporter.otlp.headers=Authentication=<token>并修改<endpoint>为您本地部署的服务地址。
  6. 启动应用。
    登录ARMS控制台后,在应用监控 > 应用列表页面选择目标应用,查看链路数据。
    说明 语言列显示Java图标图标的应用为接入应用监控的应用,显示-图标的应用为接入链路追踪的应用。