Skip to main content

spin_telemetry/
metrics.rs

1use anyhow::{Result, bail};
2use opentelemetry::global;
3use opentelemetry_otlp::WithHttpConfig;
4use opentelemetry_sdk::{
5    Resource,
6    metrics::{SdkMeterProvider, periodic_reader_with_async_runtime::PeriodicReader},
7    resource::{EnvResourceDetector, ResourceDetector, TelemetryResourceDetector},
8    runtime::Tokio,
9};
10use tracing::Subscriber;
11use tracing_opentelemetry::MetricsLayer;
12use tracing_subscriber::{Layer, registry::LookupSpan};
13
14use crate::{detector::SpinResourceDetector, env::OtlpProtocol};
15
16/// Constructs a layer for the tracing subscriber that sends metrics to an OTEL collector.
17///
18/// It pulls OTEL configuration from the environment based on the variables defined
19/// [here](https://opentelemetry.io/docs/specs/otel/protocol/exporter/) and
20/// [here](https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#general-sdk-configuration).
21pub(crate) fn otel_metrics_layer<S: Subscriber + for<'span> LookupSpan<'span>>(
22    spin_version: String,
23) -> Result<impl Layer<S>> {
24    let resource = Resource::builder()
25        .with_detectors(&[
26            // Set service.name from env OTEL_SERVICE_NAME > env OTEL_RESOURCE_ATTRIBUTES > spin
27            // Set service.version from Spin metadata
28            Box::new(SpinResourceDetector::new(spin_version)) as Box<dyn ResourceDetector>,
29            // Sets fields from env OTEL_RESOURCE_ATTRIBUTES
30            Box::new(EnvResourceDetector::new()),
31            // Sets telemetry.sdk{name, language, version}
32            Box::new(TelemetryResourceDetector),
33        ])
34        .build();
35
36    // This will configure the exporter based on the OTEL_EXPORTER_* environment variables. We
37    // currently default to using the HTTP exporter but in the future we could select off of the
38    // combination of OTEL_EXPORTER_OTLP_PROTOCOL and OTEL_EXPORTER_OTLP_TRACES_PROTOCOL to
39    // determine whether we should use http/protobuf or grpc.
40    let exporter = match OtlpProtocol::metrics_protocol_from_env() {
41        OtlpProtocol::Grpc => opentelemetry_otlp::MetricExporter::builder()
42            .with_tonic()
43            .build()?,
44        OtlpProtocol::HttpProtobuf => opentelemetry_otlp::MetricExporter::builder()
45            .with_http()
46            .with_http_client(crate::rustls_reqwest_client()?)
47            .build()?,
48        OtlpProtocol::HttpJson => bail!("http/json OTLP protocol is not supported"),
49    };
50
51    let reader = PeriodicReader::builder(exporter, Tokio).build();
52    let meter_provider = SdkMeterProvider::builder()
53        .with_reader(reader)
54        .with_resource(resource)
55        .build();
56
57    global::set_meter_provider(meter_provider.clone());
58
59    Ok(MetricsLayer::new(meter_provider))
60}
61
62#[macro_export]
63/// Records an increment to the named counter with the given attributes.
64///
65/// The increment may only be an i64 or f64. You must not mix types for the same metric.
66///
67/// Takes advantage of counter support in [tracing-opentelemetry](https://docs.rs/tracing-opentelemetry/0.32.0/tracing_opentelemetry/struct.MetricsLayer.html).
68///
69/// ```no_run
70/// # use spin_telemetry::metrics::counter;
71/// counter!(spin.metric_name = 1, metric_attribute = "value");
72/// ```
73macro_rules! counter {
74    ($metric:ident $(. $suffixes:ident)*  = $metric_value:expr $(, $attrs:ident=$values:expr)*) => {
75        tracing::trace!(counter.$metric $(. $suffixes)* = $metric_value $(, $attrs=$values)*);
76    }
77}
78
79#[macro_export]
80/// Adds an additional value to the distribution of the named histogram with the given attributes.
81///
82/// The increment may only be an i64 or f64. You must not mix types for the same metric.
83///
84/// Takes advantage of histogram support in [tracing-opentelemetry](https://docs.rs/tracing-opentelemetry/0.32.0/tracing_opentelemetry/struct.MetricsLayer.html).
85///
86/// ```no_run
87/// # use spin_telemetry::metrics::histogram;
88/// histogram!(spin.metric_name = 1.5, metric_attribute = "value");
89/// ```
90macro_rules! histogram {
91    ($metric:ident $(. $suffixes:ident)*  = $metric_value:expr $(, $attrs:ident=$values:expr)*) => {
92        tracing::trace!(histogram.$metric $(. $suffixes)* = $metric_value $(, $attrs=$values)*);
93    }
94}
95
96#[macro_export]
97/// Records an increment to the named monotonic counter with the given attributes.
98///
99/// The increment may only be a positive i64 or f64. You must not mix types for the same metric.
100///
101/// Takes advantage of monotonic counter support in [tracing-opentelemetry](https://docs.rs/tracing-opentelemetry/0.32.0/tracing_opentelemetry/struct.MetricsLayer.html).
102///
103/// ```no_run
104/// # use spin_telemetry::metrics::monotonic_counter;
105/// monotonic_counter!(spin.metric_name = 1, metric_attribute = "value");
106/// ```
107macro_rules! monotonic_counter {
108    ($metric:ident $(. $suffixes:ident)*  = $metric_value:expr $(, $attrs:ident=$values:expr)*) => {
109        tracing::trace!(monotonic_counter.$metric $(. $suffixes)* = $metric_value $(, $attrs=$values)*);
110    }
111}
112
113#[macro_export]
114/// Records an increment to the named monotonic counter with the given attributes.
115///
116/// The increment may only be a positive i64 or f64. You must not mix types for the same metric.
117///
118/// Takes advantage of gauge support in [tracing-opentelemetry](https://docs.rs/tracing-opentelemetry/0.32.0/tracing_opentelemetry/struct.MetricsLayer.html).
119///
120/// ```no_run
121/// # use spin_telemetry::metrics::gauge;
122/// gauge!(spin.metric_name = 1, metric_attribute = "value");
123/// ```
124macro_rules! gauge {
125    ($metric:ident $(. $suffixes:ident)*  = $metric_value:expr $(, $attrs:ident=$values:expr)*) => {
126        tracing::trace!(gauge.$metric $(. $suffixes)* = $metric_value $(, $attrs=$values)*);
127    }
128}
129
130pub use counter;
131pub use gauge;
132pub use histogram;
133pub use monotonic_counter;