kmuto’s blog

はてな社でMackerel CREをやっています。料理と旅行といろんなIT技術

OpenTelemetryのzero-code計装を試している〜その3。Java/Kotlin

Go、.NETときて、今回はJavaでのzero-code計装のトライである。

kmuto.hatenablog.com kmuto.hatenablog.com

公式サイトによれば、Java Agentを使う方法と、Spring Boot starterを使う方法がある。

まずはJava Agentで試すことにした。

Java Agentの場合、シンプルにopentelemetry-javaagent.jarをエージェント組み込みするだけでJVMから情報をひっぱってきてくれる。

java -javaagent:path/to/opentelemetry-javaagent.jar -Dotel.service.name=your-service-name -jar myapp.jar

なるほど簡単。引数のほか環境変数、プロパティファイルで指定することもできる。

で、さっそく素から立てやすいWebサーバーとしてsun.net.httpserver.HttpServerを使って立てたのだが…JVMのメトリックは飛ぶものの、トレースが全然送られない。

サポートライブラリの対象になかった。せやな…。

標準ライブラリ範疇だとJava Http Clientやloggingあたりの対応があるのだが、Webサーバーだと結局KtorかSpringになるようだ。

Javaと一言で表すには大掛かりになってしまうものの、KotlinのKtorで試すことにする。

Kotlinは昔書籍制作で検証するのに使ったくらいで、完全に忘却。とはいえ、公式サイトを見ながら適当にやったらできた。

Project Generatorからktor-sample.zipをダウンロードし、展開する。

src/main/kotlin/Routing.kt/500/errorのハンドラを追加した。

package com.example

import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*

fun Application.configureRouting() {
    routing {
        get("/") {
            call.respondText("Hello World!")
        }
        get("/500") {
            call.respondText("Error", status = io.ktor.http.HttpStatusCode.InternalServerError)
        }
        get("/error") {
            throw RuntimeException("Runtime Error")
        }
    }
}

ビルドして、正常に動作するか試す。

./gradlew build
java -jar build/libs/ktor-sample-all.jar

ポート8080で動いているので、curlから呼び出し。

$ curl http://localhost:8080
Hello World!
$ curl http://localhost:8080/error
(何も出ないけどサーバー側は例外が出ている)
$ curl http://localhost:8080/500
Error

いつものようにOpenTelemetry Collectorを実行しておく。

receivers:
  otlp:
    protocols:
      http:

exporters:
  debug:
    verbosity: detailed

service:
  pipelines:
    traces:
      receivers: [otlp]
      exporters: [debug]

ではzero-code計装を注入する。

java -javaagent:./opentelemetry-javaagent.jar -Dotel.service.name=kotlin-zerocode -jar build/libs/ktor-sample-all.jar

これで再度curlを試してみると、トレースが出てくる。

2025-01-19T17:50:04.119+0900    info    Traces  {"kind": "exporter", "data_type": "traces", "name": "debug", "resource spans": 1, "spans": 1}
2025-01-19T17:50:04.119+0900    info    ResourceSpans #0
Resource SchemaURL: https://opentelemetry.io/schemas/1.24.0
Resource attributes:
     -> host.arch: Str(amd64)
     -> host.name: Str(...)
     -> os.description: Str(Linux 6.1.0-30-amd64)
     -> os.type: Str(linux)
     -> process.command_args: Slice(["/usr/lib/jvm/java-17-openjdk-amd64/bin/java","-javaagent:./opentelemetry-javaagent.jar","-Dotel.service.name=kotlin-zerocode","-jar","build/libs/ktor-sample-all.jar"])
     -> process.executable.path: Str(/usr/lib/jvm/java-17-openjdk-amd64/bin/java)
     -> process.pid: Int(349907)
     -> process.runtime.description: Str(Debian OpenJDK 64-Bit Server VM 17.0.13+11-Debian-2deb12u1)
     -> process.runtime.name: Str(OpenJDK Runtime Environment)
     -> process.runtime.version: Str(17.0.13+11-Debian-2deb12u1)
     -> service.instance.id: Str(0da10240-547e-4a45-b28c-6133d3ffe1b5)
     -> service.name: Str(kotlin-zerocode)
     -> telemetry.distro.name: Str(opentelemetry-java-instrumentation)
     -> telemetry.distro.version: Str(2.12.0)
     -> telemetry.sdk.language: Str(java)
     -> telemetry.sdk.name: Str(opentelemetry)
     -> telemetry.sdk.version: Str(1.46.0)
ScopeSpans #0
ScopeSpans SchemaURL: 
InstrumentationScope io.opentelemetry.netty-4.1 2.12.0-alpha
Span #0
    Trace ID       : 287f6d7cab81f813910f909ac1ff0570
    Parent ID      : 
    ID             : aac3e33359775de7
    Name           : GET /
    Kind           : Server
    Start time     : 2025-01-19 08:50:03.077627907 +0000 UTC
    End time       : 2025-01-19 08:50:03.126250246 +0000 UTC
    Status code    : Unset
    Status message : 
Attributes:
     -> network.peer.address: Str(127.0.0.1)
     -> server.address: Str(localhost)
     -> client.address: Str(127.0.0.1)
     -> url.path: Str(/)
     -> server.port: Int(8080)
     -> http.request.method: Str(GET)
     -> thread.id: Int(35)
     -> http.response.status_code: Int(200)
     -> http.route: Str(/)
     -> user_agent.original: Str(curl/7.88.1)
     -> network.peer.port: Int(34344)
     -> network.protocol.version: Str(1.1)
     -> url.scheme: Str(http)
     -> thread.name: Str(eventLoopGroupProxy-3-1)
        {"kind": "exporter", "data_type": "traces", "name": "debug"}
2025-01-19T17:50:39.123+0900    info    Traces  {"kind": "exporter", "data_type": "traces", "name": "debug", "resource spans": 1, "spans": 1}
2025-01-19T17:50:39.123+0900    info    ResourceSpans #0
Resource SchemaURL: https://opentelemetry.io/schemas/1.24.0
Resource attributes:
     -> host.arch: Str(amd64)
     -> host.name: Str(...)
     -> os.description: Str(Linux 6.1.0-30-amd64)
     -> os.type: Str(linux)
     -> process.command_args: Slice(["/usr/lib/jvm/java-17-openjdk-amd64/bin/java","-javaagent:./opentelemetry-javaagent.jar","-Dotel.service.name=kotlin-zerocode","-jar","build/libs/ktor-sample-all.jar"])
     -> process.executable.path: Str(/usr/lib/jvm/java-17-openjdk-amd64/bin/java)
     -> process.pid: Int(349907)
     -> process.runtime.description: Str(Debian OpenJDK 64-Bit Server VM 17.0.13+11-Debian-2deb12u1)
     -> process.runtime.name: Str(OpenJDK Runtime Environment)
     -> process.runtime.version: Str(17.0.13+11-Debian-2deb12u1)
     -> service.instance.id: Str(0da10240-547e-4a45-b28c-6133d3ffe1b5)
     -> service.name: Str(kotlin-zerocode)
     -> telemetry.distro.name: Str(opentelemetry-java-instrumentation)
     -> telemetry.distro.version: Str(2.12.0)
     -> telemetry.sdk.language: Str(java)
     -> telemetry.sdk.name: Str(opentelemetry)
     -> telemetry.sdk.version: Str(1.46.0)
ScopeSpans #0
ScopeSpans SchemaURL: 
InstrumentationScope io.opentelemetry.netty-4.1 2.12.0-alpha
Span #0
    Trace ID       : 5b2a39ea9e9b83b2695d6f537901a5b8
    Parent ID      : 
    ID             : 1009964c6a9cba86
    Name           : GET /error
    Kind           : Server
    Start time     : 2025-01-19 08:50:38.102411829 +0000 UTC
    End time       : 2025-01-19 08:50:38.112710928 +0000 UTC
    Status code    : Error
    Status message : 
Attributes:
     -> network.peer.address: Str(127.0.0.1)
     -> server.address: Str(localhost)
     -> client.address: Str(127.0.0.1)
     -> url.path: Str(/error)
     -> error.type: Str(500)
     -> server.port: Int(8080)
     -> http.request.method: Str(GET)
     -> thread.id: Int(40)
     -> http.response.status_code: Int(500)
     -> http.route: Str(/error)
     -> user_agent.original: Str(curl/7.88.1)
     -> network.peer.port: Int(60186)
     -> network.protocol.version: Str(1.1)
     -> url.scheme: Str(http)
     -> thread.name: Str(eventLoopGroupProxy-3-2)
        {"kind": "exporter", "data_type": "traces", "name": "debug"}
2025-01-19T17:50:44.124+0900    info    Traces  {"kind": "exporter", "data_type": "traces", "name": "debug", "resource spans": 1, "spans": 1}
2025-01-19T17:50:44.124+0900    info    ResourceSpans #0
Resource SchemaURL: https://opentelemetry.io/schemas/1.24.0
Resource attributes:
     -> host.arch: Str(amd64)
     -> host.name: Str(...)
     -> os.description: Str(Linux 6.1.0-30-amd64)
     -> os.type: Str(linux)
     -> process.command_args: Slice(["/usr/lib/jvm/java-17-openjdk-amd64/bin/java","-javaagent:./opentelemetry-javaagent.jar","-Dotel.service.name=kotlin-zerocode","-jar","build/libs/ktor-sample-all.jar"])
     -> process.executable.path: Str(/usr/lib/jvm/java-17-openjdk-amd64/bin/java)
     -> process.pid: Int(349907)
     -> process.runtime.description: Str(Debian OpenJDK 64-Bit Server VM 17.0.13+11-Debian-2deb12u1)
     -> process.runtime.name: Str(OpenJDK Runtime Environment)
     -> process.runtime.version: Str(17.0.13+11-Debian-2deb12u1)
     -> service.instance.id: Str(0da10240-547e-4a45-b28c-6133d3ffe1b5)
     -> service.name: Str(kotlin-zerocode)
     -> telemetry.distro.name: Str(opentelemetry-java-instrumentation)
     -> telemetry.distro.version: Str(2.12.0)
     -> telemetry.sdk.language: Str(java)
     -> telemetry.sdk.name: Str(opentelemetry)
     -> telemetry.sdk.version: Str(1.46.0)
ScopeSpans #0
ScopeSpans SchemaURL: 
InstrumentationScope io.opentelemetry.netty-4.1 2.12.0-alpha
Span #0
    Trace ID       : b3d80bd3e7474a883e1213c4688219df
    Parent ID      : 
    ID             : efb2d40bf565f9f9
    Name           : GET /500
    Kind           : Server
    Start time     : 2025-01-19 08:50:40.462618021 +0000 UTC
    End time       : 2025-01-19 08:50:40.464162334 +0000 UTC
    Status code    : Error
    Status message : 
Attributes:
     -> network.peer.address: Str(127.0.0.1)
     -> server.address: Str(localhost)
     -> client.address: Str(127.0.0.1)
     -> url.path: Str(/500)
     -> error.type: Str(500)
     -> server.port: Int(8080)
     -> http.request.method: Str(GET)
     -> thread.id: Int(42)
     -> http.response.status_code: Int(500)
     -> http.route: Str(/500)
     -> user_agent.original: Str(curl/7.88.1)
     -> network.peer.port: Int(56010)
     -> network.protocol.version: Str(1.1)
     -> url.scheme: Str(http)
     -> thread.name: Str(eventLoopGroupProxy-3-3)
        {"kind": "exporter", "data_type": "traces", "name": "debug"}

.NETとだいたい同じ雰囲気だね。

Collectorでメトリックも受け付けを有効にしてみたところ、HTTP request duration、送ったスパンの数、JVMリソース状況などがメトリックとして送出されてきていた(Goや.NETもメトリックを送ってきていたんだけどそういえばちゃんと見ていなかったな)。

様子を見たいときにはOpenTelemetry Collectorの設定を以下のように変えればよい。

service:
  pipelines:
    metrics:
      receivers: [otlp]
      exporters: [debug]
    logs:
      receivers: [otlp]
      exporters: [debug]
    traces:
      receivers: [otlp]
      exporters: [debug]

Springもそのうち試そうと思うが、まずはほかの言語をひととおり見てからにする。

いまどきJavaで書くには何らかのフレームワークを使っているだろうから、Java Agentによるzero-code計装は.NET同様に手軽だし便利そうだなと感じた。

残りはPHPPythonJavaScriptか〜

ふらっと見てたらこんなissueがあった。

github.com