Skip to content

Commit

Permalink
Use constructor setup to apply config
Browse files Browse the repository at this point in the history
  • Loading branch information
tjardoo committed Sep 12, 2024
1 parent aa55dc1 commit 4ecfb2b
Show file tree
Hide file tree
Showing 10 changed files with 134 additions and 51 deletions.
16 changes: 12 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,21 +135,29 @@ Create your own log driver.

```rust
Ftail::new()
.custom(Box::new(Box::new(CustomLogger {})), LevelFilter::Debug)
.custom(
|config: ftail::Config| Box::new(CustomLogger { config }) as Box<dyn Log + Send + Sync>,
LevelFilter::Debug,
)
.datetime_format("%H:%M:%S%.3f")
.init()?;

// the custom logger implementation
struct CustomLogger {}
struct CustomLogger {
config: Config,
}

impl Log for CustomLogger {
fn enabled(&self, _metadata: &log::Metadata) -> bool {
true
}

fn log(&self, record: &log::Record) {
let time = chrono::Local::now().format("%H:%M:%S").to_string();
let time = chrono::Local::now()
.format(&self.config.datetime_format)
.to_string();

println!("{} {} {}", time, record.level(), record.args());
println!("{} [{}] {}", time.black(), record.level().bold(), record.args());
}

fn flush(&self) {}
Expand Down
16 changes: 12 additions & 4 deletions examples/custom/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
use ftail::{ansi_escape::TextStyling, Ftail};
use ftail::{ansi_escape::TextStyling, Config, Ftail};
use log::{LevelFilter, Log};

// This example demonstrates how to log messages to stdout with custom styling.

fn main() -> Result<(), Box<dyn std::error::Error>> {
Ftail::new()
.custom(Box::new(Box::new(CustomLogger {})), LevelFilter::Debug)
.custom(
|config: ftail::Config| Box::new(CustomLogger { config }) as Box<dyn Log + Send + Sync>,
LevelFilter::Debug,
)
.datetime_format("%H:%M:%S%.3f")
.init()?;

log::trace!("This is a trace message");
Expand All @@ -21,15 +25,19 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}

struct CustomLogger {}
struct CustomLogger {
config: Config,
}

impl Log for CustomLogger {
fn enabled(&self, _metadata: &log::Metadata) -> bool {
true
}

fn log(&self, record: &log::Record) {
let time = chrono::Local::now().format("%H:%M:%S").to_string();
let time = chrono::Local::now()
.format(&self.config.datetime_format)
.to_string();

let level = match record.level() {
log::Level::Trace => record.level().black().to_string(),
Expand Down
5 changes: 4 additions & 1 deletion src/drivers/console.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use log::Log;

use crate::formatters::{default::DefaultFormatter, Config, Formatter};
use crate::{
formatters::{default::DefaultFormatter, Formatter},
Config,
};

/// A logger that logs messages to the console.
pub struct ConsoleLogger {
Expand Down
3 changes: 2 additions & 1 deletion src/drivers/daily_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ use std::{

use crate::{
error::FtailError,
formatters::{default::DefaultFormatter, Config, Formatter},
formatters::{default::DefaultFormatter, Formatter},
Config,
};

/// A logger that logs messages to a daily log file.
Expand Down
5 changes: 4 additions & 1 deletion src/drivers/formatted_console.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use log::Log;

use crate::formatters::{readable::ReadableFormatter, Config, Formatter};
use crate::{
formatters::{readable::ReadableFormatter, Formatter},
Config,
};

/// A logger that logs formatted messages to the console.
pub struct FormattedConsoleLogger {
Expand Down
3 changes: 2 additions & 1 deletion src/drivers/single_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ use std::{

use crate::{
error::FtailError,
formatters::{default::DefaultFormatter, Config, Formatter},
formatters::{default::DefaultFormatter, Formatter},
Config,
};

/// A logger that logs messages to a single log file.
Expand Down
8 changes: 2 additions & 6 deletions src/formatters/mod.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
use crate::Config;

pub mod default;
pub mod readable;

pub trait Formatter {
fn format(&self) -> String;
}

#[derive(Clone)]
pub struct Config {
pub datetime_format: String,
pub timezone: chrono_tz::Tz,
}

impl Config {
pub fn new() -> Config {
Config {
Expand Down
4 changes: 2 additions & 2 deletions src/formatters/readable.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{ansi_escape::TextStyling, writer::LogWriter};
use crate::{ansi_escape::TextStyling, writer::LogWriter, Config};

use super::{Config, Formatter};
use super::Formatter;

pub struct ReadableFormatter<'a> {
record: &'a log::Record<'a>,
Expand Down
123 changes: 93 additions & 30 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,21 +135,29 @@
//!
//! ```rust
//! Ftail::new()
//! .custom(Box::new(Box::new(CustomLogger {})), LevelFilter::Debug)
//! .custom(
//! |config: ftail::Config| Box::new(CustomLogger { config }) as Box<dyn Log + Send + Sync>,
//! LevelFilter::Debug,
//! )
//! .datetime_format("%H:%M:%S%.3f")
//! .init()?;
//!
//! // the custom logger implementation
//! struct CustomLogger {}
//! struct CustomLogger {
//! config: Config,
//! }
//!
//! impl Log for CustomLogger {
//! fn enabled(&self, _metadata: &log::Metadata) -> bool {
//! true
//! }
//!
//! fn log(&self, record: &log::Record) {
//! let time = chrono::Local::now().format("%H:%M:%S").to_string();
//! let time = chrono::Local::now()
//! .format(&self.config.datetime_format)
//! .to_string();
//!
//! println!("{} {} {}", time, record.level(), record.args());
//! println!("{} [{}] {}", time.black(), record.level().bold(), record.args());
//! }
//!
//! fn flush(&self) {}
Expand All @@ -161,7 +169,6 @@ use drivers::{
single_file::SingleFileLogger,
};
use error::FtailError;
use formatters::Config;
use log::Log;

/// Module containing the ANSI escape codes.
Expand All @@ -173,22 +180,38 @@ pub mod error;
mod formatters;
mod writer;

pub(crate) struct LogDriver {
driver: Box<dyn Log>,
level: log::LevelFilter,
}

/// The main struct for configuring the logger.
pub struct Ftail {
drivers: Vec<LogDriver>,
initialized_drivers: Vec<InitializedLogDriver>,
config: Config,
}

unsafe impl Send for Ftail {}
unsafe impl Sync for Ftail {}

pub(crate) struct LogDriver {
constructor: Box<dyn Fn(Config) -> Box<dyn Log + Send + Sync>>,
level: log::LevelFilter,
}

pub(crate) struct InitializedLogDriver {
driver: Box<dyn Log + Send + Sync>,
level: log::LevelFilter,
}

#[derive(Clone)]
pub struct Config {
pub datetime_format: String,
pub timezone: chrono_tz::Tz,
}

impl Ftail {
/// Create a new instance of `Ftail`.
pub fn new() -> Self {
Self {
drivers: Vec::new(),
initialized_drivers: Vec::new(),
config: Config::new(),
}
}
Expand All @@ -207,76 +230,116 @@ impl Ftail {
self
}

fn add_driver(mut self, driver: Box<dyn Log>, level: log::LevelFilter) -> Self {
self.drivers.push(LogDriver { driver, level });

fn add_driver<F>(mut self, constructor: F, level: log::LevelFilter) -> Self
where
F: Fn(Config) -> Box<dyn Log + Send + Sync> + 'static,
{
self.drivers.push(LogDriver::new(constructor, level));
self
}

/// Add a driver that logs messages to the console.
pub fn console(self, level: log::LevelFilter) -> Self {
let config = self.config.clone();
let constructor =
|config: Config| Box::new(ConsoleLogger::new(config)) as Box<dyn Log + Send + Sync>;

self.add_driver(Box::new(ConsoleLogger::new(config)), level)
self.add_driver(constructor, level)
}

/// Add a driver that logs formatted messages to the console.
pub fn formatted_console(self, level: log::LevelFilter) -> Self {
let config = self.config.clone();
let constructor = |config: Config| {
Box::new(FormattedConsoleLogger::new(config)) as Box<dyn Log + Send + Sync>
};

self.add_driver(Box::new(FormattedConsoleLogger::new(config)), level)
self.add_driver(constructor, level)
}

/// Add a driver that logs messages to a single file.
pub fn single_file(self, path: &str, append: bool, level: log::LevelFilter) -> Self {
let config = self.config.clone();
let path = path.to_string();

self.add_driver(
Box::new(SingleFileLogger::new(path, append, config).unwrap()),
level,
)
let constructor = move |config: Config| {
Box::new(SingleFileLogger::new(&path, append, config).unwrap())
as Box<dyn Log + Send + Sync>
};

self.add_driver(constructor, level)
}

/// Add a driver that logs messages to a daily log file.
pub fn daily_file(self, path: &str, level: log::LevelFilter) -> Self {
let config = self.config.clone();
let path = path.to_string();

let constructor = move |config: Config| {
Box::new(DailyFileLogger::new(&path, config).unwrap()) as Box<dyn Log + Send + Sync>
};

self.add_driver(Box::new(DailyFileLogger::new(path, config).unwrap()), level)
self.add_driver(constructor, level)
}

/// Add a custom driver.
pub fn custom(self, driver: Box<dyn Log>, level: log::LevelFilter) -> Self {
self.add_driver(Box::new(driver), level)
pub fn custom<F>(self, constructor: F, level: log::LevelFilter) -> Self
where
F: Fn(Config) -> Box<dyn Log + Send + Sync> + 'static,
{
self.add_driver(constructor, level)
}

/// Initialize the logger.
pub fn init(self) -> Result<(), FtailError> {
pub fn init(mut self) -> Result<(), FtailError> {
if self.drivers.is_empty() {
return Err(FtailError::NoDriversError);
}

let drivers = std::mem::take(&mut self.drivers);

self.initialized_drivers = drivers
.into_iter()
.map(|driver| driver.init(self.config.clone()))
.collect();

log::set_max_level(log::LevelFilter::Trace);
log::set_boxed_logger(Box::new(self)).map_err(FtailError::SetLoggerError)
}
}

impl LogDriver {
fn new<F>(constructor: F, level: log::LevelFilter) -> Self
where
F: Fn(Config) -> Box<dyn Log + Send + Sync> + 'static,
{
Self {
constructor: Box::new(constructor),
level,
}
}

fn init(self, config: Config) -> InitializedLogDriver {
InitializedLogDriver {
driver: (self.constructor)(config),
level: self.level,
}
}
}

impl Log for Ftail {
fn enabled(&self, metadata: &log::Metadata) -> bool {
self.drivers
self.initialized_drivers
.iter()
.any(|driver| metadata.level() <= driver.level && driver.driver.enabled(metadata))
}

fn log(&self, record: &log::Record) {
for driver in &self.drivers {
for driver in &self.initialized_drivers {
if driver.level >= record.level() || driver.level == log::LevelFilter::Off {
driver.driver.log(record);
}
}
}

fn flush(&self) {
for driver in &self.drivers {
for driver in &self.initialized_drivers {
driver.driver.flush();
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/writer.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use log::Record;

use crate::formatters::Config;
use crate::Config;

pub(crate) struct LogWriter<'a> {
record: &'a Record<'a>,
Expand Down

0 comments on commit 4ecfb2b

Please sign in to comment.