Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add a simple filter #29

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions examples/filter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use kv_log_macro as log;

fn main() {
let filter = femme::Builder::new()
.filter_module("filter", femme::LevelFilter::Info)
.build();
femme::with_filter(filter);
log::error!("Buffer has to be 16 bytes in length");
log::warn!("Unauthorized access attempt", { route: "/login", user_id: "827756627", });
log::info!("Server listening", { port: "8080" });
log::info!("Request handled", { method: "GET", path: "/foo/bar", status: 200, elapsed: "4ms" });
log::debug!("Getting String as bson value type");
log::trace!("Task spawned", {task_id: "567", thread_id: "12"});
log::info!(r#"raw " fun with JSON"#);
log::info!("n\ne\nw\nl\ni\nn\ne\n");
}
127 changes: 127 additions & 0 deletions src/filter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
//! Filtering for log records.
//!
//! ```
//! use log::LevelFilter;
//!
//! let filter = femme::Builder::new()
//! .filter_level(LevelFilter::Warn)
//! .filter_module("main", LevelFilter::Debug)
//! .filter_module("test_mod", LevelFilter::Trace)
//! .filter_module("another_mod", LevelFilter::Info)
//! .build();
//! femme::with_filter(filter);
//! ```
use log::{Metadata, Level, LevelFilter};
use std::mem;

/// A log filter.
///
/// This struct can be used to determine whether or not a log record
/// should be written to the output.
#[derive(Debug)]
pub struct Filter {
directives: Vec<Directive>,
}

impl Filter {
/// Determines if a log message with the specified metadata would be logged.
pub fn enabled(&self, metadata: &Metadata) -> bool {
let level = metadata.level();
let target = metadata.target();

enabled(&self.directives, level, target)
}

/// Returns the maximum `LevelFilter` that this filter instance is
/// configured to output.
pub fn filter(&self) -> LevelFilter {
self.directives
.iter()
.map(|d| d.level)
.max()
.unwrap_or(LevelFilter::Off)
}
}

// Check whether a level and target are enabled by the set of directives.
fn enabled(directives: &[Directive], level: Level, target: &str) -> bool {
// Search for the longest match, the vector is assumed to be pre-sorted.
for directive in directives.iter().rev() {
match directive.name {
Some(ref name) if !target.starts_with(&**name) => {}
Some(..) | None => return level <= directive.level,
}
}
false
}

/// A builder for a log filter.
#[derive(Debug)]
struct Directive {
name: Option<String>,
level: LevelFilter,
}

pub struct Builder {
directives: Vec<Directive>,
}

impl Default for Builder {
fn default() -> Self {
Self::new()
}
}

impl Builder {
/// Initializes the filter builder with defaults.
pub fn new() -> Builder {
Builder {
directives: Vec::new(),
}
}

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

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

/// 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) -> &mut Self {
self.directives.push(Directive {
name: module.map(|s| s.to_string()),
level,
});
self
}

/// Build a log filter.
pub fn build(&mut self) -> Filter {
if self.directives.is_empty() {
// Adds the default filter if none exist
self.directives.push(Directive {
name: None,
level: LevelFilter::Error,
});
} else {
// Sort the directives by length of their name, this allows a
// little more efficient lookup at runtime.
self.directives.sort_by(|a, b| {
let alen = a.name.as_ref().map(|a| a.len()).unwrap_or(0);
let blen = b.name.as_ref().map(|b| b.len()).unwrap_or(0);
alen.cmp(&blen)
});
}

Filter {
directives: mem::replace(&mut self.directives, Vec::new()),
}
}
}
19 changes: 19 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ mod pretty;
#[cfg(target_arch = "wasm32")]
mod wasm;

mod filter;

pub use filter::{Filter, Builder};

/// Starts logging depending on current environment.
///
/// # Log output
Expand Down Expand Up @@ -61,3 +65,18 @@ pub fn with_level(level: log::LevelFilter) {
}
}
}

pub fn with_filter(filter: Filter) {
#[cfg(target_arch = "wasm32")]
wasm::start(level);

#[cfg(not(target_arch = "wasm32"))]
{
// Use ndjson in release mode, pretty logging while debugging.
if cfg!(debug_assertions) {
pretty::with_filter(filter);
} else {
ndjson::with_filter(filter);
}
}
}
20 changes: 17 additions & 3 deletions src/ndjson.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,34 @@
use log::{kv, LevelFilter, Log, Metadata, Record};
use std::io::{self, StdoutLock, Write};
use std::time;
use crate::filter::{Filter, Builder};

/// Start logging.
pub(crate) fn start(level: LevelFilter) {
let logger = Box::new(Logger {});
let filter = Builder::new()
.filter_level(level)
.build();
let logger = Box::new(Logger { filter });
log::set_boxed_logger(logger).expect("Could not start logging");
log::set_max_level(level);
}

/// Start logging with filter.
pub(crate) fn with_filter(filter: Filter) {
let max_level = filter.filter();
let logger = Box::new(Logger { filter });
log::set_boxed_logger(logger).expect("Could not start logging");
log::set_max_level(max_level);
}

#[derive(Debug)]
pub(crate) struct Logger {}
pub(crate) struct Logger {
filter: Filter,
}

impl Log for Logger {
fn enabled(&self, metadata: &Metadata<'_>) -> bool {
metadata.level() <= log::max_level()
self.filter.enabled(metadata)
}

fn log(&self, record: &Record<'_>) {
Expand Down
20 changes: 17 additions & 3 deletions src/pretty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use log::{kv, Level, LevelFilter, Log, Metadata, Record};
use std::io::{self, StdoutLock, Write};
use crate::filter::{Filter, Builder};

// ANSI term codes.
const RESET: &'static str = "\x1b[0m";
Expand All @@ -12,17 +13,30 @@ const YELLOW: &'static str = "\x1b[33m";

/// Start logging.
pub(crate) fn start(level: LevelFilter) {
let logger = Box::new(Logger {});
let filter = Builder::new()
.filter_level(level)
.build();
let logger = Box::new(Logger { filter });
log::set_boxed_logger(logger).expect("Could not start logging");
log::set_max_level(level);
}

/// Start logging with filter.
pub(crate) fn with_filter(filter: Filter) {
let max_level = filter.filter();
let logger = Box::new(Logger { filter });
log::set_boxed_logger(logger).expect("Could not start logging");
log::set_max_level(max_level);
}

#[derive(Debug)]
pub(crate) struct Logger {}
pub(crate) struct Logger {
filter: Filter,
}

impl Log for Logger {
fn enabled(&self, metadata: &Metadata<'_>) -> bool {
metadata.level() <= log::max_level()
self.filter.enabled(metadata)
}

fn log(&self, record: &Record<'_>) {
Expand Down
18 changes: 15 additions & 3 deletions src/wasm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,34 @@
use js_sys::Object;
use log::{kv, Level, LevelFilter, Log, Metadata, Record};
use wasm_bindgen::prelude::*;
use crate::filter::{Filter, Builder};

use std::collections::HashMap;

/// Start logging.
pub(crate) fn start(level: LevelFilter) {
let logger = Box::new(Logger {});
let filter = Builder::new()
.filter_level(level)
.build();
let logger = Box::new(Logger { filter });
log::set_boxed_logger(logger).expect("Could not start logging");
log::set_max_level(level);
}

/// Start logging with filter.
pub(crate) fn with_filter(filter: Filter) {
let max_level = filter.filter();
let logger = Box::new(Logger { filter });
log::set_boxed_logger(logger).expect("Could not start logging");
log::set_max_level(max_level);
}

#[derive(Debug)]
pub(crate) struct Logger {}
pub(crate) struct Logger { filter: Filter }

impl Log for Logger {
fn enabled(&self, metadata: &Metadata<'_>) -> bool {
metadata.level() <= log::max_level()
self.filter.enabled(metadata)
}

fn log(&self, record: &Record<'_>) {
Expand Down