edm-core 0.2.0

Core types and traits for edm.
Documentation
use super::Application;
use crate::{extension::TomlTableExt, state::State};
use metrics_exporter_prometheus::{Matcher, PrometheusBuilder};
use std::{net::IpAddr, time::Duration};

/// Initializes the metrics exporters.
pub(super) fn init<APP: Application + ?Sized>() {
    if let Some(metrics) = APP::config().get_table("metrics") {
        let exporter = metrics.get_str("exporter").unwrap_or("prometheus");
        if exporter == "prometheus" {
            let mut builder = if let Some(endpoint) = metrics.get_str("push-gateway") {
                let interval = metrics
                    .get_duration("interval")
                    .unwrap_or_else(|| Duration::from_secs(60));
                let username = metrics.get_str("username").map(|s| s.to_owned());
                let password = State::decrypt_password(metrics).map(|s| s.into_owned());
                PrometheusBuilder::new()
                    .with_push_gateway(endpoint, interval, username, password)
                    .expect("fail to configure the exporter to run in push gateway mode")
            } else {
                let host = metrics.get_str("host").unwrap_or("127.0.0.1");
                let port = metrics.get_u16("port").unwrap_or(9000);
                let host_addr = host
                    .parse::<IpAddr>()
                    .unwrap_or_else(|err| panic!("invalid host address `{host}`: {err}"));
                tracing::warn!(exporter, "listen on `{host_addr}:{port}`");
                PrometheusBuilder::new().with_http_listener((host_addr, port))
            };
            if let Some(quantiles) = metrics.get_array("quantiles") {
                let quantiles = quantiles
                    .iter()
                    .filter_map(|q| q.as_float())
                    .collect::<Vec<_>>();
                builder = builder
                    .set_quantiles(&quantiles)
                    .expect("invalid quantiles to render histograms");
            }
            if let Some(buckets) = metrics.get_table("buckets") {
                for (key, value) in buckets {
                    let matcher = if key.starts_with('^') {
                        Matcher::Prefix(key.to_owned())
                    } else if key.ends_with('$') {
                        Matcher::Suffix(key.to_owned())
                    } else {
                        Matcher::Full(key.to_owned())
                    };
                    let values = value
                        .as_array()
                        .expect("buckets should be an array of floats")
                        .iter()
                        .filter_map(|v| v.as_float())
                        .collect::<Vec<_>>();
                    builder = builder
                        .set_buckets_for_metric(matcher, &values)
                        .expect("invalid buckets to render histograms");
                }
            }
            if let Some(labels) = metrics.get_table("global-labels") {
                for (key, value) in labels {
                    builder = builder.add_global_label(key, value.to_string());
                }
            }
            if let Some(addresses) = metrics.get_array("allowed-addresses") {
                for addr in addresses {
                    builder = builder
                        .add_allowed_address(addr.as_str().unwrap_or_default())
                        .unwrap_or_else(|err| panic!("invalid IP address `{addr}`: {err}"));
                }
            }
            builder
                .install()
                .expect("fail to install Prometheus exporter");
        } else if !exporter.is_empty() {
            tracing::error!("metrics exporter `{exporter}` is unsupported");
        }
    }
}