Observability в Rust: трейсинг, метрики и логи для надежных систем

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Почему observability — это суперсила для Rust-разработчика

Представьте: ваш сервис на Rust работает в production, но внезапно latency взлетает до небес. Логи молчат, метрики показывают 200 OK, а пользователи жалуются. Знакомо? Это — классическая «чёрная коробка». Без observability в Rust вы слепы.

Observability (наблюдаемость) — это не просто «логи + метрики». Это способность задавать системе любые вопросы без необходимости писать новый код. В Rust, с его акцентом на производительность и надежность, observability становится обязательным ингредиентом для production-grade приложений.

В этой статье мы разберем три столпа observability в Rust: трейсинг, метрики и логи. Вы получите готовые примеры кода с tracing, OpenTelemetry и Prometheus, которые можно сразу внедрить в свой проект.

Трейсинг в Rust: путешествие запроса через систему

Трейсинг (tracing) — это история жизни одного запроса. Вместо разрозненных логов вы получаете связанный путь: от входа в API до запроса в базу данных и ответа клиенту. Для Rust лучший инструмент — крейт tracing от сообщества Tokio.

Настройка tracing в Rust-приложении

Начнем с базовой настройки. Добавьте в Cargo.toml:

[dependencies]
tracing = "0.1"
tracing-subscriber = "0.3"
tracing-opentelemetry = "0.23"
opentelemetry = "0.22"
opentelemetry-otlp = "0.15"

Теперь инициализируем трейсер в main.rs:

use tracing_subscriber::{fmt, prelude::*, EnvFilter};
use opentelemetry::{global, KeyValue};
use opentelemetry_otlp::WithExportConfig;

fn init_tracer() -> Result<(), Box<dyn std::error::Error>> {
    global::set_text_map_propagator(opentelemetry_jaeger::Propagator::new());
    
    let tracer = opentelemetry_otlp::new_pipeline()
        .tracing()
        .with_exporter(
            opentelemetry_otlp::new_exporter()
                .tonic()
                .with_endpoint("http://localhost:4317")
        )
        .with_trace_config(
            opentelemetry::sdk::trace::config()
                .with_resource(opentelemetry::sdk::Resource::new(vec![
                    KeyValue::new("service.name", "rust-observability-demo")
                ]))
        )
        .install_simple()?;

    // Связываем tracing с OpenTelemetry
    let telemetry = tracing_opentelemetry::layer().with_tracer(tracer);
    let subscriber = fmt::Subscriber::builder()
        .with_env_filter(EnvFilter::from_default_env())
        .finish()
        .with(telemetry);

    tracing::subscriber::set_global_default(subscriber)?;
    Ok(())
}

Создание спанов и трейсов

Теперь добавим трейсинг в бизнес-логику:

use tracing::{span, Span, Level, instrument};

#[instrument]
async fn handle_request(user_id: u64) -> Result<String, Box<dyn std::error::Error>> {
    let user_data = get_user_data(user_id).await?;
    let processed = process_data(&user_data).await?;
    Ok(processed)
}

#[instrument(skip(db_pool))]
async fn get_user_data(user_id: u64) -> Result<User, Box<dyn std::error::Error>> {
    // Имитация запроса к БД
    let span = Span::current();
    span.record("db.query", "SELECT * FROM users WHERE id = $1");
    
    tokio::time::sleep(std::time::Duration::from_millis(50)).await;
    Ok(User { id: user_id, name: "Alice".into() })
}

#[instrument]
async fn process_data(user: &User) -> Result<String, Box<dyn std::error::Error>> {
    let result = format!("Processed user: {}", user.name);
    tracing::info!(user_id = user.id, "Data processed successfully");
    Ok(result)
}

Атрибут #[instrument] автоматически создает span для функции и записывает все аргументы. В Jaeger или Grafana Tempo вы увидите красивую вложенную структуру: handle_request → get_user_data → process_data.

Метрики в Rust: цифры, которые говорят правду

Метрики — это агрегированные числовые показатели: количество запросов, время ответа, использование памяти. Для метрик Rust стандарт де-факто — prometheus и metrics крейты.

Интеграция Prometheus с актами

[dependencies]
metrics = "0.22"
metrics-exporter-prometheus = "0.14"
actix-web = "4"

Настраиваем экспорт метрик:

use actix_web::{web, App, HttpServer, HttpResponse, middleware::from_fn};
use metrics::{counter, histogram, gauge};
use metrics_exporter_prometheus::PrometheusBuilder;
use std::sync::Arc;

async fn metrics_handler() -> HttpResponse {
    let metrics_data = PROMETHEUS_HANDLER
        .lock()
        .unwrap()
        .render()
        .to_string();
    HttpResponse::Ok()
        .content_type("text/plain; charset=utf-8")
        .body(metrics_data)
}

lazy_static::lazy_static! {
    static ref PROMETHEUS_HANDLER: Arc<std::sync::Mutex<metrics_exporter_prometheus::PrometheusHandle>> = {
        let builder = PrometheusBuilder::new();
        let handle = builder
            .install_recorder()
            .expect("Failed to install Prometheus recorder");
        Arc::new(std::sync::Mutex::new(handle))
    };
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .route("/metrics", web::get().to(metrics_handler))
            .route("/api/data", web::post().to(api_handler))
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

async fn api_handler(body: web::Json<serde_json::Value>) -> HttpResponse {
    let start = std::time::Instant::now();
    
    counter!("api_requests_total", "endpoint" => "/api/data", "method" => "POST");
    
    // Бизнес-логика
    tokio::time::sleep(std::time::Duration::from_millis(100)).await;
    
    let duration = start.elapsed().as_secs_f64();
    histogram!("api_request_duration_seconds", duration, "endpoint" => "/api/data");
    
    gauge!("active_requests", 1.0);
    
    HttpResponse::Ok().json(serde_json::json!({"status": "ok"}))
}

После запуска перейдите на http://localhost:8080/metrics — вы увидите:

  • api_requests_total — счетчик запросов
  • api_request_duration_seconds — гистограмма времени ответа
  • active_requests — текущее количество активных запросов

Логирование в Rust: структурированный подход

Логирование в Rust с помощью tracing — это не просто println!. Ст

Блоги

Рекомендаций по книгам