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同様に手軽だし便利そうだなと感じた。

残りはPHP、Python、JavaScriptか〜

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

github.com

OpenTelemetryのzero-code計装を試している〜その2。.NET

前回のGoに引き続き、今度は.NETでのzero-code計装を試してみる。

kmuto.hatenablog.com

Debian GNU/Linux上に.NET 9環境をセットアップした。

.NETは完全初見なのだけど、Webサーバーは簡単に作れるらしいとのことで作成。

mkdir dotnet-web
cd dotnet-web
dotnet new web

これで各種ファイルが用意される。Program.csにすでにWebサーバーのコードが入っている。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

超簡単だな…! エラーを出すパターンをCoPilot受けながら適当に補完してみる。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");
app.MapGet("/error", (HttpContext context) => throw new Exception("Error!"));
app.MapGet("/500", () => {
    return Results.Problem("Error!", statusCode: 500);
});

app.Run();

dotnet buildでbin/Debug/net9.0/dotnet-webができた。

bin/Debug/net9.0/dotnet-webを実行し、curlなどでhttp://localhost:5000、http://localhost:5000/error、http://localhost:5000/500にアクセスして結果を確認。

$ curl http://localhost:5000
Hello World!
$ curl http://localhost:5000/error
(何も出ないけどサーバー側は例外が出ている)
$ curl http://localhost:5000/500
{"type":"https://tools.ietf.org/html/rfc9110#section-15.6.1","title":"An error occurred while processing your request.","status":500,"detail":"Error!"}

アプリケーションができたので、.NET zero-code instrumentationに従ってzero-code計装をやってみる。

例のごとくOpenTelemetry Collectorをローカルで適当な設定で動かす。

receivers:
  otlp:
    protocols:
      http:

exporters:
  debug:
    verbosity: detailed

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

.NETのzero-code計装ライブラリのセットアップ。

curl -sSfL https://github.com/open-telemetry/opentelemetry-dotnet-instrumentation/releases/latest/download/otel-dotnet-auto-install.sh -O
sh ./otel-dotnet-auto-install.sh
chmod +x $HOME/.otel-dotnet-auto/instrument.sh

現在のシェルに環境変数を取り込む。これで、今のシェル内で.NETアプリケーションを動かすとzero-code計装が有効な状態になっている。

. $HOME/.otel-dotnet-auto/instrument.sh

サービス名やバージョンを指定してアプリケーションを実行。

OTEL_SERVICE_NAME=dotnet-zerocode OTEL_RESOURCE_ATTRIBUTES=service.version=1.0.0 bin/Debug/net9.0/dotnet-web

これらはLinuxの例だけど、ドキュメントにはWindowsアプリケーション・Windowsサービス・ASP.NETでのやり方も書いてある。

ではcurlからアクセスして、Collectorにトレースが送られてくるのを見る。

2025-01-19T09:08:36.212+0900    info    Traces  {"kind": "exporter", "data_type": "traces", "name": "debug", "resource spans": 1, "spans": 2}
2025-01-19T09:08:36.212+0900    info    ResourceSpans #0
Resource SchemaURL: 
Resource attributes:
     -> os.type: Str(linux)
     -> os.description: Str(Debian GNU/Linux 12 (bookworm))
     -> os.build_id: Str(6.1.0-30-amd64)
     -> os.name: Str(Debian GNU/Linux)
     -> os.version: Str(12)
     -> host.name: Str(...)
     -> host.id: Str(86fdb17091114232ba104aea4bc5250d)
     -> process.owner: Str(kmuto)
     -> process.pid: Int(175395)
     -> process.runtime.description: Str(.NET 9.0.1)
     -> process.runtime.name: Str(.NET)
     -> process.runtime.version: Str(9.0.1)
     -> container.id: Str(8dcad6c724a5)
     -> telemetry.distro.name: Str(opentelemetry-dotnet-instrumentation)
     -> telemetry.distro.version: Str(1.9.0)
     -> telemetry.sdk.name: Str(opentelemetry)
     -> telemetry.sdk.language: Str(dotnet)
     -> telemetry.sdk.version: Str(1.9.0)
     -> service.name: Str(dotnet-zerocode)
     -> service.version: Str(1.0.0)
ScopeSpans #0
ScopeSpans SchemaURL: 
InstrumentationScope Microsoft.AspNetCore 
Span #0
    Trace ID       : d28fbe5ac778ff1c7bc29adf76ad2d63
    Parent ID      : 
    ID             : 622c1673e23ecb0e
    Name           : GET /
    Kind           : Server
    Start time     : 2025-01-19 00:08:32.1050609 +0000 UTC
    End time       : 2025-01-19 00:08:32.1881126 +0000 UTC
    Status code    : Unset
    Status message : 
Attributes:
     -> server.address: Str(localhost)
     -> server.port: Int(5000)
     -> http.request.method: Str(GET)
     -> url.scheme: Str(http)
     -> url.path: Str(/)
     -> network.protocol.version: Str(1.1)
     -> user_agent.original: Str(curl/7.88.1)
     -> http.route: Str(/)
     -> http.response.status_code: Int(200)
Span #1
    Trace ID       : c64908bdcf00dc51d84bb946a55a6e30
    Parent ID      : 
    ID             : d999c17988b97b14
    Name           : GET /error
    Kind           : Server
    Start time     : 2025-01-19 00:08:34.8026373 +0000 UTC
    End time       : 2025-01-19 00:08:34.8154717 +0000 UTC
    Status code    : Error
    Status message : 
Attributes:
     -> server.address: Str(localhost)
     -> server.port: Int(5000)
     -> http.request.method: Str(GET)
     -> url.scheme: Str(http)
     -> url.path: Str(/error)
     -> network.protocol.version: Str(1.1)
     -> user_agent.original: Str(curl/7.88.1)
     -> error.type: Str(System.Exception)
     -> http.route: Str(/error)
     -> http.response.status_code: Int(500)
        {"kind": "exporter", "data_type": "traces", "name": "debug"}
2025-01-19T09:08:41.233+0900    info    Traces  {"kind": "exporter", "data_type": "traces", "name": "debug", "resource spans": 1, "spans": 1}
2025-01-19T09:08:41.233+0900    info    ResourceSpans #0
Resource SchemaURL: 
Resource attributes:
     -> os.type: Str(linux)
     -> os.description: Str(Debian GNU/Linux 12 (bookworm))
     -> os.build_id: Str(6.1.0-30-amd64)
     -> os.name: Str(Debian GNU/Linux)
     -> os.version: Str(12)
     -> host.name: Str(...)
     -> host.id: Str(86fdb17091114232ba104aea4bc5250d)
     -> process.owner: Str(kmuto)
     -> process.pid: Int(175395)
     -> process.runtime.description: Str(.NET 9.0.1)
     -> process.runtime.name: Str(.NET)
     -> process.runtime.version: Str(9.0.1)
     -> container.id: Str(8dcad6c724a5)
     -> telemetry.distro.name: Str(opentelemetry-dotnet-instrumentation)
     -> telemetry.distro.version: Str(1.9.0)
     -> telemetry.sdk.name: Str(opentelemetry)
     -> telemetry.sdk.language: Str(dotnet)
     -> telemetry.sdk.version: Str(1.9.0)
     -> service.name: Str(dotnet-zerocode)
     -> service.version: Str(1.0.0)
ScopeSpans #0
ScopeSpans SchemaURL: 
InstrumentationScope Microsoft.AspNetCore 
Span #0
    Trace ID       : bd5555309bda6d0a5c055169ee9f5749
    Parent ID      : 
    ID             : 8f2b25897bd33a16
    Name           : GET /500
    Kind           : Server
    Start time     : 2025-01-19 00:08:37.6004667 +0000 UTC
    End time       : 2025-01-19 00:08:37.6297872 +0000 UTC
    Status code    : Error
    Status message : 
Attributes:
     -> server.address: Str(localhost)
     -> server.port: Int(5000)
     -> http.request.method: Str(GET)
     -> url.scheme: Str(http)
     -> url.path: Str(/500)
     -> network.protocol.version: Str(1.1)
     -> user_agent.original: Str(curl/7.88.1)
     -> http.route: Str(/500)
     -> http.response.status_code: Int(500)
        {"kind": "exporter", "data_type": "traces", "name": "debug"}

なるほど、リソース情報もまるっと入ってきている。

設定を見ていると、環境変数か設定ファイルを使ってかなりいろいろとカスタマイズできそうで、充実しているな〜。

OpenTelemetryのzero-code計装を試している〜まずはGo

オブザーバビリティでOpenTelemetryの計装をいざ始めよう!というときに、「そう言われても、今あるコードベースに何か追加するのは嫌なんじゃが……」ということはいかにもありそうな話。

そこでOpenTelemetryが提供している手法としてzero-code instrumentation、いわゆる自動計装というものがある。

この手法では、エージェントあるいはエージェントライクなものとして、バイトコード操作、モンキーパッチ、eBPFなどの手段でアプリケーションに計装が挿入される。現時点で公式ページに書かれているのは.NET、Go、Java、JavaScript、PHP、Pythonとなっている。

どの程度これが実用的、あるいはユーザーにとって嬉しさを感じられそうかを知っておこうと、1日1言語…とやるつもりだったけどあまり時間的余裕がなく、ここ数日ではGoと.NETを試していた。今日はGoの結果をまとめておく。

Goのはymtdzzzさんの記事にだいたい全部書かれているので、ちゃんとしたことはそっちを見たほうが早い。本記事もその追試にすぎないと言える。

ymtdzzz.dev

Go言語のzero-code計装(opentelemetry-go-instrumentation)

Go言語の場合は実行バイナリがそれだけで完結しているので、ランタイムに割り込むようなことができない。

公式で案内されているopentelemetry-go-instrumentationは、LinuxカーネルのeBPFを使ってイベントを取得する手段をとっている。そのため、Linuxネイティブでない場合は、Dockerイメージあるいは適当なLinux VMを立てて代用することになる。

ネイティブDebian GNU/Linux環境なので、普通にGitHubから展開してotel-go-instrumentationをビルドした。

git clone https://github.com/open-telemetry/opentelemetry-go-instrumentation.git
cd opentelemetry-go-instrumentation
make build

次に、TCP/5000ポートで動き、指定パスによってエラーを起こす適当なアプリケーションhello-serverを作ってビルドしておく。

package main

import "net/http"

func main() {
        http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
                w.Write([]byte("Hello, World!\n"))
        })
        http.HandleFunc("/error", func(w http.ResponseWriter, r *http.Request) {
                http.Error(w, "Internal Server Error", http.StatusInternalServerError)
        })
        http.ListenAndServe(":5000", nil)
}

OpenTelemetry Collectorを起動しておく。トレースがきてるのがわかればいいので設定は適当。

receivers:
  otlp:
    protocols:
      http:

exporters:
  debug:
    verbosity: detailed

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

では試してみよう。

  • otel-go-instrumentationの実行バイナリはカレントフォルダにあるとする
  • アプリケーションの実行バイナリは/home/kmuto/hello-server/hello-serverパスにあるとする

対象バイナリが実行されるのを待ち構える。

sudo OTEL_GO_AUTO_TARGET_EXE=/home/kmuto/hello-server/hello-server OTEL_SERVICE_NAME=go-zerocode OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 ./otel-go-instrumentation

/home/kmuto/hello-server/hello-serverを実行し、curlなどでhttp://localhost:5000とhttp://localhost:5000/errorにアクセスすると、OpenTelemetry Collectorのほうにトレースとスパンが出力される。

2025-01-18T23:04:27.708+0900    info    Traces  {"kind": "exporter", "data_type": "traces", "name": "debug", "resource spans": 1, "spans": 1}
2025-01-18T23:04:27.708+0900    info    ResourceSpans #0
Resource SchemaURL: https://opentelemetry.io/schemas/1.26.0
Resource attributes:
     -> process.runtime.description: Str(go version 1.23.1 linux/amd64)
     -> process.runtime.name: Str(go)
     -> process.runtime.version: Str(1.23.1)
     -> service.name: Str(go-zerocode)
     -> telemetry.distro.name: Str(opentelemetry-go-instrumentation)
     -> telemetry.distro.version: Str(v0.19.0-alpha)
     -> telemetry.sdk.language: Str(go)
ScopeSpans #0
ScopeSpans SchemaURL: https://opentelemetry.io/schemas/1.26.0
InstrumentationScope go.opentelemetry.io/auto/net/http v0.19.0-alpha
Span #0
    Trace ID       : f52b9e7c56d58516909ab0dbf4f6d866
    Parent ID      : 
    ID             : 4f212cd3e2c29532
    Name           : GET /
    Kind           : Server
    Start time     : 2025-01-18 14:04:23.501669372 +0000 UTC
    End time       : 2025-01-18 14:04:23.50167801 +0000 UTC
    Status code    : Unset
    Status message : 
Attributes:
     -> http.request.method: Str(GET)
     -> url.path: Str(/)
     -> http.response.status_code: Int(200)
     -> network.peer.address: Str(127.0.0.1)
     -> network.peer.port: Int(40270)
     -> server.address: Str(localhost)
     -> server.port: Int(5000)
     -> network.protocol.version: Str(1.1)
     -> http.route: Str(/)
        {"kind": "exporter", "data_type": "traces", "name": "debug"}
2025-01-18T23:04:32.712+0900    info    Traces  {"kind": "exporter", "data_type": "traces", "name": "debug", "resource spans": 1, "spans": 1}
2025-01-18T23:04:32.712+0900    info    ResourceSpans #0
Resource SchemaURL: https://opentelemetry.io/schemas/1.26.0
Resource attributes:
     -> process.runtime.description: Str(go version 1.23.1 linux/amd64)
     -> process.runtime.name: Str(go)
     -> process.runtime.version: Str(1.23.1)
     -> service.name: Str(go-zerocode)
     -> telemetry.distro.name: Str(opentelemetry-go-instrumentation)
     -> telemetry.distro.version: Str(v0.19.0-alpha)
     -> telemetry.sdk.language: Str(go)
ScopeSpans #0
ScopeSpans SchemaURL: https://opentelemetry.io/schemas/1.26.0
InstrumentationScope go.opentelemetry.io/auto/net/http v0.19.0-alpha
Span #0
    Trace ID       : 6d7626cdf485322abd7d2785d00b31b9
    Parent ID      : 
    ID             : 2c801415b9953b48
    Name           : GET /error
    Kind           : Server
    Start time     : 2025-01-18 14:04:30.454387577 +0000 UTC
    End time       : 2025-01-18 14:04:30.454400865 +0000 UTC
    Status code    : Error
    Status message : 
Attributes:
     -> http.request.method: Str(GET)
     -> url.path: Str(/error)
     -> http.response.status_code: Int(500)
     -> network.peer.address: Str(127.0.0.1)
     -> network.peer.port: Int(41402)
     -> server.address: Str(localhost)
     -> server.port: Int(5000)
     -> network.protocol.version: Str(1.1)
     -> http.route: Str(/error)
        {"kind": "exporter", "data_type": "traces", "name": "debug"}

1トレース1スパンでこれ自体はさして面白いものではないが、エラーのときはちゃんとエラーのstatus codeになっている。

Vaxilaではうまく受け取れなかった。Jaegerでもなんか変な気がする。

Go言語のzero-code計装(opentelemetry-go-auto-instrumentation)

もう1つのzero-codeとしては、alibaba/opentelemetry-go-auto-instrumentationがある。これはビルド時に計装を仕込むもの。

Alibabaということに少々ドキドキはするのだが、さすがに目に見えるもので仕込んではこないだろう…。とは言うものの、全部Dockerで閉じた環境にした。

Dockerfileを用意。

FROM golang:1.23
WORKDIR /usr/src/app

RUN apt update \
  && apt install -y sudo curl \
  && curl -fsSL https://cdn.jsdelivr.net/gh/alibaba/opentelemetry-go-auto-instrumentation@main/install.sh | bash

COPY go.mod main.go ./
RUN go mod download & go mod verify
RUN otel go build -o hello-server-alibaba main.go

CMD ["./hello-server-alibaba"]

docker-compose.yml。

services:
  alibaba:
    build:
      context: .
      dockerfile: ./Dockerfile
    ports:
      - "5000:5000"
    environment:
      OTEL_EXPORTER_OTLP_ENDPOINT: "http://otelcol:4318"
      OTEL_EXPORTER_OTLP_INSECURE: true
      OTEL_SERVICE_NAME: "go-zerocode-alibaba"

  otelcol:
    image: otel/opentelemetry-collector-contrib:latest
    volumes:
      - ./otel-col-alibaba.yaml:/etc/otelcol-contrib/config.yaml
    ports:
      - "4318:4318"

otel-col-alibaba.yamlはバインドアドレスをグローバルにしただけ。

receivers:
  otlp:
    protocols:
      http:
        endpoint: "0.0.0.0:4318"

exporters:
  debug:
    verbosity: detailed

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

docker compose upで起動し、http://localhost:5000やhttp//localhost:5000/errorにcurlでアクセスしてトレースを送ってみる。

otelcol-1  | 2025-01-18T15:02:31.587Z   info    TracesExporter  {"kind": "exporter", "data_type": "traces", "name": "debug", "resource spans": 1, "spans": 2}
otelcol-1  | 2025-01-18T15:02:31.587Z   info    ResourceSpans #0
otelcol-1  | Resource SchemaURL: https://opentelemetry.io/schemas/1.26.0
otelcol-1  | Resource attributes:
otelcol-1  |      -> service.name: Str(go-zerocode-alibaba)
otelcol-1  |      -> telemetry.sdk.language: Str(go)
otelcol-1  |      -> telemetry.sdk.name: Str(opentelemetry)
otelcol-1  |      -> telemetry.sdk.version: Str(1.33.0)
otelcol-1  | ScopeSpans #0
otelcol-1  | ScopeSpans SchemaURL: 
otelcol-1  | InstrumentationScope pkg/rules/http/server_setup.go v0.7.0
otelcol-1  | Span #0
otelcol-1  |     Trace ID       : 3b2ffa25076e74ef6b111af48c2494df
otelcol-1  |     Parent ID      : 
otelcol-1  |     ID             : c3c9a8fbc916debf
otelcol-1  |     Name           : GET /
otelcol-1  |     Kind           : Server
otelcol-1  |     Start time     : 2025-01-18 15:02:27.286134194 +0000 UTC
otelcol-1  |     End time       : 2025-01-18 15:02:27.286153625 +0000 UTC
otelcol-1  |     Status code    : Unset
otelcol-1  |     Status message : 
otelcol-1  | Attributes:
otelcol-1  |      -> http.request.method: Str(GET)
otelcol-1  |      -> url.scheme: Str(http)
otelcol-1  |      -> url.path: Str(/)
otelcol-1  |      -> url.query: Str()
otelcol-1  |      -> user_agent.original: Str(curl/7.88.1)
otelcol-1  |      -> http.response.status_code: Int(200)
otelcol-1  |      -> network.protocol.name: Str(http)
otelcol-1  |      -> network.protocol.version: Str(1.1)
otelcol-1  |      -> network.transport: Str(tcp)
otelcol-1  |      -> network.type: Str(ipv4)
otelcol-1  |      -> network.local.address: Str()
otelcol-1  |      -> network.peer.address: Str(localhost:5000)
otelcol-1  |      -> http.route: Str(/)
otelcol-1  | Span #1
otelcol-1  |     Trace ID       : 5c071af6c91bb08a9a34fcbe942b2b70
otelcol-1  |     Parent ID      : 
otelcol-1  |     ID             : dd5524e5dbf689c9
otelcol-1  |     Name           : GET /error
otelcol-1  |     Kind           : Server
otelcol-1  |     Start time     : 2025-01-18 15:02:28.919892825 +0000 UTC
otelcol-1  |     End time       : 2025-01-18 15:02:28.919909903 +0000 UTC
otelcol-1  |     Status code    : Error
otelcol-1  |     Status message : INVALID_HTTP_STATUS_CODE
otelcol-1  | Attributes:
otelcol-1  |      -> http.request.method: Str(GET)
otelcol-1  |      -> url.scheme: Str(http)
otelcol-1  |      -> url.path: Str(/error)
otelcol-1  |      -> url.query: Str()
otelcol-1  |      -> user_agent.original: Str(curl/7.88.1)
otelcol-1  |      -> http.response.status_code: Int(500)
otelcol-1  |      -> network.protocol.name: Str(http)
otelcol-1  |      -> network.protocol.version: Str(1.1)
otelcol-1  |      -> network.transport: Str(tcp)
otelcol-1  |      -> network.type: Str(ipv4)
otelcol-1  |      -> network.local.address: Str()
otelcol-1  |      -> network.peer.address: Str(localhost:5000)
otelcol-1  |      -> http.route: Str(/error)
otelcol-1  |    {"kind": "exporter", "data_type": "traces", "name": "debug"}
otelcol-1  | 2025-01-18T15:02:36.588Z   info    TracesExporter  {"kind": "exporter", "data_type": "traces", "name": "debug", "resource spans": 1, "spans": 1}
otelcol-1  | 2025-01-18T15:02:36.588Z   info    ResourceSpans #0
otelcol-1  | Resource SchemaURL: https://opentelemetry.io/schemas/1.26.0
otelcol-1  | Resource attributes:
otelcol-1  |      -> service.name: Str(go-zerocode-alibaba)
otelcol-1  |      -> telemetry.sdk.language: Str(go)
otelcol-1  |      -> telemetry.sdk.name: Str(opentelemetry)
otelcol-1  |      -> telemetry.sdk.version: Str(1.33.0)
otelcol-1  | ScopeSpans #0
otelcol-1  | ScopeSpans SchemaURL: 
otelcol-1  | InstrumentationScope pkg/rules/http/client_setup.go v0.7.0
otelcol-1  | Span #0
otelcol-1  |     Trace ID       : f3c89464869497c7d71975e077f55b6f
otelcol-1  |     Parent ID      : 
otelcol-1  |     ID             : 76adf95e3720a020
otelcol-1  |     Name           : POST
otelcol-1  |     Kind           : Client
otelcol-1  |     Start time     : 2025-01-18 15:02:31.587172333 +0000 UTC
otelcol-1  |     End time       : 2025-01-18 15:02:31.5880139 +0000 UTC
otelcol-1  |     Status code    : Unset
otelcol-1  |     Status message : 
otelcol-1  | Attributes:
otelcol-1  |      -> http.request.method: Str(POST)
otelcol-1  |      -> url.full: Str(http://otelcol:4318/v1/traces)
otelcol-1  |      -> server.address: Str(otelcol:4318)
otelcol-1  |      -> server.port: Int(4318)
otelcol-1  |      -> http.response.status_code: Int(200)
otelcol-1  |      -> network.protocol.name: Str(http)
otelcol-1  |      -> network.protocol.version: Str(1.1)
otelcol-1  |      -> network.transport: Str(tcp)
otelcol-1  |      -> network.type: Str(ipv4)
otelcol-1  |      -> network.local.address: Str()
otelcol-1  |      -> network.peer.address: Str(otelcol:4318)
otelcol-1  |      -> network.peer.port: Int(4318)
otelcol-1  |    {"kind": "exporter", "data_type": "traces", "name": "debug"}

1トレース、1スパンであることは同じだが属性は少し細かい感じ。Status messageが丁寧になっている。Collectorへの送信自体もトレースにのっけてくるのかな。

こちらはVaxilaでもうまく取ることができている。

バイナリはそれなりに大きくなった。普通のビルドだと7,521,614バイト、otelビルドだと22,813,463バイト。

とりあえずGo版のお試しはここまで。