Мониторинг приложений Rust: библиотека tracing и сбор метрик
Мониторинг — ключевой элемент production-систем. Для приложений на Rust, известных своей производительностью и надёжностью, важно иметь лёгкий и эффективный инструмент для сбора телеметрии. Библиотека tracing стала де-факто стандартом для observability в экосистеме Rust. Она предоставляет структурированное логирование, спаны (span) для трассировки запросов и интеграцию с системами сбора метрик.
Что такое tracing?
tracing — это фреймворк для инструментации Rust-программ. В отличие от классического логирования, он работает на основе событий (events) и спанов (spans). Спаны представляют собой временные отрезки выполнения кода (например, обработка HTTP-запроса), а события — это точки внутри спанов (например, «база данных ответила за 10 мс»).
Основные возможности:
- Структурированное логирование — каждое событие содержит поля (ключ-значение), а не просто строку.
- Иерархия спанов — можно вкладывать спаны друг в друга, отслеживая полный путь выполнения.
- Асинхронная поддержка — отлично работает с tokio и async/await.
- Subscriber (подписчик) — можно подключать различные бэкенды: stdout, OpenTelemetry, Prometheus, файлы.
Установка и базовое использование
Добавьте в Cargo.toml:
[dependencies]
tracing = "0.1"
tracing-subscriber = "0.3"
Простой пример структурированного логирования:
use tracing::{info, warn, error};
use tracing_subscriber;
fn main() {
// Инициализируем subscriber для вывода в stdout
tracing_subscriber::fmt::init();
let user_id = 42;
let request_duration_ms = 150;
info!(user_id, duration = request_duration_ms, "Запрос обработан");
// Логгирование с уровнем warn
warn!(user_id, "Время ответа превышает порог");
// Логгирование с ошибкой
error!("Не удалось подключиться к БД");
}
Вывод будет содержать поля в формате JSON-подобной строки, что упрощает парсинг в системах мониторинга.
Спаны и трассировка
Спаны позволяют измерять время выполнения операций и связывать события в цепочку:
use tracing::{span, info, Level};
use tracing_subscriber;
fn process_request(user_id: u32) {
// Создаём span с контекстом
let span = span!(Level::INFO, "process_request", user_id);
let _guard = span.enter(); // активуем span
info!("Начало обработки");
std::thread::sleep(std::time::Duration::from_millis(50));
info!("Обработка завершена");
}
fn main() {
tracing_subscriber::fmt::init();
process_request(100);
}
В выводе появится вложенный контекст, показывающий, что события относятся к span process_request.
Сбор метрик с tracing и Prometheus
Для production-мониторинга часто требуется экспорт метрик в Prometheus. Используйте крейт tracing-appender для записи в файл или tracing-opentelemetry для интеграции с OpenTelemetry. Однако самый прямой путь — использовать библиотеку metrics вместе с tracing.
Пример интеграции с metrics-exporter-prometheus:
[dependencies]
metrics = "0.21"
metrics-exporter-prometheus = "0.12"
tracing = "0.1"
tokio = { version = "1", features = ["full"] }
Код для сбора метрик:
use metrics::{counter, gauge, histogram};
use metrics_exporter_prometheus::PrometheusBuilder;
use std::net::SocketAddr;
#[tokio::main]
async fn main() {
// Запускаем HTTP-сервер для Prometheus
let addr: SocketAddr = "0.0.0.0:9090".parse().unwrap();
let builder = PrometheusBuilder::new();
builder
.with_http_listener(addr)
.install()
.expect("Не удалось запустить экспортёр метрик");
// Симуляция работы
loop {
// Счётчик запросов
counter!("http_requests_total", "method" => "GET", "endpoint" => "/api").increment(1);
// Гистограмма времени ответа (в миллисекундах)
histogram!("request_duration_ms", "endpoint" => "/api").record(120.5);
// Gauge для текущего количества активных соединений
gauge!("active_connections").set(10.0);
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;