Télémétrie

ADK-Rust offre une observabilité de qualité production via le crate adk-telemetry, qui intègre la journalisation structurée et le traçage distribué en utilisant l'écosystème tracing et OpenTelemetry.

Vue d'ensemble

Le système de télémétrie permet :

  • Journalisation structurée : Journaux riches et interrogeables avec des informations contextuelles
  • Traçage distribué : Suivez les requêtes à travers les hiérarchies d'agents et les limites de service
  • Intégration OpenTelemetry : Exportez les traces vers des backends d'observabilité (Jaeger, Datadog, Honeycomb, etc.)
  • Propagation automatique du contexte : Les identifiants de session, d'utilisateur et d'invocation circulent à travers toutes les opérations
  • Spans préconfigurés : Fonctions d'aide pour les opérations ADK courantes

Démarrage rapide

Journalisation de base sur console

Pour le développement et les déploiements simples, initialisez la journalisation sur console :

use adk_telemetry::init_telemetry;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialise la télémétrie avec le nom de votre service
    init_telemetry("my-agent-service")?;
    
    // Votre code d'agent ici
    
    Ok(())
}

Ceci configure la journalisation structurée vers stdout avec des valeurs par défaut sensées.

Exportation OpenTelemetry

Pour les déploiements de production avec traçage distribué :

use adk_telemetry::init_with_otlp;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialise avec l'exportateur OTLP
    init_with_otlp("my-agent-service", "http://localhost:4317")?;
    
    // Votre code d'agent ici
    
    // Vide les traces avant de quitter
    adk_telemetry::shutdown_telemetry();
    Ok(())
}

Ceci exporte les traces et les métriques vers un point de terminaison de collecteur OpenTelemetry.

Niveaux de journalisation

Contrôlez la verbosité de la journalisation à l'aide de la variable d'environnement RUST_LOG :

NiveauDescriptionCas d'utilisation
errorSeules les erreursProduction (minimale)
warnAvertissements et erreursProduction (par défaut)
infoMessages d'informationDéveloppement, staging
debugInformations de débogage détailléesDéveloppement local
traceTraçage très verbeuxDébogage approfondi

Définition des niveaux de journalisation

# Définir le niveau de journalisation global
export RUST_LOG=info

# Définir les niveaux de journalisation par module
export RUST_LOG=adk_agent=debug,adk_model=info

# Combiner les niveaux globaux et spécifiques aux modules
export RUST_LOG=warn,adk_agent=debug

Le système de télémétrie utilise le niveau info par défaut si RUST_LOG n'est pas défini.

Macros de journalisation

Utilisez les macros tracing standard pour la journalisation :

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

// Journalisation informationnelle
info!("Agent démarré avec succès");

// Journalisation structurée avec des champs
info!(
    agent.name = "my_agent",
    session.id = "sess-123",
    "Traitement de la requête utilisateur"
);

// Journalisation de débogage
debug!(user_input = ?input, "Entrée reçue");

// Journalisation d'avertissement et d'erreur
warn!("Limite de débit approchant");
error!(error = ?err, "Échec de l'appel au modèle");

Champs structurés

Ajoutez des champs contextuels aux messages de journalisation pour un meilleur filtrage et une meilleure analyse :

use adk_telemetry::info;

info!(
    agent.name = "customer_support",
    user.id = "user-456",
    session.id = "sess-789",
    invocation.id = "inv-abc",
    "Exécution de l'agent démarrée"
);

Ces champs deviennent interrogeables dans votre backend d'observabilité.

Instrumentation

Instrumentation automatique

Utilisez l'attribut #[instrument] pour créer automatiquement des spans pour les fonctions :

use adk_telemetry::{instrument, info};

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

// Crée un span nommé "process_request" avec user_id et message comme champs

Ignorer les paramètres sensibles

Excluez les données sensibles des traces :

use adk_telemetry::instrument;

#[instrument(skip(api_key))]
async fn call_external_api(api_key: &str, query: &str) {
    // api_key n'apparaîtra pas dans les traces
}

Noms de span personnalisés

use adk_telemetry::instrument;

#[instrument(name = "external_api_call")]
async fn fetch_data(url: &str) {
    // Le span sera nommé "external_api_call" au lieu de "fetch_data"
}

Spans préconfigurés

ADK-Telemetry fournit des fonctions utilitaires pour les opérations courantes :

Span d'exécution d'Agent

use adk_telemetry::agent_run_span;

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

// Code d'exécution d'Agent ici
// Tous les logs dans cette portée héritent du contexte du span

Span d'appel de Model

use adk_telemetry::model_call_span;

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

// Appel d'API de Model ici

Span d'exécution de Tool

use adk_telemetry::tool_execute_span;

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

// Code d'exécution de Tool ici

Span de rappel

use adk_telemetry::callback_span;

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

// Logique de rappel ici

Ajout d'attributs de contexte

Ajoutez le contexte utilisateur et de session au span actuel :

use adk_telemetry::add_context_attributes;

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

Création manuelle de spans

Pour une instrumentation personnalisée, créez des spans manuellement :

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");
// Code de l'opération ici

Attributs de Span

Ajoutez des attributs dynamiquement :

use adk_telemetry::Span;

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

Configuration OpenTelemetry

Point de terminaison OTLP

L'exportateur OTLP envoie les traces à un point de terminaison de collecteur :

use adk_telemetry::init_with_otlp;

// Jaeger local (port OTLP par défaut)
init_with_otlp("my-service", "http://localhost:4317")?;

// Point de terminaison du fournisseur cloud
init_with_otlp("my-service", "https://otlp.example.com:4317")?;

Exécution d'un collecteur local

Pour le développement, exécutez Jaeger avec le support OTLP :

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

# Visualisez les traces sur http://localhost:16686

Visualisation des traces

Une fois configurées, les traces apparaissent dans votre backend d'observabilité, affichant :

  • Hiérarchie d'exécution d'Agent
  • Latences d'appel de Model
  • Temps d'exécution de Tool
  • Propagation des erreurs
  • Flux de contexte (ID utilisateur, ID de session, etc.)

Intégration avec ADK

Les composants adk-rust émettent automatiquement de la télémétrie lorsque le système de télémétrie est initialisé :

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

Les opérations d'Agent, de Model et de Tool émettront automatiquement des logs et des traces structurés.

Télémétrie personnalisée dans les Tools

Ajoutez de la télémétrie aux Tools personnalisés :

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

Télémétrie personnalisée dans les Callbacks

Ajoutez de l'observabilité aux callbacks :

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

Considérations de performance

Échantillonnage

Pour les systèmes à haut débit, envisagez l'échantillonnage des traces :

// Remarque : La configuration de l'échantillonnage dépend de votre configuration OpenTelemetry
// Configurez l'échantillonnage dans votre collecteur OTLP ou votre backend

Spans asynchrones

Utilisez toujours #[instrument] sur les fonctions async pour assurer un contexte de span correct :

use adk_telemetry::instrument;

// ✅ Correct - le contexte de span est préservé à travers les points await
#[instrument]
async fn async_operation() {
    tokio::time::sleep(Duration::from_secs(1)).await;
}

// ❌ Incorrect - le span manuel peut perdre le contexte
async fn manual_span_operation() {
    let span = tracing::info_span!("operation");
    let _enter = span.enter();
    tokio::time::sleep(Duration::from_secs(1)).await;
    // Le contexte peut être perdu après await
}

Niveau de log en production

Utilisez le niveau info ou warn en production pour réduire la surcharge :

export RUST_LOG=warn,my_app=info

Dépannage

Aucun journal n'apparaît

  1. Vérifier que la variable d'environnement RUST_LOG est définie
  2. S'assurer que init_telemetry() est appelé avant toute journalisation
  3. Vérifier que la télémétrie n'est initialisée qu'une seule fois (utilise Once en interne)

Traces non exportées

  1. Vérifier que le point de terminaison OTLP est accessible
  2. Vérifier que le collecteur est en cours d'exécution et accepte les connexions
  3. Appeler shutdown_telemetry() avant la sortie de l'application pour vider les spans en attente
  4. Vérifier les problèmes de réseau/pare-feu

Contexte manquant dans les Spans

  1. Utiliser #[instrument] sur les fonctions async
  2. S'assurer que les spans sont entrés avec let _enter = span.enter()
  3. Garder la garde _enter dans la portée pendant la durée de l'opération

Bonnes Pratiques

  1. Initialiser Tôt : Appeler init_telemetry() au début de main()
  2. Utiliser des Champs Structurés : Ajouter du contexte avec des paires clé-valeur, pas d'interpolation de chaîne
  3. Instrumenter les Fonctions Async : Toujours utiliser #[instrument] sur les fonctions async
  4. Vider à la Sortie : Appeler shutdown_telemetry() avant la fin de l'application
  5. Niveaux de Journalisation Appropriés : Utiliser info pour les événements importants, debug pour les détails
  6. Éviter les Données Sensibles : Ignorer les paramètres sensibles avec #[instrument(skip(...))]
  7. Nommage Cohérent : Utiliser des noms de champ cohérents (par exemple, user.id, session.id)

Rubriques Connexes

  • Callbacks - Ajouter la télémétrie aux callbacks
  • Tools - Instrumenter les outils personnalisés
  • Déploiement - Configuration de la télémétrie de production

Précédent: ← Événements | Suivant: Lanceur →