Skip to main content

snarkos_cli/helpers/
logger.rs

1// Copyright (c) 2019-2025 Provable Inc.
2// This file is part of the snarkOS library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use crate::helpers::{DynamicFormatter, LogWriter};
17
18use anyhow::{Result, bail};
19
20use crossterm::tty::IsTty;
21use std::{
22    fs::File,
23    io,
24    path::Path,
25    str::FromStr,
26    sync::{Arc, atomic::AtomicBool},
27};
28use tokio::sync::mpsc;
29use tracing_subscriber::{
30    EnvFilter,
31    layer::{Layer, SubscriberExt},
32    util::SubscriberInitExt,
33};
34
35fn parse_log_verbosity(verbosity: u8) -> Result<EnvFilter> {
36    // First, set default log verbosity.
37    // Note, that this must not be prefixed with `RUST_LOG=`.
38    let default_log_str = match verbosity {
39        0 => "info",
40        1 => "debug",
41        2.. => "trace",
42    };
43    let filter = EnvFilter::from_str(default_log_str).unwrap();
44
45    // Now, set rules for specific crates.
46    let filter = if verbosity >= 2 {
47        filter.add_directive("snarkos_node_sync=trace".parse().unwrap())
48    } else if verbosity >= 1 {
49        filter.add_directive("snarkos_node_sync=debug".parse().unwrap())
50    } else {
51        filter
52    };
53
54    let filter = if verbosity >= 3 {
55        filter.add_directive("snarkos_node_bft=trace".parse().unwrap())
56    } else if verbosity >= 1 {
57        filter.add_directive("snarkos_node_bft=debug".parse().unwrap())
58    } else {
59        filter
60    };
61
62    let filter = if verbosity >= 4 {
63        filter.add_directive("snarkos_node_bft::gateway=trace".parse().unwrap())
64    } else if verbosity >= 1 {
65        filter.add_directive("snarkos_node_bft::gateway=debug".parse().unwrap())
66    } else {
67        filter
68    };
69
70    let filter = if verbosity >= 4 {
71        // At high log levels, also show warnings of third-party crates.
72        filter
73            .add_directive("mio=warn".parse().unwrap())
74            .add_directive("tokio_util=warn".parse().unwrap())
75            .add_directive("hyper=warn".parse().unwrap())
76            .add_directive("reqwest=warn".parse().unwrap())
77            .add_directive("want=warn".parse().unwrap())
78            .add_directive("h2=warn".parse().unwrap())
79            .add_directive("tower=warn".parse().unwrap())
80            .add_directive("axum=warn".parse().unwrap())
81            .add_directive("ureq=warn".parse().unwrap())
82            .add_directive("rustls=warn".parse().unwrap())
83    } else {
84        // Disable logs from third-party crates by default.
85        filter
86            .add_directive("mio=off".parse().unwrap())
87            .add_directive("tokio_util=off".parse().unwrap())
88            .add_directive("hyper=off".parse().unwrap())
89            .add_directive("reqwest=off".parse().unwrap())
90            .add_directive("want=off".parse().unwrap())
91            .add_directive("h2=off".parse().unwrap())
92            .add_directive("tower=off".parse().unwrap())
93            .add_directive("axum=off".parse().unwrap())
94            .add_directive("ureq=off".parse().unwrap())
95            .add_directive("rustls=off".parse().unwrap())
96    };
97
98    let filter = if verbosity >= 5 {
99        filter.add_directive("snarkos_node_router=trace".parse().unwrap())
100    } else if verbosity >= 1 {
101        filter.add_directive("snarkos_node_router=debug".parse().unwrap())
102    } else {
103        filter
104    };
105
106    let filter = if verbosity >= 6 {
107        filter.add_directive("snarkos_node_tcp=trace".parse().unwrap())
108    } else {
109        filter.add_directive("snarkos_node_tcp=off".parse().unwrap())
110    };
111
112    Ok(filter)
113}
114
115fn parse_log_filter(filter_str: &str) -> Result<EnvFilter> {
116    EnvFilter::from_str(filter_str).map_err(|err| err.into())
117}
118
119/// Sets the log filter based on the given verbosity level.
120/// Initializes the logger with the specified verbosity level, where 0 is the lowest verbosity and 6 the highest.
121///
122/// The following shows what messages are enabled at each level.
123/// ```ignore
124/// 0 => info
125/// 1 => info, debug
126/// 2 => info, debug, trace, snarkos_node_sync=trace
127/// 3 => info, debug, trace, snarkos_node_bft=trace
128/// 4 => info, debug, trace, snarkos_node_bft::gateway=trace,
129///      [mio|tokio_util|hyper|reqwest|want|h2|tower|axum]=warn
130/// 5 => info, debug, trace, snarkos_node_router=trace
131/// 6 => info, debug, trace, snarkos_node_tcp=trace
132/// ```
133pub fn initialize_logger<P: AsRef<Path>>(
134    verbosity: u8,
135    log_filter: &Option<String>,
136    nodisplay: bool,
137    logfile: P,
138    shutdown: Arc<AtomicBool>,
139) -> Result<mpsc::Receiver<Vec<u8>>> {
140    let [stdout_filter, logfile_filter] = std::array::from_fn(|_| {
141        if let Some(filter) = log_filter { parse_log_filter(filter) } else { parse_log_verbosity(verbosity) }
142    });
143
144    // Create the directories tree for a logfile if it doesn't exist.
145    let Some(logfile_dir) = logfile.as_ref().parent() else { bail!("Root directory passed as a logfile") };
146
147    if !logfile_dir.exists() {
148        if let Err(err) = std::fs::create_dir_all(logfile_dir) {
149            bail!("Failed to create a directory: '{}' ({err})", logfile_dir.display());
150        }
151    }
152    // Create a file to write logs to.
153    let logfile = match File::options().append(true).create(true).open(logfile) {
154        Ok(logfile) => logfile,
155        Err(err) => bail!("Failed to open the file for writing logs: {err}"),
156    };
157
158    // Initialize the log channel.
159    let (log_sender, log_receiver) = mpsc::channel(1024);
160
161    // Initialize the log sender.
162    // This is only needed when the terminal UI (display) wants to process log output.
163    let log_sender = match nodisplay {
164        true => None,
165        false => Some(log_sender),
166    };
167
168    // At high verbosity or when there is a custom log filter we show the target
169    // of the log event, i.e., the file/module where the log message was created.
170    let show_target = verbosity > 2 || log_filter.is_some();
171
172    // Attach tracing-subscriber.
173    let layered = tracing_subscriber::registry()
174        .with(
175            // Add layer using LogWriter for stdout / terminal
176            tracing_subscriber::fmt::Layer::default()
177                .with_ansi(log_sender.is_none() && io::stdout().is_tty())
178                .with_writer(move || LogWriter::new(&log_sender))
179                .with_target(show_target)
180                .event_format(DynamicFormatter::new(shutdown))
181                .with_filter(stdout_filter?),
182        )
183        .with(
184            // Add layer redirecting logs to the file
185            tracing_subscriber::fmt::Layer::default()
186                .with_ansi(false)
187                .with_writer(logfile)
188                .with_target(show_target)
189                .with_filter(logfile_filter?),
190        );
191
192    // Attach console-subscriber, if enabled.
193    #[cfg(feature = "tokio_console")]
194    let layered = layered.with(console_subscriber::spawn());
195
196    // Initialize tracing.
197    let _ = layered.try_init();
198
199    Ok(log_receiver)
200}
201
202/// Set up only terminal logging
203pub fn initialize_terminal_logger(verbosity: u8) -> Result<()> {
204    let stdout_filter = parse_log_verbosity(verbosity)?;
205
206    // At high verbosity or when there is a custom log filter we show the target
207    // of the log event, i.e., the file/module where the log message was created.
208    let show_target = verbosity > 2;
209
210    // Initialize tracing.
211    let _ = tracing_subscriber::registry()
212        .with(
213            // Add layer using LogWriter for stdout / terminal
214            tracing_subscriber::fmt::Layer::default()
215                .with_ansi(io::stdout().is_tty())
216                .with_target(show_target)
217                .event_format(DynamicFormatter::new(Arc::new(AtomicBool::new(false))))
218                .with_filter(stdout_filter),
219        )
220        .try_init();
221
222    Ok(())
223}
224
225/// Returns the welcome message as a string.
226pub fn welcome_message() -> String {
227    use colored::Colorize;
228
229    let mut output = String::new();
230    output += &r#"
231
232         ╦╬╬╬╬╬╦
233        ╬╬╬╬╬╬╬╬╬                    ▄▄▄▄        ▄▄▄
234       ╬╬╬╬╬╬╬╬╬╬╬                  ▐▓▓▓▓▌       ▓▓▓
235      ╬╬╬╬╬╬╬╬╬╬╬╬╬                ▐▓▓▓▓▓▓▌      ▓▓▓     ▄▄▄▄▄▄       ▄▄▄▄▄▄
236     ╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬              ▐▓▓▓  ▓▓▓▌     ▓▓▓   ▄▓▓▀▀▀▀▓▓▄   ▐▓▓▓▓▓▓▓▓▌
237    ╬╬╬╬╬╬╬╜ ╙╬╬╬╬╬╬╬            ▐▓▓▓▌  ▐▓▓▓▌    ▓▓▓  ▐▓▓▓▄▄▄▄▓▓▓▌ ▐▓▓▓    ▓▓▓▌
238   ╬╬╬╬╬╬╣     ╠╬╬╬╬╬╬           ▓▓▓▓▓▓▓▓▓▓▓▓    ▓▓▓  ▐▓▓▀▀▀▀▀▀▀▀▘ ▐▓▓▓    ▓▓▓▌
239  ╬╬╬╬╬╬╣       ╠╬╬╬╬╬╬         ▓▓▓▓▌    ▐▓▓▓▓   ▓▓▓   ▀▓▓▄▄▄▄▓▓▀   ▐▓▓▓▓▓▓▓▓▌
240 ╬╬╬╬╬╬╣         ╠╬╬╬╬╬╬       ▝▀▀▀▀      ▀▀▀▀▘  ▀▀▀     ▀▀▀▀▀▀       ▀▀▀▀▀▀
241╚╬╬╬╬╬╩           ╩╬╬╬╬╩
242
243
244"#
245    .white()
246    .bold();
247    output += &"👋 Welcome to Aleo! We thank you for running a node and supporting privacy.\n".bold();
248    output
249}
250
251#[cfg(test)]
252mod tests {
253    use super::*;
254
255    #[test]
256    fn log_filter() {
257        let result = parse_log_filter("=");
258        assert!(result.is_err(), "must disallow invalid log filter");
259
260        let result = parse_log_filter("snarkos=trace");
261        assert!(result.is_ok(), "must allow valid log filter");
262    }
263}