領先一步
VMware 提供培訓和認證,助您加速進步。
瞭解更多這是 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 整合時,有幾種替代路徑,從“直接引入執行時代理”到“使用內建 Spring 支援”。您有三種選擇:
入門很簡單:您在啟動時透過 -javaagent 標誌附加 opentelemetry-javaagent.jar。此代理對庫(HTTP、JDBC、Spring 等)進行位元組碼檢測。這是最簡單的“零程式碼更改”路徑。代理會識別追蹤、跨度(跨度是追蹤的原子部分)、指標等。
這種方法的主要問題是 Java 代理必須與您的庫版本緊密匹配,因為代理會修改它們的位元組碼。如果代理經過測試的版本與您使用的版本不匹配,問題可能很難診斷。此外,如果您正在使用 GraalVM 的 native-image 或想使用 Java 的 AOT 快取,您必須經歷額外的步驟。另外,如果您已經在使用代理,它們可能會發生衝突。
OpenTelemetry 有自己的 Spring Boot starter,它檢測某些技術。然而,他們指出 OpenTelemetry Java 代理是他們預設的檢測選擇,並且只有在代理無法使用時才應使用 starter。此外,儘管starter 本身被標記為穩定,但它引入的依賴項帶有 alpha 字尾。
隨著 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 註解的方法)中使用 ContextPropagatingTaskDecorator。ContextPropagatingTaskDecorator 使用 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 up。compose.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 中是否有日誌(您可以單擊影像放大)
在這裡,我們可以看到日誌已透過 OTLP 傳送到 Grafana,我們現在可以在一個 UI 中檢視來自三個服務的所有日誌。也可以找到所有服務的日誌,並根據追蹤 ID 進行查詢。
現在讓我們看看是否可以找到追蹤 ID 對應的追蹤。
找到了!在這裡,我們可以看到 hello 服務以漂亮的瀑布圖呼叫了 greeting 服務和 user 服務。您還可以展開一個跨度以檢視跨度屬性。
在這種情況下,我們使用了 Micrometer 的 @SpanTag 將問候語區域設定 (en_US) 和使用者 ID (1) 附加到跨度。讓我們看看第二個追蹤,其中應該有德語區域設定:
非常好,按預期工作。
最後一站,讓我們看看服務生成的指標
在這裡,您可以看到一個自定義指標,名為 say-hello(透過使用 @Observed(name = "say-hello") 註解方法建立),它計算 hello 訊息生成的次數。
您還可以開箱即用地獲得大量關於應用程式的指標,例如執行器中的執行緒數、HTTP 伺服器等。
或者許多 JVM 指標
我們希望您在 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 故事有好的想法,請在我們的問題跟蹤器中聯絡我們!