Telemetria

ADK-Rust fornece observabilidade de nível de produção por meio do crate adk-telemetry, que integra registro estruturado e rastreamento distribuído usando o ecossistema tracing e OpenTelemetry.

Visão geral

O sistema de telemetria oferece:

  • Registro estruturado: Logs avançados e consultáveis com informações contextuais
  • Rastreamento distribuído: Acompanhe solicitações entre hierarquias de agentes e limites de serviços
  • Integração com 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 de ADK

Início rápido

Registro básico no console

Para desenvolvimento e implantações simples, inicialize o registro no 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 registro estruturado para stdout com padrões adequados.

Exportação de OpenTelemetry

Para implantações de 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 coletor OpenTelemetry.

Camada componível (avançado)

Se você já tiver um subscriber tracing configurado, use build_otlp_layer para obter uma camada componível em vez de inicializar um subscriber 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 registro usando a variável de ambiente RUST_LOG:

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

Configuração dos 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 registro em console usa info por padrão se RUST_LOG não estiver definido. A coleta de spans em runtime configurada por init_with_adk_exporter é independente deste filtro do console: RUST_LOG=warn ainda retém spans de agente, modelo, ferramenta e equipe portátil para a interface de telemetria de sessões do servidor.

Telemetria de sessões no processo

Os servidores podem expor traces sem infraestrutura por meio da UI de runtime incorporada:

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

A resposta de detalhes do agente preserva o booleano services.telemetry existente e adiciona services.telemetryStatus:

  • disabled — nenhum exportador está configurado;
  • configured — o exportador está pronto, mas ainda não reteve um span compatível;
  • collecting — pelo menos um span de runtime compatível foi retido.

Chamar init_with_adk_exporter mais de uma vez retorna o mesmo exportador registrado. Se outro modo global de telemetria tiver sido inicializado primeiro, ele retorna um erro acionável em vez de um exportador desconectado que nunca poderá receber spans.

Macros de log

Use as macros padrão tracing para registro:

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 melhorar a filtragem e a 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 podem ser consultados 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 traces:

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 do agente

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 do modelo

use adk_telemetry::model_call_span;

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

// Model API call here

Span de execução da ferramenta

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

Adicione o contexto do usuário e da sessão ao span atual:

use adk_telemetry::add_context_attributes;

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

LLM Rastreamento do uso de tokens

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

Todos os provedores de modelos 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. Não é necessária instrumentação manual — o rastreamento está integrado à camada do provedor.

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

CampoDescrição
gen_ai.usage.input_tokensContagem de tokens do prompt / entrada
gen_ai.usage.output_tokensContagem de tokens da conclusão / 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 o cache
gen_ai.usage.thinking_tokensTokens de raciocínio do chain-of-thought
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

Os campos opcionais só são registrados quando o provedor os informa (diferentes de 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 dos spans

Adicione atributos dinamicamente:

use adk_telemetry::Span;

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

Configuração de 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")?;

Execução de um coletor local

Para desenvolvimento, execute o Jaeger com suporte a 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

Depois de configurados, os traces aparecem no seu backend de observabilidade, exibindo:

  • Hierarquia de execução do agente
  • Latências das chamadas ao modelo
  • Duração da execução das ferramentas
  • Propagação de erros
  • Fluxo de contexto (ID do usuário, ID da sessão etc.)

Integração com ADK

Os componentes de ADK-Rust 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-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(())
}

As operações do agente, do modelo e das ferramentas emitirão automaticamente logs e traces estruturados.

Exemplo de demonstração de telemetria

Valide localmente a seleção do recurso de telemetria:

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

Consulte o repositório adk-playground para ver uma demonstração completa de telemetria com chamadas reais ao Gemini.

Telemetria personalizada em ferramentas

Adicione telemetria a ferramentas 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 aos 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 volume, considere a amostragem de traces:

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

Spans assíncronos

Sempre use #[instrument] em funções assíncronas para garantir o contexto adequado do span:

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 aparece

  1. Verifique se a variável de ambiente RUST_LOG está definida
  2. Certifique-se de que init_telemetry() seja chamado antes de qualquer registro de log
  3. Inicialize o modo global de telemetria pretendido antes de construir o servidor

Rastreamentos 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 da saída da aplicação para liberar os spans pendentes
  4. Verifique se há problemas de rede/firewall

Para a interface do usuário do runtime incorporado, verifique services.telemetryStatus. configured significa que o exporter está anexado, mas a sessão selecionada ainda não produziu um span de runtime retido; collecting confirma que o coletor recebeu dados.

Contexto ausente nos spans

  1. Use #[instrument] em funções assíncronas
  2. Certifique-se de que os spans sejam inseridos com let _enter = span.enter()
  3. Mantenha a proteção _enter no escopo durante toda a operação

Práticas recomendadas

  1. Inicialize cedo: chame init_telemetry() no início de main()
  2. Use campos estruturados: adicione contexto com pares de chave-valor, não com interpolação de strings
  3. Instrumente funções assíncronas: sempre use #[instrument] em funções assíncronas
  4. Libere os dados na saída: chame shutdown_telemetry() antes do encerramento da aplicação
  5. Níveis de log apropriados: use info para eventos importantes e debug para detalhes
  6. Evite dados confidenciais: ignore parâmetros confidenciais com #[instrument(skip(...))]
  7. Nomenclatura consistente: use nomes de campos consistentes (por exemplo, user.id, session.id)

Anterior: ← Eventos | Próximo: Launcher →

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