use std::time::Duration;
use opentelemetry_sdk::Resource;
use opentelemetry_sdk::metrics::{MeterProviderBuilder, PeriodicReader, SdkMeterProvider};
use crate::InitError;
use crate::config::{MetricExporter, MetricReader, OpenTelemetryConfig};
pub(crate) fn build_meter_provider(
config: &OpenTelemetryConfig,
resource: Resource,
mut builder: MeterProviderBuilder,
) -> Result<SdkMeterProvider, InitError> {
builder = builder.with_resource(resource);
for reader_config in &config.meter_provider.readers {
builder = add_metric_reader(builder, reader_config)?;
}
for view_config in &config.meter_provider.views {
builder = builder.with_view(view_config.to_view());
}
Ok(builder.build())
}
fn add_metric_reader(
builder: MeterProviderBuilder,
config: &MetricReader,
) -> Result<MeterProviderBuilder, InitError> {
match config {
MetricReader::Periodic(periodic_config) => {
add_periodic_reader(builder, &periodic_config.exporter, periodic_config.interval)
}
MetricReader::Pull(pull_config) => {
add_periodic_reader(builder, &pull_config.exporter, 1000)
}
}
}
fn add_periodic_reader(
#[allow(unused_variables)] builder: MeterProviderBuilder,
exporter_config: &MetricExporter,
#[allow(unused_variables)] interval_ms: u64,
) -> Result<MeterProviderBuilder, InitError> {
let interval = Duration::from_millis(interval_ms);
match exporter_config {
#[cfg(feature = "otlp")]
MetricExporter::OtlpHttp(config) => {
let exporter = crate::exporters::otlp::build_otlp_metric_exporter(config)?;
let reader = PeriodicReader::builder(exporter)
.with_interval(interval)
.build();
Ok(builder.with_reader(reader))
}
#[cfg(feature = "otlp")]
MetricExporter::OtlpGrpc(config) => {
let exporter = crate::exporters::otlp::build_otlp_grpc_metric_exporter(config)?;
let reader = PeriodicReader::builder(exporter)
.with_interval(interval)
.build();
Ok(builder.with_reader(reader))
}
#[cfg(feature = "honeycomb")]
MetricExporter::HoneycombHttp(config) => {
let exporter = crate::exporters::honeycomb::build_http_metric_exporter(config)?;
let reader = PeriodicReader::builder(exporter)
.with_interval(interval)
.build();
Ok(builder.with_reader(reader))
}
#[cfg(feature = "honeycomb")]
MetricExporter::HoneycombGrpc(config) => {
let exporter = crate::exporters::honeycomb::build_grpc_metric_exporter(config)?;
let reader = PeriodicReader::builder(exporter)
.with_interval(interval)
.build();
Ok(builder.with_reader(reader))
}
#[cfg(feature = "new-relic")]
MetricExporter::NewRelicHttp(config) => {
let exporter = crate::exporters::new_relic::build_http_metric_exporter(config)?;
let reader = PeriodicReader::builder(exporter)
.with_interval(interval)
.build();
Ok(builder.with_reader(reader))
}
#[cfg(feature = "new-relic")]
MetricExporter::NewRelicGrpc(config) => {
let exporter = crate::exporters::new_relic::build_grpc_metric_exporter(config)?;
let reader = PeriodicReader::builder(exporter)
.with_interval(interval)
.build();
Ok(builder.with_reader(reader))
}
#[cfg(feature = "grafana-cloud")]
MetricExporter::GrafanaCloud(config) => {
let exporter = crate::exporters::grafana_cloud::build_http_metric_exporter(config)?;
let reader = PeriodicReader::builder(exporter)
.with_interval(interval)
.build();
Ok(builder.with_reader(reader))
}
#[cfg(feature = "datadog-otlp")]
MetricExporter::DatadogOtlpHttp(config) => {
let exporter = crate::exporters::datadog_otlp::build_http_metric_exporter(config)?;
let reader = PeriodicReader::builder(exporter)
.with_interval(interval)
.build();
Ok(builder.with_reader(reader))
}
#[cfg(feature = "datadog-otlp")]
MetricExporter::DatadogOtlpGrpc(config) => {
let exporter = crate::exporters::datadog_otlp::build_grpc_metric_exporter(config)?;
let reader = PeriodicReader::builder(exporter)
.with_interval(interval)
.build();
Ok(builder.with_reader(reader))
}
MetricExporter::Console(config) => {
let exporter =
apollo_opentelemetry_stdout::__private::StdoutMetricExporter::with_writer(
config.format.into(),
config.target.into(),
config.use_color(),
);
let reader = PeriodicReader::builder(exporter)
.with_interval(interval)
.build();
Ok(builder.with_reader(reader))
}
}
}
#[cfg(test)]
mod tests {
use apollo_configuration::parse_yaml;
use opentelemetry_sdk::metrics::SdkMeterProvider;
use super::*;
#[cfg(feature = "otlp")]
#[test]
fn build_meter_provider_with_otlp_http() {
let config: OpenTelemetryConfig = parse_yaml(
indoc::indoc! {"
meter_provider:
readers:
- periodic:
exporter:
otlp_http:
endpoint: http://localhost:4318
interval: 60000
"},
&Default::default(),
)
.unwrap();
let builder = SdkMeterProvider::builder();
let result = build_meter_provider(&config, (&config.resource).into(), builder);
assert!(result.is_ok());
}
#[test]
fn build_meter_provider_with_resource() {
let config: OpenTelemetryConfig = parse_yaml(
indoc::indoc! {"
resource:
attributes:
- name: service.name
value: test-service
meter_provider:
readers:
- periodic:
exporter:
console: {}
interval: 60000
"},
&Default::default(),
)
.unwrap();
let builder = SdkMeterProvider::builder();
let result = build_meter_provider(&config, (&config.resource).into(), builder);
assert!(result.is_ok());
}
#[test]
fn build_meter_provider_with_pull_reader() {
let config: OpenTelemetryConfig = parse_yaml(
indoc::indoc! {"
meter_provider:
readers:
- pull:
exporter:
console: {}
"},
&Default::default(),
)
.unwrap();
let builder = SdkMeterProvider::builder();
let result = build_meter_provider(&config, (&config.resource).into(), builder);
assert!(result.is_ok());
}
#[test]
fn build_meter_provider_with_view_drop() {
let config: OpenTelemetryConfig = parse_yaml(
indoc::indoc! {"
meter_provider:
readers:
- periodic:
exporter:
console: {}
interval: 60000
views:
- selector:
instrument_name: 'internal.*'
stream:
aggregation: drop
"},
&Default::default(),
)
.unwrap();
let builder = SdkMeterProvider::builder();
let result = build_meter_provider(&config, (&config.resource).into(), builder);
assert!(result.is_ok());
}
#[test]
fn build_meter_provider_with_view_rename() {
let config: OpenTelemetryConfig = parse_yaml(
indoc::indoc! {"
meter_provider:
readers:
- periodic:
exporter:
console: {}
interval: 60000
views:
- selector:
instrument_name: 'old.metric.name'
stream:
name: 'new.metric.name'
description: 'Updated description'
"},
&Default::default(),
)
.unwrap();
let builder = SdkMeterProvider::builder();
let result = build_meter_provider(&config, (&config.resource).into(), builder);
assert!(result.is_ok());
}
#[test]
fn build_meter_provider_with_view_histogram_buckets() {
let config: OpenTelemetryConfig = parse_yaml(
indoc::indoc! {"
meter_provider:
readers:
- periodic:
exporter:
console: {}
interval: 60000
views:
- selector:
instrument_name: 'http.request.duration'
instrument_type: histogram
stream:
aggregation:
explicit_bucket_histogram:
boundaries: [0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1.0, 5.0, 10.0]
record_min_max: true
"},
&Default::default(),
)
.unwrap();
let builder = SdkMeterProvider::builder();
let result = build_meter_provider(&config, (&config.resource).into(), builder);
assert!(result.is_ok());
}
#[test]
fn build_meter_provider_with_multiple_views() {
let config: OpenTelemetryConfig = parse_yaml(
indoc::indoc! {"
meter_provider:
readers:
- periodic:
exporter:
console: {}
interval: 60000
views:
- selector:
instrument_name: 'debug.*'
stream:
aggregation: drop
- selector:
instrument_name: 'http.*'
stream:
attribute_keys:
included:
- method
- status_code
- selector:
meter_name: 'my.library'
stream:
aggregation: sum
"},
&Default::default(),
)
.unwrap();
let builder = SdkMeterProvider::builder();
let result = build_meter_provider(&config, (&config.resource).into(), builder);
assert!(result.is_ok());
}
}