Posted on

LT会の写し書きです.

  • botcast-worker でスクリプト内の引数やOpenAI APIの所要時間等を知りたい
  • 方法としてOpenTelemetryを利用した
  • Rustで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: SpanEvent を収集する処理
#[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); // 使用
}

OpenTelemetry概要

Observability

  • システムを調査するには、アプリケーションが適切にinstrumentedされている必要がある。
    • instrumentedされている: アプリケーションがSignals(traces, metrics, logs等)を発していること
    • → 問題発生時にアプリケーションに追加の変更を加えることなく調査が可能になる。
      • なぜなら、調査に必要な情報は全て収集されているから。

OpenTelemetry

  • OpenTelemetryとは、アプリケーションをinstrumentedにするための仕組み。
    • プログラミング言語に依存しない仕様、言語ごとのSDK
  • OpenTelemetryは4つのSignalsから成る
    • Traces ←今回の話題
    • Metrics
    • Logs
    • Baggage

構成

  • app: OpenTelemetry Collector(otel-collector:4317)にトレース情報を送る
  • opentelemetry-collector: :4317 でappからトレース情報を受け取り、jaeger (jaeger:5000) に送信する
  • jaeger: 可視化バックエンド

compose.yaml

services:
  jaeger:
    image: "jaegertracing/all-in-one:latest"
    ports:
      - "5000:5000" # gRPC server
      - "16686:16686" # Web UI
    command:
      # ref: https://zenn.dev/hkdord/articles/oss-reading-jaeger
      - "--collector.otlp.grpc.host-port=5000"
  otel-collector:
    image: otel/opentelemetry-collector:latest
    command: ["--config=/etc/otel-collector-config.yaml", ""]
    ports:
      - "4317:4317"

otel-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:
  otlp:
    endpoint: jaeger:5000
processors:
  batch:
service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlp, debug]

アプリ側の設定

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(())
}

結果

標準出力

スクリプト内で呼ばれた関数の引数も捕捉できている

まとめ

  • RustでOpenTelemetryを使う方法を紹介した
  • LangfuseにもLLMのトレースを行う機能 (Ingestion API) があり、OTelとの互換性等は未調査