遥测

ADK-Rust 通过 adk-telemetry crate 提供生产级可观测性,该 crate 使用 tracing 生态系统和 OpenTelemetry 集成结构化日志记录与分布式追踪。

概述

遥测系统支持:

  • 结构化日志记录:包含上下文信息、内容丰富且可查询的日志
  • 分布式追踪:跨代理层级和服务边界跟踪请求
  • OpenTelemetry 集成:将追踪信息导出到可观测性后端(Jaeger、Datadog、Honeycomb 等)
  • 自动上下文传播:会话、用户和调用 ID 在所有操作中流转
  • 预配置 Span:为常见 ADK 操作提供辅助函数

快速开始

基本控制台日志记录

对于开发环境和简单部署,请初始化控制台日志记录:

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

这会使用合理的默认设置,将结构化日志记录配置为输出到标准输出。

OpenTelemetry 导出

对于需要分布式追踪的生产部署:

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

这会将追踪信息和指标导出到 OpenTelemetry 收集器端点。

可组合层(高级)

如果你已经配置了 tracing subscriber,请使用 build_otlp_layer 获取可组合层,而不是初始化全局 subscriber:

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

日志级别

使用 RUST_LOG 环境变量控制日志详细程度:

级别描述使用场景
error仅错误生产环境(最小)
warn警告和错误生产环境(默认)
info信息性消息开发、预发布
debug详细调试信息本地开发
trace非常详细的跟踪深度调试

设置日志级别

# 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

如果未设置 RUST_LOG,控制台日志记录默认为 info。由 init_with_adk_exporter 配置的运行时 span 收集独立于此控制台过滤器:RUST_LOG=warn 仍会保留 agent、model、tool 和可移植 team span,供服务器的会话遥测接口使用。

进程内会话遥测

服务器可以通过嵌入式运行时 UI 公开零基础设施依赖的跟踪信息:

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

agent-details 响应保留现有的 services.telemetry 布尔值,并添加 services.telemetryStatus:

  • disabled — 未配置导出器;
  • configured — 导出器已就绪,但尚未保留受支持的 span;
  • collecting — 至少已保留一个受支持的运行时 span。

多次调用 init_with_adk_exporter 会返回同一个已注册的导出器。如果其他全局遥测模式已先完成初始化,则会返回可操作的错误,而不是返回一个永远无法接收 span 的断开连接的导出器。

日志宏

使用标准 tracing 宏进行日志记录:

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

结构化字段

为日志消息添加上下文字段,以便更好地进行过滤和分析:

use adk_telemetry::info;

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

这些字段可以在你的可观测性后端中进行查询。

插桩

自动插桩

使用 #[instrument] 属性自动为函数创建 span:

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

跳过敏感参数

从跟踪信息中排除敏感数据:

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
}

自定义 Span 名称

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

预配置的 Span

ADK-Telemetry 为常见操作提供辅助函数:

Agent 执行 Span

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

Model 调用 Span

use adk_telemetry::model_call_span;

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

// Model API call here

Tool 执行 Span

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

添加上下文属性

将用户和会话上下文添加到当前 span:

use adk_telemetry::add_context_attributes;

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

LLM Token 使用跟踪

使用 OpenTelemetry GenAI 语义约定,跟踪所有 LLM 提供商的 Token 消耗。llm_generate_span 创建一个包含预先声明的 gen_ai.usage.* 字段的 span,record_llm_usage 在响应到达后填充这些字段:

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

所有 ADK 模型提供商(Gemini、OpenAI、Anthropic、Ollama、Bedrock、DeepSeek、Groq、Azure AI 以及所有兼容 OpenAI 的提供商)都会在每次 generate_content 调用中自动记录 Token 使用情况。无需手动插桩——跟踪功能已内置于提供商层。

记录的 span 字段遵循OpenTelemetry GenAI 约定:

字段描述
gen_ai.usage.input_tokens提示 / 输入令牌数
gen_ai.usage.output_tokens补全 / 输出令牌数
gen_ai.usage.total_tokensToken 总数
gen_ai.usage.cache_read_tokens从提示缓存中读取的 Token
gen_ai.usage.cache_creation_tokens用于创建缓存的 Token
gen_ai.usage.thinking_tokens思维链推理 Token
gen_ai.usage.audio_input_tokens音频输入令牌数量
gen_ai.usage.audio_output_tokens音频输出令牌数量

仅当提供商报告这些字段(非 None)时,才会记录它们。

手动创建 Span

对于自定义插桩,手动创建 Span:

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 属性

动态添加属性:

use adk_telemetry::Span;

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

OpenTelemetry 配置

OTLP 端点

OTLP 导出器将追踪发送到收集器端点:

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

运行本地收集器

在开发环境中,运行支持 OTLP 的 Jaeger:

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

# View traces at http://localhost:16686

追踪可视化

配置完成后,追踪会显示在您的可观测性后端中,其中包括:

  • Agent 执行层级
  • 模型调用延迟
  • 工具执行时间
  • 错误传播
  • 上下文流(用户 ID、会话 ID 等)

与 ADK 集成

ADK-Rust 组件会在遥测系统初始化后自动发出遥测数据:

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

Agent、模型和工具操作将自动发出结构化日志和追踪。

遥测演示示例

在本地验证遥测功能选择:

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

请参阅 adk-playground 仓库,了解使用真实 Gemini 调用的完整遥测演示。

工具中的自定义遥测

为自定义工具添加遥测:

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

回调中的自定义遥测

为回调添加可观测性:

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

性能注意事项

采样

对于高吞吐量系统,请考虑对追踪进行采样:

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

异步 Span

始终在异步函数上使用 #[instrument],以确保 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
}

生产环境中的日志级别

在生产环境中使用 info 或 warn 级别,以减少开销:

export RUST_LOG=warn,my_app=info

故障排除

未显示日志

  1. 检查是否设置了 RUST_LOG 环境变量
  2. 确保在任何日志记录之前调用 init_telemetry()
  3. 在构建服务器之前初始化预期的全局遥测模式

未导出追踪

  1. 验证 OTLP 端点是否可访问
  2. 检查收集器是否正在运行并接受连接
  3. 在应用退出前调用 shutdown_telemetry(),以刷新待处理的 span
  4. 检查网络/防火墙问题

对于嵌入式运行时 UI,请检查 services.telemetryStatus。configured 表示导出器已连接,但所选会话尚未生成保留的运行时 span;collecting 确认收集器已收到数据。

Span 中缺少上下文

  1. 在异步函数上使用 #[instrument]
  2. 确保使用 let _enter = span.enter() 进入 span
  3. 在整个操作期间保持 _enter 守卫处于作用域内

最佳实践

  1. 尽早初始化:在 main() 开始时调用 init_telemetry()
  2. 使用结构化字段:使用键值对添加上下文,而不是进行字符串插值
  3. 为异步函数添加检测:始终在异步函数上使用 #[instrument]
  4. 退出时刷新:在应用终止前调用 shutdown_telemetry()
  5. 使用适当的日志级别:对重要事件使用 info,对详细信息使用 debug
  6. 避免敏感数据:使用 #[instrument(skip(...))] 跳过敏感参数
  7. 保持命名一致:使用一致的字段名称(例如 user.id、session.id)

上一页:← 事件 | 下一页:启动器 →