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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
//! Xvc logging and output crate to be used in output channels.
//! Xvc uses to discriminate outputs of various types, (Info, Debug, Error...) and can use
//! `crossbeam_channel` to send these separately.
//! Downstream crates (xvc, xvc-file, etc.) use this crate not to use stdout, stderr directly.
#![warn(missing_docs)]
#![forbid(unsafe_code)]
use log::LevelFilter;
use log::{error, info};
use std::env;
use std::path::Path;
use std::sync::Once;

/// Debugging macro to print the given expression and its value, with the module, function and line number
#[macro_export]
macro_rules! watch {
    ( $( $x:expr ),* ) => {
        {
            $(
               ::log::trace!("{}: {:#?}", stringify!($x), $x);
            )*
        }
    };
}

/// Logging Initializer
static INIT: Once = Once::new();

/// Init logging if it's not initialized before.
/// Uses [Once] to run (non-public fn) `init_logging` once.
pub fn setup_logging(term_level: Option<LevelFilter>, file_level: Option<LevelFilter>) {
    INIT.call_once(|| init_logging(term_level, file_level));
}

fn init_logging(term_level: Option<LevelFilter>, file_level: Option<LevelFilter>) {
    let logfilename = &format!("{}/xvc.log", env::temp_dir().to_string_lossy(),);

    let logfile = Path::new(&logfilename);

    let mut dispatch = fern::Dispatch::new().format(|out, message, record| {
        out.finish(format_args!(
            "[{}][{}:{}] {}",
            record.level(),
            record.file().get_or_insert("None"),
            record.line().get_or_insert(0),
            message
        ))
    });

    if let Some(level) = term_level {
        dispatch = dispatch.level(level).chain(std::io::stderr());
    }

    if let Some(level) = file_level {
        dispatch = dispatch
            .level(level)
            .chain(fern::log_file(logfilename).expect("Cannot set log filename"));
    }

    match dispatch.apply() {
        Ok(_) => {
            if let Some(level) = term_level {
                info!("Terminal logger enabled with level: {:?}", level);
            };
            if let Some(level) = file_level {
                info!(
                    "File logger enabled with level: {:?} to {:?}",
                    level, logfile
                );
            };
        }
        Err(err) => {
            error!("Error enabling logger: {:?}", err);
        }
    };
}

/// Different channels of outputs Xvc can print
#[derive(Clone, Debug)]
pub enum XvcOutputLine {
    /// The output that we should be reporting to user
    Output(String),
    /// For informational messages the user doesn't usually follow
    Info(String),
    /// Warnings that are against some usual workflows
    Warn(String),
    /// Errors that interrupts a workflow but may be recoverable
    Error(String),
    /// Panics that interrupts the workflow and ends the program
    /// Note that this doesn't call panic! automatically
    Panic(String),
    /// Progress bar ticks.
    /// Self::Info is also used for Tick(1)
    Tick(usize),
}

impl XvcOutputLine {
    /// print [INFO] `s`
    pub fn info(s: &str) -> Self {
        Self::Info(s.to_string())
    }
    /// print [WARN] `s`
    pub fn warn(s: &str) -> Self {
        Self::Warn(s.to_string())
    }

    /// print [ERROR] `s`
    pub fn error(s: &str) -> Self {
        Self::Error(s.to_string())
    }

    /// print [PANIC] `s`
    ///
    /// Does not panic. Developer should call `panic!` macro separately.
    pub fn panic(s: &str) -> Self {
        Self::Panic(s.to_string())
    }

    /// Increment in progress bar
    pub fn tick(n: usize) -> Self {
        Self::Tick(n)
    }
}

impl From<&str> for XvcOutputLine {
    fn from(s: &str) -> Self {
        Self::Output(s.to_string())
    }
}

impl From<String> for XvcOutputLine {
    fn from(s: String) -> Self {
        Self::Output(s)
    }
}