git_cliff/
logger.rs

1use std::io::Write;
2use std::sync::atomic::{AtomicUsize, Ordering};
3use std::{env, fmt};
4
5use env_logger::Builder;
6use env_logger::fmt::{Color, Style, StyledValue};
7use git_cliff_core::error::{Error, Result};
8#[cfg(feature = "remote")]
9use indicatif::{ProgressBar, ProgressStyle};
10use log::Level;
11
12/// Environment variable to use for the logger.
13const LOGGER_ENV: &str = "RUST_LOG";
14
15/// Global variable for storing the maximum width of the modules.
16static MAX_MODULE_WIDTH: AtomicUsize = AtomicUsize::new(0);
17
18/// Wrapper for the padded values.
19struct Padded<T> {
20    value: T,
21    width: usize,
22}
23
24impl<T: fmt::Display> fmt::Display for Padded<T> {
25    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
26        write!(f, "{: <width$}", self.value, width = self.width)
27    }
28}
29
30/// Returns the max width of the target.
31fn max_target_width(target: &str) -> usize {
32    let max_width = MAX_MODULE_WIDTH.load(Ordering::Relaxed);
33    if max_width < target.len() {
34        MAX_MODULE_WIDTH.store(target.len(), Ordering::Relaxed);
35        target.len()
36    } else {
37        max_width
38    }
39}
40
41/// Adds colors to the given level and returns it.
42fn colored_level(style: &mut Style, level: Level) -> StyledValue<'_, &'static str> {
43    match level {
44        Level::Trace => style.set_color(Color::Magenta).value("TRACE"),
45        Level::Debug => style.set_color(Color::Blue).value("DEBUG"),
46        Level::Info => style.set_color(Color::Green).value("INFO "),
47        Level::Warn => style.set_color(Color::Yellow).value("WARN "),
48        Level::Error => style.set_color(Color::Red).value("ERROR"),
49    }
50}
51
52#[cfg(feature = "remote")]
53lazy_static::lazy_static! {
54    /// Lazily initialized progress bar.
55    pub static ref PROGRESS_BAR: ProgressBar = {
56        let progress_bar = ProgressBar::new_spinner();
57        progress_bar.set_style(
58            ProgressStyle::with_template("{spinner:.green} {msg}")
59                .unwrap()
60                .tick_strings(&[
61                    "▹▹▹▹▹",
62                    "▸▹▹▹▹",
63                    "▹▸▹▹▹",
64                    "▹▹▸▹▹",
65                    "▹▹▹▸▹",
66                    "▹▹▹▹▸",
67                    "▪▪▪▪▪",
68                ]),
69        );
70        progress_bar
71    };
72}
73
74/// Initializes the global logger.
75///
76/// This method also creates a progress bar which is triggered
77/// by the network operations that are related to GitHub.
78#[allow(unreachable_code, clippy::needless_return)]
79pub fn init() -> Result<()> {
80    let mut builder = Builder::new();
81    builder.format(move |f, record| {
82        let target = record.target();
83        let max_width = max_target_width(target);
84
85        let mut style = f.style();
86        let level = colored_level(&mut style, record.level());
87
88        let mut style = f.style();
89        let target = style.set_bold(true).value(Padded {
90            value: target,
91            width: max_width,
92        });
93
94        #[cfg(feature = "github")]
95        {
96            let message = record.args().to_string();
97            if message.starts_with(git_cliff_core::remote::github::START_FETCHING_MSG) {
98                PROGRESS_BAR.enable_steady_tick(std::time::Duration::from_millis(80));
99                PROGRESS_BAR.set_message(message);
100                return Ok(());
101            } else if message.starts_with(git_cliff_core::remote::github::FINISHED_FETCHING_MSG) {
102                PROGRESS_BAR.finish_and_clear();
103                return Ok(());
104            }
105        }
106
107        #[cfg(feature = "gitlab")]
108        {
109            let message = record.args().to_string();
110            if message.starts_with(git_cliff_core::remote::gitlab::START_FETCHING_MSG) {
111                PROGRESS_BAR.enable_steady_tick(std::time::Duration::from_millis(80));
112                PROGRESS_BAR.set_message(message);
113                return Ok(());
114            } else if message.starts_with(git_cliff_core::remote::gitlab::FINISHED_FETCHING_MSG) {
115                PROGRESS_BAR.finish_and_clear();
116                return Ok(());
117            }
118        }
119
120        #[cfg(feature = "gitea")]
121        {
122            let message = record.args().to_string();
123            if message.starts_with(git_cliff_core::remote::gitea::START_FETCHING_MSG) {
124                PROGRESS_BAR.enable_steady_tick(std::time::Duration::from_millis(80));
125                PROGRESS_BAR.set_message(message);
126                return Ok(());
127            } else if message.starts_with(git_cliff_core::remote::gitea::FINISHED_FETCHING_MSG) {
128                PROGRESS_BAR.finish_and_clear();
129                return Ok(());
130            }
131        }
132
133        #[cfg(feature = "bitbucket")]
134        {
135            let message = record.args().to_string();
136            if message.starts_with(git_cliff_core::remote::bitbucket::START_FETCHING_MSG) {
137                PROGRESS_BAR.enable_steady_tick(std::time::Duration::from_millis(80));
138                PROGRESS_BAR.set_message(message);
139                return Ok(());
140            } else if message.starts_with(git_cliff_core::remote::bitbucket::FINISHED_FETCHING_MSG)
141            {
142                PROGRESS_BAR.finish_and_clear();
143                return Ok(());
144            }
145        }
146
147        writeln!(f, " {} {} > {}", level, target, record.args())
148    });
149
150    if let Ok(var) = env::var(LOGGER_ENV) {
151        builder.parse_filters(&var);
152    }
153
154    builder
155        .try_init()
156        .map_err(|e| Error::LoggerError(e.to_string()))
157}