sozu_command_lib/logging/
display.rs1use std::fmt;
2
3use crate::{
4 AsStr,
5 logging::{
6 EndpointRecord, FullTags, LogContext, LogDuration, LogLevel, LogMessage, LoggerBackend,
7 Rfc3339Time,
8 },
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[;36mDEBUG",
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!(f, "[{} ", self.session_id)?;
105 match self.request_id {
106 Some(id) => write!(f, "{id}")?,
107 None => f.write_str("-")?,
108 }
109 write!(
110 f,
111 " {} {}]",
112 self.cluster_id.unwrap_or("-"),
113 self.backend_id.unwrap_or("-")
114 )
115 }
116}
117
118impl fmt::Display for EndpointRecord<'_> {
119 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
120 match self {
121 Self::Http {
122 method,
123 authority,
124 path,
125 status,
126 ..
127 } => write!(
128 f,
129 "{} {} {} {}",
130 authority.as_str_or("-"),
131 method.as_str_or("-"),
132 path.as_str_or("-"),
133 display_status(*status, f.alternate()),
134 ),
135 Self::Tcp => {
136 write!(f, "-")
137 }
138 }
139 }
140}
141
142fn display_status(status: Option<u16>, pretty: bool) -> String {
143 match (status, pretty) {
144 (Some(s @ 200..=299), true) => format!("\x1b[32m{s}"),
145 (Some(s @ 300..=399), true) => format!("\x1b[34m{s}"),
146 (Some(s @ 400..=499), true) => format!("\x1b[33m{s}"),
147 (Some(s @ 500..=599), true) => format!("\x1b[31m{s}"),
148 (Some(s), _) => s.to_string(),
149 (None, _) => "-".to_string(),
150 }
151}
152
153impl fmt::Display for FullTags<'_> {
154 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155 match (self.concatenated, self.user_agent) {
156 (None, None) => Ok(()),
157 (Some(tags), None) => write!(f, "{tags}"),
158 (Some(tags), Some(ua)) if !tags.is_empty() => {
159 write!(f, "{tags}, user-agent={}", prepare_user_agent(ua))
160 }
161 (_, Some(ua)) => write!(f, "user-agent={}", prepare_user_agent(ua)),
162 }
163 }
164}
165
166fn prepare_user_agent(user_agent: &str) -> String {
167 user_agent
168 .replace(' ', "_")
169 .replace('[', "{")
170 .replace(']', "}")
171}
172
173#[cfg(test)]
174mod tests {
175 use rusty_ulid::Ulid;
176
177 use crate::logging::LogContext;
178
179 #[test]
180 fn log_context_display_all_fields_present() {
181 let session = Ulid::from(0x01_23_45_67_89_AB_CD_EF_FE_DC_BA_98_76_54_32_10_u128);
182 let request = Ulid::from(0x01_23_45_67_89_AB_CD_EF_FE_DC_BA_98_76_54_32_11_u128);
183 let ctx = LogContext {
184 session_id: session,
185 request_id: Some(request),
186 cluster_id: Some("cluster-abc"),
187 backend_id: Some("backend-1"),
188 };
189 let rendered = format!("{ctx}");
190 assert_eq!(
191 rendered,
192 format!("[{session} {request} cluster-abc backend-1]")
193 );
194 }
195
196 #[test]
197 fn log_context_display_dashes_when_missing() {
198 let session = Ulid::from(0xABCDu128);
199 let ctx = LogContext {
200 session_id: session,
201 request_id: None,
202 cluster_id: None,
203 backend_id: None,
204 };
205 assert_eq!(format!("{ctx}"), format!("[{session} - - -]"));
206 }
207
208 #[test]
209 fn log_context_display_partial() {
210 let session = Ulid::from(0x42u128);
211 let request = Ulid::from(0x43u128);
212 let ctx = LogContext {
213 session_id: session,
214 request_id: Some(request),
215 cluster_id: None,
216 backend_id: Some("backend-2"),
217 };
218 assert_eq!(
219 format!("{ctx}"),
220 format!("[{session} {request} - backend-2]")
221 );
222 }
223}