diff --git a/CHANGELOG.md b/CHANGELOG.md index 432d2de..d25b22b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ All notable changes to this project will be documented in this file. ### Breaking changes * Bump minimum supported Rust version (MSRV) to 1.91.0. +* Replace `LoggerBuilder` with `LoggerProviderBuilder`; build a `LoggerProvider` first and obtain lightweight `Logger` handles from it. +* Replace the prefilter-only `FilterCriteria` with `Metadata`, which is shared by prefiltering and the resulting `Record`. ### New features diff --git a/README.md b/README.md index 05bcb67..15f4345 100644 --- a/README.md +++ b/README.md @@ -52,22 +52,23 @@ use logforth::append; use logforth::record::Level; fn main() { - let logger = logforth::core::builder() + let provider = logforth::core::builder() .dispatch(|d| d.append(append::Stdout::default())) .build(); + let logger = provider.logger(); logforth::info!(logger, request_id = 42_u64; "request accepted"); logforth::log!(logger, Level::Info2, "request details"); } ``` -The logger is the first argument, following the same instance-first convention as `slog`. An unnamed logger uses the call-site module path as its target. A dedicated logger can instead carry one stable name, which keeps target-based `RustLogFilter` directives without repeating `target:` at every call site: +The logger is the first argument, following the same instance-first convention as `slog`. A `LoggerProvider` owns the configured dispatches and creates lightweight logger handles. An unnamed logger uses the call-site module path as its target. A logger can instead carry one stable name, which keeps target-based `RustLogFilter` directives without repeating `target:` at every call site: ```rust -let metering = logforth::core::builder() - .name("metering") +let provider = logforth::core::builder() .dispatch(|d| d.append(logforth::append::Stdout::default())) .build(); +let metering = provider.named_logger("metering"); logforth::info!( metering, @@ -77,7 +78,7 @@ logforth::info!( ); ``` -The logger name is a stable channel or source scope, so a directive such as `RUST_LOG=metering=info` keeps working. Event kinds, tenant IDs, and other varying classifications remain structured fields. The record still carries the call-site module path separately. +The logger name is a stable filtering namespace, so a directive such as `RUST_LOG=metering=info` keeps working. Event kinds, tenant IDs, and other varying classifications remain structured fields. The record still carries the call-site module path separately. The macros check the logger before evaluating the message or its fields, so an extra enabled check is unnecessary. The `log` facade remains the recommended API for libraries because it lets the final application choose its logging implementation. diff --git a/appenders/file/src/lib.rs b/appenders/file/src/lib.rs index 9c03e95..52d3276 100644 --- a/appenders/file/src/lib.rs +++ b/appenders/file/src/lib.rs @@ -30,7 +30,8 @@ //! //! let logger = logforth_core::builder() //! .dispatch(|d| d.filter(LevelFilter::All).append(rolling)) -//! .build(); +//! .build() +//! .logger(); //! ``` #![cfg_attr(docsrs, feature(doc_cfg))] diff --git a/appenders/syslog/src/lib.rs b/appenders/syslog/src/lib.rs index 3440935..916c9af 100644 --- a/appenders/syslog/src/lib.rs +++ b/appenders/syslog/src/lib.rs @@ -25,7 +25,8 @@ //! //! let logger = logforth_core::builder() //! .dispatch(|d| d.filter(LevelFilter::All).append(append)) -//! .build(); +//! .build() +//! .logger(); //! ``` #![cfg_attr(docsrs, feature(doc_cfg))] diff --git a/bridges/log/src/lib.rs b/bridges/log/src/lib.rs index 8d704a1..528aee8 100644 --- a/bridges/log/src/lib.rs +++ b/bridges/log/src/lib.rs @@ -23,7 +23,7 @@ use std::sync::Arc; use log::Metadata; use log::Record; use logforth_core::Logger; -use logforth_core::record::FilterCriteria; +use logforth_core::record::Metadata as LogforthMetadata; /// Adapter to use a `logforth` logger instance as a `log` crate logger. #[derive(Debug)] @@ -63,36 +63,38 @@ impl log::Log for LogBridge { } fn forward_enabled(logger: &Logger, metadata: &Metadata) -> bool { - let criteria = FilterCriteria::builder() + let metadata = LogforthMetadata::builder() .target(metadata.target()) .level(level_to_level(metadata.level())) .build(); - Logger::enabled(logger, &criteria) + Logger::enabled(logger, &metadata) } fn forward_log(logger: &Logger, record: &Record) { - if !forward_enabled(logger, record.metadata()) { - return; - } - - // basic fields - let mut builder = logforth_core::record::Record::builder() + let mut metadata = LogforthMetadata::builder() .level(level_to_level(record.level())) .target(record.target()) .line(record.line()); // optional static fields - builder = if let Some(module_path) = record.module_path_static() { - builder.module_path_static(module_path) + metadata = if let Some(module_path) = record.module_path_static() { + metadata.module_path_static(module_path) } else { - builder.module_path(record.module_path()) + metadata.module_path(record.module_path()) }; - builder = if let Some(file) = record.file_static() { - builder.file_static(file) + metadata = if let Some(file) = record.file_static() { + metadata.file_static(file) } else { - builder.file(record.file()) + metadata.file(record.file()) }; + let metadata = metadata.build(); + + if !Logger::enabled(logger, &metadata) { + return; + } + + let mut builder = logforth_core::record::Record::builder().metadata(metadata); // payload builder = builder.payload(*record.args()); diff --git a/core/src/filter/mod.rs b/core/src/filter/mod.rs index 2be1190..19766ef 100644 --- a/core/src/filter/mod.rs +++ b/core/src/filter/mod.rs @@ -17,8 +17,8 @@ use std::fmt; use crate::Diagnostic; -use crate::record::FilterCriteria; use crate::record::LevelFilter; +use crate::record::Metadata; use crate::record::Record; /// The result of a filter check. @@ -34,28 +34,23 @@ pub enum FilterResult { /// A filter that can be applied to log records. pub trait Filter: fmt::Debug + Send + Sync + 'static { - /// Prefilter a record using criteria available before the complete record is constructed. + /// Prefilter a record using metadata available before the complete record is constructed. /// /// A filter that needs the message or structured fields to decide must return /// [`FilterResult::Neutral`] here and make that decision in [`Filter::matches`]. Returning - /// [`FilterResult::Reject`] promises that every record with these criteria can be rejected + /// [`FilterResult::Reject`] promises that every record with this metadata can be rejected /// without constructing it. - fn enabled(&self, criteria: &FilterCriteria, diags: &[Box]) -> FilterResult; + fn enabled(&self, metadata: &Metadata, diags: &[Box]) -> FilterResult; /// Whether the record is filtered. fn matches(&self, record: &Record, diags: &[Box]) -> FilterResult { - let criteria = FilterCriteria::builder() - .level(record.level()) - .target(record.target()) - .build(); - - self.enabled(&criteria, diags) + self.enabled(record.metadata(), diags) } } impl Filter for LevelFilter { - fn enabled(&self, criteria: &FilterCriteria, _: &[Box]) -> FilterResult { - if self.test(criteria.level()) { + fn enabled(&self, metadata: &Metadata, _: &[Box]) -> FilterResult { + if self.test(metadata.level()) { FilterResult::Neutral } else { FilterResult::Reject diff --git a/core/src/logger/builder.rs b/core/src/logger/builder.rs index 315a799..c38fc46 100644 --- a/core/src/logger/builder.rs +++ b/core/src/logger/builder.rs @@ -15,89 +15,56 @@ use crate::Append; use crate::Diagnostic; use crate::Filter; -use crate::Logger; +use crate::LoggerProvider; use crate::logger::log_impl::Dispatch; -/// Create a new empty [`LoggerBuilder`] instance for configuring log dispatching. +/// Create a new empty [`LoggerProviderBuilder`] for configuring log dispatching. /// /// # Examples /// /// ``` /// use logforth_core::append; /// -/// let logger = logforth_core::builder() +/// let provider = logforth_core::builder() /// .dispatch(|d| d.append(append::Stderr::default())) /// .build(); +/// let logger = provider.logger(); /// ``` -pub fn builder() -> LoggerBuilder { - LoggerBuilder { - name: None, - dispatches: vec![], - } +pub fn builder() -> LoggerProviderBuilder { + LoggerProviderBuilder { dispatches: vec![] } } -/// A builder for configuring log dispatching. +/// A builder for configuring a [`LoggerProvider`]. /// /// # Examples /// /// ``` /// use logforth_core::append; /// -/// let logger = logforth_core::builder() +/// let provider = logforth_core::builder() /// .dispatch(|d| d.append(append::Stdout::default())) /// .build(); +/// let logger = provider.logger(); /// ``` -#[must_use = "call `build` to construct a logger instance"] +#[must_use = "call `build` to construct a logger provider"] #[derive(Debug)] -pub struct LoggerBuilder { - // optional stable logger name - name: Option<&'static str>, - +pub struct LoggerProviderBuilder { // stashed dispatches dispatches: Vec, } -impl LoggerBuilder { - /// Assign a stable name to the logger. - /// - /// Native logging macros use this name as the record target. An unnamed logger instead uses - /// the call-site module path. The source module is recorded separately in both cases. - /// - /// A name is useful for a dedicated event channel such as `metering` or `audit`: routing is - /// selected by the logger instance, while target-based filters such as `RustLogFilter` can - /// retain the same stable namespace. Per-event classifications should remain structured - /// fields rather than logger names. - /// - /// Names must be static because they describe a bounded, application-defined namespace rather - /// than dynamic event data. - /// - /// # Examples - /// - /// ``` - /// use logforth_core::append; - /// - /// let metering = logforth_core::builder() - /// .name("metering") - /// .dispatch(|d| d.append(append::Stdout::default())) - /// .build(); - /// - /// assert_eq!(metering.name(), Some("metering")); - /// ``` - pub fn name(mut self, name: &'static str) -> Self { - self.name = Some(name); - self - } - - /// Register a new dispatch with the [`LoggerBuilder`]. +impl LoggerProviderBuilder { + /// Register a new dispatch with the [`LoggerProviderBuilder`]. /// /// # Examples /// /// ``` /// use logforth_core::append; /// - /// let logger = logforth_core::builder() + /// let provider = logforth_core::builder() /// .dispatch(|d| d.append(append::Stderr::default())) /// .build(); + /// let logger = provider.logger(); /// ``` pub fn dispatch(mut self, f: F) -> Self where @@ -107,21 +74,22 @@ impl LoggerBuilder { self } - /// Build the [`Logger`]. + /// Build the [`LoggerProvider`]. /// /// # Examples /// /// ``` /// use logforth_core::record::Record; /// - /// let l = logforth_core::builder().build(); - /// let r = Record::builder() + /// let provider = logforth_core::builder().build(); + /// let logger = provider.logger(); + /// let record = Record::builder() /// .payload(format_args!("hello world!")) /// .build(); - /// l.log(&r); + /// logger.log(&record); /// ``` - pub fn build(self) -> Logger { - Logger::new(self.name, self.dispatches) + pub fn build(self) -> LoggerProvider { + LoggerProvider::new(self.dispatches) } } @@ -134,12 +102,13 @@ impl LoggerBuilder { /// use logforth_core::record::Level; /// use logforth_core::record::LevelFilter; /// -/// let logger = logforth_core::builder() +/// let provider = logforth_core::builder() /// .dispatch(|d| { /// d.filter(LevelFilter::MoreSevereEqual(Level::Info)) /// .append(append::Stdout::default()) /// }) /// .build(); +/// let logger = provider.logger(); /// ``` #[derive(Debug)] pub struct DispatchBuilder { @@ -166,12 +135,13 @@ impl DispatchBuilder { /// use logforth_core::record::Level; /// use logforth_core::record::LevelFilter; /// - /// let logger = logforth_core::builder() + /// let provider = logforth_core::builder() /// .dispatch(|d| { /// d.filter(LevelFilter::MoreSevereEqual(Level::Error)) /// .append(append::Stderr::default()) /// }) /// .build(); + /// let logger = provider.logger(); /// ``` pub fn filter(mut self, filter: impl Into>) -> Self { self.filters.push(filter.into()); @@ -188,13 +158,14 @@ impl DispatchBuilder { /// use logforth_core::record::Level; /// use logforth_core::record::LevelFilter; /// - /// let logger = logforth_core::builder() + /// let provider = logforth_core::builder() /// .dispatch(|d| { /// d.filter(LevelFilter::MoreSevereEqual(Level::Error)) /// .diagnostic(diagnostic::ThreadLocalDiagnostic::default()) /// .append(append::Stderr::default()) /// }) /// .build(); + /// let logger = provider.logger(); /// ``` pub fn diagnostic(mut self, diagnostic: impl Into>) -> Self { self.diagnostics.push(diagnostic.into()); @@ -216,9 +187,10 @@ impl DispatchBuilder { /// ``` /// use logforth_core::append; /// - /// let logger = logforth_core::builder() + /// let provider = logforth_core::builder() /// .dispatch(|d| d.append(append::Stdout::default())) /// .build(); + /// let logger = provider.logger(); /// ``` pub fn append(mut self, append: impl Into>) -> DispatchBuilder { self.appends.push(append.into()); diff --git a/core/src/logger/log_impl.rs b/core/src/logger/log_impl.rs index 460ec27..c95bd39 100644 --- a/core/src/logger/log_impl.rs +++ b/core/src/logger/log_impl.rs @@ -14,26 +14,88 @@ use std::io::Write; use std::panic; +use std::sync::Arc; use crate::Append; use crate::Diagnostic; use crate::Error; use crate::Filter; use crate::filter::FilterResult; -use crate::record::FilterCriteria; +use crate::record::Metadata; use crate::record::Record; -/// A logger that dispatches log records to one or more dispatcher. +/// A provider that owns log dispatch configuration and creates [`Logger`] instances. +#[derive(Clone, Debug)] +pub struct LoggerProvider { + inner: Arc, +} + #[derive(Debug)] -pub struct Logger { - name: Option<&'static str>, +struct LoggerProviderInner { dispatches: Vec, } -impl Logger { - pub(super) fn new(name: Option<&'static str>, dispatches: Vec) -> Self { - Self { name, dispatches } +impl LoggerProvider { + /// Create a builder for a logger provider. + pub fn builder() -> super::LoggerProviderBuilder { + super::builder() + } + + pub(super) fn new(dispatches: Vec) -> Self { + Self { + inner: Arc::new(LoggerProviderInner { dispatches }), + } + } + + /// Create a logger backed by this provider. + pub fn logger(&self) -> Logger { + Logger { + provider: self.clone(), + name: None, + } + } + + /// Create a logger with a stable name. + /// + /// Native logging macros use this name as the record target. The source module remains + /// available separately in [`Record::module_path`]. + pub fn named_logger(&self, name: &'static str) -> Logger { + Logger { + provider: self.clone(), + name: Some(name), + } + } + + /// Flush any buffered records from every configured dispatch. + pub fn flush(&self) { + for dispatch in &self.inner.dispatches { + for err in dispatch.flush() { + handle_flush_error(&err); + } + } } + + fn enabled(&self, metadata: &Metadata) -> bool { + self.inner + .dispatches + .iter() + .any(|dispatch| dispatch.enabled(metadata)) + } + + fn log(&self, record: &Record) { + for dispatch in &self.inner.dispatches { + for err in dispatch.log(record) { + handle_log_error(record, &err); + } + } + } +} + +/// A lightweight handle that emits records through a [`LoggerProvider`]. +#[derive(Clone, Debug)] +pub struct Logger { + provider: LoggerProvider, + name: Option<&'static str>, } impl Logger { @@ -46,35 +108,25 @@ impl Logger { self.name } - /// Determine whether any dispatch may log a record with the specified criteria. + /// Determine whether any dispatch may log a record with the specified metadata. /// /// This is a prefiltering hint, not a promise that a subsequent record will be logged. Filters /// may make their final decision from the complete [`Record`], and configuration may change /// between this call and [`Logger::log`]. Calling this method before `log` is optional; the /// native logging macros already avoid evaluating messages and fields when prefiltering rejects /// them. - pub fn enabled(&self, criteria: &FilterCriteria) -> bool { - self.dispatches - .iter() - .any(|dispatch| dispatch.enabled(criteria)) + pub fn enabled(&self, metadata: &Metadata) -> bool { + self.provider.enabled(metadata) } /// Log the [`Record`]. pub fn log(&self, record: &Record) { - for dispatch in &self.dispatches { - for err in dispatch.log(record) { - handle_log_error(record, &err); - } - } + self.provider.log(record); } /// Flush any buffered records. pub fn flush(&self) { - for dispatch in &self.dispatches { - for err in dispatch.flush() { - handle_flush_error(&err); - } - } + self.provider.flush(); } } @@ -110,11 +162,11 @@ impl Dispatch { } } - fn enabled(&self, criteria: &FilterCriteria) -> bool { + fn enabled(&self, metadata: &Metadata) -> bool { let diagnostics = &self.diagnostics; for filter in &self.filters { - match filter.enabled(criteria, diagnostics) { + match filter.enabled(metadata, diagnostics) { FilterResult::Reject => return false, FilterResult::Accept => return true, FilterResult::Neutral => {} diff --git a/core/src/logger/mod.rs b/core/src/logger/mod.rs index f101393..adb3c50 100644 --- a/core/src/logger/mod.rs +++ b/core/src/logger/mod.rs @@ -14,8 +14,9 @@ mod builder; pub use self::builder::DispatchBuilder; -pub use self::builder::LoggerBuilder; +pub use self::builder::LoggerProviderBuilder; pub use self::builder::builder; mod log_impl; pub use self::log_impl::Logger; +pub use self::log_impl::LoggerProvider; diff --git a/core/src/macros.rs b/core/src/macros.rs index b64e2d1..80172bc 100644 --- a/core/src/macros.rs +++ b/core/src/macros.rs @@ -32,7 +32,7 @@ /// ``` /// use logforth_core::record::Level; /// -/// let logger = logforth_core::builder().build(); +/// let logger = logforth_core::builder().build().logger(); /// let request_id = 42_u64; /// logforth_core::log!( /// logger, @@ -60,7 +60,7 @@ macro_rules! log { /// # Examples /// /// ``` -/// let logger = logforth_core::builder().build(); +/// let logger = logforth_core::builder().build().logger(); /// logforth_core::fatal!(logger, "unrecoverable failure"); /// ``` #[macro_export] @@ -79,7 +79,7 @@ macro_rules! fatal { /// # Examples /// /// ``` -/// let logger = logforth_core::builder().build(); +/// let logger = logforth_core::builder().build().logger(); /// logforth_core::error!(logger, "operation failed"); /// ``` #[macro_export] @@ -98,7 +98,7 @@ macro_rules! error { /// # Examples /// /// ``` -/// let logger = logforth_core::builder().build(); +/// let logger = logforth_core::builder().build().logger(); /// logforth_core::warn!(logger, "retrying operation"); /// ``` #[macro_export] @@ -117,7 +117,7 @@ macro_rules! warn { /// # Examples /// /// ``` -/// let logger = logforth_core::builder().build(); +/// let logger = logforth_core::builder().build().logger(); /// logforth_core::info!(logger, user_id = 42_u64; "user connected"); /// ``` #[macro_export] @@ -136,7 +136,7 @@ macro_rules! info { /// # Examples /// /// ``` -/// let logger = logforth_core::builder().build(); +/// let logger = logforth_core::builder().build().logger(); /// logforth_core::debug!(logger, "state updated"); /// ``` #[macro_export] @@ -155,7 +155,7 @@ macro_rules! debug { /// # Examples /// /// ``` -/// let logger = logforth_core::builder().build(); +/// let logger = logforth_core::builder().build().logger(); /// logforth_core::trace!(logger, "entered operation"); /// ``` #[macro_export] @@ -178,19 +178,18 @@ macro_rules! __log { let __logforth_target = __logforth_logger .name() .unwrap_or(::std::module_path!()); - let __logforth_criteria = $crate::record::FilterCriteria::builder() + let __logforth_metadata = $crate::record::Metadata::builder() .level(__logforth_level) - .target(__logforth_target) + .target_static(__logforth_target) + .module_path_static(::std::module_path!()) + .file_static(::std::file!()) + .line(::std::option::Option::Some(::std::line!())) + .column(::std::option::Option::Some(::std::column!())) .build(); - if __logforth_logger.enabled(&__logforth_criteria) { + if __logforth_logger.enabled(&__logforth_metadata) { __logforth_logger.log( &$crate::record::Record::builder() - .level(__logforth_level) - .target_static(__logforth_target) - .module_path_static(::std::module_path!()) - .file_static(::std::file!()) - .line(::std::option::Option::Some(::std::line!())) - .column(::std::option::Option::Some(::std::column!())) + .metadata(__logforth_metadata) .payload(::std::format_args!($($message)+)) .key_values(&[ $(( @@ -211,19 +210,18 @@ macro_rules! __log { let __logforth_target = __logforth_logger .name() .unwrap_or(::std::module_path!()); - let __logforth_criteria = $crate::record::FilterCriteria::builder() + let __logforth_metadata = $crate::record::Metadata::builder() .level(__logforth_level) - .target(__logforth_target) + .target_static(__logforth_target) + .module_path_static(::std::module_path!()) + .file_static(::std::file!()) + .line(::std::option::Option::Some(::std::line!())) + .column(::std::option::Option::Some(::std::column!())) .build(); - if __logforth_logger.enabled(&__logforth_criteria) { + if __logforth_logger.enabled(&__logforth_metadata) { __logforth_logger.log( &$crate::record::Record::builder() - .level(__logforth_level) - .target_static(__logforth_target) - .module_path_static(::std::module_path!()) - .file_static(::std::file!()) - .line(::std::option::Option::Some(::std::line!())) - .column(::std::option::Option::Some(::std::column!())) + .metadata(__logforth_metadata) .payload(::std::format_args!($($message)+)) .build(), ); diff --git a/core/src/record.rs b/core/src/record.rs index 61eeaf5..272c756 100644 --- a/core/src/record.rs +++ b/core/src/record.rs @@ -24,6 +24,167 @@ use crate::kv; use crate::kv::KeyValues; use crate::str::RefStr; +/// Metadata that identifies a log record before its body and structured values are evaluated. +#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub struct Metadata<'a> { + level: Level, + target: RefStr<'a>, + module_path: Option>, + file: Option>, + line: Option, + column: Option, +} + +impl<'a> Metadata<'a> { + /// The severity level of the record. + pub fn level(&self) -> Level { + self.level + } + + /// The stable namespace used for target-based filtering. + pub fn target(&self) -> &'a str { + self.target.get() + } + + /// The target, if it is a `'static` string. + pub fn target_static(&self) -> Option<&'static str> { + self.target.get_static() + } + + /// The module path of the source that emitted the record. + pub fn module_path(&self) -> Option<&'a str> { + self.module_path.map(|path| path.get()) + } + + /// The module path, if it is a `'static` string. + pub fn module_path_static(&self) -> Option<&'static str> { + self.module_path.and_then(|path| path.get_static()) + } + + /// The source file that emitted the record. + pub fn file(&self) -> Option<&'a str> { + self.file.map(|file| file.get()) + } + + /// The source file, if it is a `'static` string. + pub fn file_static(&self) -> Option<&'static str> { + self.file.and_then(|file| file.get_static()) + } + + /// The line number in the source file. + pub fn line(&self) -> Option { + self.line + } + + /// The column number in the source file. + pub fn column(&self) -> Option { + self.column + } + + /// Create a builder initialized with this metadata. + pub fn to_builder(&self) -> MetadataBuilder<'a> { + MetadataBuilder { metadata: *self } + } + + /// Create a new metadata builder. + pub fn builder() -> MetadataBuilder<'a> { + MetadataBuilder::default() + } + + fn to_owned(self) -> MetadataOwned { + MetadataOwned { + level: self.level, + target: self.target.into_cow_static(), + module_path: self.module_path.map(|path| path.into_cow_static()), + file: self.file.map(|file| file.into_cow_static()), + line: self.line, + column: self.column, + } + } +} + +/// Builder for [`Metadata`]. +#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub struct MetadataBuilder<'a> { + metadata: Metadata<'a>, +} + +impl Default for MetadataBuilder<'_> { + fn default() -> Self { + Self { + metadata: Metadata { + level: Level::Info, + target: RefStr::Static(""), + module_path: None, + file: None, + line: None, + column: None, + }, + } + } +} + +impl<'a> MetadataBuilder<'a> { + /// Set the severity level. + pub fn level(mut self, level: Level) -> Self { + self.metadata.level = level; + self + } + + /// Set the target. + pub fn target(mut self, target: &'a str) -> Self { + self.metadata.target = RefStr::Borrowed(target); + self + } + + /// Set the target to a `'static` string. + pub fn target_static(mut self, target: &'static str) -> Self { + self.metadata.target = RefStr::Static(target); + self + } + + /// Set the source module path. + pub fn module_path(mut self, path: Option<&'a str>) -> Self { + self.metadata.module_path = path.map(RefStr::Borrowed); + self + } + + /// Set the source module path to a `'static` string. + pub fn module_path_static(mut self, path: &'static str) -> Self { + self.metadata.module_path = Some(RefStr::Static(path)); + self + } + + /// Set the source file. + pub fn file(mut self, file: Option<&'a str>) -> Self { + self.metadata.file = file.map(RefStr::Borrowed); + self + } + + /// Set the source file to a `'static` string. + pub fn file_static(mut self, file: &'static str) -> Self { + self.metadata.file = Some(RefStr::Static(file)); + self + } + + /// Set the source line. + pub fn line(mut self, line: Option) -> Self { + self.metadata.line = line; + self + } + + /// Set the source column. + pub fn column(mut self, column: Option) -> Self { + self.metadata.column = column; + self + } + + /// Build the metadata. + pub fn build(self) -> Metadata<'a> { + self.metadata + } +} + /// The payload of a log message. #[derive(Clone, Debug)] pub struct Record<'a> { @@ -31,12 +192,7 @@ pub struct Record<'a> { now: SystemTime, // the metadata - level: Level, - target: RefStr<'a>, - module_path: Option>, - file: Option>, - line: Option, - column: Option, + metadata: Metadata<'a>, // the payload payload: fmt::Arguments<'a>, @@ -51,11 +207,16 @@ impl<'a> Record<'a> { self.now } + /// The metadata associated with this record. + pub fn metadata(&self) -> &Metadata<'a> { + &self.metadata + } + /// The severity level of the message. /// /// See [`Level`] for details. pub fn level(&self) -> Level { - self.level + self.metadata.level() } /// The stable namespace used for target-based filtering. @@ -64,34 +225,34 @@ impl<'a> Record<'a> { /// named logger. Bridges preserve the target supplied by the source logging facade. The actual /// source module, when known, is available separately from [`Record::module_path`]. pub fn target(&self) -> &'a str { - self.target.get() + self.metadata.target() } /// The stable namespace used for target-based filtering, if it is a `'static` str. /// /// See [`Record::target`] for target semantics. pub fn target_static(&self) -> Option<&'static str> { - self.target.get_static() + self.metadata.target_static() } /// The module path of the message. pub fn module_path(&self) -> Option<&'a str> { - self.module_path.map(|s| s.get()) + self.metadata.module_path() } /// The module path of the message, if it is a `'static` str. pub fn module_path_static(&self) -> Option<&'static str> { - self.module_path.and_then(|s| s.get_static()) + self.metadata.module_path_static() } /// The source file containing the message. pub fn file(&self) -> Option<&'a str> { - self.file.map(|s| s.get()) + self.metadata.file() } /// The source file containing the message, if it is a `'static` str. pub fn file_static(&self) -> Option<&'static str> { - self.file.and_then(|s| s.get_static()) + self.metadata.file_static() } /// The filename of the source file. @@ -110,14 +271,14 @@ impl<'a> Record<'a> { /// This is typically set by the logging macro. If set, returns `Some(column)`; otherwise, /// returns `None`. pub fn line(&self) -> Option { - self.line + self.metadata.line() } /// The column number in the source file. /// /// This is typically not set. If set, returns `Some(column)`; otherwise, returns `None`. pub fn column(&self) -> Option { - self.column + self.metadata.column() } /// The message body. @@ -140,12 +301,7 @@ impl<'a> Record<'a> { RecordBuilder { record: Record { now: self.now, - level: self.level, - target: self.target, - module_path: self.module_path, - file: self.file, - line: self.line, - column: self.column, + metadata: self.metadata, payload: self.payload, kvs: self.kvs, }, @@ -156,12 +312,7 @@ impl<'a> Record<'a> { pub fn to_owned(&self) -> RecordOwned { RecordOwned { now: self.now, - level: self.level, - target: self.target.into_cow_static(), - module_path: self.module_path.map(|m| m.into_cow_static()), - file: self.file.map(|f| f.into_cow_static()), - line: self.line, - column: self.column, + metadata: self.metadata.to_owned(), payload: if let Some(s) = self.payload.as_str() { Cow::Borrowed(s) } else { @@ -192,12 +343,7 @@ impl Default for RecordBuilder<'_> { RecordBuilder { record: Record { now: SystemTime::now(), - level: Level::Info, - target: RefStr::Static(""), - module_path: None, - file: None, - line: None, - column: None, + metadata: MetadataBuilder::default().build(), payload: format_args!(""), kvs: KeyValues::empty(), }, @@ -206,6 +352,12 @@ impl Default for RecordBuilder<'_> { } impl<'a> RecordBuilder<'a> { + /// Set the record metadata. + pub fn metadata(mut self, metadata: Metadata<'a>) -> Self { + self.record.metadata = metadata; + self + } + /// Set [`payload`](Record::payload). pub fn payload(mut self, payload: fmt::Arguments<'a>) -> Self { self.record.payload = payload; @@ -214,55 +366,55 @@ impl<'a> RecordBuilder<'a> { /// Set [`level`](Record::level). pub fn level(mut self, level: Level) -> Self { - self.record.level = level; + self.record.metadata.level = level; self } /// Set [`target`](Record::target). pub fn target(mut self, target: &'a str) -> Self { - self.record.target = RefStr::Borrowed(target); + self.record.metadata.target = RefStr::Borrowed(target); self } /// Set [`target`](Record::target) to a `'static` string. pub fn target_static(mut self, target: &'static str) -> Self { - self.record.target = RefStr::Static(target); + self.record.metadata.target = RefStr::Static(target); self } /// Set [`module_path`](Record::module_path). pub fn module_path(mut self, path: Option<&'a str>) -> Self { - self.record.module_path = path.map(RefStr::Borrowed); + self.record.metadata.module_path = path.map(RefStr::Borrowed); self } /// Set [`module_path`](Record::module_path) to a `'static` string. pub fn module_path_static(mut self, path: &'static str) -> Self { - self.record.module_path = Some(RefStr::Static(path)); + self.record.metadata.module_path = Some(RefStr::Static(path)); self } /// Set [`file`](Record::file). pub fn file(mut self, file: Option<&'a str>) -> Self { - self.record.file = file.map(RefStr::Borrowed); + self.record.metadata.file = file.map(RefStr::Borrowed); self } /// Set [`file`](Record::file) to a `'static` string. pub fn file_static(mut self, file: &'static str) -> Self { - self.record.file = Some(RefStr::Static(file)); + self.record.metadata.file = Some(RefStr::Static(file)); self } /// Set [`line`](Record::line). pub fn line(mut self, line: Option) -> Self { - self.record.line = line; + self.record.metadata.line = line; self } /// Set [`column`](Record::column). pub fn column(mut self, column: Option) -> Self { - self.record.column = column; + self.record.metadata.column = column; self } @@ -285,12 +437,7 @@ pub struct RecordOwned { now: SystemTime, // the metadata - level: Level, - target: Cow<'static, str>, - module_path: Option>, - file: Option>, - line: Option, - column: Option, + metadata: MetadataOwned, // the payload payload: Cow<'static, str>, @@ -299,101 +446,49 @@ pub struct RecordOwned { kvs: Vec<(kv::KeyOwned, kv::ValueOwned)>, } -impl RecordOwned { - /// Execute the given function with the `Record`. - pub fn with(&self, f: impl FnOnce(Record<'_>)) { - f(Record { - now: self.now, +#[derive(Clone, Debug)] +struct MetadataOwned { + level: Level, + target: Cow<'static, str>, + module_path: Option>, + file: Option>, + line: Option, + column: Option, +} + +impl MetadataOwned { + fn as_metadata(&self) -> Metadata<'_> { + Metadata { level: self.level, target: match &self.target { - Cow::Borrowed(s) => RefStr::Static(s), - Cow::Owned(s) => RefStr::Borrowed(s.as_ref()), + Cow::Borrowed(target) => RefStr::Static(target), + Cow::Owned(target) => RefStr::Borrowed(target), }, module_path: match &self.module_path { - Some(Cow::Borrowed(s)) => Some(RefStr::Static(s)), - Some(Cow::Owned(s)) => Some(RefStr::Borrowed(s)), + Some(Cow::Borrowed(path)) => Some(RefStr::Static(path)), + Some(Cow::Owned(path)) => Some(RefStr::Borrowed(path)), None => None, }, file: match &self.file { - Some(Cow::Borrowed(s)) => Some(RefStr::Static(s)), - Some(Cow::Owned(s)) => Some(RefStr::Borrowed(s)), + Some(Cow::Borrowed(file)) => Some(RefStr::Static(file)), + Some(Cow::Owned(file)) => Some(RefStr::Borrowed(file)), None => None, }, line: self.line, column: self.column, - payload: format_args!("{}", self.payload), - kvs: KeyValues::from(self.kvs.as_slice()), - }); - } -} - -/// A minimal set of criteria for pre-filtering purposes. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] -pub struct FilterCriteria<'a> { - level: Level, - target: &'a str, -} - -impl<'a> FilterCriteria<'a> { - /// Get the [`level`](Record::level). - pub fn level(&self) -> Level { - self.level - } - - /// Get the [`target`](Record::target). - pub fn target(&self) -> &'a str { - self.target - } - - /// Create a builder initialized with the current criteria's values. - pub fn to_builder(&self) -> FilterCriteriaBuilder<'a> { - FilterCriteriaBuilder { - metadata: FilterCriteria { - level: self.level, - target: self.target, - }, - } - } - - /// Return a brand-new builder. - pub fn builder() -> FilterCriteriaBuilder<'a> { - FilterCriteriaBuilder::default() - } -} - -/// Builder for [`FilterCriteria`]. -#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] -pub struct FilterCriteriaBuilder<'a> { - metadata: FilterCriteria<'a>, -} - -impl Default for FilterCriteriaBuilder<'_> { - fn default() -> Self { - FilterCriteriaBuilder { - metadata: FilterCriteria { - level: Level::Info, - target: "", - }, } } } -impl<'a> FilterCriteriaBuilder<'a> { - /// Setter for [`level`](FilterCriteria::level). - pub fn level(mut self, arg: Level) -> Self { - self.metadata.level = arg; - self - } - - /// Setter for [`target`](FilterCriteria::target). - pub fn target(mut self, target: &'a str) -> Self { - self.metadata.target = target; - self - } - - /// Invoke the builder and return a `Metadata` - pub fn build(self) -> FilterCriteria<'a> { - self.metadata +impl RecordOwned { + /// Execute the given function with the `Record`. + pub fn with(&self, f: impl FnOnce(Record<'_>)) { + f(Record { + now: self.now, + metadata: self.metadata.as_metadata(), + payload: format_args!("{}", self.payload), + kvs: KeyValues::from(self.kvs.as_slice()), + }); } } diff --git a/core/tests/macros.rs b/core/tests/macros.rs index c5fe353..ab5663b 100644 --- a/core/tests/macros.rs +++ b/core/tests/macros.rs @@ -20,13 +20,16 @@ use std::sync::Mutex; use logforth_core::Append; use logforth_core::Diagnostic; use logforth_core::Error; +use logforth_core::Filter; use logforth_core::Logger; +use logforth_core::filter::FilterResult; use logforth_core::kv::KeyView; use logforth_core::kv::ToValue; use logforth_core::kv::Value; use logforth_core::kv::ValueView; use logforth_core::record::Level; use logforth_core::record::LevelFilter; +use logforth_core::record::Metadata; use logforth_core::record::Record; #[derive(Debug, PartialEq)] @@ -130,10 +133,40 @@ impl Append for Capture { } } +#[derive(Clone, Debug, Default)] +struct MetadataCapture { + metadata: Arc>>, +} + +#[derive(Debug, PartialEq)] +struct CapturedMetadata { + level: Level, + target: String, + module_path: Option, + file: Option, + line: Option, + column: Option, +} + +impl Filter for MetadataCapture { + fn enabled(&self, metadata: &Metadata, _: &[Box]) -> FilterResult { + self.metadata.lock().unwrap().push(CapturedMetadata { + level: metadata.level(), + target: metadata.target().to_owned(), + module_path: metadata.module_path().map(str::to_owned), + file: metadata.file().map(str::to_owned), + line: metadata.line(), + column: metadata.column(), + }); + FilterResult::Reject + } +} + fn make_logger(capture: Capture) -> Logger { logforth_core::builder() .dispatch(|dispatch| dispatch.append(capture)) .build() + .logger() } fn make_filtered_logger(capture: Capture) -> Logger { @@ -144,6 +177,7 @@ fn make_filtered_logger(capture: Capture) -> Logger { .append(capture) }) .build() + .logger() } #[test] @@ -246,10 +280,10 @@ fn captures_fine_grained_level_metadata_and_typed_fields() { #[test] fn named_logger_changes_target_without_hiding_source_module() { let capture = Capture::default(); - let logger = logforth_core::builder() - .name("metering") + let provider = logforth_core::builder() .dispatch(|dispatch| dispatch.append(capture.clone())) .build(); + let logger = provider.named_logger("metering"); logforth_core::info!(logger, metering_kind = "compute";); @@ -316,6 +350,53 @@ fn disabled_records_do_not_evaluate_payload_or_fields() { assert!(capture.take().is_empty()); } +#[test] +fn prefilter_receives_complete_source_metadata() { + let capture = Capture::default(); + let metadata_capture = MetadataCapture::default(); + let logger = logforth_core::builder() + .dispatch(|dispatch| { + dispatch + .filter(metadata_capture.clone()) + .append(capture.clone()) + }) + .build() + .logger(); + let expected_line = line!() + 1; + logforth_core::info!(logger, "filtered"); + + let metadata = metadata_capture.metadata.lock().unwrap(); + assert_eq!(metadata.len(), 1); + assert_eq!(metadata[0].level, Level::Info); + assert_eq!(metadata[0].target, "macros"); + assert_eq!(metadata[0].module_path.as_deref(), Some("macros")); + assert!( + std::path::Path::new(metadata[0].file.as_deref().unwrap()) + .ends_with(std::path::Path::new("tests").join("macros.rs")) + ); + assert_eq!(metadata[0].line, Some(expected_line)); + assert!(metadata[0].column.unwrap() > 0); + assert!(capture.take().is_empty()); +} + +#[test] +fn provider_creates_logger_handles_that_share_dispatches() { + let capture = Capture::default(); + let provider = logforth_core::builder() + .dispatch(|dispatch| dispatch.append(capture.clone())) + .build(); + let logger = provider.logger(); + let metering = provider.named_logger("metering"); + + logforth_core::info!(logger, "application"); + logforth_core::info!(metering, "usage"); + + let records = capture.take(); + assert_eq!(records.len(), 2); + assert_eq!(records[0].target, "macros"); + assert_eq!(records[1].target, "metering"); +} + #[test] fn macro_inputs_are_evaluated_once() { let capture = Capture::default(); diff --git a/core/tests/record.rs b/core/tests/record.rs new file mode 100644 index 0000000..485d361 --- /dev/null +++ b/core/tests/record.rs @@ -0,0 +1,58 @@ +// 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 logforth_core::record::Level; +use logforth_core::record::Metadata; +use logforth_core::record::Record; + +#[test] +fn record_preserves_supplied_metadata_across_owned_boundary() { + let owned = { + let target = String::from("service"); + let module_path = String::from("service::worker"); + let file = String::from("src/worker.rs"); + let metadata = Metadata::builder() + .level(Level::Warn2) + .target(&target) + .module_path(Some(&module_path)) + .file(Some(&file)) + .line(Some(42)) + .column(Some(7)) + .build(); + let record = Record::builder() + .metadata(metadata) + .payload(format_args!("retrying")) + .build(); + + assert_eq!(record.metadata(), &metadata); + assert_eq!(record.level(), Level::Warn2); + assert_eq!(record.target(), "service"); + assert_eq!(record.module_path(), Some("service::worker")); + assert_eq!(record.file(), Some("src/worker.rs")); + assert_eq!(record.line(), Some(42)); + assert_eq!(record.column(), Some(7)); + + record.to_owned() + }; + + owned.with(|record| { + assert_eq!(record.level(), Level::Warn2); + assert_eq!(record.target(), "service"); + assert_eq!(record.module_path(), Some("service::worker")); + assert_eq!(record.file(), Some("src/worker.rs")); + assert_eq!(record.line(), Some(42)); + assert_eq!(record.column(), Some(7)); + assert_eq!(record.payload().to_string(), "retrying"); + }); +} diff --git a/examples/src/asynchronous.rs b/examples/src/asynchronous.rs index e33ff01..0bf0012 100644 --- a/examples/src/asynchronous.rs +++ b/examples/src/asynchronous.rs @@ -29,7 +29,8 @@ fn main() { let logger = logforth::core::builder() .dispatch(|d| d.filter(LevelFilter::All).append(asynchronous)) - .build(); + .build() + .logger(); log::set_boxed_logger(Box::new(LogBridge::new(logger))).unwrap(); log::set_max_level(log::LevelFilter::Trace); diff --git a/examples/src/custom_layout_filter.rs b/examples/src/custom_layout_filter.rs index e6e92dc..db31af3 100644 --- a/examples/src/custom_layout_filter.rs +++ b/examples/src/custom_layout_filter.rs @@ -18,16 +18,16 @@ use logforth::Filter; use logforth::Layout; use logforth::append; use logforth::filter::FilterResult; -use logforth::record::FilterCriteria; use logforth::record::Level; +use logforth::record::Metadata; use logforth::record::Record; #[derive(Debug)] struct CustomFilter; impl Filter for CustomFilter { - fn enabled(&self, criteria: &FilterCriteria, _: &[Box]) -> FilterResult { - if criteria.level() < Level::Info { + fn enabled(&self, metadata: &Metadata, _: &[Box]) -> FilterResult { + if metadata.level() < Level::Info { FilterResult::Accept } else { FilterResult::Reject diff --git a/examples/src/log_with_logger.rs b/examples/src/log_with_logger.rs index 0cf8832..e0aab01 100644 --- a/examples/src/log_with_logger.rs +++ b/examples/src/log_with_logger.rs @@ -20,7 +20,8 @@ fn main() { let l = logforth::core::builder() .dispatch(|d| d.append(append::Stdout::default())) - .build(); + .build() + .logger(); let l = LogBridge::new(l); log::error!(logger: l, "Hello error!"); diff --git a/filters/rustlog/README.md b/filters/rustlog/README.md index 209494a..2513c54 100644 --- a/filters/rustlog/README.md +++ b/filters/rustlog/README.md @@ -3,6 +3,6 @@ This filter is derived by [env_filter](https://crates.io/crates/env_filter), with significant modifications to suit our needs: 1. Logforth needs not the original `FilterLog` struct. -2. Logforth would use its own `Level`, `LevelFilter`, `FilterCriteria`, and `Record` types. +2. Logforth would use its own `Level`, `LevelFilter`, `Metadata`, and `Record` types. 3. The regex based global filter is discarded. Filtering by targets should be sufficient. 4. Interfaces and methods are refactored to be more ergonomic. diff --git a/filters/rustlog/src/lib.rs b/filters/rustlog/src/lib.rs index bddda05..b784a54 100644 --- a/filters/rustlog/src/lib.rs +++ b/filters/rustlog/src/lib.rs @@ -29,9 +29,10 @@ //! ``` //! //! For Logforth's native macros, `target` is the call-site module path when the logger is unnamed, -//! or the stable logger name configured with [`LoggerBuilder::name`]. Records forwarded from the -//! `log` facade retain that facade's target. This lets existing target directives keep working -//! during incremental migration while new code avoids repeating a target at every call site. +//! or the stable logger name configured with [`LoggerProvider::named_logger`]. Records forwarded +//! from the `log` facade retain that facade's target. This lets existing target directives keep +//! working during incremental migration while new code avoids repeating a target at every call +//! site. //! //! The path to the module is rooted in the name of the crate it was compiled for. Thus, if your //! program is contained in a file `hello.rs`, for example, to turn on logging for this file you @@ -73,7 +74,7 @@ //! * `off` turns off all logging for the application //! * `OFF` turns off all logging for the application (same as previous) //! -//! [`LoggerBuilder::name`]: logforth_core::LoggerBuilder::name +//! [`LoggerProvider::named_logger`]: logforth_core::LoggerProvider::named_logger #![cfg_attr(docsrs, feature(doc_cfg))] #![deny(missing_docs)] @@ -85,9 +86,9 @@ use logforth_core::Diagnostic; use logforth_core::Error; use logforth_core::Filter; use logforth_core::filter::FilterResult; -use logforth_core::record::FilterCriteria; use logforth_core::record::Level; use logforth_core::record::LevelFilter; +use logforth_core::record::Metadata; #[cfg(test)] mod tests; @@ -120,9 +121,9 @@ impl RustLogFilter { } impl Filter for RustLogFilter { - fn enabled(&self, criteria: &FilterCriteria, _: &[Box]) -> FilterResult { - let level = criteria.level(); - let target = criteria.target(); + fn enabled(&self, metadata: &Metadata, _: &[Box]) -> FilterResult { + let level = metadata.level(); + let target = metadata.target(); // search for the longest match, the vector is assumed to be pre-sorted for directive in self.directives.iter().rev() { diff --git a/filters/rustlog/src/tests.rs b/filters/rustlog/src/tests.rs index 62d6c98..eff063e 100644 --- a/filters/rustlog/src/tests.rs +++ b/filters/rustlog/src/tests.rs @@ -22,9 +22,9 @@ use logforth_core::Diagnostic; use logforth_core::Error; use logforth_core::Filter; use logforth_core::filter::FilterResult; -use logforth_core::record::FilterCriteria; use logforth_core::record::Level; use logforth_core::record::LevelFilter; +use logforth_core::record::Metadata; use logforth_core::record::Record; use crate::Directive; @@ -35,12 +35,9 @@ use crate::parse_spec; impl RustLogFilter { fn rejected(&self, level: Level, target: &str) -> bool { - let criteria = FilterCriteria::builder() - .level(level) - .target(target) - .build(); + let metadata = Metadata::builder().level(level).target(target).build(); - matches!(Filter::enabled(self, &criteria, &[]), FilterResult::Reject) + matches!(Filter::enabled(self, &metadata, &[]), FilterResult::Reject) } } @@ -61,14 +58,14 @@ impl Append for CountAppend { #[test] fn named_native_logger_matches_target_directive() { let count = Arc::new(AtomicUsize::new(0)); - let logger = logforth_core::builder() - .name("metering") + let provider = logforth_core::builder() .dispatch(|dispatch| { dispatch .filter(RustLogFilterBuilder::from_spec("off,metering=info").build()) .append(CountAppend(Arc::clone(&count))) }) .build(); + let logger = provider.named_logger("metering"); logforth_core::debug!(logger, "disabled by the metering directive"); logforth_core::info!(logger, "accepted by the metering directive"); diff --git a/logforth/src/lib.rs b/logforth/src/lib.rs index 1261c1e..2db34a9 100644 --- a/logforth/src/lib.rs +++ b/logforth/src/lib.rs @@ -67,19 +67,21 @@ //! use logforth::append; //! use logforth::record::Level; //! -//! let logger = logforth::core::builder() +//! let provider = logforth::core::builder() //! .dispatch(|d| d.append(append::Stdout::default())) //! .build(); +//! let logger = provider.logger(); //! //! logforth::info!(logger, request_id = 42_u64; "request accepted"); //! logforth::log!(logger, Level::Info2, "request details"); //! ``` //! -//! Dedicated channels can use a named logger. Its name becomes the native record target while the -//! call-site module remains available as source metadata: +//! A logger can carry a stable name. Its name becomes the native record target while the call-site +//! module remains available as source metadata: //! //! ``` -//! let metering = logforth::core::builder().name("metering").build(); +//! let provider = logforth::core::builder().build(); +//! let metering = provider.named_logger("metering"); //! logforth::info!(metering, tenant_id = "acme", compute_time_ms = 42_u64;); //! ``` //! @@ -146,7 +148,8 @@ pub mod core { // structs pub use logforth_core::DispatchBuilder; pub use logforth_core::Logger; - pub use logforth_core::LoggerBuilder; + pub use logforth_core::LoggerProvider; + pub use logforth_core::LoggerProviderBuilder; // methods pub use logforth_core::builder; } diff --git a/logforth/src/starter_log.rs b/logforth/src/starter_log.rs index b45455b..a657d6b 100644 --- a/logforth/src/starter_log.rs +++ b/logforth/src/starter_log.rs @@ -15,7 +15,7 @@ //! Starter configurations for quickly setting up logforth with the `log` crate use logforth_bridge_log::LogBridge; -use logforth_core::Logger; +use logforth_core::LoggerProvider; use crate::Append; use crate::Error; @@ -23,12 +23,12 @@ use crate::Filter; use crate::Layout; use crate::append; use crate::core::DispatchBuilder; -use crate::core::LoggerBuilder; +use crate::core::LoggerProviderBuilder; use crate::filter::rustlog::RustLogFilterBuilder; /// A builder for setting up logforth with the `log` crate. pub struct LogStarterBuilder { - builder: LoggerBuilder, + builder: LoggerProviderBuilder, } /// Create a new empty [`LogStarterBuilder`] instance for configuring logforth setups. @@ -89,7 +89,7 @@ impl LogStarterBuilder { pub fn try_apply(self) -> Result<(), Error> { let make_error = |_| Error::new("logging system has already been setup"); - let logger = Box::new(LogBridge::new(self.build())); + let logger = Box::new(LogBridge::new(self.build().logger())); log::set_boxed_logger(logger).map_err(make_error)?; log::set_max_level(log::LevelFilter::Trace); @@ -121,7 +121,7 @@ impl LogStarterBuilder { .expect("LogStarterBuilder::apply must be called before the global logger initialized"); } - /// Build the configured [`Logger`]. + /// Build the configured [`LoggerProvider`]. /// /// This is useful for advanced use cases where you want to intercept extra configs before /// setting the logger as the global logger. @@ -133,14 +133,14 @@ impl LogStarterBuilder { /// /// use logforth::bridge::log::LogBridge; /// - /// let logger = logforth::starter_log::builder().build(); - /// let logger = Arc::new(LogBridge::new(logger)); + /// let provider = logforth::starter_log::builder().build(); + /// let logger = Arc::new(LogBridge::new(provider.logger())); /// log::set_boxed_logger(Box::new(logger.clone())).unwrap(); /// log::set_max_level(log::LevelFilter::Trace); /// /// logger.flush(); /// ``` - pub fn build(self) -> Logger { + pub fn build(self) -> LoggerProvider { self.builder.build() } } @@ -251,7 +251,7 @@ impl LogStarterTestingBuilder { ); } - /// Build the configured [`Logger`]. + /// Build the configured [`LoggerProvider`]. /// /// This is useful for advanced use cases where you want to intercept extra configs before /// setting the logger as the global logger. @@ -263,14 +263,14 @@ impl LogStarterTestingBuilder { /// /// use logforth::bridge::log::LogBridge; /// - /// let logger = logforth::starter_log::testing().build(); - /// let logger = Arc::new(LogBridge::new(logger)); + /// let provider = logforth::starter_log::testing().build(); + /// let logger = Arc::new(LogBridge::new(provider.logger())); /// log::set_boxed_logger(Box::new(logger.clone())).unwrap(); /// log::set_max_level(log::LevelFilter::Trace); /// /// logger.flush(); /// ``` - pub fn build(self) -> Logger { + pub fn build(self) -> LoggerProvider { self.into_builder().build() } @@ -411,7 +411,7 @@ impl LogStarterStdStreamBuilder { ); } - /// Build the configured [`Logger`]. + /// Build the configured [`LoggerProvider`]. /// /// This is useful for advanced use cases where you want to intercept extra configs before /// setting the logger as the global logger. @@ -423,14 +423,14 @@ impl LogStarterStdStreamBuilder { /// /// use logforth::bridge::log::LogBridge; /// - /// let logger = logforth::starter_log::stdout().build(); - /// let logger = Arc::new(LogBridge::new(logger)); + /// let provider = logforth::starter_log::stdout().build(); + /// let logger = Arc::new(LogBridge::new(provider.logger())); /// log::set_boxed_logger(Box::new(logger.clone())).unwrap(); /// log::set_max_level(log::LevelFilter::Trace); /// /// logger.flush(); /// ``` - pub fn build(self) -> Logger { + pub fn build(self) -> LoggerProvider { self.into_builder().build() } diff --git a/logforth/tests/native_macros.rs b/logforth/tests/native_macros.rs index 50871d7..16f4e46 100644 --- a/logforth/tests/native_macros.rs +++ b/logforth/tests/native_macros.rs @@ -16,7 +16,7 @@ use logforth::record::Level; #[test] fn native_macros_are_reexported() { - let logger = logforth::core::builder().build(); + let logger = logforth::core::builder().build().logger(); logforth::info!(logger, answer = 42_u64; "hello"); logforth::log!(logger, Level::Info2, "fine-grained");