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/// ```no_run
65/// # use spin_telemetry::metrics::counter;
66/// counter!(spin.metric_name = 1, metric_attribute = "value");
67/// ```
68macro_rules! counter {
69    ($metric:ident $(. $suffixes:ident)*  = $metric_value:expr $(, $attrs:ident=$values:expr)*) => {
70        tracing::trace!(counter.$metric $(. $suffixes)* = $metric_value $(, $attrs=$values)*);
71    }
72}
73
74#[macro_export]
75/// Adds an additional value to the distribution of the named histogram with the given attributes.
76///
77/// The increment may only be an i64 or f64. You must not mix types for the same metric.
78///
79/// ```no_run
80/// # use spin_telemetry::metrics::histogram;
81/// histogram!(spin.metric_name = 1.5, metric_attribute = "value");
82/// ```
83macro_rules! histogram {
84    ($metric:ident $(. $suffixes:ident)*  = $metric_value:expr $(, $attrs:ident=$values:expr)*) => {
85        tracing::trace!(histogram.$metric $(. $suffixes)* = $metric_value $(, $attrs=$values)*);
86    }
87}
88
89#[macro_export]
90/// Records an increment to the named monotonic counter with the given attributes.
91///
92/// The increment may only be a positive i64 or f64. You must not mix types for the same metric.
93///
94/// ```no_run
95/// # use spin_telemetry::metrics::monotonic_counter;
96/// monotonic_counter!(spin.metric_name = 1, metric_attribute = "value");
97/// ```
98macro_rules! monotonic_counter {
99    ($metric:ident $(. $suffixes:ident)*  = $metric_value:expr $(, $attrs:ident=$values:expr)*) => {
100        tracing::trace!(monotonic_counter.$metric $(. $suffixes)* = $metric_value $(, $attrs=$values)*);
101    }
102}
103
104pub use counter;
105pub use histogram;
106pub use monotonic_counter;