snarkos_aot/
cli.rs

1#[cfg(feature = "flame")]
2use std::fs::File;
3#[cfg(feature = "flame")]
4use std::io::BufWriter;
5use std::{io, path::PathBuf, thread};
6
7use anyhow::Result;
8#[cfg(any(feature = "clipages", feature = "mangen"))]
9use clap::CommandFactory;
10use clap::Parser;
11#[cfg(feature = "node")]
12use crossterm::tty::IsTty;
13use reqwest::Url;
14use tracing::level_filters::LevelFilter;
15use tracing_appender::non_blocking::WorkerGuard;
16use tracing_subscriber::{layer::SubscriberExt, reload, util::SubscriberInitExt, EnvFilter, Layer};
17
18#[cfg(feature = "node")]
19use crate::runner::Runner;
20use crate::{
21    accounts::GenAccounts, auth::AuthCommand, genesis::Genesis, ledger::Ledger,
22    program::ProgramCommand, Network,
23};
24
25#[derive(Debug, Parser)]
26#[clap(author = "MONADIC.US")]
27pub struct Cli<N: Network> {
28    #[arg(long)]
29    pub enable_profiling: bool,
30
31    /// The path to the log file.
32    #[arg(long)]
33    pub log: Option<PathBuf>,
34    /// The verbosity level of the logs.
35    #[arg(long, default_value_t = 4)]
36    pub verbosity: u8,
37    /// The optional loki url to send logs to.
38    #[arg(long)]
39    pub loki: Option<Url>,
40
41    #[clap(subcommand)]
42    pub command: Command<N>,
43}
44
45/// The different AOT commands.
46#[derive(Debug, Parser)]
47pub enum Command<N: Network> {
48    Genesis(Genesis<N>),
49    Accounts(GenAccounts),
50    Ledger(Ledger<N>),
51    #[cfg(feature = "node")]
52    Run(Runner<N>),
53    #[clap(subcommand)]
54    Auth(AuthCommand<N>),
55    #[clap(subcommand)]
56    Program(ProgramCommand<N>),
57    #[cfg(feature = "mangen")]
58    Man(snops_common::mangen::Mangen),
59    #[cfg(feature = "clipages")]
60    Md(snops_common::clipages::Clipages),
61}
62
63pub trait Flushable {
64    fn flush(&self);
65}
66
67impl Flushable for () {
68    fn flush(&self) {}
69}
70
71#[cfg(feature = "flame")]
72impl Flushable for tracing_flame::FlushGuard<BufWriter<File>> {
73    fn flush(&self) {
74        // Implementation for flushing if necessary
75    }
76}
77
78#[cfg(feature = "flame")]
79type FlameGuard = Box<dyn Flushable>;
80#[cfg(not(feature = "flame"))]
81type FlameGuard = ();
82
83pub type ReloadHandler = reload::Handle<EnvFilter, tracing_subscriber::Registry>;
84
85pub fn make_env_filter(verbosity: u8) -> EnvFilter {
86    let level = match verbosity {
87        0 => LevelFilter::INFO,
88        1 => LevelFilter::DEBUG,
89        2.. => LevelFilter::TRACE,
90    };
91
92    // Filter out undesirable logs. (unfortunately EnvFilter cannot be cloned)
93    {
94        let filter = tracing_subscriber::EnvFilter::builder()
95            .with_env_var("AOT_LOG")
96            .with_default_directive(level.into())
97            .from_env_lossy()
98            .add_directive("mio=off".parse().unwrap())
99            .add_directive("tarpc=off".parse().unwrap())
100            .add_directive("tokio_util=off".parse().unwrap())
101            .add_directive("tokio_tungstenite=off".parse().unwrap())
102            .add_directive("tracing_tungstenite=off".parse().unwrap())
103            .add_directive("tungstenite=off".parse().unwrap())
104            .add_directive("hyper=off".parse().unwrap())
105            .add_directive("reqwest=off".parse().unwrap())
106            .add_directive("want=off".parse().unwrap())
107            .add_directive("warp=off".parse().unwrap());
108
109        let filter = if verbosity >= 2 {
110            filter.add_directive("snarkos_node_sync=trace".parse().unwrap())
111        } else {
112            filter.add_directive("snarkos_node_sync=debug".parse().unwrap())
113        };
114
115        let filter = if verbosity >= 3 {
116            filter
117                .add_directive("snarkos_node_bft=trace".parse().unwrap())
118                .add_directive("snarkos_node_bft::gateway=debug".parse().unwrap())
119        } else {
120            filter.add_directive("snarkos_node_bft=debug".parse().unwrap())
121        };
122
123        let filter = if verbosity >= 4 {
124            filter.add_directive("snarkos_node_bft::gateway=trace".parse().unwrap())
125        } else {
126            filter.add_directive("snarkos_node_bft::gateway=debug".parse().unwrap())
127        };
128
129        let filter = if verbosity >= 5 {
130            filter.add_directive("snarkos_node_router=trace".parse().unwrap())
131        } else {
132            filter.add_directive("snarkos_node_router=debug".parse().unwrap())
133        };
134
135        if verbosity >= 6 {
136            filter.add_directive("snarkos_node_tcp=trace".parse().unwrap())
137        } else {
138            filter.add_directive("snarkos_node_tcp=off".parse().unwrap())
139        }
140    }
141}
142
143impl<N: Network> Cli<N> {
144    /// Initializes the logger.
145    ///
146    /// ```ignore
147    /// 0 => info
148    /// 1 => info, debug
149    /// 2 => info, debug, trace, snarkos_node_sync=trace
150    /// 3 => info, debug, trace, snarkos_node_bft=trace
151    /// 4 => info, debug, trace, snarkos_node_bft::gateway=trace
152    /// 5 => info, debug, trace, snarkos_node_router=trace
153    /// 6 => info, debug, trace, snarkos_node_tcp=trace
154    /// ```
155    pub fn init_logger(&self) -> (FlameGuard, Vec<WorkerGuard>, ReloadHandler) {
156        let verbosity = self.verbosity;
157
158        let (env_filter, reload_handler) = reload::Layer::new(make_env_filter(verbosity));
159
160        let mut layers = vec![];
161        let mut guards = vec![];
162
163        macro_rules! non_blocking_appender {
164            ($name:ident = ( $args:expr )) => {
165                let ($name, guard) = tracing_appender::non_blocking($args);
166                guards.push(guard);
167            };
168        }
169
170        if cfg!(not(feature = "flame")) && self.enable_profiling {
171            // TODO should be an error
172            panic!("Flame feature is not enabled");
173        }
174
175        #[cfg(feature = "flame")]
176        let guard = if self.enable_profiling {
177            let (flame_layer, guard) =
178                tracing_flame::FlameLayer::with_file("./tracing.folded").unwrap();
179            layers.push(flame_layer.boxed());
180            Box::new(guard) as Box<dyn Flushable>
181        } else {
182            Box::new(())
183        };
184
185        #[cfg(not(feature = "flame"))]
186        let guard = ();
187
188        if let Some(logfile) = self.log.as_ref() {
189            // Create the directories tree for a logfile if it doesn't exist.
190            let logfile_dir = logfile
191                .parent()
192                .expect("Root directory passed as a logfile");
193            if !logfile_dir.exists() {
194                std::fs::create_dir_all(logfile_dir)
195                .expect("Failed to create a directories: '{logfile_dir}', please check if user has permissions");
196            }
197
198            let file_appender = tracing_appender::rolling::daily(logfile_dir, logfile);
199            non_blocking_appender!(log_writer = (file_appender));
200
201            // Add layer redirecting logs to the file
202
203            layers.push(
204                tracing_subscriber::fmt::layer()
205                    .with_ansi(false)
206                    .with_thread_ids(true)
207                    .with_writer(log_writer)
208                    .boxed(),
209            )
210        };
211
212        // Initialize tracing.
213        // Add layer using LogWriter for stdout / terminal
214        match self.command {
215            #[cfg(feature = "node")]
216            Command::Run(_) => {
217                non_blocking_appender!(stdout = (io::stdout()));
218                layers.push(
219                    tracing_subscriber::fmt::layer()
220                        .with_ansi(io::stdout().is_tty())
221                        .with_thread_ids(true)
222                        .with_writer(stdout)
223                        .boxed(),
224                );
225            }
226            _ => {
227                non_blocking_appender!(stderr = (io::stderr()));
228                layers.push(tracing_subscriber::fmt::layer().with_writer(stderr).boxed());
229            }
230        }
231
232        if let Some(loki) = &self.loki {
233            let mut builder = tracing_loki::builder();
234
235            let env_var = std::env::var("SNOPS_LOKI_LABELS").ok();
236            let fields = match &env_var {
237                Some(var) => var
238                    .split(',')
239                    .map(|item| item.split_once('=').unwrap_or((item, "")))
240                    .collect(),
241                None => vec![],
242            };
243
244            for (key, value) in fields {
245                builder = builder.label(key, value).expect("bad loki label");
246            }
247
248            let (layer, task) = builder.build_url(loki.to_owned()).expect("bad loki url");
249            thread::spawn(|| {
250                let rt = tokio::runtime::Runtime::new().unwrap();
251                let handle = rt.spawn(task);
252                rt.block_on(handle).unwrap();
253            });
254            layers.push(layer.boxed());
255        };
256
257        tracing_subscriber::registry()
258            .with(env_filter)
259            .with(layers)
260            .init();
261        (guard, guards, reload_handler)
262    }
263
264    pub fn run(self) -> Result<()> {
265        let (_guard, _guards, log_level_handler) = self.init_logger();
266
267        match self.command {
268            Command::Accounts(command) => command.parse::<N>(),
269            Command::Genesis(command) => command.parse(),
270            Command::Ledger(command) => command.parse(log_level_handler),
271            #[cfg(feature = "node")]
272            Command::Run(command) => command.parse(log_level_handler),
273            Command::Auth(command) => command.parse(),
274            Command::Program(command) => command.parse(),
275            #[cfg(feature = "mangen")]
276            Command::Man(mangen) => mangen.run(
277                Cli::<N>::command(),
278                env!("CARGO_PKG_VERSION"),
279                env!("CARGO_PKG_NAME"),
280            ),
281            #[cfg(feature = "clipages")]
282            Command::Md(clipages) => clipages.run::<Cli<N>>(env!("CARGO_PKG_NAME")),
283        }
284    }
285}