reth_tracing/
layers.rs

1use crate::{formatter::LogFormat, LayerInfo};
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    pub(crate) fn samply(&mut self, config: LayerInfo) -> eyre::Result<()> {
134        self.add_layer(
135            tracing_samply::SamplyLayer::new()
136                .map_err(|e| eyre::eyre!("Failed to create samply layer: {e}"))?
137                .with_filter(build_env_filter(
138                    Some(config.default_directive.parse()?),
139                    &config.filters,
140                )?),
141        );
142        Ok(())
143    }
144
145    /// Add OTLP spans layer to the layer collection
146    #[cfg(feature = "otlp")]
147    pub fn with_span_layer(
148        &mut self,
149        otlp_config: OtlpConfig,
150        filter: EnvFilter,
151    ) -> eyre::Result<()> {
152        // Create the span provider
153
154        let span_layer = span_layer(otlp_config)
155            .map_err(|e| eyre::eyre!("Failed to build OTLP span exporter {}", e))?
156            .with_filter(filter);
157
158        self.add_layer(span_layer);
159
160        Ok(())
161    }
162}
163
164/// Holds configuration information for file logging.
165///
166/// Contains details about the log file's path, name, size, and rotation strategy.
167#[derive(Debug, Clone)]
168pub struct FileInfo {
169    dir: PathBuf,
170    file_name: String,
171    max_size_bytes: u64,
172    max_files: usize,
173}
174
175impl FileInfo {
176    /// Creates a new `FileInfo` instance.
177    pub const fn new(
178        dir: PathBuf,
179        file_name: String,
180        max_size_bytes: u64,
181        max_files: usize,
182    ) -> Self {
183        Self { dir, file_name, max_size_bytes, max_files }
184    }
185
186    /// Creates the log directory if it doesn't exist.
187    ///
188    /// # Returns
189    /// A reference to the path of the log directory.
190    fn create_log_dir(&self) -> &Path {
191        let log_dir: &Path = self.dir.as_ref();
192        if !log_dir.exists() {
193            std::fs::create_dir_all(log_dir).expect("Could not create log directory");
194        }
195        log_dir
196    }
197
198    /// Creates a non-blocking writer for the log file.
199    ///
200    /// # Returns
201    /// A tuple containing the non-blocking writer and its associated worker guard.
202    fn create_log_writer(&self) -> (tracing_appender::non_blocking::NonBlocking, WorkerGuard) {
203        let log_dir = self.create_log_dir();
204        let (writer, guard) = tracing_appender::non_blocking(
205            RollingFileAppender::new(
206                log_dir.join(&self.file_name),
207                RollingConditionBasic::new().max_size(self.max_size_bytes),
208                self.max_files,
209            )
210            .expect("Could not initialize file logging"),
211        );
212        (writer, guard)
213    }
214}
215
216/// Builds an environment filter for logging.
217///
218/// The events are filtered by `default_directive`, unless overridden by `RUST_LOG`.
219///
220/// # Arguments
221/// * `default_directive` - An optional `Directive` that sets the default directive.
222/// * `directives` - Additional directives as a comma-separated string.
223///
224/// # Returns
225/// An `eyre::Result<EnvFilter>` that can be used to configure a tracing subscriber.
226fn build_env_filter(
227    default_directive: Option<Directive>,
228    directives: &str,
229) -> eyre::Result<EnvFilter> {
230    let env_filter = if let Some(default_directive) = default_directive {
231        EnvFilter::builder().with_default_directive(default_directive).from_env_lossy()
232    } else {
233        EnvFilter::builder().from_env_lossy()
234    };
235
236    DEFAULT_ENV_FILTER_DIRECTIVES
237        .into_iter()
238        .chain(directives.split(',').filter(|d| !d.is_empty()))
239        .try_fold(env_filter, |env_filter, directive| {
240            Ok(env_filter.add_directive(directive.parse()?))
241        })
242}