Мониторинг Rust приложений: метрики и алерты на продвинутом уровне

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

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

Начать курс

Введение: Почему мониторинг Rust-приложений — это вызов и возможность

Rust славится своей производительностью и безопасностью памяти. Однако, когда ваше приложение работает на грани возможностей железа (например, высоконагруженный WebSocket-сервер или парсер данных), стандартные метрики CPU и RAM часто оказываются недостаточными. На продвинутом уровне мониторинг Rust-приложений требует не просто сбора данных, а создания точной системы алертов, которая предскажет сбой до того, как он произойдет.

В этой статье мы рассмотрим, как построить production-ready систему мониторинга для Rust: от кастомных метрик с использованием prometheus crate до настройки алертов через Alertmanager и Grafana.

1. Продвинутые метрики: не только HTTP-запросы

Базовый мониторинг (количество запросов, задержки) — это минимум. На продвинутом уровне мы собираем метрики, которые отражают внутреннее состояние приложения: размер очередей, количество активных задач в Tokio, состояние пула соединений.

1.1. Кастомные метрики с типом Histogram и Gauge

Используем крейт prometheus. Рассмотрим пример сбора метрик для пула соединений к базе данных:

use prometheus::{HistogramOpts, HistogramVec, Gauge, register_histogram_vec, register_gauge};
use lazy_static::lazy_static;

lazy_static! {
    static ref DB_CONNECTION_POOL: Gauge = register_gauge!(
        "db_connection_pool_size",
        "Текущее количество активных соединений в пуле"
    ).unwrap();

    static ref DB_QUERY_DURATION: HistogramVec = register_histogram_vec!(
        HistogramOpts::new("db_query_duration_seconds", "Время выполнения запроса к БД"),
        &["query_type", "status"]
    ).unwrap();
}

// Использование в коде
fn execute_query(query_type: &str) -> Result<(), String> {
    DB_CONNECTION_POOL.inc();
    let start = std::time::Instant::now();
    // ... выполнение запроса ...
    let duration = start.elapsed().as_secs_f64();
    DB_QUERY_DURATION.with_label_values(&[query_type, "success"]).observe(duration);
    DB_CONNECTION_POOL.dec();
    Ok(())
}

Важно: Используйте HistogramVec для категоризации запросов по типу (SELECT, INSERT) и статусу (success, error). Это позволит строить тепловые карты в Grafana.

1.2. Метрики изнутри Tokio runtime

Для асинхронных приложений критично знать загруженность планировщика. Используем tokio-metrics:

use tokio_metrics::RuntimeMetrics;
use std::sync::Arc;

async fn monitor_runtime(metrics: Arc<RuntimeMetrics>) {
    loop {
        tokio::time::sleep(std::time::Duration::from_secs(5)).await;
        let num_alive_tasks = metrics.num_alive_tasks();
        let blocking_threads = metrics.num_blocking_threads();
        // Отправляем эти значения в Prometheus через Gauge
        println!("Активные задачи: {}, Блокирующие потоки: {}", num_alive_tasks, blocking_threads);
    }
}

Эти метрики помогают выявить утечки задач (когда num_alive_tasks постоянно растет) или избыточное использование block_in_place.

2. Архитектура сбора метрик: Push vs Pull

В Rust-экосистеме чаще используется Pull-модель (Prometheus scraper), но для короткоживущих процессов (CLI-утилиты, парсеры) удобен Push-подход через Pushgateway.

2.1. Pull-модель: HTTP-эндпоинт

Стандартный способ — запустить отдельный HTTP-сервер на порту 9090 (или другом) внутри вашего приложения:

use warp::Filter;
use prometheus::{Encoder, TextEncoder};

async fn metrics_handler() -> Result<impl warp::Reply, warp::Rejection> {
    let metric_families = prometheus::gather();
    let mut buffer = Vec::new();
    let encoder = TextEncoder::new();
    encoder.encode(&metric_families, &mut buffer).unwrap();
    Ok(warp::reply::with_header(buffer, "Content-Type", "text/plain; charset=utf-8"))
}

#[tokio::main]
async fn main() {
    let metrics_route = warp::path!("metrics").and_then(metrics_handler);
    warp::serve(metrics_route).run(([0, 0, 0, 0], 9090)).await;
}

2.2. Push-модель для batch-задач

Если ваше приложение запускается раз в час и завершается, используйте prometheus-pushgateway:

use prometheus_pushgateway::PushGateway;

fn push_metrics(job_name: &str) {
    let gateway = PushGateway::new("http://pushgateway:9091");
    let metric_families = prometheus::gather();
    gateway.push_add(metric_families, job_name, &std::collections::HashMap::new()).unwrap();
}

3. Настройка алертов: от порогов до аномалий

Алерты в Prometheus (Alertmanager) — это язык PromQL. Для Rust-приложений типичны следующие правила:

3.1. Алерт на высокую задержку запросов (p99)

Создайте файл alerts.yml:

groups:
  - name: rust_app_alerts
    rules:
      - alert: HighQueryLatency
        expr: histogram_quantile(0.99, rate(db_query_duration_seconds_bucket[5m])) > 2.0
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "P99 задержка запросов к БД превышает 2 секунды"
          description: "Текущее значение: {{ $value }} с"

3.2. Алерт на утечку задач Tokio

Если количество активных задач не уменьшается в течение 10 минут:

      - alert: TokioTaskLeak
        expr: avg_over_time(runtime_tasks_alive[10m]) > 1000
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "Подозрение на утечку задач Tokio"
          description: "Среднее количество задач за 10 мин: {{ $value }}"

3.3. Интеграция с Alertmanager

Настройте маршруты для отправки уведомлений в Telegram/Slack:

route:
  receiver: 'telegram'
  routes:
    - match:
        severity: critical
      receiver: 'telegram-critical'

receivers:                

Блоги

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