1use crate::{formatter::LogFormat, LayerInfo};
2#[cfg(feature = "otlp-logs")]
3use reth_tracing_otlp::{log_layer, OtlpLogsConfig};
4#[cfg(feature = "otlp")]
5use reth_tracing_otlp::{span_layer, OtlpConfig};
6use rolling_file::{RollingConditionBasic, RollingFileAppender};
7use std::{
8 fmt,
9 path::{Path, PathBuf},
10};
11use tracing_appender::non_blocking::WorkerGuard;
12use tracing_subscriber::{filter::Directive, EnvFilter, Layer, Registry};
13
14pub type FileWorkerGuard = tracing_appender::non_blocking::WorkerGuard;
19
20pub(crate) type BoxedLayer<S> = Box<dyn Layer<S> + Send + Sync>;
22
23const DEFAULT_ENV_FILTER_DIRECTIVES: [&str; 9] = [
28 "hyper::proto::h1=off",
29 "hickory_resolver=off",
30 "hickory_proto=off",
31 "discv5=off",
32 "jsonrpsee-server=off",
33 "opentelemetry-otlp=warn",
34 "opentelemetry_sdk=warn",
35 "opentelemetry-http=warn",
36 "hyper_util::client::legacy::pool=off",
37];
38
39#[derive(Default)]
44pub struct Layers {
45 inner: Vec<BoxedLayer<Registry>>,
46}
47
48impl fmt::Debug for Layers {
49 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50 f.debug_struct("Layers").field("layers_count", &self.inner.len()).finish()
51 }
52}
53
54impl Layers {
55 pub fn new() -> Self {
57 Self::default()
58 }
59
60 pub fn add_layer<L>(&mut self, layer: L)
62 where
63 L: Layer<Registry> + Send + Sync,
64 {
65 self.inner.push(layer.boxed());
66 }
67
68 pub(crate) fn into_inner(self) -> Vec<BoxedLayer<Registry>> {
70 self.inner
71 }
72
73 pub(crate) fn journald(&mut self, filter: &str) -> eyre::Result<()> {
81 let journald_filter = build_env_filter(None, filter)?;
82 let layer = tracing_journald::layer()?.with_filter(journald_filter);
83 self.add_layer(layer);
84 Ok(())
85 }
86
87 pub(crate) fn stdout(
101 &mut self,
102 format: LogFormat,
103 default_directive: Directive,
104 filters: &str,
105 color: Option<String>,
106 ) -> eyre::Result<()> {
107 let filter = build_env_filter(Some(default_directive), filters)?;
108 let layer = format.apply(filter, color, None);
109 self.add_layer(layer);
110 Ok(())
111 }
112
113 pub(crate) fn file(
123 &mut self,
124 format: LogFormat,
125 filter: &str,
126 file_info: FileInfo,
127 ) -> eyre::Result<FileWorkerGuard> {
128 let (writer, guard) = file_info.create_log_writer()?;
129 let file_filter = build_env_filter(None, filter)?;
130 let layer = format.apply(file_filter, None, Some(writer));
131 self.add_layer(layer);
132 Ok(guard)
133 }
134
135 pub(crate) fn samply(&mut self, config: LayerInfo) -> eyre::Result<()> {
136 self.add_layer(
137 tracing_samply::SamplyLayer::new()
138 .map_err(|e| eyre::eyre!("Failed to create samply layer: {e}"))?
139 .with_filter(build_env_filter(
140 Some(config.default_directive.parse()?),
141 &config.filters,
142 )?),
143 );
144 Ok(())
145 }
146
147 #[cfg(feature = "tracy")]
148 pub(crate) fn tracy(&mut self, config: LayerInfo) -> eyre::Result<()> {
149 struct Config(tracing_subscriber::fmt::format::DefaultFields);
150 impl tracing_tracy::Config for Config {
151 type Formatter = tracing_subscriber::fmt::format::DefaultFields;
152 fn formatter(&self) -> &Self::Formatter {
153 &self.0
154 }
155 fn format_fields_in_zone_name(&self) -> bool {
156 false
157 }
158 }
159
160 self.add_layer(tracing_tracy::TracyLayer::new(Config(Default::default())).with_filter(
161 build_env_filter(Some(config.default_directive.parse()?), &config.filters)?,
162 ));
163 Ok(())
164 }
165
166 #[cfg(feature = "otlp")]
168 pub fn with_span_layer(
169 &mut self,
170 otlp_config: OtlpConfig,
171 filter: EnvFilter,
172 ) -> eyre::Result<()> {
173 let span_layer = span_layer(otlp_config)
176 .map_err(|e| eyre::eyre!("Failed to build OTLP span exporter {}", e))?
177 .with_filter(filter);
178
179 self.add_layer(span_layer);
180
181 Ok(())
182 }
183
184 #[cfg(feature = "otlp-logs")]
186 pub fn with_log_layer(
187 &mut self,
188 otlp_config: OtlpLogsConfig,
189 filter: EnvFilter,
190 ) -> eyre::Result<()> {
191 let log_layer = log_layer(otlp_config)
192 .map_err(|e| eyre::eyre!("Failed to build OTLP log exporter {}", e))?
193 .with_filter(filter);
194
195 self.add_layer(log_layer);
196
197 Ok(())
198 }
199}
200
201#[derive(Debug, Clone)]
205pub struct FileInfo {
206 dir: PathBuf,
207 file_name: String,
208 max_size_bytes: u64,
209 max_files: usize,
210}
211
212impl FileInfo {
213 pub const fn new(
215 dir: PathBuf,
216 file_name: String,
217 max_size_bytes: u64,
218 max_files: usize,
219 ) -> Self {
220 Self { dir, file_name, max_size_bytes, max_files }
221 }
222
223 fn create_log_dir(&self) -> eyre::Result<&Path> {
225 let log_dir: &Path = self.dir.as_ref();
226 if !log_dir.exists() {
227 std::fs::create_dir_all(log_dir)
228 .map_err(|err| eyre::eyre!("Could not create log directory {log_dir:?}: {err}"))?;
229 }
230 Ok(log_dir)
231 }
232
233 fn create_log_writer(
235 &self,
236 ) -> eyre::Result<(tracing_appender::non_blocking::NonBlocking, WorkerGuard)> {
237 let log_dir = self.create_log_dir()?;
238 let (writer, guard) = tracing_appender::non_blocking(
239 RollingFileAppender::new(
240 log_dir.join(&self.file_name),
241 RollingConditionBasic::new().max_size(self.max_size_bytes),
242 self.max_files,
243 )
244 .map_err(|err| eyre::eyre!("Could not initialize file logging: {err}"))?,
245 );
246 Ok((writer, guard))
247 }
248}
249
250fn build_env_filter(
261 default_directive: Option<Directive>,
262 directives: &str,
263) -> eyre::Result<EnvFilter> {
264 let env_filter = if let Some(default_directive) = default_directive {
265 EnvFilter::builder().with_default_directive(default_directive).from_env_lossy()
266 } else {
267 EnvFilter::builder().from_env_lossy()
268 };
269
270 DEFAULT_ENV_FILTER_DIRECTIVES
271 .into_iter()
272 .chain(directives.split(',').filter(|d| !d.is_empty()))
273 .try_fold(env_filter, |env_filter, directive| {
274 Ok(env_filter.add_directive(directive.parse()?))
275 })
276}