reth_tracing/
layers.rs

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