Logger now has a builder.

This commit is contained in:
Filipe Rodrigues 2025-10-12 16:22:03 +01:00
parent 2913756373
commit 5f6c00fca2
Signed by: zenithsiz
SSH Key Fingerprint: SHA256:Mb5ppb3Sh7IarBO/sBTXLHbYEOz37hJAlslLQPPAPaU
2 changed files with 169 additions and 90 deletions

142
zutil-logger/src/builder.rs Normal file
View File

@ -0,0 +1,142 @@
//! Logger builder
use {
crate::{
Logger,
LoggerSubscriber,
file::{self, FileWriter},
pre_init::PreInitLogger,
term,
},
std::{
collections::HashMap,
io::{self, Write},
},
tracing::Subscriber,
tracing_subscriber::{fmt::MakeWriter, prelude::*, registry::LookupSpan},
};
/// Logger builder
pub struct LoggerBuilder<W, S> {
/// Pre-initialization logger
pre_init_logger: PreInitLogger,
/// Stderr
stderr: W,
/// Subscriber
subscriber: S,
/// Stderr filters
stderr_filters: HashMap<Option<String>, String>,
/// Filter filters
file_filters: HashMap<Option<String>, String>,
}
impl LoggerBuilder<fn() -> io::Stderr, LoggerSubscriber> {
/// Creates a new builder
pub fn new() -> Self {
// Create the pre-init logger to log everything until we have our loggers running.
let pre_init_logger = PreInitLogger::new();
Self {
pre_init_logger,
stderr: io::stderr,
subscriber: LoggerSubscriber::default(),
stderr_filters: [(None, "info".to_owned())].into(),
file_filters: [(None, "debug".to_owned())].into(),
}
}
}
impl<W, S> LoggerBuilder<W, S> {
/// Sets the stderr output of this logger
pub fn stderr<W2>(self, stderr: W2) -> LoggerBuilder<W2, S> {
LoggerBuilder { stderr, ..self }
}
/// Adds a layer to this logger's subscriber
pub fn layer<L>(self, layer: L) -> LoggerBuilder<W, tracing_subscriber::layer::Layered<L, S>>
where
S: Subscriber,
L: tracing_subscriber::Layer<S>,
{
LoggerBuilder {
subscriber: self.subscriber.with(layer),
..self
}
}
/// Sets the default stderr filter
pub fn stderr_filter_default(mut self, filter: &str) -> Self {
self.stderr_filters.insert(None, filter.to_owned());
self
}
/// Sets a stderr filter
pub fn stderr_filter(mut self, key: &str, filter: &str) -> Self {
self.stderr_filters.insert(Some(key.to_owned()), filter.to_owned());
self
}
/// Sets the default file filter
pub fn file_filter_default(mut self, filter: &str) -> Self {
self.file_filters.insert(None, filter.to_owned());
self
}
/// Sets a file filter
pub fn file_filter(mut self, key: &str, filter: &str) -> Self {
self.file_filters.insert(Some(key.to_owned()), filter.to_owned());
self
}
/// Sets a filter for both the stderr and file layers
pub fn filter(self, key: &str, filter: &str) -> Self {
self.stderr_filter(key, filter).file_filter(key, filter)
}
/// Builds the logger
pub fn build(self) -> Logger
where
W: for<'a> MakeWriter<'a> + Clone + Send + Sync + 'static,
S: Subscriber + for<'a> LookupSpan<'a> + Send + Sync + 'static,
{
// Then initialize our logging
let file_writer = FileWriter::memory();
// Note: Due to [this issue](https://github.com/tokio-rs/tracing/issues/1817),
// the order here matters, and the stderr ones must be last.
let file_layer = file::layer(file_writer.clone(), self::filters_iter(&self.file_filters));
let term_layer = term::layer(self.stderr.clone(), self::filters_iter(&self.stderr_filters));
let subscriber = self.subscriber.with(file_layer).with(term_layer);
if let Err(err) = subscriber.try_init() {
eprintln!("Failed to set global logger: {err}");
}
// Finally write the pre-init output to our writes
self.pre_init_logger
.into_output()
.with_bytes(|bytes| {
self.stderr.make_writer().write_all(bytes)?;
file_writer.make_writer().write_all(bytes)
})
.expect("Unable to write pre-init output");
tracing::info!("Successfully initialized logger");
Logger { file_writer }
}
}
impl Default for LoggerBuilder<fn() -> io::Stderr, LoggerSubscriber> {
fn default() -> Self {
Self::new()
}
}
/// Converts the filters field into an iterator
fn filters_iter(filters: &HashMap<Option<String>, String>) -> impl Iterator<Item = (Option<&'_ str>, &'_ str)> {
filters.iter().map(|(key, value)| (key.as_deref(), value.as_str()))
}

View File

@ -1,27 +1,35 @@
//! Logger
// Features
#![feature(nonpoison_mutex, sync_nonpoison, anonymous_lifetime_in_impl_trait)]
#![feature(
nonpoison_mutex,
sync_nonpoison,
anonymous_lifetime_in_impl_trait,
type_changing_struct_update,
never_type
)]
// Modules
mod builder;
mod file;
mod pre_init;
mod term;
// Exports
pub use self::builder::LoggerBuilder;
// Imports
use {
self::{file::FileWriter, pre_init::PreInitLogger},
self::file::FileWriter,
itertools::Itertools,
std::{
self,
collections::{HashMap, hash_map},
env::{self, VarError},
fs,
io::Write,
io,
path::Path,
},
tracing::Subscriber,
tracing_subscriber::{Layer, Registry, fmt::MakeWriter, layer::Layered, prelude::*, registry::LookupSpan},
tracing_subscriber::Registry,
};
/// Logger
@ -31,49 +39,14 @@ pub struct Logger {
}
impl Logger {
/// Creates a new logger
///
/// Starts already logging to stderr.
pub fn new<W, L>(
stderr: W,
extra_layers: L,
default_stderr_filters: impl IntoIterator<Item = (Option<&'_ str>, &'_ str)>,
default_file_filters: impl IntoIterator<Item = (Option<&'_ str>, &'_ str)>,
) -> Self
where
W: for<'a> MakeWriter<'a> + Clone + Send + Sync + 'static,
L: ExtraLayers<LoggerSubscriber>,
L::Subscriber: Subscriber + for<'a> LookupSpan<'a> + Send + Sync + 'static,
{
// Create the pre-init logger to log everything until we have our loggers running.
let pre_init_logger = PreInitLogger::new();
/// Creates a default logger
pub fn new() -> Self {
Self::builder().build()
}
// Then initialize our logging
let file_writer = FileWriter::memory();
// Note: Due to [this issue](https://github.com/tokio-rs/tracing/issues/1817),
// the order here matters, and the stderr ones must be last.
let subscriber = LoggerSubscriber::default();
let subscriber = extra_layers
.layer_on(subscriber)
.with(file::layer(file_writer.clone(), default_file_filters))
.with(term::layer(stderr.clone(), default_stderr_filters));
if let Err(err) = subscriber.try_init() {
eprintln!("Failed to set global logger: {err}");
}
// Finally write the pre-init output to our writes
pre_init_logger
.into_output()
.with_bytes(|bytes| {
stderr.make_writer().write_all(bytes)?;
file_writer.make_writer().write_all(bytes)
})
.expect("Unable to write pre-init output");
tracing::info!("Successfully initialized logger");
Self { file_writer }
/// Creates a builder for the logger
pub fn builder() -> LoggerBuilder<fn() -> io::Stderr, LoggerSubscriber> {
LoggerBuilder::new()
}
/// Sets a file to log into.
@ -97,6 +70,12 @@ impl Logger {
}
}
impl Default for Logger {
fn default() -> Self {
Self::new()
}
}
/// Logger subscriber
// TODO: Hide this behind a trait impl type alias
pub type LoggerSubscriber = Registry;
@ -150,45 +129,3 @@ fn get_env_filters(env: &str, default_filters: impl IntoIterator<Item = (Option<
var
}
/// Extra layers
pub trait ExtraLayers<S> {
/// Subscriber with all layers
type Subscriber;
/// Layers all layers onto a subscriber
fn layer_on(self, subscriber: S) -> Self::Subscriber;
}
impl<S> ExtraLayers<S> for () {
type Subscriber = S;
fn layer_on(self, subscriber: S) -> Self::Subscriber {
subscriber
}
}
impl<S, L> ExtraLayers<S> for (L,)
where
S: Subscriber,
L: Layer<S>,
{
type Subscriber = Layered<L, S>;
fn layer_on(self, subscriber: S) -> Self::Subscriber {
subscriber.with(self.0)
}
}
impl<S, L0, L1> ExtraLayers<S> for (L0, L1)
where
S: Subscriber,
L0: Layer<S>,
L1: Layer<Layered<L0, S>>,
{
type Subscriber = Layered<L1, Layered<L0, S>>;
fn layer_on(self, subscriber: S) -> Self::Subscriber {
subscriber.with(self.0).with(self.1)
}
}