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