Telemetrie

ADK-Rust bietet produktionsreife Beobachtbarkeit durch das Crate adk-telemetry, das strukturiertes Logging und verteiltes Tracing mithilfe des Ökosystems tracing und OpenTelemetry integriert.

Übersicht

Das Telemetriesystem ermöglicht:

  • Strukturiertes Logging: Umfangreiche, abfragbare Logs mit Kontextinformationen
  • Verteiltes Tracing: Nachverfolgung von Requests über Agentenhierarchien und Service-Grenzen hinweg
  • Integration von OpenTelemetry: Export von Traces an Observability-Backends (Jaeger, Datadog, Honeycomb usw.)
  • Automatische Kontextweitergabe: Sitzungs-, Benutzer- und Aufruf-IDs werden durch alle Vorgänge weitergegeben
  • Vorkonfigurierte Spans: Hilfsfunktionen für gängige ADK-Vorgänge

Schnellstart

Einfaches Konsolen-Logging

Für die Entwicklung und einfache Bereitstellungen initialisieren Sie das Konsolen-Logging:

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

Damit wird strukturiertes Logging mit sinnvollen Standardeinstellungen nach stdout konfiguriert.

OpenTelemetry-Export

Für Produktionsbereitstellungen mit verteiltem Tracing:

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

Damit werden Traces und Metriken an einen OpenTelemetry-Collector-Endpunkt exportiert.

Zusammensetzbare Layer (Fortgeschritten)

Wenn Sie bereits einen tracing-Subscriber konfiguriert haben, verwenden Sie build_otlp_layer, um einen zusammensetzbaren Layer zu erhalten, anstatt einen globalen Subscriber zu initialisieren:

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

Log-Level

Steuern Sie die Ausführlichkeit des Loggings mithilfe der Umgebungsvariable RUST_LOG:

StufeBeschreibungAnwendungsfall
errorNur FehlerProduktion (minimal)
warnWarnungen und FehlerProduktion (Standard)
infoInformative MeldungenEntwicklung, Staging
debugDetaillierte Debugging-InformationenLokale Entwicklung
traceSehr ausführliches TracingTiefgreifendes Debugging

Protokollierungsebenen festlegen

# 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

Die Konsolenprotokollierung verwendet standardmäßig info, wenn RUST_LOG nicht festgelegt ist. Die durch init_with_adk_exporter konfigurierte Sammlung von Laufzeit-Spans ist unabhängig von diesem Konsolenfilter: RUST_LOG=warn behält weiterhin Agent-, Modell-, Tool- und portable Team-Spans für die Sitzungstelemetrie-Schnittstelle des Servers bei.

Sitzungstelemetrie im Prozess

Server können Traces ohne Infrastruktur über die eingebettete Laufzeit-Benutzeroberfläche bereitstellen:

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

Die Agent-Details-Antwort bewahrt den bestehenden booleschen Wert services.telemetry und fügt services.telemetryStatus hinzu:

  • disabled — kein Exporter ist konfiguriert;
  • configured — der Exporter ist bereit, hat aber noch keinen unterstützten Span gespeichert;
  • collecting — mindestens ein unterstützter Laufzeit-Span wurde gespeichert.

Ein mehrmaliger Aufruf von init_with_adk_exporter gibt denselben registrierten Exporter zurück. Wenn zuvor ein anderer globaler Telemetriemodus initialisiert wurde, wird ein aussagekräftiger Fehler zurückgegeben, anstatt eines getrennten Exporters, der niemals Spans empfangen kann.

Protokollierungs-Makros

Verwenden Sie die standardmäßigen tracing-Makros für die Protokollierung:

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");

Strukturierte Felder

Fügen Sie Protokollnachrichten Kontextfelder hinzu, um Filterung und Analyse zu verbessern:

use adk_telemetry::info;

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

Diese Felder können in Ihrem Observability-Backend abgefragt werden.

Instrumentierung

Automatische Instrumentierung

Verwenden Sie das Attribut #[instrument], um automatisch Spans für Funktionen zu erstellen:

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

Vertrauliche Parameter überspringen

Schließen Sie vertrauliche Daten aus Traces aus:

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
}

Benutzerdefinierte Span-Namen

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"
}

Vorkonfigurierte Spans

ADK-Telemetry stellt Hilfsfunktionen für häufige Vorgänge bereit:

Span für die Agent-Ausführung

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

Span für den Modellaufruf

use adk_telemetry::model_call_span;

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

// Model API call here

Span für die Tool-Ausführung

use adk_telemetry::tool_execute_span;

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

// Tool execution code here

Callback-Span

use adk_telemetry::callback_span;

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

// Callback logic here

Kontextattribute hinzufügen

Fügen Sie dem aktuellen Span Benutzer- und Sitzungskontext hinzu:

use adk_telemetry::add_context_attributes;

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

LLM Tokenverbrauchsverfolgung

Verfolgen Sie den Tokenverbrauch über alle LLM-Provider hinweg anhand der semantischen Konventionen von OpenTelemetry GenAI. llm_generate_span erstellt einen Span mit vorab deklarierten gen_ai.usage.*-Feldern, und record_llm_usage füllt diese nach Eingang der Antwort:

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

Alle ADK-Modellprovider (Gemini, OpenAI, Anthropic, Ollama, Bedrock, DeepSeek, Groq, Azure AI und alle mit OpenAI kompatiblen Provider) erfassen automatisch den Tokenverbrauch bei jedem generate_content-Aufruf. Eine manuelle Instrumentierung ist nicht erforderlich — die Verfolgung ist in die Provider-Schicht integriert.

Die aufgezeichneten Span-Felder folgen den OpenTelemetry-GenAI-Konventionen:

FeldBeschreibung
gen_ai.usage.input_tokensPrompt-/Eingabe-Tokenanzahl
gen_ai.usage.output_tokensCompletion-/Ausgabe-Tokenanzahl
gen_ai.usage.total_tokensGesamtanzahl der Tokens
gen_ai.usage.cache_read_tokensAus dem Prompt-Cache gelesene Tokens
gen_ai.usage.cache_creation_tokensZum Erstellen des Caches verwendete Tokens
gen_ai.usage.thinking_tokensTokens für die Chain-of-Thought-Argumentation
gen_ai.usage.audio_input_tokensAnzahl der Audio-Eingabetoken
gen_ai.usage.audio_output_tokensAnzahl der Audio-Ausgabetoken

Optionale Felder werden nur aufgezeichnet, wenn der Provider sie meldet (nicht-None).

Manuelle Span-Erstellung

Für benutzerdefinierte Instrumentierung können Spans manuell erstellt werden:

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-Attribute

Attribute dynamisch hinzufügen:

use adk_telemetry::Span;

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

OpenTelemetry-Konfiguration

OTLP-Endpunkt

Der OTLP-Exporter sendet Traces an einen Collector-Endpunkt:

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")?;

Ausführen eines lokalen Collectors

Für die Entwicklung Jaeger mit OTLP-Unterstützung ausführen:

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

# View traces at http://localhost:16686

Trace-Visualisierung

Nach der Konfiguration erscheinen Traces in Ihrem Observability-Backend und zeigen Folgendes:

  • Hierarchie der Agent-Ausführung
  • Latenzen von Modellaufrufen
  • Zeitabläufe der Tool-Ausführung
  • Weitergabe von Fehlern
  • Kontextfluss (Benutzer-ID, Sitzungs-ID usw.)

Integration mit ADK

ADK-Rust-Komponenten geben automatisch Telemetrie aus, sobald das Telemetriesystem initialisiert wurde:

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

Die Operationen des Agents, des Modells und des Tools geben automatisch strukturierte Logs und Traces aus.

Beispiel für eine Telemetrie-Demo

Die Auswahl der Telemetriefunktionen lokal validieren:

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

Im adk-playground-Repository finden Sie eine vollständige Telemetrie-Demo mit echten Gemini-Aufrufen.

Benutzerdefinierte Telemetrie in Tools

Telemetrie zu benutzerdefinierten Tools hinzufügen:

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,
);

Benutzerdefinierte Telemetrie in Callbacks

Observability zu Callbacks hinzufügen:

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()?;

Überlegungen zur Performance

Sampling

Für Systeme mit hohem Durchsatz sollte Trace-Sampling in Betracht gezogen werden:

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

Async-Spans

Immer #[instrument] für asynchrone Funktionen verwenden, um den korrekten Span-Kontext sicherzustellen:

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
}

Log-Level in der Produktion

In der Produktion info oder die Ebene warn verwenden, um den Overhead zu reduzieren:

export RUST_LOG=warn,my_app=info

Fehlerbehebung

Keine Logs werden angezeigt

  1. Prüfen, ob die Umgebungsvariable RUST_LOG gesetzt ist
  2. Sicherstellen, dass init_telemetry() vor jeglicher Protokollierung aufgerufen wird
  3. Den vorgesehenen globalen Telemetriemodus initialisieren, bevor der Server erstellt wird

Traces nicht exportiert

  1. Überprüfen Sie, ob der OTLP-Endpunkt erreichbar ist
  2. Überprüfen Sie, ob der Collector ausgeführt wird und Verbindungen akzeptiert
  3. Rufen Sie shutdown_telemetry() vor dem Beenden der Anwendung auf, um ausstehende Spans zu leeren
  4. Überprüfen Sie Netzwerk-/Firewall-Probleme

Überprüfen Sie für die eingebettete Runtime-Benutzeroberfläche services.telemetryStatus. configured bedeutet, dass der Exporter angehängt ist, die ausgewählte Sitzung jedoch noch keinen beibehaltenen Runtime-Span erzeugt hat; collecting bestätigt, dass der Collector Daten empfangen hat.

Fehlender Kontext in Spans

  1. Verwenden Sie #[instrument] für asynchrone Funktionen
  2. Stellen Sie sicher, dass Spans mit let _enter = span.enter() betreten werden
  3. Lassen Sie den _enter-Guard für die Dauer der Operation im Gültigkeitsbereich

Best Practices

  1. Frühzeitig initialisieren: Rufen Sie init_telemetry() am Anfang von main() auf
  2. Strukturierte Felder verwenden: Fügen Sie Kontext mit Schlüssel-Wert-Paaren hinzu, nicht durch String-Interpolation
  3. Asynchrone Funktionen instrumentieren: Verwenden Sie für asynchrone Funktionen immer #[instrument]
  4. Beim Beenden leeren: Rufen Sie shutdown_telemetry() vor der Beendigung der Anwendung auf
  5. Geeignete Protokollebenen: Verwenden Sie info für wichtige Ereignisse und debug für Details
  6. Sensible Daten vermeiden: Überspringen Sie sensible Parameter mit #[instrument(skip(...))]
  7. Konsistente Benennung: Verwenden Sie konsistente Feldnamen (z. B. user.id, session.id)

Zurück: ← Ereignisse | Weiter: Launcher →

Telemetrie - ADK-Rust Dokumentation | ADK-Rust