Введение: почему логирование критично для Rust-приложений
Логирование — это не просто запись событий в файл. В production-среде качественные логи позволяют быстро диагностировать ошибки, отслеживать производительность и анализировать поведение системы. Rust, будучи языком системного программирования, предлагает гибкие и производительные инструменты для этой задачи. В этой статье мы разберём лучшие практики логирования в Rust: от выбора крейта до настройки структурированных логов и ротации.
1. Основы: крейты log и env_logger
Экосистема Rust предлагает модульный подход. Крейт log предоставляет абстракцию (фасад), а конкретную реализацию (бэкенд) подключает отдельный крейт, например env_logger или pretty_env_logger.
Установка и настройка
Добавьте зависимости в Cargo.toml:
[dependencies]
log = "0.4"
env_logger = "0.10"
Базовый пример
use log::{info, warn, error};
fn main() {
env_logger::init();
info!("Приложение запущено");
let user_id = 42;
warn!("Пользователь {} не найден в кэше", user_id);
error!("Ошибка подключения к базе данных: {:?}", "timeout");
}
Важно: По умолчанию логи не выводятся, пока вы не установите переменную окружения RUST_LOG. Запустите программу так:
RUST_LOG=info cargo run
Уровни логирования
Крейт log поддерживает пять стандартных уровней (от наиболее приоритетного к наименее):
error— критические ошибки, требующие немедленного вмешательства.warn— предупреждения о потенциальных проблемах.info— общая информация о ходе выполнения.debug— отладочная информация для разработчиков.trace— максимально детализированные события (часто используются в циклах).
Фильтрация по модулям: RUST_LOG=my_crate=debug,other_crate=info.
2. Продвинутое логирование: крейт tracing
Для сложных приложений (веб-серверы, микросервисы) рекомендуется использовать крейт tracing. Он предоставляет концепцию span (интервалов времени) и структурированное логирование с контекстом.
Установка tracing
[dependencies]
tracing = "0.1"
tracing-subscriber = "0.3"
Пример с span
use tracing::{info, instrument, span, Level};
use tracing_subscriber;
fn main() {
tracing_subscriber::fmt::init();
let parent_span = span!(Level::INFO, "обработка_запроса", request_id = 123);
let _guard = parent_span.enter();
process_data();
}
#[instrument]
fn process_data() {
info!("Обработка данных начата");
std::thread::sleep(std::time::Duration::from_millis(50));
info!("Обработка данных завершена");
}
Атрибут #[instrument] автоматически создаёт span для функции и записывает время выполнения. Вывод будет содержать вложенные временные метки, что незаменимо для профилирования.
3. Структурированные логи (JSON) и ротация
В production-среде логи часто собираются централизованно (ELK, Loki, Grafana). Для этого удобно выводить логи в формате JSON.
Вывод в JSON с tracing-subscriber
use tracing_subscriber::fmt::format::Json;
use tracing_subscriber::prelude::*;
fn main() {
let format = Json::new()
.with_timer(tracing_subscriber::fmt::time::UtcTime::rfc3339())
.with_target(true)
.with_level(true);
tracing_subscriber::fmt()
.event_format(format)
.init();
tracing::info!("Сервер запущен на порту 8080");
}
Теперь логи будут выглядеть так:
{"timestamp":"2025-04-10T12:00:00Z","level":"INFO","target":"my_app","message":"Сервер запущен на порту 8080"}
Ротация логов с крейтом log4rs
Для записи в файлы с ротацией используйте log4rs:
[dependencies]
log4rs = "1.2"
Конфигурация в коде:
use log4rs::{
append::rolling_file::{
RollingFileAppender,
policy::compound::{
CompoundPolicy,
trigger::size::SizeTrigger,
roller::fixed_window::FixedWindowRoller,
},
},
config::{Appender, Config, Root},
encode::pattern::PatternEncoder,
};
fn main() {
let window_roller = FixedWindowRoller::builder()
.build("logs/app_{}.log", 5)
.unwrap();
let size_trigger = SizeTrigger::new(10_000_000); // 10 MB
let policy = CompoundPolicy::new(Box::new(size_trigger), Box::new(window_roller));
let file_appender = RollingFileAppender::builder()
.encoder(Box::new(PatternEncoder::new("{d(%Y-%m-%d %H:%M:%S)} {l} - {m}\n")))
.build("logs/app.log", Box::new(policy))
.unwrap();
let config = Config::builder()
.appender(Appender::builder().build("file", Box::new(file_appender)))
.build(Root::builder().appender("file").build(log::LevelFilter::Info))
.unwrap();
log4rs::init_config(config).unwrap();
log::info!("Логирование с ротацией настроено");
}
Этот код создаёт файлы app.log, app_1.log... app_5.log и ротирует их при достижении 10 МБ.
4. Лучшие практики для production
Избегайте синхронного логирования в горячих путях
Запись в файл — блокирующая операция. В высоконагруженных системах используйте асинхронный бэкенд, например tracing-appender с NonBlocking.
Не логируйте секреты и чувствительные данные
Всегда очищайте пароли