Line data Source code
1 : //! Tracing wrapper for Hyper HTTP server
2 :
3 : use hyper::HeaderMap;
4 : use hyper::{Body, Request, Response};
5 : use std::future::Future;
6 : use tracing::Instrument;
7 : use tracing_opentelemetry::OpenTelemetrySpanExt;
8 :
9 : /// Configuration option for what to use as the "otel.name" field in the traces.
10 : pub enum OtelName<'a> {
11 : /// Use a constant string
12 : Constant(&'a str),
13 :
14 : /// Use the path from the request.
15 : ///
16 : /// That's very useful information, but is not appropriate if the
17 : /// path contains parameters that differ on ever request, or worse,
18 : /// sensitive information like usernames or email addresses.
19 : ///
20 : /// See <https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#name>
21 : UriPath,
22 : }
23 :
24 : /// Handle an incoming HTTP request using the given handler function,
25 : /// with OpenTelemetry tracing.
26 : ///
27 : /// This runs 'handler' on the request in a new span, with fields filled in
28 : /// from the request. Notably, if the request contains tracing information,
29 : /// it is propagated to the span, so that this request is traced as part of
30 : /// the same trace.
31 : ///
32 : /// XXX: Usually, this is handled by existing libraries, or built
33 : /// directly into HTTP servers. However, I couldn't find one for Hyper,
34 : /// so I had to write our own. OpenTelemetry website has a registry of
35 : /// instrumentation libraries at:
36 : /// <https://opentelemetry.io/registry/?language=rust&component=instrumentation>
37 : /// If a Hyper crate appears, consider switching to that.
38 0 : pub async fn tracing_handler<F, R>(
39 0 : req: Request<Body>,
40 0 : handler: F,
41 0 : otel_name: OtelName<'_>,
42 0 : ) -> Response<Body>
43 0 : where
44 0 : F: Fn(Request<Body>) -> R,
45 0 : R: Future<Output = Response<Body>>,
46 0 : {
47 : // Create a tracing span, with context propagated from the incoming
48 : // request if any.
49 : //
50 : // See list of standard fields defined for HTTP requests at
51 : // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md
52 : // We only fill in a few of the most useful ones here.
53 0 : let otel_name = match otel_name {
54 0 : OtelName::Constant(s) => s,
55 0 : OtelName::UriPath => req.uri().path(),
56 : };
57 :
58 0 : let span = tracing::info_span!(
59 : "http request",
60 : otel.name= %otel_name,
61 0 : http.method = %req.method(),
62 : http.status_code = tracing::field::Empty,
63 : );
64 0 : let parent_ctx = extract_remote_context(req.headers());
65 0 : span.set_parent(parent_ctx);
66 :
67 : // Handle the request within the span
68 0 : let response = handler(req).instrument(span.clone()).await;
69 :
70 : // Fill in the fields from the response code
71 0 : let status = response.status();
72 0 : span.record("http.status_code", status.as_str());
73 0 : span.record(
74 0 : "otel.status_code",
75 0 : if status.is_success() { "OK" } else { "ERROR" },
76 : );
77 :
78 0 : response
79 0 : }
80 :
81 : // Extract remote tracing context from the HTTP headers
82 0 : fn extract_remote_context(headers: &HeaderMap) -> opentelemetry::Context {
83 : struct HeaderExtractor<'a>(&'a HeaderMap);
84 :
85 : impl<'a> opentelemetry::propagation::Extractor for HeaderExtractor<'a> {
86 0 : fn get(&self, key: &str) -> Option<&str> {
87 0 : self.0.get(key).and_then(|value| value.to_str().ok())
88 0 : }
89 :
90 0 : fn keys(&self) -> Vec<&str> {
91 0 : self.0.keys().map(|value| value.as_str()).collect()
92 0 : }
93 : }
94 0 : let extractor = HeaderExtractor(headers);
95 0 : opentelemetry::global::get_text_map_propagator(|propagator| propagator.extract(&extractor))
96 0 : }
|