텔레메트리

ADK-Rust는 adk-telemetry crate를 통해 프로덕션 수준의 관측 가능성을 제공하며, tracing 생태계와 OpenTelemetry를 사용하여 구조화된 로깅과 분산 추적을 통합합니다.

개요

텔레메트리 시스템은 다음을 지원합니다:

  • 구조화된 로깅: 컨텍스트 정보가 포함된 풍부하고 쿼리 가능한 로그
  • 분산 추적: 에이전트 계층 구조와 서비스 경계를 가로질러 요청 추적
  • OpenTelemetry 통합: 관측 가능성 백엔드(Jaeger, Datadog, Honeycomb 등)로 추적 내보내기
  • 자동 컨텍스트 전파: 세션, 사용자 및 호출 ID가 모든 작업을 통해 전달됨
  • 사전 구성된 스팬: 일반적인 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(())
}

이렇게 하면 합리적인 기본값으로 stdout에 구조화된 로깅이 구성됩니다.

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 collector 엔드포인트로 내보내집니다.

조합 가능한 레이어(고급)

이미 tracing subscriber가 구성되어 있다면 전역 subscriber를 초기화하는 대신 build_otlp_layer을 사용하여 조합 가능한 레이어를 가져옵니다:

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은 서버의 세션 텔레메트리 인터페이스를 위해 에이전트, 모델, 도구 및 이식 가능한 팀 span을 계속 보존합니다.

프로세스 내 세션 텔레메트리

서버는 내장 런타임 UI를 통해 인프라가 필요 없는 trace를 노출할 수 있습니다.

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

에이전트 세부 정보 응답은 기존 services.telemetry boolean을 유지하고 services.telemetryStatus를 추가합니다.

  • disabled — exporter가 구성되지 않았습니다.
  • configured — exporter가 준비되었지만 지원되는 span을 아직 보존하지 않았습니다.
  • collecting — 하나 이상의 지원되는 런타임 span이 보존되었습니다.

init_with_adk_exporter을 두 번 이상 호출하면 동일하게 등록된 exporter가 반환됩니다. 다른 전역 텔레메트리 모드가 먼저 초기화된 경우에는 span을 수신할 수 없는 연결이 끊긴 exporter 대신 조치 가능한 오류가 반환됩니다.

로깅 매크로

로깅에는 표준 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"
);

이러한 필드는 observability 백엔드에서 쿼리할 수 있습니다.

계측

자동 계측

#[instrument] attribute를 사용하여 함수에 대한 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

민감한 매개변수 건너뛰기

trace에서 민감한 데이터를 제외합니다.

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는 일반적인 작업을 위한 도우미 함수를 제공합니다.

에이전트 실행 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

모델 호출 Span

use adk_telemetry::model_call_span;

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

// Model API call here

도구 실행 Span

use adk_telemetry::tool_execute_span;

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

// Tool execution code here

콜백 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 토큰 사용량 추적

OpenTelemetry GenAI 의미 체계를 사용하여 모든 LLM 제공업체의 토큰 소비량을 추적합니다. llm_generate_span는 미리 선언된 gen_ai.usage.* 필드가 포함된 스팬을 생성하고, 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 호출에서 토큰 사용량을 자동으로 기록합니다. 수동 계측은 필요하지 않습니다. 추적 기능이 제공업체 계층에 내장되어 있습니다.

기록되는 스팬 필드는 OpenTelemetry GenAI 규칙을 따릅니다:

항목설명
gen_ai.usage.input_tokens프롬프트 / 입력 토큰 수
gen_ai.usage.output_tokens완료 / 출력 토큰 수
gen_ai.usage.total_tokens총 토큰 수
gen_ai.usage.cache_read_tokens프롬프트 캐시에서 읽은 토큰
gen_ai.usage.cache_creation_tokens캐시 생성에 사용된 토큰
gen_ai.usage.thinking_tokens사고 과정 추론 토큰
gen_ai.usage.audio_input_tokens오디오 입력 토큰 수
gen_ai.usage.audio_output_tokens오디오 출력 토큰 수

선택적 필드는 provider가 해당 필드를 보고하는 경우에만 기록됩니다(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 exporter는 trace를 collector 엔드포인트로 전송합니다:

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

로컬 Collector 실행

개발을 위해 OTLP를 지원하는 Jaeger를 실행합니다:

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

# View traces at http://localhost:16686

Trace 시각화

구성이 완료되면 observability backend에 다음 정보가 표시됩니다:

  • Agent 실행 계층
  • Model 호출 지연 시간
  • Tool 실행 시간
  • 오류 전파
  • Context 흐름(user ID, session 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, model, tool 작업은 구조화된 로그와 trace를 자동으로 생성합니다.

텔레메트리 데모 예제

로컬에서 텔레메트리 기능 선택을 검증합니다:

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

실제 Gemini 호출을 사용하는 전체 텔레메트리 데모는 adk-playground repo를 참조하세요.

Tool의 사용자 지정 텔레메트리

사용자 지정 tool에 텔레메트리를 추가합니다:

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

Callback의 사용자 지정 텔레메트리

Callback에 observability를 추가합니다:

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

성능 고려 사항

Sampling

높은 처리량의 시스템에서는 trace sampling을 고려하세요:

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

Async Span

적절한 Span context를 보장하려면 비동기 함수에서 항상 #[instrument]을 사용하세요:

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
}

Production 환경의 Log Level

오버헤드를 줄이려면 production 환경에서 info 또는 warn level을 사용하세요:

export RUST_LOG=warn,my_app=info

문제 해결

로그가 표시되지 않음

  1. RUST_LOG 환경 변수가 설정되어 있는지 확인합니다
  2. 로깅 전에 init_telemetry()가 호출되는지 확인합니다
  3. 서버를 구성하기 전에 의도한 전역 텔레메트리 모드를 초기화합니다

내보내지 않은 트레이스

  1. OTLP 엔드포인트에 연결할 수 있는지 확인합니다.
  2. 수집기가 실행 중이며 연결을 수락하는지 확인합니다.
  3. 애플리케이션이 종료되기 전에 보류 중인 스팬을 플러시하도록 shutdown_telemetry()을 호출합니다.
  4. 네트워크/방화벽 문제를 확인합니다.

임베디드 런타임 UI의 경우 services.telemetryStatus을 확인합니다. configured은 내보내기가 연결되어 있지만 선택한 세션에서 보존된 런타임 스팬이 아직 생성되지 않았음을 의미하며, collecting는 수집기가 데이터를 수신했음을 확인합니다.

스팬의 컨텍스트 누락

  1. 비동기 함수에 #[instrument]을 사용합니다.
  2. let _enter = span.enter()을 사용하여 스팬에 진입하는지 확인합니다.
  3. 작업이 수행되는 동안 _enter 가드를 스코프 내에 유지합니다.

모범 사례

  1. 조기 초기화: main() 시작 시 init_telemetry()을 호출합니다.
  2. 구조화된 필드 사용: 문자열 보간이 아닌 키-값 쌍으로 컨텍스트를 추가합니다.
  3. 비동기 함수 계측: 비동기 함수에는 항상 #[instrument]을 사용합니다.
  4. 종료 시 플러시: 애플리케이션이 종료되기 전에 shutdown_telemetry()을 호출합니다.
  5. 적절한 로그 수준: 중요한 이벤트에는 info을, 세부 정보에는 debug을 사용합니다.
  6. 민감한 데이터 방지: #[instrument(skip(...))]을 사용하여 민감한 매개변수를 건너뜁니다.
  7. 일관된 명명: 일관된 필드 이름(예: user.id, session.id)을 사용합니다.

이전: ← 이벤트 | 다음: 런처 →

텔레메트리 - ADK-Rust 문서 | ADK-Rust