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; 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 reloadable: bool,
107 ) -> eyre::Result<Option<LogFilterReloadHandle>> {
108 let filter = build_env_filter(Some(default_directive), filters)?;
109
110 let show_target = reloadable ||
114 filter.max_level_hint().is_none_or(|max_level| max_level > tracing::Level::INFO);
115
116 if reloadable {
117 let (reloadable_filter, handle) = reload::Layer::new(filter);
118 let layer = format.apply(reloadable_filter, color, show_target, None);
119 self.add_layer(layer);
120 Ok(Some(handle))
121 } else {
122 let layer = format.apply(filter, color, show_target, None);
123 self.add_layer(layer);
124 Ok(None)
125 }
126 }
127
128 pub(crate) fn file(
141 &mut self,
142 format: LogFormat,
143 filter: &str,
144 file_info: FileInfo,
145 reloadable: bool,
146 ) -> eyre::Result<(FileWorkerGuard, Option<LogFilterReloadHandle>)> {
147 let (writer, guard) = file_info.create_log_writer()?;
148 let file_filter = build_env_filter(None, filter)?;
149
150 if reloadable {
151 let (reloadable_filter, handle) = reload::Layer::new(file_filter);
152 self.add_layer(format.apply(reloadable_filter, None, true, Some(writer)));
153 Ok((guard, Some(handle)))
154 } else {
155 self.add_layer(format.apply(file_filter, None, true, Some(writer)));
156 Ok((guard, None))
157 }
158 }
159
160 pub(crate) fn samply(&mut self, config: LayerInfo) -> eyre::Result<()> {
161 self.add_layer(
162 tracing_samply::SamplyLayer::new()
163 .map_err(|e| eyre::eyre!("Failed to create samply layer: {e}"))?
164 .with_filter(build_env_filter(
165 Some(config.default_directive.parse()?),
166 &config.filters,
167 )?),
168 );
169 Ok(())
170 }
171
172 #[cfg(feature = "tracy")]
173 pub(crate) fn tracy(&mut self, config: LayerInfo) -> eyre::Result<()> {
174 struct TracyFields(tracing_subscriber::fmt::format::DefaultFields);
179 impl<'writer> tracing_subscriber::fmt::FormatFields<'writer> for TracyFields {
180 fn format_fields<R: tracing_subscriber::field::RecordFields>(
181 &self,
182 writer: tracing_subscriber::fmt::format::Writer<'writer>,
183 fields: R,
184 ) -> core::fmt::Result {
185 self.0.format_fields(writer, fields)
186 }
187 }
188
189 struct Config(TracyFields);
190 impl tracing_tracy::Config for Config {
191 type Formatter = TracyFields;
192 fn formatter(&self) -> &Self::Formatter {
193 &self.0
194 }
195 fn format_fields_in_zone_name(&self) -> bool {
196 false
197 }
198 }
199
200 self.add_layer(
201 tracing_tracy::TracyLayer::new(Config(TracyFields(Default::default()))).with_filter(
202 build_env_filter(Some(config.default_directive.parse()?), &config.filters)?,
203 ),
204 );
205 Ok(())
206 }
207
208 #[cfg(feature = "otlp")]
210 pub fn with_span_layer(
211 &mut self,
212 otlp_config: OtlpConfig,
213 filter: EnvFilter,
214 ) -> eyre::Result<()> {
215 let span_layer = span_layer(otlp_config)
218 .map_err(|e| eyre::eyre!("Failed to build OTLP span exporter {}", e))?
219 .with_filter(filter);
220
221 self.add_layer(span_layer);
222
223 Ok(())
224 }
225
226 #[cfg(feature = "otlp-logs")]
228 pub fn with_log_layer(
229 &mut self,
230 otlp_config: OtlpLogsConfig,
231 filter: EnvFilter,
232 ) -> eyre::Result<()> {
233 let log_layer = log_layer(otlp_config)
234 .map_err(|e| eyre::eyre!("Failed to build OTLP log exporter {}", e))?
235 .with_filter(filter);
236
237 self.add_layer(log_layer);
238
239 Ok(())
240 }
241}
242
243#[derive(Debug, Clone)]
247pub struct FileInfo {
248 dir: PathBuf,
249 file_name: String,
250 max_size_bytes: u64,
251 max_files: usize,
252}
253
254impl FileInfo {
255 pub const fn new(
257 dir: PathBuf,
258 file_name: String,
259 max_size_bytes: u64,
260 max_files: usize,
261 ) -> Self {
262 Self { dir, file_name, max_size_bytes, max_files }
263 }
264
265 fn create_log_dir(&self) -> eyre::Result<&Path> {
267 let log_dir: &Path = self.dir.as_ref();
268 if !log_dir.exists() {
269 std::fs::create_dir_all(log_dir)
270 .map_err(|err| eyre::eyre!("Could not create log directory {log_dir:?}: {err}"))?;
271 }
272 Ok(log_dir)
273 }
274
275 fn create_log_writer(
277 &self,
278 ) -> eyre::Result<(tracing_appender::non_blocking::NonBlocking, WorkerGuard)> {
279 let log_dir = self.create_log_dir()?;
280 let (writer, guard) = tracing_appender::non_blocking(
281 RollingFileAppender::new(
282 log_dir.join(&self.file_name),
283 RollingConditionBasic::new().max_size(self.max_size_bytes),
284 self.max_files,
285 )
286 .map_err(|err| eyre::eyre!("Could not initialize file logging: {err}"))?,
287 );
288 Ok((writer, guard))
289 }
290}
291
292fn build_env_filter(
303 default_directive: Option<Directive>,
304 directives: &str,
305) -> eyre::Result<EnvFilter> {
306 let env_filter = if let Some(default_directive) = default_directive {
307 EnvFilter::builder().with_default_directive(default_directive).from_env_lossy()
308 } else {
309 EnvFilter::builder().from_env_lossy()
310 };
311
312 DEFAULT_ENV_FILTER_DIRECTIVES
313 .into_iter()
314 .chain(directives.split(',').filter(|d| !d.is_empty()))
315 .try_fold(env_filter, |env_filter, directive| {
316 Ok(env_filter.add_directive(directive.parse()?))
317 })
318}