텔레메트리

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

개요

텔레메트리 시스템은 다음을 가능하게 합니다:

  • 구조화된 로깅: 컨텍스트 정보가 포함된 풍부하고 쿼리 가능한 로그
  • 분산 트레이싱: 에이전트 계층 및 서비스 경계를 넘어 요청 추적
  • 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가 구성되어 있다면, 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 레벨로 설정됩니다.

로깅 매크로

로깅을 위해 표준 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] 속성을 사용하세요:

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
}

사용자 지정 스팬 이름

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

사전 구성된 스팬

ADK-Telemetry는 일반적인 작업을 위한 헬퍼 함수를 제공합니다:

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

모델 호출 스팬

use adk_telemetry::model_call_span;

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

// Model API call here

Tool 실행 스팬

use adk_telemetry::tool_execute_span;

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

// Tool execution code here

콜백 스팬

use adk_telemetry::callback_span;

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

// Callback logic here

컨텍스트 속성 추가

현재 스팬에 사용자 및 세션 컨텍스트 추가:

use adk_telemetry::add_context_attributes;

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

LLM 토큰 사용량 추적

모든 LLM 공급자에서 OpenTelemetry GenAI 시맨틱 컨벤션을 사용하여 토큰 소비를 추적합니다. 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-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()
});

모든 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오디오 출력 토큰 수

선택적 필드는 공급자가 보고할 때만 기록됩니다 (non-None).

수동 스팬 생성

사용자 지정 계측을 위해 스팬을 수동으로 생성합니다:

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

스팬 속성

속성을 동적으로 추가합니다:

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 실행 계층
  • Model 호출 지연 시간
  • Tool 실행 타이밍
  • 오류 전파
  • 컨텍스트 흐름 (사용자 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-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(())
}

Agent, Model, Tool 작업은 자동으로 구조화된 로그와 트레이스를 내보냅니다.

텔레메트리 데모 예시

로컬에서 텔레메트리 기능 선택을 검증하세요:

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

실제 모델 호출을 사용하는 전체 텔레메트리 예제는 이 사이트에 포함된 ADK-Rust Playground에서 확인하세요.

Tools의 커스텀 텔레메트리

커스텀 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,
);

Callbacks의 커스텀 텔레메트리

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

성능 고려 사항

샘플링

높은 처리량 시스템의 경우, 트레이스 샘플링을 고려하세요:

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

Async Spans

적절한 span 컨텍스트를 보장하기 위해 async 함수에서 항상 #[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
}

프로덕션 환경의 로그 레벨

오버헤드를 줄이기 위해 프로덕션 환경에서 info 또는 warn 레벨을 사용하세요:

export RUST_LOG=warn,my_app=info

문제 해결

로그가 나타나지 않음

  1. RUST_LOG 환경 변수가 설정되었는지 확인
  2. init_telemetry()이 로깅 전에 호출되는지 확인
  3. 텔레메트리가 한 번만 초기화되는지 확인 (내부적으로 Once 사용)

내보내지지 않은 트레이스

  1. OTLP 엔드포인트에 도달 가능한지 확인
  2. 컬렉터가 실행 중이며 연결을 수락하는지 확인
  3. 애플리케이션 종료 전에 shutdown_telemetry()을 호출하여 보류 중인 스팬을 플러시
  4. 네트워크/방화벽 문제 확인

스팬에 컨텍스트 누락

  1. async 함수에서 #[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)을 사용합니다.
  • 콜백 - 콜백에 텔레메트리 추가
  • Tools - 사용자 정의 Tools 계측
  • 배포 - 프로덕션 텔레메트리 설정

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