RustでOpenTelemetryを使う
botcast-worker でスクリプト内の引数やOpenAI APIの所要時間等を知りたかったので OpenTelemetry を利用した。
log crate
Rustでは log crateを使ってロギングを行う。I/Fと実装は分かれている(I/F: log crate, 実装: env_logger crate, fern crate 等)。
fn say_hello(id: u32) {
log::info!("Hello, {}", id); // Event
}
fn main() {
env_logger::init(); // 初期化
say_hello(123); // 使用
}tracing crate
コンテキスト(HTTPリクエスト情報等)を含むロギングは tracing crateを使う。
Span: 処理を含む期間でコンテキスト情報を持てるEvent:Spanに記録するトレースしたい事象Subscriber:SpanとEventを収集する処理
#[tracing::instrument(skip(key))] // Span: say_hello
fn say_hello(key: &str, id: u32) {
tracing::info!("Hello, {} key={}", id, key); // Event
}
fn main() {
tracing_subscriber::fmt().init(); // 初期化
say_hello("secret", 123); // 使用
}参考: tracing crateを利用したRustのlogging方法について
OpenTelemetry概要
Observability
- システムを調査するには、アプリケーションが適切に instrumented されている必要がある
- instrumented されている = アプリケーションがSignals(traces, metrics, logs等)を発していること
- → 問題発生時にアプリケーションに追加の変更を加えることなく調査が可能になる。なぜなら、調査に必要な情報は全て収集されているから
OpenTelemetry
- アプリケーションを instrumented にするための仕組み
- プログラミング言語に依存しない仕様、言語ごとのSDK
- 4つのSignalsから成る: Traces(ここでの話題) / Metrics / Logs / Baggage
構成
- app: OpenTelemetry Collector(
otel-collector:4317)にトレース情報を送る - opentelemetry-collector:
:4317でappからトレース情報を受け取り、jaeger(jaeger:5000) に送信する - jaeger: 可視化バックエンド
アプリ側の設定
- まずはJSONで出す
tracing = "0.1.37"
tracing-subscriber = { version = "0.3.16", features = ["json"] }
tracing_subscriber::fmt()
.json()
.with_max_level(tracing::Level::INFO)
.init();- OTLP exporter を足す
fn init_tracing(otlp_collector_endpoint: String) -> anyhow::Result<()> {
let subscriber = tracing_subscriber::registry();
let exporter = opentelemetry_otlp::SpanExporter::builder()
.with_tonic()
.with_endpoint(otlp_collector_endpoint)
.build()?;
let tracer_provider = TracerProvider::builder()
.with_batch_exporter(exporter, Tokio)
.with_resource(Resource::new(vec![KeyValue::new(
"service.name",
crate_name.to_string(),
)]))
.build();
let otel_layer = OpenTelemetryLayer::new(tracer_provider.tracer("worker"));
let subscriber = subscriber
.with(otel_layer)
.with(EnvFilter::from_str(&format!("info,{}=trace", crate_name))?);
let fmt_layer = tracing_subscriber::fmt::layer().pretty();
let subscriber = subscriber.with(fmt_layer);
tracing::subscriber::set_global_default(subscriber)?;
Ok(())
}結果


未調査:
- LangfuseにもLLMのトレースを行う機能(Ingestion API)があり、OTelとの互換性等
- メトリクスとしてTraceを加工したり、アプリ側でTrace情報を取得する方法
セットアップのメモ
-
OTel collector
go install github.com/equinix-labs/otel-cli@latest
otel-cli exec --name "test" --protocol grpc --endpoint http://localhost:4317 --verbose --tp-print echo 1
1
# trace id: 8aaf4d2ca62204f400ae88705fa55afb
# span id: 8430124b37cd2a50
TRACEPARENT=00-8aaf4d2ca62204f400ae88705fa55afb-8430124b37cd2a50-01otel-collector-1 | 2024-11-30T09:34:29.355Z error internal/queue_sender.go:92 Exporting failed. Dropping data. {"kind": "exporter", "data_type": "traces", "name": "otlp", "error": "not retryable error: Permanent error: rpc error: code = Unimplemented desc = unknown service opentelemetry.proto.collector.trace.v1.TraceService", "dropped_items": 1}
otel-collector-1 | go.opentelemetry.io/collector/exporter/exporterhelper/internal.NewQueueSender.func1
otel-collector-1 | go.opentelemetry.io/collector/exporter@v0.114.0/exporterhelper/internal/queue_sender.go:92
otel-collector-1 | go.opentelemetry.io/collector/exporter/internal/queue.(*Consumers[...]).Start.func1
otel-collector-1 | go.opentelemetry.io/collector/exporter@v0.114.0/internal/queue/consumers.go:43otel-collector-config.yaml
receivers:
otlp:
protocols:
grpc:
# ref: https://github.com/open-telemetry/opentelemetry-rust/issues/861#issuecomment-2408304710
endpoint: 0.0.0.0:4317
exporters:
debug:
otlp:
endpoint: jaeger:5000
tls:
insecure: true
processors:
batch:
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [otlp, debug]
compose.yaml
services:
jaeger:
image: "jaegertracing/all-in-one:latest"
ports:
- "5000:5000" # gRPC server
- "16686:16686" # Web UI
environment:
- COLLECTOR_OTLP_ENABLED=true
command:
# ref: https://zenn.dev/hkdord/articles/oss-reading-jaeger
- "--collector.otlp.grpc.host-port=5000"
networks:
- langfuse-network
otel-collector:
image: otel/opentelemetry-collector:latest
restart: always
command: ["--config=/etc/otel-collector-config.yaml", ""]
volumes:
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
ports:
- "4317:4317"
networks:
- langfuse-network
depends_on:
- jaeger
networks:
langfuse-network:
driver: bridge