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 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
//! `tracing-actix-web` provides [`TracingLogger`], a middleware to log request and response info //! when using the [`actix-web`] framework. //! //! [`TracingLogger`] is designed as a drop-in replacement of [`actix-web`]'s [`Logger`]. //! //! [`Logger`] is built on top of the [`log`] crate: you need to use regular expressions to parse //! the request information out of the logged message. //! //! [`TracingLogger`] relies on [`tracing`], a modern instrumentation framework for structured //! logging: all request information is captured as a machine-parsable set of key-value pairs. //! It also enables propagation of context information to children spans. //! //! [`TracingLogger`]: #struct.TracingLogger.html //! [`actix-web`]: https://docs.rs/actix-web //! [`Logger`]: https://docs.rs/actix-web/2.0.0/actix_web/middleware/struct.Logger.html //! [`log`]: https://docs.rs/log //! [`tracing`]: https://docs.rs/tracing use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform}; use actix_web::Error; use futures::future::{ok, Ready}; use futures::task::{Context, Poll}; use std::future::Future; use std::pin::Pin; use tracing::Span; use tracing_futures::Instrument; use uuid::Uuid; /// `TracingLogger` is a middleware to log request and response info in a structured format. /// /// `TracingLogger` is designed as a drop-in replacement of [`actix-web`]'s [`Logger`]. /// /// [`Logger`] is built on top of the [`log`] crate: you need to use regular expressions to parse /// the request information out of the logged message. /// /// `TracingLogger` relies on [`tracing`], a modern instrumentation framework for structured /// logging: all request information is captured as a machine-parsable set of key-value pairs. /// It also enables propagation of context information to children spans. /// /// ## Usage /// /// Register `TracingLogger` as a middleware for your application using `.wrap` on `App`. /// Add a `Subscriber` implementation to output logs to the console. /// /// ```rust /// use actix_web::middleware::Logger; /// use actix_web::App; /// use tracing::{Subscriber, subscriber::set_global_default}; /// use tracing_actix_web::TracingLogger; /// use tracing_bunyan_formatter::{BunyanFormattingLayer, JsonStorageLayer}; /// use tracing_subscriber::{layer::SubscriberExt, EnvFilter, Registry}; /// /// /// Compose multiple layers into a `tracing`'s subscriber. /// pub fn get_subscriber( /// name: String, /// env_filter: String /// ) -> impl Subscriber + Send + Sync { /// let env_filter = EnvFilter::try_from_default_env() /// .unwrap_or(EnvFilter::new(env_filter)); /// let formatting_layer = BunyanFormattingLayer::new( /// name.into(), /// std::io::stdout /// ); /// Registry::default() /// .with(env_filter) /// .with(JsonStorageLayer) /// .with(formatting_layer) /// } /// /// /// Register a subscriber as global default to process span data. /// /// /// /// It should only be called once! /// pub fn init_subscriber(subscriber: impl Subscriber + Send + Sync) { /// LogTracer::init().expect("Failed to set logger"); /// set_global_default(subscriber).expect("Failed to set subscriber"); /// } /// /// fn main() { /// let subscriber = get_subscriber("app".into(), "info".into()); /// init_subscriber(subscriber); /// /// let app = App::new().wrap(TracingLogger); /// } /// ``` /// /// [`actix-web`]: https://docs.rs/actix-web /// [`Logger`]: https://docs.rs/actix-web/2.0.0/actix_web/middleware/struct.Logger.html /// [`log`]: https://docs.rs/log /// [`tracing`]: https://docs.rs/tracing pub struct TracingLogger; impl<S, B> Transform<S> for TracingLogger where S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>, S::Future: 'static, B: 'static, { type Request = ServiceRequest; type Response = ServiceResponse<B>; type Error = Error; type Transform = TracingLoggerMiddleware<S>; type InitError = (); type Future = Ready<Result<Self::Transform, Self::InitError>>; fn new_transform(&self, service: S) -> Self::Future { ok(TracingLoggerMiddleware { service }) } } #[doc(hidden)] pub struct TracingLoggerMiddleware<S> { service: S, } impl<S, B> Service for TracingLoggerMiddleware<S> where S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>, S::Future: 'static, B: 'static, { type Request = ServiceRequest; type Response = ServiceResponse<B>; type Error = Error; type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>; fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { self.service.poll_ready(cx) } fn call(&mut self, req: ServiceRequest) -> Self::Future { let user_agent = req .headers() .get("User-Agent") .map(|h| h.to_str().unwrap_or("")) .unwrap_or(""); let span = tracing::info_span!( "Request", request_path = %req.path(), user_agent = %user_agent, client_ip_address = %req.connection_info().remote().unwrap_or(""), request_id = %Uuid::new_v4(), status_code = tracing::field::Empty, ); let fut = self.service.call(req); Box::pin( async move { let outcome = fut.await; let status_code = match &outcome { Ok(response) => response.response().status(), Err(error) => error.as_response_error().status_code(), }; Span::current().record("status_code", &status_code.as_u16()); outcome } .instrument(span), ) } }