use std::sync::{LazyLock, Mutex};
use serde_json::{json, Value};
use super::pretty::format_pretty_line;
use super::{active_logging_config, LogEvent};
static JSON_CAPTURE: LazyLock<Mutex<Option<Vec<u8>>>> = LazyLock::new(|| Mutex::new(None));
pub fn enable_json_capture_for_tests() {
*crate::_lock::lock(&JSON_CAPTURE) = Some(Vec::new());
}
pub fn take_json_capture() -> Vec<u8> {
crate::_lock::lock(&JSON_CAPTURE).take().unwrap_or_default()
}
static CONSOLE_CAPTURE: LazyLock<Mutex<Option<Vec<u8>>>> = LazyLock::new(|| Mutex::new(None));
pub fn enable_console_capture_for_tests() {
*crate::_lock::lock(&CONSOLE_CAPTURE) = Some(Vec::new());
}
pub fn take_console_capture() -> Vec<u8> {
crate::_lock::lock(&CONSOLE_CAPTURE)
.take()
.unwrap_or_default()
}
static PRETTY_CAPTURE: LazyLock<Mutex<Option<Vec<u8>>>> = LazyLock::new(|| Mutex::new(None));
pub fn enable_pretty_capture_for_tests() {
*crate::_lock::lock(&PRETTY_CAPTURE) = Some(Vec::new());
}
pub fn take_pretty_capture() -> Vec<u8> {
crate::_lock::lock(&PRETTY_CAPTURE)
.take()
.unwrap_or_default()
}
pub(crate) fn now_iso8601() -> String {
use std::time::{SystemTime, UNIX_EPOCH};
let d = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default();
iso8601_from_unix_parts(d.as_secs(), d.subsec_millis())
}
fn iso8601_from_unix_parts(ts: u64, ms: u32) -> String {
let z: i64 = (ts / 86_400) as i64 + 719_468;
let era: i64 = z / 146_097;
let doe: i64 = z - era * 146_097;
let yoe: i64 = (doe - doe / 1_460 + doe / 36_524 - doe / 146_096) / 365;
let y: i64 = yoe + era * 400;
let doy: i64 = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp: i64 = (5 * doy + 2) / 153;
let day: i64 = doy - (153 * mp + 2) / 5 + 1;
let month: i64 = if mp < 10 { mp + 3 } else { mp - 9 };
let year: i64 = if month <= 2 { y + 1 } else { y };
let sod = ts % 86_400;
let hour = sod / 3_600;
let min = (sod % 3_600) / 60;
let sec = sod % 60;
format!("{year:04}-{month:02}-{day:02}T{hour:02}:{min:02}:{sec:02}.{ms:03}Z")
}
fn emit_json_line(event: &LogEvent, include_timestamp: bool) {
let mut record = if include_timestamp {
json!({
"message": event.message,
"level": event.level,
"timestamp": now_iso8601(),
})
} else {
json!({
"message": event.message,
"level": event.level,
})
};
let obj = record.as_object_mut().expect("json object");
for (k, v) in &event.context {
obj.insert(k.clone(), v.clone());
}
if let Some(tid) = &event.trace_id {
obj.insert("trace_id".to_string(), Value::String(tid.clone()));
}
if let Some(sid) = &event.span_id {
obj.insert("span_id".to_string(), Value::String(sid.clone()));
}
obj.insert(
"logger_name".to_string(),
Value::String(event.target.clone()),
);
let line = serde_json::to_string(obj).unwrap_or_default();
let mut capture = crate::_lock::lock(&JSON_CAPTURE);
if let Some(buf) = capture.as_mut() {
buf.extend_from_slice(line.as_bytes());
buf.push(b'\n');
} else {
eprintln!("{line}");
}
}
pub(super) fn emit_if_json(event: &LogEvent) {
let logging = active_logging_config();
if logging.fmt.eq_ignore_ascii_case("json") {
emit_json_line(event, logging.include_timestamp);
}
}
fn format_console_line(event: &LogEvent, include_timestamp: bool) -> String {
let mut s = String::new();
if include_timestamp {
s.push_str(&now_iso8601());
s.push_str(" ");
}
s.push_str(&format!("{:<5}", event.level));
s.push_str(" ");
s.push_str(&event.target);
s.push_str(" ");
s.push_str(&event.message);
for (k, v) in &event.context {
s.push_str(&format!(" {k}={v}"));
}
s
}
pub(super) fn emit_if_console(event: &LogEvent) {
let logging = active_logging_config();
if logging.fmt.eq_ignore_ascii_case("json") || logging.fmt.eq_ignore_ascii_case("pretty") {
return;
}
let line = format_console_line(event, logging.include_timestamp);
let mut capture = crate::_lock::lock(&CONSOLE_CAPTURE);
if let Some(buf) = capture.as_mut() {
buf.extend_from_slice(line.as_bytes());
buf.push(b'\n');
} else {
eprintln!("{line}");
}
}
pub(super) fn emit_if_pretty(event: &LogEvent) {
let logging = active_logging_config();
if !logging.fmt.eq_ignore_ascii_case("pretty") {
return;
}
let line = format_pretty_line(event, &logging);
let mut capture = crate::_lock::lock(&PRETTY_CAPTURE);
if let Some(buf) = capture.as_mut() {
buf.extend_from_slice(line.as_bytes());
buf.push(b'\n');
} else {
eprintln!("{line}");
}
}
#[cfg(feature = "otel")]
pub(super) fn emit_if_otel(event: &LogEvent) {
crate::otel::logs::emit_log(event);
}
#[cfg(not(feature = "otel"))]
pub(super) fn emit_if_otel(_event: &LogEvent) {}
#[cfg(test)]
#[path = "emit_tests.rs"]
mod tests;