1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
//! Utilities related to logging

use crate::utils::console::MULTI_PROGRESS;
use fern::colors::{Color, ColoredLevelConfig};
use log::LevelFilter;

/// Configure an opinionated way of logging.
///
/// This is just one way of outputing logs, and users are free to use this function
/// or choose their own way of outputing logs. That's why this function is "opinionated".
///
/// It will log to file and standard output.
/// All logs with level `debug`(with parameter `verbose`=true or system variable `RUST_LOG`="debug") or above
/// will be recorded in `./logs/<date>.log`.
/// Logs with level `info` and above will be output to standard output, with colored tag.
///
/// # Example
///
/// ```
/// use lib_flutter_rust_bridge_codegen::utils::logs::configure_opinionated_logging;
/// configure_opinionated_logging("./logs/", false).expect("failed to initialize log");
/// ```
pub fn configure_opinionated_logging(path: &str, verbose: bool) -> Result<(), fern::InitError> {
    std::fs::create_dir_all(path).unwrap();

    let mut fern_logger = fern::Dispatch::new();
    fern_logger = log_format_simple(fern_logger);
    fern_logger = match log_level_from_env_var().unwrap_or_else(|| verbose_to_level_filter(verbose))
    {
        LevelFilter::Debug => fern_logger
            .level(LevelFilter::Debug)
            .chain(fern::DateBased::new(path, "%Y-%m-%d.log"))
            .chain(std::io::stdout()),
        LevelFilter::Info => fern_logger
            .level(LevelFilter::Info)
            .level_for("cbindgen", LevelFilter::Error)
            .chain(std::io::stdout()),
        _ => panic!("only allow \"debug\" or \"info\""),
    };

    let (max_level, fern_logger) = fern_logger.into_log();
    let log_wrapper = indicatif_log_bridge::LogWrapper::new(MULTI_PROGRESS.clone(), fern_logger);

    // ref: fern.apply / LogWrapper.try_init
    log::set_boxed_logger(Box::new(log_wrapper))?;
    log::set_max_level(max_level);

    let prev = std::panic::take_hook();
    std::panic::set_hook(Box::new(move |info| {
        log::error!("{}", info);
        prev(info);
    }));

    Ok(())
}

fn log_level_from_env_var() -> Option<LevelFilter> {
    std::env::var("RUST_LOG")
        .ok()
        .map(|value| match &value[..] {
            "trace" => LevelFilter::Trace,
            "debug" => LevelFilter::Debug,
            "info" => LevelFilter::Info,
            "warn" => LevelFilter::Warn,
            "error" => LevelFilter::Error,
            "off" => LevelFilter::Off,
            _ => panic!("{}", "unknown RUST_LOG level: {value}"),
        })
}

fn verbose_to_level_filter(verbose: bool) -> LevelFilter {
    if verbose {
        LevelFilter::Debug
    } else {
        LevelFilter::Info
    }
}

fn log_format_simple(d: fern::Dispatch) -> fern::Dispatch {
    let colored_output = ColoredLevelConfig::new()
        .error(Color::Red)
        .warn(Color::Yellow)
        .info(Color::Green)
        .debug(Color::Blue)
        .trace(Color::BrightBlack);

    d.format(move |out, message, record| {
        let time = chrono::Utc::now().format("%Y-%m-%dT%H:%M:%S%.3fZ");

        let level = record.level();
        let level = if atty::is(atty::Stream::Stdout) {
            colored_output.color(level).to_string()
        } else {
            level.to_string()
        };

        out.finish(format_args!(
            "[{} {} {}:{}] {}",
            time,
            level,
            record.file().unwrap_or(""),
            record.line().unwrap_or(0),
            message
        ))
    })
}

/// Configure an opinionated way of logging, useful in tests.
pub fn configure_opinionated_test_logging() {
    // https://github.com/daboross/fern/issues/54
    // This will fail if called twice; don't worry.
    let _ = log_format_simple(fern::Dispatch::new())
        .level(log_level_from_env_var().unwrap_or(LevelFilter::Debug))
        .chain(fern::Output::call(|record| println!("{}", record.args())))
        .apply();
}