.
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
8
Cargo.toml
Normal file
8
Cargo.toml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
[package]
|
||||||
|
name = "picolog"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
itertools = "*"
|
||||||
|
log = { version = "*", features = ["std"] }
|
||||||
108
src/lib.rs
Normal file
108
src/lib.rs
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
use std::io::{stdout, Write};
|
||||||
|
use itertools::{Itertools, Position};
|
||||||
|
use log::*;
|
||||||
|
|
||||||
|
//╶───╴Initializers╶─────────────────────────────────────────────────────────╴
|
||||||
|
|
||||||
|
pub fn init() -> Result<(), SetLoggerError>{
|
||||||
|
init_with_filter(LevelFilter::Info, |_|true)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init_with_level(max_level_filter: LevelFilter) -> Result<(), SetLoggerError>{
|
||||||
|
init_with_filter(max_level_filter, |_|true)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init_with_filter(
|
||||||
|
max_level_filter: LevelFilter,
|
||||||
|
filter: impl Fn(&Metadata) -> bool + Send + Sync + 'static
|
||||||
|
) -> Result<(), SetLoggerError> {
|
||||||
|
set_boxed_logger(Box::new(PicoLogger {
|
||||||
|
filter,
|
||||||
|
}))?;
|
||||||
|
set_max_level(max_level_filter);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
//╶───╴PicoLogger╶───────────────────────────────────────────────────────────╴
|
||||||
|
|
||||||
|
pub struct PicoLogger<F: Fn(&Metadata) -> bool + Send + Sync> {
|
||||||
|
filter: F
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F: Fn(&Metadata) -> bool + Send + Sync> Log for PicoLogger<F> {
|
||||||
|
fn enabled(&self, metadata: &Metadata) -> bool { (self.filter)(metadata) }
|
||||||
|
fn log(&self, record: &Record) { _=format(record); }
|
||||||
|
fn flush(&self) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
//╶───╴Formatting╶───────────────────────────────────────────────────────────╴
|
||||||
|
|
||||||
|
fn format(record: &Record) -> std::io::Result<()> {
|
||||||
|
let mut buffer = stdout();
|
||||||
|
let buffer = &mut buffer;
|
||||||
|
write!(buffer, "{GRAY}")?;
|
||||||
|
|
||||||
|
// module name
|
||||||
|
let mp = rtrunc(record.module_path().unwrap_or(""), 12);
|
||||||
|
write!(buffer, "{mp:>12} ")?;
|
||||||
|
|
||||||
|
// file path
|
||||||
|
let fp = rtrunc(record.file().unwrap_or(""), 12);
|
||||||
|
write!(buffer, "{fp:>12}:{:<4}", record.line().unwrap_or(0))?;
|
||||||
|
|
||||||
|
// write the record level
|
||||||
|
match record.level() {
|
||||||
|
Level::Error => write!(buffer, "[{RED}Errr{GRAY}] "),
|
||||||
|
Level::Warn => write!(buffer, "[{YELLOW}Warn{GRAY}] "),
|
||||||
|
Level::Info => write!(buffer, "[{BLUE}Info{GRAY}] "),
|
||||||
|
Level::Debug => write!(buffer, "[{PURPLE}Dbug{GRAY}] "),
|
||||||
|
Level::Trace => write!(buffer, "[{GREEN}Trce{GRAY}] "),
|
||||||
|
}?;
|
||||||
|
|
||||||
|
// write the actual message
|
||||||
|
use std::fmt::Write;
|
||||||
|
let mut splicer = LineSplicer(buffer);
|
||||||
|
_=write!(splicer, "{}", record.args());
|
||||||
|
|
||||||
|
// finalize
|
||||||
|
write!(buffer, "\n")?;
|
||||||
|
buffer.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LineSplicer<'a>(&'a mut dyn Write);
|
||||||
|
impl<'a> std::fmt::Write for LineSplicer<'a> {
|
||||||
|
fn write_str(&mut self, s: &str) -> std::fmt::Result {
|
||||||
|
for (p, part) in s.split("\n").with_position() {
|
||||||
|
match p {
|
||||||
|
Position::First => write!(self.0, "\n {GRAY}╷{RESET} "),
|
||||||
|
Position::Middle => write!(self.0, "\n {GRAY}│{RESET} "),
|
||||||
|
Position::Last => write!(self.0, "\n {GRAY}╵{RESET} "),
|
||||||
|
Position::Only => write!(self.0, "{RESET}"),
|
||||||
|
}.map_err(|_|std::fmt::Error)?;
|
||||||
|
self.0.write(part.as_bytes())
|
||||||
|
.map_err(|_|std::fmt::Error)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//╶───╴Utility╶──────────────────────────────────────────────────────────────╴
|
||||||
|
|
||||||
|
const RESET : &str = "\x1B[0m";
|
||||||
|
const RED : &str = "\x1B[0;31m";
|
||||||
|
const GREEN : &str = "\x1B[0;32m";
|
||||||
|
const YELLOW : &str = "\x1B[0;33m";
|
||||||
|
const BLUE : &str = "\x1B[0;34m";
|
||||||
|
const PURPLE : &str = "\x1B[0;35m";
|
||||||
|
const GRAY : &str = "\x1B[0;90m";
|
||||||
|
|
||||||
|
/// truncate strings to a maximum length, right aligned
|
||||||
|
fn rtrunc(string: &str, max_len: usize) -> &str {
|
||||||
|
let len = string.chars().count();
|
||||||
|
if len <= max_len { return string; }
|
||||||
|
|
||||||
|
let crop = string.char_indices()
|
||||||
|
.nth(len - max_len)
|
||||||
|
.unwrap_or((0,'\0')).0;
|
||||||
|
&string[crop..]
|
||||||
|
}
|
||||||
14
src/main.rs
Normal file
14
src/main.rs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
include!("lib.rs");
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
_=init_with_level(LevelFilter::Trace);
|
||||||
|
trace!("trace");
|
||||||
|
debug!("debug");
|
||||||
|
info!("info");
|
||||||
|
warn!("warn");
|
||||||
|
error!("error");
|
||||||
|
|
||||||
|
info!("multiline\ndemo");
|
||||||
|
info!("one\ntwo\nthree\nfour");
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user