antony@notes:~/ops-automation$ cat "Get-started-with-OpenTelemetry-in-Java.md"
Get started with OpenTelemetry in Java
Get started with OpenTelemetry in Java
0. Preface
本頁面將說明如何在 Java 中開始使用 OpenTelemetry。
您將學習如何自動檢測 (instrument) 一個簡單的 Java 應用程式,使其能夠將追蹤 (trace)、指標 (metric) 與日誌 (log) 輸出到主控台。
1. 先決條件
- 由於使用 Spring Boot 3,因此需要 Java JDK 17+; 否則需要 Java 8+
- Gradle 8.14
2. 範例應用
下列範例使用了一個基本的 Spring Boot 網站應用程式。
依賴套件
首先,請在一個名為
java-simple的新目錄中設定環境。mkdir -p "${HOME}"/wulin/java-simple; cd "${HOME}"/wulin/java-simple在該目錄中,建立一個名為
build.gradle.kts的檔案,其內容如下:nano build.gradle.kts執行結果:
plugins { id("java") id("org.springframework.boot") version "3.0.6" id("io.spring.dependency-management") version "1.1.0" } sourceSets { main { java.setSrcDirs(setOf(".")) } } repositories { mavenCentral() } dependencies { implementation("org.springframework.boot:spring-boot-starter-web") }建立一個名為
DiceApplication.java的文件,並將以下程式碼新增至該文件:nano DiceApplication.java執行結果:
package otel; import org.springframework.boot.Banner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class DiceApplication { public static void main(String[] args) { SpringApplication app = new SpringApplication(DiceApplication.class); app.setBannerMode(Banner.Mode.OFF); app.run(args); } }建立另一個名為
RollController.java的文件,並將以下程式碼新增至該文件:nano RollController.java執行結果:
package otel; import java.util.Optional; import java.util.concurrent.ThreadLocalRandom; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class RollController { private static final Logger logger = LoggerFactory.getLogger(RollController.class); @GetMapping("/rolldice") public String index(@RequestParam("player") Optional<String> player) { int result = this.getRandomNumber(1, 6); if (player.isPresent()) { logger.info("{} is rolling the dice: {}", player.get(), result); } else { logger.info("Anonymous player is rolling the dice: {}", result); } return Integer.toString(result); } public int getRandomNumber(int min, int max) { return ThreadLocalRandom.current().nextInt(min, max + 1); } }請使用下列指令來建置並執行應用程式
gradle assemble java -jar ./build/libs/java-simple.jar透過網路存取
http://localhost:8080/rolldice,以確保它能正常運作curl http://localhost:8080/rolldice; echo執行結果:
6按
Ctrl + c停止網站應用程序將以上步驟 1 ~ 5 寫進 Dockerfile
nano Dockerfile-v2-nonroot檔案內容如下:
FROM registry.suse.com/bci/bci-base:15.7 AS useradder ARG user RUN groupadd $user && \ useradd -m -d /home/$user -g $user $user FROM docker.io/library/gradle:8.14.3-jdk17-alpine AS build ARG user COPY --from=useradder /etc/passwd /etc/passwd COPY --from=useradder /etc/group /etc/group COPY --from=useradder /etc/shadow /etc/shadow COPY --from=useradder /home/$user /home/$user COPY build.gradle.kts DiceApplication.java RollController.java /java-simple WORKDIR /java-simple RUN chown -R $user:$user /java-simple && \ gradle assemble USER $user FROM registry.suse.com/bci/openjdk:17 ARG user COPY --from=useradder /etc/passwd /etc/passwd COPY --from=useradder /etc/group /etc/group COPY --from=useradder /etc/shadow /etc/shadow COPY --from=useradder /home/$user /home/$user RUN mkdir /app WORKDIR /app COPY --from=build --chown=$user:$user /java-simple/build/libs/java-simple.jar /app/java-simple.jar USER $user EXPOSE 8080 ENTRYPOINT ["java", "-jar", "/app/java-simple.jar"]製作 image
podman build --no-cache \ --squash-all \ --build-arg user=bigred \ -t java-simple:v2-nonroot \ -f Dockerfile-v2-nonrootRun java app container 測試是否符合預期
podman run -d \ --name java-simple \ -p 9999:8080 \ localhost/java-simple:v2-nonroot檢視 app container 運作狀態
podman ps -a --filter name=java-simple正確執行結果:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 3c1739039eed localhost/java-simple:v2-nonroot 14 minutes ago Up 14 minutes 0.0.0.0:9999->8080/tcp java-simple透過網路存取
http://192.168.11.21:9999/rolldice,以確保它能正常運作curl http://192.168.11.21:9999/rolldice; echo執行結果:
2移除 java app container
podman rm -f java-simple
3. Instrumentation
接下來,您將使用 Java 代理程式 (Java agent),在應用程式啟動時自動為其進行 instrument。雖然設定 Java 代理程式的方式有很多種,但以下步驟將採用環境變數來完成。
請從
opentelemetry-java-instrumentation儲存庫 (repository) 的 Releases 頁面下載opentelemetry-javaagent.jar。這個 JAR 檔案包含了代理程式 (agent) 以及所有 automatic instrumentation 所需的套件:curl -L -O https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar請設定並匯出用於指定 Java 代理程式(Java agent)JAR 檔與主控台匯出器(console exporter)的環境變數。請使用適合您 shell/終端機環境的語法——我們將示範適用於 bash 系列 shell 的語法:
export JAVA_TOOL_OPTIONS="-javaagent:$(pwd)/opentelemetry-javaagent.jar" \ OTEL_TRACES_EXPORTER=logging \ OTEL_METRICS_EXPORTER=logging \ OTEL_LOGS_EXPORTER=logging \ OTEL_METRIC_EXPORT_INTERVAL=15000:::danger
重要
- 請注意上述範例中的
$(pwd)/opentelemetry-javaagent.jar替換為您存放 JAR 檔的實際路徑。 - 只有在測試時,才將
OTEL_METRIC_EXPORT_INTERVAL的值設定得遠低於預設值(如上所示),這能幫助您更快地確認指標(metrics)是否已正確產生。
:::
- 請注意上述範例中的
再次執行您的應用程式
java -jar ./build/libs/java-simple.jar從另一個終端,使用
curl發送請求curl http://localhost:8080/rolldice; echo按
Ctrl + c停止網站應用程序在步驟 4,您應該會看到來自伺服器和用戶端的追蹤 (trace) 與日誌 (log) 輸出,內容大致如下 (為方便閱讀,追蹤輸出內容已自動換行):
2025-09-17T07:47:41.594Z INFO 'Anonymous player is rolling the dice: 6' : 0a4494bc657323e1cfc1bd2ca0f1fb9b 01edf287800f72ee [scopeInfo: otel.RollController:] {} 2025-09-17T15:47:41.594+08:00 INFO 22570 --- [io-8080-exec-10] otel.RollController : Anonymous player is rolling the dice: 6 [otel.javaagent 2025-09-17 15:47:41:595 +0800] [http-nio-8080-exec-10] INFO io.opentelemetry.exporter.logging.LoggingSpanExporter - 'GET /rolldice' : 0a4494bc657323e1cfc1bd2ca0f1fb9b 01edf287800f72ee SERVER [tracer: io.opentelemetry.tomcat-10.0:2.20.0-alpha] AttributesMap{data={http.route=/rolldice, http.request.method=GET, url.path=/rolldice, server.address=localhost, client.address=0:0:0:0:0:0:0:1, network.peer.address=0:0:0:0:0:0:0:1, network.peer.port=58634, network.protocol.version=1.1, thread.id=33, http.response.status_code=200, user_agent.original=curl/7.76.1, server.port=8080, url.scheme=http, thread.name=http-nio-8080-exec-10}, capacity=128, totalAddedValues=14}在步驟 5,當停止伺服器時,您應該會看到所有已收集指標 (metrics) 的輸出 (為方便閱讀,指標輸出內容已自動換行並經過簡化):
[otel.javaagent 2025-09-17 17:03:02:686 +0800] [Thread-0] INFO io.opentelemetry.exporter.logging.LoggingMetricExporter - Received a collection of 2 metrics for export. [otel.javaagent 2025-09-17 17:03:02:686 +0800] [Thread-0] INFO io.opentelemetry.exporter.logging.LoggingMetricExporter - metric: ImmutableMetricData{resource=Resource{schemaUrl=https://opentelemetry.io/schemas/1.24.0, attributes={container.id="1c59603168a7210a0d1f7edadc57129fcd3788da67ddbb0dad0ffd99def32bd2", host.arch="amd64", host.name="bastion.topgun.kubeantony.com", os.description="Linux 5.14.0-570.44.1.el9_6.x86_64", os.type="linux", process.command_args=[/usr/lib/jvm/java-17-openjdk-17.0.16.0.8-2.el9.x86_64/bin/java, -jar, ./build/libs/java-simple.jar], process.executable.path="/usr/lib/jvm/java-17-openjdk-17.0.16.0.8-2.el9.x86_64/bin/java", process.pid=23849, process.runtime.description="Red Hat, Inc. OpenJDK 64-Bit Server VM 17.0.16+8-LTS", process.runtime.name="OpenJDK Runtime Environment", process.runtime.version="17.0.16+8-LTS", service.instance.id="12b46158-6949-48ee-b8b6-54aff876afaa", service.name="java-simple", telemetry.distro.name="opentelemetry-java-instrumentation", telemetry.distro.version="2.20.0", telemetry.sdk.language="java", telemetry.sdk.name="opentelemetry", telemetry.sdk.version="1.54.0"}}, instrumentationScopeInfo=InstrumentationScopeInfo{name=io.opentelemetry.runtime-telemetry-java8, version=2.20.0-alpha, schemaUrl=null, attributes={}}, name=jvm.gc.duration, description=Duration of JVM garbage collection actions., unit=s, type=HISTOGRAM, data=ImmutableHistogramData{aggregationTemporality=CUMULATIVE, points=[ImmutableHistogramPointData{getStartEpochNanos=1758099741657518036, getEpochNanos=1758099782686024415, getAttributes={jvm.gc.action="end of minor GC", jvm.gc.name="G1 Young Generation"}, getSum=0.041, getCount=9, hasMin=true, getMin=0.002, hasMax=true, getMax=0.008, getBoundaries=[0.01, 0.1, 1.0, 10.0], getCounts=[9, 0, 0, 0, 0], getExemplars=[]}]}} [otel.javaagent 2025-09-17 17:03:02:686 +0800] [Thread-0] INFO io.opentelemetry.exporter.logging.LoggingMetricExporter - metric: ImmutableMetricData{resource=Resource{schemaUrl=https://opentelemetry.io/schemas/1.24.0, attributes={container.id="1c59603168a7210a0d1f7edadc57129fcd3788da67ddbb0dad0ffd99def32bd2", host.arch="amd64", host.name="bastion.topgun.kubeantony.com", os.description="Linux 5.14.0-570.44.1.el9_6.x86_64", os.type="linux", process.command_args=[/usr/lib/jvm/java-17-openjdk-17.0.16.0.8-2.el9.x86_64/bin/java, -jar, ./build/libs/java-simple.jar], process.executable.path="/usr/lib/jvm/java-17-openjdk-17.0.16.0.8-2.el9.x86_64/bin/java", process.pid=23849, process.runtime.description="Red Hat, Inc. OpenJDK 64-Bit Server VM 17.0.16+8-LTS", process.runtime.name="OpenJDK Runtime Environment", process.runtime.version="17.0.16+8-LTS", service.instance.id="12b46158-6949-48ee-b8b6-54aff876afaa", service.name="java-simple", telemetry.distro.name="opentelemetry-java-instrumentation", telemetry.distro.version="2.20.0", telemetry.sdk.language="java", telemetry.sdk.name="opentelemetry", telemetry.sdk.version="1.54.0"}}, instrumentationScopeInfo=InstrumentationScopeInfo{name=io.opentelemetry.tomcat-10.0, version=2.20.0-alpha, schemaUrl=null, attributes={}}, name=http.server.request.duration, description=Duration of HTTP server requests., unit=s, type=HISTOGRAM, data=ImmutableHistogramData{aggregationTemporality=CUMULATIVE, points=[ImmutableHistogramPointData{getStartEpochNanos=1758099741657518036, getEpochNanos=1758099782686024415, getAttributes=FilteredAttributes{http.request.method=GET,http.response.status_code=200,http.route=/rolldice,network.protocol.version=1.1,url.scheme=http}, getSum=0.08728259, getCount=1, hasMin=true, getMin=0.08728259, hasMax=true, getMax=0.08728259, getBoundaries=[0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1.0, 2.5, 5.0, 7.5, 10.0], getCounts=[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], getExemplars=[]}]}}將步驟 1-2 融入 Dockerfile
nano Dockerfile-v3-opentelemetry-instrumentation檔案內容如下:
FROM registry.suse.com/bci/bci-base:15.7 AS useradder ARG user RUN groupadd $user && \ useradd -m -d /home/$user -g $user $user FROM docker.io/library/gradle:8.14.3-jdk17-alpine AS build ARG user COPY --from=useradder /etc/passwd /etc/passwd COPY --from=useradder /etc/group /etc/group COPY --from=useradder /etc/shadow /etc/shadow COPY --from=useradder /home/$user /home/$user COPY build.gradle.kts DiceApplication.java RollController.java /java-simple WORKDIR /java-simple RUN chown -R $user:$user /java-simple && \ gradle assemble USER $user FROM registry.suse.com/bci/openjdk:17 ARG user COPY --from=useradder /etc/passwd /etc/passwd COPY --from=useradder /etc/group /etc/group COPY --from=useradder /etc/shadow /etc/shadow COPY --from=useradder /home/$user /home/$user RUN install -g $user -o $user -d /app WORKDIR /app COPY --from=build --chown=$user:$user /java-simple/build/libs/java-simple.jar /app/java-simple.jar USER $user RUN curl -L -O https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar ENV JAVA_TOOL_OPTIONS="-javaagent:/app/opentelemetry-javaagent.jar" \ OTEL_TRACES_EXPORTER=logging \ OTEL_METRICS_EXPORTER=logging \ OTEL_LOGS_EXPORTER=logging \ OTEL_METRIC_EXPORT_INTERVAL=15000 EXPOSE 8080 ENTRYPOINT ["java", "-jar", "/app/java-simple.jar"]製作 image
podman build --no-cache \ --squash-all \ --build-arg user=bigred \ -t java-simple:v3-opentelemetry-instrumentation \ -f Dockerfile-v3-opentelemetry-instrumentationRun java app container 測試是否符合預期
podman run -d \ --name java-simple \ -p 9999:8080 \ localhost/java-simple:v3-opentelemetry-instrumentation檢視 app container 運作狀態
podman ps -a --filter name=java-simple正確執行結果:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 01805e4e24f3 localhost/java-simple:v3-opentelemetry-instrumentation 4 minutes ago Up 4 minutes 0.0.0.0:9999->8080/tcp java-simple透過網路存取
http://192.168.11.21:9999/rolldice,以確保它能正常運作curl http://192.168.11.21:9999/rolldice; echo執行結果:
6檢視 log 是否擁有 trace 資訊
podman logs java-simple執行結果:
2025-09-18T02:04:52.34Z INFO 'Anonymous player is rolling the dice: 3' : 4dd621a6aa359c6caa6cb710fa10963a d192f1ee1b7afd06 [scopeInfo: otel.RollController:] {} 2025-09-18T02:04:52.340Z INFO 1 --- [nio-8080-exec-4] otel.RollController : Anonymous player is rolling the dice: 3 [otel.javaagent 2025-09-18 02:04:52:341 +0000] [http-nio-8080-exec-4] INFO io.opentelemetry.exporter.logging.LoggingSpanExporter - 'GET /rolldice' : 4dd621a6aa359c6caa6cb710fa10963a d192f1ee1b7afd06 SERVER [tracer: io.opentelemetry.tomcat-10.0:2.20.0-alpha] AttributesMap{data={server.address=192.168.11.21, client.address=169.254.1.2, network.peer.address=169.254.1.2, url.path=/rolldice, http.request.method=GET, network.peer.port=60368, http.route=/rolldice, url.scheme=http, thread.name=http-nio-8080-exec-4, user_agent.original=curl/7.76.1, http.response.status_code=200, thread.id=27, server.port=9999, network.protocol.version=1.1}, capacity=128, totalAddedValues=14}移除 java app container
podman rm -f java-simple