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 fs::File,
10 path::{Path, PathBuf},
11};
12use tracing_appender::non_blocking::WorkerGuard;
13use tracing_subscriber::{filter::Directive, reload, EnvFilter, Layer, Registry};
14
15pub type FileWorkerGuard = tracing_appender::non_blocking::WorkerGuard;
20
21#[derive(Default)]
23pub struct TracingGuards {
24 _file: Option<FileWorkerGuard>,
25 _chrome: Option<tracing_chrome::FlushGuard>,
26}
27
28impl fmt::Debug for TracingGuards {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 f.debug_struct("TracingGuards")
31 .field("file", &self._file.is_some())
32 .field("chrome", &self._chrome.is_some())
33 .finish()
34 }
35}
36
37impl TracingGuards {
38 pub const fn new(
40 file: Option<FileWorkerGuard>,
41 chrome: Option<tracing_chrome::FlushGuard>,
42 ) -> Self {
43 Self { _file: file, _chrome: chrome }
44 }
45}
46
47pub(crate) type BoxedLayer<S> = Box<dyn Layer<S> + Send + Sync>;
49
50const DEFAULT_ENV_FILTER_DIRECTIVES: [&str; 11] = [
55 "hyper::proto::h1=off",
56 "hickory_resolver=off",
57 "hickory_proto=off",
58 "discv5=off",
59 "jsonrpsee-server=off",
60 "opentelemetry-otlp=warn",
61 "opentelemetry_sdk=warn",
62 "opentelemetry-http=warn",
63 "hyper_util::client::legacy::pool=off",
64 "rustls=warn",
65 "tungstenite=warn",
66];
67
68#[derive(Default)]
73pub struct Layers {
74 inner: Vec<BoxedLayer<Registry>>,
75}
76
77impl fmt::Debug for Layers {
78 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79 f.debug_struct("Layers").field("layers_count", &self.inner.len()).finish()
80 }
81}
82
83impl Layers {
84 pub fn new() -> Self {
86 Self::default()
87 }
88
89 pub fn add_layer<L>(&mut self, layer: L)
91 where
92 L: Layer<Registry> + Send + Sync,
93 {
94 self.inner.push(layer.boxed());
95 }
96
97 pub(crate) fn into_inner(self) -> Vec<BoxedLayer<Registry>> {
99 self.inner
100 }
101
102 pub(crate) fn journald(&mut self, filter: &str) -> eyre::Result<()> {
110 let journald_filter = build_env_filter(None, filter)?;
111 let layer = tracing_journald::layer()?.with_filter(journald_filter);
112 self.add_layer(layer);
113 Ok(())
114 }
115
116 pub(crate) fn stdout(
130 &mut self,
131 format: LogFormat,
132 default_directive: Directive,
133 filters: &str,
134 color: Option<String>,
135 reloadable: bool,
136 ) -> eyre::Result<Option<LogFilterReloadHandle>> {
137 let filter = build_env_filter(Some(default_directive), filters)?;
138
139 let show_target = reloadable ||
143 filter.max_level_hint().is_none_or(|max_level| max_level > tracing::Level::INFO);
144
145 if reloadable {
146 let (reloadable_filter, handle) = reload::Layer::new(filter);
147 let layer = format.apply(reloadable_filter, color, show_target, None);
148 self.add_layer(layer);
149 Ok(Some(handle))
150 } else {
151 let layer = format.apply(filter, color, show_target, None);
152 self.add_layer(layer);
153 Ok(None)
154 }
155 }
156
157 pub(crate) fn file(
170 &mut self,
171 format: LogFormat,
172 filter: &str,
173 file_info: FileInfo,
174 reloadable: bool,
175 ) -> eyre::Result<(FileWorkerGuard, Option<LogFilterReloadHandle>)> {
176 let (writer, guard) = file_info.create_log_writer()?;
177 let file_filter = build_env_filter(None, filter)?;
178
179 if reloadable {
180 let (reloadable_filter, handle) = reload::Layer::new(file_filter);
181 self.add_layer(format.apply(reloadable_filter, None, true, Some(writer)));
182 Ok((guard, Some(handle)))
183 } else {
184 self.add_layer(format.apply(file_filter, None, true, Some(writer)));
185 Ok((guard, None))
186 }
187 }
188
189 pub(crate) fn samply(&mut self, config: LayerInfo) -> eyre::Result<()> {
190 self.add_layer(
191 tracing_samply::SamplyLayer::new()
192 .map_err(|e| eyre::eyre!("Failed to create samply layer: {e}"))?
193 .with_filter(build_env_filter(
194 Some(config.default_directive.parse()?),
195 &config.filters,
196 )?),
197 );
198 Ok(())
199 }
200
201 pub(crate) fn chrome(
202 &mut self,
203 config: LayerInfo,
204 file: &Path,
205 ) -> eyre::Result<tracing_chrome::FlushGuard> {
206 let writer = File::create(file)
207 .map_err(|err| eyre::eyre!("Failed to create Chrome trace file {file:?}: {err}"))?;
208 let (layer, guard) = tracing_chrome::ChromeLayerBuilder::new()
209 .writer(writer)
210 .include_args(true)
211 .include_locations(false)
212 .build();
213 self.add_layer(layer.with_filter(build_env_filter(
214 Some(config.default_directive.parse()?),
215 &config.filters,
216 )?));
217 Ok(guard)
218 }
219
220 #[cfg(feature = "tracy")]
221 pub(crate) fn tracy(&mut self, config: LayerInfo) -> eyre::Result<()> {
222 struct TracyFields(tracing_subscriber::fmt::format::DefaultFields);
227 impl<'writer> tracing_subscriber::fmt::FormatFields<'writer> for TracyFields {
228 fn format_fields<R: tracing_subscriber::field::RecordFields>(
229 &self,
230 writer: tracing_subscriber::fmt::format::Writer<'writer>,
231 fields: R,
232 ) -> core::fmt::Result {
233 self.0.format_fields(writer, fields)
234 }
235 }
236
237 struct Config(TracyFields);
238 impl tracing_tracy::Config for Config {
239 type Formatter = TracyFields;
240 fn formatter(&self) -> &Self::Formatter {
241 &self.0
242 }
243 fn format_fields_in_zone_name(&self) -> bool {
244 false
245 }
246 }
247
248 self.add_layer(
249 tracing_tracy::TracyLayer::new(Config(TracyFields(Default::default()))).with_filter(
250 build_env_filter(Some(config.default_directive.parse()?), &config.filters)?,
251 ),
252 );
253 Ok(())
254 }
255
256 #[cfg(feature = "otlp")]
258 pub fn with_span_layer(
259 &mut self,
260 otlp_config: OtlpConfig,
261 filter: EnvFilter,
262 ) -> eyre::Result<()> {
263 let span_layer = span_layer(otlp_config)
266 .map_err(|e| eyre::eyre!("Failed to build OTLP span exporter {}", e))?
267 .with_filter(filter);
268
269 self.add_layer(span_layer);
270
271 Ok(())
272 }
273
274 #[cfg(feature = "otlp-logs")]
276 pub fn with_log_layer(
277 &mut self,
278 otlp_config: OtlpLogsConfig,
279 filter: EnvFilter,
280 ) -> eyre::Result<()> {
281 let log_layer = log_layer(otlp_config)
282 .map_err(|e| eyre::eyre!("Failed to build OTLP log exporter {}", e))?
283 .with_filter(filter);
284
285 self.add_layer(log_layer);
286
287 Ok(())
288 }
289}
290
291#[derive(Debug, Clone)]
295pub struct FileInfo {
296 dir: PathBuf,
297 file_name: String,
298 max_size_bytes: u64,
299 max_files: usize,
300}
301
302impl FileInfo {
303 pub const fn new(
305 dir: PathBuf,
306 file_name: String,
307 max_size_bytes: u64,
308 max_files: usize,
309 ) -> Self {
310 Self { dir, file_name, max_size_bytes, max_files }
311 }
312
313 fn create_log_dir(&self) -> eyre::Result<&Path> {
315 let log_dir: &Path = self.dir.as_ref();
316 if !log_dir.exists() {
317 std::fs::create_dir_all(log_dir)
318 .map_err(|err| eyre::eyre!("Could not create log directory {log_dir:?}: {err}"))?;
319 }
320 Ok(log_dir)
321 }
322
323 fn create_log_writer(
325 &self,
326 ) -> eyre::Result<(tracing_appender::non_blocking::NonBlocking, WorkerGuard)> {
327 let log_dir = self.create_log_dir()?;
328 let (writer, guard) = tracing_appender::non_blocking(
329 RollingFileAppender::new(
330 log_dir.join(&self.file_name),
331 RollingConditionBasic::new().max_size(self.max_size_bytes),
332 self.max_files,
333 )
334 .map_err(|err| eyre::eyre!("Could not initialize file logging: {err}"))?,
335 );
336 Ok((writer, guard))
337 }
338}
339
340fn build_env_filter(
351 default_directive: Option<Directive>,
352 directives: &str,
353) -> eyre::Result<EnvFilter> {
354 let env_filter = if let Some(default_directive) = default_directive {
355 EnvFilter::builder().with_default_directive(default_directive).from_env_lossy()
356 } else {
357 EnvFilter::builder().from_env_lossy()
358 };
359
360 DEFAULT_ENV_FILTER_DIRECTIVES
361 .into_iter()
362 .chain(directives.split(',').filter(|d| !d.is_empty()))
363 .try_fold(env_filter, |env_filter, directive| {
364 Ok(env_filter.add_directive(directive.parse()?))
365 })
366}