1use crate::{formatter::LogFormat, LayerInfo, LogFilterReloadHandle};
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, reload, 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; 11] = [
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 "rustls=warn",
38 "tungstenite=warn",
39];
40
41#[derive(Default)]
46pub struct Layers {
47 inner: Vec<BoxedLayer<Registry>>,
48}
49
50impl fmt::Debug for Layers {
51 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52 f.debug_struct("Layers").field("layers_count", &self.inner.len()).finish()
53 }
54}
55
56impl Layers {
57 pub fn new() -> Self {
59 Self::default()
60 }
61
62 pub fn add_layer<L>(&mut self, layer: L)
64 where
65 L: Layer<Registry> + Send + Sync,
66 {
67 self.inner.push(layer.boxed());
68 }
69
70 pub(crate) fn into_inner(self) -> Vec<BoxedLayer<Registry>> {
72 self.inner
73 }
74
75 pub(crate) fn journald(&mut self, filter: &str) -> eyre::Result<()> {
83 let journald_filter = build_env_filter(None, filter)?;
84 let layer = tracing_journald::layer()?.with_filter(journald_filter);
85 self.add_layer(layer);
86 Ok(())
87 }
88
89 pub(crate) fn stdout(
103 &mut self,
104 format: LogFormat,
105 default_directive: Directive,
106 filters: &str,
107 color: Option<String>,
108 reloadable: bool,
109 ) -> eyre::Result<Option<LogFilterReloadHandle>> {
110 let filter = build_env_filter(Some(default_directive), filters)?;
111
112 let show_target = reloadable ||
116 filter.max_level_hint().is_none_or(|max_level| max_level > tracing::Level::INFO);
117
118 if reloadable {
119 let (reloadable_filter, handle) = reload::Layer::new(filter);
120 let layer = format.apply(reloadable_filter, color, show_target, None);
121 self.add_layer(layer);
122 Ok(Some(handle))
123 } else {
124 let layer = format.apply(filter, color, show_target, None);
125 self.add_layer(layer);
126 Ok(None)
127 }
128 }
129
130 pub(crate) fn file(
143 &mut self,
144 format: LogFormat,
145 filter: &str,
146 file_info: FileInfo,
147 reloadable: bool,
148 ) -> eyre::Result<(FileWorkerGuard, Option<LogFilterReloadHandle>)> {
149 let (writer, guard) = file_info.create_log_writer()?;
150 let file_filter = build_env_filter(None, filter)?;
151
152 if reloadable {
153 let (reloadable_filter, handle) = reload::Layer::new(file_filter);
154 self.add_layer(format.apply(reloadable_filter, None, true, Some(writer)));
155 Ok((guard, Some(handle)))
156 } else {
157 self.add_layer(format.apply(file_filter, None, true, Some(writer)));
158 Ok((guard, None))
159 }
160 }
161
162 pub(crate) fn samply(&mut self, config: LayerInfo) -> eyre::Result<()> {
163 self.add_layer(
164 tracing_samply::SamplyLayer::new()
165 .map_err(|e| eyre::eyre!("Failed to create samply layer: {e}"))?
166 .with_filter(build_env_filter(
167 Some(config.default_directive.parse()?),
168 &config.filters,
169 )?),
170 );
171 Ok(())
172 }
173
174 #[cfg(feature = "tracy")]
175 pub(crate) fn tracy(&mut self, config: LayerInfo) -> eyre::Result<()> {
176 struct TracyFields(tracing_subscriber::fmt::format::DefaultFields);
181 impl<'writer> tracing_subscriber::fmt::FormatFields<'writer> for TracyFields {
182 fn format_fields<R: tracing_subscriber::field::RecordFields>(
183 &self,
184 writer: tracing_subscriber::fmt::format::Writer<'writer>,
185 fields: R,
186 ) -> core::fmt::Result {
187 self.0.format_fields(writer, fields)
188 }
189 }
190
191 struct Config(TracyFields);
192 impl tracing_tracy::Config for Config {
193 type Formatter = TracyFields;
194 fn formatter(&self) -> &Self::Formatter {
195 &self.0
196 }
197 fn format_fields_in_zone_name(&self) -> bool {
198 false
199 }
200 }
201
202 self.add_layer(
203 tracing_tracy::TracyLayer::new(Config(TracyFields(Default::default()))).with_filter(
204 build_env_filter(Some(config.default_directive.parse()?), &config.filters)?,
205 ),
206 );
207 Ok(())
208 }
209
210 #[cfg(feature = "otlp")]
212 pub fn with_span_layer(
213 &mut self,
214 otlp_config: OtlpConfig,
215 filter: EnvFilter,
216 ) -> eyre::Result<()> {
217 let span_layer = span_layer(otlp_config)
220 .map_err(|e| eyre::eyre!("Failed to build OTLP span exporter {}", e))?
221 .with_filter(filter);
222
223 self.add_layer(span_layer);
224
225 Ok(())
226 }
227
228 #[cfg(feature = "otlp-logs")]
230 pub fn with_log_layer(
231 &mut self,
232 otlp_config: OtlpLogsConfig,
233 filter: EnvFilter,
234 ) -> eyre::Result<()> {
235 let log_layer = log_layer(otlp_config)
236 .map_err(|e| eyre::eyre!("Failed to build OTLP log exporter {}", e))?
237 .with_filter(filter);
238
239 self.add_layer(log_layer);
240
241 Ok(())
242 }
243}
244
245#[derive(Debug, Clone)]
249pub struct FileInfo {
250 dir: PathBuf,
251 file_name: String,
252 max_size_bytes: u64,
253 max_files: usize,
254}
255
256impl FileInfo {
257 pub const fn new(
259 dir: PathBuf,
260 file_name: String,
261 max_size_bytes: u64,
262 max_files: usize,
263 ) -> Self {
264 Self { dir, file_name, max_size_bytes, max_files }
265 }
266
267 fn create_log_dir(&self) -> eyre::Result<&Path> {
269 let log_dir: &Path = self.dir.as_ref();
270 if !log_dir.exists() {
271 std::fs::create_dir_all(log_dir)
272 .map_err(|err| eyre::eyre!("Could not create log directory {log_dir:?}: {err}"))?;
273 }
274 Ok(log_dir)
275 }
276
277 fn create_log_writer(
279 &self,
280 ) -> eyre::Result<(tracing_appender::non_blocking::NonBlocking, WorkerGuard)> {
281 let log_dir = self.create_log_dir()?;
282 let (writer, guard) = tracing_appender::non_blocking(
283 RollingFileAppender::new(
284 log_dir.join(&self.file_name),
285 RollingConditionBasic::new().max_size(self.max_size_bytes),
286 self.max_files,
287 )
288 .map_err(|err| eyre::eyre!("Could not initialize file logging: {err}"))?,
289 );
290 Ok((writer, guard))
291 }
292}
293
294fn build_env_filter(
305 default_directive: Option<Directive>,
306 directives: &str,
307) -> eyre::Result<EnvFilter> {
308 let env_filter = if let Some(default_directive) = default_directive {
309 EnvFilter::builder().with_default_directive(default_directive).from_env_lossy()
310 } else {
311 EnvFilter::builder().from_env_lossy()
312 };
313
314 DEFAULT_ENV_FILTER_DIRECTIVES
315 .into_iter()
316 .chain(directives.split(',').filter(|d| !d.is_empty()))
317 .try_fold(env_filter, |env_filter, directive| {
318 Ok(env_filter.add_directive(directive.parse()?))
319 })
320}