pleme-observability 0.3.0

Observability library for Pleme platform - tracing, metrics, distributed tracing, and metric definition macros
Documentation
//! Declarative macros for Prometheus metric definitions.
//!
//! The `define_metrics!` macro reduces boilerplate by generating:
//! - Struct fields for each metric
//! - Creation and registration in `new()`
//! - Public accessor functions (using a caller-provided metrics getter)
//!
//! ## Example
//!
//! ```ignore
//! // In your service's observability module:
//! pleme_observability::define_metrics! {
//!     metrics_fn: crate::observability::metrics;
//!
//!     struct Metrics {
//!         counter "my_ads_created_total" => ads_created_total: "Total ads created";
//!
//!         counter_vec "my_http_requests_total" => http_requests_total: "Total HTTP requests"
//!             labels: ["method", "path", "status"];
//!
//!         gauge "my_db_connections" => db_connections: "Current DB connections";
//!
//!         gauge_vec "my_jobs_in_flight" => jobs_in_flight: "Jobs in flight"
//!             labels: ["category"];
//!
//!         histogram "my_http_duration_seconds" => http_request_duration: "HTTP duration"
//!             labels: ["method", "path"]
//!             buckets: [0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0];
//!     }
//! }
//! ```

/// Main macro for defining a Prometheus metrics struct with auto-registration.
///
/// Each service provides its own `metrics_fn` path — a function that returns
/// `&'static Arc<Metrics>`. The generated accessor functions use this path
/// so the macro works regardless of which crate it's invoked in.
#[macro_export]
macro_rules! define_metrics {
    (
        metrics_fn: $metrics_fn:path;

        struct $struct_name:ident {
            $(
                $metric_type:ident $metric_name:literal => $field_name:ident : $help:literal
                $(labels: [$($label:literal),* $(,)?])?
                $(buckets: [$($bucket:literal),* $(,)?])?
            );* $(;)?
        }
    ) => {
        /// Metrics container — generated by `define_metrics!`
        #[derive(Clone)]
        pub struct $struct_name {
            registry: ::prometheus::Registry,
            $(
                pub $field_name: $crate::metric_field_type!($metric_type),
            )*
        }

        impl $struct_name {
            /// Create and register all metrics
            pub fn new() -> ::std::result::Result<Self, ::prometheus::Error> {
                let registry = ::prometheus::Registry::new();

                $(
                    let $field_name = $crate::create_metric!(
                        $metric_type,
                        $metric_name,
                        $help
                        $(, labels: [$($label),*])?
                        $(, buckets: [$($bucket),*])?
                    )?;
                    registry.register(Box::new($field_name.clone()))?;
                )*

                Ok(Self {
                    registry,
                    $($field_name,)*
                })
            }

            /// Get the Prometheus registry
            pub fn registry(&self) -> &::prometheus::Registry {
                &self.registry
            }
        }

        // Generate accessor functions using the caller-provided metrics getter
        $(
            #[doc = concat!("Get the `", stringify!($field_name), "` metric")]
            #[inline]
            pub fn $field_name() -> &'static $crate::metric_field_type!($metric_type) {
                &$metrics_fn().$field_name
            }
        )*
    };
}

/// Helper macro to determine the Prometheus field type from a metric kind.
#[macro_export]
macro_rules! metric_field_type {
    (counter) => {
        ::prometheus::IntCounter
    };
    (counter_vec) => {
        ::prometheus::IntCounterVec
    };
    (gauge) => {
        ::prometheus::IntGauge
    };
    (gauge_vec) => {
        ::prometheus::IntGaugeVec
    };
    (histogram) => {
        ::prometheus::HistogramVec
    };
}

/// Helper macro to create a Prometheus metric from its kind and metadata.
#[macro_export]
macro_rules! create_metric {
    // IntCounter - no labels, no buckets
    (counter, $name:literal, $help:literal) => {
        ::prometheus::IntCounter::new($name, $help)
    };

    // IntCounterVec - with labels
    (counter_vec, $name:literal, $help:literal, labels: [$($label:literal),*]) => {
        ::prometheus::IntCounterVec::new(
            ::prometheus::Opts::new($name, $help),
            &[$($label),*]
        )
    };

    // IntGauge - no labels
    (gauge, $name:literal, $help:literal) => {
        ::prometheus::IntGauge::new($name, $help)
    };

    // IntGaugeVec - with labels
    (gauge_vec, $name:literal, $help:literal, labels: [$($label:literal),*]) => {
        ::prometheus::IntGaugeVec::new(
            ::prometheus::Opts::new($name, $help),
            &[$($label),*]
        )
    };

    // HistogramVec - with labels and buckets
    (histogram, $name:literal, $help:literal, labels: [$($label:literal),*], buckets: [$($bucket:literal),*]) => {
        ::prometheus::HistogramVec::new(
            ::prometheus::HistogramOpts::new($name, $help)
                .buckets(vec![$($bucket as f64),*]),
            &[$($label),*]
        )
    };
}