rocket_slogger/
lib.rs

1pub mod fairing;
2pub mod from_request;
3
4#[cfg(feature = "transactions")]
5pub mod transaction;
6
7// various slog re-exports for convenience
8pub use slog::{o, o as log_fields, Drain, Logger};
9// logging macros that are compiled away in release mode
10pub use slog::{debug, trace};
11// logging macros that are kept in all builds
12pub use slog::{error, info, warn};
13
14use rocket::{Request, Response};
15use std::sync::Arc;
16
17#[allow(unused_imports)]
18use std::future::Future;
19#[allow(unused_imports)]
20use std::pin::Pin;
21
22#[derive(Clone)]
23pub struct Slogger {
24    logger: Arc<Logger>,
25
26    #[cfg(feature = "callbacks")]
27    request_handlers: Vec<
28        Arc<
29            dyn for<'r> Fn(
30                    Arc<Logger>,
31                    &'r mut Request<'_>,
32                )
33                    -> Pin<Box<dyn Future<Output = Option<Arc<Logger>>> + Send + 'r>>
34                + Send
35                + Sync
36                + 'static,
37        >,
38    >,
39
40    #[cfg(feature = "callbacks")]
41    response_handlers: Vec<
42        Arc<
43            dyn for<'r> Fn(
44                    Arc<Logger>,
45                    &'r Request<'_>,
46                    &'r mut Response<'_>,
47                )
48                    -> Pin<Box<dyn Future<Output = Option<Arc<Logger>>> + Send + 'r>>
49                + Send
50                + Sync
51                + 'static,
52        >,
53    >,
54}
55
56impl Slogger {
57    #[cfg(all(feature = "terminal", not(feature = "envlogger")))]
58    pub fn new_terminal_logger() -> Self {
59        use slog_term::{FullFormat, PlainSyncDecorator};
60
61        let plain_logger = PlainSyncDecorator::new(std::io::stdout());
62        let logger = Logger::root(FullFormat::new(plain_logger).build().fuse(), log_fields!());
63
64        Self::from_logger(logger)
65    }
66
67    #[cfg(all(feature = "terminal", feature = "envlogger"))]
68    pub fn new_terminal_logger() -> Self {
69        use slog_envlogger::EnvLogger;
70        use slog_term::{FullFormat, PlainSyncDecorator};
71
72        let plain_logger = PlainSyncDecorator::new(std::io::stdout());
73        let env_logger = EnvLogger::new(plain_logger);
74        let logger = Logger::root(FullFormat::new(env_logger).build().fuse(), log_fields!());
75
76        Self::from_logger(logger)
77    }
78
79    #[cfg(all(feature = "bunyan", not(feature = "envlogger")))]
80    pub fn new_bunyan_logger(name: &'static str) -> Self {
81        use std::sync::Mutex;
82
83        let bunyan_logger = slog_bunyan::with_name(name, std::io::stderr()).build();
84        let logger = Logger::root(Mutex::new(bunyan_logger).fuse(), log_fields!());
85
86        Self::from_logger(logger)
87    }
88
89    #[cfg(all(feature = "bunyan", feature = "envlogger"))]
90    pub fn new_bunyan_logger(name: &'static str) -> Self {
91        use slog_envlogger::EnvLogger;
92        use std::sync::Mutex;
93
94        let bunyan_logger = slog_bunyan::with_name(name, std::io::stderr()).build();
95        let env_logger = EnvLogger::new(bunyan_logger);
96        let logger = Logger::root(Mutex::new(env_logger).fuse(), log_fields!());
97
98        Self::from_logger(logger)
99    }
100
101    pub fn from_logger(logger: Logger) -> Self {
102        Self {
103            logger: Arc::new(logger),
104
105            #[cfg(feature = "callbacks")]
106            request_handlers: vec![],
107
108            #[cfg(feature = "callbacks")]
109            response_handlers: vec![],
110        }
111    }
112
113    pub fn get(&self) -> &Logger {
114        &self.logger
115    }
116
117    pub fn get_for_request(&self, request: &Request<'_>) -> Logger {
118        let content_type = request.content_type().map(|format| format.to_string());
119        let user_agent = request
120            .headers()
121            .get("user-agent")
122            .collect::<Vec<_>>()
123            .join("; ");
124
125        #[cfg(not(feature = "transactions"))]
126        let logger = self.logger.new(log_fields!(
127            "user-agent" => user_agent,
128            "content-type" => content_type,
129        ));
130
131        #[cfg(feature = "transactions")]
132        let logger = {
133            let transaction = transaction::RequestTransaction::new().attach_on(request);
134
135            self.logger.new(log_fields!(
136                "received" => transaction.received_as_string(),
137                "transaction" => transaction.id_as_string(),
138
139                "user-agent" => user_agent,
140                "content-type" => content_type,
141            ))
142        };
143
144        Self::new_logger_with_request_details(&logger, request)
145    }
146
147    pub fn get_for_response(&self, request: &Request<'_>, response: &Response<'_>) -> Logger {
148        let content_type = response.content_type().map(|format| format.to_string());
149        let status = response.status();
150
151        #[cfg(not(feature = "transactions"))]
152        let logger = self.logger.new(log_fields!(
153            "content-type" => content_type,
154            "reason" => status.reason().map(|reason| reason.to_string()),
155            "code" => status.code,
156        ));
157
158        #[cfg(feature = "transactions")]
159        let logger = {
160            let transaction = transaction::RequestTransaction::new().attach_on(request);
161
162            self.logger.new(log_fields!(
163                "elapsed_ns" => transaction.elapsed_ns(),
164                "received" => transaction.received_as_string(),
165                "transaction" => transaction.id_as_string(),
166                "content-type" => content_type,
167                "reason" => status.reason().map(|reason| reason.to_string()),
168                "code" => status.code,
169            ))
170        };
171
172        Self::new_logger_with_request_details(&logger, request)
173    }
174
175    fn new_logger_with_request_details(logger: &Logger, request: &Request<'_>) -> Logger {
176        if let Some(route) = request.route() {
177            logger.new(log_fields!(
178                "rank" => route.rank,
179                "route" => route.name.as_ref().map(|route| route.to_string()),
180                "path" => format!("{}", route.uri),
181                "method" => format!("{}", route.method),
182                "uri" => format!("{}", request.uri()),
183            ))
184        } else {
185            logger.new(log_fields!(
186                "method" => format!("{}", request.method()),
187                "uri" => format!("{}", request.uri()),
188            ))
189        }
190    }
191
192    #[cfg(feature = "callbacks")]
193    pub fn on_request(
194        mut self,
195        handler: impl for<'r> Fn(
196                Arc<Logger>,
197                &'r mut Request<'_>,
198            )
199                -> Pin<Box<dyn Future<Output = Option<Arc<Logger>>> + Send + 'r>>
200            + Send
201            + Sync
202            + 'static,
203    ) -> Self {
204        self.request_handlers.push(Arc::new(handler));
205        self
206    }
207
208    #[cfg(feature = "callbacks")]
209    pub fn on_response(
210        mut self,
211        handler: impl for<'r> Fn(
212                Arc<Logger>,
213                &'r Request<'_>,
214                &'r mut Response<'_>,
215            )
216                -> Pin<Box<dyn Future<Output = Option<Arc<Logger>>> + Send + 'r>>
217            + Send
218            + Sync
219            + 'static,
220    ) -> Self {
221        self.response_handlers.push(Arc::new(handler));
222        self
223    }
224}
225
226impl From<Logger> for Slogger {
227    fn from(logger: Logger) -> Self {
228        Slogger::from_logger(logger)
229    }
230}
231
232impl From<&Logger> for Slogger {
233    fn from(logger: &Logger) -> Self {
234        Slogger::from_logger(logger.clone())
235    }
236}
237
238impl std::ops::Deref for Slogger {
239    type Target = Logger;
240
241    fn deref(&self) -> &Logger {
242        &self.logger
243    }
244}