텔레메트리
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
문제 해결
로그가 나타나지 않음
RUST_LOG환경 변수가 설정되어 있는지 확인- 로깅 전에
init_telemetry()가 호출되었는지 확인 - 텔레메트리가 한 번만 초기화되었는지 확인 (내부적으로
Once를 사용)
추적이 내보내지지 않음
- OTLP 엔드포인트에 연결할 수 있는지 확인
- 컬렉터가 실행 중이고 연결을 수락하는지 확인
- 애플리케이션 종료 전에
shutdown_telemetry()를 호출하여 보류 중인 스팬을 플러시 - 네트워크/방화벽 문제 확인
스팬에 컨텍스트가 없음
- 비동기 함수에
#[instrument]사용 let _enter = span.enter()를 사용하여 스팬이 진입되었는지 확인- 작업이 진행되는 동안
_enter가드를 스코프 내에 유지
모범 사례
- 초기화는 일찍:
main()시작 시init_telemetry()호출 - 구조화된 필드 사용: 문자열 보간 대신 키-값 쌍으로 컨텍스트 추가
- 비동기 함수 계측: 비동기 함수에는 항상
#[instrument]사용 - 종료 시 플러시: 애플리케이션 종료 전에
shutdown_telemetry()호출 - 적절한 로그 레벨: 중요한 이벤트에는
info, 세부 정보에는debug사용 - 민감한 데이터 피하기:
#[instrument(skip(...))]로 민감한 매개변수 건너뛰기 - 일관된 명명: 일관된 필드 이름 사용 (예:
user.id,session.id)