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, default_value_t = 5)]
52 pub log_file_max_files: usize,
53
54 #[arg(long = "log.journald", global = true)]
56 pub journald: bool,
57
58 #[arg(
60 long = "log.journald.filter",
61 value_name = "FILTER",
62 global = true,
63 default_value = "error"
64 )]
65 pub journald_filter: String,
66
67 #[arg(long = "log.samply", global = true, hide = true)]
69 pub samply: bool,
70
71 #[arg(
73 long = "log.samply.filter",
74 value_name = "FILTER",
75 global = true,
76 default_value = PROFILER_TRACING_FILTER,
77 hide = true
78 )]
79 pub samply_filter: String,
80
81 #[arg(long = "log.tracy", global = true, hide = true)]
83 pub tracy: bool,
84
85 #[arg(
87 long = "log.tracy.filter",
88 value_name = "FILTER",
89 global = true,
90 default_value = PROFILER_TRACING_FILTER,
91 hide = true
92 )]
93 pub tracy_filter: String,
94
95 #[arg(
98 long,
99 value_name = "COLOR",
100 global = true,
101 default_value_t = ColorMode::Always
102 )]
103 pub color: ColorMode,
104
105 #[command(flatten)]
107 pub verbosity: Verbosity,
108}
109
110impl LogArgs {
111 fn layer_info(&self, format: LogFormat, filter: String, use_color: bool) -> LayerInfo {
113 LayerInfo::new(
114 format,
115 self.verbosity.directive().to_string(),
116 filter,
117 use_color.then(|| self.color.to_string()),
118 )
119 }
120
121 fn file_info(&self) -> FileInfo {
123 FileInfo::new(
124 self.log_file_directory.clone().into(),
125 self.log_file_name.clone(),
126 self.log_file_max_size * MB_TO_BYTES,
127 self.log_file_max_files,
128 )
129 }
130
131 pub fn init_tracing(&self) -> eyre::Result<Option<FileWorkerGuard>> {
138 self.init_tracing_with_layers(Layers::new())
139 }
140
141 pub fn init_tracing_with_layers(
145 &self,
146 layers: Layers,
147 ) -> eyre::Result<Option<FileWorkerGuard>> {
148 let mut tracer = RethTracer::new();
149
150 let stdout = self.layer_info(self.log_stdout_format, self.log_stdout_filter.clone(), true);
151 tracer = tracer.with_stdout(stdout);
152
153 if self.journald {
154 tracer = tracer.with_journald(self.journald_filter.clone());
155 }
156
157 if self.log_file_max_files > 0 {
158 let info = self.file_info();
159 let file = self.layer_info(self.log_file_format, self.log_file_filter.clone(), false);
160 tracer = tracer.with_file(file, info);
161 }
162
163 if self.samply {
164 let config = self.layer_info(LogFormat::Terminal, self.samply_filter.clone(), false);
165 tracer = tracer.with_samply(config);
166 }
167
168 if self.tracy {
169 #[cfg(feature = "tracy")]
170 {
171 let config = self.layer_info(LogFormat::Terminal, self.tracy_filter.clone(), false);
172 tracer = tracer.with_tracy(config);
173 }
174 #[cfg(not(feature = "tracy"))]
175 {
176 tracing::warn!("`--log.tracy` requested but `tracy` feature was not compiled in");
177 }
178 }
179
180 let guard = tracer.init_with_layers(layers)?;
181 Ok(guard)
182 }
183}
184
185#[derive(Debug, Copy, Clone, ValueEnum, Eq, PartialEq)]
187pub enum ColorMode {
188 Always,
190 Auto,
192 Never,
194}
195
196impl Display for ColorMode {
197 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
198 match self {
199 Self::Always => write!(f, "always"),
200 Self::Auto => write!(f, "auto"),
201 Self::Never => write!(f, "never"),
202 }
203 }
204}
205
206#[derive(Debug, Copy, Clone, Args)]
208#[command(next_help_heading = "Display")]
209pub struct Verbosity {
210 #[arg(short, long, action = ArgAction::Count, global = true, default_value_t = 3, verbatim_doc_comment, help_heading = "Display")]
218 verbosity: u8,
219
220 #[arg(long, alias = "silent", short = 'q', global = true, help_heading = "Display")]
222 quiet: bool,
223}
224
225impl Verbosity {
226 pub fn directive(&self) -> Directive {
229 if self.quiet {
230 LevelFilter::OFF.into()
231 } else {
232 let level = match self.verbosity - 1 {
233 0 => Level::ERROR,
234 1 => Level::WARN,
235 2 => Level::INFO,
236 3 => Level::DEBUG,
237 _ => Level::TRACE,
238 };
239
240 level.into()
241 }
242 }
243}