zutil_logger/
builder.rs

1//! Logger builder
2
3use {
4	crate::{
5		Logger,
6		LoggerSubscriber,
7		file::{self, FileWriter},
8		pre_init::PreInitLogger,
9		term,
10	},
11	std::{
12		collections::HashMap,
13		io::{self, Write},
14	},
15	tracing::Subscriber,
16	tracing_subscriber::{fmt::MakeWriter, prelude::*, registry::LookupSpan},
17};
18
19/// Logger builder
20pub struct LoggerBuilder<W, S> {
21	/// Pre-initialization logger
22	pre_init_logger: PreInitLogger,
23
24	/// Stderr
25	stderr: W,
26
27	/// Subscriber
28	subscriber: S,
29
30	/// Stderr filters
31	stderr_filters: HashMap<Option<String>, String>,
32
33	/// Filter filters
34	file_filters: HashMap<Option<String>, String>,
35}
36
37impl LoggerBuilder<fn() -> io::Stderr, LoggerSubscriber> {
38	/// Creates a new builder
39	pub fn new() -> Self {
40		// Create the pre-init logger to log everything until we have our loggers running.
41		let pre_init_logger = PreInitLogger::new();
42
43		Self {
44			pre_init_logger,
45			stderr: io::stderr,
46			subscriber: LoggerSubscriber::default(),
47			stderr_filters: [(None, "info".to_owned())].into(),
48			file_filters: [(None, "debug".to_owned())].into(),
49		}
50	}
51}
52
53impl<W, S> LoggerBuilder<W, S> {
54	/// Sets the stderr output of this logger
55	pub fn stderr<W2>(self, stderr: W2) -> LoggerBuilder<W2, S> {
56		LoggerBuilder { stderr, ..self }
57	}
58
59	/// Adds a layer to this logger's subscriber
60	pub fn layer<L>(self, layer: L) -> LoggerBuilder<W, tracing_subscriber::layer::Layered<L, S>>
61	where
62		S: Subscriber,
63		L: tracing_subscriber::Layer<S>,
64	{
65		LoggerBuilder {
66			subscriber: self.subscriber.with(layer),
67			..self
68		}
69	}
70
71	/// Sets the default stderr filter
72	pub fn stderr_filter_default(mut self, filter: &str) -> Self {
73		self.stderr_filters.insert(None, filter.to_owned());
74		self
75	}
76
77	/// Sets a stderr filter
78	pub fn stderr_filter(mut self, key: &str, filter: &str) -> Self {
79		self.stderr_filters.insert(Some(key.to_owned()), filter.to_owned());
80		self
81	}
82
83	/// Sets the default file filter
84	pub fn file_filter_default(mut self, filter: &str) -> Self {
85		self.file_filters.insert(None, filter.to_owned());
86		self
87	}
88
89	/// Sets a file filter
90	pub fn file_filter(mut self, key: &str, filter: &str) -> Self {
91		self.file_filters.insert(Some(key.to_owned()), filter.to_owned());
92		self
93	}
94
95	/// Sets a filter for both the stderr and file layers
96	pub fn filter(self, key: &str, filter: &str) -> Self {
97		self.stderr_filter(key, filter).file_filter(key, filter)
98	}
99
100	/// Builds the logger
101	pub fn build(self) -> Logger
102	where
103		W: for<'a> MakeWriter<'a> + Clone + Send + Sync + 'static,
104		S: Subscriber + for<'a> LookupSpan<'a> + Send + Sync + 'static,
105	{
106		// Then initialize our logging
107		let file_writer = FileWriter::memory();
108
109		// Note: Due to [this issue](https://github.com/tokio-rs/tracing/issues/1817),
110		//       the order here matters, and the stderr ones must be last.
111		let file_layer = file::layer(file_writer.clone(), self::filters_iter(&self.file_filters));
112		let term_layer = term::layer(self.stderr.clone(), self::filters_iter(&self.stderr_filters));
113		let subscriber = self.subscriber.with(file_layer).with(term_layer);
114		if let Err(err) = subscriber.try_init() {
115			eprintln!("Failed to set global logger: {err}");
116		}
117
118		// Finally write the pre-init output to our writes
119		self.pre_init_logger
120			.into_output()
121			.with_bytes(|bytes| {
122				self.stderr.make_writer().write_all(bytes)?;
123				file_writer.make_writer().write_all(bytes)
124			})
125			.expect("Unable to write pre-init output");
126
127		tracing::info!("Successfully initialized logger");
128
129		Logger { file_writer }
130	}
131}
132
133impl Default for LoggerBuilder<fn() -> io::Stderr, LoggerSubscriber> {
134	fn default() -> Self {
135		Self::new()
136	}
137}
138
139/// Converts the filters field into an iterator
140fn filters_iter(filters: &HashMap<Option<String>, String>) -> impl Iterator<Item = (Option<&'_ str>, &'_ str)> {
141	filters.iter().map(|(key, value)| (key.as_deref(), value.as_str()))
142}