接入ARMS應用監控以后,ARMS探針對常見的Java框架進行了自動埋點,因此不需要修改任何代碼,就可以實現調用鏈信息的采集。如果您需要在調用鏈信息中,體現業務方法的執行情況,可以引入OpenTelemetry Java SDK,在業務代碼中增加自定義埋點。
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埋點的兼容
ARMS對OpenTelemetry埋點的兼容性介紹涉及以下名詞,OpenTelemetry相關的其他名稱解釋,請參見OpenTelemetry Specification。
Span:一次請求的一個具體操作,比如遠程調用入口或者內部方法調用。
SpanContext:一次請求追蹤的上下文,用于關聯該次請求下的具體操作。
Attribute:Span的附加屬性字段,用于記錄關鍵信息。
OpenTelemetry的Span可以分為三類:
入口Span:會創建新的SpanContext,例如Server、Consumer。
說明對于此類Span,ARMS的埋點入口多位于框架內部,手動埋點時鏈路上下文已存在,ARMS會將OpenTelemetry的入口Span作為內部Span處理。對于ARMS沒有埋點的入口,則OpenTelemetry的入口Span保持不變,例如異步調用或者自定義RPC框架入口,同時ARMS會在客戶端聚合,生成相關統計數據。
內部Span:會復用已經創建的SpanContext,作為內部方法棧記錄。
出口Span:會將SpanContext透傳下去,例如Client、Producer。
目前,ARMS對于入口Span和內部Span做了兼容。對于出口Span,ARMS暫不支持按照OpenTelemetry標準透傳,而是按照ARMS自定義格式透傳。
例如以下代碼:
以下代碼片段需要注意,最終獲取OpenTelemetry實例需要通過調用GlobalOpenTelemetry.get()方法獲取,不能直接使用上一步通過OpenTelemetry SDK手動構建的Opentelemetry實例。否則會導致在4.x版本探針中無法看到通過SDK埋點生成的Span數據。
package com.alibaba.arms.brightroar.console.controller;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.baggage.Baggage;
import io.opentelemetry.api.common.AttributeKey;
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.api.trace.Tracer;
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.PostConstruct;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/ot")
public class OpenTelemetryController {
private Tracer tracer;
private ScheduledExecutorService ses = Executors.newSingleThreadScheduledExecutor();
@PostConstruct
public void init() {
OpenTelemetry openTelemetry = 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.builder()
.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()) {
span.setAttribute("user.id", Baggage.current().getEntryValue("user.id"));
span.addEvent("Sleep Start");
Thread.sleep(1000);
Attributes attr = Attributes.of(AttributeKey.longKey("cost"), 1000L);
span.addEvent("Sleep End", attr);
} catch (Throwable e) {
span.setStatus(StatusCode.ERROR, e.getMessage());
} finally {
span.end();
}
}
}
以上示例代碼中通過OpenTelemetry SDK創建了三個Span:
parent
:按照OpenTelemetry標準是HTTP入口,但是由于ARMS在Tomcat內置代碼中已經創建了鏈路上下文,因此這里會作為一個內部方法記錄在ARMS方法棧上。child
:parent
Span的內部Span,作為內部方法記錄在方法棧上。schedule
:獨立線程入口Span,默認情況下ARMS沒有為此類入口創建上下文,因此這里會作為一個自定義方法入口,并生成相應的統計數據。
使用OpenTelemetry Baggage API透傳業務自定義標簽
在OpenTelemetry中,Baggage可以作為上下文信息在Span之間傳遞,通過向Baggage設置鍵值對,透傳業務自定義標簽。Baggage存儲在HTTP Header中并通過HTTP Header進行傳播,因此不應在Baggage中存儲敏感數據。
以上示例代碼的Parent Span先在Baggage中存儲兩個鍵值對,然后在Child Span中獲取Baggage中存儲的值。
獲取Trace ID
SpanContext中包含Trace ID和Span ID等信息,Trace ID可以通過Span.current().getSpanContext().getTraceId()
方法獲得。
在ARMS控制臺查看parent
和child
在ARMS控制臺找到/ot/parent的HTTP入口的內部方法棧,可以看到多了以下Span的展示。更多信息,請參見調用鏈分析。
在ARMS控制臺查看schedule
ARMS控制臺支持通過以下幾個頁面查看:
異步上下文傳遞
僅4.x及以上探針版本支持。
下方是一個簡單的生產者消費者模式代碼示例,在生產者中生產事件時,將生產者線程的Trace上下文記錄到事件中,在消費事件時,取出上下文并還原。
class Event {
private Context context;
private String msg;
public Event(Context context, String msg) {
this.context = context;
this.msg = msg;
}
}
private final LinkedBlockingQueue<Event> linkedBlockingQueue = new LinkedBlockingQueue<Event>();
public void produce(String msg) {
linkedBlockingQueue.add(new Event(Context.current(), msg));
}
public void consume() throws Exception {
Event event = linkedBlockingQueue.take();
try(Scope scope = event.context.makeCurrent()) {
processEvent(event);
}
}
public void processEvent(Event event) {
//todo process event
}
相關文檔
您可以在應用的業務日志中關聯調用鏈的TraceId信息,從而在應用出現問題時,能夠通過調用鏈的TraceId快速關聯到業務日志,及時定位、分析解決問題。更多信息,請參見業務日志關聯調用鏈的TraceId信息。