opentelemetry_otlp/
metric.rs

1//! OTEL metric exporter
2//!
3//! Defines a [MetricExporter] to send metric data to backend via OTLP protocol.
4//!
5
6#[cfg(any(feature = "http-proto", feature = "http-json", feature = "grpc-tonic"))]
7use crate::HasExportConfig;
8
9#[cfg(any(feature = "http-proto", feature = "http-json"))]
10use crate::{exporter::http::HttpExporterBuilder, HasHttpConfig, HttpExporterBuilderSet};
11
12#[cfg(feature = "grpc-tonic")]
13use crate::{exporter::tonic::TonicExporterBuilder, HasTonicConfig, TonicExporterBuilderSet};
14
15use crate::{ExporterBuildError, NoExporterBuilderSet};
16
17use core::fmt;
18use opentelemetry_sdk::error::OTelSdkResult;
19
20use opentelemetry_sdk::metrics::{
21    data::ResourceMetrics, exporter::PushMetricExporter, Temporality,
22};
23use std::fmt::{Debug, Formatter};
24use std::time::Duration;
25
26/// Target to which the exporter is going to send metrics, defaults to https://localhost:4317/v1/metrics.
27/// Learn about the relationship between this constant and default/spans/logs at
28/// <https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#endpoint-urls-for-otlphttp>
29pub const OTEL_EXPORTER_OTLP_METRICS_ENDPOINT: &str = "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT";
30/// Max waiting time for the backend to process each metrics batch, defaults to 10s.
31pub const OTEL_EXPORTER_OTLP_METRICS_TIMEOUT: &str = "OTEL_EXPORTER_OTLP_METRICS_TIMEOUT";
32/// Compression algorithm to use, defaults to none.
33pub const OTEL_EXPORTER_OTLP_METRICS_COMPRESSION: &str = "OTEL_EXPORTER_OTLP_METRICS_COMPRESSION";
34/// Key-value pairs to be used as headers associated with gRPC or HTTP requests
35/// for sending metrics.
36/// Example: `k1=v1,k2=v2`
37/// Note: this is only supported for HTTP.
38pub const OTEL_EXPORTER_OTLP_METRICS_HEADERS: &str = "OTEL_EXPORTER_OTLP_METRICS_HEADERS";
39
40/// A builder for creating a new [MetricExporter].
41#[derive(Debug, Default, Clone)]
42pub struct MetricExporterBuilder<C> {
43    client: C,
44    temporality: Temporality,
45}
46
47impl MetricExporterBuilder<NoExporterBuilderSet> {
48    /// Create a new [MetricExporterBuilder] with default settings.
49    pub fn new() -> Self {
50        MetricExporterBuilder::default()
51    }
52}
53
54impl<C> MetricExporterBuilder<C> {
55    /// With the gRPC Tonic transport.
56    #[cfg(feature = "grpc-tonic")]
57    pub fn with_tonic(self) -> MetricExporterBuilder<TonicExporterBuilderSet> {
58        MetricExporterBuilder {
59            client: TonicExporterBuilderSet(TonicExporterBuilder::default()),
60            temporality: self.temporality,
61        }
62    }
63
64    /// With the HTTP transport.
65    #[cfg(any(feature = "http-proto", feature = "http-json"))]
66    pub fn with_http(self) -> MetricExporterBuilder<HttpExporterBuilderSet> {
67        MetricExporterBuilder {
68            client: HttpExporterBuilderSet(HttpExporterBuilder::default()),
69            temporality: self.temporality,
70        }
71    }
72
73    /// Set the temporality for the metrics.
74    pub fn with_temporality(self, temporality: Temporality) -> MetricExporterBuilder<C> {
75        MetricExporterBuilder {
76            client: self.client,
77            temporality,
78        }
79    }
80}
81
82#[cfg(feature = "grpc-tonic")]
83impl MetricExporterBuilder<TonicExporterBuilderSet> {
84    /// Build the [MetricExporter] with the gRPC Tonic transport.
85    pub fn build(self) -> Result<MetricExporter, ExporterBuildError> {
86        let exporter = self.client.0.build_metrics_exporter(self.temporality)?;
87        opentelemetry::otel_debug!(name: "MetricExporterBuilt");
88        Ok(exporter)
89    }
90}
91
92#[cfg(any(feature = "http-proto", feature = "http-json"))]
93impl MetricExporterBuilder<HttpExporterBuilderSet> {
94    /// Build the [MetricExporter] with the HTTP transport.
95    pub fn build(self) -> Result<MetricExporter, ExporterBuildError> {
96        let exporter = self.client.0.build_metrics_exporter(self.temporality)?;
97        Ok(exporter)
98    }
99}
100
101#[cfg(feature = "grpc-tonic")]
102impl HasExportConfig for MetricExporterBuilder<TonicExporterBuilderSet> {
103    fn export_config(&mut self) -> &mut crate::ExportConfig {
104        &mut self.client.0.exporter_config
105    }
106}
107
108#[cfg(any(feature = "http-proto", feature = "http-json"))]
109impl HasExportConfig for MetricExporterBuilder<HttpExporterBuilderSet> {
110    fn export_config(&mut self) -> &mut crate::ExportConfig {
111        &mut self.client.0.exporter_config
112    }
113}
114
115#[cfg(feature = "grpc-tonic")]
116impl HasTonicConfig for MetricExporterBuilder<TonicExporterBuilderSet> {
117    fn tonic_config(&mut self) -> &mut crate::TonicConfig {
118        &mut self.client.0.tonic_config
119    }
120}
121
122#[cfg(any(feature = "http-proto", feature = "http-json"))]
123impl HasHttpConfig for MetricExporterBuilder<HttpExporterBuilderSet> {
124    fn http_client_config(&mut self) -> &mut crate::exporter::http::HttpConfig {
125        &mut self.client.0.http_config
126    }
127}
128
129/// An interface for OTLP metrics clients
130pub(crate) trait MetricsClient: fmt::Debug + Send + Sync + 'static {
131    fn export(
132        &self,
133        metrics: &ResourceMetrics,
134    ) -> impl std::future::Future<Output = OTelSdkResult> + Send;
135    fn shutdown(&self) -> OTelSdkResult;
136}
137
138/// Export metrics in OTEL format.
139pub struct MetricExporter {
140    client: SupportedTransportClient,
141    temporality: Temporality,
142}
143
144#[derive(Debug)]
145enum SupportedTransportClient {
146    #[cfg(feature = "grpc-tonic")]
147    Tonic(crate::exporter::tonic::metrics::TonicMetricsClient),
148    #[cfg(any(feature = "http-proto", feature = "http-json"))]
149    Http(crate::exporter::http::OtlpHttpClient),
150}
151
152impl Debug for MetricExporter {
153    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
154        f.debug_struct("MetricExporter").finish()
155    }
156}
157
158impl PushMetricExporter for MetricExporter {
159    async fn export(&self, metrics: &ResourceMetrics) -> OTelSdkResult {
160        match &self.client {
161            #[cfg(feature = "grpc-tonic")]
162            SupportedTransportClient::Tonic(client) => client.export(metrics).await,
163            #[cfg(any(feature = "http-proto", feature = "http-json"))]
164            SupportedTransportClient::Http(client) => client.export(metrics).await,
165        }
166    }
167
168    fn force_flush(&self) -> OTelSdkResult {
169        // this component is stateless
170        Ok(())
171    }
172
173    fn shutdown(&self) -> OTelSdkResult {
174        self.shutdown_with_timeout(Duration::from_secs(5))
175    }
176
177    fn shutdown_with_timeout(&self, _timeout: std::time::Duration) -> OTelSdkResult {
178        match &self.client {
179            #[cfg(feature = "grpc-tonic")]
180            SupportedTransportClient::Tonic(client) => client.shutdown(),
181            #[cfg(any(feature = "http-proto", feature = "http-json"))]
182            SupportedTransportClient::Http(client) => client.shutdown(),
183        }
184    }
185
186    fn temporality(&self) -> Temporality {
187        self.temporality
188    }
189}
190
191impl MetricExporter {
192    /// Obtain a builder to configure a [MetricExporter].
193    pub fn builder() -> MetricExporterBuilder<NoExporterBuilderSet> {
194        MetricExporterBuilder::default()
195    }
196
197    #[cfg(feature = "grpc-tonic")]
198    pub(crate) fn from_tonic(
199        client: crate::exporter::tonic::metrics::TonicMetricsClient,
200        temporality: Temporality,
201    ) -> Self {
202        Self {
203            client: SupportedTransportClient::Tonic(client),
204            temporality,
205        }
206    }
207
208    #[cfg(any(feature = "http-proto", feature = "http-json"))]
209    pub(crate) fn from_http(
210        client: crate::exporter::http::OtlpHttpClient,
211        temporality: Temporality,
212    ) -> Self {
213        Self {
214            client: SupportedTransportClient::Http(client),
215            temporality,
216        }
217    }
218}