跳至內容

antony@notes:~/ops-automation$ cat "Get-started-with-OpenTelemetry-in-Java.md"

Get started with OpenTelemetry in Java

2025-09-18· 維運與自動化

0. Preface

本頁面將說明如何在 Java 中開始使用 OpenTelemetry。

您將學習如何自動檢測 (instrument) 一個簡單的 Java 應用程式,使其能夠將追蹤 (trace)指標 (metric)日誌 (log) 輸出到主控台。

1. 先決條件

  1. 由於使用 Spring Boot 3,因此需要 Java JDK 17+; 否則需要 Java 8+
  2. Gradle 8.14

2. 範例應用

下列範例使用了一個基本的 Spring Boot 網站應用程式。

依賴套件

  1. 首先,請在一個名為 java-simple 的新目錄中設定環境。

    mkdir -p "${HOME}"/wulin/java-simple; cd "${HOME}"/wulin/java-simple
  2. 在該目錄中,建立一個名為 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")
    }
  3. 建立一個名為 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);
      }
    }
  4. 建立另一個名為 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);
      }
    }
  5. 請使用下列指令來建置並執行應用程式

    gradle assemble
    java -jar ./build/libs/java-simple.jar
  6. 透過網路存取 http://localhost:8080/rolldice,以確保它能正常運作

    curl http://localhost:8080/rolldice; echo

    執行結果:

    6
  7. Ctrl + c 停止網站應用程序

  8. 將以上步驟 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"]
  9. 製作 image

    podman build --no-cache \
      --squash-all \
      --build-arg user=bigred \
      -t java-simple:v2-nonroot \
      -f Dockerfile-v2-nonroot
  10. Run java app container 測試是否符合預期

    podman run -d \
      --name java-simple \
      -p 9999:8080 \
      localhost/java-simple:v2-nonroot
  11. 檢視 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
  12. 透過網路存取 http://192.168.11.21:9999/rolldice,以確保它能正常運作

    curl http://192.168.11.21:9999/rolldice; echo

    執行結果:

    2
  13. 移除 java app container

    podman rm -f java-simple

3. Instrumentation

接下來,您將使用 Java 代理程式 (Java agent),在應用程式啟動時自動為其進行 instrument。雖然設定 Java 代理程式的方式有很多種,但以下步驟將採用環境變數來完成。

  1. 請從 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
  2. 請設定並匯出用於指定 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

    重要

    • 請注意上述範例中的 $(pwd)/opentelemetry-javaagent.jar 替換為您存放 JAR 檔的實際路徑
    • 只有在測試時,才將 OTEL_METRIC_EXPORT_INTERVAL 的值設定得遠低於預設值(如上所示),這能幫助您更快地確認指標(metrics)是否已正確產生。
  3. 再次執行您的應用程式

    java -jar ./build/libs/java-simple.jar
  4. 從另一個終端,使用 curl 發送請求

    curl http://localhost:8080/rolldice; echo
  5. Ctrl + c 停止網站應用程序

  6. 步驟 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}
  7. 步驟 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=[]}]}}
  8. 將步驟 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"]
  9. 製作 image

    podman build --no-cache \
      --squash-all \
      --build-arg user=bigred \
      -t java-simple:v3-opentelemetry-instrumentation \
      -f Dockerfile-v3-opentelemetry-instrumentation
  10. Run java app container 測試是否符合預期

    podman run -d \
      --name java-simple \
      -p 9999:8080 \
      localhost/java-simple:v3-opentelemetry-instrumentation
  11. 檢視 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
  12. 透過網路存取 http://192.168.11.21:9999/rolldice,以確保它能正常運作

    curl http://192.168.11.21:9999/rolldice; echo

    執行結果:

    6
  13. 檢視 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}
  14. 移除 java app container

    podman rm -f java-simple