use std::fmt;
use crate::{
AsStr,
logging::{
EndpointRecord, FullTags, LogContext, LogDuration, LogLevel, LogMessage, LoggerBackend,
Rfc3339Time,
},
};
impl LogLevel {
pub const fn as_str(&self, access: bool, colored: bool) -> &'static str {
match (self, access, colored) {
(LogLevel::Error, false, false) => "ERROR",
(LogLevel::Warn, false, false) => "WARN ",
(LogLevel::Info, false, false) => "INFO ",
(LogLevel::Debug, false, false) => "DEBUG",
(LogLevel::Trace, false, false) => "TRACE",
(LogLevel::Error, false, true) => "\x1b[;31;1mERROR",
(LogLevel::Warn, false, true) => "\x1b[;33;1mWARN ",
(LogLevel::Info, false, true) => "\x1b[;32;1mINFO ",
(LogLevel::Debug, false, true) => "\x1b[;36mDEBUG",
(LogLevel::Trace, false, true) => "\x1b[;90mTRACE",
(LogLevel::Error, true, false) => "ERROR-ACCESS",
(LogLevel::Info, true, false) => "INFO-ACCESS ",
(_, true, false) => "???",
(LogLevel::Error, true, true) => "\x1b[;35;1mERROR-ACCESS",
(LogLevel::Info, true, true) => "\x1b[;35;1mINFO-ACCESS ",
(_, true, true) => "\x1b[;35;1m???",
}
}
}
impl AsRef<str> for LoggerBackend {
fn as_ref(&self) -> &str {
match self {
LoggerBackend::Stdout(_) => "stdout",
LoggerBackend::Unix(_) => "UNIX socket",
LoggerBackend::Udp(_, _) => "UDP socket",
LoggerBackend::Tcp(_) => "TCP socket",
LoggerBackend::File(_) => "file",
}
}
}
impl fmt::Display for Rfc3339Time {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
let t = self.inner;
write!(
f,
"{}-{:02}-{:02}T{:02}:{:02}:{:02}.{:06}Z",
t.year(),
t.month() as u8,
t.day(),
t.hour(),
t.minute(),
t.second(),
t.microsecond()
)
}
}
impl fmt::Display for LogMessage<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 {
Some(message) => write!(f, " | {message}"),
None => Ok(()),
}
}
}
impl fmt::Display for LogDuration {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.0 {
None => write!(f, "-"),
Some(duration) => {
let secs = duration.as_secs();
if secs >= 10 {
return write!(f, "{secs}s");
}
let ms = duration.as_millis();
if ms < 10 {
let us = duration.as_micros();
if us >= 10 {
return write!(f, "{us}μs");
}
let ns = duration.as_nanos();
return write!(f, "{ns}ns");
}
write!(f, "{ms}ms")
}
}
}
}
impl fmt::Display for LogContext<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "[{} ", self.session_id)?;
match self.request_id {
Some(id) => write!(f, "{id}")?,
None => f.write_str("-")?,
}
write!(
f,
" {} {}]",
self.cluster_id.unwrap_or("-"),
self.backend_id.unwrap_or("-")
)
}
}
impl fmt::Display for EndpointRecord<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Http {
method,
authority,
path,
status,
..
} => write!(
f,
"{} {} {} {}",
authority.as_str_or("-"),
method.as_str_or("-"),
path.as_str_or("-"),
display_status(*status, f.alternate()),
),
Self::Tcp => {
write!(f, "-")
}
}
}
}
fn display_status(status: Option<u16>, pretty: bool) -> String {
match (status, pretty) {
(Some(s @ 200..=299), true) => format!("\x1b[32m{s}"),
(Some(s @ 300..=399), true) => format!("\x1b[34m{s}"),
(Some(s @ 400..=499), true) => format!("\x1b[33m{s}"),
(Some(s @ 500..=599), true) => format!("\x1b[31m{s}"),
(Some(s), _) => s.to_string(),
(None, _) => "-".to_string(),
}
}
impl fmt::Display for FullTags<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match (self.concatenated, self.user_agent) {
(None, None) => Ok(()),
(Some(tags), None) => write!(f, "{tags}"),
(Some(tags), Some(ua)) if !tags.is_empty() => {
write!(f, "{tags}, user-agent={}", prepare_user_agent(ua))
}
(_, Some(ua)) => write!(f, "user-agent={}", prepare_user_agent(ua)),
}
}
}
fn prepare_user_agent(user_agent: &str) -> String {
user_agent
.replace(' ', "_")
.replace('[', "{")
.replace(']', "}")
}
#[cfg(test)]
mod tests {
use rusty_ulid::Ulid;
use crate::logging::LogContext;
#[test]
fn log_context_display_all_fields_present() {
let session = Ulid::from(0x01_23_45_67_89_AB_CD_EF_FE_DC_BA_98_76_54_32_10_u128);
let request = Ulid::from(0x01_23_45_67_89_AB_CD_EF_FE_DC_BA_98_76_54_32_11_u128);
let ctx = LogContext {
session_id: session,
request_id: Some(request),
cluster_id: Some("cluster-abc"),
backend_id: Some("backend-1"),
};
let rendered = format!("{ctx}");
assert_eq!(
rendered,
format!("[{session} {request} cluster-abc backend-1]")
);
}
#[test]
fn log_context_display_dashes_when_missing() {
let session = Ulid::from(0xABCDu128);
let ctx = LogContext {
session_id: session,
request_id: None,
cluster_id: None,
backend_id: None,
};
assert_eq!(format!("{ctx}"), format!("[{session} - - -]"));
}
#[test]
fn log_context_display_partial() {
let session = Ulid::from(0x42u128);
let request = Ulid::from(0x43u128);
let ctx = LogContext {
session_id: session,
request_id: Some(request),
cluster_id: None,
backend_id: Some("backend-2"),
};
assert_eq!(
format!("{ctx}"),
format!("[{session} {request} - backend-2]")
);
}
}