xvc_logging/
lib.rs

1//! Xvc logging and output crate to be used in output channels.
2//! Xvc uses to discriminate outputs of various types, (Info, Debug, Error...) and can use
3//! `crossbeam_channel` to send these separately.
4//! Downstream crates (xvc, xvc-file, etc.) use this crate not to use stdout, stderr directly.
5#![warn(missing_docs)]
6#![forbid(unsafe_code)]
7use crossbeam_channel::Sender;
8use log::LevelFilter;
9use std::env;
10use std::fmt::Display;
11use std::path::Path;
12use std::sync::Once;
13
14/// Debugging macro to print the given expression and its value, with the module, function and line number
15#[macro_export]
16macro_rules! watch {
17    ( $( $x:expr ),* ) => {
18        // TODO: Use stdout for this and convert all tracing macros to trace!
19        {
20            $(
21               ::log::trace!("{}: {}", stringify!($x), format!("{:#?}", $x).replace("\\n", "\n"));
22            )*
23        }
24    };
25}
26
27/// Either send a [XvcOutputLine::Error] value to the given channel, or log via `log` crate
28#[macro_export]
29macro_rules! error {
30    ($fmt:literal $(, $x:expr )* ) => {
31        {
32            ::log::error!($fmt $(,$x)*);
33        }
34    };
35    ( $channel:expr, $fmt:literal $(, $x:expr )* ) => {
36        {
37            (&$channel).send(Some(::xvc_logging::XvcOutputLine::Error(format!($fmt $(, $x)*)))).unwrap();
38        }
39    };
40}
41
42/// Either send [XvcOutputLine::Info] to the given channel, or log via `log` crate
43#[macro_export]
44macro_rules! info {
45    ($fmt:literal $(, $x:expr )* ) => {
46        {
47            ::log::info!($fmt $(, $x)*);
48        }
49    };
50    ( $channel:expr, $fmt:literal $(, $x:expr )* ) => {
51        {
52            (&$channel).send(Some(::xvc_logging::XvcOutputLine::Info(format!($fmt $(,$x)*)))).unwrap();
53        }
54    };
55}
56
57/// Either send [XvcOutputLine::Warn] to the given channel, or log via `log` crate
58#[macro_export]
59macro_rules! warn {
60    ($fmt:literal $(, $x:expr )* ) => {
61        {
62            ::log::warn!($fmt $(, $x)*);
63        }
64    };
65    ( $channel:expr, $fmt:literal $(, $x:expr )* ) => {
66        {
67            (&$channel).send(
68                Some(
69                    ::xvc_logging::XvcOutputLine::Warn(
70                        format!($fmt $(, $x)*)))).unwrap();
71        }
72    };
73}
74
75/// Either send [XvcOutputLine::Debug] to the given channel, or log via `log` crate
76#[macro_export]
77macro_rules! debug {
78    ($fmt:literal $(, $x:expr )* ) => {
79        {
80            ::log::debug!($fmt $(, $x)*);
81        }
82    };
83    ( $channel:expr, $fmt:literal $(, $x:expr )* ) => {
84        {
85                    (&$channel).send(Some(::xvc_logging::XvcOutputLine::Debug(format!($fmt $(, $x)*)))).unwrap();
86        }
87    };
88}
89
90/// Either send [XvcOutputLine::Trace] to the given channel, or log via `log` crate
91#[macro_export]
92macro_rules! trace {
93    ($fmt:literal $(, $x:expr )* ) => {
94        {
95            ::log::trace!($fmt $(, $x)*);
96        }
97    };
98    ( $channel:expr, $fmt:literal $(, $x:expr ),* ) => {
99        {
100            (&$channel).send(Some(::xvc_logging::XvcOutputLine::Trace(format!("{} [{}::{}]", format!($fmt $(, $x)*), file!(), line!())))).unwrap();
101        }
102    };
103
104    ( $( $x:expr ),* ) => {
105        {
106            $(
107               ::log::trace!("{}: {}", stringify!($x), format!("{:#?}", $x).replace("\\n", "\n"));
108            )*
109        }
110    };
111}
112
113/// Either send [XvcOutputLine::Output] to the given channel, or print to stdout
114#[macro_export]
115macro_rules! output {
116    ($fmt:literal $(, $x:expr )* ) => {
117        {
118            ::std::println!($fmt $(, $x)*);
119        }
120    };
121    ( $channel:expr, $fmt:literal $(, $x:expr )* ) => {
122        {
123            (&$channel).send(Some(::xvc_logging::XvcOutputLine::Output(format!($fmt $(, $x)*)))).unwrap();
124        }
125    };
126}
127
128/// Either send [XvcOutputLine::Panic] to the given channel, or print to stdout
129#[macro_export]
130macro_rules! panic {
131    ($fmt:literal $(, $x:expr )* ) => {
132        {
133            watch!($fmt $(, $x)*);
134            ::std::panic!($fmt $(, $x)*);
135        }
136    };
137    ( $channel:expr, $fmt:literal $(, $x:expr )* ) => {
138        {
139            (&$channel).send(Some(::xvc_logging::XvcOutputLine::Panic(format!("{} [{}::{}]",
140                                                                 format!($fmt $(, $x)*), file!(), line!())))).unwrap();
141            ::std::panic!($fmt $(, $x)*);
142        }
143    };
144}
145
146/// Either send [XvcOutputLine::Tick] to the given channel, or print dots to stdout
147#[macro_export]
148macro_rules! tick {
149    ( $channel:ident, $n:literal) => {{
150        (&$channel)
151            .send(Some(::xvc_logging::XvcOutputLine::Tick($n)))
152            .unwrap();
153    }};
154    ($n:literal) => {{
155        for _ in 0..$n {
156            ::std::print!(".");
157        }
158    }};
159}
160
161/// Unwrap the result of an expression, and if it is an error, send it to the given channel
162/// and panic.
163/// This is mostly to be used in `for_each` blocks, where the error is not propagated.
164#[macro_export]
165macro_rules! uwr {
166    ( $e:expr, $channel:expr ) => {{
167        match $e {
168            Ok(v) => v,
169            Err(e) => {
170                (&$channel)
171                    .send(Some(::xvc_logging::XvcOutputLine::Panic(format!(
172                        "{:?}, [{}::{}]",
173                        e,
174                        file!(),
175                        line!()
176                    ))))
177                    .unwrap();
178                ::std::panic!("{:?}", e);
179            }
180        }
181    }};
182}
183
184/// Unwrap an option, and if it is an error, send it to the given channel
185/// and panic.
186/// This is mostly to be used in `for_each` blocks, where the error is not propagated.
187#[macro_export]
188macro_rules! uwo {
189    ( $e:expr, $channel:expr ) => {{
190        match $e {
191            Some(v) => v,
192            None => {
193                watch!($e);
194                let msg = format!(
195                    "None from the expression: {} [{}::{}]",
196                    stringify!($e),
197                    file!(),
198                    line!()
199                );
200                (&$channel)
201                    .send(Some(::xvc_logging::XvcOutputLine::Panic(msg.clone())))
202                    .unwrap();
203                ::std::panic!("{}", msg);
204            }
205        }
206    }};
207}
208
209/// Logging Initializer
210static INIT: Once = Once::new();
211
212/// Init logging if it's not initialized before.
213/// Uses [Once] to run (non-public fn) `init_logging` once.
214pub fn setup_logging(term_level: Option<LevelFilter>, file_level: Option<LevelFilter>) {
215    INIT.call_once(|| init_logging(term_level, file_level));
216}
217
218fn init_logging(term_level: Option<LevelFilter>, file_level: Option<LevelFilter>) {
219    let logfilename = &format!("{}/xvc.log", env::temp_dir().to_string_lossy(),);
220
221    let logfile = Path::new(&logfilename);
222
223    let mut dispatch = fern::Dispatch::new().format(|out, message, record| {
224        out.finish(format_args!(
225            "[{}][{}::{}] {}",
226            record.level(),
227            record.file().get_or_insert("None"),
228            record.line().get_or_insert(0),
229            message
230        ))
231    });
232
233    if let Some(level) = term_level {
234        dispatch = dispatch.level(level).chain(std::io::stderr());
235    }
236
237    if let Some(level) = file_level {
238        dispatch = dispatch
239            .level(level)
240            .chain(fern::log_file(logfilename).expect("Cannot set log filename"));
241    }
242
243    match dispatch.apply() {
244        Ok(_) => {
245            if let Some(level) = term_level {
246                debug!("Terminal logger enabled with level: {:?}", level);
247            };
248            if let Some(level) = file_level {
249                debug!(
250                    "File logger enabled with level: {:?} to {:?}",
251                    level, logfile
252                );
253            };
254        }
255        Err(err) => {
256            error!("Error enabling logger: {:?}", err);
257        }
258    };
259}
260
261/// Different channels of outputs Xvc can print
262#[derive(Clone, Debug)]
263pub enum XvcOutputLine {
264    /// The output that we should be reporting to user
265    Output(String),
266    /// For informational messages
267    Info(String),
268    /// For debug output to show the internals of Xvc
269    Debug(String),
270    /// Warnings that are against some usual workflows
271    Warn(String),
272    /// Errors that interrupts a workflow but may be recoverable
273    Error(String),
274    /// Panics that interrupts the workflow and ends the program
275    /// Note that this doesn't call panic! automatically
276    Panic(String),
277    /// Progress bar ticks.
278    /// Self::Info is also used for Tick(1)
279    Tick(usize),
280}
281
282/// The channel type to send and receive output/log/debug messages
283pub type XvcOutputSender = Sender<Option<XvcOutputLine>>;
284
285impl XvcOutputLine {
286    /// print [INFO] `s`
287    pub fn info(s: &str) -> Self {
288        Self::Info(s.to_string())
289    }
290    /// print [DEBUG]
291    pub fn debug(s: &str) -> Self {
292        Self::Debug(s.to_string())
293    }
294    /// print [WARN] `s`
295    pub fn warn(s: &str) -> Self {
296        Self::Warn(s.to_string())
297    }
298
299    /// print [ERROR] `s`
300    pub fn error(s: &str) -> Self {
301        Self::Error(s.to_string())
302    }
303
304    /// print [PANIC] `s`
305    ///
306    /// Does not panic. Developer should call `panic!` macro separately.
307    pub fn panic(s: &str) -> Self {
308        Self::Panic(s.to_string())
309    }
310
311    /// Increment in progress bar
312    pub fn tick(n: usize) -> Self {
313        Self::Tick(n)
314    }
315}
316
317impl From<&str> for XvcOutputLine {
318    fn from(s: &str) -> Self {
319        Self::Output(s.to_string())
320    }
321}
322
323impl From<String> for XvcOutputLine {
324    fn from(s: String) -> Self {
325        Self::Output(s)
326    }
327}
328
329impl Display for XvcOutputLine {
330    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
331        match &self {
332            XvcOutputLine::Output(s) => writeln!(f, "{}", s),
333            XvcOutputLine::Info(s) => writeln!(f, "[INFO] {}", s),
334            XvcOutputLine::Debug(s) => writeln!(f, "[DEBUG] {}", s),
335            XvcOutputLine::Warn(s) => writeln!(f, "[WARN] {}", s),
336            XvcOutputLine::Error(s) => writeln!(f, "[ERROR] {}", s),
337            XvcOutputLine::Panic(s) => writeln!(f, "[PANIC] {}", s),
338            XvcOutputLine::Tick(n) => write!(f, "{}", ".".repeat(*n)),
339        }
340    }
341}