sozu_command_lib/logging/
display.rs

1use std::fmt;
2
3use crate::{
4    logging::{
5        EndpointRecord, FullTags, LogContext, LogDuration, LogLevel, LogMessage, LoggerBackend,
6        Rfc3339Time,
7    },
8    AsStr,
9};
10
11impl LogLevel {
12    pub const fn as_str(&self, access: bool, colored: bool) -> &'static str {
13        match (self, access, colored) {
14            (LogLevel::Error, false, false) => "ERROR",
15            (LogLevel::Warn, false, false) => "WARN ",
16            (LogLevel::Info, false, false) => "INFO ",
17            (LogLevel::Debug, false, false) => "DEBUG",
18            (LogLevel::Trace, false, false) => "TRACE",
19
20            (LogLevel::Error, false, true) => "\x1b[;31;1mERROR",
21            (LogLevel::Warn, false, true) => "\x1b[;33;1mWARN ",
22            (LogLevel::Info, false, true) => "\x1b[;32;1mINFO ",
23            (LogLevel::Debug, false, true) => "\x1b[;34mDEBUG",
24            (LogLevel::Trace, false, true) => "\x1b[;90mTRACE",
25
26            (LogLevel::Error, true, false) => "ERROR-ACCESS",
27            (LogLevel::Info, true, false) => "INFO-ACCESS ",
28            (_, true, false) => "???",
29
30            (LogLevel::Error, true, true) => "\x1b[;35;1mERROR-ACCESS",
31            (LogLevel::Info, true, true) => "\x1b[;35;1mINFO-ACCESS ",
32            (_, true, true) => "\x1b[;35;1m???",
33        }
34    }
35}
36
37impl AsRef<str> for LoggerBackend {
38    fn as_ref(&self) -> &str {
39        match self {
40            LoggerBackend::Stdout(_) => "stdout",
41            LoggerBackend::Unix(_) => "UNIX socket",
42            LoggerBackend::Udp(_, _) => "UDP socket",
43            LoggerBackend::Tcp(_) => "TCP socket",
44            LoggerBackend::File(_) => "file",
45        }
46    }
47}
48
49impl fmt::Display for Rfc3339Time {
50    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
51        let t = self.inner;
52        write!(
53            f,
54            "{}-{:02}-{:02}T{:02}:{:02}:{:02}.{:06}Z",
55            t.year(),
56            t.month() as u8,
57            t.day(),
58            t.hour(),
59            t.minute(),
60            t.second(),
61            t.microsecond()
62        )
63    }
64}
65
66impl fmt::Display for LogMessage<'_> {
67    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68        match self.0 {
69            Some(message) => write!(f, " | {message}"),
70            None => Ok(()),
71        }
72    }
73}
74
75impl fmt::Display for LogDuration {
76    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
77        match self.0 {
78            None => write!(f, "-"),
79            Some(duration) => {
80                let secs = duration.as_secs();
81                if secs >= 10 {
82                    return write!(f, "{secs}s");
83                }
84
85                let ms = duration.as_millis();
86                if ms < 10 {
87                    let us = duration.as_micros();
88                    if us >= 10 {
89                        return write!(f, "{us}μs");
90                    }
91
92                    let ns = duration.as_nanos();
93                    return write!(f, "{ns}ns");
94                }
95
96                write!(f, "{ms}ms")
97            }
98        }
99    }
100}
101
102impl fmt::Display for LogContext<'_> {
103    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
104        write!(
105            f,
106            "[{} {} {}]",
107            self.request_id,
108            self.cluster_id.unwrap_or("-"),
109            self.backend_id.unwrap_or("-")
110        )
111    }
112}
113
114impl fmt::Display for EndpointRecord<'_> {
115    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
116        match self {
117            Self::Http {
118                method,
119                authority,
120                path,
121                status,
122                ..
123            } => write!(
124                f,
125                "{} {} {} {}",
126                authority.as_str_or("-"),
127                method.as_str_or("-"),
128                path.as_str_or("-"),
129                display_status(*status, f.alternate()),
130            ),
131            Self::Tcp => {
132                write!(f, "-")
133            }
134        }
135    }
136}
137
138fn display_status(status: Option<u16>, pretty: bool) -> String {
139    match (status, pretty) {
140        (Some(s @ 200..=299), true) => format!("\x1b[32m{s}"),
141        (Some(s @ 300..=399), true) => format!("\x1b[34m{s}"),
142        (Some(s @ 400..=499), true) => format!("\x1b[33m{s}"),
143        (Some(s @ 500..=599), true) => format!("\x1b[31m{s}"),
144        (Some(s), _) => s.to_string(),
145        (None, _) => "-".to_string(),
146    }
147}
148
149impl<'a> fmt::Display for FullTags<'a> {
150    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151        match (self.concatenated, self.user_agent) {
152            (None, None) => Ok(()),
153            (Some(tags), None) => write!(f, "{tags}"),
154            (Some(tags), Some(ua)) if !tags.is_empty() => {
155                write!(f, "{tags}, user-agent={}", prepare_user_agent(ua))
156            }
157            (_, Some(ua)) => write!(f, "user-agent={}", prepare_user_agent(ua)),
158        }
159    }
160}
161
162fn prepare_user_agent(user_agent: &str) -> String {
163    user_agent
164        .replace(' ', "_")
165        .replace('[', "{")
166        .replace(']', "}")
167}