zutil_logger/
lib.rs

1//! Logger
2
3// Features
4#![feature(
5	nonpoison_mutex,
6	sync_nonpoison,
7	anonymous_lifetime_in_impl_trait,
8	type_changing_struct_update,
9	never_type
10)]
11
12// Modules
13mod builder;
14mod file;
15mod pre_init;
16mod term;
17
18// Exports
19pub use self::builder::LoggerBuilder;
20
21// Imports
22use {
23	self::file::FileWriter,
24	itertools::Itertools,
25	std::{
26		collections::{HashMap, hash_map},
27		env::{self, VarError},
28		fs,
29		io,
30		path::Path,
31	},
32	tracing_subscriber::Registry,
33};
34
35/// Logger
36pub struct Logger {
37	/// File writer
38	file_writer: FileWriter,
39}
40
41impl Logger {
42	/// Creates a default logger
43	pub fn new() -> Self {
44		Self::builder().build()
45	}
46
47	/// Creates a builder for the logger
48	pub fn builder() -> LoggerBuilder<fn() -> io::Stderr, LoggerSubscriber> {
49		LoggerBuilder::new()
50	}
51
52	/// Sets a file to log into.
53	///
54	/// Once the logger is finished, any logs produced until then
55	/// will be retro-actively written into this log file.
56	pub fn set_file(&self, path: Option<&Path>) {
57		match path {
58			Some(path) => match fs::File::create(path) {
59				Ok(file) => {
60					self.file_writer.set_file(file);
61					tracing::info!("Logging to file: {path:?}");
62				},
63				Err(err) => {
64					tracing::warn!("Unable to create log file {path:?}: {err}");
65					self.file_writer.set_empty()
66				},
67			},
68			None => self.file_writer.set_empty(),
69		}
70	}
71}
72
73impl Default for Logger {
74	fn default() -> Self {
75		Self::new()
76	}
77}
78
79/// Logger subscriber
80// TODO: Hide this behind a trait impl type alias
81pub type LoggerSubscriber = Registry;
82
83/// Returns the env filters of a variable.
84///
85/// Adds default filters, if not specified
86#[must_use]
87fn get_env_filters(env: &str, default_filters: impl IntoIterator<Item = (Option<&'_ str>, &'_ str)>) -> String {
88	// Get the current filters
89	let env_var;
90	let mut cur_filters = match env::var(env) {
91		// Split filters by `,`, then src and level by `=`
92		Ok(var) => {
93			env_var = var;
94			env_var
95				.split(',')
96				.map(|s| match s.split_once('=') {
97					Some((src, level)) => (Some(src), level),
98					None => (None, s),
99				})
100				.collect::<HashMap<_, _>>()
101		},
102
103		// If there were none, don't use any
104		Err(err) => {
105			if let VarError::NotUnicode(var) = err {
106				tracing::warn!("Ignoring non-utf8 env variable {env:?}: {var:?}");
107			}
108
109			HashMap::new()
110		},
111	};
112
113	// Add all default filters, if not specified
114	for (src, level) in default_filters {
115		if let hash_map::Entry::Vacant(entry) = cur_filters.entry(src) {
116			let _ = entry.insert(level);
117		}
118	}
119
120	// Then re-create it
121	let var = cur_filters
122		.into_iter()
123		.map(|(src, level)| match src {
124			Some(src) => format!("{src}={level}"),
125			None => level.to_owned(),
126		})
127		.join(",");
128	tracing::trace!("Using {env}={var}");
129
130	var
131}