Spring Boot 中的 OpenTelemetry

工程 | 莫里茨·哈爾布里特 | 2025年11月18日 | ...

這是 Road to GA 系列中的一篇新部落格文章,這次我們將探討 Spring Boot 中的 OpenTelemetry。

引言

在現代雲原生架構中,可觀測性不再是可選項;它是一項基本要求。你希望透過指標瞭解應用程式正在做什麼,透過跟蹤瞭解請求如何流經應用程式,以及透過日誌瞭解應用程式正在說什麼。

OpenTelemetry 專案(有時縮寫為 OTel)提供了一個供應商中立的開源框架,用於收集、處理和匯出遙測資料。它由 雲原生計算基金會 提供支援,提供 API、SDK、用於匯出資料的標準網路協議 OTLP,以及用於處理資料攝取、處理和匯出到後端的可插拔架構(包括 OpenTelemetry Collector)。

經過檢測的專案和庫使用 API 傳送可觀測性資料。實現該 API 的 SDK 用於配置資料如何收集和匯出。Collector 是一個可選元件,可以幫助聚合和篩選資料,但您也可以將資料直接傳送到任何相容的後端。

Spring 生態系統透過 Micrometer 提供了強大的可觀測性支援,將 Spring Boot 與 OpenTelemetry 結合使用是涵蓋所有可觀測性訊號(指標、追蹤和日誌)的強大方式。這裡的關鍵支援因素是 OTLP 協議,而不是特定的庫。

在這篇文章中,我們將詳細介紹 OpenTelemetry 的含義,並瞭解如何將其與 Spring Boot 整合,以完美覆蓋可觀測性需求。

將 OpenTelemetry 與 Spring Boot 結合使用

當您選擇將 OpenTelemetry 與 Spring Boot 整合時,有幾種替代路徑,從“直接引入執行時代理”到“使用內建 Spring 支援”。您有三種選擇:

使用 OpenTelemetry Java 代理

入門很簡單:您在啟動時透過 -javaagent 標誌附加 opentelemetry-javaagent.jar。此代理對庫(HTTP、JDBC、Spring 等)進行位元組碼檢測。這是最簡單的“零程式碼更改”路徑。代理會識別追蹤、跨度(跨度是追蹤的原子部分)、指標等。

這種方法的主要問題是 Java 代理必須與您的庫版本緊密匹配,因為代理會修改它們的位元組碼。如果代理經過測試的版本與您使用的版本不匹配,問題可能很難診斷。此外,如果您正在使用 GraalVM 的 native-image 或想使用 Java 的 AOT 快取,您必須經歷額外的步驟。另外,如果您已經在使用代理,它們可能會發生衝突。

使用第三方 OpenTelemetry Spring Boot Starter

OpenTelemetry 有自己的 Spring Boot starter,它檢測某些技術。然而,他們指出 OpenTelemetry Java 代理是他們預設的檢測選擇,並且只有在代理無法使用時才應使用 starter。此外,儘管starter 本身被標記為穩定,但它引入的依賴項帶有 alpha 字尾。

使用 Spring 團隊的 OpenTelemetry Spring Boot Starter

隨著 Spring Boot 4.0 的釋出,我們正在引入一個新的 OpenTelemetry Spring Boot Starter。它被稱為 spring-boot-starter-opentelemetry(我們對這些名稱確實很聰明,不是嗎),並且可以透過 start.spring.io 上的“OpenTelemetry”依賴項進行選擇。

我們可能有些偏見,但這是我們在 Spring Boot 應用程式中實現可觀測性最喜歡的選項。

該 starter 包含 OpenTelemetry API 和透過 OTLP 匯出 Micrometer 訊號的元件。Micrometer Tracing 與 OpenTelemetry 橋接一起使用 OpenTelemetry API,以 OTLP 格式匯出追蹤。Micrometer OtlpMeterRegistry 用於透過 OTLP 協議將透過 Micrometer API 收集的指標傳送到支援 OpenTelemetry 的指標後端。

重申一下:重要的是協議,而不是所使用的庫。儘管 Spring 組合專案使用 Micrometer 作為其可觀測性 API,但完全可以透過 OTLP 將訊號匯出到任何支援 OpenTelemetry 的後端,您很快就會看到。

Spring Boot 還支援透過 OTLP 將日誌傳送到支援 OpenTelemetry 的後端,但它不會在 Logback 和 Log4j2 中開箱即用安裝日誌附加器。這將來可能會改變,但即使現在,也很容易做到,我們也會在這篇部落格文章中介紹設定。

這篇部落格文章的其餘部分將重點介紹如何使用 Spring 團隊的新 OpenTelemetry Spring Boot Starter 在您的 Spring Boot 應用程式中獲得無縫的 OpenTelemetry 體驗。

匯出指標

如前所述,Spring Boot 使用 Micrometer OTLP 登錄檔透過 OTLP 將 Micrometer 指標匯出到任何支援 OpenTelemetry 的後端。所需的依賴項 io.micrometer:micrometer-registry-otlp 已包含在 spring-boot-starter-opentelemetry 中。有了這個依賴項,Micrometer 將以 OTLP 格式將指標匯出到後端 https://:4318/v1/metrics。要自定義指標匯出位置,請設定 management.otlp.metrics.export.url 屬性,例如:

management.otlp.metrics.export.url=http://otlp.example.com:4318/v1/metrics

Micrometer 團隊還為 OpenTelemetry 添加了所謂的觀察約定。OpenTelemetry 中的訊號遵循 OpenTelemetry 語義約定,Micrometer 中的觀察約定實現了 OpenTelemetry 語義約定的穩定部分。要在 Spring Boot 中使用它們,您必須定義一些配置(這將來可能會改進

@Configuration(proxyBeanMethods = false)
public class OpenTelemetryConfiguration {

    @Bean
    OpenTelemetryServerRequestObservationConvention openTelemetryServerRequestObservationConvention() {
        return new OpenTelemetryServerRequestObservationConvention();
    }

    @Bean
    OpenTelemetryJvmCpuMeterConventions openTelemetryJvmCpuMeterConventions() {
        return new OpenTelemetryJvmCpuMeterConventions(Tags.empty());
    }

    @Bean
    ProcessorMetrics processorMetrics() {
        return new ProcessorMetrics(List.of(), new OpenTelemetryJvmCpuMeterConventions(Tags.empty()));
    }

    @Bean
    JvmMemoryMetrics jvmMemoryMetrics() {
        return new JvmMemoryMetrics(List.of(), new OpenTelemetryJvmMemoryMeterConventions(Tags.empty()));
    }

    @Bean
    JvmThreadMetrics jvmThreadMetrics() {
        return new JvmThreadMetrics(List.of(), new OpenTelemetryJvmThreadMeterConventions(Tags.empty()));
    }

    @Bean
    ClassLoaderMetrics classLoaderMetrics() {
        return new ClassLoaderMetrics(new OpenTelemetryJvmClassLoadingMeterConventions());
    }

}

Spring Boot 不會自動配置用於指標的 OpenTelemetry API。如果您真的想使用 OpenTelemetry 指標 API(我們不推薦)而不是 Micrometer API,或者如果您有一個使用 OpenTelemetry 指標 API 的庫,請檢視 Spring Boot 文件,瞭解如何使其工作。

匯出追蹤

Spring 專案使用 Micrometer Observation API 建立觀測。觀測是 Micrometer 中一個有趣的概念,因為它可以轉換為指標**和**追蹤。

然後,透過 io.micrometer:micrometer-tracing-bridge-otel 依賴項(也包含在 spring-boot-starter-opentelemetry 中),將由觀測生成的追蹤適配到 OpenTelemetry API。

Spring Boot 包含 OpenTelemetry SDK 的自動配置。對於追蹤,它安裝了一個 OtlpHttpSpanExporter(如果您更喜歡 gRPC 而不是 HTTP,則為 OtlpGrpcSpanExporter)。有了這個匯出器,OpenTelemetry SDK 現在開始以 OTLP 格式將追蹤(源自 Micrometer Observation)傳送到您的後端。

要在您的應用程式中啟用它,您必須設定 management.opentelemetry.tracing.export.otlp.endpoint 屬性,例如:

management.opentelemetry.tracing.export.otlp.endpoint=https://:4318/v1/traces

匯出日誌

如前所述,Spring Boot 包含自動配置,可配置 OpenTelemetry SDK 以便能夠以 OTLP 格式匯出日誌。然而,它不會在 Logback 或 Log4j2 中安裝附加器,因此儘管底層基礎設施存在,但實際上並沒有匯出任何日誌。要以 OTLP 格式匯出日誌,您需要做兩件事:

首先,設定屬性 management.opentelemetry.logging.export.otlp.endpoint,例如:

management.opentelemetry.logging.export.otlp.endpoint=https://:4318/v1/logs

其次,在 Logback 或 Log4j2 中安裝一個附加器,將日誌條目傳送到 OpenTelemetry API。

對於 Logback,我們首先需要包含 io.opentelemetry.instrumentation:opentelemetry-logback-appender-1.0:2.21.0-alpha 依賴項(版本號中的 -alpha 表示它被標記為不穩定;不幸的是,附加器沒有非 alpha 版本。您可以在此處閱讀更多資訊)。

然後,我們必須建立一個自定義的 Logback 配置,該配置位於 src/main/resources/logback-spring.xml 檔案中:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/base.xml"/>

    <appender name="OTEL" class="io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender">
    </appender>

    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="OTEL"/>
    </root>
</configuration>

此配置匯入 Spring Boot 的 Logback 基本配置,然後定義一個名為 OTEL 的附加程式,該附加程式將所有日誌事件傳送到 OpenTelemetry API。然後將此附加程式新增到根記錄器,從而除了控制檯之外,還將所有日誌條目傳送到 OpenTelemetry API。

最後要做的是讓 OpenTelemetryAppender 知道要使用哪個 OpenTelemetry API 例項。為此,我們可以建立一個小型 bean,將 OpenTelemetry 例項注入:

@Component
class InstallOpenTelemetryAppender implements InitializingBean {

    private final OpenTelemetry openTelemetry;

    InstallOpenTelemetryAppender(OpenTelemetry openTelemetry) {
        this.openTelemetry = openTelemetry;
    }

    @Override
    public void afterPropertiesSet() {
        OpenTelemetryAppender.install(this.openTelemetry);
    }
    
}

注意上下文

日誌、指標和追蹤使用上下文資訊,例如當前的追蹤 ID。預設情況下,當 Micrometer Tracing 在類路徑上時,Spring Boot 會調整日誌模式,以便在日誌訊息中也包含追蹤 ID 和跨度 ID。如果您正在嘗試查詢屬於某個追蹤的日誌,這會非常有用。

一個有用的模式是在伺服器的響應中(例如,透過 HTTP 標頭)包含請求的追蹤 ID。這樣,如果使用者從您的 HTTP 端點收到錯誤響應,他們可以將追蹤 ID 包含在工單中,您可以使用此追蹤 ID 來獲取屬於該錯誤請求的所有日誌。

使用此 Servlet 過濾器可以將追蹤 ID 放入標頭中:

@Component
class TraceIdFilter extends OncePerRequestFilter {
    
    private final Tracer tracer;

    TraceIdFilter(Tracer tracer) {
        this.tracer = tracer;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String traceId = getTraceId();
        if (traceId != null) {
            response.setHeader("X-Trace-Id", traceId);
        }
        filterChain.doFilter(request, response);
    }

    private @Nullable String getTraceId() {
        TraceContext context = this.tracer.currentTraceContext().context();
        return context != null ? context.traceId() : null;
    }
    
}

如果您正在處理切換執行緒的方法,例如 @Async 註解的方法或使用 Spring 的 AsyncTaskExecutor,您會注意到在新執行緒中上下文丟失了。丟失的上下文會影響日誌(不再包含追蹤 ID)和追蹤(丟失了跨度)。

上下文丟失是因為它儲存在 ThreadLocal 中,而 ThreadLocal 不會傳輸到新執行緒。然而,解決方案非常簡單:在 AsyncTaskExecutor(也用於 @Async 註解的方法)中使用 ContextPropagatingTaskDecoratorContextPropagatingTaskDecorator 使用 Micrometer 的上下文傳播 API 來確保追蹤上下文傳輸到新執行緒。安裝 ContextPropagatingTaskDecorator 很簡單:只需定義一個 @Bean 方法,如以下程式碼片段所示:

@Configuration(proxyBeanMethods = false)
public class ContextPropagationConfiguration {

    @Bean
    ContextPropagatingTaskDecorator contextPropagatingTaskDecorator() {
        return new ContextPropagatingTaskDecorator();
    }

}

Spring Boot 的自動配置會查詢 TaskDecorator bean 並將其安裝到 AsyncTaskExecutor 中。有了 ContextPropagatingTaskDecorator,上下文現在被傳輸到新執行緒,從而修復了日誌中丟失的追蹤 ID 和丟失的跨度。ContextPropagatingTaskDecorator 的整個設定將來可能會得到改進,以提供更無縫的體驗。

上下文傳播,再次

如果您正在處理多個服務,如果所有服務的追蹤 ID 都相同,這樣您就可以檢視一個追蹤並看到所有相關服務,或者找到參與該追蹤的所有服務的日誌,那不是很好嗎?這就是分散式追蹤中“分散式”的來源。

現在,您是否曾想過被呼叫的服務如何知道它是正在進行的追蹤的一部分?這又是上下文傳播,但這次上下文不是跨執行緒傳播,而是跨程序邊界傳播。

有一個關於透過 HTTP 進行上下文傳播的 W3C 建議,Spring Boot 開箱即用地配置了所有相關元件以使用它。傳送方必須將當前追蹤 ID 新增到標頭中,接收方必須從標頭中提取追蹤 ID 並恢復上下文。

這一切都無縫執行,只要您遵循一個簡單規則:不要談論搏擊俱樂部。哦,抱歉,劇本錯了。您必須遵循的唯一規則是:不要自己 new 一個 RestTemplate / RestClient / WebClient。相反,注入一個 RestTemplateBuilder / RestClient.Builder / WebClient.Builder 並使用它來建立客戶端。

Spring Boot 會自動配置這些構建器,並提供所有必要的基礎設施,以自動在標頭中傳送追蹤上下文。如果您自己用 new 建立客戶端,那麼此基礎設施將不存在,上下文也不會傳播,導致值班團隊成員感到悲傷,並且衝刺回顧中出現紅色方塊。

讓我們看看實際效果

我們準備了一個示例專案,您可以使用它來體驗 OpenTelemetry 可觀測性。它包含三個服務:

使用者服務使用記憶體中的 H2 資料庫和 Spring Data JDBC 根據使用者 ID 查詢使用者。它公開了一個 HTTP API,用於根據給定的使用者 ID 查詢使用者。

問候語服務有一個 HTTP API,它根據 Accept-Language 標頭中指定的語言返回問候語。它知道英語、德語和西班牙語的問候語。

Hello 服務是入口點。它有一個 HTTP API,接收使用者 ID 並返回該使用者的問候語。為此,它會使用使用者 ID 呼叫使用者服務以獲取使用者的姓名。它還會呼叫問候語服務以獲取問候語。然後它將使用者的姓名與問候語結合起來並返回。

首先,讓我們啟動所有三個服務。此設定還包括 spring-boot-docker-compose 模組,該模組會自動檢測名為 compose.yaml 的 Docker Compose 配置檔案並呼叫 docker compose upcompose.yaml 檔案包含一個使用 grafana/otel-lgtm 映象的服務。Grafana LGTM 堆疊包含支援 OTLP 的日誌、指標和追蹤後端,所有這些都打包在一個 UI 中,我們可以使用它來檢視可觀測性訊號。

Docker 容器啟動並執行後,Spring Boot 會自動配置日誌、指標和追蹤的匯出器,使其指向 Docker 容器。這就是為什麼我們在 application.properties 中找不到前面提到的 OTLP 匯出屬性;這一切都在幕後進行。當您將其部署到生產環境時,您必須自己設定這些屬性。如果您想了解更多關於這種開發者體驗功能的資訊,請閱讀這篇部落格文章

現在讓我們執行第一個請求:

> curl -i https://:8080/api/1
HTTP/1.1 200 
X-Trace-Id: 0dbe0809731e35081d6db16c2ca0ef91
Content-Type: application/json
Content-Length: 12

Hello Moritz

太棒了,成功了。現在讓我們用德語試試:

> curl -i https://:8080/api/1 --header "Accept-Language: de"
HTTP/1.1 200 
X-Trace-Id: 6c0753c7ec390fff15fcf05f536e21cd
Content-Type: application/json
Content-Length: 12

Hallo Moritz

很好,我們現在有兩個追蹤 ID 可以玩了。請注意,請求的追蹤 ID 如何包含在 X-Trace-Id 標頭中,使用了上面的 Servlet 過濾器。

讓我們看看 Grafana UI 中是否有日誌(您可以單擊影像放大

A screenshot of Grafana, showing logs from all three services

在這裡,我們可以看到日誌已透過 OTLP 傳送到 Grafana,我們現在可以在一個 UI 中檢視來自三個服務的所有日誌。也可以找到所有服務的日誌,並根據追蹤 ID 進行查詢。

A screenshot of Grafana, showing logs from all three services for a given trace id

現在讓我們看看是否可以找到追蹤 ID 對應的追蹤。

A screenshot of Grafana, showing the trace details

找到了!在這裡,我們可以看到 hello 服務以漂亮的瀑布圖呼叫了 greeting 服務和 user 服務。您還可以展開一個跨度以檢視跨度屬性。

A screenshot of Grafana, showing a span with the en-US locale

在這種情況下,我們使用了 Micrometer 的 @SpanTag 將問候語區域設定 (en_US) 和使用者 ID (1) 附加到跨度。讓我們看看第二個追蹤,其中應該有德語區域設定:

A screenshot of Grafana, showing a span with the de locale

非常好,按預期工作。

最後一站,讓我們看看服務生成的指標

A screenshot of Grafana, showing the custom say-hello metric

在這裡,您可以看到一個自定義指標,名為 say-hello(透過使用 @Observed(name = "say-hello") 註解方法建立),它計算 hello 訊息生成的次數。

您還可以開箱即用地獲得大量關於應用程式的指標,例如執行器中的執行緒數、HTTP 伺服器等。

A screenshot of Grafana, showing a part of the thread metrics

或者許多 JVM 指標

A screenshot of Grafana, showing a part of the JVM metrics

結論

我們希望您在 Spring Boot 與 OpenTelemetry 的這次旋風之旅中玩得開心。正如您所看到的,Spring Boot 與 OpenTelemetry 進行了很好的整合。無論您是否使用 Micrometer 的 Observation API,從 OTel 整合角度來看,這並不重要。重要的是協議 OTLP,它抽象了用於檢測應用程式的 API。

帶有新 OpenTelemetry starter 的 Spring Boot 4.0 將於 11 月 20 日釋出。Micrometer 1.16 已釋出,其中包含 OpenTelemetry 增強功能和許多其他新功能。如果您發現任何問題或對如何改進整個 OpenTelemetry 故事有好的想法,請在我們的問題跟蹤器中聯絡我們!

獲取 Spring 新聞通訊

透過 Spring 新聞通訊保持聯絡

訂閱

領先一步

VMware 提供培訓和認證,助您加速進步。

瞭解更多

獲得支援

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位制檔案,只需一份簡單的訂閱。

瞭解更多

即將舉行的活動

檢視 Spring 社群所有即將舉行的活動。

檢視所有