lambda_otel_utils/
http_meter_provider.rsuse std::time::Duration;
use opentelemetry::metrics::{MeterProvider, Result as MetricsResult};
use opentelemetry_http::HttpClient;
use opentelemetry_otlp::{ExportConfig, Protocol, WithExportConfig};
use opentelemetry_sdk::{
metrics::{PeriodicReader, SdkMeterProvider, InstrumentKind},
metrics::data::Temporality,
metrics::reader::TemporalitySelector,
runtime,
};
use otlp_stdout_client::StdoutClient;
use std::env;
use crate::http_tracer_provider::get_lambda_resource;
#[derive(Debug, Default)]
struct DefaultTemporalitySelector;
impl TemporalitySelector for DefaultTemporalitySelector {
fn temporality(&self, kind: InstrumentKind) -> Temporality {
match kind {
InstrumentKind::Counter | InstrumentKind::ObservableCounter | InstrumentKind::Histogram => {
Temporality::Delta
}
InstrumentKind::UpDownCounter | InstrumentKind::ObservableUpDownCounter | InstrumentKind::ObservableGauge | InstrumentKind::Gauge => {
Temporality::Cumulative
}
}
}
}
#[derive(Debug)]
pub struct HttpMeterProviderBuilder<C: HttpClient + 'static = StdoutClient> {
client: Option<C>,
meter_name: Option<&'static str>,
export_interval: Duration,
export_timeout: Duration,
}
impl Default for HttpMeterProviderBuilder {
fn default() -> Self {
Self::new()
}
}
impl HttpMeterProviderBuilder {
pub fn new() -> Self {
Self {
client: None,
meter_name: None,
export_interval: Duration::from_secs(60),
export_timeout: Duration::from_secs(10),
}
}
pub fn with_stdout_client(mut self) -> Self {
self.client = Some(StdoutClient::new());
self
}
pub fn with_meter_name(mut self, meter_name: &'static str) -> Self {
self.meter_name = Some(meter_name);
self
}
pub fn with_export_interval(mut self, interval: Duration) -> Self {
self.export_interval = interval;
self
}
pub fn with_export_timeout(mut self, timeout: Duration) -> Self {
self.export_timeout = timeout;
self
}
pub fn build(self) -> MetricsResult<SdkMeterProvider> {
let protocol = match env::var("OTEL_EXPORTER_OTLP_PROTOCOL")
.unwrap_or_default()
.to_lowercase()
.as_str()
{
"http/protobuf" => Protocol::HttpBinary,
"http/json" | "" => Protocol::HttpJson,
unsupported => {
eprintln!(
"Warning: OTEL_EXPORTER_OTLP_PROTOCOL value '{}' is not supported. Defaulting to HTTP JSON.",
unsupported
);
Protocol::HttpJson
}
};
let export_config = ExportConfig {
protocol,
timeout: self.export_timeout,
..Default::default()
};
let mut exporter_builder = opentelemetry_otlp::new_exporter().http().with_export_config(export_config);
if let Some(client) = self.client {
exporter_builder = exporter_builder.with_http_client(client);
}
let exporter = exporter_builder.build_metrics_exporter(Box::new(DefaultTemporalitySelector))?;
let reader = PeriodicReader::builder(exporter, runtime::Tokio)
.with_interval(self.export_interval)
.build();
let provider = SdkMeterProvider::builder()
.with_reader(reader)
.with_resource(get_lambda_resource())
.build();
Ok(provider)
}
pub fn get_meter(self, provider: &SdkMeterProvider) -> opentelemetry::metrics::Meter {
provider.meter(self.meter_name.unwrap_or("default"))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_http_meter_provider_builder_default() {
let builder = HttpMeterProviderBuilder::default();
assert!(builder.client.is_none());
assert!(builder.meter_name.is_none());
assert_eq!(builder.export_interval, Duration::from_secs(60));
assert_eq!(builder.export_timeout, Duration::from_secs(10));
}
#[test]
fn test_http_meter_provider_builder_customization() {
let builder = HttpMeterProviderBuilder::new()
.with_stdout_client()
.with_meter_name("test-meter")
.with_export_interval(Duration::from_secs(30))
.with_export_timeout(Duration::from_secs(5));
assert!(builder.client.is_some());
assert_eq!(builder.meter_name, Some("test-meter"));
assert_eq!(builder.export_interval, Duration::from_secs(30));
assert_eq!(builder.export_timeout, Duration::from_secs(5));
}
}