Skip to content

Commit

Permalink
Pretty logging with FmtSubscriber (#2)
Browse files Browse the repository at this point in the history
* Add pretty fmt logger

* Change import name in README.md

* Add docs

* Deny missing docs in lib
  • Loading branch information
tqwewe authored Aug 9, 2022
1 parent 5c2efa7 commit 3b9acad
Show file tree
Hide file tree
Showing 8 changed files with 263 additions and 7 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@ readme = "Readme.md"


[dependencies]
ansi_term = "0.12"
chrono = "0.4"
lunatic = "^0.10"
serde = { version = "1.0", features = ["derive"] }
7 changes: 4 additions & 3 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,19 @@ running processes.
## How to use `lunatic-log`?

Add it as a dependency:

```toml
lunatic-log = "0.1"
```

In your code:

```rust
use logger_rs::{info, subscriber::fmt::FmtSubscriber, LevelFilter};
use lunatic_log::{info, subscriber::fmt::FmtSubscriber, LevelFilter};

fn main() {
// Initialize subscriber
logger_rs::init(FmtSubscriber::new(LevelFilter::Info));
lunatic_log::init(FmtSubscriber::new(LevelFilter::Info));

// Log message
info!("Hello, {}", "World");
Expand All @@ -41,4 +42,4 @@ Licensed under either of
- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)

at your option.
at your option.
16 changes: 16 additions & 0 deletions examples/pretty.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use lunatic_log::{debug, error, info, subscriber::fmt::FmtSubscriber, trace, warn, LevelFilter};

fn main() {
// Initialize subscriber
lunatic_log::init(FmtSubscriber::new(LevelFilter::Trace).pretty());

// Log message
error!("Error");
warn!("Warn");
info!("Info");
debug!("Debug");
trace!("Trace");

// Wait for events to propagate
lunatic::sleep(std::time::Duration::from_millis(50));
}
23 changes: 22 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
//! A logging library for lunatic Rust applications.
//!
//! A [`Subscriber`] is initialized in a [`lunatic::Process`] with [`init`].
//! Logs are emitted to the subscriber when the [`error`], [`warn`], [`info`], [`debug`], [`trace`] macros are used.
//!
//! # Example
//!
//! ```
//! use lunatic_log::{info, subscriber::fmt::FmtSubscriber};
//!
//! // Initialize subscriber
//! init(FmtSubscriber::new(LevelFilter::Info).pretty());
//!
//! // Log info message
//! info!("Hello, {}", "world");
//! ```
#![deny(missing_docs)]

mod level;
#[macro_use]
mod macros;
Expand All @@ -17,7 +36,9 @@ process_local! {
static LOGGING_PROCESS: RefCell<Option<Process<Event>>> = RefCell::new(None);
}

/// Initialize a subscriber to log events.
/// Initialize a subscriber to handle log events.
///
/// The subscriber is spawned in a [`lunatic::Process`] and receives log events.
pub fn init(subscriber: impl Subscriber) -> Process<Event> {
if Process::<Event>::lookup("lunatic::logger").is_some() {
panic!("logger already initialized");
Expand Down
3 changes: 3 additions & 0 deletions src/macros.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/// Logs a message with a specified level.
///
/// You should use [`error`], [`warn`], [`info`], [`debug`], [`trace`] macros instead.
#[macro_export]
macro_rules! log {
// log!(target: "my_target", Level::Info; "a {} event", "log");
Expand Down
28 changes: 28 additions & 0 deletions src/subscriber.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,39 @@
//! A [`Subscriber`] handles log events.
//!
//! It can be used to print to stdout with [`FmtSubscriber`](fmt::FmtSubscriber),
//! but is also capable of handling logs in other ways.
pub mod fmt;
pub mod multiple;

use serde::{de::DeserializeOwned, Serialize};

use crate::{Event, Metadata};

/// A subscriber which handles incoming log [`Event`]s.
///
/// # Example
///
/// ```
/// #[derive(Serialize, Deserialize)]
/// pub struct FmtSubscriber {
/// level_filter: LevelFilter,
/// }
///
/// impl Subscriber for FmtSubscriber {
/// fn enabled(&self, metadata: &Metadata) -> bool {
/// metadata.level() <= &self.level_filter
/// }
///
/// fn event(&self, event: &Event) {
/// println!("Log: {}", event.message());
/// }
/// }
/// ```
pub trait Subscriber: Serialize + DeserializeOwned {
/// Indicate whether subscriber is enabled given some [`Metadata`].
fn enabled(&self, metadata: &Metadata) -> bool;

/// Handle a log [`Event`].
fn event(&self, event: &Event);
}
184 changes: 181 additions & 3 deletions src/subscriber/fmt.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,121 @@
//! Subscriber that prints to stdout/stderr.
//!
//! Supports pretty printing with colors.
use std::fmt::Write;

use ansi_term::Color;
use chrono::Utc;
use serde::{Deserialize, Serialize};

use crate::{level::LevelFilter, Event, Metadata};
use crate::{level::LevelFilter, Event, Level, Metadata};

use super::Subscriber;

const GRAY: Color = Color::Black;

/// A subscriber printing to stdout/stderr.
///
/// # Basic example
///
/// ```
/// lunatic_log::init(FmtSubscriber::new(LevelFilter::Info));
/// ```
///
/// # Pretty example
///
/// ```
/// lunatic_log::init(FmtSubscriber::new(LevelFilter::Info).pretty());
/// ```
#[derive(Serialize, Deserialize)]
pub struct FmtSubscriber {
color: bool,
file: bool,
level: bool,
level_filter: LevelFilter,
line_number: bool,
target: bool,
time: bool,
time_format: Option<String>,
}

impl Default for FmtSubscriber {
fn default() -> Self {
Self {
color: false,
file: false,
level: false,
level_filter: LevelFilter::Off,
line_number: false,
target: false,
time: false,
time_format: None,
}
}
}

impl FmtSubscriber {
/// Creates an instance of [`FmtSubscriber`].
pub fn new(level_filter: LevelFilter) -> Self {
FmtSubscriber { level_filter }
FmtSubscriber {
level_filter,
..Default::default()
}
}

/// Configures logging to be pretty with colors, filenames, and more.
pub fn pretty(mut self) -> Self {
self.color = true;
self.file = true;
self.level = true;
self.line_number = true;
self.target = true;
self.time = true;
self
}

/// Enables printing color.
pub fn with_color(mut self, color: bool) -> Self {
self.color = color;
self
}

/// Print filename where log originated.
pub fn with_file(mut self, file: bool) -> Self {
self.file = file;
self
}

/// Print the log level.
pub fn with_level(mut self, level: bool) -> Self {
self.level = level;
self
}

/// Print the line number where log originated.
pub fn with_line_number(mut self, line_number: bool) -> Self {
self.line_number = line_number;
self
}

/// Print the target of the log.
pub fn with_target(mut self, target: bool) -> Self {
self.target = target;
self
}

/// Print the time with the log.
pub fn with_time(mut self, time: bool) -> Self {
self.time = time;
self
}

/// Customize the time format.
///
/// This must be in the `strftime` format supported by [chrono](https://docs.rs/chrono/latest/chrono/format/strftime/index.html).
pub fn with_time_format(mut self, time_format: impl Into<String>) -> Self {
self.time_format = Some(time_format.into());
self
}
}

Expand All @@ -21,6 +125,80 @@ impl Subscriber for FmtSubscriber {
}

fn event(&self, event: &Event) {
println!("{}", event.message());
let mut line = String::new();
if self.time {
let now = Utc::now();
let now_string = now
.format(
self.time_format
.as_deref()
.unwrap_or("%Y-%m-%dT%H:%M:%S%.6fZ"),
)
.to_string();
if self.color {
write!(line, "{} ", GRAY.paint(now_string)).unwrap();
} else {
write!(line, "{now_string} ").unwrap();
}
}
if self.level {
if self.color {
let level_string = match event.metadata().level() {
Level::Error => Color::Red,
Level::Warn => Color::Yellow,
Level::Info => Color::Green,
Level::Debug => Color::Blue,
Level::Trace => Color::Purple,
}
.paint(event.metadata().level().as_str());
for _ in 0..(5 - event.metadata().level().as_str().len()) {
line.push(' ');
}
write!(line, "{level_string} ").unwrap();
} else {
let level_string = event.metadata().level().as_str();
for _ in 0..(5 - event.metadata().level().as_str().len()) {
line.push(' ');
}
write!(line, "{level_string} ").unwrap();
};
}
if self.target {
if self.color {
write!(
line,
"{}",
GRAY.paint(format!("{}: ", event.metadata().target()))
)
.unwrap();
} else {
write!(line, "{}: ", event.metadata().target()).unwrap();
}
}
if self.file {
if let Some(file) = event.metadata().file() {
if self.color {
write!(line, "{}", GRAY.paint(format!("{file}:"))).unwrap();
} else {
write!(line, "{}:", file).unwrap();
}
}
}
if self.line_number {
if let Some(line_number) = event.metadata().line() {
if self.color {
write!(line, "{}", GRAY.paint(format!("{line_number}: "))).unwrap();
} else {
write!(line, "{}: ", line_number).unwrap();
}
}
} else {
line.push(' ');
}
if event.metadata().level() == &Level::Error {
eprintln!("{line}{}", event.message());
} else {
println!("{line}{}", event.message());
}
}
}
7 changes: 7 additions & 0 deletions src/subscriber/multiple.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
//! Combine multiple subscribers.
use lunatic::Process;
use serde::{Deserialize, Serialize};

use crate::{spawn_subscriber, Event, Metadata};

use super::Subscriber;

/// Combines multiple subscribers into a single subscriber.
///
/// Child subscriber processes are spawned, and each one is notified of incoming events.
#[derive(Default, Serialize, Deserialize)]
pub struct MultipleSubscribers {
subscribers: Vec<Process<Event>>,
}

impl MultipleSubscribers {
/// Creates an instance of [`MultipleSubscribers`].
pub fn new() -> Self {
MultipleSubscribers::default()
}

/// Adds a child subscriber which runs in its own process.
pub fn add_subscriber(mut self, subscriber: impl Subscriber) -> Self {
let process = spawn_subscriber(subscriber);
self.subscribers.push(process);
Expand Down

0 comments on commit 3b9acad

Please sign in to comment.