metry 0.1.1

All-in-one telemetry framework, based on tracing crate.
Documentation
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;

/// Common configuration options for a metrics [LayerBuilder].
#[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
    }
}

/// Configuration options for a [LayerBuilder::CloudWatch].
#[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 {
    /// Metrics layer emitting to stdout.
    Stdout(MetricsOptions),
    /// Metrics layer emitting to AWS CloudWatch.
    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))
    }
}