Telemetrie

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

Übersicht

Das Telemetriesystem ermöglicht:

  • Strukturiertes Logging: Umfangreiche, abfragbare Logs mit kontextuellen Informationen
  • Verteiltes Tracing: Verfolgen von Anfragen über Agent-Hierarchien und Dienstgrenzen hinweg
  • OpenTelemetry Integration: Exportieren von Traces an Observability-Backends (Jaeger, Datadog, Honeycomb, etc.)
  • Automatische Kontext-Weitergabe: Session, user und invocation IDs fließen durch alle Operationen
  • Vorkonfigurierte Spans: Hilfsfunktionen für gängige ADK-Operationen

Schnellstart

Grundlegendes Konsolen-Logging

Für 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>> {
    // Initialisieren Sie die Telemetrie mit Ihrem Dienstnamen
    init_telemetry("my-agent-service")?;
    
    // Ihr Agent-Code hier
    
    Ok(())
}

Dies konfiguriert strukturiertes Logging nach stdout mit vernünftigen Standardeinstellungen.

OpenTelemetry-Export

Für Produktionsbereitstellungen mit verteiltes Tracing:

use adk_telemetry::init_with_otlp;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Mit OTLP-Exporter initialisieren
    init_with_otlp("my-agent-service", "http://localhost:4317")?;
    
    // Ihr Agent-Code hier
    
    // Traces vor dem Beenden leeren
    adk_telemetry::shutdown_telemetry();
    Ok(())
}

Dies exportiert Traces und Metriken an einen OpenTelemetry-Collector-Endpunkt.

Log-Level

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

LevelBeschreibungAnwendungsfall
errorNur FehlerProduktion (minimal)
warnWarnungen und FehlerProduktion (Standard)
infoInformative NachrichtenEntwicklung, Staging
debugDetaillierte Debugging-InformationenLokale Entwicklung
traceSehr ausführliches TracingTiefes Debugging

Log-Level einstellen

# Globales Log-Level setzen
export RUST_LOG=info

# Log-Level pro Modul setzen
export RUST_LOG=adk_agent=debug,adk_model=info

# Globale und modulspezifische Level kombinieren
export RUST_LOG=warn,adk_agent=debug

Das Telemetriesystem verwendet standardmäßig das info-Level, wenn RUST_LOG nicht gesetzt ist.

Logging-Makros

Verwenden Sie die Standard tracing Makros für das Logging:

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

// Informatives Logging
info!("Agent erfolgreich gestartet");

// Strukturiertes Logging mit Feldern
info!(
    agent.name = "my_agent",
    session.id = "sess-123",
    "Benutzeranfrage wird verarbeitet"
);

// Debug-Logging
debug!(user_input = ?input, "Eingabe erhalten");

// Warn- und Fehler-Logging
warn!("Ratenbegrenzung nähert sich");
error!(error = ?err, "Fehler beim Aufruf des Modells");

Strukturierte Felder

Fügen Sie kontextuelle Felder zu Log-Nachrichten hinzu, um eine bessere Filterung und Analyse zu ermöglichen:

use adk_telemetry::info;

info!(
    agent.name = "customer_support",
    user.id = "user-456",
    session.id = "sess-789",
    invocation.id = "inv-abc",
    "Agent-Ausführung gestartet"
);

Diese Felder werden in Ihrem Observability-Backend abfragbar.

Instrumentierung

Automatische Instrumentierung

Verwenden Sie das Attribut #[instrument], um Spans automatisch 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
}

// Erstellt einen Span namens "process_request" mit user_id und message als Felder

Sensible Parameter überspringen

Schließen Sie sensible Daten von 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 wird "external_api_call" anstelle von "fetch_data" genannt
}

Vorkonfigurierte Spans

adk-telemetry bietet Hilfsfunktionen für gängige Operationen:

Agent-Ausführungs-Span

use adk_telemetry::agent_run_span;

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

// Agent-Ausführungscode hier
// Alle Logs innerhalb dieses Scopes erben den Span-Kontext

Modellaufruf-Span

use adk_telemetry::model_call_span;

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

// Modell-API-Aufruf hier

Tool-Ausführungs-Span

use adk_telemetry::tool_execute_span;

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

// Tool-Ausführungscode hier

Callback-Span

use adk_telemetry::callback_span;

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

// Callback-Logik hier

Kontextattribute hinzufügen

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

use adk_telemetry::add_context_attributes;

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

Manuelle Span-Erstellung

Für eine benutzerdefinierte Instrumentierung erstellen Sie Spans manuell:

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");
// Operationscode hier

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;

// Lokaler Jaeger (Standard-OTLP-Port)
init_with_otlp("my-service", "http://localhost:4317")?;

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

Lokalen Collector ausführen

Für die Entwicklung führen Sie Jaeger mit OTLP-Unterstützung aus:

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

# Traces unter http://localhost:16686 anzeigen

Trace-Visualisierung

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

  • Agent-Ausführungshierarchie
  • Latenzen von Modellaufrufen
  • Timing der Tool-Ausführung
  • Fehlerfortpflanzung
  • Kontextfluss (Benutzer-ID, Session-ID usw.)

Integration mit ADK

adk-rust Komponenten senden automatisch Telemetriedaten, wenn das Telemetriesystem initialisiert wird:

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-2.5-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 Agent-, Model- und Tool-Operationen senden automatisch strukturierte Logs und Traces.

Benutzerdefinierte Telemetrie in Tools

Fügen Sie benutzerdefinierten Tools Telemetrie hinzu:

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

Fügen Sie Callbacks Observability hinzu:

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

Bei Systemen mit hohem Durchsatz sollten Sie Trace-Sampling in Betracht ziehen:

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

Async Spans

Verwenden Sie immer #[instrument] für async-Funktionen, um den richtigen Span-Kontext sicherzustellen:

use adk_telemetry::instrument;

// ✅ Korrekt - der Span-Kontext bleibt über await-Punkte hinweg erhalten
#[instrument]
async fn async_operation() {
    tokio::time::sleep(Duration::from_secs(1)).await;
}

// ❌ Falsch - ein manueller Span kann den Kontext verlieren
async fn manual_span_operation() {
    let span = tracing::info_span!("operation");
    let _enter = span.enter();
    tokio::time::sleep(Duration::from_secs(1)).await;
    // Der Kontext kann nach await verloren gehen
}

Log Level in Produktion

Verwenden Sie in der Produktion die Stufen info oder warn, um den Overhead zu reduzieren:

export RUST_LOG=warn,my_app=info

Fehlerbehebung

Keine Protokolle werden angezeigt

  1. Überprüfen Sie, ob die Umgebungsvariable RUST_LOG gesetzt ist
  2. Stellen Sie sicher, dass init_telemetry() vor jeder Protokollierung aufgerufen wird
  3. Überprüfen Sie, ob die telemetry nur einmal initialisiert wird (verwendet intern Once)

Traces werden nicht exportiert

  1. Überprüfen Sie, ob der OTLP-Endpunkt erreichbar ist
  2. Überprüfen Sie, ob der collector läuft und Verbindungen akzeptiert
  3. Rufen Sie shutdown_telemetry() vor dem Beenden der Anwendung auf, um ausstehende spans zu leeren
  4. Suchen Sie nach Netzwerk-/Firewall-Problemen

Fehlender Kontext in Spans

  1. Verwenden Sie #[instrument] für async Funktionen
  2. Stellen Sie sicher, dass spans mit let _enter = span.enter() betreten werden
  3. Halten Sie den _enter-Guard für die Dauer des Vorgangs im Geltungsbereich

Bewährte Methoden

  1. Frühzeitig initialisieren: Rufen Sie init_telemetry() zu Beginn von main() auf
  2. Strukturierte Felder verwenden: Fügen Sie Kontext mit Schlüssel-Wert-Paaren hinzu, nicht mit Zeichenketten-Interpolation
  3. Async-Funktionen instrumentieren: Verwenden Sie immer #[instrument] für async Funktionen
  4. Beim Beenden leeren: Rufen Sie shutdown_telemetry() vor dem Beenden der Anwendung auf
  5. Angemessene Protokollebenen: Verwenden Sie info für wichtige Ereignisse, debug für Details
  6. Vermeiden Sie sensible Daten: Überspringen Sie sensible Parameter mit #[instrument(skip(...))]
  7. Konsistente Benennung: Verwenden Sie konsistente Feldnamen (z. B. user.id, session.id)

Verwandt

  • Callbacks - Fügen Sie Callbacks telemetry hinzu
  • Tools - Instrumentieren Sie benutzerdefinierte Tools
  • Bereitstellung - telemetry-Einrichtung für die Produktion

Zurück: ← Events | Weiter: Launcher →