use super::LogLevel;
use libc::STDERR_FILENO;
fn sanitize(s: &str) -> String {
let mut out = String::with_capacity(s.len());
for ch in s.chars() {
let keep = match ch {
'\n' | '\t' => true,
'\u{20}'..='\u{7e}' => true,
'\u{80}'..='\u{9f}' => false, '\u{a0}'..='\u{10ffff}' => true, _ => false, };
if keep {
out.push(ch);
}
}
out
}
pub fn log(level: LogLevel, tag: &str, msg: &str) {
let level_str = match level {
LogLevel::Verbose => "VERBOSE",
LogLevel::Debug => "DEBUG",
LogLevel::Info => "INFO",
LogLevel::Warn => "WARN",
LogLevel::Error => "ERROR",
LogLevel::Fatal => "FATAL",
};
let line = format!("[{level_str}][{tag}] {}\n", sanitize(msg));
let bytes = line.as_bytes();
let mut written = 0;
while written < bytes.len() {
let r = unsafe {
libc::write(
STDERR_FILENO,
bytes[written..].as_ptr() as *const libc::c_void,
bytes.len() - written,
)
};
if r < 0 {
let err = std::io::Error::last_os_error();
if err.raw_os_error() == Some(libc::EINTR) {
continue;
}
break;
}
if r == 0 {
break;
}
written += r as usize;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sanitize_keeps_plain_text_and_newlines() {
assert_eq!(sanitize("hello world\n"), "hello world\n");
assert_eq!(sanitize("tab\there"), "tab\there");
}
#[test]
fn sanitize_strips_esc_and_control_chars() {
assert_eq!(sanitize("a\x1b[2Jb"), "a[2Jb");
assert_eq!(sanitize("a\x07b"), "ab");
assert_eq!(sanitize("a\rb"), "ab");
}
#[test]
fn sanitize_keeps_unicode() {
assert_eq!(sanitize("app名"), "app名");
}
}