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