Skip to main content

rapx/utils/
log.rs

1use crate::rap_warn;
2use chrono::Local;
3use fern::colors::{Color, ColoredLevelConfig};
4use fern::{self, Dispatch};
5use log::LevelFilter;
6
7fn parse_rapx_log() -> (LevelFilter, Vec<(String, LevelFilter)>) {
8    let mut global_level = LevelFilter::Info;
9    let mut module_levels = Vec::new();
10    let crate_name = env!("CARGO_PKG_NAME");
11
12    let Ok(raw) = std::env::var("RAPX_LOG") else {
13        return (global_level, module_levels);
14    };
15
16    for item in raw.split(',') {
17        let item = item.trim();
18        if item.is_empty() {
19            continue;
20        }
21
22        // `module:LEVEL`
23        if let Some((module, level)) = item.rsplit_once(':') {
24            let module = module.trim();
25            let level = level.trim();
26            if module.is_empty() {
27                rap_warn!("RAPX_LOG module is empty in entry: {item}");
28                continue;
29            }
30            match level.parse() {
31                Ok(parsed) => module_levels.push((format!("{}::{}", crate_name, module), parsed)),
32                Err(err) => rap_warn!("RAPX_LOG entry is invalid: {item} ({err})"),
33            }
34            continue;
35        }
36
37        match item.parse() {
38            Ok(parsed) => global_level = parsed,
39            Err(err) => rap_warn!("RAPX_LOG entry is invalid: {item} ({err})"),
40        }
41    }
42
43    (global_level, module_levels)
44}
45
46/// Detect `RAPX_LOG` environment variable first; if it's not set,
47/// default to INFO level.
48///
49/// Supported forms:
50/// - `RAPX_LOG=TRACE` -> global level
51/// - `RAPX_LOG=some::module:DEBUG` -> module level
52/// - `RAPX_LOG=TRACE,some::module:INFO,other::module:DEBUG` -> mixed
53pub fn init_log() -> Result<(), fern::InitError> {
54    let (global_level, module_levels) = parse_rapx_log();
55    let mut dispatch = Dispatch::new().level(global_level);
56    for (module, level) in module_levels {
57        dispatch = dispatch.level_for(module, level);
58    }
59
60    let color_line = ColoredLevelConfig::new()
61        .error(Color::Red)
62        .warn(Color::Yellow)
63        .info(Color::White)
64        .debug(Color::Blue)
65        .trace(Color::Cyan);
66
67    let color_level = color_line.info(Color::Green);
68    let stderr_dispatch = Dispatch::new()
69        .format(move |callback, args, record| {
70            let now = Local::now();
71            callback.finish(format_args!(
72                "{}{}|RAPx|{}{}|: {}\x1B[0m",
73                format_args!(
74                    "\x1B[{}m",
75                    color_line.get_color(&record.level()).to_fg_str()
76                ),
77                now.format("%H:%M:%S"),
78                color_level.color(record.level()),
79                format_args!(
80                    "\x1B[{}m",
81                    color_line.get_color(&record.level()).to_fg_str()
82                ),
83                args
84            ))
85        })
86        .chain(std::io::stderr());
87
88    /* Note that we cannot dispatch to stdout due to some bugs */
89    dispatch.chain(stderr_dispatch).apply()?;
90    Ok(())
91}
92
93#[macro_export]
94macro_rules! rap_trace {
95    ($($arg:tt)+) => (
96        ::log::trace!($($arg)+)
97    );
98}
99
100#[macro_export]
101macro_rules! rap_debug {
102    ($($arg:tt)+) => (
103        ::log::debug!($($arg)+)
104    );
105}
106
107#[macro_export]
108macro_rules! rap_info {
109    (green, $($arg:tt)+) => (
110        ::log::info!("\x1B[32m{}\x1B[0m", format_args!($($arg)+))
111    );
112    (yellow, $($arg:tt)+) => (
113        ::log::info!("\x1B[33m{}\x1B[0m", format_args!($($arg)+))
114    );
115    (red, $($arg:tt)+) => (
116        ::log::info!("\x1B[31m{}\x1B[0m", format_args!($($arg)+))
117    );
118    ($($arg:tt)+) => (
119        ::log::info!($($arg)+)
120    );
121}
122
123#[macro_export]
124macro_rules! rap_warn {
125    (green, $($arg:tt)+) => (
126        ::log::warn!("\x1B[32m{}\x1B[0m", format_args!($($arg)+))
127    );
128    (yellow, $($arg:tt)+) => (
129        ::log::warn!("\x1B[33m{}\x1B[0m", format_args!($($arg)+))
130    );
131    (red, $($arg:tt)+) => (
132        ::log::warn!("\x1B[31m{}\x1B[0m", format_args!($($arg)+))
133    );
134    ($($arg:tt)+) => (
135        ::log::warn!($($arg)+)
136    );
137}
138
139#[macro_export]
140macro_rules! rap_error {
141    (green, $($arg:tt)+) => (
142        ::log::error!("\x1B[32m{}\x1B[0m", format_args!($($arg)+))
143    );
144    (yellow, $($arg:tt)+) => (
145        ::log::error!("\x1B[33m{}\x1B[0m", format_args!($($arg)+))
146    );
147    (red, $($arg:tt)+) => (
148        ::log::error!("\x1B[31m{}\x1B[0m", format_args!($($arg)+))
149    );
150    ($($arg:tt)+) => (
151        ::log::error!($($arg)+)
152    );
153}
154
155pub fn rap_error_and_exit(msg: impl AsRef<str>) -> ! {
156    rap_error!("{}", msg.as_ref());
157    std::process::exit(1)
158}