Telemetria

ADK-Rust fornece observabilidade de nível de produção através da crate adk-telemetry, que integra log estruturado e rastreamento distribuído usando o ecossistema tracing e OpenTelemetry.

Visão Geral

O sistema de telemetria permite:

  • Log Estruturado: Logs ricos e consultáveis com informações contextuais
  • Rastreamento Distribuído: Rastreie solicitações através de hierarquias de agentes e limites de serviço
  • Integração OpenTelemetry: Exporte rastreamentos para backends de observabilidade (Jaeger, Datadog, Honeycomb, etc.)
  • Propagação Automática de Contexto: IDs de sessão, usuário e invocação fluem por todas as operações
  • Spans Pré-configurados: Funções auxiliares para operações comuns do ADK

Início Rápido

Log Básico no Console

Para desenvolvimento e implantações simples, inicialize o log do console:

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

Isso configura o log estruturado para stdout com padrões sensatos.

Exportação OpenTelemetry

Para implantações em produção com rastreamento distribuído:

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

Isso exporta rastreamentos e métricas para um endpoint de coletor OpenTelemetry.

Camada Componível (Avançado)

Se você já tem um tracing assinante configurado, use build_otlp_layer para obter uma camada componível em vez de inicializar um assinante global:

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

Níveis de Log

Controle a verbosidade do logging usando a variável de ambiente RUST_LOG:

NívelDescriçãoCaso de Uso
errorApenas errosProdução (mínima)
warnAvisos e errosProdução (padrão)
infoMensagens informativasDesenvolvimento, homologação
debugInformações detalhadas de depuraçãoDesenvolvimento local
traceRastreamento muito detalhadoDepuração profunda

Configurando Níveis de Log

# 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

O sistema de telemetria usa o nível info por padrão se RUST_LOG não estiver definido.

Macros de Log

Use as macros tracing padrão para log:

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

Campos Estruturados

Adicione campos contextuais às mensagens de log para melhor filtragem e análise:

use adk_telemetry::info;

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

Esses campos se tornam consultáveis no seu backend de observabilidade.

Instrumentação

Instrumentação Automática

Use o atributo #[instrument] para criar spans automaticamente para funções:

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

Ignorar Parâmetros Sensíveis

Exclua dados sensíveis dos rastreamentos:

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
}

Nomes de Span Personalizados

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

Spans Pré-configurados

ADK-Telemetry fornece funções auxiliares para operações comuns:

Span de Execução de Agent

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 de Chamada de Modelo

use adk_telemetry::model_call_span;

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

// Model API call here

Span de Execução de Tool

use adk_telemetry::tool_execute_span;

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

// Tool execution code here

Span de Callback

use adk_telemetry::callback_span;

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

// Callback logic here

Adicionando Atributos de Contexto

Adicionar contexto de usuário e sessão ao span atual:

use adk_telemetry::add_context_attributes;

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

Rastreamento de Uso de Tokens LLM

Rastreie o consumo de tokens em todos os provedores LLM com as convenções semânticas OpenTelemetry GenAI. O llm_generate_span cria um span com campos gen_ai.usage.* pré-declarados, e o record_llm_usage os preenche após a chegada da resposta:

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

let span = llm_generate_span("openai", "gpt-5-mini", 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()
});

Todos os provedores de modelo ADK (Gemini, OpenAI, Anthropic, Ollama, Bedrock, DeepSeek, Groq, Azure AI, e todos os provedores compatíveis com OpenAI) registram automaticamente o uso de tokens em cada chamada generate_content. Nenhuma instrumentação manual é necessária — o rastreamento é integrado à camada do provedor.

Os campos de span registrados seguem as convenções OpenTelemetry GenAI:

CampoDescrição
gen_ai.usage.input_tokensContagem de tokens de prompt / entrada
gen_ai.usage.output_tokensConclusão / contagem de tokens de saída
gen_ai.usage.total_tokensContagem total de tokens
gen_ai.usage.cache_read_tokensTokens lidos do cache de prompt
gen_ai.usage.cache_creation_tokensTokens usados para criar cache
gen_ai.usage.thinking_tokensTokens de raciocínio em cadeia de pensamento
gen_ai.usage.audio_input_tokensContagem de tokens de entrada de áudio
gen_ai.usage.audio_output_tokensContagem de tokens de saída de áudio

Campos opcionais são registrados apenas quando o provedor os reporta (não-None).

Criação Manual de Spans

Para instrumentação personalizada, crie spans manualmente:

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

Atributos de Span

Adicione atributos dinamicamente:

use adk_telemetry::Span;

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

Configuração do OpenTelemetry

Endpoint OTLP

O exportador OTLP envia traces para um endpoint de coletor:

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

Executando um Coletor Local

Para desenvolvimento, execute o Jaeger com suporte OTLP:

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

# View traces at http://localhost:16686

Visualização de Traces

Uma vez configurado, os traces aparecem no seu backend de observabilidade mostrando:

  • Hierarquia de execução do Agent
  • Latências de chamadas do Model
  • Tempo de execução do Tool
  • Propagação de erros
  • Fluxo de contexto (user ID, session ID, etc.)

Integração com ADK

ADK-Rust componentes emitem telemetria automaticamente quando o sistema de telemetria é inicializado:

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

As operações de agent, model e tool emitirão automaticamente logs e traces estruturados.

Exemplo de Demonstração de Telemetria

Valide a seleção de recursos de telemetria localmente:

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

Abra o ADK-Rust Playground incorporado para ver exemplos completos de telemetria com chamadas reais de modelos.

Telemetria Personalizada em Tools

Adicione telemetria a tools personalizadas:

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

Telemetria Personalizada em Callbacks

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

Considerações de Desempenho

Amostragem

Para sistemas de alto throughput, considere a amostragem de traces:

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

Spans Async

Sempre use #[instrument] em funções async para garantir o contexto de span adequado:

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
}

Nível de Log em Produção

Use o nível info ou warn em produção para reduzir a sobrecarga:

export RUST_LOG=warn,my_app=info

Solução de Problemas

Nenhum Log Aparecendo

  1. Verifique se a variável de ambiente RUST_LOG está definida
  2. Garanta que init_telemetry() seja chamada antes de qualquer registro (logging)
  3. Verifique se a telemetria é inicializada apenas uma vez (usa Once internamente)

Traços Não Exportados

  1. Verifique se o endpoint OTLP está acessível
  2. Verifique se o coletor está em execução e aceitando conexões
  3. Chame shutdown_telemetry() antes do encerramento da aplicação para descarregar (flush) spans pendentes
  4. Verifique a existência de problemas de rede/firewall

Contexto Ausente em Spans

  1. Use #[instrument] em funções async
  2. Garanta que os spans sejam inseridos com let _enter = span.enter()
  3. Mantenha o guard _enter no escopo pela duração da operação

Melhores Práticas

  1. Inicialize Cedo: Chame init_telemetry() no início de main()
  2. Use Campos Estruturados: Adicione contexto com pares chave-valor, não interpolação de string
  3. Instrumente Funções Async: Sempre use #[instrument] em async functions
  4. Descarregue na Saída: Chame shutdown_telemetry() antes do encerramento da aplicação
  5. Níveis de Log Apropriados: Use info para eventos importantes, debug para detalhes
  6. Evite Dados Sensíveis: Pule parâmetros sensíveis com #[instrument(skip(...))]
  7. Nomenclatura Consistente: Use nomes de campo consistentes (ex: user.id, session.id)
  • Callbacks - Adicione telemetria a callbacks
  • Tools - Instrumente ferramentas personalizadas
  • Implantação - Configuração de telemetria de produção

Anterior: ← Eventos | Próximo: Lançador →

Telemetria - Documentação ADK-Rust | ADK-Rust