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 DEFAULT_ENV_FILTER_DIRECTIVES: [&str; 5] = [
24 "hyper::proto::h1=off",
25 "hickory_resolver=off",
26 "hickory_proto=off",
27 "discv5=off",
28 "jsonrpsee-server=off",
29];
30
31#[derive(Default)]
36pub struct Layers {
37 inner: Vec<BoxedLayer<Registry>>,
38}
39
40impl fmt::Debug for Layers {
41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42 f.debug_struct("Layers").field("layers_count", &self.inner.len()).finish()
43 }
44}
45
46impl Layers {
47 pub fn new() -> Self {
49 Self::default()
50 }
51
52 pub fn add_layer<L>(&mut self, layer: L)
54 where
55 L: Layer<Registry> + Send + Sync,
56 {
57 self.inner.push(layer.boxed());
58 }
59
60 pub(crate) fn into_inner(self) -> Vec<BoxedLayer<Registry>> {
62 self.inner
63 }
64
65 pub(crate) fn journald(&mut self, filter: &str) -> eyre::Result<()> {
73 let journald_filter = build_env_filter(None, filter)?;
74 let layer = tracing_journald::layer()?.with_filter(journald_filter);
75 self.add_layer(layer);
76 Ok(())
77 }
78
79 pub(crate) fn stdout(
93 &mut self,
94 format: LogFormat,
95 default_directive: Directive,
96 filters: &str,
97 color: Option<String>,
98 ) -> eyre::Result<()> {
99 let filter = build_env_filter(Some(default_directive), filters)?;
100 let layer = format.apply(filter, color, None);
101 self.add_layer(layer);
102 Ok(())
103 }
104
105 pub(crate) fn file(
115 &mut self,
116 format: LogFormat,
117 filter: &str,
118 file_info: FileInfo,
119 ) -> eyre::Result<FileWorkerGuard> {
120 let (writer, guard) = file_info.create_log_writer();
121 let file_filter = build_env_filter(None, filter)?;
122 let layer = format.apply(file_filter, None, Some(writer));
123 self.add_layer(layer);
124 Ok(guard)
125 }
126}
127
128#[derive(Debug, Clone)]
132pub struct FileInfo {
133 dir: PathBuf,
134 file_name: String,
135 max_size_bytes: u64,
136 max_files: usize,
137}
138
139impl FileInfo {
140 pub const fn new(
142 dir: PathBuf,
143 file_name: String,
144 max_size_bytes: u64,
145 max_files: usize,
146 ) -> Self {
147 Self { dir, file_name, max_size_bytes, max_files }
148 }
149
150 fn create_log_dir(&self) -> &Path {
155 let log_dir: &Path = self.dir.as_ref();
156 if !log_dir.exists() {
157 std::fs::create_dir_all(log_dir).expect("Could not create log directory");
158 }
159 log_dir
160 }
161
162 fn create_log_writer(&self) -> (tracing_appender::non_blocking::NonBlocking, WorkerGuard) {
167 let log_dir = self.create_log_dir();
168 let (writer, guard) = tracing_appender::non_blocking(
169 RollingFileAppender::new(
170 log_dir.join(&self.file_name),
171 RollingConditionBasic::new().max_size(self.max_size_bytes),
172 self.max_files,
173 )
174 .expect("Could not initialize file logging"),
175 );
176 (writer, guard)
177 }
178}
179
180fn build_env_filter(
191 default_directive: Option<Directive>,
192 directives: &str,
193) -> eyre::Result<EnvFilter> {
194 let env_filter = if let Some(default_directive) = default_directive {
195 EnvFilter::builder().with_default_directive(default_directive).from_env_lossy()
196 } else {
197 EnvFilter::builder().from_env_lossy()
198 };
199
200 DEFAULT_ENV_FILTER_DIRECTIVES
201 .into_iter()
202 .chain(directives.split(',').filter(|d| !d.is_empty()))
203 .try_fold(env_filter, |env_filter, directive| {
204 Ok(env_filter.add_directive(directive.parse()?))
205 })
206}