全部產品
Search
文件中心

Application Real-Time Monitoring Service:通過OpenTelemetry Java SDK為調用鏈增加自訂埋點

更新時間:Dec 24, 2024

接入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埋點的相容

名詞介紹

此處只介紹一些常用的名詞,其他名稱解釋,請參見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();
        }
    }
}

範例程式碼說明:

  1. OpenTelemetryControllerinit方法中啟動了一個定時調度任務,並在調度任務開始時建立一個Span,在調度任務結束時關閉該Span。

  2. OpenTelemetryControllerparent方法中調用了Opentelemetry SDK的多個方法。

    1. 每次被調用時建立一個名為parent的Span,並在方法結束時關閉該Span。

    2. 調用Baggage SDK,新加了名為user.iduser.name的兩對Baggage,這兩個Baggage後續會傳遞到下遊應用。

    3. 為步驟2.1建立的Span新增了兩個Attribute。

  3. OpenTelemetryControllerchild方法中會執行以下操作。

    1. 每次被調用時建立一個名為child的Span,並在方法結束時關閉改Span,需要注意,該Span是2.1步驟建立的Span的子Span。

    2. 調用擷取Trace內容相關的方法並列印traceId。

    3. 調用擷取Baggage的方法並列印步驟2.2中新增的Baggage。

不同探針差異點對比

對於上述代碼中的不同操作,ARMS 3.x及以下版本探針和ARMS 4.x探針的支援情況有所區別,詳情可見下方表格。

步驟

4.x及以上版本探針

3.x及以下版本探針

1

支援,會產生一個新的Span。

支援,會產生一個新的Span。

2.1

支援

支援

2.2

支援

不支援

2.3

支援

支援

3.1

支援

支援,該Span會作為步驟2.1建立出來的Span的方法棧存在。

3.2

支援,和ARMS中traceId是一個。

不支援,列印出的traceId和ARMS探針中的traceId不同。

3.2

支援

支援

埋點效果展示

4.x及以上版本

  • 步驟1埋點效果:

    可以正常看到通過OpenTelemetry SDK產生的Span。

    image

  • 步驟2.x、3.x埋點效果:

    通過OpenTelemetry SDK產生的Span(紅框)和當前探針埋點Tomcat產生的Span(黃框)在一條鏈路上,且通過OpenTelemetry SDK產生的Span的相關Attribute也設定成功(藍框)。

    image.png

3.x及以上版本探針

  • 步驟1埋點效果:

    2024-12-16_15-41-45

  • 步驟2.x、3.x埋點效果:

    通過OpenTelemetry SDK產生的Span(紅框)和當前探針埋點Tomcat產生的Span(黃框)在一條鏈路上,其中名為child的Span會作為名為parent的Span的方法棧存在,且通過OpenTelemetry SDK產生的Span的相關Attribute也設定成功(藍框)。

    image

    image

相關文檔

您可以在應用的業務日誌中關聯調用鏈的TraceId資訊,從而在應用出現問題時,能夠通過調用鏈的TraceId快速關聯到業務日誌,及時定位、分析解決問題。更多資訊,請參見Java應用業務日誌關聯調用鏈TraceId