|
| 1 | +use std::time::Duration; |
| 2 | + |
| 3 | +use anyhow::{bail, Result}; |
| 4 | +use opentelemetry_otlp::MetricsExporterBuilder; |
| 5 | +use opentelemetry_sdk::{ |
| 6 | + metrics::{ |
| 7 | + reader::{DefaultAggregationSelector, DefaultTemporalitySelector}, |
| 8 | + PeriodicReader, SdkMeterProvider, |
| 9 | + }, |
| 10 | + resource::{EnvResourceDetector, TelemetryResourceDetector}, |
| 11 | + runtime, Resource, |
| 12 | +}; |
| 13 | +use tracing_opentelemetry::{MetricsLayer, OpenTelemetryLayer}; |
| 14 | +use tracing_subscriber::{filter::Filtered, layer::Layered, EnvFilter, Registry}; |
| 15 | + |
| 16 | +use crate::{detector::SpinResourceDetector, env::OtlpProtocol}; |
| 17 | + |
| 18 | +/// Constructs a layer for the tracing subscriber that sends metrics to an OTEL collector. |
| 19 | +/// |
| 20 | +/// It pulls OTEL configuration from the environment based on the variables defined |
| 21 | +/// [here](https://opentelemetry.io/docs/specs/otel/protocol/exporter/) and |
| 22 | +/// [here](https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#general-sdk-configuration). |
| 23 | +pub(crate) fn otel_metrics_layer(spin_version: String) -> Result<CustomMetricsLayer> { |
| 24 | + let resource = Resource::from_detectors( |
| 25 | + Duration::from_secs(5), |
| 26 | + vec![ |
| 27 | + // Set service.name from env OTEL_SERVICE_NAME > env OTEL_RESOURCE_ATTRIBUTES > spin |
| 28 | + // Set service.version from Spin metadata |
| 29 | + Box::new(SpinResourceDetector::new(spin_version)), |
| 30 | + // Sets fields from env OTEL_RESOURCE_ATTRIBUTES |
| 31 | + Box::new(EnvResourceDetector::new()), |
| 32 | + // Sets telemetry.sdk{name, language, version} |
| 33 | + Box::new(TelemetryResourceDetector), |
| 34 | + ], |
| 35 | + ); |
| 36 | + |
| 37 | + // This will configure the exporter based on the OTEL_EXPORTER_* environment variables. We |
| 38 | + // currently default to using the HTTP exporter but in the future we could select off of the |
| 39 | + // combination of OTEL_EXPORTER_OTLP_PROTOCOL and OTEL_EXPORTER_OTLP_TRACES_PROTOCOL to |
| 40 | + // determine whether we should use http/protobuf or grpc. |
| 41 | + let exporter_builder: MetricsExporterBuilder = match OtlpProtocol::metrics_protocol_from_env() { |
| 42 | + OtlpProtocol::Grpc => opentelemetry_otlp::new_exporter().tonic().into(), |
| 43 | + OtlpProtocol::HttpProtobuf => opentelemetry_otlp::new_exporter().http().into(), |
| 44 | + OtlpProtocol::HttpJson => bail!("http/json OTLP protocol is not supported"), |
| 45 | + }; |
| 46 | + let exporter = exporter_builder.build_metrics_exporter( |
| 47 | + Box::new(DefaultTemporalitySelector::new()), |
| 48 | + Box::new(DefaultAggregationSelector::new()), |
| 49 | + )?; |
| 50 | + |
| 51 | + let reader = PeriodicReader::builder(exporter, runtime::Tokio).build(); |
| 52 | + let meter_provider = SdkMeterProvider::builder() |
| 53 | + .with_reader(reader) |
| 54 | + .with_resource(resource) |
| 55 | + .build(); |
| 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 | +/// ```no_run |
| 66 | +/// # use spin_telemetry::metrics::counter; |
| 67 | +/// counter!(spin.metric_name = 1, metric_attribute = "value"); |
| 68 | +/// ``` |
| 69 | +macro_rules! counter { |
| 70 | + ($metric:ident $(. $suffixes:ident)* = $metric_value:expr $(, $attrs:ident=$values:expr)*) => { |
| 71 | + tracing::trace!(counter.$metric $(. $suffixes)* = $metric_value $(, $attrs=$values)*); |
| 72 | + } |
| 73 | +} |
| 74 | + |
| 75 | +#[macro_export] |
| 76 | +/// Adds an additional value to the distribution of the named histogram with the given attributes. |
| 77 | +/// |
| 78 | +/// The increment may only be an i64 or f64. You must not mix types for the same metric. |
| 79 | +/// |
| 80 | +/// ```no_run |
| 81 | +/// # use spin_telemetry::metrics::histogram; |
| 82 | +/// histogram!(spin.metric_name = 1.5, metric_attribute = "value"); |
| 83 | +/// ``` |
| 84 | +macro_rules! histogram { |
| 85 | + ($metric:ident $(. $suffixes:ident)* = $metric_value:expr $(, $attrs:ident=$values:expr)*) => { |
| 86 | + tracing::trace!(histogram.$metric $(. $suffixes)* = $metric_value $(, $attrs=$values)*); |
| 87 | + } |
| 88 | +} |
| 89 | + |
| 90 | +#[macro_export] |
| 91 | +/// Records an increment to the named monotonic counter with the given attributes. |
| 92 | +/// |
| 93 | +/// The increment may only be a positive i64 or f64. You must not mix types for the same metric. |
| 94 | +/// |
| 95 | +/// ```no_run |
| 96 | +/// # use spin_telemetry::metrics::monotonic_counter; |
| 97 | +/// monotonic_counter!(spin.metric_name = 1, metric_attribute = "value"); |
| 98 | +/// ``` |
| 99 | +macro_rules! monotonic_counter { |
| 100 | + ($metric:ident $(. $suffixes:ident)* = $metric_value:expr $(, $attrs:ident=$values:expr)*) => { |
| 101 | + tracing::trace!(monotonic_counter.$metric $(. $suffixes)* = $metric_value $(, $attrs=$values)*); |
| 102 | + } |
| 103 | +} |
| 104 | + |
| 105 | +pub use counter; |
| 106 | +pub use histogram; |
| 107 | +pub use monotonic_counter; |
| 108 | + |
| 109 | +/// This really large type alias is require to make the registry.with() pattern happy. |
| 110 | +type CustomMetricsLayer = MetricsLayer< |
| 111 | + Layered< |
| 112 | + Option< |
| 113 | + Filtered< |
| 114 | + OpenTelemetryLayer<Registry, opentelemetry_sdk::trace::Tracer>, |
| 115 | + EnvFilter, |
| 116 | + Registry, |
| 117 | + >, |
| 118 | + >, |
| 119 | + Registry, |
| 120 | + >, |
| 121 | +>; |
0 commit comments