diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1fbc994..8f5a325 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -71,11 +71,11 @@ jobs: run: cargo test --all-features -- --nocapture - name: Run examples run: | - cargo run --example text_stdio + cargo run --example simple_stdout + cargo run --example multiple_dispatches cargo run --example custom_layout_filter - cargo run --example env_filter - cargo run --features="no-color" --example text_stdio - cargo run --features="json" --example json_stdio + cargo run --features="no-color" --example simple_stdout + cargo run --features="json" --example json_stdout cargo run --features="json,rolling_file" --example rolling_file required: diff --git a/Cargo.toml b/Cargo.toml index 75a9fb6..2fe4fbb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,11 +48,11 @@ colored = { version = "2.1" } env_filter = { version = "0.1" } jiff = { version = "0.1.13" } log = { version = "0.4", features = ["std", "kv_unstable"] } -paste = { version = "1.0" } [dev-dependencies] rand = "0.8" tempfile = "3.13" +tokio = { version = "1", features = ["rt-multi-thread"] } ## Serde dependencies [dependencies.serde] @@ -96,13 +96,16 @@ version = "0.26" ## Examples [[example]] -name = "text_stdio" -path = "examples/text_stdio.rs" +name = "simple_stdout" +path = "examples/simple_stdout.rs" [[example]] -name = "json_stdio" -path = "examples/json_stdio.rs" -required-features = ["json"] +name = "json_stdout" +path = "examples/json_stdout.rs" + +[[example]] +name = "multiple_dispatches" +path = "examples/multiple_dispatches.rs" [[example]] name = "rolling_file" @@ -112,7 +115,3 @@ required-features = ["rolling_file", "json"] [[example]] name = "custom_layout_filter" path = "examples/custom_layout_filter.rs" - -[[example]] -name = "env_filter" -path = "examples/env_filter.rs" diff --git a/README.md b/README.md index 08358c8..666c897 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Logforth Project +# Logforth [![Crates.io][crates-badge]][crates-url] [![Documentation][docs-badge]][docs-url] @@ -16,40 +16,58 @@ [actions-badge]: https://github.com/fast/logforth/workflows/CI/badge.svg [actions-url]:https://github.com/fast/logforth/actions?query=workflow%3ACI -## Overview +Logforth is a flexible and easy-to-use logging framework for Rust applications. It allows you to configure multiple dispatches, filters, and appenders to customize your logging setup according to your needs. -A versatile and extensible logging implementation. +## Features -## Usage +- **Multiple Dispatches**: Configure different logging behaviors for different parts of your application. +- **Flexible Filters**: Use built-in or custom filters to control which log records are processed. +- **Various Appenders**: Output logs to stdout, stderr, files, or even send them to OpenTelemetry collectors. +- **Custom Layouts**: Format log records using predefined layouts or create your own. -Add the dependencies to your `Cargo.toml` with: +## Getting Started + +Add `log` and `logforth` to your `Cargo.toml`: ```shell cargo add log cargo add logforth ``` -... where [log](https://crates.io/crates/log) is the logging facade and [logforth](https://crates.io/crates/logforth) is the logging implementation. +## Simple Usage -Then, you can use the logger with the simplest default setup: +Set up a basic logger that outputs to stdout: ```rust fn main() { - logforth::stderr().apply(); + logforth::stdout().apply(); + + log::info!("This is an info message."); + log::debug!("This debug message will not be printed by default."); } ``` -Or configure the logger in a more fine-grained way: +## Advanced Usage + +Configure multiple dispatches with different filters and appenders: ```rust -use log::LevelFilter; use logforth::append; +use log::LevelFilter; fn main() { logforth::builder() - .dispatch(|d| d.filter(LevelFilter::Debug).append(append::Stderr::default())) - .dispatch(|d| d.filter(LevelFilter::Info).append(append::Stdout::default())) + .dispatch(|d| d + .filter(LevelFilter::Error) + .append(append::Stderr::default())) + .dispatch(|d| d + .filter(LevelFilter::Info) + .append(append::Stdout::default())) .apply(); + + log::error!("This error will be logged to stderr."); + log::info!("This info will be logged to stdout."); + log::debug!("This debug message will not be logged."); } ``` diff --git a/examples/custom_layout_filter.rs b/examples/custom_layout_filter.rs index 048906f..00be13b 100644 --- a/examples/custom_layout_filter.rs +++ b/examples/custom_layout_filter.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use log::LevelFilter; use logforth::append; use logforth::filter::CustomFilter; use logforth::filter::FilterResult; @@ -21,8 +20,8 @@ use logforth::layout::CustomLayout; fn main() { logforth::builder() .dispatch(|d| { - d.filter(CustomFilter::new(|metadata: &log::Metadata| { - if metadata.level() > LevelFilter::Info { + d.filter(CustomFilter::new(|metadata| { + if metadata.level() < log::Level::Info { FilterResult::Accept } else { FilterResult::Reject @@ -30,7 +29,7 @@ fn main() { })) .append( append::Stdout::default().with_layout(CustomLayout::new(|record| { - Ok(format!("[system alert] {}", record.args()).into_bytes()) + Ok(format!("[Alert] {}", record.args()).into_bytes()) })), ) }) diff --git a/examples/json_stdio.rs b/examples/json_stdout.rs similarity index 83% rename from examples/json_stdio.rs rename to examples/json_stdout.rs index 491bac3..4ef7b54 100644 --- a/examples/json_stdio.rs +++ b/examples/json_stdout.rs @@ -20,9 +20,6 @@ fn main() { .dispatch(|d| d.append(append::Stdout::default().with_layout(JsonLayout::default()))) .apply(); - log::error!("Hello error!"); - log::warn!("Hello warn!"); - log::info!("Hello info!"); - log::debug!("Hello debug!"); - log::trace!("Hello trace!"); + log::info!("This is an info message."); + log::debug!("This debug message will not be printed by default."); } diff --git a/examples/text_stdio.rs b/examples/multiple_dispatches.rs similarity index 89% rename from examples/text_stdio.rs rename to examples/multiple_dispatches.rs index a44afd9..1b35b9a 100644 --- a/examples/text_stdio.rs +++ b/examples/multiple_dispatches.rs @@ -18,10 +18,13 @@ use logforth::append; fn main() { logforth::builder() .dispatch(|d| { - d.filter(LevelFilter::Trace) - .append(append::Stdout::default()) + d.filter(LevelFilter::Error) .append(append::Stderr::default()) }) + .dispatch(|d| { + d.filter(LevelFilter::Info) + .append(append::Stdout::default()) + }) .apply(); log::error!("Hello error!"); diff --git a/examples/rolling_file.rs b/examples/rolling_file.rs index 735490a..98638d5 100644 --- a/examples/rolling_file.rs +++ b/examples/rolling_file.rs @@ -16,26 +16,22 @@ use logforth::append::rolling_file::NonBlockingBuilder; use logforth::append::rolling_file::RollingFile; use logforth::append::rolling_file::RollingFileWriter; use logforth::append::rolling_file::Rotation; -use logforth::append::Stdout; use logforth::layout::JsonLayout; fn main() { - let rolling = RollingFileWriter::builder() - .rotation(Rotation::Minutely) - .filename_prefix("example") - .filename_suffix("log") - .max_log_files(10) - .max_file_size(1024 * 1024) + let rolling_writer = RollingFileWriter::builder() + .rotation(Rotation::Daily) + .filename_prefix("app_log") .build("logs") .unwrap(); - let (writer, _guard) = NonBlockingBuilder::default().finish(rolling); + + let (non_blocking, _guard) = NonBlockingBuilder::default().finish(rolling_writer); logforth::builder() .dispatch(|d| { - d.filter("trace") - .append(RollingFile::new(writer).with_layout(JsonLayout::default())) + d.filter(log::LevelFilter::Trace) + .append(RollingFile::new(non_blocking).with_layout(JsonLayout::default())) }) - .dispatch(|d| d.filter("info").append(Stdout::default())) .apply(); let repeat = 1; diff --git a/examples/env_filter.rs b/examples/simple_stdout.rs similarity index 100% rename from examples/env_filter.rs rename to examples/simple_stdout.rs diff --git a/src/append/fastrace.rs b/src/append/fastrace.rs index d90a0f3..34469af 100644 --- a/src/append/fastrace.rs +++ b/src/append/fastrace.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +//! Appender for integrating with [fastrace](https://crates.io/crates/fastrace). + use std::borrow::Cow; use jiff::Zoned; @@ -21,6 +23,14 @@ use crate::append::Append; use crate::layout::collect_kvs; /// An appender that adds log records to fastrace as an event associated to the current span. +/// +/// # Examples +/// +/// ``` +/// use logforth::append::FastraceEvent; +/// +/// let fastrace_appender = FastraceEvent::default(); +/// ``` #[derive(Default, Debug, Clone)] pub struct FastraceEvent { _private: (), // suppress structure literal syntax diff --git a/src/append/mod.rs b/src/append/mod.rs index 7375a73..7a4b3c8 100644 --- a/src/append/mod.rs +++ b/src/append/mod.rs @@ -12,10 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Dispatch log records to the appropriate target. +//! Dispatch log records to various targets. use std::fmt; +#[cfg(feature = "fastrace")] +mod fastrace; +#[cfg(feature = "opentelemetry")] +pub mod opentelemetry; +#[cfg(feature = "rolling_file")] +pub mod rolling_file; +mod stdio; + #[cfg(feature = "fastrace")] pub use self::fastrace::FastraceEvent; #[cfg(feature = "opentelemetry")] @@ -25,14 +33,9 @@ pub use self::rolling_file::RollingFile; pub use self::stdio::Stderr; pub use self::stdio::Stdout; -#[cfg(feature = "fastrace")] -mod fastrace; -#[cfg(feature = "opentelemetry")] -pub mod opentelemetry; -#[cfg(feature = "rolling_file")] -pub mod rolling_file; -mod stdio; - +/// A trait representing an appender that can process log records. +/// +/// Implementors of this trait can handle log records in custom ways. pub trait Append: fmt::Debug + Send + Sync + 'static { /// Dispatches a log record to the append target. fn append(&self, record: &log::Record) -> anyhow::Result<()>; diff --git a/src/append/opentelemetry.rs b/src/append/opentelemetry.rs index 4056881..46952f0 100644 --- a/src/append/opentelemetry.rs +++ b/src/append/opentelemetry.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +//! Appenders and utilities for integrating with OpenTelemetry. + use std::borrow::Cow; use std::sync::Arc; use std::time::Duration; @@ -32,7 +34,7 @@ use opentelemetry_sdk::logs::LoggerProvider; use crate::append::Append; use crate::Layout; -/// The communication protocol to opentelemetry that used when exporting data. +/// Specifies the wire protocol to use when sending logs to OpenTelemetry. /// /// This is a logical re-exported [`Protocol`] to avoid version lock-in to /// `opentelemetry_otlp`. @@ -57,7 +59,15 @@ pub struct OpentelemetryLogBuilder { } impl OpentelemetryLogBuilder { - /// Create a new builder with the given name and OTLP endpoint. + /// Creates a new [`OpentelemetryLogBuilder`]. + /// + /// # Examples + /// + /// ``` + /// use logforth::append::opentelemetry::OpentelemetryLogBuilder; + /// + /// let builder = OpentelemetryLogBuilder::new("my_service", "http://localhost:4317"); + /// ``` pub fn new(name: impl Into, otlp_endpoint: impl Into) -> Self { OpentelemetryLogBuilder { name: name.into(), @@ -68,11 +78,17 @@ impl OpentelemetryLogBuilder { } } - /// Set the protocol to use when exporting data to opentelemetry. + /// Sets the wire protocol to use. + /// + /// # Examples /// - /// Default to [`Grpc`]. + /// ``` + /// use logforth::append::opentelemetry::OpentelemetryLogBuilder; + /// use logforth::append::opentelemetry::OpentelemetryWireProtocol; /// - /// [`Grpc`]: OpentelemetryWireProtocol::Grpc + /// let builder = OpentelemetryLogBuilder::new("my_service", "http://localhost:4317"); + /// builder.protocol(OpentelemetryWireProtocol::HttpJson); + /// ``` pub fn protocol(mut self, protocol: OpentelemetryWireProtocol) -> Self { self.protocol = match protocol { OpentelemetryWireProtocol::Grpc => Protocol::Grpc, @@ -82,7 +98,16 @@ impl OpentelemetryLogBuilder { self } - /// Append a label to the resource. + /// Adds a label to the logs. + /// + /// # Examples + /// + /// ``` + /// use logforth::append::opentelemetry::OpentelemetryLogBuilder; + /// + /// let builder = OpentelemetryLogBuilder::new("my_service", "http://localhost:4317"); + /// builder.label("env", "production"); + /// ``` pub fn label( mut self, key: impl Into>, @@ -92,7 +117,16 @@ impl OpentelemetryLogBuilder { self } - /// Append multiple labels to the resource. + /// Adds multiple labels to the logs. + /// + /// # Examples + /// + /// ``` + /// use logforth::append::opentelemetry::OpentelemetryLogBuilder; + /// + /// let builder = OpentelemetryLogBuilder::new("my_service", "http://localhost:4317"); + /// builder.labels(vec![("env", "production"), ("version", "1.0")]); + /// ``` pub fn labels(mut self, labels: impl IntoIterator) -> Self where K: Into>, @@ -103,13 +137,34 @@ impl OpentelemetryLogBuilder { self } - /// Set the layout to use when formatting log records. + /// Sets the layout for the logs. + /// + /// # Examples + /// + /// ``` + /// use logforth::append::opentelemetry::OpentelemetryLogBuilder; + /// use logforth::layout::JsonLayout; + /// + /// let builder = OpentelemetryLogBuilder::new("my_service", "http://localhost:4317"); + /// builder.layout(JsonLayout::default()); + /// ``` pub fn layout(mut self, layout: impl Into) -> Self { self.layout = Some(layout.into()); self } - /// Build the [`OpentelemetryLog`] appender. + /// Builds the [`OpentelemetryLog`] appender. + /// + /// # Examples + /// + /// ``` + /// use logforth::append::opentelemetry::OpentelemetryLogBuilder; + /// + /// let builder = OpentelemetryLogBuilder::new("my_service", "http://localhost:4317"); + /// let otlp_appender = tokio::runtime::Runtime::new() + /// .unwrap() + /// .block_on(async { builder.build().unwrap() }); + /// ``` pub fn build(self) -> Result { let OpentelemetryLogBuilder { name, @@ -157,7 +212,21 @@ impl OpentelemetryLogBuilder { } } -/// An appender that sends log records to opentelemetry. +/// An appender that sends log records to OpenTelemetry. +/// +/// # Examples +/// +/// ``` +/// use logforth::append::opentelemetry::OpentelemetryLogBuilder; +/// use logforth::append::opentelemetry::OpentelemetryWireProtocol; +/// +/// let otlp_appender = tokio::runtime::Runtime::new().unwrap().block_on(async { +/// OpentelemetryLogBuilder::new("service_name", "http://localhost:4317") +/// .protocol(OpentelemetryWireProtocol::Grpc) +/// .build() +/// .unwrap(); +/// }); +/// ``` #[derive(Debug)] pub struct OpentelemetryLog { name: String, diff --git a/src/append/rolling_file/append.rs b/src/append/rolling_file/append.rs index 27c763e..a289269 100644 --- a/src/append/rolling_file/append.rs +++ b/src/append/rolling_file/append.rs @@ -19,8 +19,7 @@ use crate::append::Append; use crate::layout::TextLayout; use crate::Layout; -/// An appender that writes log records to a file that rolls over when it reaches a certain date -/// time. +/// An appender that writes log records to rolling files. #[derive(Debug)] pub struct RollingFile { layout: Layout, @@ -28,9 +27,9 @@ pub struct RollingFile { } impl RollingFile { - /// Creates a new `RollingFile` appender that writes log records to the given writer. + /// Creates a new [`RollingFile`] appender. /// - /// This appender by default uses [`TextLayout`] to format log records as bytes. + /// This appender by default uses [`TextLayout`] to format log records. pub fn new(writer: NonBlocking) -> Self { Self { layout: TextLayout::default().no_color().into(), @@ -38,7 +37,7 @@ impl RollingFile { } } - /// Sets the layout used to format log records as bytes. + /// Sets the layout used to format log records. pub fn with_layout(mut self, layout: impl Into) -> Self { self.layout = layout.into(); self diff --git a/src/append/rolling_file/mod.rs b/src/append/rolling_file/mod.rs index bec33b2..1c6eadf 100644 --- a/src/append/rolling_file/mod.rs +++ b/src/append/rolling_file/mod.rs @@ -12,6 +12,35 @@ // See the License for the specific language governing permissions and // limitations under the License. +//! Appender for writing log records to rolling files. +//! +//! # Example +//! +//!``` +//! use logforth::append::rolling_file::NonBlockingBuilder; +//! use logforth::append::rolling_file::RollingFile; +//! use logforth::append::rolling_file::RollingFileWriter; +//! use logforth::append::rolling_file::Rotation; +//! use logforth::layout::JsonLayout; +//! +//! let rolling_writer = RollingFileWriter::builder() +//! .rotation(Rotation::Daily) +//! .filename_prefix("app_log") +//! .build("logs") +//! .unwrap(); +//! +//! let (non_blocking, _guard) = NonBlockingBuilder::default().finish(rolling_writer); +//! +//! logforth::builder() +//! .dispatch(|d| { +//! d.filter(log::LevelFilter::Trace) +//! .append(RollingFile::new(non_blocking).with_layout(JsonLayout::default())) +//! }) +//! .apply(); +//! +//! log::info!("This log will be written to a rolling file."); +//! ``` + pub use append::RollingFile; pub use non_blocking::NonBlocking; pub use non_blocking::NonBlockingBuilder; diff --git a/src/append/rolling_file/non_blocking.rs b/src/append/rolling_file/non_blocking.rs index fc5cf95..845702e 100644 --- a/src/append/rolling_file/non_blocking.rs +++ b/src/append/rolling_file/non_blocking.rs @@ -25,7 +25,7 @@ use crossbeam_channel::Sender; use crate::append::rolling_file::worker::Worker; use crate::append::rolling_file::Message; -/// A guard that flushes log records associated to a [`NonBlocking`] on a drop. +/// A guard that flushes log records associated with a [`NonBlocking`] writer on drop. /// /// Writing to a [`NonBlocking`] writer will **not** immediately write the log record to the /// underlying output. Instead, the log record will be written by a dedicated logging thread at @@ -85,7 +85,7 @@ impl Drop for WorkerGuard { } } -/// A non-blocking, off-thread writer. +/// A non-blocking writer for rolling files. #[derive(Clone, Debug)] pub struct NonBlocking { sender: Sender, @@ -123,7 +123,7 @@ impl NonBlocking { } } -/// A builder for [`NonBlocking`]. +/// A builder for configuring [`NonBlocking`]. #[derive(Debug)] pub struct NonBlockingBuilder { thread_name: String, diff --git a/src/append/rolling_file/rolling.rs b/src/append/rolling_file/rolling.rs index f4181ce..203129b 100644 --- a/src/append/rolling_file/rolling.rs +++ b/src/append/rolling_file/rolling.rs @@ -27,7 +27,7 @@ use parking_lot::RwLock; use crate::append::rolling_file::clock::Clock; use crate::append::rolling_file::Rotation; -/// A file writer with the ability to rotate log files at a fixed schedule. +/// A writer for rolling files. #[derive(Debug)] pub struct RollingFileWriter { state: State, @@ -35,6 +35,15 @@ pub struct RollingFileWriter { } impl RollingFileWriter { + /// Creates a new [`RollingFileWriterBuilder`]. + /// + /// # Examples + /// + /// ``` + /// use logforth::append::rolling_file::RollingFileWriter; + /// + /// let builder = RollingFileWriter::builder(); + /// ``` #[must_use] pub fn builder() -> RollingFileWriterBuilder { RollingFileWriterBuilder::new() @@ -65,7 +74,7 @@ impl Write for RollingFileWriter { } } -/// A builder for [`RollingFileWriter`]. +/// A builder for configuring [`RollingFileWriter`]. #[derive(Debug)] pub struct RollingFileWriterBuilder { rotation: Rotation, @@ -83,6 +92,7 @@ impl Default for RollingFileWriterBuilder { } impl RollingFileWriterBuilder { + /// Creates a new [`RollingFileWriterBuilder`]. #[must_use] pub const fn new() -> Self { Self { @@ -95,12 +105,14 @@ impl RollingFileWriterBuilder { } } + /// Sets the rotation policy. #[must_use] pub fn rotation(mut self, rotation: Rotation) -> Self { self.rotation = rotation; self } + /// Sets the filename prefix. #[must_use] pub fn filename_prefix(mut self, prefix: impl Into) -> Self { let prefix = prefix.into(); @@ -112,6 +124,7 @@ impl RollingFileWriterBuilder { self } + /// Sets the filename suffix. #[must_use] pub fn filename_suffix(mut self, suffix: impl Into) -> Self { let suffix = suffix.into(); @@ -123,6 +136,7 @@ impl RollingFileWriterBuilder { self } + /// Sets the maximum number of log files to keep. #[must_use] pub fn max_log_files(mut self, n: usize) -> Self { self.max_files = Some(n); @@ -142,6 +156,7 @@ impl RollingFileWriterBuilder { self } + /// Builds the [`RollingFileWriter`]. pub fn build(self, dir: impl AsRef) -> anyhow::Result { let Self { rotation, diff --git a/src/append/rolling_file/rotation.rs b/src/append/rolling_file/rotation.rs index 117ee22..bdff45c 100644 --- a/src/append/rolling_file/rotation.rs +++ b/src/append/rolling_file/rotation.rs @@ -18,16 +18,16 @@ use jiff::Unit; use jiff::Zoned; use jiff::ZonedRound; -/// Defines a fixed period for rolling of a log file. +/// Rotation policies for rolling files. #[derive(Clone, Eq, PartialEq, Debug)] pub enum Rotation { - /// Minutely Rotation + /// Rotate files every minute. Minutely, - /// Hourly Rotation + /// Rotate files every hour. Hourly, - /// Daily Rotation + /// Rotate files every day. Daily, - /// No Time Rotation + /// Never rotate files. Never, } diff --git a/src/append/stdio.rs b/src/append/stdio.rs index 79474d4..8b2a54f 100644 --- a/src/append/stdio.rs +++ b/src/append/stdio.rs @@ -18,7 +18,15 @@ use crate::append::Append; use crate::layout::TextLayout; use crate::Layout; -/// An appender that prints log records to stdout. +/// An appender that writes log records to standard output. +/// +/// # Examples +/// +/// ``` +/// use logforth::append::Stdout; +/// +/// let stdout_appender = Stdout::default(); +/// ``` #[derive(Debug)] pub struct Stdout { layout: Layout, @@ -33,7 +41,16 @@ impl Default for Stdout { } impl Stdout { - /// Creates a new `Stdout` appender with the given layout. + /// Sets the layout for the [`Stdout`] appender. + /// + /// # Examples + /// + /// ``` + /// use logforth::append::Stdout; + /// use logforth::layout::TextLayout; + /// + /// let stdout_appender = Stdout::default().with_layout(TextLayout::default()); + /// ``` pub fn with_layout(mut self, layout: impl Into) -> Self { self.layout = layout.into(); self @@ -53,7 +70,15 @@ impl Append for Stdout { } } -/// An appender that prints log records to stderr. +/// An appender that writes log records to standard error. +/// +/// # Examples +/// +/// ``` +/// use logforth::append::Stderr; +/// +/// let stderr_appender = Stderr::default(); +/// ``` #[derive(Debug)] pub struct Stderr { layout: Layout, @@ -68,7 +93,16 @@ impl Default for Stderr { } impl Stderr { - /// Creates a new `Stderr` appender with the given layout. + /// Sets the layout for the [`Stderr`] appender. + /// + /// # Examples + /// + /// ``` + /// use logforth::append::Stderr; + /// use logforth::layout::JsonLayout; + /// + /// let stderr_appender = Stderr::default().with_layout(JsonLayout::default()); + /// ``` pub fn with_layout(mut self, encoder: impl Into) -> Self { self.layout = encoder.into(); self diff --git a/src/filter/custom.rs b/src/filter/custom.rs index 804d1a3..90d2131 100644 --- a/src/filter/custom.rs +++ b/src/filter/custom.rs @@ -19,21 +19,19 @@ use log::Metadata; use crate::filter::Filter; use crate::filter::FilterResult; -/// A filter that you can pass the custom filter function. +/// A custom filter using a user-defined function. /// -/// The custom filter function accepts [`&log::Metadata`][Metadata] and returns the -/// [`FilterResult`]. For example: +/// # Examples /// -/// ```rust -/// use log::Metadata; +/// ``` /// use logforth::filter::CustomFilter; /// use logforth::filter::FilterResult; /// -/// let filter = CustomFilter::new(|metadata: &Metadata| { -/// if metadata.target() == "my_crate" { +/// let custom_filter = CustomFilter::new(|metadata| { +/// if metadata.level() == log::Level::Error { /// FilterResult::Accept /// } else { -/// FilterResult::Neutral +/// FilterResult::Reject /// } /// }); /// ``` @@ -48,6 +46,7 @@ impl Debug for CustomFilter { } impl CustomFilter { + /// Creates a new [`CustomFilter`]. pub fn new(filter: impl Fn(&Metadata) -> FilterResult + Send + Sync + 'static) -> Self { CustomFilter { f: Box::new(filter), diff --git a/src/filter/env_filter.rs b/src/filter/env_filter.rs index 53c48cf..0744a7d 100644 --- a/src/filter/env_filter.rs +++ b/src/filter/env_filter.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +//! Provides [`env_filter`](https://crates.io/crates/env_filter) based filter for log records. + use std::borrow::Cow; use std::str::FromStr; @@ -21,7 +23,7 @@ use log::Metadata; use crate::filter::FilterResult; use crate::Filter; -/// The default name for the environment variable to read filters from. +/// The default environment variable for filtering logs. pub const DEFAULT_FILTER_ENV: &str = "RUST_LOG"; /// A filter consists of one or more comma-separated directives which match on [`log::Record`]. diff --git a/src/filter/mod.rs b/src/filter/mod.rs index 16a8888..58fdc0d 100644 --- a/src/filter/mod.rs +++ b/src/filter/mod.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Determinate whether a log record should be processed. +//! Filters for log records. use std::str::FromStr; @@ -24,7 +24,7 @@ pub use self::env_filter::EnvFilter; mod custom; pub mod env_filter; -/// The result of a filter may return. +/// The result of a filter check. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum FilterResult { /// The record will be processed without further filtering. @@ -35,9 +35,12 @@ pub enum FilterResult { Neutral, } +/// Represents a filter that can be applied to log records. #[derive(Debug)] pub enum Filter { + /// An env_logger filter. Env(EnvFilter), + /// A custom filter. Custom(CustomFilter), } diff --git a/src/layout/json.rs b/src/layout/json.rs index 33095c2..fb6cb5c 100644 --- a/src/layout/json.rs +++ b/src/layout/json.rs @@ -24,7 +24,7 @@ use serde_json::Value; use crate::layout::Layout; -/// A layout that formats log record as JSON lines. +/// A JSON layout for formatting log records. /// /// Output format: /// @@ -36,11 +36,33 @@ use crate::layout::Layout; /// {"timestamp":"2024-08-11T22:44:57.172353+08:00","level":"TRACE","module_path":"rolling_file","file":"examples/rolling_file.rs","line":55,"message":"Hello trace!","kvs":{}} /// ``` /// -/// You can customize the timezone of the timestamp by setting the `tz` field with a [`TimeZone`] -/// instance. Otherwise, the system timezone is used. +/// # Examples +/// +/// ``` +/// use logforth::layout::JsonLayout; +/// +/// let json_layout = JsonLayout::default(); +/// ``` #[derive(Default, Debug, Clone)] pub struct JsonLayout { - pub tz: Option, + tz: Option, +} + +impl JsonLayout { + /// Sets the timezone for timestamps. + /// + /// # Examples + /// + /// ``` + /// use jiff::tz::TimeZone; + /// use logforth::layout::JsonLayout; + /// + /// let json_layout = JsonLayout::default().timezone(TimeZone::UTC); + /// ``` + pub fn timezone(mut self, tz: TimeZone) -> Self { + self.tz = Some(tz); + self + } } struct KvCollector<'a> { diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 2ba0697..cb43e08 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Describe how to format a log record. +//! Layouts for formatting log records. pub use custom::CustomLayout; #[cfg(feature = "json")] @@ -28,7 +28,7 @@ mod json; mod kv; mod text; -/// A layout describes how to format a log record. +/// Represents a layout for formatting log records. #[derive(Debug)] pub enum Layout { Custom(CustomLayout), diff --git a/src/layout/text.rs b/src/layout/text.rs index 49e9736..205a73c 100644 --- a/src/layout/text.rs +++ b/src/layout/text.rs @@ -45,6 +45,14 @@ use crate::layout::Layout; /// /// You can customize the timezone of the timestamp by setting the `tz` field with a [`TimeZone`] /// instance. Otherwise, the system timezone is used. +/// +/// # Examples +/// +/// ``` +/// use logforth::layout::TextLayout; +/// +/// let text_layout = TextLayout::default(); +/// ``` #[derive(Default, Debug, Clone)] pub struct TextLayout { colors: LevelColor, @@ -53,68 +61,88 @@ pub struct TextLayout { } impl TextLayout { - /// Turn off coloring of log levels. + /// Disables colored output. pub fn no_color(mut self) -> Self { self.no_color = true; self } - /// Set the timezone of the timestamp; default to the system timezone. + /// Sets the timezone for timestamps. + /// + /// # Examples + /// + /// ``` + /// use jiff::tz::TimeZone; + /// use logforth::layout::TextLayout; + /// + /// let text_layout = TextLayout::default().timezone(TimeZone::UTC); + /// ``` pub fn timezone(mut self, tz: TimeZone) -> Self { self.tz = Some(tz); self } - /// Customize the color of each log level; no effect if `no_color` is set to `true`, - /// or the `no-color` feature flag is enabled. + /// Customize the color of each log level. + /// + /// No effect if `no_color` is set to `true` or the `no-color` feature flag is enabled. pub fn colors(mut self, colors: LevelColor) -> Self { self.colors = colors; self } - /// Customize the color of the error log level; no effect if `no_color` is set to `true`, - /// or the `no-color` feature flag is enabled. Default to red. + /// Customize the color of the error log level. Default to red. + /// + /// No effect if `no_color` is set to `true` or the `no-color` feature flag is enabled. pub fn error_color(mut self, color: Color) -> Self { self.colors.error = color; self } - /// Customize the color of the warn log level; no effect if `no_color` is set to `true`, - /// or the `no-color` feature flag is enabled. Default to yellow. + /// Customize the color of the warn log level. Default to yellow. + /// + /// No effect if `no_color` is set to `true` or the `no-color` feature flag is enabled. pub fn warn_color(mut self, color: Color) -> Self { self.colors.warn = color; self } - /// Customize the color of the info log level; no effect if `no_color` is set to `true`, - /// or the `no-color` feature flag is enabled. Default to green. + /// Customize the color of the info log level/ Default to green. + /// + /// No effect if `no_color` is set to `true` or the `no-color` feature flag is enabled. pub fn info_color(mut self, color: Color) -> Self { self.colors.info = color; self } - /// Customize the color of the debug log level; no effect if `no_color` is set to `true`, - /// or the `no-color` feature flag is enabled. Default to blue. + /// Customize the color of the debug log level. Default to blue. + /// + /// No effect if `no_color` is set to `true` or the `no-color` feature flag is enabled. pub fn debug_color(mut self, color: Color) -> Self { self.colors.debug = color; self } - /// Customize the color of the trace log level; no effect if `no_color` is set to `true`, - /// or the `no-color` feature flag is enabled. Default to magenta. + /// Customize the color of the trace log level. Default to magenta. + /// + /// No effect if `no_color` is set to `true` or the `no-color` feature flag is enabled. pub fn trace_color(mut self, color: Color) -> Self { self.colors.trace = color; self } } -/// Customize the color of each log level. +/// Colors for different log levels. #[derive(Debug, Clone)] pub struct LevelColor { + /// Color for error level logs. pub error: Color, + /// Color for warning level logs. pub warn: Color, + /// Color for info level logs. pub info: Color, + /// Color for debug level logs. pub debug: Color, + /// Color for trace level logs. pub trace: Color, } diff --git a/src/lib.rs b/src/lib.rs index 7bb5fa2..101257f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,40 +12,45 @@ // See the License for the specific language governing permissions and // limitations under the License. -/*! -# A versatile and extensible logging implementation - -## Usage - -Add the dependencies to your `Cargo.toml` with: - -```shell -cargo add log -cargo add logforth -``` - -[`log`] is the logging facade and `logforth` is the logging implementation. - -Then, you can use the logger with the simplest default setup: - -```rust -logforth::stderr().apply(); -``` - -Or configure the logger in a more fine-grained way: - -```rust -use log::LevelFilter; -use logforth::append; - -logforth::builder() - .dispatch(|d| d.filter(LevelFilter::Debug).append(append::Stderr::default())) - .dispatch(|d| d.filter(LevelFilter::Info).append(append::Stdout::default())) - .apply(); -``` - -Read more demos under the [examples](https://github.com/fast/logforth/tree/main/examples) directory. -*/ +//! Logforth is a flexible logging framework for Rust applications, providing easy log dispatching +//! and configuration. +//! +//! # Overview +//! +//! Logforth allows you to set up multiple log dispatches with different filters and appenders. You +//! can configure global log levels, use built-in appenders for stdout, stderr, files, or create +//! custom appenders. It integrates seamlessly with the `log` crate. +//! +//! # Examples +//! +//! Simple setup with default stdout appender: +//! +//! ``` +//! logforth::stdout().apply(); +//! +//! log::info!("This is an info message."); +//! ``` +//! +//! Advanced setup with custom filters and multiple appenders: +//! +//! ``` +//! use log::LevelFilter; +//! use logforth::append; +//! +//! logforth::builder() +//! .dispatch(|d| { +//! d.filter(LevelFilter::Error) +//! .append(append::Stderr::default()) +//! }) +//! .dispatch(|d| { +//! d.filter(LevelFilter::Info) +//! .append(append::Stdout::default()) +//! }) +//! .apply(); +//! +//! log::error!("Error message."); +//! log::info!("Info message."); +//! ``` #![cfg_attr(docsrs, feature(doc_auto_cfg))] diff --git a/src/logger/builder.rs b/src/logger/builder.rs index cfe514e..9ebb54b 100644 --- a/src/logger/builder.rs +++ b/src/logger/builder.rs @@ -21,50 +21,29 @@ use crate::filter::EnvFilter; use crate::Append; use crate::Filter; -/// Create a new empty [builder][Builder]. +/// Creates a new empty [`Builder`] instance for configuring log dispatching. /// -/// At least one dispatch would be added: +/// # Examples /// -/// ```rust -/// use log::LevelFilter; -/// use logforth::append; -/// -/// logforth::builder() -/// .dispatch(|d| { -/// d.filter(LevelFilter::Info) -/// .append(append::Stdout::default()) -/// }) -/// .apply(); /// ``` -/// -/// Multiple dispatches can be added: -/// -/// ```rust -/// use log::LevelFilter; /// use logforth::append; /// -/// logforth::builder() -/// .dispatch(|d| { -/// d.filter(LevelFilter::Info) -/// .append(append::Stdout::default()) -/// }) -/// .dispatch(|d| { -/// d.filter(LevelFilter::Debug) -/// .append(append::Stderr::default()) -/// }) +/// let builder = logforth::builder() +/// .dispatch(|d| d.append(append::Stderr::default())) /// .apply(); /// ``` pub fn builder() -> Builder { Builder::new() } -/// Create a new [`Builder`] with a default [`Stdout`][append::Stdout] append configured, and -/// respect the `RUST_LOG` environment variable for filtering logs. +/// Creates a [`Builder`] with a default [`append::Stderr`] appender and an [`env_filter`](https://crates.io/crates/env_filter) +/// respecting `RUST_LOG`. /// -/// This is a convenient API that you can use as: +/// # Examples /// -/// ```rust -/// logforth::stdout().apply(); +/// ``` +/// logforth::stderr().apply(); +/// log::error!("This error will be logged to stderr."); /// ``` pub fn stdout() -> Builder { crate::builder().dispatch(|d| { @@ -73,13 +52,14 @@ pub fn stdout() -> Builder { }) } -/// Create a new [`Builder`] with a default [`Stderr`][append::Stderr] append configured, and -/// respect the `RUST_LOG` environment variable for filtering logs. +/// Creates a [`Builder`] with a default [`append::Stdout`] appender and an [`env_filter`](https://crates.io/crates/env_filter) +/// respecting `RUST_LOG`. /// -/// This is a convenient API that you can use as: +/// # Examples /// -/// ```rust -/// logforth::stderr().apply(); +/// ``` +/// logforth::stdout().apply(); +/// log::info!("This info will be logged to stdout."); /// ``` pub fn stderr() -> Builder { crate::builder().dispatch(|d| { @@ -88,21 +68,15 @@ pub fn stderr() -> Builder { }) } -/// A builder for configuring the logger. Always constructed via [`builder`] for a fluent API. +/// A builder for configuring log dispatching and setting up the global logger. /// -/// ## Examples +/// # Examples /// -/// Create a new builder and configure filters and appends: -/// -/// ```rust -/// use log::LevelFilter; +/// ``` /// use logforth::append; /// /// logforth::builder() -/// .dispatch(|d| { -/// d.filter(LevelFilter::Info) -/// .append(append::Stdout::default()) -/// }) +/// .dispatch(|d| d.append(append::Stdout::default())) /// .apply(); /// ``` #[must_use = "call `apply` to set the global logger"] @@ -123,7 +97,17 @@ impl Builder { } } - /// Register a new dispatch. + /// Registers a new dispatch with the [`Builder`]. + /// + /// # Examples + /// + /// ``` + /// use logforth::append; + /// + /// logforth::builder() + /// .dispatch(|d| d.append(append::Stderr::default())) + /// .apply(); + /// ``` pub fn dispatch(mut self, f: F) -> Self where F: FnOnce(DispatchBuilder) -> DispatchBuilder, @@ -132,23 +116,39 @@ impl Builder { self } - /// Set the global maximum log level. + /// Sets the global maximum log level. + /// + /// This will bypass to `log::set_max_level()`. + /// + /// # Examples /// - /// This will be passed to [`log::set_max_level`] on [`Builder::apply`]. + /// ``` + /// logforth::builder() + /// .max_level(log::LevelFilter::Warn) + /// .apply(); + /// ``` pub fn max_level(mut self, max_level: LevelFilter) -> Self { self.max_level = max_level; self } - /// Set up the global logger with all the dispatches configured. + /// Sets up the global logger with all the configured dispatches. /// /// This should be called early in the execution of a Rust program. Any log events that occur /// before initialization will be ignored. /// /// # Errors /// - /// This function will fail if it is called more than once, or if another library has already - /// initialized a global logger. + /// Returns an error if a global logger has already been set. + /// + /// # Examples + /// + /// ``` + /// let result = logforth::builder().try_apply(); + /// if let Err(e) = result { + /// eprintln!("Failed to set logger: {}", e); + /// } + /// ``` pub fn try_apply(self) -> Result<(), log::SetLoggerError> { let logger = Logger::new(self.dispatches); log::set_boxed_logger(Box::new(logger))?; @@ -156,23 +156,42 @@ impl Builder { Ok(()) } - /// Set up the global logger with all the dispatches configured. + /// Sets up the global logger with all the configured dispatches. /// - /// This should be called early in the execution of a Rust program. Any log events that occur - /// before initialization will be ignored. + /// This function will panic if it is called more than once, or if another library has already + /// initialized a global logger. /// /// # Panics /// - /// This function will panic if it is called more than once, or if another library has already - /// initialized a global logger. + /// Panics if the global logger has already been set. + /// + /// # Examples + /// + /// ``` + /// logforth::builder().apply(); + /// ``` pub fn apply(self) { self.try_apply() .expect("Builder::apply should not be called after the global logger initialized"); } } +/// A builder for configuring a log dispatch, including filters and appenders. +/// +/// # Examples +/// +/// ``` +/// use logforth::append; +/// +/// logforth::builder() +/// .dispatch(|d| { +/// d.filter(log::LevelFilter::Info) +/// .append(append::Stdout::default()) +/// }) +/// .apply(); +/// ``` #[derive(Debug)] -pub struct DispatchBuilder { +pub struct DispatchBuilder { filters: Vec, appends: Vec>, } @@ -184,6 +203,25 @@ impl DispatchBuilder { appends: vec![], } } + + /// Sets the filter for this dispatch. + /// + /// # Examples + /// + /// ``` + /// use logforth::append; + /// + /// logforth::builder() + /// .dispatch(|d| { + /// d.filter(log::LevelFilter::Error) + /// .append(append::Stderr::default()) + /// }) + /// .apply(); + /// ``` + pub fn filter(mut self, filter: impl Into) -> Self { + self.filters.push(filter.into()); + self + } } impl DispatchBuilder { @@ -193,13 +231,17 @@ impl DispatchBuilder { } impl DispatchBuilder { - /// Add a [`Filter`] to the under constructing `Dispatch`. - pub fn filter(mut self, filter: impl Into) -> Self { - self.filters.push(filter.into()); - self - } - - /// Add an [`Append`] to the under constructing `Dispatch`. + /// Appends an appender to this dispatch. + /// + /// # Examples + /// + /// ``` + /// use logforth::append; + /// + /// logforth::builder() + /// .dispatch(|d| d.append(append::Stdout::default())) + /// .apply(); + /// ``` pub fn append(mut self, append: impl Append) -> DispatchBuilder { self.appends.push(Box::new(append)); DispatchBuilder {