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:
| Stufe | Beschreibung | Anwendungsfall |
|---|---|---|
error | Nur Fehler | Produktion (minimal) |
warn | Warnungen und Fehler | Produktion (Standard) |
info | Informative Meldungen | Entwicklung, Staging |
debug | Detaillierte Debugging-Informationen | Lokale Entwicklung |
trace | Sehr ausführliches Tracing | Tiefgreifendes 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:
| Feld | Beschreibung |
|---|---|
gen_ai.usage.input_tokens | Prompt-/Eingabe-Tokenanzahl |
gen_ai.usage.output_tokens | Completion-/Ausgabe-Tokenanzahl |
gen_ai.usage.total_tokens | Gesamtanzahl der Tokens |
gen_ai.usage.cache_read_tokens | Aus dem Prompt-Cache gelesene Tokens |
gen_ai.usage.cache_creation_tokens | Zum Erstellen des Caches verwendete Tokens |
gen_ai.usage.thinking_tokens | Tokens für die Chain-of-Thought-Argumentation |
gen_ai.usage.audio_input_tokens | Anzahl der Audio-Eingabetoken |
gen_ai.usage.audio_output_tokens | Anzahl 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
- Prüfen, ob die Umgebungsvariable
RUST_LOGgesetzt ist - Sicherstellen, dass
init_telemetry()vor jeglicher Protokollierung aufgerufen wird - Den vorgesehenen globalen Telemetriemodus initialisieren, bevor der Server erstellt wird
Traces nicht exportiert
- Überprüfen Sie, ob der OTLP-Endpunkt erreichbar ist
- Überprüfen Sie, ob der Collector ausgeführt wird und Verbindungen akzeptiert
- Rufen Sie
shutdown_telemetry()vor dem Beenden der Anwendung auf, um ausstehende Spans zu leeren - Ü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
- Verwenden Sie
#[instrument]für asynchrone Funktionen - Stellen Sie sicher, dass Spans mit
let _enter = span.enter()betreten werden - Lassen Sie den
_enter-Guard für die Dauer der Operation im Gültigkeitsbereich
Best Practices
- Frühzeitig initialisieren: Rufen Sie
init_telemetry()am Anfang vonmain()auf - Strukturierte Felder verwenden: Fügen Sie Kontext mit Schlüssel-Wert-Paaren hinzu, nicht durch String-Interpolation
- Asynchrone Funktionen instrumentieren: Verwenden Sie für asynchrone Funktionen immer
#[instrument] - Beim Beenden leeren: Rufen Sie
shutdown_telemetry()vor der Beendigung der Anwendung auf - Geeignete Protokollebenen: Verwenden Sie
infofür wichtige Ereignisse unddebugfür Details - Sensible Daten vermeiden: Überspringen Sie sensible Parameter mit
#[instrument(skip(...))] - Konsistente Benennung: Verwenden Sie konsistente Feldnamen (z. B.
user.id,session.id)
Verwandte Themen
- Telemetrie zu Google Cloud – Direkter Cloud-Trace-Export und Cloud-Logging JSON
- Callbacks – Telemetrie zu Callbacks hinzufügen
- Tools – Benutzerdefinierte Tools instrumentieren
- Bereitstellung – Einrichtung der Telemetrie für die Produktion
Zurück: ← Ereignisse | Weiter: Launcher →