1use crate::dirs::{LogsDir, PlatformPath};
4use clap::{ArgAction, Args, ValueEnum};
5use reth_tracing::{
6 tracing_subscriber::filter::Directive, FileInfo, LayerInfo, Layers, LogFormat, RethTracer,
7 Tracer, TracingGuards,
8};
9use std::{fmt, fmt::Display, path::PathBuf, sync::OnceLock};
10use tracing::{level_filters::LevelFilter, Level};
11
12const MB_TO_BYTES: u64 = 1024 * 1024;
14
15const PROFILER_TRACING_FILTER: &str = "debug";
16
17static LOG_DEFAULTS: OnceLock<DefaultLogArgs> = OnceLock::new();
19
20#[derive(Debug, Args)]
22#[command(next_help_heading = "Logging")]
23pub struct LogArgs {
24 #[arg(long = "log.stdout.format", value_name = "FORMAT", global = true, default_value_t = DefaultLogArgs::get_global().log_stdout_format)]
26 pub log_stdout_format: LogFormat,
27
28 #[arg(long = "log.stdout.filter", value_name = "FILTER", global = true, default_value_t = DefaultLogArgs::get_global().log_stdout_filter.clone())]
30 pub log_stdout_filter: String,
31
32 #[arg(long = "log.file.format", value_name = "FORMAT", global = true, default_value_t = DefaultLogArgs::get_global().log_file_format)]
34 pub log_file_format: LogFormat,
35
36 #[arg(long = "log.file.filter", value_name = "FILTER", global = true, default_value_t = DefaultLogArgs::get_global().log_file_filter.clone())]
38 pub log_file_filter: String,
39
40 #[arg(long = "log.file.directory", value_name = "PATH", global = true, default_value_t)]
42 pub log_file_directory: PlatformPath<LogsDir>,
43
44 #[arg(long = "log.file.name", value_name = "NAME", global = true, default_value_t = DefaultLogArgs::get_global().log_file_name.clone())]
46 pub log_file_name: String,
47
48 #[arg(long = "log.file.max-size", value_name = "SIZE", global = true, default_value_t = DefaultLogArgs::get_global().log_file_max_size)]
50 pub log_file_max_size: u64,
51
52 #[arg(long = "log.file.max-files", value_name = "COUNT", global = true)]
57 pub log_file_max_files: Option<usize>,
58
59 #[arg(long = "log.journald", global = true, default_value_t = DefaultLogArgs::get_global().journald)]
61 pub journald: bool,
62
63 #[arg(
65 long = "log.journald.filter",
66 value_name = "FILTER",
67 global = true,
68 default_value_t = DefaultLogArgs::get_global().journald_filter.clone()
69 )]
70 pub journald_filter: String,
71
72 #[arg(long = "log.samply", global = true, hide = true, default_value_t = DefaultLogArgs::get_global().samply)]
74 pub samply: bool,
75
76 #[arg(
78 long = "log.samply.filter",
79 value_name = "FILTER",
80 global = true,
81 default_value_t = DefaultLogArgs::get_global().samply_filter.clone(),
82 hide = true
83 )]
84 pub samply_filter: String,
85
86 #[arg(long = "log.tracing-chrome", global = true, hide = true, default_value_t = DefaultLogArgs::get_global().tracing_chrome)]
88 pub tracing_chrome: bool,
89
90 #[arg(
92 long = "log.tracing-chrome.file",
93 value_name = "PATH",
94 global = true,
95 default_value = "trace.json",
96 hide = true
97 )]
98 pub tracing_chrome_file: PathBuf,
99
100 #[arg(
102 long = "log.tracing-chrome.filter",
103 value_name = "FILTER",
104 global = true,
105 default_value_t = DefaultLogArgs::get_global().tracing_chrome_filter.clone(),
106 hide = true
107 )]
108 pub tracing_chrome_filter: String,
109
110 #[arg(long = "log.tracy", global = true, hide = true, default_value_t = DefaultLogArgs::get_global().tracy)]
112 pub tracy: bool,
113
114 #[arg(
116 long = "log.tracy.filter",
117 value_name = "FILTER",
118 global = true,
119 default_value_t = DefaultLogArgs::get_global().tracy_filter.clone(),
120 hide = true
121 )]
122 pub tracy_filter: String,
123
124 #[arg(
127 long,
128 value_name = "COLOR",
129 global = true,
130 default_value_t = DefaultLogArgs::get_global().color
131 )]
132 pub color: ColorMode,
133
134 #[command(flatten)]
136 pub verbosity: Verbosity,
137}
138
139impl LogArgs {
140 pub const DEFAULT_MAX_LOG_FILES_NODE: usize = 5;
142
143 pub fn effective_log_file_max_files(&self) -> usize {
151 self.log_file_max_files.unwrap_or(0)
152 }
153
154 pub const fn apply_node_defaults(&mut self) {
157 if self.log_file_max_files.is_none() {
158 self.log_file_max_files = Some(Self::DEFAULT_MAX_LOG_FILES_NODE);
159 }
160 }
161
162 fn layer_info(&self, format: LogFormat, filter: String, use_color: bool) -> LayerInfo {
164 LayerInfo::new(
165 format,
166 self.verbosity.directive().to_string(),
167 filter,
168 use_color.then(|| self.color.to_string()),
169 )
170 }
171
172 fn file_info(&self) -> FileInfo {
174 FileInfo::new(
175 self.log_file_directory.clone().into(),
176 self.log_file_name.clone(),
177 self.log_file_max_size * MB_TO_BYTES,
178 self.effective_log_file_max_files(),
179 )
180 }
181
182 pub fn init_tracing(&self) -> eyre::Result<TracingGuards> {
186 self.init_tracing_with_layers(Layers::new(), false)
187 }
188
189 pub fn init_tracing_with_layers(
200 &self,
201 layers: Layers,
202 enable_reload: bool,
203 ) -> eyre::Result<TracingGuards> {
204 let mut tracer = RethTracer::new();
205
206 let stdout = self.layer_info(self.log_stdout_format, self.log_stdout_filter.clone(), true);
207 tracer = tracer.with_stdout(stdout);
208
209 if self.journald {
210 tracer = tracer.with_journald(self.journald_filter.clone());
211 }
212
213 if self.effective_log_file_max_files() > 0 {
214 let info = self.file_info();
215 let file = self.layer_info(self.log_file_format, self.log_file_filter.clone(), false);
216 tracer = tracer.with_file(file, info);
217 }
218
219 if self.samply {
220 let config = self.layer_info(LogFormat::Terminal, self.samply_filter.clone(), false);
221 tracer = tracer.with_samply(config);
222 }
223
224 if self.tracing_chrome {
225 let config =
226 self.layer_info(LogFormat::Terminal, self.tracing_chrome_filter.clone(), false);
227 tracer = tracer.with_chrome(config, self.tracing_chrome_file.clone());
228 }
229
230 if self.tracy {
231 #[cfg(feature = "tracy")]
232 {
233 let config = self.layer_info(LogFormat::Terminal, self.tracy_filter.clone(), false);
234 tracer = tracer.with_tracy(config);
235 }
236 #[cfg(not(feature = "tracy"))]
237 {
238 tracing::warn!("`--log.tracy` requested but `tracy` feature was not compiled in");
239 }
240 }
241
242 tracer.with_reload(enable_reload).init_with_layers(layers)
243 }
244}
245
246#[derive(Debug, Clone)]
250pub struct DefaultLogArgs {
251 log_stdout_format: LogFormat,
252 log_stdout_filter: String,
253 log_file_format: LogFormat,
254 log_file_filter: String,
255 log_file_name: String,
256 log_file_max_size: u64,
257 journald: bool,
258 journald_filter: String,
259 samply: bool,
260 samply_filter: String,
261 tracing_chrome: bool,
262 tracing_chrome_filter: String,
263 tracy: bool,
264 tracy_filter: String,
265 color: ColorMode,
266}
267
268impl DefaultLogArgs {
269 pub fn try_init(self) -> Result<(), Self> {
271 LOG_DEFAULTS.set(self)
272 }
273
274 pub fn get_global() -> &'static Self {
276 LOG_DEFAULTS.get_or_init(Self::default)
277 }
278
279 pub const fn with_log_stdout_format(mut self, v: LogFormat) -> Self {
281 self.log_stdout_format = v;
282 self
283 }
284
285 pub fn with_log_stdout_filter(mut self, v: String) -> Self {
287 self.log_stdout_filter = v;
288 self
289 }
290
291 pub const fn with_log_file_format(mut self, v: LogFormat) -> Self {
293 self.log_file_format = v;
294 self
295 }
296
297 pub fn with_log_file_filter(mut self, v: String) -> Self {
299 self.log_file_filter = v;
300 self
301 }
302
303 pub fn with_log_file_name(mut self, v: String) -> Self {
305 self.log_file_name = v;
306 self
307 }
308
309 pub const fn with_log_file_max_size(mut self, v: u64) -> Self {
311 self.log_file_max_size = v;
312 self
313 }
314
315 pub const fn with_journald(mut self, v: bool) -> Self {
317 self.journald = v;
318 self
319 }
320
321 pub fn with_journald_filter(mut self, v: String) -> Self {
323 self.journald_filter = v;
324 self
325 }
326
327 pub const fn with_samply(mut self, v: bool) -> Self {
329 self.samply = v;
330 self
331 }
332
333 pub fn with_samply_filter(mut self, v: String) -> Self {
335 self.samply_filter = v;
336 self
337 }
338
339 pub const fn with_tracing_chrome(mut self, v: bool) -> Self {
341 self.tracing_chrome = v;
342 self
343 }
344
345 pub fn with_tracing_chrome_filter(mut self, v: String) -> Self {
347 self.tracing_chrome_filter = v;
348 self
349 }
350
351 pub const fn with_tracy(mut self, v: bool) -> Self {
353 self.tracy = v;
354 self
355 }
356
357 pub fn with_tracy_filter(mut self, v: String) -> Self {
359 self.tracy_filter = v;
360 self
361 }
362
363 pub const fn with_color(mut self, v: ColorMode) -> Self {
365 self.color = v;
366 self
367 }
368}
369
370impl Default for DefaultLogArgs {
371 fn default() -> Self {
372 Self {
373 log_stdout_format: LogFormat::Terminal,
374 log_stdout_filter: String::new(),
375 log_file_format: LogFormat::Terminal,
376 log_file_filter: "debug".to_string(),
377 log_file_name: "reth.log".to_string(),
378 log_file_max_size: 200,
379 journald: false,
380 journald_filter: "error".to_string(),
381 samply: false,
382 samply_filter: PROFILER_TRACING_FILTER.to_string(),
383 tracing_chrome: false,
384 tracing_chrome_filter: PROFILER_TRACING_FILTER.to_string(),
385 tracy: false,
386 tracy_filter: PROFILER_TRACING_FILTER.to_string(),
387 color: ColorMode::Always,
388 }
389 }
390}
391
392#[derive(Debug, Copy, Clone, ValueEnum, Eq, PartialEq)]
394pub enum ColorMode {
395 Always,
397 Auto,
399 Never,
401}
402
403impl Display for ColorMode {
404 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
405 match self {
406 Self::Always => write!(f, "always"),
407 Self::Auto => write!(f, "auto"),
408 Self::Never => write!(f, "never"),
409 }
410 }
411}
412
413#[derive(Debug, Copy, Clone, Args)]
415#[command(next_help_heading = "Display")]
416pub struct Verbosity {
417 #[arg(short, long, action = ArgAction::Count, global = true, default_value_t = 3, verbatim_doc_comment, help_heading = "Display")]
425 verbosity: u8,
426
427 #[arg(long, alias = "silent", short = 'q', global = true, help_heading = "Display")]
429 quiet: bool,
430}
431
432impl Verbosity {
433 pub fn directive(&self) -> Directive {
436 if self.quiet {
437 LevelFilter::OFF.into()
438 } else {
439 let level = match self.verbosity - 1 {
440 0 => Level::ERROR,
441 1 => Level::WARN,
442 2 => Level::INFO,
443 3 => Level::DEBUG,
444 _ => Level::TRACE,
445 };
446
447 level.into()
448 }
449 }
450}