use std::fmt;
use std::time::Duration;
use opentelemetry::global;
use opentelemetry_sdk::{
metrics::{MeterProviderBuilder, PeriodicReader, View},
runtime, Resource,
};
use tracing_core::Subscriber;
use tracing_subscriber::{registry::LookupSpan, Layer};
use super::exporters::CloudWatchMetricsExporterBuilder;
use crate::layers::TelemetryLayerBuilder;
const DEFAULT_EXPORT_INTERVAL: u64 = 5;
#[derive(Default)]
pub struct MetricsOptions {
resource: Option<Resource>,
export_interval: Option<Duration>,
views: Vec<Box<dyn View>>,
}
impl fmt::Debug for MetricsOptions {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("MetricsOptions")
}
}
pub trait MetricsOptionsConfigurator {
fn with_exporter_interval(self, export_interval: Duration) -> Self;
fn with_resource(self, view: Resource) -> Self;
fn with_view<T: View>(self, view: T) -> Self;
}
impl MetricsOptionsConfigurator for MetricsOptions {
fn with_exporter_interval(mut self, export_interval: Duration) -> Self {
self.export_interval = Some(export_interval);
self
}
fn with_resource(mut self, resource: Resource) -> Self {
self.resource = Some(resource);
self
}
fn with_view<T: View>(mut self, view: T) -> Self {
self.views.push(Box::new(view));
self
}
}
#[derive(Default)]
pub struct CloudWatchMetricsOptions {
namespace: Option<String>,
metrics_options: MetricsOptions,
}
impl fmt::Debug for CloudWatchMetricsOptions {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("CloudWatchMetricsOptions")
}
}
pub trait CloudWatchMetricsOptionsConfigurator {
fn with_namespace(self, namespace: String) -> Self;
}
impl CloudWatchMetricsOptionsConfigurator for CloudWatchMetricsOptions {
fn with_namespace(mut self, namespace: String) -> Self {
self.namespace = Some(namespace);
self
}
}
impl MetricsOptionsConfigurator for CloudWatchMetricsOptions {
fn with_exporter_interval(mut self, export_interval: Duration) -> Self {
self.metrics_options.export_interval = Some(export_interval);
self
}
fn with_resource(mut self, resource: Resource) -> Self {
self.metrics_options.resource = Some(resource);
self
}
fn with_view<T: View>(mut self, view: T) -> Self {
self.metrics_options.views.push(Box::new(view));
self
}
}
#[derive(Debug)]
pub enum LayerBuilder {
Stdout(MetricsOptions),
CloudWatch(CloudWatchMetricsOptions),
}
impl TelemetryLayerBuilder for LayerBuilder {
async fn layer<S>(self) -> Result<Box<dyn Layer<S> + Send + Sync + 'static>, String>
where
S: Subscriber,
for<'a> S: LookupSpan<'a>,
{
let (reader, views, resource) = match self {
LayerBuilder::Stdout(options) => {
let exporter = opentelemetry_stdout::MetricsExporter::default();
let export_interval = options
.export_interval
.unwrap_or(Duration::from_secs(DEFAULT_EXPORT_INTERVAL));
let reader = PeriodicReader::builder(exporter, runtime::Tokio)
.with_interval(export_interval)
.build();
(reader, options.views, options.resource)
}
LayerBuilder::CloudWatch(options) => {
let exporter_name = options
.namespace
.unwrap_or("default-metrics-exporter".to_string());
let exporter = CloudWatchMetricsExporterBuilder::new(&exporter_name)
.await
.build();
let export_interval = options
.metrics_options
.export_interval
.unwrap_or(Duration::from_secs(DEFAULT_EXPORT_INTERVAL));
let reader = PeriodicReader::builder(exporter, runtime::Tokio)
.with_interval(export_interval)
.build();
(
reader,
options.metrics_options.views,
options.metrics_options.resource,
)
}
};
let mut meter_bulder = MeterProviderBuilder::default()
.with_resource(resource.unwrap_or_default())
.with_reader(reader);
if !views.is_empty() {
for view in views.into_iter() {
meter_bulder = meter_bulder.with_view(view);
}
}
let meter_provider = meter_bulder.build();
global::set_meter_provider(meter_provider.clone());
let metrics_layer = tracing_opentelemetry::MetricsLayer::new(meter_provider);
Ok(Box::new(metrics_layer))
}
}