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 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
46pub 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 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}