leo_lang/cli/helpers/
logger.rs

1// Copyright (C) 2019-2025 Provable Inc.
2// This file is part of the Leo library.
3
4// The Leo library is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// The Leo library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
16
17use leo_errors::Result;
18
19use colored::Colorize;
20use std::{fmt, sync::Once};
21use tracing::{event::Event, subscriber::Subscriber};
22use tracing_subscriber::{
23    FmtSubscriber,
24    fmt::{FmtContext, FormattedFields, format::*, time::*},
25    registry::LookupSpan,
26};
27
28static START: Once = Once::new();
29
30#[derive(Debug, Clone)]
31pub struct Format<F = Full, T = SystemTime> {
32    format: F,
33    #[allow(dead_code)] // todo: revisit this after merging span module
34    pub timer: T,
35    pub ansi: bool,
36    pub display_target: bool,
37    pub display_level: bool,
38    pub display_thread_id: bool,
39    pub display_thread_name: bool,
40}
41
42impl<F, T> Format<F, T> {
43    /// Use the given [`timer`] for log message timestamps.
44    ///
45    /// See [`time`] for the provided timer implementations.
46    ///
47    /// Note that using the `chrono` feature flag enables the
48    /// additional time formatters [`ChronoUtc`] and [`ChronoLocal`].
49    ///
50    /// [`time`]: ./time/index.html
51    /// [`timer`]: ./time/trait.FormatTime.html
52    /// [`ChronoUtc`]: ./time/struct.ChronoUtc.html
53    /// [`ChronoLocal`]: ./time/struct.ChronoLocal.html
54    pub fn with_timer<T2>(self, timer: T2) -> Format<F, T2> {
55        Format {
56            format: self.format,
57            timer,
58            ansi: self.ansi,
59            display_target: self.display_target,
60            display_level: self.display_level,
61            display_thread_id: self.display_thread_id,
62            display_thread_name: self.display_thread_name,
63        }
64    }
65
66    /// Do not emit timestamps with log messages.
67    pub fn without_time(self) -> Format<F, ()> {
68        Format {
69            format: self.format,
70            timer: (),
71            ansi: self.ansi,
72            display_target: self.display_target,
73            display_level: self.display_level,
74            display_thread_id: self.display_thread_id,
75            display_thread_name: self.display_thread_name,
76        }
77    }
78
79    /// Enable ANSI terminal colors for formatted output.
80    pub fn with_ansi(self, ansi: bool) -> Format<F, T> {
81        Format { ansi, ..self }
82    }
83
84    /// Sets whether or not an event's target is displayed.
85    pub fn with_target(self, display_target: bool) -> Format<F, T> {
86        Format { display_target, ..self }
87    }
88
89    /// Sets whether or not an event's level is displayed.
90    pub fn with_level(self, display_level: bool) -> Format<F, T> {
91        Format { display_level, ..self }
92    }
93
94    /// Sets whether or not the [thread ID] of the current thread is displayed
95    /// when formatting events
96    ///
97    /// [thread ID]: https://doc.rust-lang.org/stable/std/thread/struct.ThreadId.html
98    pub fn with_thread_ids(self, display_thread_id: bool) -> Format<F, T> {
99        Format { display_thread_id, ..self }
100    }
101
102    /// Sets whether or not the [name] of the current thread is displayed
103    /// when formatting events
104    ///
105    /// [name]: https://doc.rust-lang.org/stable/std/thread/index.html#naming-threads
106    pub fn with_thread_names(self, display_thread_name: bool) -> Format<F, T> {
107        Format { display_thread_name, ..self }
108    }
109}
110
111impl Default for Format<Full, SystemTime> {
112    fn default() -> Self {
113        Format {
114            format: Full,
115            timer: SystemTime,
116            ansi: true,
117            display_target: true,
118            display_level: true,
119            display_thread_id: false,
120            display_thread_name: false,
121        }
122    }
123}
124impl<S, N, T> FormatEvent<S, N> for Format<Full, T>
125where
126    S: Subscriber + for<'a> LookupSpan<'a>,
127    N: for<'a> FormatFields<'a> + 'static,
128    T: FormatTime,
129{
130    fn format_event(&self, context: &FmtContext<'_, S, N>, mut writer: Writer, event: &Event<'_>) -> fmt::Result {
131        let meta = event.metadata();
132
133        if self.display_level {
134            fn colored_string(level: &tracing::Level, message: &str) -> colored::ColoredString {
135                match *level {
136                    tracing::Level::ERROR => message.bold().red(),
137                    tracing::Level::WARN => message.bold().yellow(),
138                    tracing::Level::INFO => message.bold().cyan(),
139                    tracing::Level::DEBUG => message.bold().magenta(),
140                    tracing::Level::TRACE => message.bold(),
141                }
142            }
143
144            let mut message = "".to_string();
145
146            match context.lookup_current() {
147                Some(span_ref) => {
148                    let scope = span_ref.scope();
149
150                    for span in scope {
151                        message += span.metadata().name();
152
153                        let ext = span.extensions();
154                        let fields = &ext
155                            .get::<FormattedFields<N>>()
156                            .expect("Unable to find FormattedFields in extensions; this is a bug");
157                        if !fields.is_empty() {
158                            message = format!("{message} {{{fields}}}");
159                        }
160                    }
161                }
162                None => return Err(std::fmt::Error),
163            }
164
165            write!(&mut writer, "{:>10} ", colored_string(meta.level(), &message)).expect("Error writing event");
166        }
167
168        context.format_fields(writer.by_ref(), event)?;
169        writeln!(&mut writer)
170    }
171}
172
173/// Initialize logger with custom format and verbosity.
174pub fn init_logger(_app_name: &'static str, verbosity: usize) -> Result<()> {
175    // This line enables Windows 10 ANSI coloring API.
176    #[cfg(target_family = "windows")]
177    ansi_term::enable_ansi_support().map_err(|_| leo_errors::CliError::failed_to_enable_ansi_support())?;
178
179    use tracing_subscriber::fmt::writer::MakeWriterExt;
180
181    let stderr = std::io::stderr.with_max_level(tracing::Level::WARN);
182    let mk_writer = stderr.or_else(std::io::stdout);
183
184    let subscriber = FmtSubscriber::builder()
185        // all spans/events with a level higher than TRACE (e.g, debug, info, warn, etc.)
186        // will be written to stdout.
187        .with_max_level(match verbosity {
188            0 => tracing::Level::WARN,
189            1 => tracing::Level::INFO,
190            2 => tracing::Level::DEBUG,
191            _ => tracing::Level::TRACE
192        })
193        .with_writer(mk_writer)
194        .without_time()
195        .with_target(false)
196        .event_format(Format::default())
197        .finish();
198
199    // call this line only once per process. needed for tests using same thread
200    START.call_once(|| {
201        tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");
202    });
203    Ok(())
204}