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