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 version(conn: &Conn, _color: bool) -> Version {
53 conn.http_version()
54}
55
56mod status_mod {
57 use super::*;
58 #[derive(Copy, Clone)]
60 pub struct StatusOutput(Option<Status>, bool);
61
62 impl Display for StatusOutput {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 let StatusOutput(status, color) = *self;
65 let Some(status) = status else {
66 return f.write_str("---");
67 };
68 let s = (status as u16).to_string();
69 if color {
70 f.write_fmt(format_args!(
71 "{}",
72 s.color(match status as u16 {
73 200..=299 => "green",
74 300..=399 => "cyan",
75 400..=499 => "yellow",
76 500..=599 => "red",
77 _ => "white",
78 })
79 ))
80 } else {
81 f.write_str(&s)
82 }
83 }
84 }
85
86 pub fn status(conn: &Conn, color: bool) -> StatusOutput {
91 StatusOutput(conn.status(), color)
92 }
93}
94
95pub use status_mod::status;
96
97pub fn request_header(header_name: impl Into<HeaderName<'static>>) -> impl ClientLogFormatter {
100 let header_name = header_name.into();
101 move |conn: &Conn, _color: bool| {
102 format!(
103 "{:?}",
104 conn.request_headers()
105 .get_str(header_name.clone())
106 .unwrap_or("")
107 )
108 }
109}
110
111pub fn response_header(header_name: impl Into<HeaderName<'static>>) -> impl ClientLogFormatter {
114 let header_name = header_name.into();
115 move |conn: &Conn, _color: bool| {
116 format!(
117 "{:?}",
118 conn.response_headers()
119 .get_str(header_name.clone())
120 .unwrap_or("")
121 )
122 }
123}
124
125mod timestamp_mod {
126 use super::*;
127 use time::{OffsetDateTime, macros::format_description};
128
129 pub struct Now;
131
132 pub fn timestamp(_conn: &Conn, _color: bool) -> Now {
135 Now
136 }
137
138 impl Display for Now {
139 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140 let now = OffsetDateTime::now_local()
141 .unwrap_or_else(|_| OffsetDateTime::now_utc())
142 .format(format_description!(
143 version = 2,
144 "[day]/[month repr:short]/[year repr:full]:[hour repr:24]:[minute]:[second] \
145 [offset_hour sign:mandatory][offset_minute]"
146 ))
147 .unwrap();
148 f.write_str(&now)
149 }
150 }
151}
152
153pub use timestamp_mod::timestamp;
154
155pub fn body_len_human(conn: &Conn, _color: bool) -> Cow<'static, str> {
158 conn.response_headers()
159 .content_length()
160 .map(|l| {
161 Size::from_bytes(l)
162 .format()
163 .with_base(Base::Base10)
164 .to_string()
165 .into()
166 })
167 .unwrap_or_else(|| Cow::from("-"))
168}
169
170pub fn bytes(conn: &Conn, _color: bool) -> u64 {
172 conn.response_headers().content_length().unwrap_or_default()
173}
174
175pub fn secure(conn: &Conn, _color: bool) -> &'static str {
177 match conn.url().scheme() {
178 "https" | "wss" => "🔒",
179 _ => " ",
180 }
181}
182
183mod response_time_mod {
184 use super::*;
185
186 pub struct ResponseTimeOutput(Option<Instant>);
188
189 impl Display for ResponseTimeOutput {
190 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
191 match self.0 {
192 Some(start) => f.write_fmt(format_args!("{:?}", Instant::now() - start)),
193 None => f.write_str("-"),
194 }
195 }
196 }
197
198 pub fn response_time(conn: &Conn, _color: bool) -> ResponseTimeOutput {
204 ResponseTimeOutput(conn.state::<RequestStart>().map(|RequestStart(i)| *i))
205 }
206}
207
208pub use response_time_mod::response_time;
209
210mod error_mod {
211 use super::*;
212
213 pub struct ErrorOutput(Option<String>, bool);
215
216 impl Display for ErrorOutput {
217 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
218 let Some(msg) = &self.0 else {
219 return Ok(());
220 };
221 f.write_str(" ")?;
222 if self.1 {
223 f.write_fmt(format_args!("{}", msg.as_str().red()))
224 } else {
225 f.write_str(msg)
226 }
227 }
228 }
229
230 pub fn error(conn: &Conn, color: bool) -> ErrorOutput {
239 ErrorOutput(conn.error().map(ToString::to_string), color)
240 }
241}
242
243pub use error_mod::error;
244
245impl ClientLogFormatter for &'static str {
246 type Output = Self;
247
248 fn format(&self, _conn: &Conn, _color: bool) -> Self::Output {
249 self
250 }
251}
252
253impl ClientLogFormatter for Arc<str> {
254 type Output = Self;
255
256 fn format(&self, _conn: &Conn, _color: bool) -> Self::Output {
257 Arc::clone(self)
258 }
259}
260
261impl ClientLogFormatter for ColoredString {
262 type Output = String;
263
264 fn format(&self, _conn: &Conn, color: bool) -> Self::Output {
265 if color {
266 self.to_string()
267 } else {
268 (**self).to_string()
269 }
270 }
271}
272
273impl<F, O> ClientLogFormatter for F
274where
275 F: Fn(&Conn, bool) -> O + Send + Sync + 'static,
276 O: Display + Send + Sync + 'static,
277{
278 type Output = O;
279
280 fn format(&self, conn: &Conn, color: bool) -> Self::Output {
281 self(conn, color)
282 }
283}
284
285mod tuples {
286 use super::*;
287
288 pub struct TupleOutput<O>(O);
291
292 macro_rules! impl_formatter_tuple {
293 ($($name:ident)+) => (
294 #[allow(non_snake_case)]
295 impl<$($name,)*> Display for TupleOutput<($($name,)*)>
296 where
297 $($name: Display + Send + Sync + 'static,)*
298 {
299 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
300 let ($(ref $name,)*) = self.0;
301 f.write_fmt(format_args!(
302 concat!($(concat!("{", stringify!($name), ":}")),*),
303 $($name = ($name)),*
304 ))
305 }
306 }
307
308 #[allow(non_snake_case)]
309 impl<$($name),*> ClientLogFormatter for ($($name,)*)
310 where
311 $($name: ClientLogFormatter),*
312 {
313 type Output = TupleOutput<($($name::Output,)*)>;
314 fn format(&self, conn: &Conn, color: bool) -> Self::Output {
315 let ($(ref $name,)*) = *self;
316 TupleOutput(($(($name).format(conn, color),)*))
317 }
318 }
319 )
320 }
321
322 impl_formatter_tuple! { A B }
323 impl_formatter_tuple! { A B C }
324 impl_formatter_tuple! { A B C D }
325 impl_formatter_tuple! { A B C D E }
326 impl_formatter_tuple! { A B C D E F }
327 impl_formatter_tuple! { A B C D E F G }
328 impl_formatter_tuple! { A B C D E F G H }
329 impl_formatter_tuple! { A B C D E F G H I }
330 impl_formatter_tuple! { A B C D E F G H I J }
331 impl_formatter_tuple! { A B C D E F G H I J K }
332 impl_formatter_tuple! { A B C D E F G H I J K L }
333 impl_formatter_tuple! { A B C D E F G H I J K L M }
334 impl_formatter_tuple! { A B C D E F G H I J K L M N }
335 impl_formatter_tuple! { A B C D E F G H I J K L M N O }
336 impl_formatter_tuple! { A B C D E F G H I J K L M N O P }
337 impl_formatter_tuple! { A B C D E F G H I J K L M N O P Q }
338 impl_formatter_tuple! { A B C D E F G H I J K L M N O P Q R }
339 impl_formatter_tuple! { A B C D E F G H I J K L M N O P Q R S }
340 impl_formatter_tuple! { A B C D E F G H I J K L M N O P Q R S T }
341 impl_formatter_tuple! { A B C D E F G H I J K L M N O P Q R S T U }
342 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 }
343 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 }
344 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 }
345 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 }
346 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 }
347}