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