テレメトリ

ADK-Rust は、adk-telemetry クレートを通じて本番環境向けの可観測性を提供します。このクレートは、tracing エコシステムと OpenTelemetry を使用して、構造化ロギングと分散トレーシングを統合します。

概要

テレメトリシステムでは、以下が可能です。

  • 構造化ロギング: コンテキスト情報を含む、詳細でクエリ可能なログ
  • 分散トレーシング: エージェント階層およびサービス境界をまたいだリクエストの追跡
  • OpenTelemetry 統合: トレースを可観測性バックエンド(Jaeger、Datadog、Honeycomb など)へエクスポート
  • コンテキストの自動伝播: セッション ID、ユーザー ID、呼び出し ID をすべての操作にわたって伝播
  • 事前設定済みスパン: 一般的な ADK 操作用のヘルパー関数

クイックスタート

基本的なコンソールロギング

開発環境やシンプルなデプロイでは、コンソールロギングを初期化します。

use adk_telemetry::init_telemetry;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize telemetry with your service name
    init_telemetry("my-agent-service")?;
    
    // Your agent code here
    
    Ok(())
}

これにより、適切なデフォルト設定で stdout への構造化ロギングが構成されます。

OpenTelemetry エクスポート

分散トレーシングを使用する本番環境へのデプロイでは、以下を使用します。

use adk_telemetry::init_with_otlp;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize with OTLP exporter
    init_with_otlp("my-agent-service", "http://localhost:4317")?;
    
    // Your agent code here
    
    // Flush traces before exit
    adk_telemetry::shutdown_telemetry();
    Ok(())
}

これにより、トレースとメトリクスが OpenTelemetry コレクターのエンドポイントへエクスポートされます。

合成可能なレイヤー(上級)

すでに tracing サブスクライバーを構成している場合は、グローバルサブスクライバーを初期化する代わりに build_otlp_layer を使用して、合成可能なレイヤーを取得します。

use adk_telemetry::build_otlp_layer;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};

let otlp_layer = build_otlp_layer("my-agent", "http://localhost:4317")?;

tracing_subscriber::registry()
    .with(otlp_layer)
    .with(tracing_subscriber::fmt::layer())
    .init();

ログレベル

RUST_LOG 環境変数を使用して、ロギングの詳細度を制御します。

レベル説明ユースケース
errorエラーのみ本番環境(最小限)
warn警告とエラー本番環境(デフォルト)
info情報メッセージ開発、ステージング
debug詳細なデバッグ情報ローカル開発
trace非常に詳細なトレース深いデバッグ

ログレベルの設定

# Set global log level
export RUST_LOG=info

# Set per-module log levels
export RUST_LOG=adk_agent=debug,adk_model=info

# Combine global and module-specific levels
export RUST_LOG=warn,adk_agent=debug

RUST_LOGが設定されていない場合、コンソールロギングのデフォルトはinfoです。init_with_adk_exporterで構成されるランタイムスパンの収集は、このコンソールフィルターとは独立しています。RUST_LOG=warnは、サーバーのセッションテレメトリインターフェース向けに、エージェント、モデル、ツール、ポータブルチームのスパンを引き続き保持します。

プロセス内セッションテレメトリ

サーバーは、組み込みランタイム UI を通じて、インフラストラクチャ不要のトレースを公開できます。

let exporter = adk_telemetry::init_with_adk_exporter("my-agent")?;
let config = ServerConfig::new(agent_loader, session_service)
    .with_span_exporter(exporter);

agent-details のレスポンスは既存のservices.telemetry boolean を保持し、services.telemetryStatusを追加します。

  • disabled — エクスポーターが構成されていません。
  • configured — エクスポーターは準備できていますが、サポート対象のスパンをまだ保持していません。
  • collecting — 少なくとも 1 つのサポート対象ランタイムスパンが保持されています。

init_with_adk_exporterを複数回呼び出すと、同じ登録済みエクスポーターが返されます。別のグローバルテレメトリモードが先に初期化されていた場合は、スパンを受信できない切断されたエクスポーターではなく、対処可能なエラーが返されます。

ロギングマクロ

標準のtracingマクロをロギングに使用します。

use adk_telemetry::{trace, debug, info, warn, error};

// Informational logging
info!("Agent started successfully");

// Structured logging with fields
info!(
    agent.name = "my_agent",
    session.id = "sess-123",
    "Processing user request"
);

// Debug logging
debug!(user_input = ?input, "Received input");

// Warning and error logging
warn!("Rate limit approaching");
error!(error = ?err, "Failed to call model");

構造化フィールド

フィルタリングと分析を改善するため、ログメッセージにコンテキストフィールドを追加します。

use adk_telemetry::info;

info!(
    agent.name = "customer_support",
    user.id = "user-456",
    session.id = "sess-789",
    invocation.id = "inv-abc",
    "Agent execution started"
);

これらのフィールドは、オブザーバビリティバックエンドでクエリ可能になります。

インストルメンテーション

自動インストルメンテーション

#[instrument]属性を使用すると、関数のスパンを自動的に作成できます。

use adk_telemetry::{instrument, info};

#[instrument]
async fn process_request(user_id: &str, message: &str) {
    info!("Processing request");
    // Function logic here
}

// Creates a span named "process_request" with user_id and message as fields

機密パラメーターの除外

トレースから機密データを除外します。

use adk_telemetry::instrument;

#[instrument(skip(api_key))]
async fn call_external_api(api_key: &str, query: &str) {
    // api_key won't appear in traces
}

カスタムスパン名

use adk_telemetry::instrument;

#[instrument(name = "external_api_call")]
async fn fetch_data(url: &str) {
    // Span will be named "external_api_call" instead of "fetch_data"
}

事前構成済みスパン

ADK-Telemetry は、一般的な操作向けのヘルパー関数を提供します。

エージェント実行スパン

use adk_telemetry::agent_run_span;

let span = agent_run_span("my_agent", "inv-123");
let _enter = span.enter();

// Agent execution code here
// All logs within this scope inherit the span context

モデル呼び出しスパン

use adk_telemetry::model_call_span;

let span = model_call_span("gemini-3.7-flash");
let _enter = span.enter();

// Model API call here

ツール実行スパン

use adk_telemetry::tool_execute_span;

let span = tool_execute_span("weather_tool");
let _enter = span.enter();

// Tool execution code here

コールバックスパン

use adk_telemetry::callback_span;

let span = callback_span("before_model");
let _enter = span.enter();

// Callback logic here

コンテキスト属性の追加

現在のスパンにユーザーとセッションのコンテキストを追加します。

use adk_telemetry::add_context_attributes;

add_context_attributes("user-456", "sess-789");

LLM トークン使用量の追跡

すべての LLM プロバイダーにわたるトークン消費量を、OpenTelemetry GenAI のセマンティック規約に従って追跡します。llm_generate_span は事前に宣言された gen_ai.usage.* フィールドを持つスパンを作成し、record_llm_usage はレスポンスの到着後にそれらのフィールドへ値を設定します。

use adk_telemetry::{llm_generate_span, record_llm_usage, LlmUsage};

let span = llm_generate_span("openai", "gpt-5.6-terra", true);
let _enter = span.enter();

// After receiving the LLM response with usage metadata:
record_llm_usage(&LlmUsage {
    input_tokens: 100,
    output_tokens: 50,
    total_tokens: 150,
    cache_read_tokens: Some(80),
    ..Default::default()
});

すべての ADK モデルプロバイダー(Gemini、OpenAI、Anthropic、Ollama、Bedrock、DeepSeek、Groq、Azure AI、およびすべての OpenAI 互換プロバイダー)は、各 generate_content 呼び出しでトークン使用量を自動的に記録します。手動で計装する必要はありません — 追跡機能はプロバイダーレイヤーに組み込まれています。

記録されるスパンフィールドは、OpenTelemetry GenAI の規約 に従います。

フィールド説明
gen_ai.usage.input_tokensプロンプト / 入力トークン数
gen_ai.usage.output_tokens完了 / 出力トークン数
gen_ai.usage.total_tokensトークン総数
gen_ai.usage.cache_read_tokensプロンプトキャッシュから読み取られたトークン
gen_ai.usage.cache_creation_tokensキャッシュの作成に使用されたトークン
gen_ai.usage.thinking_tokens思考連鎖推論トークン
gen_ai.usage.audio_input_tokens音声入力トークン数
gen_ai.usage.audio_output_tokens音声出力トークン数

オプションフィールドは、プロバイダーが報告した場合(None ではない場合)のみ記録されます。

手動での Span 作成

カスタム計装では、Span を手動で作成します。

use adk_telemetry::{info, Span};

let span = tracing::info_span!(
    "custom_operation",
    operation.type = "data_processing",
    operation.id = "op-123"
);

let _enter = span.enter();
info!("Performing custom operation");
// Operation code here

Span 属性

属性を動的に追加します。

use adk_telemetry::Span;

let span = Span::current();
span.record("result.count", 42);
span.record("result.status", "success");

OpenTelemetry の設定

OTLP エンドポイント

OTLP exporter はトレースをコレクターエンドポイントに送信します。

use adk_telemetry::init_with_otlp;

// Local Jaeger (default OTLP port)
init_with_otlp("my-service", "http://localhost:4317")?;

// Cloud provider endpoint
init_with_otlp("my-service", "https://otlp.example.com:4317")?;

ローカルコレクターの実行

開発時には、OTLP をサポートする Jaeger を実行します。

docker run -d --name jaeger \
  -p 4317:4317 \
  -p 16686:16686 \
  jaegertracing/all-in-one:latest

# View traces at http://localhost:16686

トレースの可視化

設定が完了すると、トレースはオブザーバビリティバックエンドに表示され、次の情報を確認できます。

  • エージェントの実行階層
  • モデル呼び出しのレイテンシ
  • ツール実行のタイミング
  • エラーの伝播
  • コンテキストのフロー(ユーザー ID、セッション ID など)

ADK との統合

テレメトリシステムが初期化されると、ADK-Rust コンポーネントは自動的にテレメトリを出力します。

use adk_rust::prelude::*;
use adk_telemetry::init_telemetry;
use std::sync::Arc;

#[tokio::main]
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
    // Initialize telemetry first
    init_telemetry("my-agent-app")?;
    
    let api_key = std::env::var("GOOGLE_API_KEY")?;
    let model = Arc::new(GeminiModel::new(&api_key, "gemini-3.7-flash")?);
    
    let agent = LlmAgentBuilder::new("support_agent")
        .model(model)
        .instruction("You are a helpful support agent.")
        .build()?;
    
    // Use Launcher for simple execution
    Launcher::new(Arc::new(agent)).run().await?;
    
    Ok(())
}

エージェント、モデル、ツールの操作は、構造化ログとトレースを自動的に出力します。

テレメトリのデモ例

テレメトリ機能の選択をローカルで検証します。

cargo check -p adk-telemetry --no-default-features
cargo check -p adk-telemetry --no-default-features --features otlp

実際の Gemini 呼び出しを使用した完全なテレメトリデモについては、adk-playground リポジトリを参照してください。

ツールでのカスタムテレメトリ

カスタムツールにテレメトリを追加します。

use adk_rust::prelude::*;
use adk_telemetry::{info, instrument, tool_execute_span};
use serde_json::{json, Value};

#[instrument(skip(ctx))]
async fn weather_tool_impl(
    ctx: Arc<dyn ToolContext>,
    args: Value,
) -> Result<Value> {
    let span = tool_execute_span("weather_tool");
    let _enter = span.enter();
    
    let location = args["location"].as_str().unwrap_or("unknown");
    info!(location = location, "Fetching weather data");
    
    // Tool logic here
    let result = json!({
        "temperature": 72,
        "condition": "sunny"
    });
    
    info!(location = location, "Weather data retrieved");
    Ok(result)
}

let weather_tool = FunctionTool::new(
    "get_weather",
    "Get current weather for a location",
    json!({
        "type": "object",
        "properties": {
            "location": {"type": "string"}
        },
        "required": ["location"]
    }),
    weather_tool_impl,
);

コールバックでのカスタムテレメトリ

コールバックにオブザーバビリティを追加します。

use adk_rust::prelude::*;
use adk_telemetry::{info, callback_span};
use std::sync::Arc;

let agent = LlmAgentBuilder::new("observed_agent")
    .model(model)
    .before_callback(Box::new(|ctx| {
        Box::pin(async move {
            let span = callback_span("before_agent");
            let _enter = span.enter();
            
            info!(
                agent.name = ctx.agent_name(),
                user.id = ctx.user_id(),
                session.id = ctx.session_id(),
                "Agent execution starting"
            );
            
            Ok(None)
        })
    }))
    .after_callback(Box::new(|ctx| {
        Box::pin(async move {
            let span = callback_span("after_agent");
            let _enter = span.enter();
            
            info!(
                agent.name = ctx.agent_name(),
                "Agent execution completed"
            );
            
            Ok(None)
        })
    }))
    .build()?;

パフォーマンスに関する考慮事項

サンプリング

高スループットのシステムでは、トレースのサンプリングを検討してください。

// Note: Sampling configuration depends on your OpenTelemetry setup
// Configure sampling in your OTLP collector or backend

非同期 Span

適切な Span コンテキストを確保するため、非同期関数では必ず #[instrument] を使用します。

use adk_telemetry::instrument;

// ✅ Correct - span context preserved across await points
#[instrument]
async fn async_operation() {
    tokio::time::sleep(Duration::from_secs(1)).await;
}

// ❌ Incorrect - manual span may lose context
async fn manual_span_operation() {
    let span = tracing::info_span!("operation");
    let _enter = span.enter();
    tokio::time::sleep(Duration::from_secs(1)).await;
    // Context may be lost after await
}

本番環境でのログレベル

オーバーヘッドを削減するため、本番環境では info または warn レベルを使用します。

export RUST_LOG=warn,my_app=info

トラブルシューティング

ログが表示されない

  1. RUST_LOG 環境変数が設定されていることを確認する
  2. ロギングの前に init_telemetry() が呼び出されていることを確認する
  3. サーバーを構築する前に、意図したグローバルテレメトリモードを初期化する

トレースがエクスポートされない

  1. OTLP エンドポイントに到達できることを確認する
  2. コレクターが実行中で、接続を受け付けていることを確認する
  3. アプリケーションの終了前に shutdown_telemetry() を呼び出して、保留中のスパンをフラッシュする
  4. ネットワークまたはファイアウォールの問題を確認する

組み込みランタイム UI については、services.telemetryStatus を確認してください。configured は、エクスポーターは接続されているものの、選択したセッションで保持対象のランタイムスパンがまだ生成されていないことを 意味します。collecting は、コレクターがデータを受信したことを確認します。

スパンにコンテキストがない

  1. 非同期関数で #[instrument] を使用する
  2. スパンが let _enter = span.enter() で開始されていることを確認する
  3. 操作の実行中は _enter ガードをスコープ内に保持する

ベストプラクティス

  1. 早期に初期化する: main() の開始時に init_telemetry() を呼び出す
  2. 構造化フィールドを使用する: 文字列補間ではなく、キーと値のペアでコンテキストを追加する
  3. 非同期関数を計装する: 非同期関数では常に #[instrument] を使用する
  4. 終了時にフラッシュする: アプリケーションの終了前に shutdown_telemetry() を呼び出す
  5. 適切なログレベル: 重要なイベントには info を、詳細には debug を使用する
  6. 機密データを避ける: #[instrument(skip(...))] で機密パラメーターを省略する
  7. 一貫した命名: 一貫したフィールド名を使用する(例: user.id、session.id)

前へ: ← イベント | 次へ: ランチャー →