1use std::{
2 fmt,
3 path::{Path, PathBuf},
4};
5
6use rolling_file::{RollingConditionBasic, RollingFileAppender};
7use tracing_appender::non_blocking::WorkerGuard;
8use tracing_subscriber::{filter::Directive, EnvFilter, Layer, Registry};
9
10use crate::formatter::LogFormat;
11
12pub type FileWorkerGuard = tracing_appender::non_blocking::WorkerGuard;
17
18pub(crate) type BoxedLayer<S> = Box<dyn Layer<S> + Send + Sync>;
20
21const RETH_LOG_FILE_NAME: &str = "reth.log";
22
23const DEFAULT_ENV_FILTER_DIRECTIVES: [&str; 5] = [
26 "hyper::proto::h1=off",
27 "hickory_resolver=off",
28 "hickory_proto=off",
29 "discv5=off",
30 "jsonrpsee-server=off",
31];
32
33#[derive(Default)]
38pub struct Layers {
39 inner: Vec<BoxedLayer<Registry>>,
40}
41
42impl fmt::Debug for Layers {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 f.debug_struct("Layers").field("layers_count", &self.inner.len()).finish()
45 }
46}
47
48impl Layers {
49 pub fn new() -> Self {
51 Self::default()
52 }
53
54 pub fn add_layer<L>(&mut self, layer: L)
56 where
57 L: Layer<Registry> + Send + Sync,
58 {
59 self.inner.push(layer.boxed());
60 }
61
62 pub(crate) fn into_inner(self) -> Vec<BoxedLayer<Registry>> {
64 self.inner
65 }
66
67 pub(crate) fn journald(&mut self, filter: &str) -> eyre::Result<()> {
75 let journald_filter = build_env_filter(None, filter)?;
76 let layer = tracing_journald::layer()?.with_filter(journald_filter);
77 self.add_layer(layer);
78 Ok(())
79 }
80
81 pub(crate) fn stdout(
95 &mut self,
96 format: LogFormat,
97 default_directive: Directive,
98 filters: &str,
99 color: Option<String>,
100 ) -> eyre::Result<()> {
101 let filter = build_env_filter(Some(default_directive), filters)?;
102 let layer = format.apply(filter, color, None);
103 self.add_layer(layer);
104 Ok(())
105 }
106
107 pub(crate) fn file(
117 &mut self,
118 format: LogFormat,
119 filter: &str,
120 file_info: FileInfo,
121 ) -> eyre::Result<FileWorkerGuard> {
122 let (writer, guard) = file_info.create_log_writer();
123 let file_filter = build_env_filter(None, filter)?;
124 let layer = format.apply(file_filter, None, Some(writer));
125 self.add_layer(layer);
126 Ok(guard)
127 }
128}
129
130#[derive(Debug, Clone)]
134pub struct FileInfo {
135 dir: PathBuf,
136 file_name: String,
137 max_size_bytes: u64,
138 max_files: usize,
139}
140
141impl FileInfo {
142 pub fn new(dir: PathBuf, max_size_bytes: u64, max_files: usize) -> Self {
144 Self { dir, file_name: RETH_LOG_FILE_NAME.to_string(), max_size_bytes, max_files }
145 }
146
147 fn create_log_dir(&self) -> &Path {
152 let log_dir: &Path = self.dir.as_ref();
153 if !log_dir.exists() {
154 std::fs::create_dir_all(log_dir).expect("Could not create log directory");
155 }
156 log_dir
157 }
158
159 fn create_log_writer(&self) -> (tracing_appender::non_blocking::NonBlocking, WorkerGuard) {
164 let log_dir = self.create_log_dir();
165 let (writer, guard) = tracing_appender::non_blocking(
166 RollingFileAppender::new(
167 log_dir.join(&self.file_name),
168 RollingConditionBasic::new().max_size(self.max_size_bytes),
169 self.max_files,
170 )
171 .expect("Could not initialize file logging"),
172 );
173 (writer, guard)
174 }
175}
176
177fn build_env_filter(
188 default_directive: Option<Directive>,
189 directives: &str,
190) -> eyre::Result<EnvFilter> {
191 let env_filter = if let Some(default_directive) = default_directive {
192 EnvFilter::builder().with_default_directive(default_directive).from_env_lossy()
193 } else {
194 EnvFilter::builder().from_env_lossy()
195 };
196
197 DEFAULT_ENV_FILTER_DIRECTIVES
198 .into_iter()
199 .chain(directives.split(',').filter(|d| !d.is_empty()))
200 .try_fold(env_filter, |env_filter, directive| {
201 Ok(env_filter.add_directive(directive.parse()?))
202 })
203}