rocket_community/trace/
traceable.rs

1use std::error::Error as StdError;
2
3use crate::error::ErrorKind;
4use crate::request::ConnectionMeta;
5use crate::sentinel::Sentry;
6use crate::util::Formatter;
7use crate::{route, Catcher, Config, Error, Request, Response, Route};
8
9use figment::Figment;
10use rocket::http::Header;
11use tracing::Level;
12
13pub trait Trace {
14    fn trace(&self, level: Level);
15
16    #[inline(always)]
17    fn trace_info(&self) {
18        self.trace(Level::INFO)
19    }
20    #[inline(always)]
21    fn trace_warn(&self) {
22        self.trace(Level::WARN)
23    }
24    #[inline(always)]
25    fn trace_error(&self) {
26        self.trace(Level::ERROR)
27    }
28    #[inline(always)]
29    fn trace_debug(&self) {
30        self.trace(Level::DEBUG)
31    }
32    #[inline(always)]
33    fn trace_trace(&self) {
34        self.trace(Level::TRACE)
35    }
36}
37
38pub trait TraceAll: Sized {
39    fn trace_all(self, level: Level);
40
41    #[inline(always)]
42    fn trace_all_info(self) {
43        self.trace_all(Level::INFO)
44    }
45    #[inline(always)]
46    fn trace_all_warn(self) {
47        self.trace_all(Level::WARN)
48    }
49    #[inline(always)]
50    fn trace_all_error(self) {
51        self.trace_all(Level::ERROR)
52    }
53    #[inline(always)]
54    fn trace_all_debug(self) {
55        self.trace_all(Level::DEBUG)
56    }
57    #[inline(always)]
58    fn trace_all_trace(self) {
59        self.trace_all(Level::TRACE)
60    }
61}
62
63impl<T: Trace, I: IntoIterator<Item = T>> TraceAll for I {
64    fn trace_all(self, level: Level) {
65        self.into_iter().for_each(|i| i.trace(level))
66    }
67}
68
69impl<T: Trace> Trace for &T {
70    #[inline(always)]
71    fn trace(&self, level: Level) {
72        T::trace(self, level)
73    }
74}
75
76impl Trace for Figment {
77    fn trace(&self, level: Level) {
78        for param in Config::PARAMETERS {
79            if let Some(source) = self.find_metadata(param) {
80                if param.contains("secret") {
81                    continue;
82                }
83
84                event! { level, "figment",
85                    param,
86                    %source.name,
87                    source.source = source.source.as_ref().map(display),
88                    value = self.find_value(param).ok().map(debug),
89                }
90            }
91        }
92
93        // Check for now deprecated config values.
94        for (key, replacement) in Config::DEPRECATED_KEYS {
95            if let Some(source) = self.find_metadata(key) {
96                event! { Level::WARN, "deprecated",
97                    key,
98                    replacement,
99                    %source.name,
100                    source.source = source.source.as_ref().map(display),
101                    "config key `{key}` is deprecated and has no meaning"
102                }
103            }
104        }
105    }
106}
107
108impl Trace for Config {
109    fn trace(&self, level: Level) {
110        event! { level, "config",
111            http2 = cfg!(feature = "http2"),
112            log_level = self.log_level.map(|l| l.as_str()),
113            log_format = ?self.log_format,
114            cli_colors = %self.cli_colors,
115            workers = self.workers,
116            max_blocking = self.max_blocking,
117            ident = %self.ident,
118            ip_header = self.ip_header.as_ref().map(|s| s.as_str()),
119            proxy_proto_header = self.proxy_proto_header.as_ref().map(|s| s.as_str()),
120            limits = %Formatter(|f| f.debug_map()
121                .entries(self.limits.limits.iter().map(|(k, v)| (k.as_str(), display(v))))
122                .finish()),
123            temp_dir = %self.temp_dir.relative().display(),
124            keep_alive = (self.keep_alive != 0).then_some(self.keep_alive),
125            shutdown.ctrlc = self.shutdown.ctrlc,
126            shutdown.signals = %{
127                #[cfg(not(unix))] {
128                    "disabled (not unix)"
129                }
130
131                #[cfg(unix)] {
132                    Formatter(|f| f.debug_set()
133                        .entries(self.shutdown.signals.iter().map(|s| s.as_str()))
134                        .finish())
135                }
136            },
137                shutdown.grace = self.shutdown.grace,
138                shutdown.mercy = self.shutdown.mercy,
139                shutdown.force = self.shutdown.force,
140        }
141
142        #[cfg(feature = "secrets")]
143        {
144            if !self.secret_key.is_provided() {
145                warn! {
146                    name: "volatile_secret_key",
147                    "secrets enabled without configuring a stable `secret_key`\n\
148                    private/signed cookies will become unreadable after restarting\n\
149                    disable the `secrets` feature or configure a `secret_key`\n\
150                    this becomes a hard error in non-debug profiles",
151                }
152            }
153
154            let secret_key_is_known = Config::KNOWN_SECRET_KEYS.iter().any(|&key_str| {
155                let value = figment::value::Value::from(key_str);
156                self.secret_key == value.deserialize().expect("known key is valid")
157            });
158
159            if secret_key_is_known {
160                warn! {
161                    name: "insecure_secret_key",
162                    "The configured `secret_key` is exposed and insecure. \
163                    The configured key is publicly published and thus insecure. \
164                    Try generating a new key with `head -c64 /dev/urandom | base64`."
165                }
166            }
167        }
168    }
169}
170
171impl Trace for Route {
172    fn trace(&self, level: Level) {
173        event! { level, "route",
174            name = self.name.as_deref(),
175            rank = self.rank,
176            method = %Formatter(|f| match self.method {
177                Some(method) => write!(f, "{}", method),
178                None => write!(f, "[any]"),
179            }),
180            uri = %self.uri,
181            uri.base = %self.uri.base(),
182            uri.unmounted = %self.uri.unmounted(),
183            format = self.format.as_ref().map(display),
184            location = self.location.as_ref()
185                .map(|(file, line, _)| Formatter(move |f| write!(f, "{file}:{line}")))
186                .map(display),
187        }
188
189        event! { Level::DEBUG, "sentinels",
190            route = self.name.as_deref(),
191            sentinels = %Formatter(|f| {
192                f.debug_set()
193                    .entries(self.sentinels.iter().filter(|s| s.specialized).map(|s| s.type_name))
194                    .finish()
195            })
196        }
197    }
198}
199
200impl Trace for Catcher {
201    fn trace(&self, level: Level) {
202        event! { level, "catcher",
203            name = self.name.as_deref(),
204            code = %Formatter(|f| match self.code {
205                Some(code) => write!(f, "{}", code),
206                None => write!(f, "default"),
207            }),
208            rank = self.rank,
209            uri.base = %self.base(),
210            location = self.location.as_ref()
211                .map(|(file, line, _)| Formatter(move |f| write!(f, "{file}:{line}")))
212                .map(display),
213        }
214    }
215}
216
217impl Trace for &dyn crate::fairing::Fairing {
218    fn trace(&self, level: Level) {
219        self.info().trace(level)
220    }
221}
222
223impl Trace for crate::fairing::Info {
224    fn trace(&self, level: Level) {
225        event!(level, "fairing", name = self.name, kind = %self.kind)
226    }
227}
228
229impl Trace for figment::error::Kind {
230    fn trace(&self, _: Level) {
231        use figment::error::{Kind::*, OneOf as V};
232
233        match self {
234            Message(message) => error!(message),
235            InvalidType(actual, expected) => error!(%actual, expected, "invalid type"),
236            InvalidValue(actual, expected) => error!(%actual, expected, "invalid value"),
237            InvalidLength(actual, expected) => error!(%actual, expected, "invalid length"),
238            UnknownVariant(actual, v) => error!(actual, expected = %V(v), "unknown variant"),
239            UnknownField(actual, v) => error!(actual, expected = %V(v), "unknown field"),
240            UnsupportedKey(actual, v) => error!(%actual, expected = &**v, "unsupported key"),
241            MissingField(value) => error!(value = &**value, "missing field"),
242            DuplicateField(value) => error!(value, "duplicate field"),
243            ISizeOutOfRange(value) => error!(value, "out of range signed integer"),
244            USizeOutOfRange(value) => error!(value, "out of range unsigned integer"),
245            Unsupported(value) => error!(%value, "unsupported type"),
246        }
247    }
248}
249
250impl Trace for figment::Error {
251    fn trace(&self, _: Level) {
252        for e in self.clone() {
253            span_error!("config",
254                key = (!e.path.is_empty()).then_some(&e.path).and_then(|path| {
255                    let (profile, metadata) = (e.profile.as_ref()?, e.metadata.as_ref()?);
256                    Some(metadata.interpolate(profile, path))
257                }),
258                source.name = e.metadata.as_ref().map(|m| &*m.name),
259                source.source = e.metadata.as_ref().and_then(|m| m.source.as_ref()).map(display)
260                => e.kind.trace_error());
261        }
262    }
263}
264
265impl Trace for Header<'_> {
266    fn trace(&self, level: Level) {
267        event!(
268            level,
269            "header",
270            name = self.name().as_str(),
271            value = self.value()
272        );
273    }
274}
275
276impl Trace for route::Outcome<'_> {
277    fn trace(&self, level: Level) {
278        event!(
279            level,
280            "outcome",
281            outcome = match self {
282                Self::Success(..) => "success",
283                Self::Error(..) => "error",
284                Self::Forward(..) => "forward",
285            },
286            status = match self {
287                Self::Success(r) => r.status().code,
288                Self::Error(s) => s.code,
289                Self::Forward((_, s)) => s.code,
290            },
291        )
292    }
293}
294
295impl Trace for Response<'_> {
296    fn trace(&self, level: Level) {
297        event!(level, "response", status = self.status().code);
298    }
299}
300
301impl Trace for Error {
302    fn trace(&self, level: Level) {
303        self.kind.trace(level);
304    }
305}
306
307impl Trace for Sentry {
308    fn trace(&self, level: Level) {
309        let (file, line, col) = self.location;
310        event!(level, "sentry",
311            type_name = self.type_name,
312            location = %Formatter(|f| write!(f, "{file}:{line}:{col}"))
313        );
314    }
315}
316
317impl Trace for Request<'_> {
318    fn trace(&self, level: Level) {
319        event!(level, "request", method = %self.method(), uri = %self.uri())
320    }
321}
322
323impl Trace for ConnectionMeta {
324    fn trace(&self, level: Level) {
325        event!(
326            level,
327            "connection",
328            endpoint = self.peer_endpoint.as_ref().map(display),
329            certs = self.peer_certs.is_some(),
330        )
331    }
332}
333
334impl Trace for ErrorKind {
335    fn trace(&self, level: Level) {
336        use ErrorKind::*;
337
338        fn try_downcast<'a, T>(error: &'a (dyn StdError + 'static)) -> Option<&'a T>
339        where
340            T: StdError + 'static,
341        {
342            error
343                .downcast_ref()
344                .or_else(|| error.source()?.downcast_ref())
345        }
346
347        match self {
348            Bind(endpoint, error) => {
349                if let Some(e) = try_downcast::<crate::Error>(&**error) {
350                    e.trace(level);
351                } else if let Some(e) = try_downcast::<figment::Error>(&**error) {
352                    e.trace(level);
353                } else {
354                    event!(level, "error::bind",
355                        reason = %error,
356                        endpoint = endpoint.as_ref().map(display),
357                        "binding to network interface failed"
358                    )
359                }
360            }
361            Io(reason) => event!(level, "error::io", %reason, "i/o error"),
362            Config(error) => error.trace(level),
363            Collisions { routes, catchers } => {
364                span!(
365                    level,
366                    "collision",
367                    route.pairs = routes.len(),
368                    catcher.pairs = catchers.len(),
369                    "colliding items detected"
370                )
371                .in_scope(|| {
372                    for (a, b) in routes {
373                        span!(level, "colliding route pair").in_scope(|| {
374                            a.trace(level);
375                            b.trace(level);
376                        })
377                    }
378
379                    for (a, b) in catchers {
380                        span!(level, "colliding catcher pair").in_scope(|| {
381                            a.trace(level);
382                            b.trace(level);
383                        })
384                    }
385
386                    span!(
387                        Level::INFO,
388                        "collisions can usually be resolved by ranking items"
389                    );
390                });
391            }
392            FailedFairings(fairings) => {
393                let span = span!(level, "failed ignite fairings", count = fairings.len());
394                span.in_scope(|| fairings.iter().trace_all(level));
395            }
396            SentinelAborts(sentries) => {
397                let span = span!(level, "sentries", "sentry launch abort");
398                span.in_scope(|| sentries.iter().trace_all(level));
399            }
400            InsecureSecretKey(profile) => event!(level, "insecure_key", %profile,
401                "secrets enabled in a non-debug profile without a stable `secret_key`\n\
402                disable the `secrets` feature or configure a `secret_key`"
403            ),
404            Liftoff(_, reason) => event!(level, "panic", %reason, "liftoff fairing failed"),
405            Shutdown(_) => event!(level, "shutdown", "shutdown failed"),
406        }
407    }
408}