Skip to main content

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}