القياس عن بعد (Telemetry)

يوفر ADK-Rust قابلية مراقبة بجودة الإنتاج من خلال الكريت adk-telemetry، الذي يدمج التسجيل المنظم (structured logging) والتتبع الموزع (distributed tracing) باستخدام نظام tracing البيئي و OpenTelemetry.

نظرة عامة

يمكّن نظام القياس عن بعد ما يلي:

  • التسجيل المنظم: سجلات غنية وقابلة للاستعلام مع معلومات سياقية
  • التتبع الموزع: تتبع الطلبات عبر تسلسلات Agent الهرمية وحدود الخدمة
  • تكامل OpenTelemetry: تصدير التتبعات إلى الواجهات الخلفية للمراقبة (مثل Jaeger و Datadog و Honeycomb وغيرها)
  • النشر التلقائي للسياق: تتدفق معرفات Session والمستخدم (user) والاستدعاء (invocation) عبر جميع العمليات
  • النطاقات (Spans) المكونة مسبقًا: دوال مساعدة لعمليات ADK الشائعة

البدء السريع

التسجيل الأساسي للطرفية (Console Logging)

للتطوير والعمليات البسيطة، قم بتهيئة تسجيل الطرفية:

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.

مستويات السجل

تحكم في تفاصيل التسجيل باستخدام متغير البيئة RUST_LOG:

المستوىالوصفحالة الاستخدام
errorالأخطاء فقطالإنتاج (الحد الأدنى)
warnالتحذيرات والأخطاءالإنتاج (افتراضي)
infoرسائل إعلاميةالتطوير، الاختبار المرحلي (staging)
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

يستخدم نظام القياس عن بعد المستوى info افتراضيًا إذا لم يتم تعيين RUST_LOG.

وحدات ماكرو التسجيل

استخدم وحدات ماكرو 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 backend) الخاصة بك.

الأدوات

الأدوات التلقائية

استخدم السمة #[instrument] لإنشاء نطاقات (spans) تلقائيًا للدوال:

use adk_telemetry::{instrument, info};

#[instrument]
async fn process_request(user_id: &str, message: &str) {
    info!("Processing request");
    // Function logic here
}

// ينشئ نطاقًا باسم "process_request" مع user_id و message كحقول

تخطي المعلمات الحساسة

استبعاد البيانات الحساسة من التتبعات:

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) {
    // سيتم تسمية النطاق "external_api_call" بدلاً من "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 هنا
// جميع السجلات ضمن هذا النطاق ترث سياق النطاق

نطاق استدعاء 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 هنا

نطاق رد الاتصال (Callback)

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

تشغيل مجمّع محلي

للتطوير، قم بتشغيل Jaeger مع دعم OTLP:

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

# عرض التتبعات على http://localhost:16686

تصور التتبع

بمجرد التكوين، تظهر التتبعات في الواجهة الخلفية للمراقبة لديك لعرض:

  • التسلسل الهرمي لتنفيذ Agent
  • أزمنة استجابة استدعاء Model
  • توقيت تنفيذ Tool
  • انتشار الأخطاء
  • تدفق السياق (معرف المستخدم، معرف الجلسة، إلخ.)

التكامل مع ADK

تُصدر مكونات adk-rust تلقائيًا بيانات القياس عن بعد (telemetry) عند تهيئة نظام القياس عن بعد:

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 تلقائيًا سجلات وتتبعات منظمة.

القياس عن بعد المخصص في Tools

أضف القياس عن بعد إلى Tools المخصصة:

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

أضف قابلية المراقبة (observability) إلى 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()?;

اعتبارات الأداء

أخذ العينات (Sampling)

لأنظمة ذات الإنتاجية العالية، ضع في اعتبارك أخذ عينات التتبع (trace sampling):

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

Async Spans

استخدم دائمًا #[instrument] على دوال async لضمان سياق Span الصحيح:

use adk_telemetry::instrument;

// ✅ صحيح - سياق Span محفوظ عبر نقاط await
#[instrument]
async fn async_operation() {
    tokio::time::sleep(Duration::from_secs(1)).await;
}

// ❌ غير صحيح - قد يفقد الـ Span اليدوي السياق
async fn manual_span_operation() {
    let span = tracing::info_span!("operation");
    let _enter = span.enter();
    tokio::time::sleep(Duration::from_secs(1)).await;
    // قد يفقد السياق بعد await
}

مستوى السجل (Log Level) في بيئة الإنتاج

استخدم مستوى info أو warn في بيئة الإنتاج لتقليل الحمل الزائد (overhead):

export RUST_LOG=warn,my_app=info

استكشاف الأخطاء وإصلاحها

عدم ظهور السجلات

  1. تحقق من تعيين متغير البيئة RUST_LOG
  2. تأكد من استدعاء init_telemetry() قبل أي تسجيل
  3. تحقق من تهيئة التتبع مرة واحدة فقط (يستخدم Once داخليًا)

عدم تصدير التتبعات

  1. تحقق من إمكانية الوصول إلى نقطة نهاية OTLP
  2. تحقق من أن المجمع يعمل ويقبل الاتصالات
  3. استدعاء shutdown_telemetry() قبل إنهاء التطبيق لمسح الفترات المعلقة
  4. تحقق من وجود مشكلات في الشبكة/جدار الحماية

سياق مفقود في Spans

  1. استخدم #[instrument] على الوظائف غير المتزامنة
  2. تأكد من الدخول إلى الفترات باستخدام let _enter = span.enter()
  3. احتفظ بحارس _enter في النطاق طوال مدة العملية

أفضل الممارسات

  1. التهيئة المبكرة: استدعاء init_telemetry() في بداية main()
  2. استخدام الحقول المنظمة: أضف السياق باستخدام أزواج المفتاح-القيمة، وليس استيفاء السلاسل النصية
  3. أدوات الوظائف غير المتزامنة: استخدم دائمًا #[instrument] على الوظائف غير المتزامنة
  4. مسح عند الخروج: استدعاء shutdown_telemetry() قبل إنهاء التطبيق
  5. مستويات السجل المناسبة: استخدم info للأحداث الهامة، وdebug للتفاصيل
  6. تجنب البيانات الحساسة: تجاوز المعلمات الحساسة باستخدام #[instrument(skip(...))]
  7. تسمية متسقة: استخدم أسماء حقول متسقة (على سبيل المثال، user.id، session.id)

ذات صلة

  • Callbacks - أضف التتبع إلى Callbacks
  • Tools - أدوات مخصصة لأدوات التتبع
  • Deployment - إعداد تتبع الإنتاج

السابق: ← Events | التالي: Launcher →