Наблюдаемость позволяет понять систему извне, задавая вопросы о ней, при этом не зная ее внутреннего устройства. Кроме того, она позволяет легко устранять неполадки и решать новые проблемы, то есть «неизвестные неизвестные». Она также поможет вам ответить на вопрос «почему что-то происходит?».
Чтобы задать эти вопросы о вашей системе, приложение должно содержать полный набор инструментов. То есть код приложения должен отправлять сигналы, такие как трассировки, метрики и журналы. Приложение правильно инструментировано, когда разработчикам не нужно добавлять дополнительные инструменты для устранения неполадок, потому что у них есть вся необходимая информация.
Терминология
Событие журнала (Log event/message) - событие, произошедшее в конкретный момент времени;
Промежуток (Span record) - запись потока исполнения в системе за период времени. Он также выполняет функции контекста для событий журнала и родителя для под-промежутков;
Трасса (trace) - полная запись потока исполнения в системе от получения запроса до отправки ответа. Это по сути промежуток-родитель, или корневой промежуток;
Подписчик (subscriber) - реализует способ сбора данных трассы, например, запись их в стандартный вывод;
Контекст трассировки (Tracing Context): набор значений, которые будут передаваться между службами
usetracing::info;fnmain(){// Установка глобального сборщика по конфигурации
tracing_subscriber::fmt::init();letnumber_of_yaks=3;// новое событие, вне промежутков
info!(number_of_yaks,"preparing to shave yaks");}
Ручная инициализация свойств подписчика для форматирования лога:
fnsetup_tracing(){letsubscriber=tracing_subscriber::fmt().json()// нужно cargo add tracing-subscriber -F json
.with_max_level(tracing::Level::TRACE)// МАХ уровень логирования
.compact()// компактный лог
.with_file(true)// показывать файл-исходник
.with_line_number(true)// показать номера строк кода
.with_thread_ids(true)// показать ID потока с событием
.with_target(false)// не показывать цель (модуль) события
.finish();tracing::subscriber::set_global_default(subscriber).unwrap();tracing::info!("Starting up");tracing::warn!("Are you sure this is a good idea?");tracing::error!("This is an error!");}
Макрос #[instrument] автоматически создаёт промежутки (spans) для функций, а подписчик (subscriber) настроен выводить промежутки в stdout.
Трассировка потоков в асинхронном режиме
usetracing_subscriber::fmt::format::FmtSpan;#[tracing::instrument]// инструмент следит за временем работы
asyncfnhello(){tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;}#[tokio::main]// cargo add tokio -F time,macros,rt-multi-thread
asyncfnmain()-> anyhow::Result<()>{letsubscriber=tracing_subscriber::fmt().json().compact().with_file(true).with_line_number(true).with_thread_ids(true).with_target(false).with_span_events(FmtSpan::ENTER|FmtSpan::CLOSE)// вход и выход
.finish();// потока отслеживать
tracing::subscriber::set_global_default(subscriber).unwrap();hello().await;Ok(())}
В итоге получаем лог работы потока с временем задержки:
2024-12-24T14:30:17.378906Z INFO ThreadId(01) hello: src/main.rs:3: {"message":"enter"}{}2024-12-24T14:30:18.383596Z INFO ThreadId(01) hello: src/main.rs:3: {"message":"enter"}{}2024-12-24T14:30:18.383653Z INFO ThreadId(01) hello: src/main.rs:3: {"message":"enter"}{}2024-12-24T14:30:18.383675Z INFO ThreadId(01) hello: src/main.rs:3: {"message":"close","time.busy":"179µs","time.idle":"1.00s"}{}
Для записи журнала применяется отдельный модуль tracing-appender:
cargo add tracing-appender
У него много функций не только записи в облачные службы типа Datadog, но и создание журнала с дозаписью (минутной, часовой, дневной), а также запись как неблокирующее действие во время многопоточного исполнения.
Пример инициализации неблокирующей записи журнала в файл (лучше всего как JSON), одновременно вывод на экран и организация часового журнала с дозаписью (rolling):
usetracing::{instrument,warn};// тянем std::io::Write признак в режиме неблокирования
usetracing_subscriber::fmt::writer::MakeWriterExt;fnsetup_tracing(){// инициализация файла с дозаписью
letlogfile=tracing_appender::rolling::hourly("/some/directory","app-log");// уровень записи при логировании = INFO
letstdout=std::io::stdout.with_max_level(tracing::Level::INFO);letsubscriber=tracing_subscriber::fmt().with_max_level(tracing::Level::TRACE).json().compact().with_file(true).with_line_number(true).with_thread_ids(true).with_target(false).with_writer(stdout.and(logfile))// обязательно указать запись тут
.finish();// в файл и в stdout консоль
tracing::subscriber::set_global_default(subscriber).unwrap();}#[instrument]fnsync_tracing(){warn!("event 1");sync_tracing_sub();}#[instrument]fnsync_tracing_sub(){warn!("event 2");}fnmain(){setup_tracing();sync_tracing();}
Инициализация единого трассировщика на проекте
В составе Workspace трассировщик следует инициализировать единожды и далее использовать во всём проекте.
Для этого на верхнем уровне Workspace в Cargo.toml вписываем зависимость с применением по всему Workspace:
usecommon_log::setup_tracing;usetracing::{debug,error,info,warn};fnmain(){setup_tracing();// инициализация логгера из библиотеки common-log
info!("Logger initialized. App started.");// вызов логгера
}
Лог будет сохранён в папке /log/ в бинарном крейте, так как из него делается вызов инициализации.
Использование в тестах
#[cfg(test)]modtests{usesuper::*;usecommon_logging::setup_tracking;#[test]fntest_something(){init_test_logger();// ваш тест
}}
Настройка через переменные окружения
# Установка уровня логированияRUST_LOG=debug cargo run
RUST_LOG=my_library=debug,info cargo run
# Для конкретного крейтаRUST_LOG=my_library=debug cargo run