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
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 #[cfg(feature = "otlp")]
135 pub fn with_span_layer(
136 &mut self,
137 otlp_config: OtlpConfig,
138 filter: EnvFilter,
139 ) -> eyre::Result<()> {
140 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#[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 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 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 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
204fn 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}