接入ARMS应用监控以后,ARMS探针对常见的Java框架进行了自动埋点,因此不需要修改任何代码,就可以实现调用链信息的采集。如果您需要在调用链信息中,体现业务方法的执行情况,可以引入OpenTelemetry Java SDK,在业务代码中增加自定义埋点。本文介绍如何通过OpenTelemetry Java SDK实现自定义埋点、获取Trace上下文信息、自定义Baggage以及自定义Attribute。
ARMS探针支持的组件和框架,请参见ARMS应用监控支持的Java组件和框架。
前提条件
引入依赖
请先参考如下Maven代码引入OpenTelemetry Java SDK。更多信息,请参见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-sdk</artifactId>
</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>
ARMS对OpenTelemetry埋点的兼容
名词介绍
此处只介绍一些常用的名词,其他名称解释,请参见OpenTelemetry Specification。
-
Span:一次请求的一个具体操作,比如远程调用入口或者内部方法调用。
-
SpanContext:一次请求追踪的上下文,包含traceId、spanId等信息。
-
Attribute:Span的附加属性字段,用于记录关键信息。
-
Baggae:在整条链路上透传的一些Key、Value信息。
使用OpenTelemetry Java SDK
通过OpenTelemetry的SDK主要可以实现以下操作:
-
埋点生成Span。
-
为Span增加Attributes。
-
在链路上下文透传Baggage。
-
获取当前Trace上下文并打印traceId、spanId等。
此处通过一段示例代码演示如何使用OpenTelemetry SDK完成上述操作。
以下代码片段需要注意,最终获取OpenTelemetry实例需要通过调用GlobalOpenTelemetry.get()方法获取,不能直接使用上一步通过OpenTelemetry SDK手动构建的Opentelemetry实例。否则会导致在4.x版本探针中无法看到通过SDK埋点生成的Span数据。
@RestController
@RequestMapping("/ot")
public class OpenTelemetryController {
private Tracer tracer;
private ScheduledExecutorService ses = Executors.newSingleThreadScheduledExecutor();
@PostConstruct
public void init() {
OpenTelemetrySdk.builder()
.setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance()))
.buildAndRegisterGlobal();
tracer = GlobalOpenTelemetry.get().getTracer("manual-sdk", "1.0.0");
ses.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
Span span = tracer.spanBuilder("schedule")
.setAttribute("schedule.time", System.currentTimeMillis())
.startSpan();
try (Scope scope = span.makeCurrent()) {
System.out.println("scheduled!");
Thread.sleep(500L);
span.setAttribute("schedule.success", true);
System.out.println(Span.current().getSpanContext().getTraceId()); // 获取 TraceId
} catch (Throwable t) {
span.setStatus(StatusCode.ERROR, t.getMessage());
} finally {
span.end();
}
}
}, 10, 30, TimeUnit.SECONDS);
}
@ResponseBody
@RequestMapping("/parent")
public String parent() {
Span span = tracer.spanBuilder("parent").setSpanKind(SpanKind.SERVER).startSpan();
try (Scope scope = span.makeCurrent()) {
// 使用Baggage透传业务自定义标签
Baggage baggage = Baggage.current().toBuilder()
.put("user.id", "1")
.put("user.name", "name")
.build();
try (Scope baggageScope = baggage.storeInContext(Context.current()).makeCurrent()) {
child();
}
span.setAttribute("http.method", "GET");
span.setAttribute("http.uri", "/parent");
} finally {
span.end();
}
return "parent";
}
private void child() {
Span span = tracer.spanBuilder("child").startSpan();
try (Scope scope = span.makeCurrent()) {
System.out.println("current traceId = " + Span.current().getSpanContext().getTraceId());
System.out.println("userId in baggage = " + Baggage.current().getEntryValue("user.id"));
Thread.sleep(1000);
} catch (Throwable e) {
span.setStatus(StatusCode.ERROR, e.getMessage());
} finally {
span.end();
}
}
}
示例代码说明:
-
在
OpenTelemetryController的init方法中启动了一个定时调度任务,并在调度任务开始时创建一个Span,在调度任务结束时关闭该Span。 -
在
OpenTelemetryController的parent方法中调用了Opentelemetry SDK的多个方法。-
每次被调用时创建一个名为
parent的Span,并在方法结束时关闭该Span。 -
调用Baggage SDK,新加了名为
user.id和user.name的两对Baggage,这两个Baggage后续会传递到下游应用。 -
为步骤2.a创建的Span新增了两个Attribute。
-
-
在
OpenTelemetryController的child方法中会执行以下操作。
不同探针差异点对比
对于上述代码中的不同操作,ARMS 3.x及以下版本探针和ARMS 4.x探针的支持情况有所区别,详情可见下方表格。
|
步骤 |
4.x及以上版本探针 |
3.x及以下版本探针 |
|
1 |
支持,会生成一个新的Span。 |
支持,会生成一个新的Span。 |
|
2.1 |
支持 |
支持 |
|
2.2 |
支持 |
不支持 |
|
2.3 |
支持 |
支持 |
|
3.1 |
支持 |
支持,该Span会作为步骤2.a创建出来的Span的方法栈存在。 |
|
3.2 |
支持,和ARMS中traceId是一个。 |
不支持,打印出的traceId和ARMS探针中的traceId不同。 |
|
3.2 |
支持 |
支持 |
埋点效果展示
4.x及以上版本
-
步骤1埋点效果:
可以正常看到通过OpenTelemetry SDK生成的Span。
在ARMS链路追踪的Span详情中,可查看应用名(如
elastic-search-8)、接口名(如schedule)、Span类型(INTERNAL)、耗时等基本信息。在Attributes标签页中可确认OpenTelemetry相关属性,例如otel.scope.name=manual-sdk、otel.scope.version=1.0.0,以及业务自定义属性如schedule.success=true等,验证埋点已生效。 -
步骤2.x、3.x埋点效果:
通过OpenTelemetry SDK生成的Span和当前探针埋点Tomcat生成的Span在一条链路上,且通过OpenTelemetry SDK生成的Span的相关Attribute也设置成功。
Trace详情中,Tomcat探针埋点生成的Span接口名为
/opentelemetry/parent,OpenTelemetry SDK生成的Span接口名为parent及其子Spanchild,Span详情的Attribute中http.method为GET、http.uri为/parent,自定义属性otel.scope.name为manual-sdk。
3.x及以上版本探针
-
步骤1埋点效果:
在 ARMS 控制台的调用链详情页中,可查看埋点采集到的 Trace 信息。左侧瀑布图展示 span 调用关系,例如应用
elastic-search-9的schedule方法被采集为一个 span,组件类型为user_method,总响应时间 567ms。右侧 Span Details 面板展示应用名、Span Name、IP、spanId、Status Code 等基础信息。下方 Attributes 区域分组展示附加属性,包括 HTTP Information(http.path、http.status_code)、RPC Information(rpc.type)及 Buildin Information(component.name=user_method、slow=1等)。 -
步骤2.x、3.x埋点效果:
通过OpenTelemetry SDK生成的Span和当前探针埋点Tomcat生成的Span在一条链路上,其中名为
child的Span会作为名为parent的Span的方法栈存在,且通过OpenTelemetry SDK生成的Span的相关Attribute也设置成功。在方法栈视图中,最外层为 OpenTelemetry Entry Span,内部依次调用 parent 和 child,三层调用均耗时约1.01秒。child 方法的 Attributes 中可见
line=-1、rpc.type=98。
相关文档
您可以在应用的业务日志中关联调用链的TraceId信息,从而在应用出现问题时,能够通过调用链的TraceId快速关联到业务日志,及时定位、分析解决问题。更多信息,请参见Java应用业务日志关联调用链TraceId。