1use ansiterm::Colour;
4use std::str;
5use std::{env, io};
6use tracing::{Level, Metadata};
7pub use tracing_subscriber::{
8 self,
9 filter::{EnvFilter, LevelFilter},
10 fmt::{format::FmtSpan, MakeWriter},
11};
12
13const ACTION_COLUMN_WIDTH: usize = 12;
14
15fn get_action_indentation(action: &str) -> String {
17 if action.len() < ACTION_COLUMN_WIDTH {
18 " ".repeat(ACTION_COLUMN_WIDTH - action.len())
19 } else {
20 "".to_string()
21 }
22}
23
24pub fn println_label_green(label: &str, txt: &str) {
26 println_label(label, txt, Colour::Green);
27}
28
29pub fn println_action_green(action: &str, txt: &str) {
31 println_action(action, txt, Colour::Green);
32}
33
34pub fn println_label_red(label: &str, txt: &str) {
36 println_action(label, txt, Colour::Red);
37}
38
39fn println_label(label: &str, txt: &str, color: Colour) {
40 tracing::info!("{} {}", color.bold().paint(label), txt);
41}
42
43pub fn println_action_red(action: &str, txt: &str) {
45 println_action(action, txt, Colour::Red);
46}
47
48pub fn println_action_yellow(action: &str, txt: &str) {
50 println_action(action, txt, Colour::Yellow);
51}
52
53fn println_action(action: &str, txt: &str, color: Colour) {
54 tracing::info!(
55 "{}{} {}",
56 get_action_indentation(action),
57 color.bold().paint(action),
58 txt
59 );
60}
61
62pub fn println_warning(txt: &str) {
64 tracing::warn!("{}: {}", Colour::Yellow.paint("warning"), txt);
65}
66
67pub fn println_warning_verbose(txt: &str) {
69 tracing::debug!("{}: {}", Colour::Yellow.paint("warning"), txt);
70}
71
72pub fn println_error(txt: &str) {
74 tracing::warn!("{}: {}", Colour::Red.paint("error"), txt);
75}
76
77pub fn println_red(txt: &str) {
78 println_std_out(txt, Colour::Red);
79}
80
81pub fn println_green(txt: &str) {
82 println_std_out(txt, Colour::Green);
83}
84
85pub fn println_yellow(txt: &str) {
86 println_std_out(txt, Colour::Yellow);
87}
88
89pub fn println_green_bold(txt: &str) {
90 tracing::info!("{}", Colour::Green.bold().paint(txt));
91}
92
93pub fn println_yellow_bold(txt: &str) {
94 tracing::info!("{}", Colour::Yellow.bold().paint(txt));
95}
96
97pub fn println_yellow_err(txt: &str) {
98 println_std_err(txt, Colour::Yellow);
99}
100
101pub fn println_red_err(txt: &str) {
102 println_std_err(txt, Colour::Red);
103}
104
105fn println_std_out(txt: &str, color: Colour) {
106 tracing::info!("{}", color.paint(txt));
107}
108
109fn println_std_err(txt: &str, color: Colour) {
110 tracing::error!("{}", color.paint(txt));
111}
112
113const LOG_FILTER: &str = "RUST_LOG";
114
115pub struct StdioTracingWriter {
118 pub writer_mode: TracingWriterMode,
119}
120
121impl<'a> MakeWriter<'a> for StdioTracingWriter {
122 type Writer = Box<dyn io::Write>;
123
124 fn make_writer(&'a self) -> Self::Writer {
125 if self.writer_mode == TracingWriterMode::Stderr {
126 Box::new(io::stderr())
127 } else {
128 Box::new(io::stdout())
132 }
133 }
134
135 fn make_writer_for(&'a self, meta: &Metadata<'_>) -> Self::Writer {
136 if self.writer_mode == TracingWriterMode::Stderr
140 || (self.writer_mode == TracingWriterMode::Stdio && meta.level() <= &Level::WARN)
141 {
142 return Box::new(io::stderr());
143 }
144
145 Box::new(io::stdout())
147 }
148}
149
150#[derive(PartialEq, Eq)]
151pub enum TracingWriterMode {
152 Stdio,
154 Stdout,
156 Stderr,
158}
159
160#[derive(Default)]
161pub struct TracingSubscriberOptions {
162 pub verbosity: Option<u8>,
163 pub silent: Option<bool>,
164 pub log_level: Option<LevelFilter>,
165 pub writer_mode: Option<TracingWriterMode>,
166}
167
168pub fn init_tracing_subscriber(options: TracingSubscriberOptions) {
172 let env_filter = match env::var_os(LOG_FILTER) {
173 Some(_) => EnvFilter::try_from_default_env().expect("Invalid `RUST_LOG` provided"),
174 None => EnvFilter::new("info"),
175 };
176 let level_filter = options
177 .log_level
178 .or_else(|| {
179 options.verbosity.and_then(|verbosity| {
180 match verbosity {
181 1 => Some(LevelFilter::DEBUG), 2 => Some(LevelFilter::TRACE), _ => None,
184 }
185 })
186 })
187 .or_else(|| {
188 options
189 .silent
190 .and_then(|silent| if silent { Some(LevelFilter::OFF) } else { None })
191 });
192
193 let builder = tracing_subscriber::fmt::Subscriber::builder()
194 .with_env_filter(env_filter)
195 .with_ansi(true)
196 .with_level(false)
197 .with_file(false)
198 .with_line_number(false)
199 .without_time()
200 .with_target(false)
201 .with_writer(StdioTracingWriter {
202 writer_mode: options.writer_mode.unwrap_or(TracingWriterMode::Stdio),
203 });
204
205 if let Some(level_filter) = level_filter {
207 builder.with_max_level(level_filter).init();
208 } else {
209 builder.init();
210 }
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216 use tracing_test::traced_test;
217
218 #[traced_test]
219 #[test]
220 fn test_println_label_green() {
221 let txt = "main.sw";
222 println_label_green("Compiling", txt);
223
224 let expected_action = "\x1b[1;32mCompiling\x1b[0m";
225 assert!(logs_contain(&format!("{} {}", expected_action, txt)));
226 }
227
228 #[traced_test]
229 #[test]
230 fn test_println_label_red() {
231 let txt = "main.sw";
232 println_label_red("Error", txt);
233
234 let expected_action = "\x1b[1;31mError\x1b[0m";
235 assert!(logs_contain(&format!("{} {}", expected_action, txt)));
236 }
237
238 #[traced_test]
239 #[test]
240 fn test_println_action_green() {
241 let txt = "main.sw";
242 println_action_green("Compiling", txt);
243
244 let expected_action = "\x1b[1;32mCompiling\x1b[0m";
245 assert!(logs_contain(&format!(" {} {}", expected_action, txt)));
246 }
247
248 #[traced_test]
249 #[test]
250 fn test_println_action_green_long() {
251 let txt = "main.sw";
252 println_action_green("Supercalifragilistic", txt);
253
254 let expected_action = "\x1b[1;32mSupercalifragilistic\x1b[0m";
255 assert!(logs_contain(&format!("{} {}", expected_action, txt)));
256 }
257
258 #[traced_test]
259 #[test]
260 fn test_println_action_red() {
261 let txt = "main";
262 println_action_red("Removing", txt);
263
264 let expected_action = "\x1b[1;31mRemoving\x1b[0m";
265 assert!(logs_contain(&format!(" {} {}", expected_action, txt)));
266 }
267}