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
操作步骤
- 登录APM控制台。
- 单击左侧
,选择“管理与监管 > 应用性能管理 APM”,进入APM服务页面。 - 在左侧导航栏中选择“应用监控 > 应用列表”。
- 单击“接入应用”,进入接入应用页面。
- 选择“区域”和“应用”。单击“创建应用”,弹出“创建应用”弹窗,可以具体操作参见创建应用。
- “接入方式”选择OpenTelemetry。
- “服务端语言”选择Java。
- 数据接入,相关参数与操作步骤如下。
表1 参数说明 参数
说明
是否必填
探针安装路径
opentelemetry-agent-path,即:探针下载后存放的路径。
必填
应用名称
应用显示的名称。一个应用代表一个逻辑单元,是一个全局概念,各个region都可以看到相同的应用信息,比如一个租户下面比较独立的功能模块可以定义为一个应用。
必填
组件名称
组件名称,代表一个组件,需要使用英文字符开头。同一个应用下,组件名称不能重复。一个组件可以包含多个环境。不能重复,如果要重复,使用instanceName区分。
必填
环境名称
环境名称,代表一个应用在一个地方的部署。一个应用程序根据配置不同可以部署多个环境,比如测试环境,现网环境。每个环境都在一个region部署,具有唯一的region属性。该参数可以为空,代表默认环境。
选填
用户应用
用户所属的应用名称。
必填
使用Java agent自动埋点
OpenTelemetry java agent 支持对一些常用库/框架进行无侵入埋点方式,详情参考自动埋点支持的库/框架。
- 获取探针。
- 添加探针启动参数并重启应用。
添加启动命令并重启应用,命令格式如下:
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
- 在APM控制台链路追踪中查看应用的监控数据。详细操作参见指标。
使用Java SDK手动埋点
当自动埋点不满足业务场景,或者需要自定义业务埋点逻辑时,可以引入OpenTelemetry SDK 进行手动埋点。
- 添加Maven依赖。
<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> - 初始化 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(); } } } - 在APM控制台链路追踪中查看应用的监控数据。详细操作参见指标。