surf/middleware/logger/
native.rs

1use crate::middleware::{Middleware, Next};
2use crate::{Client, Request, Response};
3
4use std::fmt::Arguments;
5use std::sync::atomic::{AtomicUsize, Ordering};
6use std::time;
7
8static COUNTER: AtomicUsize = AtomicUsize::new(0);
9
10/// Log each request's duration.
11#[derive(Debug, Default)]
12pub struct Logger {
13    _priv: (),
14}
15
16impl Logger {
17    /// Create a new instance.
18    pub fn new() -> Self {
19        Logger { _priv: () }
20    }
21}
22
23#[async_trait::async_trait]
24impl Middleware for Logger {
25    #[allow(missing_doc_code_examples)]
26    async fn handle(
27        &self,
28        req: Request,
29        client: Client,
30        next: Next<'_>,
31    ) -> Result<Response, http_types::Error> {
32        let start_time = time::Instant::now();
33        let uri = format!("{}", req.url());
34        let method = format!("{}", req.method());
35        let id = COUNTER.fetch_add(1, Ordering::Relaxed);
36        print(
37            log::Level::Info,
38            format_args!("sending request"),
39            RequestPairs {
40                id,
41                uri: &uri,
42                method: &method,
43            },
44        );
45
46        let res = next.run(req, client).await?;
47
48        let status = res.status();
49        let elapsed = start_time.elapsed();
50        let level = if status.is_server_error() {
51            log::Level::Error
52        } else if status.is_client_error() {
53            log::Level::Warn
54        } else {
55            log::Level::Info
56        };
57
58        print(
59            level,
60            format_args!("request completed"),
61            ResponsePairs {
62                id,
63                elapsed: &format!("{:?}", elapsed),
64                status: status.into(),
65            },
66        );
67
68        Ok(res)
69    }
70}
71
72struct RequestPairs<'a> {
73    id: usize,
74    method: &'a str,
75    uri: &'a str,
76}
77impl<'a> log::kv::Source for RequestPairs<'a> {
78    fn visit<'kvs>(
79        &'kvs self,
80        visitor: &mut dyn log::kv::Visitor<'kvs>,
81    ) -> Result<(), log::kv::Error> {
82        visitor.visit_pair("req.id".into(), self.id.into())?;
83        visitor.visit_pair("req.method".into(), self.method.into())?;
84        visitor.visit_pair("req.uri".into(), self.uri.into())?;
85        Ok(())
86    }
87}
88
89struct ResponsePairs<'a> {
90    id: usize,
91    status: u16,
92    elapsed: &'a str,
93}
94
95impl<'a> log::kv::Source for ResponsePairs<'a> {
96    fn visit<'kvs>(
97        &'kvs self,
98        visitor: &mut dyn log::kv::Visitor<'kvs>,
99    ) -> Result<(), log::kv::Error> {
100        visitor.visit_pair("req.id".into(), self.id.into())?;
101        visitor.visit_pair("req.status".into(), self.status.into())?;
102        visitor.visit_pair("elapsed".into(), self.elapsed.into())?;
103        Ok(())
104    }
105}
106
107fn print(level: log::Level, msg: Arguments<'_>, key_values: impl log::kv::Source) {
108    if level <= log::STATIC_MAX_LEVEL && level <= log::max_level() {
109        log::logger().log(
110            &log::Record::builder()
111                .args(msg)
112                .key_values(&key_values)
113                .level(level)
114                .target(module_path!())
115                .module_path(Some(module_path!()))
116                .file(Some(file!()))
117                .line(Some(line!()))
118                .build(),
119        );
120    }
121}