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 #[arg(long)]
33 pub log: Option<PathBuf>,
34 #[arg(long, default_value_t = 4)]
36 pub verbosity: u8,
37 #[arg(long)]
39 pub loki: Option<Url>,
40
41 #[clap(subcommand)]
42 pub command: Command<N>,
43}
44
45#[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 }
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 {
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 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 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 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 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 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}