Skip to content

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

Get started with OpenTelemetry in Java

2025-09-18· ops-automation

Get started with OpenTelemetry in Java

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

    :::danger

    重要

    • 請注意上述範例中的 $(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