use std::fmt::Write;
use std::time::{SystemTime, UNIX_EPOCH};
use crate::level::LogLevel;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum OutputFormat {
Text,
Json,
Custom(String),
}
pub struct Formatter {
format: OutputFormat,
}
impl Formatter {
pub fn new() -> Self {
Formatter {
format: OutputFormat::Text,
}
}
pub fn with_format(format: OutputFormat) -> Self {
Formatter { format }
}
pub fn with_pattern(pattern: String) -> Self {
Formatter {
format: OutputFormat::Custom(pattern),
}
}
pub fn format(&self, level: LogLevel, message: &str) -> String {
match self.format {
OutputFormat::Text => self.format_text(level, message),
OutputFormat::Json => self.format_json(level, message),
OutputFormat::Custom(ref pattern) => self.format_custom(pattern, level, message),
}
}
fn format_text(&self, level: LogLevel, message: &str) -> String {
format!(
"[{}] [{}] {}",
Self::get_timestamp(),
level,
message
)
}
pub fn escape_json(s: &str) -> String {
let mut result = String::with_capacity(s.len());
for c in s.chars() {
match c {
'"' => result.push_str("\\\""),
'\\' => result.push_str("\\\\"),
'\n' => result.push_str("\\n"),
'\r' => result.push_str("\\r"),
'\t' => result.push_str("\\t"),
c if c.is_control() => {
write!(result, "\\u{:04x}", c as u32).unwrap();
}
c => result.push(c),
}
}
result
}
fn format_json(&self, level: LogLevel, message: &str) -> String {
format!(
r#"{{"time":"{}","level":"{}","message":"{}"}}"#,
Self::get_timestamp(),
Self::escape_json(&level.to_string()),
Self::escape_json(message),
)
}
fn format_custom(&self, pattern: &str, level: LogLevel, message: &str) -> String {
let timestamp = Self::get_timestamp();
let (date, time) = timestamp.split_at(10);
pattern
.replace("%d", ×tamp)
.replace("%D", date)
.replace("%T", &time[1..])
.replace("%p", &level.to_string())
.replace("%P", level.short_name())
.replace("%m", message)
.replace("%n", "\n")
}
pub fn get_date_string() -> String {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default();
let total_seconds = now.as_secs();
let minutes = total_seconds / 60;
let hours = minutes / 60;
let days = hours / 24;
let (year, month, day) = Self::unix_days_to_date(days);
format!("{:04}{:02}{:02}", year, month, day)
}
pub fn get_timestamp() -> String {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default();
let total_seconds = now.as_secs();
let nanoseconds = now.subsec_nanos();
let minutes = total_seconds / 60;
let seconds = total_seconds % 60;
let hours = minutes / 60;
let minutes = minutes % 60;
let days = hours / 24;
let hours = hours % 24;
let (year, month, day) = Self::unix_days_to_date(days);
format!(
"{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:09}",
year, month, day, hours, minutes, seconds, nanoseconds
)
}
fn unix_days_to_date(mut days: u64) -> (u64, u64, u64) {
let mut year = 1970;
let mut month = 1;
let mut day = 1;
let days_in_month = |y: u64, m: u64| -> u64 {
match m {
1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
4 | 6 | 9 | 11 => 30,
2 => {
if y.is_multiple_of(4) && (!y.is_multiple_of(100) || y.is_multiple_of(400)) {
29
} else {
28
}
}
_ => 0,
}
};
while days > 0 {
let dim = days_in_month(year, month);
if days >= dim {
days -= dim;
month += 1;
if month > 12 {
month = 1;
year += 1;
}
} else {
day += days;
days = 0;
}
}
(year, month, day)
}
}