reth_tracing/
layers.rs

1use std::{
2    fmt,
3    path::{Path, PathBuf},
4};
5
6use rolling_file::{RollingConditionBasic, RollingFileAppender};
7use tracing_appender::non_blocking::WorkerGuard;
8use tracing_subscriber::{filter::Directive, EnvFilter, Layer, Registry};
9
10use crate::formatter::LogFormat;
11
12/// A worker guard returned by the file layer.
13///
14///  When a guard is dropped, all events currently in-memory are flushed to the log file this guard
15///  belongs to.
16pub type FileWorkerGuard = tracing_appender::non_blocking::WorkerGuard;
17
18///  A boxed tracing [Layer].
19pub(crate) type BoxedLayer<S> = Box<dyn Layer<S> + Send + Sync>;
20
21const RETH_LOG_FILE_NAME: &str = "reth.log";
22
23/// Default [directives](Directive) for [`EnvFilter`] which disables high-frequency debug logs from
24/// `hyper`, `hickory-resolver`, `jsonrpsee-server`, and `discv5`.
25const DEFAULT_ENV_FILTER_DIRECTIVES: [&str; 5] = [
26    "hyper::proto::h1=off",
27    "hickory_resolver=off",
28    "hickory_proto=off",
29    "discv5=off",
30    "jsonrpsee-server=off",
31];
32
33/// Manages the collection of layers for a tracing subscriber.
34///
35/// `Layers` acts as a container for different logging layers such as stdout, file, or journald.
36/// Each layer can be configured separately and then combined into a tracing subscriber.
37#[derive(Default)]
38pub struct Layers {
39    inner: Vec<BoxedLayer<Registry>>,
40}
41
42impl fmt::Debug for Layers {
43    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44        f.debug_struct("Layers").field("layers_count", &self.inner.len()).finish()
45    }
46}
47
48impl Layers {
49    /// Creates a new `Layers` instance.
50    pub fn new() -> Self {
51        Self::default()
52    }
53
54    /// Adds a layer to the collection of layers.
55    pub fn add_layer<L>(&mut self, layer: L)
56    where
57        L: Layer<Registry> + Send + Sync,
58    {
59        self.inner.push(layer.boxed());
60    }
61
62    /// Consumes the `Layers` instance, returning the inner vector of layers.
63    pub(crate) fn into_inner(self) -> Vec<BoxedLayer<Registry>> {
64        self.inner
65    }
66
67    /// Adds a journald layer to the layers collection.
68    ///
69    /// # Arguments
70    /// * `filter` - A string containing additional filter directives for this layer.
71    ///
72    /// # Returns
73    /// An `eyre::Result<()>` indicating the success or failure of the operation.
74    pub(crate) fn journald(&mut self, filter: &str) -> eyre::Result<()> {
75        let journald_filter = build_env_filter(None, filter)?;
76        let layer = tracing_journald::layer()?.with_filter(journald_filter);
77        self.add_layer(layer);
78        Ok(())
79    }
80
81    /// Adds a stdout layer with specified formatting and filtering.
82    ///
83    /// # Type Parameters
84    /// * `S` - The type of subscriber that will use these layers.
85    ///
86    /// # Arguments
87    /// * `format` - The log message format.
88    /// * `directive` - Directive for the default logging level.
89    /// * `filter` - Additional filter directives as a string.
90    /// * `color` - Optional color configuration for the log messages.
91    ///
92    /// # Returns
93    /// An `eyre::Result<()>` indicating the success or failure of the operation.
94    pub(crate) fn stdout(
95        &mut self,
96        format: LogFormat,
97        default_directive: Directive,
98        filters: &str,
99        color: Option<String>,
100    ) -> eyre::Result<()> {
101        let filter = build_env_filter(Some(default_directive), filters)?;
102        let layer = format.apply(filter, color, None);
103        self.add_layer(layer);
104        Ok(())
105    }
106
107    /// Adds a file logging layer to the layers collection.
108    ///
109    /// # Arguments
110    /// * `format` - The format for log messages.
111    /// * `filter` - Additional filter directives as a string.
112    /// * `file_info` - Information about the log file including path and rotation strategy.
113    ///
114    /// # Returns
115    /// An `eyre::Result<FileWorkerGuard>` representing the file logging worker.
116    pub(crate) fn file(
117        &mut self,
118        format: LogFormat,
119        filter: &str,
120        file_info: FileInfo,
121    ) -> eyre::Result<FileWorkerGuard> {
122        let (writer, guard) = file_info.create_log_writer();
123        let file_filter = build_env_filter(None, filter)?;
124        let layer = format.apply(file_filter, None, Some(writer));
125        self.add_layer(layer);
126        Ok(guard)
127    }
128}
129
130/// Holds configuration information for file logging.
131///
132/// Contains details about the log file's path, name, size, and rotation strategy.
133#[derive(Debug, Clone)]
134pub struct FileInfo {
135    dir: PathBuf,
136    file_name: String,
137    max_size_bytes: u64,
138    max_files: usize,
139}
140
141impl FileInfo {
142    /// Creates a new `FileInfo` instance.
143    pub fn new(dir: PathBuf, max_size_bytes: u64, max_files: usize) -> Self {
144        Self { dir, file_name: RETH_LOG_FILE_NAME.to_string(), max_size_bytes, max_files }
145    }
146
147    /// Creates the log directory if it doesn't exist.
148    ///
149    /// # Returns
150    /// A reference to the path of the log directory.
151    fn create_log_dir(&self) -> &Path {
152        let log_dir: &Path = self.dir.as_ref();
153        if !log_dir.exists() {
154            std::fs::create_dir_all(log_dir).expect("Could not create log directory");
155        }
156        log_dir
157    }
158
159    /// Creates a non-blocking writer for the log file.
160    ///
161    /// # Returns
162    /// A tuple containing the non-blocking writer and its associated worker guard.
163    fn create_log_writer(&self) -> (tracing_appender::non_blocking::NonBlocking, WorkerGuard) {
164        let log_dir = self.create_log_dir();
165        let (writer, guard) = tracing_appender::non_blocking(
166            RollingFileAppender::new(
167                log_dir.join(&self.file_name),
168                RollingConditionBasic::new().max_size(self.max_size_bytes),
169                self.max_files,
170            )
171            .expect("Could not initialize file logging"),
172        );
173        (writer, guard)
174    }
175}
176
177/// Builds an environment filter for logging.
178///
179/// The events are filtered by `default_directive`, unless overridden by `RUST_LOG`.
180///
181/// # Arguments
182/// * `default_directive` - An optional `Directive` that sets the default directive.
183/// * `directives` - Additional directives as a comma-separated string.
184///
185/// # Returns
186/// An `eyre::Result<EnvFilter>` that can be used to configure a tracing subscriber.
187fn build_env_filter(
188    default_directive: Option<Directive>,
189    directives: &str,
190) -> eyre::Result<EnvFilter> {
191    let env_filter = if let Some(default_directive) = default_directive {
192        EnvFilter::builder().with_default_directive(default_directive).from_env_lossy()
193    } else {
194        EnvFilter::builder().from_env_lossy()
195    };
196
197    DEFAULT_ENV_FILTER_DIRECTIVES
198        .into_iter()
199        .chain(directives.split(',').filter(|d| !d.is_empty()))
200        .try_fold(env_filter, |env_filter, directive| {
201            Ok(env_filter.add_directive(directive.parse()?))
202        })
203}