@@ -24,7 +24,7 @@ use core::fmt;
2424use opentelemetry_sdk:: error:: OTelSdkResult ;
2525
2626use opentelemetry_sdk:: metrics:: {
27- data:: ResourceMetrics , exporter:: PushMetricExporter , Temporality ,
27+ data:: ResourceMetrics , exporter:: PushMetricExporter , HistogramAggregation , Temporality ,
2828} ;
2929use std:: fmt:: { Debug , Formatter } ;
3030use std:: time:: Duration ;
@@ -45,12 +45,18 @@ pub const OTEL_EXPORTER_OTLP_METRICS_HEADERS: &str = "OTEL_EXPORTER_OTLP_METRICS
4545/// Temporality preference for metrics, defaults to cumulative.
4646pub const OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE : & str =
4747 "OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE" ;
48+ /// Default histogram aggregation for metrics.
49+ /// Valid values: `explicit_bucket_histogram`, `base2_exponential_bucket_histogram` (case-insensitive).
50+ /// Defaults to `explicit_bucket_histogram`.
51+ pub const OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION : & str =
52+ "OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION" ;
4853
4954/// A builder for creating a new [MetricExporter].
5055#[ derive( Debug , Default , Clone ) ]
5156pub struct MetricExporterBuilder < C > {
5257 client : C ,
5358 temporality : Option < Temporality > ,
59+ histogram_aggregation : Option < HistogramAggregation > ,
5460}
5561
5662impl MetricExporterBuilder < NoExporterBuilderSet > {
@@ -89,6 +95,7 @@ impl<C> MetricExporterBuilder<C> {
8995 MetricExporterBuilder {
9096 client : TonicExporterBuilderSet ( TonicExporterBuilder :: default ( ) ) ,
9197 temporality : self . temporality ,
98+ histogram_aggregation : self . histogram_aggregation ,
9299 }
93100 }
94101
@@ -98,6 +105,7 @@ impl<C> MetricExporterBuilder<C> {
98105 MetricExporterBuilder {
99106 client : HttpExporterBuilderSet ( HttpExporterBuilder :: default ( ) ) ,
100107 temporality : self . temporality ,
108+ histogram_aggregation : self . histogram_aggregation ,
101109 }
102110 }
103111
@@ -108,6 +116,25 @@ impl<C> MetricExporterBuilder<C> {
108116 MetricExporterBuilder {
109117 client : self . client ,
110118 temporality : Some ( temporality) ,
119+ histogram_aggregation : self . histogram_aggregation ,
120+ }
121+ }
122+
123+ /// Set the default histogram aggregation for the metrics.
124+ ///
125+ /// Valid values: [HistogramAggregation::ExplicitBucketHistogram] (default),
126+ /// [HistogramAggregation::Base2ExponentialBucketHistogram].
127+ ///
128+ /// Note: Programmatically setting this will override any value set via the
129+ /// `OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION` environment variable.
130+ pub fn with_histogram_aggregation (
131+ self ,
132+ histogram_aggregation : HistogramAggregation ,
133+ ) -> MetricExporterBuilder < C > {
134+ MetricExporterBuilder {
135+ client : self . client ,
136+ temporality : self . temporality ,
137+ histogram_aggregation : Some ( histogram_aggregation) ,
111138 }
112139 }
113140}
@@ -132,12 +159,40 @@ fn resolve_temporality(provided: Option<Temporality>) -> Result<Temporality, Exp
132159 Ok ( Temporality :: default ( ) )
133160}
134161
162+ /// Resolve histogram aggregation with priority:
163+ /// 1. Provided config value
164+ /// 2. OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION environment variable
165+ /// 3. Default (ExplicitBucketHistogram)
166+ #[ cfg( any( feature = "http-proto" , feature = "http-json" , feature = "grpc-tonic" ) ) ]
167+ fn resolve_histogram_aggregation (
168+ provided : Option < HistogramAggregation > ,
169+ ) -> Result < HistogramAggregation , ExporterBuildError > {
170+ if let Some ( histogram_aggregation) = provided {
171+ return Ok ( histogram_aggregation) ;
172+ }
173+ if let Ok ( val) = std:: env:: var ( OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION ) {
174+ return val
175+ . parse :: < HistogramAggregation > ( )
176+ . map_err ( |_| ExporterBuildError :: InvalidConfig {
177+ name : OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION . to_string ( ) ,
178+ reason : format ! (
179+ "Invalid value '{val}'. Expected: explicit_bucket_histogram or base2_exponential_bucket_histogram"
180+ ) ,
181+ } ) ;
182+ }
183+ Ok ( HistogramAggregation :: default ( ) )
184+ }
185+
135186#[ cfg( feature = "grpc-tonic" ) ]
136187impl MetricExporterBuilder < TonicExporterBuilderSet > {
137188 /// Build the [MetricExporter] with the gRPC Tonic transport.
138189 pub fn build ( self ) -> Result < MetricExporter , ExporterBuildError > {
139190 let temporality = resolve_temporality ( self . temporality ) ?;
140- let exporter = self . client . 0 . build_metrics_exporter ( temporality) ?;
191+ let histogram_aggregation = resolve_histogram_aggregation ( self . histogram_aggregation ) ?;
192+ let exporter = self
193+ . client
194+ . 0
195+ . build_metrics_exporter ( temporality, histogram_aggregation) ?;
141196 opentelemetry:: otel_debug!( name: "MetricExporterBuilt" ) ;
142197 Ok ( exporter)
143198 }
@@ -148,7 +203,11 @@ impl MetricExporterBuilder<HttpExporterBuilderSet> {
148203 /// Build the [MetricExporter] with the HTTP transport.
149204 pub fn build ( self ) -> Result < MetricExporter , ExporterBuildError > {
150205 let temporality = resolve_temporality ( self . temporality ) ?;
151- let exporter = self . client . 0 . build_metrics_exporter ( temporality) ?;
206+ let histogram_aggregation = resolve_histogram_aggregation ( self . histogram_aggregation ) ?;
207+ let exporter = self
208+ . client
209+ . 0
210+ . build_metrics_exporter ( temporality, histogram_aggregation) ?;
152211 Ok ( exporter)
153212 }
154213}
@@ -194,6 +253,7 @@ pub(crate) trait MetricsClient: fmt::Debug + Send + Sync + 'static {
194253pub struct MetricExporter {
195254 client : SupportedTransportClient ,
196255 temporality : Temporality ,
256+ histogram_aggregation : HistogramAggregation ,
197257}
198258
199259#[ derive( Debug ) ]
@@ -241,6 +301,10 @@ impl PushMetricExporter for MetricExporter {
241301 fn temporality ( & self ) -> Temporality {
242302 self . temporality
243303 }
304+
305+ fn default_histogram_aggregation ( & self ) -> HistogramAggregation {
306+ self . histogram_aggregation
307+ }
244308}
245309
246310impl MetricExporter {
@@ -253,21 +317,25 @@ impl MetricExporter {
253317 pub ( crate ) fn from_tonic (
254318 client : crate :: exporter:: tonic:: metrics:: TonicMetricsClient ,
255319 temporality : Temporality ,
320+ histogram_aggregation : HistogramAggregation ,
256321 ) -> Self {
257322 Self {
258323 client : SupportedTransportClient :: Tonic ( client) ,
259324 temporality,
325+ histogram_aggregation,
260326 }
261327 }
262328
263329 #[ cfg( any( feature = "http-proto" , feature = "http-json" ) ) ]
264330 pub ( crate ) fn from_http (
265331 client : crate :: exporter:: http:: OtlpHttpClient ,
266332 temporality : Temporality ,
333+ histogram_aggregation : HistogramAggregation ,
267334 ) -> Self {
268335 Self {
269336 client : SupportedTransportClient :: Http ( client) ,
270337 temporality,
338+ histogram_aggregation,
271339 }
272340 }
273341}
@@ -373,4 +441,97 @@ mod tests {
373441 assert_eq ! ( result, Temporality :: Cumulative ) ;
374442 } ) ;
375443 }
444+
445+ #[ test]
446+ fn histogram_aggregation_code_config_overrides_env_var ( ) {
447+ run_env_test (
448+ vec ! [ (
449+ OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION ,
450+ "explicit_bucket_histogram" ,
451+ ) ] ,
452+ || {
453+ let result = resolve_histogram_aggregation ( Some (
454+ HistogramAggregation :: Base2ExponentialBucketHistogram ,
455+ ) )
456+ . unwrap ( ) ;
457+ assert_eq ! (
458+ result,
459+ HistogramAggregation :: Base2ExponentialBucketHistogram
460+ ) ;
461+ } ,
462+ ) ;
463+ }
464+
465+ #[ test]
466+ fn histogram_aggregation_env_var_sets_explicit_bucket ( ) {
467+ run_env_test (
468+ vec ! [ (
469+ OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION ,
470+ "explicit_bucket_histogram" ,
471+ ) ] ,
472+ || {
473+ let result = resolve_histogram_aggregation ( None ) . unwrap ( ) ;
474+ assert_eq ! ( result, HistogramAggregation :: ExplicitBucketHistogram ) ;
475+ } ,
476+ ) ;
477+ }
478+
479+ #[ test]
480+ fn histogram_aggregation_env_var_sets_base2_exponential ( ) {
481+ run_env_test (
482+ vec ! [ (
483+ OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION ,
484+ "base2_exponential_bucket_histogram" ,
485+ ) ] ,
486+ || {
487+ let result = resolve_histogram_aggregation ( None ) . unwrap ( ) ;
488+ assert_eq ! (
489+ result,
490+ HistogramAggregation :: Base2ExponentialBucketHistogram
491+ ) ;
492+ } ,
493+ ) ;
494+ }
495+
496+ #[ test]
497+ fn histogram_aggregation_env_var_case_insensitive ( ) {
498+ run_env_test (
499+ vec ! [ (
500+ OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION ,
501+ "BASE2_EXPONENTIAL_BUCKET_HISTOGRAM" ,
502+ ) ] ,
503+ || {
504+ let result = resolve_histogram_aggregation ( None ) . unwrap ( ) ;
505+ assert_eq ! (
506+ result,
507+ HistogramAggregation :: Base2ExponentialBucketHistogram
508+ ) ;
509+ } ,
510+ ) ;
511+ }
512+
513+ #[ test]
514+ fn histogram_aggregation_invalid_env_var_returns_error ( ) {
515+ run_env_test (
516+ vec ! [ (
517+ OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION ,
518+ "invalid" ,
519+ ) ] ,
520+ || {
521+ let result = resolve_histogram_aggregation ( None ) ;
522+ assert ! ( result. is_err( ) ) ;
523+ } ,
524+ ) ;
525+ }
526+
527+ #[ test]
528+ fn histogram_aggregation_default_when_nothing_set ( ) {
529+ temp_env:: with_var_unset (
530+ OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION ,
531+ || {
532+ let result = resolve_histogram_aggregation ( None ) . unwrap ( ) ;
533+ assert_eq ! ( result, HistogramAggregation :: ExplicitBucketHistogram ) ;
534+ } ,
535+ ) ;
536+ }
376537}
0 commit comments