prometheus-derive-macros 0.1.0

Procedural macros for prometheus with automatic metric registration
Documentation
//! # Prometheus Metrics Macro
//!
//! This crate provides procedural macros for generating Prometheus metrics with automatic
//! registration. It uses syn-based AST parsing for robust and reliable
//! code generation.
//!
//! ## Features
//!
//! - Automatic metric registration with global registry
//! - Support for labeled and unlabeled metrics
//! - Type-safe label structs
//! - Compile-time metric validation
//!
//! ## Example
//!
//! ```rust,ignore
//! use metrics_macro::prometheus_metrics;
//! use prometheus_client::metrics::counter::Counter;
//! use prometheus_client::metrics::gauge::Gauge;
//!
//! prometheus_metrics! {
//!     /// Total HTTP requests received
//!     #[labels(method = String, status = u16)]
//!     static http_requests_total: Counter;
//!
//!     /// Current number of active connections
//!     static active_connections: Gauge;
//! }
//! ```

#[cfg(feature = "prometheus-client")]
extern crate prometheus_client;

use proc_macro::TokenStream;
use quote::quote;
use syn::parse_macro_input;

mod codegen;
mod parsing;

use codegen::generate_metric_code;
use parsing::{extract_metric_definition, MetricsInput};

/// Define Prometheus metric families with automatic registration.
///
/// This macro generates:
/// - Static metric instances with proper types
/// - Label structs for metrics with labels
/// - Automatic registration with a global registry
///
/// # Syntax
///
/// ```rust,ignore
/// use metrics_macro::prometheus_metrics;
/// use prometheus_client::metrics::counter::Counter;
///
/// prometheus_metrics! {
///     /// Documentation comment for the metric
///     #[labels(label_name = String)]  // Optional labels
///     static metric_name: Counter;
/// }
/// ```
///
/// # Supported Metric Types
///
/// - `Counter` - Monotonically increasing counter
/// - `Gauge` - Value that can go up and down
///
/// # Examples
///
/// ## Simple Counter
/// ```rust,ignore
/// use metrics_macro::prometheus_metrics;
/// use prometheus_client::metrics::counter::Counter;
///
/// prometheus_metrics! {
///     /// Total number of requests processed
///     static requests_total: Counter;
/// }
/// ```
///
/// ## Labeled Gauge
/// ```rust,ignore
/// use metrics_macro::prometheus_metrics;
/// use prometheus_client::metrics::gauge::Gauge;
///
/// prometheus_metrics! {
///     /// Current memory usage by component
///     #[labels(component = String, unit = String)]
///     static memory_usage: Gauge;
/// }
/// ```
#[proc_macro]
pub fn prometheus_metrics(input: TokenStream) -> TokenStream {
    let metrics_input = parse_macro_input!(input as MetricsInput);

    // Extract metric definitions from parsed AST
    let metrics: Vec<_> = metrics_input
        .metrics
        .iter()
        .map(extract_metric_definition)
        .collect();

    // Generate code for each metric
    let generated_metrics: Vec<_> = metrics.iter().map(generate_metric_code).collect();

    // We import types needed by the generated code, but we want types that are shown to the user
    // like Counter or Gauge, not being imported, and let the user be flexible with it (e.g.
    // have aliases)
    let output = quote! {
        use std::sync::LazyLock;
        use prometheus_client::metrics::family::Family;
        use prometheus_client::encoding::EncodeLabelSet;

        #(#generated_metrics)*
    };

    TokenStream::from(output)
}

/// Generate registration code for metrics.
///
/// This macro should be called with a list of metric static variables
/// that should be registered with the global registry.
///
/// # Example
///
/// ```rust,ignore
/// use metrics::{prometheus_metrics, register_metrics, GLOBAL_REGISTRY};
///
/// prometheus_metrics! {
///     static COUNTER: Counter;
///     #[labels(method = String)]
///     static HTTP_REQUESTS: Counter;
/// }
///
/// register_metrics!(COUNTER, HTTP_REQUESTS);
/// ```
#[proc_macro]
pub fn register_metrics(input: TokenStream) -> TokenStream {
    let input_str = input.to_string();
    let metric_names: Vec<&str> = input_str.split(',').map(|s| s.trim()).collect();

    let registrations: Vec<_> = metric_names
        .iter()
        .map(|name| {
            let metric_ident = syn::parse_str::<syn::Ident>(name).expect("Invalid metric name");
            let metric_name = name.to_lowercase();
            let help = format!("Generated metric: {}", name);

            quote! {
                GLOBAL_REGISTRY.write().unwrap().register(
                    #metric_name,
                    #help,
                    #metric_ident.clone(),
                );
            }
        })
        .collect();

    let output = quote! {
        {
            #(#registrations)*
        }
    };

    TokenStream::from(output)
}