use std::collections::BTreeMap;
use rusm_logfmt as fmt;
use crate::exit::ExitReason;
use crate::pid::Pid;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Debug)]
pub enum LogLevel {
#[default]
Off,
Error,
Warn,
Info,
Debug,
}
impl LogLevel {
pub fn parse(s: &str) -> Self {
match s.trim().to_ascii_lowercase().as_str() {
"error" => Self::Error,
"warn" | "warning" => Self::Warn,
"info" => Self::Info,
"debug" | "trace" => Self::Debug,
_ => Self::Off,
}
}
pub fn for_exit(reason: ExitReason) -> Self {
match reason {
ExitReason::Crashed => Self::Error,
ExitReason::Killed | ExitReason::NoProc => Self::Warn,
ExitReason::Normal => Self::Info,
}
}
fn colour(self) -> &'static str {
match self {
Self::Error => fmt::ERROR,
Self::Warn => fmt::WARN,
Self::Info => fmt::OK,
_ => fmt::LEVEL,
}
}
}
fn ident(label: &str, pid: Pid) -> String {
format!(
"{}{}",
fmt::paint(fmt::BOLD, label),
fmt::paint(fmt::DIM, &format!("#{}", pid.0))
)
}
pub fn log_spawn(pid: Pid, label: &str, detail: &str) {
eprintln!(
"{}",
fmt::platform_line(
fmt::LEVEL, "spawn",
&format!("{} {}", ident(label, pid), fmt::paint(fmt::DIM, detail)),
)
);
}
pub fn log_exit(pid: Pid, label: &str, reason: ExitReason) {
let code = LogLevel::for_exit(reason).colour();
eprintln!(
"{}",
fmt::platform_line(
code,
"exit",
&format!(
"{} {}",
ident(label, pid),
fmt::paint(code, &format!("{reason:?}").to_lowercase())
),
)
);
}
pub fn log_census(components: &BTreeMap<String, u64>, tags: &BTreeMap<String, u64>) {
let entry = |name: &str, n: &u64, name_colour: &str| {
format!(
"{}{}{}",
fmt::paint(name_colour, name),
fmt::paint(fmt::DIM, "="),
fmt::paint(fmt::LEVEL, &n.to_string())
)
};
let mut entries: Vec<String> = components
.iter()
.map(|(name, n)| entry(name, n, fmt::BOLD))
.collect();
entries.extend(tags.iter().map(|(name, n)| entry(name, n, fmt::TAG)));
let body = if entries.is_empty() {
fmt::paint(fmt::DIM, "(none)")
} else {
entries.join(" ")
};
eprintln!("{}", fmt::platform_line(fmt::LEVEL, "census", &body));
}
pub fn log_kill(pid: Pid) {
eprintln!(
"{}",
fmt::platform_line(
fmt::WARN,
"kill",
&fmt::paint(fmt::DIM, &format!("#{}", pid.0)),
)
);
}
pub fn log_kill_tag(tag: &str, killed: usize) {
let body = format!(
"{} {} {}",
fmt::paint(fmt::TAG, tag),
fmt::paint(fmt::DIM, "→"),
fmt::paint(fmt::LEVEL, &killed.to_string()),
);
eprintln!("{}", fmt::platform_line(fmt::WARN, "kill", &body));
}
#[cfg(test)]
mod tests {
use super::LogLevel;
#[test]
fn parse_maps_known_levels_and_quiets_the_rest() {
assert_eq!(LogLevel::parse("debug"), LogLevel::Debug);
assert_eq!(LogLevel::parse("INFO"), LogLevel::Info);
assert_eq!(LogLevel::parse("warning"), LogLevel::Warn);
assert_eq!(LogLevel::parse("error"), LogLevel::Error);
assert_eq!(LogLevel::parse(""), LogLevel::Off);
assert_eq!(LogLevel::parse("loud"), LogLevel::Off);
}
#[test]
fn levels_are_ordered_off_to_debug() {
assert!(LogLevel::Off < LogLevel::Error);
assert!(LogLevel::Error < LogLevel::Warn);
assert!(LogLevel::Warn < LogLevel::Info);
assert!(LogLevel::Info < LogLevel::Debug);
}
}