1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
use actix_web::http::header::{HeaderName, HeaderValue};
use actix_web::{
    dev::{Service, ServiceRequest, ServiceResponse, Transform},
    Error,
};
use future::{ok, LocalBoxFuture, Ready};
use futures::prelude::*;
use std::{
    cell::RefCell,
    rc::Rc,
    task::{Context, Poll},
};
use tracing::{span, trace, Instrument, Level};
use uuid::Uuid;

pub const REQUEST_ID_HEADER: &str = "x-request-id";

pub struct Tracer;

impl<S, B> Transform<S, ServiceRequest> for Tracer
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    type Error = Error;
    type InitError = ();
    type Transform = TracerMiddleware<S>;
    type Future = Ready<Result<Self::Transform, Self::InitError>>;

    fn new_transform(&self, service: S) -> Self::Future {
        ok(TracerMiddleware {
            service: Rc::new(RefCell::new(service)),
        })
    }
}

pub struct TracerMiddleware<S> {
    pub service: Rc<RefCell<S>>,
}

impl<S, B> Service<ServiceRequest> for TracerMiddleware<S>
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    type Error = S::Error;
    type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;

    fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        self.service.poll_ready(cx)
    }

    fn call(&self, req: ServiceRequest) -> Self::Future {
        let request_id = Uuid::new_v4().to_string();
        let root_span = span!(Level::INFO, "HttpRequest", %request_id, method = req.method().as_str(), path = req.path(), query = req.query_string(), remote_ip = req.connection_info().peer_addr().unwrap_or("-"));

        let fut = self.service.call(req);
        Box::pin(
            async move {
                let mut res = fut.await?;

                res.headers_mut().insert(
                    HeaderName::from_static(REQUEST_ID_HEADER),
                    HeaderValue::from_str(&request_id).unwrap(),
                );
                trace!("request");

                Ok(res)
            }
            .instrument(root_span),
        )
    }
}