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};
10use tracing::{level_filters::LevelFilter, Level};
11
12const MB_TO_BYTES: u64 = 1024 * 1024;
14
15const PROFILER_TRACING_FILTER: &str = "debug";
16
17#[derive(Debug, Args)]
19#[command(next_help_heading = "Logging")]
20pub struct LogArgs {
21 #[arg(long = "log.stdout.format", value_name = "FORMAT", global = true, default_value_t = LogFormat::Terminal)]
23 pub log_stdout_format: LogFormat,
24
25 #[arg(long = "log.stdout.filter", value_name = "FILTER", global = true, default_value = "")]
27 pub log_stdout_filter: String,
28
29 #[arg(long = "log.file.format", value_name = "FORMAT", global = true, default_value_t = LogFormat::Terminal)]
31 pub log_file_format: LogFormat,
32
33 #[arg(long = "log.file.filter", value_name = "FILTER", global = true, default_value = "debug")]
35 pub log_file_filter: String,
36
37 #[arg(long = "log.file.directory", value_name = "PATH", global = true, default_value_t)]
39 pub log_file_directory: PlatformPath<LogsDir>,
40
41 #[arg(long = "log.file.name", value_name = "NAME", global = true, default_value = "reth.log")]
43 pub log_file_name: String,
44
45 #[arg(long = "log.file.max-size", value_name = "SIZE", global = true, default_value_t = 200)]
47 pub log_file_max_size: u64,
48
49 #[arg(long = "log.file.max-files", value_name = "COUNT", global = true)]
54 pub log_file_max_files: Option<usize>,
55
56 #[arg(long = "log.journald", global = true)]
58 pub journald: bool,
59
60 #[arg(
62 long = "log.journald.filter",
63 value_name = "FILTER",
64 global = true,
65 default_value = "error"
66 )]
67 pub journald_filter: String,
68
69 #[arg(long = "log.samply", global = true, hide = true)]
71 pub samply: bool,
72
73 #[arg(
75 long = "log.samply.filter",
76 value_name = "FILTER",
77 global = true,
78 default_value = PROFILER_TRACING_FILTER,
79 hide = true
80 )]
81 pub samply_filter: String,
82
83 #[arg(long = "log.tracy", global = true, hide = true)]
85 pub tracy: bool,
86
87 #[arg(
89 long = "log.tracy.filter",
90 value_name = "FILTER",
91 global = true,
92 default_value = PROFILER_TRACING_FILTER,
93 hide = true
94 )]
95 pub tracy_filter: String,
96
97 #[arg(
100 long,
101 value_name = "COLOR",
102 global = true,
103 default_value_t = ColorMode::Always
104 )]
105 pub color: ColorMode,
106
107 #[command(flatten)]
109 pub verbosity: Verbosity,
110}
111
112impl LogArgs {
113 pub const DEFAULT_MAX_LOG_FILES_NODE: usize = 5;
115
116 pub fn effective_log_file_max_files(&self) -> usize {
124 self.log_file_max_files.unwrap_or(0)
125 }
126
127 pub const fn apply_node_defaults(&mut self) {
130 if self.log_file_max_files.is_none() {
131 self.log_file_max_files = Some(Self::DEFAULT_MAX_LOG_FILES_NODE);
132 }
133 }
134
135 fn layer_info(&self, format: LogFormat, filter: String, use_color: bool) -> LayerInfo {
137 LayerInfo::new(
138 format,
139 self.verbosity.directive().to_string(),
140 filter,
141 use_color.then(|| self.color.to_string()),
142 )
143 }
144
145 fn file_info(&self) -> FileInfo {
147 FileInfo::new(
148 self.log_file_directory.clone().into(),
149 self.log_file_name.clone(),
150 self.log_file_max_size * MB_TO_BYTES,
151 self.effective_log_file_max_files(),
152 )
153 }
154
155 pub fn init_tracing(&self) -> eyre::Result<Option<FileWorkerGuard>> {
159 self.init_tracing_with_layers(Layers::new(), false)
160 }
161
162 pub fn init_tracing_with_layers(
173 &self,
174 layers: Layers,
175 enable_reload: bool,
176 ) -> eyre::Result<Option<FileWorkerGuard>> {
177 let mut tracer = RethTracer::new();
178
179 let stdout = self.layer_info(self.log_stdout_format, self.log_stdout_filter.clone(), true);
180 tracer = tracer.with_stdout(stdout);
181
182 if self.journald {
183 tracer = tracer.with_journald(self.journald_filter.clone());
184 }
185
186 if self.effective_log_file_max_files() > 0 {
187 let info = self.file_info();
188 let file = self.layer_info(self.log_file_format, self.log_file_filter.clone(), false);
189 tracer = tracer.with_file(file, info);
190 }
191
192 if self.samply {
193 let config = self.layer_info(LogFormat::Terminal, self.samply_filter.clone(), false);
194 tracer = tracer.with_samply(config);
195 }
196
197 if self.tracy {
198 #[cfg(feature = "tracy")]
199 {
200 let config = self.layer_info(LogFormat::Terminal, self.tracy_filter.clone(), false);
201 tracer = tracer.with_tracy(config);
202 }
203 #[cfg(not(feature = "tracy"))]
204 {
205 tracing::warn!("`--log.tracy` requested but `tracy` feature was not compiled in");
206 }
207 }
208
209 tracer.with_reload(enable_reload).init_with_layers(layers)
210 }
211}
212
213#[derive(Debug, Copy, Clone, ValueEnum, Eq, PartialEq)]
215pub enum ColorMode {
216 Always,
218 Auto,
220 Never,
222}
223
224impl Display for ColorMode {
225 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226 match self {
227 Self::Always => write!(f, "always"),
228 Self::Auto => write!(f, "auto"),
229 Self::Never => write!(f, "never"),
230 }
231 }
232}
233
234#[derive(Debug, Copy, Clone, Args)]
236#[command(next_help_heading = "Display")]
237pub struct Verbosity {
238 #[arg(short, long, action = ArgAction::Count, global = true, default_value_t = 3, verbatim_doc_comment, help_heading = "Display")]
246 verbosity: u8,
247
248 #[arg(long, alias = "silent", short = 'q', global = true, help_heading = "Display")]
250 quiet: bool,
251}
252
253impl Verbosity {
254 pub fn directive(&self) -> Directive {
257 if self.quiet {
258 LevelFilter::OFF.into()
259 } else {
260 let level = match self.verbosity - 1 {
261 0 => Level::ERROR,
262 1 => Level::WARN,
263 2 => Level::INFO,
264 3 => Level::DEBUG,
265 _ => Level::TRACE,
266 };
267
268 level.into()
269 }
270 }
271}