From 176eae7cf336d707bc5d9a020846216f01f2f65d Mon Sep 17 00:00:00 2001 From: tison Date: Wed, 9 Apr 2025 12:07:10 +0800 Subject: [PATCH 1/2] refactor: logger builder Signed-off-by: tison --- .github/workflows/ci.yml | 1 + CHANGELOG.md | 2 ++ Cargo.lock | 4 ++-- Cargo.toml | 7 +++++- examples/log_with_logger.rs | 26 ++++++++++++++++++++ src/append/journald/README.md | 2 +- src/append/rolling_file/rotation.rs | 2 ++ src/diagnostic/mod.rs | 1 + src/diagnostic/static_global.rs | 3 +++ src/diagnostic/thread_local.rs | 2 ++ src/lib.rs | 2 ++ src/logger/builder.rs | 37 +++++++++++------------------ src/logger/log_impl.rs | 4 ++-- src/logger/mod.rs | 1 + 14 files changed, 65 insertions(+), 29 deletions(-) create mode 100644 examples/log_with_logger.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cfe5606..2a1b04b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -79,6 +79,7 @@ jobs: run: | set -x cargo run --example simple_stdout + cargo run --example log_with_logger cargo run --example multiple_dispatches cargo run --example custom_layout_filter cargo run --no-default-features --example simple_stdout diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bcf814..0985915 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ All notable changes to this project will be documented in this file. * `Append::flush` is now fallible. * `Diagnostic`'s and `Visitor`'s `visit` methods are fallible. * `NonBlocking` related types and the feature flag are now private. +* `logforth::Builder` has no longer an option to configure the global `max_level`. Check its documentation for more details. * Constructing `RollingFile` and `Syslog` appender is heavily simplified. Before: @@ -75,6 +76,7 @@ fn construct_syslog() { * Add `LogfmtLayout` to support logfmt format. * Add `GoogleStructuredLogLayout` to support Google structured log format. +* `logforth::Builder` now has a `build` method to construct the `Logger` for use. ## [0.23.1] 2025-03-23 diff --git a/Cargo.lock b/Cargo.lock index 8a35111..6cd28f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -793,9 +793,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.26" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" dependencies = [ "serde", "value-bag", diff --git a/Cargo.toml b/Cargo.toml index 9f24a82..1a5db2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,7 +56,7 @@ internal-serde = ["dep:serde", "log/kv_serde", "jiff/serde"] anyhow = { version = "1.0" } env_filter = { version = "0.1.1" } jiff = { version = "0.2" } -log = { version = "0.4", features = ["std", "kv"] } +log = { version = "0.4.27", features = ["std", "kv"] } # Optional dependencies colored = { version = "3.0", optional = true } @@ -91,6 +91,11 @@ doc-scrape-examples = true name = "simple_stdout" path = "examples/simple_stdout.rs" +[[example]] +doc-scrape-examples = true +name = "log_with_logger" +path = "examples/log_with_logger.rs" + [[example]] doc-scrape-examples = true name = "json_stdout" diff --git a/examples/log_with_logger.rs b/examples/log_with_logger.rs new file mode 100644 index 0000000..ca73663 --- /dev/null +++ b/examples/log_with_logger.rs @@ -0,0 +1,26 @@ +// Copyright 2024 FastLabs Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use log::LevelFilter; + +fn main() { + log::set_max_level(LevelFilter::Trace); + let l = logforth::stdout().build(); + + log::error!(logger: l, "Hello error!"); + log::warn!(logger: l, "Hello warn!"); + log::info!(logger: l, "Hello info!"); + log::debug!(logger: l, "Hello debug!"); + log::trace!(logger: l, "Hello trace!"); +} diff --git a/src/append/journald/README.md b/src/append/journald/README.md index f12cdb1..eceb1b0 100644 --- a/src/append/journald/README.md +++ b/src/append/journald/README.md @@ -1,3 +1,3 @@ -# Rolling File Appender +# Journald Appender This appender is a remix of [tracing-journald](https://crates.io/crates/tracing-journald) and [systemd-journal-logger](https://crates.io/crates/systemd-journal-logger), with several modifications to fit this crate's needs. diff --git a/src/append/rolling_file/rotation.rs b/src/append/rolling_file/rotation.rs index bdff45c..d6a314a 100644 --- a/src/append/rolling_file/rotation.rs +++ b/src/append/rolling_file/rotation.rs @@ -32,6 +32,7 @@ pub enum Rotation { } impl Rotation { + /// Get the next date timestamp based on the current date and rotation policy. pub fn next_date_timestamp(&self, current_date: &Zoned) -> Option { let timestamp_round = ZonedRound::new().mode(RoundMode::Trunc); @@ -50,6 +51,7 @@ impl Rotation { Some(next_date.timestamp().as_millisecond() as usize) } + /// Get the date format string for the rotation policy. pub fn date_format(&self) -> &'static str { match *self { Rotation::Minutely => "%F-%H-%M", diff --git a/src/diagnostic/mod.rs b/src/diagnostic/mod.rs index e7505ba..698175a 100644 --- a/src/diagnostic/mod.rs +++ b/src/diagnostic/mod.rs @@ -36,6 +36,7 @@ pub trait Visitor { /// A trait representing a Mapped Diagnostic Context (MDC) that provides diagnostic key-values. pub trait Diagnostic: fmt::Debug + Send + Sync + 'static { + /// Visits the MDC key-values with the provided visitor. fn visit(&self, visitor: &mut dyn Visitor) -> anyhow::Result<()>; } diff --git a/src/diagnostic/static_global.rs b/src/diagnostic/static_global.rs index a5ef6ef..632b6e7 100644 --- a/src/diagnostic/static_global.rs +++ b/src/diagnostic/static_global.rs @@ -34,10 +34,12 @@ pub struct StaticDiagnostic { } impl StaticDiagnostic { + /// Creates a new [`StaticDiagnostic`] instance with a prebuilt key-value store. pub fn new(kvs: BTreeMap) -> Self { Self { kvs } } + /// Inserts a key-value pair into the static diagnostic . pub fn insert(&mut self, key: K, value: V) where K: Into, @@ -46,6 +48,7 @@ impl StaticDiagnostic { self.kvs.insert(key.into(), value.into()); } + /// Remove a key-value pair from the static diagnostic. pub fn remove(&mut self, key: &str) { self.kvs.remove(key); } diff --git a/src/diagnostic/thread_local.rs b/src/diagnostic/thread_local.rs index 332dfff..a6a8aa5 100644 --- a/src/diagnostic/thread_local.rs +++ b/src/diagnostic/thread_local.rs @@ -36,6 +36,7 @@ thread_local! { pub struct ThreadLocalDiagnostic {} impl ThreadLocalDiagnostic { + /// Inserts a key-value pair into the thread local diagnostic . pub fn insert(key: K, value: V) where K: Into, @@ -46,6 +47,7 @@ impl ThreadLocalDiagnostic { }); } + /// Removes a key-value pair from the thread local diagnostic. pub fn remove(key: &str) { CONTEXT.with(|map| { map.borrow_mut().remove(key); diff --git a/src/lib.rs b/src/lib.rs index e041a38..ab92ba3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![deny(missing_docs)] + //! Logforth is a flexible logging framework for Rust applications, providing easy log dispatching //! and configuration. //! diff --git a/src/logger/builder.rs b/src/logger/builder.rs index c1efa31..685b7d4 100644 --- a/src/logger/builder.rs +++ b/src/logger/builder.rs @@ -85,17 +85,11 @@ pub fn stderr() -> Builder { pub struct Builder { // stashed dispatches dispatches: Vec, - - // default to trace - we need this because the global default is OFF - max_level: LevelFilter, } impl Builder { fn new() -> Self { - Builder { - dispatches: vec![], - max_level: LevelFilter::Trace, - } + Builder { dispatches: vec![] } } /// Registers a new dispatch with the [`Builder`]. @@ -117,20 +111,9 @@ impl Builder { self } - /// Sets the global maximum log level. Default to [`LevelFilter::Trace`]. - /// - /// This will be passed to `log::set_max_level()`. - /// - /// # Examples - /// - /// ``` - /// logforth::builder() - /// .max_level(log::LevelFilter::Warn) - /// .apply(); - /// ``` - pub fn max_level(mut self, max_level: LevelFilter) -> Self { - self.max_level = max_level; - self + /// Build the [`Logger`]. + pub fn build(self) -> Logger { + Logger::new(self.dispatches) } /// Sets up the global logger with all the configured dispatches. @@ -138,6 +121,10 @@ impl Builder { /// This should be called early in the execution of a Rust program. Any log events that occur /// before initialization will be ignored. /// + /// This will set the global maximum log level to [`LevelFilter::Trace`]. To override this, + /// call [`log::set_max_level`] after this function. Alternatively, you can obtain a [`Logger`] instance + /// by calling [`Builder::build`], and then call [`log::set_boxed_logger`] manually. + /// /// # Errors /// /// Returns an error if a global logger has already been set. @@ -151,9 +138,9 @@ impl Builder { /// } /// ``` pub fn try_apply(self) -> Result<(), log::SetLoggerError> { - let logger = Logger::new(self.dispatches); + let logger = self.build(); log::set_boxed_logger(Box::new(logger))?; - log::set_max_level(self.max_level); + log::set_max_level(LevelFilter::Trace); Ok(()) } @@ -162,6 +149,10 @@ impl Builder { /// This function will panic if it is called more than once, or if another library has already /// initialized a global logger. /// + /// This function will set the global maximum log level to [`LevelFilter::Trace`]. To override this, + /// call [`log::set_max_level`] after this function. Alternatively, you can obtain a [`Logger`] instance + /// by calling [`Builder::build`], and then call [`log::set_boxed_logger`] manually. + /// /// # Panics /// /// Panics if the global logger has already been set. diff --git a/src/logger/log_impl.rs b/src/logger/log_impl.rs index ab91794..db2e154 100644 --- a/src/logger/log_impl.rs +++ b/src/logger/log_impl.rs @@ -22,12 +22,12 @@ use crate::Append; use crate::Diagnostic; use crate::Filter; -/// A logger facade that dispatches log records to one or more [`Dispatch`] instances. +/// A logger facade that dispatches log records to one or more dispatcher. /// /// This struct implements [`log::Log`] to bridge Logforth's logging implementations /// with the [`log`] crate. #[derive(Debug)] -pub(super) struct Logger { +pub struct Logger { dispatches: Vec, } diff --git a/src/logger/mod.rs b/src/logger/mod.rs index c7f7d07..fb0bab6 100644 --- a/src/logger/mod.rs +++ b/src/logger/mod.rs @@ -16,3 +16,4 @@ mod builder; pub use builder::*; mod log_impl; +pub use log_impl::Logger; From f932113c59b681416f3b67dbf94dbdf6b3bc581b Mon Sep 17 00:00:00 2001 From: tison Date: Wed, 9 Apr 2025 12:11:35 +0800 Subject: [PATCH 2/2] rename Signed-off-by: tison --- CHANGELOG.md | 5 +++-- src/logger/builder.rs | 43 ++++++++++++++++++++++++++----------------- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0985915..6ca3ae8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,8 @@ All notable changes to this project will be documented in this file. * `Append::flush` is now fallible. * `Diagnostic`'s and `Visitor`'s `visit` methods are fallible. * `NonBlocking` related types and the feature flag are now private. -* `logforth::Builder` has no longer an option to configure the global `max_level`. Check its documentation for more details. +* `logforth::Builder` is renamed to `logforth::LoggerBuilder`. +* `LoggerBuilder` has no longer an option to configure the global `max_level`. Check its documentation for more details. * Constructing `RollingFile` and `Syslog` appender is heavily simplified. Before: @@ -76,7 +77,7 @@ fn construct_syslog() { * Add `LogfmtLayout` to support logfmt format. * Add `GoogleStructuredLogLayout` to support Google structured log format. -* `logforth::Builder` now has a `build` method to construct the `Logger` for use. +* `LoggerBuilder` now has a `build` method to construct the `Logger` for use. ## [0.23.1] 2025-03-23 diff --git a/src/logger/builder.rs b/src/logger/builder.rs index 685b7d4..e6c742b 100644 --- a/src/logger/builder.rs +++ b/src/logger/builder.rs @@ -22,7 +22,7 @@ use crate::Append; use crate::Diagnostic; use crate::Filter; -/// Creates a new empty [`Builder`] instance for configuring log dispatching. +/// Creates a new empty [`LoggerBuilder`] instance for configuring log dispatching. /// /// # Examples /// @@ -33,11 +33,11 @@ use crate::Filter; /// .dispatch(|d| d.append(append::Stderr::default())) /// .apply(); /// ``` -pub fn builder() -> Builder { - Builder::new() +pub fn builder() -> LoggerBuilder { + LoggerBuilder::new() } -/// Creates a [`Builder`] with a default [`append::Stdout`] appender and an [`env_filter`](https://crates.io/crates/env_filter) +/// Creates a [`LoggerBuilder`] with a default [`append::Stdout`] appender and an [`env_filter`](https://crates.io/crates/env_filter) /// respecting `RUST_LOG`. /// /// # Examples @@ -46,14 +46,14 @@ pub fn builder() -> Builder { /// logforth::stdout().apply(); /// log::error!("This error will be logged to stdout."); /// ``` -pub fn stdout() -> Builder { +pub fn stdout() -> LoggerBuilder { builder().dispatch(|d| { d.filter(EnvFilter::from_default_env()) .append(append::Stdout::default()) }) } -/// Creates a [`Builder`] with a default [`append::Stderr`] appender and an [`env_filter`](https://crates.io/crates/env_filter) +/// Creates a [`LoggerBuilder`] with a default [`append::Stderr`] appender and an [`env_filter`](https://crates.io/crates/env_filter) /// respecting `RUST_LOG`. /// /// # Examples @@ -62,7 +62,7 @@ pub fn stdout() -> Builder { /// logforth::stderr().apply(); /// log::info!("This info will be logged to stderr."); /// ``` -pub fn stderr() -> Builder { +pub fn stderr() -> LoggerBuilder { builder().dispatch(|d| { d.filter(EnvFilter::from_default_env()) .append(append::Stderr::default()) @@ -80,19 +80,19 @@ pub fn stderr() -> Builder { /// .dispatch(|d| d.append(append::Stdout::default())) /// .apply(); /// ``` -#[must_use = "call `apply` to set the global logger"] +#[must_use = "call `apply` to set the global logger or `build` to construct a logger instance"] #[derive(Debug)] -pub struct Builder { +pub struct LoggerBuilder { // stashed dispatches dispatches: Vec, } -impl Builder { +impl LoggerBuilder { fn new() -> Self { - Builder { dispatches: vec![] } + LoggerBuilder { dispatches: vec![] } } - /// Registers a new dispatch with the [`Builder`]. + /// Registers a new dispatch with the [`LoggerBuilder`]. /// /// # Examples /// @@ -112,6 +112,13 @@ impl Builder { } /// Build the [`Logger`]. + /// + /// # Examples + /// + /// ``` + /// let l = logforth::builder().build(); + /// log::error!(logger: l, "Hello error!"); + /// ``` pub fn build(self) -> Logger { Logger::new(self.dispatches) } @@ -122,8 +129,9 @@ impl Builder { /// before initialization will be ignored. /// /// This will set the global maximum log level to [`LevelFilter::Trace`]. To override this, - /// call [`log::set_max_level`] after this function. Alternatively, you can obtain a [`Logger`] instance - /// by calling [`Builder::build`], and then call [`log::set_boxed_logger`] manually. + /// call [`log::set_max_level`] after this function. Alternatively, you can obtain a [`Logger`] + /// instance by calling [`LoggerBuilder::build`], and then call [`log::set_boxed_logger`] + /// manually. /// /// # Errors /// @@ -149,9 +157,10 @@ impl Builder { /// This function will panic if it is called more than once, or if another library has already /// initialized a global logger. /// - /// This function will set the global maximum log level to [`LevelFilter::Trace`]. To override this, - /// call [`log::set_max_level`] after this function. Alternatively, you can obtain a [`Logger`] instance - /// by calling [`Builder::build`], and then call [`log::set_boxed_logger`] manually. + /// This function will set the global maximum log level to [`LevelFilter::Trace`]. To override + /// this, call [`log::set_max_level`] after this function. Alternatively, you can obtain a + /// [`Logger`] instance by calling [`LoggerBuilder::build`], and then call + /// [`log::set_boxed_logger`] manually. /// /// # Panics ///