Tracing для Rust: полное руководство с примерами

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

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

Начать курс

Что такое Tracing и зачем он нужен в Rust?

Библиотека Tracing — это мощный фреймворк для структурированного логирования и инструментирования (трейсинга) приложений на языке Rust. В отличие от простых логгеров, Tracing предоставляет концепцию спанов (spans) и событий (events), которые позволяют отслеживать выполнение кода в контексте асинхронных операций, распределенных систем и сложных вложенных вызовов.

Основная цель Tracing — дать разработчику полную картину того, что происходит внутри программы: какие функции вызываются, сколько времени они выполняются, какие ошибки возникают и как они связаны между собой. Это незаменимый инструмент для отладки, профилирования и мониторинга production-систем.

Библиотека является частью экосистемы Tokio и активно используется в таких проектах, как actix-web, axum, hyper и многих других.

Установка Tracing

Для добавления Tracing в ваш проект на Rust, откройте файл Cargo.toml и добавьте следующие зависимости:

[dependencies]
tracing = "0.1"
tracing-subscriber = "0.3"  # Для вывода логов в консоль
tracing-appender = "0.2"    # Для записи логов в файл (опционально)

После этого выполните команду cargo build, чтобы скачать и скомпилировать библиотеки.

Быстрый старт: первый пример

Давайте напишем простейшую программу, которая логирует сообщение с использованием Tracing:

use tracing::{info, Level};
use tracing_subscriber::FmtSubscriber;

fn main() {
    // Инициализируем подписчика (subscriber) для вывода логов в stdout
    let subscriber = FmtSubscriber::builder()
        .with_max_level(Level::INFO)
        .finish();
    tracing::subscriber::set_global_default(subscriber)
        .expect("Не удалось установить глобальный subscriber");

    // Логируем простое сообщение
    info!("Приложение запущено!");

    // Вызов функции с трейсингом
    process_data("example.txt");
}

fn process_data(filename: &str) {
    // Создаем спан (span) для отслеживания функции
    let span = tracing::span!(Level::INFO, "process_data", filename = filename);
    let _guard = span.enter(); // Входим в спан

    info!("Начинаем обработку файла");
    // ... какая-то работа ...
    std::thread::sleep(std::time::Duration::from_millis(100));
    info!("Обработка завершена");
}

При запуске вы увидите структурированный вывод с временными метками, уровнями и именами спанов.

Основные методы и функции Tracing

Ниже представлены 10 ключевых функций и макросов библиотеки Tracing с подробным описанием и примерами.

1. tracing::span! — Создание спана

Что делает: Создает новый спан — логическую единицу работы, которая может быть вложенной. Спан имеет имя, уровень и набор полей.

Параметры:

  • Level — уровень логирования (ERROR, WARN, INFO, DEBUG, TRACE)
  • target (опционально) — строка, идентифицирующая модуль
  • field = value — произвольные поля для контекста

Пример:

use tracing::Level;

fn handle_request(id: u64) {
    let span = tracing::span!(Level::INFO, "handle_request", request_id = id);
    let _guard = span.enter();
    // Код внутри спана
    tracing::info!("Обрабатываем запрос");
}

2. tracing::info! — Логирование на уровне INFO

Что делает: Записывает событие с уровнем INFO. Событие — это единичное сообщение, которое может быть связано с текущим спаном.

Параметры:

  • Строка форматирования (как в format!)
  • Аргументы для форматирования
  • Поля через field = value

Пример:

let user = "Alice";
let age = 30;
tracing::info!(user, age, "Пользователь вошел в систему");

3. tracing::error! — Логирование ошибок

Что делает: Записывает событие с уровнем ERROR. Обычно используется для критических ошибок, требующих внимания.

Параметры: Аналогично info!, но с уровнем ERROR.

Пример:

let result: Result<i32, String> = Err("Файл не найден".to_string());
if let Err(e) = result {
    tracing::error!(error = %e, "Операция не удалась");
}

4. tracing::warn! — Предупреждения

Что делает: Записывает событие с уровнем WARN для ситуаций, которые не являются ошибками, но требуют внимания.

Пример:

let disk_space = 0.1;
if disk_space < 0.2 {
    tracing::warn!(disk_space, "Мало места на диске");
}

5. tracing::debug! — Отладочные сообщения

Что делает: Записывает событие с уровнем DEBUG. Используется для детальной отладки и обычно отключается в production.

Пример:

for i in 0..10 {
    tracing::debug!(iteration = i, "Обработка элемента");
}

6. tracing::trace! — Самый детальный уровень

Что делает: Записывает событие с уровнем TRACE. Используется для максимально подробного логирования, часто в горячих циклах.

Пример:

fn compute(x: f64) -> f64 {
    tracing::trace!(x, "Вычисляем квадрат");
    x * x
}

7. tracing::event! — Создание события с произвольным уровнем

Что делает: Позволяет создать событие с любым уровнем, указанным явно, а не через макрос-сокращение.

Параметры:

  • Level — уровень события
  • Строка форматирования и поля

Пример:

use tracing::Level;
tracing::event!(Level::WARN, "Это предупреждение через event!");

8. tracing::field::display и tracing::field::debug — Форматирование полей

Что делает: Указывают, как форматировать значение поля: через Display (%) или Debug (?).

Пример:

let data = vec![1, 2, 3];
tracing::info!(
    data = tracing::field::debug(&data),
    "Вектор данных"
);

9. tracing::Span::current() — Получение текущего спана

Что делает: Возвращает ссылку на текущий активный спан (если есть). Полезно для передачи контекста между функциями.

Пример:

fn inner_function() {
    let current_span = tracing::Span::current();
    tracing::info!(parent_span = ?current_span, "Внутренняя функция");
}

10. tracing::subscriber::set_global_default() — Установка глобального подписчика

Что делает: Устанавливает глобальный subscriber, который будет обрабатывать все события и спаны в приложении. Должен быть вызван один раз в начале программы.

Параметры: Объект, реализующий трейт Subscriber (например, FmtSubscriber).

Пример:

use tracing_subscriber::FmtSubscriber;

let subscriber = FmtSubscriber::builder()
    .with_max_level(tracing::Level::TRACE)
    .finish();
tracing::subscriber::set_global_default(subscriber)
    .expect("Ошибка установки subscriber");

Реальный практический пример: HTTP-сервер с трейсингом

Рассмотрим пример простого HTTP-сервера на базе axum, который использует Tracing для логирования всех входящих запросов и их обработки.

use axum::{
    routing::get,
    Router,
    response::Json,
    extract::Path,
};
use serde_json::json;
use std::net::SocketAddr;
use tracing::{info, span, Level};
use tracing_subscriber::FmtSubscriber;

#[tokio::main]
async fn main() {
    // Инициализируем логирование
    let subscriber = FmtSubscriber::builder()
        .with_max_level(Level::INFO)
        .finish();
    tracing::subscriber::set_global_default(subscriber)
        .expect("Не удалось установить subscriber");

    // Создаем маршруты
    let app = Router::new()
        .route("/user/{id}", get(get_user));

    // Запускаем сервер
    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
    info!("Сервер запущен на {}", addr);
    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();
}

async fn get_user(Path(id): Path<u64>) -> Json<serde_json::Value> {
    // Создаем спан для этого запроса
    let span = span!(Level::INFO, "get_user", user_id = id);
    let _guard = span.enter();

    info!("Получен запрос на получение пользователя");

    // Имитация задержки
    tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;

    let response = json!({
        "id": id,
        "name": format!("User {}", id),
        "email": format!("user{}@example.com", id)
    });

    info!(response = %response, "Ответ отправлен");
    Json(response)
}

При запуске этого сервера и выполнении запроса curl http://localhost:3000/user/42 вы увидите в консоли структурированные логи с информацией о спане get_user, времени выполнения и контексте запроса.

Советы и типичные ошибки

Советы:

  • Используйте спаны для группировки операций. Вместо того чтобы писать логи в каждой функции, создайте спан на уровне запроса или транзакции — это даст полную картину.
  • Выбирайте правильный уровень логирования. Используйте ERROR для критических проблем, WARN для предупреждений, INFO для важных событий, DEBUG и TRACE для отладки.
  • Добавляйте контекстные поля. Всегда добавляйте идентификаторы (user_id, request_id, order_id) в спаны и события — это упростит поиск проблем.
  • Не логируйте секреты. Никогда не передавайте пароли, токены или другую чувствительную информацию в поля логов.
  • Используйте tracing-appender для production. В production-среде настройте запись логов в файл с ротацией, а не только в stdout.

Типичные ошибки:

  • Забыли установить глобальный subscriber. Если не вызвать set_global_default, все макросы Tracing будут игнорироваться.
  • Потеря контекста спана в асинхронном коде. При использовании tokio::spawn или других асинхронных конструкций спан может "потеряться". Используйте

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