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
12pub type FileWorkerGuard = tracing_appender::non_blocking::WorkerGuard;
17
18pub(crate) type BoxedLayer<S> = Box<dyn Layer<S> + Send + Sync>;
20
21const 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#[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 pub fn new() -> Self {
55 Self::default()
56 }
57
58 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 pub(crate) fn into_inner(self) -> Vec<BoxedLayer<Registry>> {
68 self.inner
69 }
70
71 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 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 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 #[cfg(feature = "otlp")]
147 pub fn with_span_layer(
148 &mut self,
149 otlp_config: OtlpConfig,
150 filter: EnvFilter,
151 ) -> eyre::Result<()> {
152 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#[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 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 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 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
216fn 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}