first commit, mlog (logfmt)
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
3
src/lib.rs
Normal file
3
src/lib.rs
Normal file
@ -0,0 +1,3 @@
|
||||
mod logger;
|
||||
|
||||
pub use logger::LoggerBuilder;
|
171
src/logger.rs
Normal file
171
src/logger.rs
Normal file
@ -0,0 +1,171 @@
|
||||
use chrono::Local;
|
||||
use env_logger::Builder;
|
||||
use log::kv::{Error, Key, Value, VisitSource};
|
||||
use std::io::Write;
|
||||
|
||||
/// Struct for visiting key-value pairs and formatting them as logfmt.
|
||||
struct LogFmtVisitor<'a> {
|
||||
log: &'a mut String,
|
||||
}
|
||||
|
||||
impl<'kvs> VisitSource<'kvs> for LogFmtVisitor<'kvs> {
|
||||
fn visit_pair(&mut self, key: Key<'kvs>, value: Value<'kvs>) -> Result<(), Error> {
|
||||
self.log.push_str(&format!(" {}=\"{}\"", key, value));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder for configuring the logfmt logger.
|
||||
pub struct LoggerBuilder {
|
||||
skip_messages: Vec<String>,
|
||||
include_date: bool,
|
||||
include_target: bool,
|
||||
}
|
||||
|
||||
impl LoggerBuilder {
|
||||
/// Creates a new `LoggerBuilder` with default settings.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
skip_messages: vec![],
|
||||
include_date: true,
|
||||
include_target: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a message pattern to skip in the logs.
|
||||
pub fn skip_message(mut self, message: &str) -> Self {
|
||||
self.skip_messages.push(message.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Enables or disables the inclusion of the date in logs.
|
||||
pub fn include_date(mut self, include: bool) -> Self {
|
||||
self.include_date = include;
|
||||
self
|
||||
}
|
||||
|
||||
/// Enables or disables the inclusion of the target in logs.
|
||||
pub fn include_target(mut self, include: bool) -> Self {
|
||||
self.include_target = include;
|
||||
self
|
||||
}
|
||||
|
||||
/// Formats a log record into a string based on the configuration.
|
||||
fn format_log(&self, record: &log::Record) -> Option<String> {
|
||||
let msg = record.args().to_string();
|
||||
|
||||
if self.skip_messages.iter().any(|skip| msg.contains(skip)) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut log = String::new();
|
||||
|
||||
if self.include_date {
|
||||
log.push_str(&format!(
|
||||
"date=\"{}\" ",
|
||||
Local::now().format("%Y-%m-%d %H:%M:%S")
|
||||
));
|
||||
}
|
||||
|
||||
log.push_str(&format!(
|
||||
"level=\"{}\" msg=\"{}\"",
|
||||
record.level().as_str().to_lowercase(),
|
||||
msg
|
||||
));
|
||||
|
||||
let _ = record
|
||||
.key_values()
|
||||
.visit(&mut LogFmtVisitor { log: &mut log });
|
||||
|
||||
if self.include_target {
|
||||
log.push_str(&format!(" target=\"{}\"", record.target()));
|
||||
}
|
||||
|
||||
Some(log)
|
||||
}
|
||||
|
||||
/// Initializes the logger with the configured settings.
|
||||
pub fn init(self) {
|
||||
Builder::from_default_env()
|
||||
.format(move |buf, record| {
|
||||
if let Some(log) = self.format_log(record) {
|
||||
writeln!(buf, "{}", log)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
.init();
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LoggerBuilder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::LoggerBuilder;
|
||||
use chrono::Local;
|
||||
use log::{Level, Record};
|
||||
|
||||
#[test]
|
||||
fn test_format_log_with_date_and_target() {
|
||||
let builder = LoggerBuilder::new().include_date(true).include_target(true);
|
||||
|
||||
let record = Record::builder()
|
||||
.args(format_args!("Test message"))
|
||||
.level(Level::Info)
|
||||
.target("my_target")
|
||||
.build();
|
||||
|
||||
let formatted = builder.format_log(&record).unwrap();
|
||||
|
||||
let expected_date = Local::now().format("%Y-%m-%d").to_string();
|
||||
assert!(formatted.contains(&format!("date=\"{}", expected_date)));
|
||||
assert!(formatted.contains("level=\"info\""));
|
||||
assert!(formatted.contains("msg=\"Test message\""));
|
||||
assert!(formatted.contains("target=\"my_target\""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_log_without_date_and_target() {
|
||||
let builder = LoggerBuilder::new()
|
||||
.include_date(false)
|
||||
.include_target(false);
|
||||
|
||||
let record = Record::builder()
|
||||
.args(format_args!("Another test message"))
|
||||
.level(Level::Error)
|
||||
.target("no_target")
|
||||
.build();
|
||||
|
||||
let formatted = builder.format_log(&record).unwrap();
|
||||
|
||||
assert!(!formatted.contains("date="));
|
||||
assert!(formatted.contains("level=\"error\""));
|
||||
assert!(formatted.contains("msg=\"Another test message\""));
|
||||
assert!(!formatted.contains("target=\"no_target\""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_log_skip_message() {
|
||||
let builder = LoggerBuilder::new().skip_message("skip_this");
|
||||
|
||||
let record_skip = Record::builder()
|
||||
.args(format_args!("skip_this: This should not appear"))
|
||||
.level(Level::Warn)
|
||||
.target("skipped_target")
|
||||
.build();
|
||||
|
||||
let record_show = Record::builder()
|
||||
.args(format_args!("This should appear"))
|
||||
.level(Level::Warn)
|
||||
.target("shown_target")
|
||||
.build();
|
||||
|
||||
assert!(builder.format_log(&record_skip).is_none());
|
||||
assert!(builder.format_log(&record_show).is_some());
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user