use anyhow::{Context, Result};
use once_cell::sync::OnceCell;
use prometheus::{register_int_counter_vec, register_int_gauge_vec, IntCounterVec, IntGaugeVec};
use std::sync::Arc;
static TLS_METRICS: OnceCell<Arc<TlsMetrics>> = OnceCell::new();
pub fn get_tls_metrics() -> Option<Arc<TlsMetrics>> {
TLS_METRICS.get().cloned()
}
pub fn init_tls_metrics() -> Result<Arc<TlsMetrics>> {
if let Some(metrics) = TLS_METRICS.get() {
return Ok(metrics.clone());
}
let metrics = Arc::new(TlsMetrics::new()?);
let _ = TLS_METRICS.set(metrics.clone());
Ok(metrics)
}
pub struct TlsMetrics {
sni_certs_skipped_total: IntCounterVec,
certificates_loaded: IntGaugeVec,
reload_total: IntCounterVec,
folder_entries_skipped_total: IntCounterVec,
}
impl TlsMetrics {
pub fn new() -> Result<Self> {
let sni_certs_skipped_total = register_int_counter_vec!(
"zentinel_tls_sni_certs_skipped_total",
"Total number of SNI certificates skipped during initialization (usually pending ACME issuance)",
&["listener", "primary_domain"]
)
.context("Failed to register zentinel_tls_sni_certs_skipped_total metric")?;
let certificates_loaded = register_int_gauge_vec!(
"zentinel_tls_certificates_loaded",
"Number of TLS certificates currently loaded for a listener",
&["listener"]
)
.context("Failed to register zentinel_tls_certificates_loaded metric")?;
let reload_total = register_int_counter_vec!(
"zentinel_tls_reload_total",
"Total TLS certificate reload attempts by outcome",
&["listener", "result"]
)
.context("Failed to register zentinel_tls_reload_total metric")?;
let folder_entries_skipped_total = register_int_counter_vec!(
"zentinel_tls_folder_entries_skipped_total",
"Files in a scanned certificate folder that could not be loaded",
&["listener", "reason"]
)
.context("Failed to register zentinel_tls_folder_entries_skipped_total metric")?;
Ok(Self {
sni_certs_skipped_total,
certificates_loaded,
reload_total,
folder_entries_skipped_total,
})
}
pub fn set_certificates_loaded(&self, listener_id: &str, count: usize) {
self.certificates_loaded
.with_label_values(&[listener_id])
.set(count as i64);
}
pub fn record_reload(&self, listener_id: &str, succeeded: bool) {
let result = if succeeded { "success" } else { "failure" };
self.reload_total
.with_label_values(&[listener_id, result])
.inc();
}
pub fn record_folder_entry_skipped(&self, listener_id: &str, reason: &str) {
self.folder_entries_skipped_total
.with_label_values(&[listener_id, reason])
.inc();
}
pub fn record_sni_cert_skip(&self, listener_id: &str, primary_domain: &str) {
self.sni_certs_skipped_total
.with_label_values(&[listener_id, primary_domain])
.inc();
}
}