1use crate::formatter::LogFormat;
2use rolling_file::{RollingConditionBasic, RollingFileAppender};
3use std::{
4 fmt,
5 path::{Path, PathBuf},
6};
7use tracing_appender::non_blocking::WorkerGuard;
8use tracing_subscriber::{filter::Directive, EnvFilter, Layer, Registry};
9#[cfg(feature = "otlp")]
10use {
11 reth_tracing_otlp::{span_layer, OtlpProtocol},
12 url::Url,
13};
14
15pub type FileWorkerGuard = tracing_appender::non_blocking::WorkerGuard;
20
21pub(crate) type BoxedLayer<S> = Box<dyn Layer<S> + Send + Sync>;
23
24const DEFAULT_ENV_FILTER_DIRECTIVES: [&str; 9] = [
29 "hyper::proto::h1=off",
30 "hickory_resolver=off",
31 "hickory_proto=off",
32 "discv5=off",
33 "jsonrpsee-server=off",
34 "opentelemetry-otlp=warn",
35 "opentelemetry_sdk=warn",
36 "opentelemetry-http=warn",
37 "hyper_util::client::legacy::pool=off",
38];
39
40#[derive(Default)]
45pub struct Layers {
46 inner: Vec<BoxedLayer<Registry>>,
47}
48
49impl fmt::Debug for Layers {
50 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51 f.debug_struct("Layers").field("layers_count", &self.inner.len()).finish()
52 }
53}
54
55impl Layers {
56 pub fn new() -> Self {
58 Self::default()
59 }
60
61 pub fn add_layer<L>(&mut self, layer: L)
63 where
64 L: Layer<Registry> + Send + Sync,
65 {
66 self.inner.push(layer.boxed());
67 }
68
69 pub(crate) fn into_inner(self) -> Vec<BoxedLayer<Registry>> {
71 self.inner
72 }
73
74 pub(crate) fn journald(&mut self, filter: &str) -> eyre::Result<()> {
82 let journald_filter = build_env_filter(None, filter)?;
83 let layer = tracing_journald::layer()?.with_filter(journald_filter);
84 self.add_layer(layer);
85 Ok(())
86 }
87
88 pub(crate) fn stdout(
102 &mut self,
103 format: LogFormat,
104 default_directive: Directive,
105 filters: &str,
106 color: Option<String>,
107 ) -> eyre::Result<()> {
108 let filter = build_env_filter(Some(default_directive), filters)?;
109 let layer = format.apply(filter, color, None);
110 self.add_layer(layer);
111 Ok(())
112 }
113
114 pub(crate) fn file(
124 &mut self,
125 format: LogFormat,
126 filter: &str,
127 file_info: FileInfo,
128 ) -> eyre::Result<FileWorkerGuard> {
129 let (writer, guard) = file_info.create_log_writer();
130 let file_filter = build_env_filter(None, filter)?;
131 let layer = format.apply(file_filter, None, Some(writer));
132 self.add_layer(layer);
133 Ok(guard)
134 }
135
136 #[cfg(feature = "otlp")]
138 pub fn with_span_layer(
139 &mut self,
140 service_name: String,
141 endpoint_exporter: Url,
142 filter: EnvFilter,
143 otlp_protocol: OtlpProtocol,
144 ) -> eyre::Result<()> {
145 let span_layer = span_layer(service_name, &endpoint_exporter, otlp_protocol)
148 .map_err(|e| eyre::eyre!("Failed to build OTLP span exporter {}", e))?
149 .with_filter(filter);
150
151 self.add_layer(span_layer);
152
153 Ok(())
154 }
155}
156
157#[derive(Debug, Clone)]
161pub struct FileInfo {
162 dir: PathBuf,
163 file_name: String,
164 max_size_bytes: u64,
165 max_files: usize,
166}
167
168impl FileInfo {
169 pub const fn new(
171 dir: PathBuf,
172 file_name: String,
173 max_size_bytes: u64,
174 max_files: usize,
175 ) -> Self {
176 Self { dir, file_name, max_size_bytes, max_files }
177 }
178
179 fn create_log_dir(&self) -> &Path {
184 let log_dir: &Path = self.dir.as_ref();
185 if !log_dir.exists() {
186 std::fs::create_dir_all(log_dir).expect("Could not create log directory");
187 }
188 log_dir
189 }
190
191 fn create_log_writer(&self) -> (tracing_appender::non_blocking::NonBlocking, WorkerGuard) {
196 let log_dir = self.create_log_dir();
197 let (writer, guard) = tracing_appender::non_blocking(
198 RollingFileAppender::new(
199 log_dir.join(&self.file_name),
200 RollingConditionBasic::new().max_size(self.max_size_bytes),
201 self.max_files,
202 )
203 .expect("Could not initialize file logging"),
204 );
205 (writer, guard)
206 }
207}
208
209fn build_env_filter(
220 default_directive: Option<Directive>,
221 directives: &str,
222) -> eyre::Result<EnvFilter> {
223 let env_filter = if let Some(default_directive) = default_directive {
224 EnvFilter::builder().with_default_directive(default_directive).from_env_lossy()
225 } else {
226 EnvFilter::builder().from_env_lossy()
227 };
228
229 DEFAULT_ENV_FILTER_DIRECTIVES
230 .into_iter()
231 .chain(directives.split(',').filter(|d| !d.is_empty()))
232 .try_fold(env_filter, |env_filter, directive| {
233 Ok(env_filter.add_directive(directive.parse()?))
234 })
235}