git_cliff/
logger.rs

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