reth_tracing/
layers.rs

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