1use crate::dirs::{LogsDir, PlatformPath};
4use clap::{ArgAction, Args, ValueEnum};
5use reth_tracing::{
6 tracing_subscriber::filter::Directive, FileInfo, FileWorkerGuard, LayerInfo, Layers, LogFormat,
7 RethTracer, Tracer,
8};
9use std::{fmt, fmt::Display, 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.tracy", global = true, hide = true, default_value_t = DefaultLogArgs::get_global().tracy)]
88 pub tracy: bool,
89
90 #[arg(
92 long = "log.tracy.filter",
93 value_name = "FILTER",
94 global = true,
95 default_value_t = DefaultLogArgs::get_global().tracy_filter.clone(),
96 hide = true
97 )]
98 pub tracy_filter: String,
99
100 #[arg(
103 long,
104 value_name = "COLOR",
105 global = true,
106 default_value_t = DefaultLogArgs::get_global().color
107 )]
108 pub color: ColorMode,
109
110 #[command(flatten)]
112 pub verbosity: Verbosity,
113}
114
115impl LogArgs {
116 pub const DEFAULT_MAX_LOG_FILES_NODE: usize = 5;
118
119 pub fn effective_log_file_max_files(&self) -> usize {
127 self.log_file_max_files.unwrap_or(0)
128 }
129
130 pub const fn apply_node_defaults(&mut self) {
133 if self.log_file_max_files.is_none() {
134 self.log_file_max_files = Some(Self::DEFAULT_MAX_LOG_FILES_NODE);
135 }
136 }
137
138 fn layer_info(&self, format: LogFormat, filter: String, use_color: bool) -> LayerInfo {
140 LayerInfo::new(
141 format,
142 self.verbosity.directive().to_string(),
143 filter,
144 use_color.then(|| self.color.to_string()),
145 )
146 }
147
148 fn file_info(&self) -> FileInfo {
150 FileInfo::new(
151 self.log_file_directory.clone().into(),
152 self.log_file_name.clone(),
153 self.log_file_max_size * MB_TO_BYTES,
154 self.effective_log_file_max_files(),
155 )
156 }
157
158 pub fn init_tracing(&self) -> eyre::Result<Option<FileWorkerGuard>> {
162 self.init_tracing_with_layers(Layers::new(), false)
163 }
164
165 pub fn init_tracing_with_layers(
176 &self,
177 layers: Layers,
178 enable_reload: bool,
179 ) -> eyre::Result<Option<FileWorkerGuard>> {
180 let mut tracer = RethTracer::new();
181
182 let stdout = self.layer_info(self.log_stdout_format, self.log_stdout_filter.clone(), true);
183 tracer = tracer.with_stdout(stdout);
184
185 if self.journald {
186 tracer = tracer.with_journald(self.journald_filter.clone());
187 }
188
189 if self.effective_log_file_max_files() > 0 {
190 let info = self.file_info();
191 let file = self.layer_info(self.log_file_format, self.log_file_filter.clone(), false);
192 tracer = tracer.with_file(file, info);
193 }
194
195 if self.samply {
196 let config = self.layer_info(LogFormat::Terminal, self.samply_filter.clone(), false);
197 tracer = tracer.with_samply(config);
198 }
199
200 if self.tracy {
201 #[cfg(feature = "tracy")]
202 {
203 let config = self.layer_info(LogFormat::Terminal, self.tracy_filter.clone(), false);
204 tracer = tracer.with_tracy(config);
205 }
206 #[cfg(not(feature = "tracy"))]
207 {
208 tracing::warn!("`--log.tracy` requested but `tracy` feature was not compiled in");
209 }
210 }
211
212 tracer.with_reload(enable_reload).init_with_layers(layers)
213 }
214}
215
216#[derive(Debug, Clone)]
220pub struct DefaultLogArgs {
221 log_stdout_format: LogFormat,
222 log_stdout_filter: String,
223 log_file_format: LogFormat,
224 log_file_filter: String,
225 log_file_name: String,
226 log_file_max_size: u64,
227 journald: bool,
228 journald_filter: String,
229 samply: bool,
230 samply_filter: String,
231 tracy: bool,
232 tracy_filter: String,
233 color: ColorMode,
234}
235
236impl DefaultLogArgs {
237 pub fn try_init(self) -> Result<(), Self> {
239 LOG_DEFAULTS.set(self)
240 }
241
242 pub fn get_global() -> &'static Self {
244 LOG_DEFAULTS.get_or_init(Self::default)
245 }
246
247 pub const fn with_log_stdout_format(mut self, v: LogFormat) -> Self {
249 self.log_stdout_format = v;
250 self
251 }
252
253 pub fn with_log_stdout_filter(mut self, v: String) -> Self {
255 self.log_stdout_filter = v;
256 self
257 }
258
259 pub const fn with_log_file_format(mut self, v: LogFormat) -> Self {
261 self.log_file_format = v;
262 self
263 }
264
265 pub fn with_log_file_filter(mut self, v: String) -> Self {
267 self.log_file_filter = v;
268 self
269 }
270
271 pub fn with_log_file_name(mut self, v: String) -> Self {
273 self.log_file_name = v;
274 self
275 }
276
277 pub const fn with_log_file_max_size(mut self, v: u64) -> Self {
279 self.log_file_max_size = v;
280 self
281 }
282
283 pub const fn with_journald(mut self, v: bool) -> Self {
285 self.journald = v;
286 self
287 }
288
289 pub fn with_journald_filter(mut self, v: String) -> Self {
291 self.journald_filter = v;
292 self
293 }
294
295 pub const fn with_samply(mut self, v: bool) -> Self {
297 self.samply = v;
298 self
299 }
300
301 pub fn with_samply_filter(mut self, v: String) -> Self {
303 self.samply_filter = v;
304 self
305 }
306
307 pub const fn with_tracy(mut self, v: bool) -> Self {
309 self.tracy = v;
310 self
311 }
312
313 pub fn with_tracy_filter(mut self, v: String) -> Self {
315 self.tracy_filter = v;
316 self
317 }
318
319 pub const fn with_color(mut self, v: ColorMode) -> Self {
321 self.color = v;
322 self
323 }
324}
325
326impl Default for DefaultLogArgs {
327 fn default() -> Self {
328 Self {
329 log_stdout_format: LogFormat::Terminal,
330 log_stdout_filter: String::new(),
331 log_file_format: LogFormat::Terminal,
332 log_file_filter: "debug".to_string(),
333 log_file_name: "reth.log".to_string(),
334 log_file_max_size: 200,
335 journald: false,
336 journald_filter: "error".to_string(),
337 samply: false,
338 samply_filter: PROFILER_TRACING_FILTER.to_string(),
339 tracy: false,
340 tracy_filter: PROFILER_TRACING_FILTER.to_string(),
341 color: ColorMode::Always,
342 }
343 }
344}
345
346#[derive(Debug, Copy, Clone, ValueEnum, Eq, PartialEq)]
348pub enum ColorMode {
349 Always,
351 Auto,
353 Never,
355}
356
357impl Display for ColorMode {
358 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
359 match self {
360 Self::Always => write!(f, "always"),
361 Self::Auto => write!(f, "auto"),
362 Self::Never => write!(f, "never"),
363 }
364 }
365}
366
367#[derive(Debug, Copy, Clone, Args)]
369#[command(next_help_heading = "Display")]
370pub struct Verbosity {
371 #[arg(short, long, action = ArgAction::Count, global = true, default_value_t = 3, verbatim_doc_comment, help_heading = "Display")]
379 verbosity: u8,
380
381 #[arg(long, alias = "silent", short = 'q', global = true, help_heading = "Display")]
383 quiet: bool,
384}
385
386impl Verbosity {
387 pub fn directive(&self) -> Directive {
390 if self.quiet {
391 LevelFilter::OFF.into()
392 } else {
393 let level = match self.verbosity - 1 {
394 0 => Level::ERROR,
395 1 => Level::WARN,
396 2 => Level::INFO,
397 3 => Level::DEBUG,
398 _ => Level::TRACE,
399 };
400
401 level.into()
402 }
403 }
404}