Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Тема
Описание
Доп.

Логирование

Стандартные требования к логированию:

  • Обеспечить разный уровень логирования (Debug; Info; Warning; Error и т.д.)
  • Логировать HTTP/HTTPS запросы
  • Дублировать консольный вывод в файлы на диске
  • Обеспечить самоочистку файлов логирования по времени и объёму

Когда нужно иметь логи

Как правильно писать логи (?)

два сценария, когда нужно иметь логи:

  1. что-то происходит и надо знать что
  2. что-то сломалось и нужно дополнительно активировать триггер
  • DEBUG — то, что будет читать только разработчик
  • INFO — то, что может почитать инженер поддержки, но не факт
  • WARNING — то, что читает инженер поддержки
  • ERROR — то, что обязательно читает инженер поддержки, и на что реагируют алерты.

Вот краткое пояснение уровней логирования в порядке их приоритетности (от самого высокого к самому низкому):

  1. error
  2. warn
  3. info
  4. debug
  5. trace
$ cargo run -- --log-level info 

Сообщения уровней Error, Warn, и Info будут включены в лог, если установлен уровень debug. Если вы хотите, чтобы info не логировалось, вам нужно установить уровень логирования выше, чем info, например, warn или error.

Макросы возвращают информацию о месте вызова в исходном коде.

std::line!() - Возвращает u32 номер строки, где был вызван макрос.

std::file!() - Возвращает имя файла (&'static str), в котором вызван макрос.

std::column!() - Возвращает u32 номер колонки в строке исходного файла (с позиции начала файла до символа).

column


fn main() {
    println!("File: {}", file!());
    println!("Line: {}", line!());
    println!("Column: {}", column!());
}

Я часто при написании программы с нуля повсеместно использую debug уровень для логирования с расчётом, что на продакшене будет выставлен уровень логирования info и тем самым сократится зашумлённость сообщениями. Но в таком подходе часто возникают ситуация, что логов вдруг становится не хватать. Трудно угадать, какая информация понадобиться, что бы отловить редкий баг.

формат строки сообщения

Логи должны содержать информацию, необходимую для реконструкции переходов состояний программы. Кроме того, логи должны содержать ключевые характеристики текущего состояния и причину перехода. Предпринимая попытку логирования, разработчикам следует выбрать правильный уровень архитектуры приложения, который содержит полную информацию о переходах состояний и причинах.

  1. Когда писать логи? В момент перехода в критическое состояние.

  2. Что записывать в лог? Ключевые характеристики текущего состояния и причину перехода состояния.

  3. Кто должен записывать логи? Логирование должно происходить на правильном уровне, содержащем достаточно информации.

  4. Каким должно быть количество логов? Определите X-фактор (по формуле # логов = X * # рабочих элементов+ константы) и настройте его экономно, но выгодно.

Типичный формат строки сообщения в логе можно условно разделить на следующие составные части:

[system info] + [message] + [context]

Где:

  • system info: метка времени, ид процесса, ид потока и другая служебная информация
  • message: текст сообщения
  • context: любая дополнительная информация, контекст может быть общим для сообщений в рамках какой то операции.

По возможности используйте ленивые вычисления для вывод сообщений в лог

Поиск по логам команда grep

grep ServiceManager" some.log | grep «Can't connect to host» | less

log crate представляет собой единый унифицированный интерфейс (фасад), который используется всеми библиотеками одновременно, но поддерживается одной реальной реализацией серверной части по вашему выбору. Это позволяет управлять всеми журналами (приложения и его зависимостей) из одного места и унифицированным образом: журналы регистрации и отказа библиотек, отдельные журналы по адресатам и т. Д.

Библиотеки должны связываться только с log ящиком и использовать предоставленные макросы для регистрации любой информации, которая будет полезна нижестоящим потребителям. Исполняемые файлы должны выбрать реализацию регистратора и инициализировать ее на ранней стадии выполнения программы. Реализации журналирования обычно включают функцию для этого. Одна интересная часть состоит в том, что уровни журналов можно отключить во время компиляции, таким образом, это не окажет никакого влияния на производительность во время выполнения, если вы не отлаживаете.

Одно важное замечание: crate log предоставляет только API, а не его реализации. Вы должны выбрать библиотеку, которая реализует регистратор log4rs, env_logger ...

Уровни логирования

  • error!: Критические ошибки, приложение не может продолжать работу. (для серьезных проблем)
  • warn!: Предупреждения, что-то пошло не так, но работа может продолжаться. (для неожиданных, но некритичных событий)
  • info!: Важная информация о ходе выполнения. ( для важных шагов процесса)
  • debug!: Детальная информация для отладки. (для входных/выходных данных функций)
  • trace!: Очень подробная информация, вход/выход из функций. (для пошагового выполнения)
pub async fn call_api(url: &str) -> Result<Response> {
    info!("Calling external API: {}", url);
    let response = reqwest::get(url).await?;
    
    let status = response.status();
    debug!("API response status: {}", status);
    
    if !status.is_success() {
        warn!("API call failed: {}", status);
    } else {
        trace!("API response headers: {:?}", response.headers());
    }
    Ok(response)
}

Настройка через переменные среды

# Показывает info и выше:
$ RUST_LOG=info cargo run

# Показывает debug и выше:
$ RUST_LOG=debug cargo run`

# trace для cli_args, debug для os:
$ RUST_LOG=cli_args=trace,os=debug cargo run

Логирование в test

#[test]
fn test_execute_command() {
    let _ = env_logger::builder().is_test(true).try_init();
    
    info!("Starting test_execute_command");
    let result = execute_command("echo test");
    assert!(result.is_ok(), "Command failed: {}", result.unwrap_err());
}
is_test(true) предотвращает конфликты инициализации в тестах.

Обработка panic

Это обеспечит информативное логирование даже при панике.


use log::set_hook;

fn main() {
    set_hook(Box::new(|panic_info| {
        error!("Application panicked: {}", panic_info);
    }));
    // ...
}

Для структурированных логов есть отличный crate slog в Rust экосистеме.

Наша цель - стать Библиотекой журналов для Rust.

slog должны учитывать различные функции и требования к ведению журнала.

Если есть необходимая функция и отсутствует стандартный log ящик, она slog должна быть у вас.

Он обратно и вперед совместим с crate log, расширяет его идеи и имеет отличную производительность

ключи по умолчанию

Для такого источника:


fn main(){
    let file_general = OpenOptions::new()
        .append(true)
        .create(true)
        .write(true)
        .truncate(false)
        .open(general_log_path)
        .unwrap();

    let prepare_drain: slog::Fuse> = slog_json::Json::default(file_general).fuse();
}

Формат будет таким:

msgleveltsversion
MESSAGEINFO2024-06-05T09:02:42.908403041Z0.1.0
MESSAGEDEBG2024-06-05T09:04:08.822208096Z0.1.0
MESSAGETRCE2024-06-05T09:04:08.822768038Z0.1.0
MESSAGEERRO2024-06-05T09:04:16.68434404Z0.1.0

Что это за формат времени ISO 8601: 2024-06-05T09:02:42.908403041Z

Что это за формат времени RFC 3339: 2024-06-05T11:02:42.908+02:00

Что это за формат времени: 2024-06-05T09:02:42.908403041Z

Это стандартный формат представления даты и времени, известный как ISO 8601. В данном случае, строка "2024-06-05T09:02:42.908403041Z" представляет собой дату и время в UTC (Coordinated Universal Time). Расшифровка каждой части:

  • 2024: год
  • 06: месяц (июнь)
  • 05: день месяца
  • T: разделитель между датой и временем
  • 09: часы (в 24-часовом формате)
  • 02: минуты
  • 42: секунды
  • .908403041: миллисекунды и микросекунды (дробная часть секунды с высокой точностью)
  • Z: означает UTC или "Зулу время", то есть время по Гринвичу без смещения часового пояса

Этот формат широко используется в компьютерных системах, так как он однозначен, легко читается машинами и позволяет точно представлять моменты времени вплоть до микросекунд.


Формат RFC 3339 действительно может быть лучшим выбором в большинстве случаев. RFC 3339 - это профиль стандарта ISO 8601, который уточняет и ограничивает некоторые аспекты, делая его более простым и однозначным для использования в интернет-протоколах. Преимущества RFC 3339 перед "сырым" ISO 8601:

Простота: RFC 3339 убирает многие необязательные форматы из ISO 8601, оставляя только самые полезные и однозначные. Читаемость: Формат более понятен людям, что снижает вероятность ошибок. Временные зоны: Поддерживает явное указание смещения временной зоны (например, "+02:00" для UTC+2), что делает его более удобным для работы с локальными временами. Распространенность: Широко используется в веб-стандартах (JSON, XML) и протоколах (HTTP, SMTP).

Пример того же времени в RFC 3339:

  • Copy code2024-06-05T09:02:42.908Z (если это UTC)
  • 2024-06-05T11:02:42.908+02:00 (если это, например, восточноевропейское летнее время)

Заметьте, что RFC 3339 рекомендует ограничивать точность до миллисекунд в большинстве случаев, так как микросекунды редко требуются и могут создавать проблемы совместимости. Так что да, для большинства веб-приложений и API формат RFC 3339 предпочтительнее, так как он обеспечивает баланс между точностью, читаемостью и простотой использования.

Пример логирования в разные файлы в формате NDJSON

  • с изменной меткой времени на rfc3339
  • и управление уровнями логирования через ENV
  • и фильтрацией по target

Файл Cargo.toml:

chrono = "0.4"
# log
slog = "2.7"
slog-scope = "4.4"
slog-stdlog = "4.1"
slog-async = "2.8"
slog-json = "2.6"
slog-envlogger = "2.2"
log = "0.4"

//! Logging application state transition steps.
//! Logs are saved to a file in NDJSON format.

pub use logger::init;
pub mod logger {
    use slog::{o, Drain, Filter, FnValue, PushFnValue, Record, *};
    use std::{fs::OpenOptions, time::Duration};

    pub struct GuardLogger {
        _guard: slog_scope::GlobalLoggerGuard,
    }
    impl GuardLogger {
        pub fn emergency_data_reset(&self) {
            std::thread::sleep(Duration::from_secs(2));
        }
    }

    pub fn init() -> GuardLogger {
        let general_log_path = ".general.ndjson.log";
        let event_target_log_path = ".event.ndjson.log";

        let general_drain = {
            let file_general = OpenOptions::new()
                .append(true)
                .create(true)
                .truncate(false)
                .open(general_log_path)
                .unwrap();
            let builder = slog_json::Json::new(file_general)
                .set_pretty(false)
                .set_flush(false)
                .set_newlines(true);

            let drain = builder.build().fuse();
            let drain = slog_async::Async::new(drain).build().fuse();
            let drain = slog_envlogger::new(drain).fuse();
            let drain_filter = Filter::new(drain, |record| {
                !record.tag().starts_with("EV")
            })
            .fuse();
            drain_filter
        };

        let event_drain = {
            let file_special_target = OpenOptions::new()
                .append(true)
                .create(true)
                .truncate(false)
                .open(event_target_log_path)
                .unwrap();
            let builder = slog_json::Json::new(file_special_target)
                .set_pretty(false)
                .set_flush(false)
                .set_newlines(true);

            let drain = builder.build().fuse();
            let drain: Fuse =
                slog_async::Async::new(drain).build().fuse();
            let drain = slog_envlogger::new(drain).fuse();
            let drain_filter =
                Filter::new(drain, |record| record.tag().starts_with("EV"))
                    .fuse();
            drain_filter
        };

        let drain = slog::Duplicate::new(general_drain, event_drain).fuse();

        let values = o!(
            "version" => env!("CARGO_PKG_VERSION"),
            "OS" => std::env::consts::OS,
            "ts" => PushFnValue(move |_: &Record, ser| {
                ser.emit(chrono::Utc::now().to_rfc3339())
            }),
            "lvl" => FnValue(move |rinfo: &Record| {
                rinfo.level().as_str()
            }),
            "msg" => PushFnValue(move |record: &Record, ser| {
                ser.emit(record.msg())
            }),
        );
        let logger = slog::Logger::root(drain, values);
        let guard = slog_scope::set_global_logger(logger); // перенаправляет все вызовы макросов log info! error! ... в slog
        slog_stdlog::init().unwrap();
        GuardLogger { _guard: guard }
    }
}
fn main() -> anyhow::Result<()> {
    let guard_log = my_mod_log::init();
    info!("API info!: {}", "URL");
    info!(target: "EV ", "API info!: port: {}, speed: {}","PORT", "SPEED");
    debug!("API debug!: {}", "MESSAGE");
    warn!("API warn!: {}", "MESSAGE");
    trace!("API trace!: {:?}", ["MESSAGE", "MESSAGE", "MESSAGE"]);
    error!("API error!: {}", "MESSAGE");
 
    let result = Cli::try_parse();
    match result {
        Ok(cli) => match &cli.command {
            Commands::FileContent { path_file } => {
                wrap_file_content(path_file);
            }
            Commands::ResourceList { path_dir } => {
                wrap_resource_list(path_dir);
            }
            Commands::AnyCommandExecute {
                command,
                path_dir,
                path_file,
            } => {
                wrap_execute_any_command(command, path_dir, path_file.as_ref());
            }
        },
        Err(error) => {
            eprintln!("{}", error);
            guard_log.emergency_data_reset();
            std::process::exit(2);
        }
    }
    Ok(())
}

Запуск:

RUST_LOG=warn cargo run
log = "0.4"
env_logger = "0.9"
async-log = "2.0.*" 
slog-async = "2.7"
slog-json = "2.4"
slog-stream = "1.2"
slog-term = "2.8"
slog-scope = "4.4"
slog = "2.7"
chrono = "0.4"

#[macro_use(o, slog_log, slog_trace, slog_debug, slog_info, slog_warn, slog_error)]
extern crate slog;
extern crate slog_async;
extern crate slog_json;
extern crate slog_stream;
extern crate slog_term;
#[macro_use]
extern crate slog_scope;
use std::fs::OpenOptions;

use slog::Drain;

use std::fs::File;
use std::io;
use std::sync::Mutex;

//use slog_bunyan::new;
use slog::Duplicate;
use slog::Level;
use slog::Logger;

use slog::*;
use std::time::{Duration, SystemTime};

use slog_async::Async;

/*
* [slog-term] (https://docs.rs/slog-term/) для вывода терминала
* [slog-async] (https://docs.rs/slog-async/ ) для асинхронного ведения журнала
* [slog-json] (https://docs.rs/slog-json/) для ведения журнала JSON
* [slog-syslog] (https://docs.rs/slog-syslog/) для logging to syslog
* [sloggers] (https://docs.rs/sloggers/) для удобства методов
*/

fn main() {
    // Перевод на JSON
    // Форматированный вывод как настроить
    // Первый пункт = только в консоль, но с полем "file":"app.log"
    // Как локально и глобально slog_scope ?

    /* JSON
    let d2 = Mutex::new(slog_json::Json::default(io::stdout())).fuse();
    let file_drain = slog_stream::stream(io::stdout(), slog_json::default());
    Mutex::new(slog_json::Json::default(std::io::stderr())).map(slog::Fuse);

    let drain = slog_json::Json::new(std::io::stdout())
    .set_pretty(true)
    .add_default_keys()
    .build()
    .fuse();
    let drain = slog_async::Async::new(drain).build().fuse();
    */

    // slog_term Цветастый вывод
    //let decorator = slog_term::TermDecorator::new().stdout().build();
    //let d_stdout = slog_term::CompactFormat::new(decorator).build().fuse();
    //let d_stdout = slog_async::Async::new(d_stdout).build().fuse();

    //let decorator = slog_term::PlainDecorator::new(std::io::stderr());
    //let d_stderr = slog_term::FullFormat::new(decorator).build().fuse();
    //let d_stderr = slog_async::Async::new(d_stderr).build().fuse();

    // Или
    //let plain = slog_term::PlainSyncDecorator::new(std::io::stdout());
    //let d_stdout = slog_term::FullFormat::new(plain).build().fuse();

    //let plain = slog_term::PlainSyncDecorator::new(std::io::stderr());
    //let d_stderr = slog_term::FullFormat::new(plain).build().fuse();

    // Или
    //let decorator = slog_term::PlainDecorator::new(std::io::stdout());
    //let d_stdout = Async::new(slog_term::FullFormat::new(decorator).build().fuse() ).build().fuse();

    //let decorator = slog_term::PlainDecorator::new(std::io::stdout());
    //let d_stderr = Async::new(slog_term::FullFormat::new(decorator).build().fuse() ).build().fuse();

    // slog_json
    let drain = slog_json::Json::new(std::io::stdout())
        .set_pretty(false)
        .add_default_keys()
        .build()
        .fuse();
    let d_stdout = slog_async::Async::new(drain).build().fuse();

    let drain = slog_json::Json::new(std::io::stderr())
        .set_pretty(false)
        .add_default_keys()
        .build()
        .fuse();
    let d_stderr = slog_async::Async::new(drain).build().fuse();

    use std::io::ErrorKind;
    enum Cmp {
        Less = 0,
        Greater,
    }
    pub struct MyLevelFilter(pub D, pub Level, pub Cmp);

    impl MyLevelFilter {
        pub fn new(drain: D, level: Level, cmp: Cmp) -> Self {
            MyLevelFilter(drain, level, cmp)
        }
    }

    impl Drain for MyLevelFilter {
        type Ok = ();
        type Err = Never;
        fn log(
            &self,
            record: &Record,
            logger_values: &OwnedKVList,
        ) -> std::result::Result {
            // is_at_least Возвращает true, если уровень self не ниже уровня
            // println!("record.level() = {} AND self.1 = {}",record.level().as_usize(),self.1.as_usize());
            match self.2 {
                Cmp::Less => {
                    if record.level().as_usize() <= self.1.as_usize() {
                        self.0.log(record, logger_values);
                    }
                }
                Cmp::Greater => {
                    if record.level().as_usize() >= self.1.as_usize() {
                        self.0.log(record, logger_values);
                    }
                }
            }

            // else {
            //        Err(std::io::Error::new(std::io::ErrorKind::Other, "Empty stack"))
            //     }
            Ok(())
        }
        /* #[inline]
        fn is_enabled(&self, level: Level) -> bool {
        level.is_at_least(self.1) && self.0.is_enabled(level)
        }*/
    }

    // ---------------------------------------------------------------
    pub struct Threeplicate(pub D1, pub D2, pub D3);
    impl Threeplicate {
        /// Create `Duplicate`
        pub fn new(drain1: D1, drain2: D2, drain3: D3) -> Self {
            Threeplicate(drain1, drain2, drain3)
        }
    }
    impl Drain for Threeplicate {
        type Ok = (D1::Ok, D2::Ok, D3::Ok);
        type Err = (
            std::result::Result,
            std::result::Result,
            std::result::Result,
        );
        fn log(
            &self,
            record: &Record,
            logger_values: &OwnedKVList,
        ) -> std::result::Result {
            let res1 = self.0.log(record, logger_values);
            let res2 = self.1.log(record, logger_values);
            let res3 = self.2.log(record, logger_values);

            match (res1, res2, res3) {
                (Ok(o1), Ok(o2), Ok(o3)) => Ok((o1, o2, o3)),
                (r1, r2, r3) => Err((r1, r2, r3)),
            }
        }
        #[inline]
        fn is_enabled(&self, level: Level) -> bool {
            self.0.is_enabled(level) || self.1.is_enabled(level) || self.2.is_enabled(level)
        }
    }
    //--------------------------------------------------------------
    //  Debug
    let log_path = "app.log";
    let file: std::fs::File = OpenOptions::new()
        .create(true)
        .write(true)
        .truncate(true)
        .open(log_path)
        .unwrap();

    let drain = slog_json::Json::new(file)
        .set_pretty(false)
        .set_newlines(true)
        .add_default_keys()
        //.add_key_value()
        .build()
        .fuse();
    let drain_file = slog_async::Async::new(drain).build().fuse();

    let drain_base = Threeplicate::new(
        MyLevelFilter::new(d_stderr, Level::Warning, Cmp::Less),
        MyLevelFilter::new(d_stdout, Level::Info, Cmp::Greater),
        MyLevelFilter::new(drain_file, Level::Trace, Cmp::Less),
    )
    .fuse();

    let utc: chrono::DateTime = chrono::Utc::now();
    let root = Logger::root(
        drain_base,
        o!("ts" => format!("{}", utc.to_rfc2822()) ),
    );

    //info!(root, "foo is {foo} {bar} {baz}", bar=3, foo = 2, baz=4,);
    //info!(root, "formatted {num_entries} entries of {}", "something", num_entries = 2; "log-key" => true);
    //  info!(root, "{method} {path}", method = "POST", path = "/some"; );
    //  warn!(root,"http");
    //  error!(root,"http");
    //  trace!(root,"http");
    //  debug!(root, "debug values"; "x" => 1, "y" => -1);

    // register slog_stdlog в качестве обработчика журнала с логом для журнала
    //slog_stdlog::init().unwrap();

    let _guard = slog_scope::set_global_logger(root); //crate slog_scope глобальная регистрация

    // info!("global logger");
    slog_debug!(slog_scope::logger(), "slog_debug");
    slog_info!(slog_scope::logger(), "slog_info");
    slog_warn!(slog_scope::logger(), "slog_warn");
    slog_error!(slog_scope::logger(), "slog_error");
    //slog_trace!(slog_scope::logger(), "foo");

    //slog_scope::scope(slog_scope::logger().new(o!("where" => "Test logging scope")), || {
    //
    //});
    // debug!(" global logger");
}

Чтобы начать ведение журнала, вы должны создать приложение, которое сообщает, куда идут журналы: консоль, файл или системный журнал.


use log::error;
use log::info;
use log::warn;
use log::{debug, LevelFilter};
use log4rs::append::console::ConsoleAppender;
use log4rs::config::{Appender, Root};
use log4rs::Config;
fn main() {
    let stdout = ConsoleAppender::builder().build();
    let config = Config::builder()
        .appender(Appender::builder().build("stdout", Box::new(stdout)))
        .build(Root::builder().appender("stdout").build(LevelFilter::Trace))
        .unwrap();
    let _handle = log4rs::init_config(config).unwrap();
    debug!("Mary has a little lamb");
    error!("{}", "Its fleece was white as snow");
    info!("{:?}", "And every where that Mary went");
    warn!("{:#?}", "The lamb was sure to go");
}

Замените env_logger на flexi_logger для логирования в файл и консоль. Кроме того, есть несколько других библиотек, построенных на основе env_logger. Например, ящики pretty_env_logger и json_env_logger. Вы можете найти их полезными, если хотите красиво распечатать журнал или записывать сообщения как json, а не как текст.

С помощью одной строки конфигурации в коде env_logger::init() эта простая библиотека записывает весь ваш журнал в stderr(настраиваемый stdout)

Как следует из названия, env_logger использует переменную среды RUST_LOG для настройки своего уровня журнала. Например, RUST_LOG=debug cargo run --bin env_logger регистрируется все, что испускается env_logger двоичным файлом на уровне отладки или выше, то есть все, кроме трассировки.

log = "0.4"
env_logger = "0.9" 

fn main(){
   let start = std::time::Instant::now();
    env_logger::Builder::from_default_env().format(move |buf, rec| {
        let t = start.elapsed().as_secs_f32();
        writeln!(buf, "{:.03} [{}] - {}", t, rec.level(),rec.args())
    }).init();
}

Расширенная конфигурация env_logger в main.rs


use env_logger::{Env, Builder, Target};

fn main() {
    Builder::from_env(Env::default().default_filter_or("info"))
        .target(Target::Stdout)
        .format_timestamp_secs()
        .format_module_path(true)
        .init();

    // ...
}

default_filter_or("info"): По умолчанию уровень info.
target(Target::Stdout): Вывод в stdout (можно и в файл).
format_timestamp_secs(): Добавляет временную метку.
format_module_path(true): Показывает путь модуля.

use log::debug;
use log::error;
use log::info;
use log::warn;
fn main() {
    env_logger::init();
    debug!("Mary has a little lamb");
    error!("{}", "Its fleece was white as snow");
    info!("{:?}", "And every where that Mary went");
    warn!("{:#?}", "The lamb was sure to go");
}

Логирование в файл с env_logger


use env_logger::{Builder, Target};
use std::fs::File;

fn main() {
    let file = File::create("app.log").unwrap();

    Builder::new()
        .target(Target::Pipe(Box::new(file)))
        .init();

    // Или в оба места
    Builder::new()
        .target(Target::Stdout)
        .target(Target::Pipe(Box::new(File::create("app.log").unwrap())))
        .init();
}

flexi_logger предлагает богатый набор функций для файлового логирования:

  • Ротация логов
  • Несколько файлов
  • Гибкое именование
  • Разные форматы для файла и консоли
  • Устойчивость к ошибкам ввода-вывода

Базовая настройка в main.rs

Cargo.toml:
[dependencies]
log = "0.4"
flexi_logger = "0.27"

use flexi_logger::{Logger, FileSpec};

fn main() {
    Logger::try_with_str("info")
        .unwrap()
        .log_to_file(FileSpec::default())
        .start()
        .unwrap();

    info!("Application started");
    // ...
}

Расширенные функции flexi_logger


use flexi_logger::{Logger, FileSpec, Age, Cleanup, Naming, Duplicate};

fn main() {
    Logger::try_with_str("debug,reqwest=info")
        .unwrap()
        .log_to_file(
            FileSpec::default()
                .directory("logs")
                .suffix("log")
        )
        .rotate(
            Age::Day,
            Cleanup::KeepLogFiles(7),
            Naming::Timestamps
        )
        .create_symlink("current.log")
        .duplicate_to_stdout(Duplicate::Info)
        .start()
        .unwrap();

    // ...
}
directory("logs"): Логи в папку logs.
suffix("log"): Файлы с расширением .log.
rotate(...): Ротация логов ежедневно, хранение за 7 дней.
Naming::Timestamps: Имена файлов с временными метками.
create_symlink("current.log"): Симлинк на текущий лог.
duplicate_to_stdout(Duplicate::Info): В консоль только info и выше.

Дополнительные возможности flexi_logger


fn main(){
    Logger::try_with_str("trace")
        .unwrap()
        .log_to_file(FileSpec::default().suppress_timestamp())
        .format_for_files(flexi_logger::detailed_format)
        .format_for_stdout(flexi_logger::colored_detailed_format)
        .print_message()
        .create_symlink("trace.log")
        .use_utc()
        .start()
        .unwrap();

    suppress_timestamp(): Без временных меток в имени файла.
    format_for_files(...): Детальный формат для файлов.
    format_for_stdout(...): Цветной формат для консоли.
    print_message(): Включает тело сообщения в вывод.
    use_utc(): Использует UTC вместо локального времени.
}

Логирование в несколько файлов


fn main(){
    Logger::try_with_str("info")
        .unwrap()
        .log_to_file(FileSpec::default().directory("logs/info"))
        .log_to_file(FileSpec::default().directory("logs/errors").only_errors())
        .start()
        .unwrap();
}

Динамическая настройка


fn main(){
    let mut logger = Logger::try_with_str("info").unwrap();
    // ...
    if debug_mode {
        logger = logger.log_to_file("debug.log").modify_max_log_level(log::LevelFilter::Debug);
    }
    let _logger = logger.start().unwrap();
}

Обработка ошибок файловой системы

append() - Дописывает в существующий файл

o_non_block() и o_sync() - Опции для надежности ввода-вывода


fn main(){
    Logger::try_with_str("warn")
        .unwrap()
        .log_to_file(FileSpec::default())
        .append()
        .o_non_block()
        .o_sync()
        .start()
        .unwrap();
}

Возможности асинхронной трассировки для стандартного crate log

Мониторинг OS Servera:

apt-get install munin munin-node
service munin-node start
ps ax | grep muni

Теперь munin-node будет собирать метрики системы и писать их в бд, а munin раз в 5 минут будет генерировать из этой бд html-отчёты и класть их в папку /var/cache/munin/www

Если у Вас установлен apache2, то при установки munin к его настройкам автоматически добавится конфиг файл виртуалхоста для доступа к мониторингу. Web интерфейс будет доступен по адресу http://localhost/munin и только с локального компьютера.

Мониторин HTTP Сервера

Знаменитый crate tracing великолепно справляется как с отслеживанием, так и с структурированным журналированием.

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

Его «убийственной особенностью», несомненно, является функциональность спанов , поэтому люди склонны предпочитать егоslog даже для обычного журналирования. Он также совместим назад и вперед сlog ящиком.

Говоря о трассировке , tracingкрейт имеет хорошую интеграцию с OpenTelemetry -совместимыми распределенными системами трассировки (и аналогичными). Все это позволяет повторно использовать одно и то же решение как для журналирования, трассировки (например, Jaeger , Zipkin ), профилирования (например, coz , Tracy ), отчетов об ошибках (например, Sentry ) и т. д.

tracing представляет собой платформу для оснащения программ Rust сбором структурированной диагностической информации на основе событий.

В асинхронных системах, таких как Tokio, интерпретация традиционных сообщений журнала часто может быть весьма сложной задачей. Поскольку отдельные задачи мультиплексируются в одном потоке, связанные события и строки журнала перемешиваются, что затрудняет отслеживание логического потока. tracing расширяет диагностику в стиле журналирования, позволяя библиотекам и приложениям записывать структурированные события с дополнительной информацией о временности и причинно-следственной связи — в отличие от сообщения журнала, интервал tracing имеет время начала и окончания, может вводиться и выходить из потока выполнения, и может существовать внутри вложенного дерева аналогичных промежутков. Кроме того, tracing промежутки структурированы с возможностью записи как типизированных данных, так и текстовых сообщений.

Cargo.toml:

# log
log = "0.4"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["json", "fmt","std","env-filter", "time"] }
tracing-appender = "0.2"

pub use log::init;
pub mod log {
    use std::fs::OpenOptions;
    use tracing_appender::non_blocking::WorkerGuard;
    use tracing_subscriber::{
        fmt::{format::FmtSpan, time, SubscriberBuilder},
        EnvFilter,
    };
    pub struct GuardLogger {
        _guard: WorkerGuard,
    }
    pub fn init() -> GuardLogger {
        let general_log_path = ".general.ndjson.log";
        let file_appender = OpenOptions::new()
            .append(true)
            .create(true)
            .open(general_log_path)
            .expect("Failed to open log file");
        let (non_blocking, guard) =
            tracing_appender::non_blocking(file_appender);
        let env_filter = EnvFilter::from_default_env();
        let subscriber = SubscriberBuilder::default()
            .json()
            .with_timer(time::UtcTime::rfc_3339())
            .with_env_filter(env_filter)
            .with_span_events(FmtSpan::CLOSE)
            .with_file(true)
            .with_line_number(true)
            .with_thread_ids(false)
            .with_thread_names(false)
            .with_target(false)
            .with_writer(non_blocking)
            .finish();

        tracing::subscriber::set_global_default(subscriber)
            .expect("setting default subscriber failed");
        GuardLogger { _guard: guard }
    }
}

// Use:
use tracing::info;
#[tokio::main]
async fn main() {
   let _guard_log = logger::init();
   info!("path_dir: {:?} OS:{}", path_dir.as_ref(), std::env::consts::OS);
}
use opentelemetry::trace::SpanBuilder;
use serde::ser::{SerializeMap, Serializer as _};
use std::io;
use tracing::{Event, Subscriber};
use tracing_serde::AsSerde;
use tracing_subscriber::fmt::format::Writer;
use tracing_subscriber::fmt::time::FormatTime;
use tracing_subscriber::fmt::{FmtContext, FormatEvent, FormatFields};
use tracing_subscriber::registry::LookupSpan;

pub struct WriteAdaptor<'a> {
    fmt_write: &'a mut dyn std::fmt::Write,
}

impl<'a> WriteAdaptor<'a> {
    pub fn new(fmt_write: &'a mut dyn std::fmt::Write) -> Self {
        Self { fmt_write }
    }
}

impl<'a> io::Write for WriteAdaptor<'a> {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        let s =
            std::str::from_utf8(buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;

        self.fmt_write
            .write_str(s)
            .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;

        Ok(s.as_bytes().len())
    }

    fn flush(&mut self) -> io::Result<()> {
        Ok(())
    }
}

pub struct TraceIdFormat;

impl<S, N> FormatEvent<S, N> for TraceIdFormat
where
    S: Subscriber + for<'lookup> LookupSpan<'lookup>,
    N: for<'writer> FormatFields<'writer> + 'static {
    fn format_event(
        &self,
        ctx: &FmtContext<'_, S, N>,
        mut writer: Writer<'_>,
        event: &Event<'_>,
    ) -> std::fmt::Result
    where
        S: Subscriber + for<'a> LookupSpan<'a> {
        let meta = event.metadata();
        let mut visit = || {
            let mut serializer = serde_json::Serializer::new(WriteAdaptor::new(&mut writer));
            let mut serializer = serializer.serialize_map(None)?;
            serializer.serialize_entry("level", &meta.level().as_serde())?;

            let format_field_marker: std::marker::PhantomData<N> = std::marker::PhantomData;
            use tracing_serde::fields::AsMap;
            serializer.serialize_entry("fields", &event.field_map())?;
            serializer.serialize_entry("target", meta.target())?;

            if let Some(span_ref) = ctx.lookup_current() {
                if let Some(builder) = span_ref.extensions().get::<SpanBuilder>() {
                    if let Some(trace_id) = builder.trace_id {
                        serializer.serialize_entry("trace_id", &trace_id.to_hex())?;
                    }
                }
            }
            serializer.end()
        };
        visit().map_err(|_| std::fmt::Error)?;
        writeln!(writer)
    }
}

let fmt_layer = tracing_subscriber::fmt::layer()
    //.json()
    .event_format(TraceIdFormat);

logstach или graylog - сервисы сбора логов

elasticsearch - поиск данных, хранение логов

kibana - визуализация логов

Сбор метрик

Prometheus - Сбор метрик

Grafana - визуализация метрик