use ntime::Format;
use std::io;
use crate::attributes::value::Value;
use crate::attributes::{Map, MetadataField, MetadataImpl};
use crate::console::Color;
use crate::constant::DEFAULT_LOG_DELIMITER_STRING;
use crate::format::compact;
use crate::format::{FormatterConfig, OutputFormat};
use crate::level::Level;
use crate::sink::LogUpdate;
pub fn default_format_config() -> FormatterConfig {
FormatterConfig {
format: OutputFormat::ColorCompact,
time_format: ntime::Format::LocalMillisDateTime,
delimiter: DEFAULT_LOG_DELIMITER_STRING.into(),
}
}
fn write_value<T: io::Write>(out: &mut T, attrs: &Map, val: &Value) -> io::Result<()> {
compact::write_value(out, attrs, val)
}
pub fn write<T: io::Write>(out: &mut T, time_format: &Format, update: &LogUpdate) -> io::Result<()> {
out.write(Color::White.to_escape_str().as_bytes())?;
update.when().write(out, time_format)?;
write!(
out,
" {level_color}{level} {msg_color}{msg}",
level_color = update.level().color().to_escape_str(),
level = update.level().as_short_str(),
msg_color = (if Level::Debug.includes(&update.level()) { Color::White } else { Color::BrightWhite }).to_escape_str(),
msg = update.message(),
)?;
for (key, val, meta) in update.attributes().iter() {
write!(
out,
" {key_color}{key}={val_color}",
key_color = (if meta.get(MetadataField::Ephemeral) { Color::Cyan } else { Color::BrightCyan }).to_escape_str(),
val_color = (if meta.get(MetadataField::Error) { Color::BrightRed } else { Color::White }).to_escape_str(),
)?;
write_value(out, update.attributes(), &val)?;
}
write!(out, "{color_close}", color_close = Color::Default.to_escape_str())?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::attributes::{Scalar, Value};
use crate::console;
use crate::sink::PartialLogUpdate;
use ntime::Timestamp;
#[test]
fn serialize_value() {
for tc in [
(Value::from(true), "true"),
(Value::from(89801234567890123 as usize), "0x13f09bf3ecf84cb"),
(
Value::from(&[
Scalar::from(false),
Scalar::from("abcd 1234"),
Scalar::from(-123),
Scalar::from(89801234567890123 as usize),
Scalar::from(5678901.2345),
]),
"[false, \"abcd 1234\", -123, 0x13f09bf3ecf84cb, 5678901.2345]",
),
(
Value::from((
&[Scalar::from("key_a"), Scalar::from("key_b"), Scalar::from("key_c")],
&[Scalar::from(false), Scalar::from(-123), Scalar::from(456.789)],
)),
"{\"key_a\": false, \"key_b\": -123, \"key_c\": 456.789}",
),
] {
let (v, want): (Value, &str) = tc;
let mut out = Vec::new();
let attrs = Map::new();
assert!(write_value(&mut out, &attrs, &v).is_ok());
assert_eq!(String::from_utf8(out).unwrap(), want);
}
}
#[test]
fn serialize_color() {
let mut attrs = Map::new();
attrs.insert("an_int", Value::from(123 as i32));
attrs.insert_ephemeral("a_float", Value::from(-456.789));
attrs.insert("some_string", Value::from("hi there!"));
attrs.insert_ephemeral("a_set", Value::from(&[Scalar::from(349834934 as usize), Scalar::from(true)]));
let pupdate = PartialLogUpdate::new(
Timestamp::from_utc_date(2026, 04, 12, 17, 56, 39, 123, 456).expect("failed to initialize timestamp"),
Level::Warning,
1,
"test compact update".into(),
);
let update = LogUpdate::from((&pupdate, &attrs));
let time_format = &ntime::Format::TimestampNanoseconds;
for tc in [
(
false,
"1776016599123000456 WRN test compact update an_int=123 a_float=-456.789 some_string=\"hi there!\" a_set=[0x14da0eb6, true]",
),
(
true,
"\u{1b}[37m1776016599123000456 \u{1b}[33mWRN \u{1b}[97mtest compact update \u{1b}[96man_int=\u{1b}[37m123 \u{1b}[36ma_float=\u{1b}[37m-456.789 \u{1b}[96msome_string=\u{1b}[37m\"hi there!\" \u{1b}[36ma_set=\u{1b}[37m[0x14da0eb6, true]\u{1b}[0m",
),
] {
let (enable, want) = tc;
let mut out = Vec::new();
console::colorterm_force(enable);
assert!(write(&mut out, time_format, &update).is_ok());
console::colorterm_unforce();
assert_eq!(String::from_utf8(out).unwrap(), String::from(want));
}
}
}