Skip to main content

reth_node_core/args/
log.rs

1//! clap [Args](clap::Args) for logging configuration.
2
3use 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
12/// Constant to convert megabytes to bytes
13const MB_TO_BYTES: u64 = 1024 * 1024;
14
15const PROFILER_TRACING_FILTER: &str = "debug";
16
17/// The log configuration.
18#[derive(Debug, Args)]
19#[command(next_help_heading = "Logging")]
20pub struct LogArgs {
21    /// The format to use for logs written to stdout.
22    #[arg(long = "log.stdout.format", value_name = "FORMAT", global = true, default_value_t = LogFormat::Terminal)]
23    pub log_stdout_format: LogFormat,
24
25    /// The filter to use for logs written to stdout.
26    #[arg(long = "log.stdout.filter", value_name = "FILTER", global = true, default_value = "")]
27    pub log_stdout_filter: String,
28
29    /// The format to use for logs written to the log file.
30    #[arg(long = "log.file.format", value_name = "FORMAT", global = true, default_value_t = LogFormat::Terminal)]
31    pub log_file_format: LogFormat,
32
33    /// The filter to use for logs written to the log file.
34    #[arg(long = "log.file.filter", value_name = "FILTER", global = true, default_value = "debug")]
35    pub log_file_filter: String,
36
37    /// The path to put log files in.
38    #[arg(long = "log.file.directory", value_name = "PATH", global = true, default_value_t)]
39    pub log_file_directory: PlatformPath<LogsDir>,
40
41    /// The prefix name of the log files.
42    #[arg(long = "log.file.name", value_name = "NAME", global = true, default_value = "reth.log")]
43    pub log_file_name: String,
44
45    /// The maximum size (in MB) of one log file.
46    #[arg(long = "log.file.max-size", value_name = "SIZE", global = true, default_value_t = 200)]
47    pub log_file_max_size: u64,
48
49    /// The maximum amount of log files that will be stored. If set to 0, background file logging
50    /// is disabled.
51    #[arg(long = "log.file.max-files", value_name = "COUNT", global = true, default_value_t = 5)]
52    pub log_file_max_files: usize,
53
54    /// Write logs to journald.
55    #[arg(long = "log.journald", global = true)]
56    pub journald: bool,
57
58    /// The filter to use for logs written to journald.
59    #[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    /// Emit traces to samply. Only useful when profiling.
68    #[arg(long = "log.samply", global = true, hide = true)]
69    pub samply: bool,
70
71    /// The filter to use for traces emitted to samply.
72    #[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    /// Emit traces to tracy. Only useful when profiling.
82    #[arg(long = "log.tracy", global = true, hide = true)]
83    pub tracy: bool,
84
85    /// The filter to use for traces emitted to tracy.
86    #[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    /// Sets whether or not the formatter emits ANSI terminal escape codes for colors and other
96    /// text formatting.
97    #[arg(
98        long,
99        value_name = "COLOR",
100        global = true,
101        default_value_t = ColorMode::Always
102    )]
103    pub color: ColorMode,
104
105    /// The verbosity settings for the tracer.
106    #[command(flatten)]
107    pub verbosity: Verbosity,
108}
109
110impl LogArgs {
111    /// Creates a [`LayerInfo`] instance.
112    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    /// File info from the current log options.
122    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    /// Initializes tracing with the configured options from cli args.
132    ///
133    /// Uses default layers for tracing. If you need to include custom layers,
134    /// use `init_tracing_with_layers` instead.
135    ///
136    /// Returns the file worker guard if a file worker was configured.
137    pub fn init_tracing(&self) -> eyre::Result<Option<FileWorkerGuard>> {
138        self.init_tracing_with_layers(Layers::new())
139    }
140
141    /// Initializes tracing with the configured options from cli args.
142    ///
143    /// Returns the file worker guard, and the file name, if a file worker was configured.
144    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/// The color mode for the cli.
186#[derive(Debug, Copy, Clone, ValueEnum, Eq, PartialEq)]
187pub enum ColorMode {
188    /// Colors on
189    Always,
190    /// Auto-detect
191    Auto,
192    /// Colors off
193    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/// The verbosity settings for the cli.
207#[derive(Debug, Copy, Clone, Args)]
208#[command(next_help_heading = "Display")]
209pub struct Verbosity {
210    /// Set the minimum log level.
211    ///
212    /// -v      Errors
213    /// -vv     Warnings
214    /// -vvv    Info
215    /// -vvvv   Debug
216    /// -vvvvv  Traces (warning: very verbose!)
217    #[arg(short, long, action = ArgAction::Count, global = true, default_value_t = 3, verbatim_doc_comment, help_heading = "Display")]
218    verbosity: u8,
219
220    /// Silence all log output.
221    #[arg(long, alias = "silent", short = 'q', global = true, help_heading = "Display")]
222    quiet: bool,
223}
224
225impl Verbosity {
226    /// Get the corresponding [Directive] for the given verbosity, or none if the verbosity
227    /// corresponds to silent.
228    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}