텔레메트리

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

개요

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

  • 구조화된 로깅: 문맥 정보가 풍부하고 쿼리 가능한 로그
  • 분산 추적: 에이전트 계층 및 서비스 경계를 넘어 요청 추적
  • OpenTelemetry 통합: 추적을 관측 가능성 백엔드(Jaeger, Datadog, Honeycomb 등)로 내보내기
  • 자동 컨텍스트 전파: Session, user, invocation ID가 모든 작업에서 전달
  • 사전 구성된 스팬: 일반적인 ADK 작업을 위한 헬퍼 함수

빠른 시작

기본 콘솔 로깅

개발 및 간단한 배포를 위해 콘솔 로깅을 초기화합니다:

use adk_telemetry::init_telemetry;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 텔레메트리를 서비스 이름으로 초기화합니다.
    init_telemetry("my-agent-service")?;
    
    // 여기에 에이전트 코드를 넣습니다.
    
    Ok(())
}

이는 합리적인 기본값으로 stdout에 구조화된 로깅을 구성합니다.

OpenTelemetry 내보내기

분산 추적이 포함된 프로덕션 배포의 경우:

use adk_telemetry::init_with_otlp;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // OTLP exporter로 초기화합니다.
    init_with_otlp("my-agent-service", "http://localhost:4317")?;
    
    // 여기에 에이전트 코드를 넣습니다.
    
    // 종료하기 전에 추적을 플러시합니다.
    adk_telemetry::shutdown_telemetry();
    Ok(())
}

이는 추적 및 메트릭을 OpenTelemetry collector 엔드포인트로 내보냅니다.

로그 레벨

RUST_LOG 환경 변수를 사용하여 로깅 상세도를 제어합니다:

레벨설명사용 사례
error오류만프로덕션 (최소)
warn경고 및 오류프로덕션 (기본)
info정보 메시지개발, 스테이징
debug상세 디버깅 정보로컬 개발
trace매우 상세한 추적심층 디버깅

로그 레벨 설정

# 전역 로그 레벨 설정
export RUST_LOG=info

# 모듈별 로그 레벨 설정
export RUST_LOG=adk_agent=debug,adk_model=info

# 전역 및 모듈별 레벨 결합
export RUST_LOG=warn,adk_agent=debug

RUST_LOG가 설정되지 않으면 텔레메트리 시스템은 기본적으로 info 레벨을 사용합니다.

로깅 매크로

표준 tracing 매크로를 사용하여 로깅합니다:

use adk_telemetry::{trace, debug, info, warn, error};

// 정보 로깅
info!("Agent started successfully");

// 필드가 있는 구조화된 로깅
info!(
    agent.name = "my_agent",
    session.id = "sess-123",
    "Processing user request"
);

// 디버그 로깅
debug!(user_input = ?input, "Received input");

// 경고 및 오류 로깅
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
}

// user_id와 message를 필드로 사용하여 "process_request"라는 이름의 스팬을 생성합니다.

민감한 매개변수 건너뛰기

트레이스에서 민감한 데이터를 제외합니다.

use adk_telemetry::instrument;

#[instrument(skip(api_key))]
async fn call_external_api(api_key: &str, query: &str) {
    // api_key는 트레이스에 나타나지 않습니다.
}

사용자 정의 스팬 이름

use adk_telemetry::instrument;

#[instrument(name = "external_api_call")]
async fn fetch_data(url: &str) {
    // 스팬 이름이 "fetch_data" 대신 "external_api_call"로 지정됩니다.
}

사전 구성된 스팬

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

Agent 실행 스팬

use adk_telemetry::agent_run_span;

let span = agent_run_span("my_agent", "inv-123");
let _enter = span.enter();

// 여기에 Agent 실행 코드가 위치합니다.
// 이 스코프 내의 모든 로그는 스팬 컨텍스트를 상속합니다.

Model 호출 스팬

use adk_telemetry::model_call_span;

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

// 여기에 Model API 호출이 위치합니다.

Tool 실행 스팬

use adk_telemetry::tool_execute_span;

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

// 여기에 Tool 실행 코드가 위치합니다.

콜백 스팬

use adk_telemetry::callback_span;

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

// 여기에 콜백 로직이 위치합니다.

컨텍스트 속성 추가

현재 스팬에 사용자 및 세션 컨텍스트를 추가합니다.

use adk_telemetry::add_context_attributes;

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

수동 스팬 생성

사용자 정의 계측의 경우 스팬을 수동으로 생성합니다.

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");
// 여기에 작업 코드가 위치합니다.

스팬 속성

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

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;

// 로컬 Jaeger (기본 OTLP 포트)
init_with_otlp("my-service", "http://localhost:4317")?;

// 클라우드 공급자 엔드포인트
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

# 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 작업은 구조화된 로그와 trace를 자동으로 방출합니다.

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에 관측 기능을 추가합니다:

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

성능 고려 사항

샘플링

높은 처리량 시스템의 경우 trace 샘플링을 고려하십시오:

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

비동기 Span

올바른 span context를 보장하려면 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. 비동기 함수에 #[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)

관련 항목

  • 콜백 - 콜백에 텔레메트리 추가
  • 도구 - 사용자 정의 도구 계측
  • 배포 - 프로덕션 텔레메트리 설정

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