1use super::{ClientLogFormatter, RequestStart};
7use colored::{ColoredString, Colorize};
8use size::{Base, Size};
9use std::{borrow::Cow, fmt::Display, sync::Arc, time::Instant};
10use trillium_client::{Conn, ConnExt, HeaderName, Method, Status, Version};
11
12pub fn dev_formatter(conn: &Conn, color: bool) -> impl Display + Send + 'static + use<> {
22 (
23 version,
24 " ",
25 method,
26 " ",
27 url,
28 " ",
29 status,
30 " ",
31 response_time,
32 error,
33 )
34 .format(conn, color)
35}
36
37pub fn method(conn: &Conn, _color: bool) -> Method {
39 conn.method()
40}
41
42pub fn url(conn: &Conn, _color: bool) -> String {
44 conn.url().to_string()
45}
46
47pub fn peer_addr(conn: &Conn, _color: bool) -> Cow<'static, str> {
53 conn.peer_addr()
54 .map_or(Cow::Borrowed("-"), |addr| Cow::Owned(addr.to_string()))
55}
56
57pub fn version(conn: &Conn, _color: bool) -> Version {
63 conn.http_version()
64}
65
66mod status_mod {
67 use super::*;
68 #[derive(Copy, Clone)]
70 pub struct StatusOutput(Option<Status>, bool);
71
72 impl Display for StatusOutput {
73 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74 let StatusOutput(status, color) = *self;
75 let Some(status) = status else {
76 return f.write_str("---");
77 };
78 let s = (status as u16).to_string();
79 if color {
80 f.write_fmt(format_args!(
81 "{}",
82 s.color(match status as u16 {
83 200..=299 => "green",
84 300..=399 => "cyan",
85 400..=499 => "yellow",
86 500..=599 => "red",
87 _ => "white",
88 })
89 ))
90 } else {
91 f.write_str(&s)
92 }
93 }
94 }
95
96 pub fn status(conn: &Conn, color: bool) -> StatusOutput {
101 StatusOutput(conn.status(), color)
102 }
103}
104
105pub use status_mod::status;
106
107pub fn request_header(header_name: impl Into<HeaderName<'static>>) -> impl ClientLogFormatter {
110 let header_name = header_name.into();
111 move |conn: &Conn, _color: bool| {
112 format!(
113 "{:?}",
114 conn.request_headers()
115 .get_str(header_name.clone())
116 .unwrap_or("")
117 )
118 }
119}
120
121pub fn response_header(header_name: impl Into<HeaderName<'static>>) -> impl ClientLogFormatter {
124 let header_name = header_name.into();
125 move |conn: &Conn, _color: bool| {
126 format!(
127 "{:?}",
128 conn.response_headers()
129 .get_str(header_name.clone())
130 .unwrap_or("")
131 )
132 }
133}
134
135mod timestamp_mod {
136 use super::*;
137 use time::{OffsetDateTime, macros::format_description};
138
139 pub struct Now;
141
142 pub fn timestamp(_conn: &Conn, _color: bool) -> Now {
145 Now
146 }
147
148 impl Display for Now {
149 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150 let now = OffsetDateTime::now_local()
151 .unwrap_or_else(|_| OffsetDateTime::now_utc())
152 .format(format_description!(
153 version = 2,
154 "[day]/[month repr:short]/[year repr:full]:[hour repr:24]:[minute]:[second] \
155 [offset_hour sign:mandatory][offset_minute]"
156 ))
157 .unwrap();
158 f.write_str(&now)
159 }
160 }
161}
162
163pub use timestamp_mod::timestamp;
164
165pub fn body_len_human(conn: &Conn, _color: bool) -> Cow<'static, str> {
168 conn.response_headers()
169 .content_length()
170 .map(|l| {
171 Size::from_bytes(l)
172 .format()
173 .with_base(Base::Base10)
174 .to_string()
175 .into()
176 })
177 .unwrap_or_else(|| Cow::from("-"))
178}
179
180pub fn bytes(conn: &Conn, _color: bool) -> u64 {
182 conn.response_headers().content_length().unwrap_or_default()
183}
184
185pub fn secure(conn: &Conn, _color: bool) -> &'static str {
187 match conn.url().scheme() {
188 "https" | "wss" => "🔒",
189 _ => " ",
190 }
191}
192
193mod response_time_mod {
194 use super::*;
195
196 pub struct ResponseTimeOutput(Option<Instant>);
198
199 impl Display for ResponseTimeOutput {
200 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
201 match self.0 {
202 Some(start) => f.write_fmt(format_args!("{:?}", Instant::now() - start)),
203 None => f.write_str("-"),
204 }
205 }
206 }
207
208 pub fn response_time(conn: &Conn, _color: bool) -> ResponseTimeOutput {
214 ResponseTimeOutput(conn.state::<RequestStart>().map(|RequestStart(i)| *i))
215 }
216}
217
218pub use response_time_mod::response_time;
219
220mod error_mod {
221 use super::*;
222
223 pub struct ErrorOutput(Option<String>, bool);
225
226 impl Display for ErrorOutput {
227 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
228 let Some(msg) = &self.0 else {
229 return Ok(());
230 };
231 f.write_str(" ")?;
232 if self.1 {
233 f.write_fmt(format_args!("{}", msg.as_str().red()))
234 } else {
235 f.write_str(msg)
236 }
237 }
238 }
239
240 pub fn error(conn: &Conn, color: bool) -> ErrorOutput {
249 ErrorOutput(conn.error().map(ToString::to_string), color)
250 }
251}
252
253pub use error_mod::error;
254
255impl ClientLogFormatter for &'static str {
256 type Output = Self;
257
258 fn format(&self, _conn: &Conn, _color: bool) -> Self::Output {
259 self
260 }
261}
262
263impl ClientLogFormatter for Arc<str> {
264 type Output = Self;
265
266 fn format(&self, _conn: &Conn, _color: bool) -> Self::Output {
267 Arc::clone(self)
268 }
269}
270
271impl ClientLogFormatter for ColoredString {
272 type Output = String;
273
274 fn format(&self, _conn: &Conn, color: bool) -> Self::Output {
275 if color {
276 self.to_string()
277 } else {
278 (**self).to_string()
279 }
280 }
281}
282
283impl<F, O> ClientLogFormatter for F
284where
285 F: Fn(&Conn, bool) -> O + Send + Sync + 'static,
286 O: Display + Send + Sync + 'static,
287{
288 type Output = O;
289
290 fn format(&self, conn: &Conn, color: bool) -> Self::Output {
291 self(conn, color)
292 }
293}
294
295mod tuples {
296 use super::*;
297
298 pub struct TupleOutput<O>(O);
301
302 macro_rules! impl_formatter_tuple {
303 ($($name:ident)+) => (
304 #[allow(non_snake_case)]
305 impl<$($name,)*> Display for TupleOutput<($($name,)*)>
306 where
307 $($name: Display + Send + Sync + 'static,)*
308 {
309 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
310 let ($(ref $name,)*) = self.0;
311 f.write_fmt(format_args!(
312 concat!($(concat!("{", stringify!($name), ":}")),*),
313 $($name = ($name)),*
314 ))
315 }
316 }
317
318 #[allow(non_snake_case)]
319 impl<$($name),*> ClientLogFormatter for ($($name,)*)
320 where
321 $($name: ClientLogFormatter),*
322 {
323 type Output = TupleOutput<($($name::Output,)*)>;
324 fn format(&self, conn: &Conn, color: bool) -> Self::Output {
325 let ($(ref $name,)*) = *self;
326 TupleOutput(($(($name).format(conn, color),)*))
327 }
328 }
329 )
330 }
331
332 impl_formatter_tuple! { A B }
333 impl_formatter_tuple! { A B C }
334 impl_formatter_tuple! { A B C D }
335 impl_formatter_tuple! { A B C D E }
336 impl_formatter_tuple! { A B C D E F }
337 impl_formatter_tuple! { A B C D E F G }
338 impl_formatter_tuple! { A B C D E F G H }
339 impl_formatter_tuple! { A B C D E F G H I }
340 impl_formatter_tuple! { A B C D E F G H I J }
341 impl_formatter_tuple! { A B C D E F G H I J K }
342 impl_formatter_tuple! { A B C D E F G H I J K L }
343 impl_formatter_tuple! { A B C D E F G H I J K L M }
344 impl_formatter_tuple! { A B C D E F G H I J K L M N }
345 impl_formatter_tuple! { A B C D E F G H I J K L M N O }
346 impl_formatter_tuple! { A B C D E F G H I J K L M N O P }
347 impl_formatter_tuple! { A B C D E F G H I J K L M N O P Q }
348 impl_formatter_tuple! { A B C D E F G H I J K L M N O P Q R }
349 impl_formatter_tuple! { A B C D E F G H I J K L M N O P Q R S }
350 impl_formatter_tuple! { A B C D E F G H I J K L M N O P Q R S T }
351 impl_formatter_tuple! { A B C D E F G H I J K L M N O P Q R S T U }
352 impl_formatter_tuple! { A B C D E F G H I J K L M N O P Q R S T U V }
353 impl_formatter_tuple! { A B C D E F G H I J K L M N O P Q R S T U V W }
354 impl_formatter_tuple! { A B C D E F G H I J K L M N O P Q R S T U V W X }
355 impl_formatter_tuple! { A B C D E F G H I J K L M N O P Q R S T U V W X Y }
356 impl_formatter_tuple! { A B C D E F G H I J K L M N O P Q R S T U V W X Y Z }
357}