接入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自訂格式透傳。
例如以下代碼:
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資訊。