bindcar/
metrics.rs

1// Copyright (c) 2025 Erick Bourgeois, firestoned
2// SPDX-License-Identifier: MIT
3
4//! Prometheus metrics for bindcar
5//!
6//! This module provides comprehensive metrics for monitoring the BIND9 RNDC API server:
7//! - HTTP request metrics (count, duration, status codes)
8//! - Zone operation metrics (creates, deletes, reloads, etc.)
9//! - RNDC command execution metrics
10//! - System health metrics
11
12use lazy_static::lazy_static;
13use prometheus::{
14    opts, register_counter_vec, register_gauge, register_histogram_vec, CounterVec, Encoder, Gauge,
15    HistogramVec, TextEncoder,
16};
17
18lazy_static! {
19    /// HTTP request counter by method, path, and status code
20    pub static ref HTTP_REQUESTS_TOTAL: CounterVec = register_counter_vec!(
21        opts!(
22            "bindcar_http_requests_total",
23            "Total number of HTTP requests processed"
24        ),
25        &["method", "path", "status"]
26    )
27    .expect("Failed to create HTTP_REQUESTS_TOTAL metric");
28
29    /// HTTP request duration histogram
30    pub static ref HTTP_REQUEST_DURATION_SECONDS: HistogramVec = register_histogram_vec!(
31        "bindcar_http_request_duration_seconds",
32        "HTTP request duration in seconds",
33        &["method", "path"],
34        vec![0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0]
35    )
36    .expect("Failed to create HTTP_REQUEST_DURATION_SECONDS metric");
37
38    /// Zone operations counter by operation type and result
39    pub static ref ZONE_OPERATIONS_TOTAL: CounterVec = register_counter_vec!(
40        opts!(
41            "bindcar_zone_operations_total",
42            "Total number of zone operations"
43        ),
44        &["operation", "result"]
45    )
46    .expect("Failed to create ZONE_OPERATIONS_TOTAL metric");
47
48    /// RNDC command counter by command and result
49    pub static ref RNDC_COMMANDS_TOTAL: CounterVec = register_counter_vec!(
50        opts!(
51            "bindcar_rndc_commands_total",
52            "Total number of RNDC commands executed"
53        ),
54        &["command", "result"]
55    )
56    .expect("Failed to create RNDC_COMMANDS_TOTAL metric");
57
58    /// RNDC command duration histogram
59    pub static ref RNDC_COMMAND_DURATION_SECONDS: HistogramVec = register_histogram_vec!(
60        "bindcar_rndc_command_duration_seconds",
61        "RNDC command execution duration in seconds",
62        &["command"],
63        vec![0.01, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0]
64    )
65    .expect("Failed to create RNDC_COMMAND_DURATION_SECONDS metric");
66
67    /// Total number of zones managed
68    pub static ref ZONES_MANAGED_TOTAL: Gauge = register_gauge!(
69        opts!(
70            "bindcar_zones_managed_total",
71            "Total number of zones currently managed"
72        )
73    )
74    .expect("Failed to create ZONES_MANAGED_TOTAL metric");
75
76    /// Application info metric
77    pub static ref APP_INFO: CounterVec = register_counter_vec!(
78        opts!(
79            "bindcar_app_info",
80            "Application information"
81        ),
82        &["version"]
83    )
84    .expect("Failed to create APP_INFO metric");
85
86    /// Rate limit counter by result
87    pub static ref RATE_LIMIT_REQUESTS_TOTAL: CounterVec = register_counter_vec!(
88        opts!(
89            "bindcar_rate_limit_requests_total",
90            "Total number of rate limit checks"
91        ),
92        &["result"]
93    )
94    .expect("Failed to create RATE_LIMIT_REQUESTS_TOTAL metric");
95}
96
97/// Initialize metrics with application info
98pub fn init_metrics() {
99    APP_INFO
100        .with_label_values(&[env!("CARGO_PKG_VERSION")])
101        .inc();
102}
103
104/// Generate metrics output in Prometheus format
105pub fn gather_metrics() -> Result<String, Box<dyn std::error::Error>> {
106    let encoder = TextEncoder::new();
107    let metric_families = prometheus::gather();
108    let mut buffer = Vec::new();
109    encoder.encode(&metric_families, &mut buffer)?;
110    Ok(String::from_utf8(buffer)?)
111}
112
113/// Record an HTTP request
114pub fn record_http_request(method: &str, path: &str, status: u16, duration: f64) {
115    HTTP_REQUESTS_TOTAL
116        .with_label_values(&[method, path, &status.to_string()])
117        .inc();
118    HTTP_REQUEST_DURATION_SECONDS
119        .with_label_values(&[method, path])
120        .observe(duration);
121}
122
123/// Record a zone operation
124pub fn record_zone_operation(operation: &str, success: bool) {
125    let result = if success { "success" } else { "error" };
126    ZONE_OPERATIONS_TOTAL
127        .with_label_values(&[operation, result])
128        .inc();
129}
130
131/// Record an RNDC command execution
132pub fn record_rndc_command(command: &str, success: bool, duration: f64) {
133    let result = if success { "success" } else { "error" };
134    RNDC_COMMANDS_TOTAL
135        .with_label_values(&[command, result])
136        .inc();
137    RNDC_COMMAND_DURATION_SECONDS
138        .with_label_values(&[command])
139        .observe(duration);
140}
141
142/// Update the total number of managed zones
143pub fn update_zones_count(count: i64) {
144    ZONES_MANAGED_TOTAL.set(count as f64);
145}
146
147/// Record a rate limit check
148pub fn record_rate_limit(allowed: bool) {
149    let result = if allowed { "allowed" } else { "rejected" };
150    RATE_LIMIT_REQUESTS_TOTAL.with_label_values(&[result]).inc();
151}
152
153/// Record an nsupdate command execution
154pub fn record_nsupdate_command(operation: &str, success: bool, duration: f64) {
155    let result = if success { "success" } else { "error" };
156    let command = format!("nsupdate_{}", operation);
157    RNDC_COMMANDS_TOTAL
158        .with_label_values(&[command.as_str(), result])
159        .inc();
160    RNDC_COMMAND_DURATION_SECONDS
161        .with_label_values(&[command.as_str()])
162        .observe(duration);
163}
164
165/// Record a DNS record operation (add, remove, update)
166pub fn record_record_operation(operation: &str, success: bool) {
167    let result = if success { "success" } else { "error" };
168    let op = format!("record_{}", operation);
169    ZONE_OPERATIONS_TOTAL
170        .with_label_values(&[op.as_str(), result])
171        .inc();
172}