Skip to content

Commit

Permalink
feat: support env filter (#55)
Browse files Browse the repository at this point in the history
Signed-off-by: tison <[email protected]>
  • Loading branch information
tisonkun committed Aug 16, 2024
1 parent 2e3ada1 commit 21f4e70
Show file tree
Hide file tree
Showing 9 changed files with 299 additions and 14 deletions.
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ jobs:
cargo run --features="json" --example json_stdio
cargo run --features="json,rolling_file" --example rolling_file
cargo run --example fn_layout_filter
cargo run --features="env-filter" --example env_filter
required:
name: Required
Expand Down
23 changes: 21 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[features]
env-filter = ["dep:env_filter"]
fastrace = ["dep:fastrace"]
json = ["dep:serde_json", "dep:serde", "jiff/serde"]
no-color = ["colored/no-color"]
Expand All @@ -48,13 +49,26 @@ colored = { version = "2.1" }
jiff = { version = "0.1.5" }
log = { version = "0.4", features = ["std", "kv_unstable"] }
paste = { version = "1.0" }
serde = { version = "1.0", features = ["derive"], optional = true }
serde_json = { version = "1.0", optional = true }

[dev-dependencies]
rand = "0.8"
tempfile = "3.12"

## Env filter dependencies
[dependencies.env_filter]
optional = true
version = "0.1"

## Serde dependencies
[dependencies.serde]
features = ["derive"]
optional = true
version = "1.0"

[dependencies.serde_json]
optional = true
version = "1.0"

## Rolling file dependencies
[dependencies.crossbeam-channel]
optional = true
Expand Down Expand Up @@ -103,3 +117,8 @@ required-features = ["rolling_file", "json"]
[[example]]
name = "fn_layout_filter"
path = "examples/fn_layout_filter.rs"

[[example]]
name = "env_filter"
path = "examples/env_filter.rs"
required-features = ["env-filter"]
37 changes: 37 additions & 0 deletions examples/env_filter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// 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::append;
use logforth::filter::EnvFilter;
use logforth::layout::TextLayout;
use logforth::Dispatch;
use logforth::Logger;

fn main() {
Logger::new()
.dispatch(
Dispatch::new()
.filter(EnvFilter::from_default_env())
.layout(TextLayout::default())
.append(append::Stdout),
)
.apply()
.unwrap();

log::error!("Hello error!");
log::warn!("Hello warn!");
log::info!("Hello info!");
log::debug!("Hello debug!");
log::trace!("Hello trace!");
}
2 changes: 1 addition & 1 deletion src/filter/custom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ impl CustomFilter {
}
}

pub(crate) fn filter(&self, metadata: &Metadata) -> FilterResult {
pub(crate) fn enabled(&self, metadata: &Metadata) -> FilterResult {
(self.f)(metadata)
}
}
Expand Down
204 changes: 204 additions & 0 deletions src/filter/env.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
// 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 std::borrow::Cow;

use log::LevelFilter;
use log::Metadata;

use crate::filter::FilterResult;
use crate::Filter;

/// The default name for the environment variable to read filters from.
pub const DEFAULT_FILTER_ENV: &str = "RUST_LOG";

/// A filter that respects the `RUST_LOG` environment variable.
///
/// Read more from [the `env_logger` documentation](https://docs.rs/env_logger/#enabling-logging).
#[derive(Debug)]
pub struct EnvFilter(env_filter::Filter);

impl EnvFilter {
/// Initializes the filter builder from the environment using default variable name `RUST_LOG`.
///
/// # Examples
///
/// Initialize a filter using the default environment variables:
///
/// ```
/// use logforth::filter::EnvFilter;
/// let filter = EnvFilter::from_default_env();
/// ```
pub fn from_default_env() -> Self {
EnvFilter::from_env(DEFAULT_FILTER_ENV)
}

/// Initializes the filter builder from the environment using default variable name `RUST_LOG`.
/// If the variable is not set, the default value will be used.
///
/// # Examples
///
/// Initialize a filter using the default environment variables, or fallback to the default
/// value:
///
/// ```
/// use logforth::filter::EnvFilter;
/// let filter = EnvFilter::from_default_env_or("info");
/// ```
pub fn from_default_env_or<'a, V>(default: V) -> Self
where
V: Into<Cow<'a, str>>,
{
EnvFilter::from_env_or(DEFAULT_FILTER_ENV, default)
}

/// Initializes the filter builder from the environment using specific variable name.
///
/// # Examples
///
/// Initialize a filter using the using specific variable name:
///
/// ```
/// use logforth::filter::EnvFilter;
/// let filter = EnvFilter::from_env("MY_LOG");
/// ```
pub fn from_env<'a, E>(name: E) -> Self
where
E: Into<Cow<'a, str>>,
{
let name = name.into();

let builder = EnvFilterBuilder::new();
if let Ok(s) = std::env::var(&*name) {
EnvFilter::new(builder.parse(&s))
} else {
EnvFilter::new(builder)
}
}

/// Initializes the filter builder from the environment using specific variable name.
/// If the variable is not set, the default value will be used.
///
/// # Examples
///
/// Initialize a filter using the using specific variable name, or fallback to the default
/// value:
///
/// ```
/// use logforth::filter::EnvFilter;
/// let filter = EnvFilter::from_env_or("MY_LOG", "info");
/// ```
pub fn from_env_or<'a, 'b, E, V>(name: E, default: V) -> Self
where
E: Into<Cow<'a, str>>,
V: Into<Cow<'b, str>>,
{
let name = name.into();
let default = default.into();

let builder = EnvFilterBuilder::new();
if let Ok(s) = std::env::var(&*name) {
EnvFilter::new(builder.parse(&s))
} else {
EnvFilter::new(builder.parse(&default))
}
}

/// Initializes the filter builder from the [EnvFilterBuilder].
pub fn new(mut builder: EnvFilterBuilder) -> Self {
EnvFilter(builder.0.build())
}

pub(crate) fn enabled(&self, metadata: &Metadata) -> FilterResult {
if self.0.enabled(metadata) {
FilterResult::Neutral
} else {
FilterResult::Reject
}
}

pub(crate) fn matches(&self, record: &log::Record) -> FilterResult {
if self.0.matches(record) {
FilterResult::Neutral
} else {
FilterResult::Reject
}
}
}

impl From<EnvFilter> for Filter {
fn from(filter: EnvFilter) -> Self {
Filter::Env(filter)
}
}

/// A builder for the env log filter.
///
/// It can be used to parse a set of directives from a string before building a [EnvFilter]
/// instance.
#[derive(Default, Debug)]
pub struct EnvFilterBuilder(env_filter::Builder);

impl EnvFilterBuilder {
/// Initializes the filter builder with defaults.
pub fn new() -> Self {
EnvFilterBuilder(env_filter::Builder::new())
}

/// Try to initialize the filter builder from an environment; return `None` if the environment
/// variable is not set or invalid.
pub fn try_from_env(env: &str) -> Option<Self> {
let mut builder = env_filter::Builder::new();
let config = std::env::var(env).ok()?;
builder.try_parse(&config).ok()?;
Some(EnvFilterBuilder(builder))
}

/// Adds a directive to the filter for a specific module.
pub fn filter_module(mut self, module: &str, level: LevelFilter) -> Self {
self.0.filter_module(module, level);
self
}

/// Adds a directive to the filter for all modules.
pub fn filter_level(mut self, level: LevelFilter) -> Self {
self.0.filter_level(level);
self
}

/// Adds a directive to the filter.
///
/// The given module (if any) will log at most the specified level provided. If no module is
/// provided then the filter will apply to all log messages.
pub fn filter(mut self, module: Option<&str>, level: LevelFilter) -> Self {
self.0.filter(module, level);
self
}

/// Parses the directive string, returning an error if the given directive string is invalid.
///
/// See [the `env_logger` documentation](https://docs.rs/env_logger/#enabling-logging) for more details.
pub fn try_parse(mut self, filters: &str) -> anyhow::Result<Self> {
self.0.try_parse(filters)?;
Ok(self)
}

/// Parses the directives string.
///
/// See [the `env_logger` documentation](https://docs.rs/env_logger/#enabling-logging) for more details.
pub fn parse(mut self, filters: &str) -> Self {
self.0.parse(filters);
self
}
}
2 changes: 1 addition & 1 deletion src/filter/level.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ impl LevelFilter {
LevelFilter(level)
}

pub(crate) fn filter(&self, metadata: &Metadata) -> FilterResult {
pub(crate) fn enabled(&self, metadata: &Metadata) -> FilterResult {
let level = metadata.level();
if level <= self.0 {
FilterResult::Neutral
Expand Down
26 changes: 22 additions & 4 deletions src/filter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,14 @@
//! Determinate whether a log record should be processed.

pub use self::custom::CustomFilter;
#[cfg(feature = "env-filter")]
pub use self::env::EnvFilter;
pub use self::level::LevelFilter;
pub use self::target::TargetFilter;

mod custom;
#[cfg(feature = "env-filter")]
pub mod env;
mod level;
mod target;

Expand All @@ -35,17 +39,31 @@ pub enum FilterResult {

#[derive(Debug)]
pub enum Filter {
#[cfg(feature = "env-filter")]
Env(EnvFilter),
Level(LevelFilter),
Target(TargetFilter),
Custom(CustomFilter),
}

impl Filter {
pub(crate) fn filter(&self, metadata: &log::Metadata) -> FilterResult {
pub(crate) fn enabled(&self, metadata: &log::Metadata) -> FilterResult {
match self {
Filter::Level(filter) => filter.filter(metadata),
Filter::Target(filter) => filter.filter(metadata),
Filter::Custom(filter) => filter.filter(metadata),
#[cfg(feature = "env-filter")]
Filter::Env(filter) => filter.enabled(metadata),
Filter::Level(filter) => filter.enabled(metadata),
Filter::Target(filter) => filter.enabled(metadata),
Filter::Custom(filter) => filter.enabled(metadata),
}
}

pub(crate) fn matches(&self, record: &log::Record) -> FilterResult {
match self {
#[cfg(feature = "env-filter")]
Filter::Env(filter) => filter.matches(record),
Filter::Level(filter) => filter.enabled(record.metadata()),
Filter::Target(filter) => filter.enabled(record.metadata()),
Filter::Custom(filter) => filter.enabled(record.metadata()),
}
}
}
2 changes: 1 addition & 1 deletion src/filter/target.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ impl TargetFilter {
}
}

pub(crate) fn filter(&self, metadata: &Metadata) -> FilterResult {
pub(crate) fn enabled(&self, metadata: &Metadata) -> FilterResult {
let matched = metadata.target().starts_with(self.target.as_ref());
if (matched && !self.not) || (!matched && self.not) {
let level = metadata.level();
Expand Down
Loading

0 comments on commit 21f4e70

Please sign in to comment.