通过OpenTelemetry Java Agent接入APM
华为云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
使用Java agent自动埋点
OpenTelemetry java agent 支持对一些常用库/框架进行无侵入埋点方式,详情参考自动埋点支持的库/框架。
- 获取探针。
- 添加探针启动参数并重启应用。添加启动命令并重启应用,命令格式如下:
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
- 登录APM控制台。
- 单击左侧
,选择“管理与监管> 应用性能管理 APM”,进入APM服务页面。 - 在左侧导航栏选择“链路追踪 > 指标”。
- 在界面左侧树单击环境,单击“概览”,切换至概览页签,在概览页签可以查看该实例的应用监控数据。详细操作参见指标
使用Java SDK手动埋点
当自动埋点不满足业务场景,或者需要自定义业务埋点逻辑时,可以引入OpenTelemetry SDK 进行手动埋点。
- 添加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> - 初始化 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("接入地址") // 接入地址 .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; } } - 创建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(); } } }
- 编写一个工具类,用于配置和创建 OpenTelemetry实例。
- 登录APM控制台。
- 单击左侧
,选择“管理与监管> 应用性能管理 APM”,进入APM服务页面。 - 在左侧导航栏选择“链路追踪 > 指标”。
- 在界面左侧树单击环境,单击“概览”,切换至概览页签,在概览页签可以查看该实例的应用监控数据。详细操作参见指标