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    ///
52    /// Default: 5 for `node` command, 0 for non-node utility subcommands.
53    #[arg(long = "log.file.max-files", value_name = "COUNT", global = true)]
54    pub log_file_max_files: Option<usize>,
55
56    /// Write logs to journald.
57    #[arg(long = "log.journald", global = true)]
58    pub journald: bool,
59
60    /// The filter to use for logs written to journald.
61    #[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    /// Emit traces to samply. Only useful when profiling.
70    #[arg(long = "log.samply", global = true, hide = true)]
71    pub samply: bool,
72
73    /// The filter to use for traces emitted to samply.
74    #[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    /// Emit traces to tracy. Only useful when profiling.
84    #[arg(long = "log.tracy", global = true, hide = true)]
85    pub tracy: bool,
86
87    /// The filter to use for traces emitted to tracy.
88    #[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    /// Sets whether or not the formatter emits ANSI terminal escape codes for colors and other
98    /// text formatting.
99    #[arg(
100        long,
101        value_name = "COLOR",
102        global = true,
103        default_value_t = ColorMode::Always
104    )]
105    pub color: ColorMode,
106
107    /// The verbosity settings for the tracer.
108    #[command(flatten)]
109    pub verbosity: Verbosity,
110}
111
112impl LogArgs {
113    /// The default number of log files for the `node` subcommand.
114    pub const DEFAULT_MAX_LOG_FILES_NODE: usize = 5;
115
116    /// Returns the effective maximum number of log files.
117    ///
118    /// If `log_file_max_files` was explicitly set, returns that value.
119    /// Otherwise returns 0 (file logging disabled).
120    ///
121    /// Note: Callers should apply the node-specific default (5) before calling
122    /// `init_tracing` if the command is the `node` subcommand.
123    pub fn effective_log_file_max_files(&self) -> usize {
124        self.log_file_max_files.unwrap_or(0)
125    }
126
127    /// Applies the default `log_file_max_files` value for the `node` subcommand
128    /// if not explicitly set by the user.
129    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    /// Creates a [`LayerInfo`] instance.
136    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    /// File info from the current log options.
146    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    /// Initializes tracing with the configured options from cli args.
156    ///
157    /// Returns the file worker guard if a file worker was configured.
158    pub fn init_tracing(&self) -> eyre::Result<Option<FileWorkerGuard>> {
159        self.init_tracing_with_layers(Layers::new(), false)
160    }
161
162    /// Initializes tracing with the configured options from cli args.
163    ///
164    /// When `enable_reload` is true, a global log handle is installed that allows changing
165    /// log levels at runtime via RPC methods like `debug_verbosity` and `debug_vmodule`.
166    ///
167    /// # Arguments
168    /// * `layers` - Pre-configured layers to include
169    /// * `enable_reload` - If true, enables runtime log level changes
170    ///
171    /// Returns the file worker guard if a file worker was configured.
172    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/// The color mode for the cli.
214#[derive(Debug, Copy, Clone, ValueEnum, Eq, PartialEq)]
215pub enum ColorMode {
216    /// Colors on
217    Always,
218    /// Auto-detect
219    Auto,
220    /// Colors off
221    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/// The verbosity settings for the cli.
235#[derive(Debug, Copy, Clone, Args)]
236#[command(next_help_heading = "Display")]
237pub struct Verbosity {
238    /// Set the minimum log level.
239    ///
240    /// -v      Errors
241    /// -vv     Warnings
242    /// -vvv    Info
243    /// -vvvv   Debug
244    /// -vvvvv  Traces (warning: very verbose!)
245    #[arg(short, long, action = ArgAction::Count, global = true, default_value_t = 3, verbatim_doc_comment, help_heading = "Display")]
246    verbosity: u8,
247
248    /// Silence all log output.
249    #[arg(long, alias = "silent", short = 'q', global = true, help_heading = "Display")]
250    quiet: bool,
251}
252
253impl Verbosity {
254    /// Get the corresponding [Directive] for the given verbosity, or none if the verbosity
255    /// corresponds to silent.
256    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}