trillium_opentelemetry/instrument.rs
1use crate::{Metrics, Trace, instrumentation_scope};
2use opentelemetry::global::{BoxedTracer, ObjectSafeTracer};
3use std::{borrow::Cow, sync::Arc};
4use trillium::{Conn, HeaderName};
5use trillium_macros::Handler;
6
7/// a handler to send both traces and metrics in accordances with [semantic conventions for
8/// http](https://opentelemetry.io/docs/specs/semconv/http/).
9///
10/// This is composed of a [`Trace`] handler and [`Metrics`] handler.
11#[derive(Debug, Handler)]
12pub struct Instrument((Trace<BoxedTracer>, Metrics));
13
14/// construct an [`Instrument`] with the provided meter and tracer
15pub fn instrument<T: ObjectSafeTracer + Send + Sync + 'static>(
16 meter: impl Into<Metrics>,
17 tracer: T,
18) -> Instrument {
19 Instrument::new(meter, tracer)
20}
21
22impl Instrument {
23 /// construct a new [`Instrument`] with the provided meter and tracer
24 pub fn new(
25 meter: impl Into<Metrics>,
26 tracer: impl ObjectSafeTracer + Send + Sync + 'static,
27 ) -> Self {
28 Self((Trace::new(BoxedTracer::new(Box::new(tracer))), meter.into()))
29 }
30
31 /// provides a route specification
32 ///
33 /// in order to avoid forcing anyone to use a particular router, this is provided as a
34 /// configuration hook.
35 ///
36 /// for use with [`trillium-router`](https://docs.trillium.rs/trillium_router/index.html),
37 /// ```
38 /// use trillium_router::RouterConnExt;
39 /// trillium_opentelemetry::Metrics::new(&opentelemetry::global::meter("example"))
40 /// .with_route(|conn| conn.route().map(|r| r.to_string().into()));
41 /// ```
42 pub fn with_route<F>(mut self, route: F) -> Self
43 where
44 F: Fn(&Conn) -> Option<Cow<'static, str>> + Send + Sync + 'static,
45 {
46 let route = Arc::new(route);
47 self.0.0.route = Some(route.clone());
48 self.0.1.route = Some(route);
49 self
50 }
51
52 /// Provides an optional low-cardinality error type specification to the metrics collector.
53 ///
54 /// The implementation of this is application specific, but will often look like checking the
55 /// [`Conn::state`] for an error enum and mapping that to a low-cardinality `&'static str`.
56 pub fn with_error_type<F>(mut self, error_type: F) -> Self
57 where
58 F: Fn(&Conn) -> Option<Cow<'static, str>> + Send + Sync + 'static,
59 {
60 let error_type = Arc::new(error_type);
61 self.0.0.error_type = Some(error_type.clone());
62 self.0.1.error_type = Some(error_type);
63 self
64 }
65
66 /// Provides a callback for `server.address` and `server.port` attributes to be used in metrics
67 /// attributes. This has no effect on tracing span attributes, where `server.address` and
68 /// `server.port` are always enabled.
69 ///
70 /// These should be set based on request headers according to the [OpenTelemetry HTTP semantic
71 /// conventions][semconv-server-address-port].
72 ///
73 /// It is not recommended to enable this when the server is exposed to clients outside of your
74 /// control, as request headers could arbitrarily increase the cardinality of these attributes.
75 ///
76 /// [semconv-server-address-port]:
77 /// https://opentelemetry.io/docs/specs/semconv/http/http-spans/#setting-serveraddress-and-serverport-attributes
78 pub fn with_metrics_server_address_and_port<F>(mut self, server_address_and_port: F) -> Self
79 where
80 F: Fn(&Conn) -> Option<(Cow<'static, str>, u16)> + Send + Sync + 'static,
81 {
82 self.0.1.server_address_and_port = Some(Arc::new(server_address_and_port));
83 self
84 }
85
86 /// Specify a list of request headers to include in the trace spans
87 pub fn with_headers(
88 mut self,
89 headers: impl IntoIterator<Item = impl Into<HeaderName<'static>>>,
90 ) -> Self {
91 self.0.0.headers = headers.into_iter().map(Into::into).collect();
92 self
93 }
94
95 /// Enable population of the local socket address and port in the trace spans.
96 ///
97 /// This populates the `network.local.address` and `network.local.port` attributes.
98 pub fn with_local_address_and_port(mut self) -> Self {
99 self.0.0.enable_local_address_and_port = true;
100 self
101 }
102}
103
104/// The primary entrypoint if using [`opentelemetry::global`].
105///
106/// constructs a versioned meter and tracer with the name `"trillium-opentelemetry"`.
107pub fn instrument_global() -> Instrument {
108 let scope = instrumentation_scope();
109 instrument(
110 opentelemetry::global::meter_provider().meter_with_scope(scope.clone()),
111 opentelemetry::global::tracer_with_scope(scope),
112 )
113}