// Copyright 2024 RustFS Team // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Metrics and monitoring for proxy validation performance and results. use crate::{ProxyError, ValidationMode}; use metrics::{counter, describe_counter, describe_gauge, describe_histogram, gauge, histogram}; use std::time::Duration; use tracing::info; /// Collector for proxy validation metrics. #[derive(Debug, Clone)] pub struct ProxyMetrics { /// Whether metrics collection is enabled. enabled: bool, /// Application name used as a label for metrics. app_name: String, } impl ProxyMetrics { /// Creates a new `ProxyMetrics` collector. pub fn new(app_name: &str, enabled: bool) -> Self { let metrics = Self { enabled, app_name: app_name.to_string(), }; // Register metric descriptions for Prometheus. metrics.register_descriptions(); metrics } /// Registers descriptions for all metrics. fn register_descriptions(&self) { if !self.enabled { return; } describe_counter!( "rustfs_trusted_proxy_validation_attempts_total", "Total number of proxy validation attempts" ); describe_counter!( "rustfs_trusted_proxy_validation_success_total", "Total number of successful proxy validations" ); describe_counter!( "rustfs_trusted_proxy_validation_failure_total", "Total number of failed proxy validations" ); describe_counter!( "rustfs_trusted_proxy_validation_failure_by_type_total", "Total number of failed proxy validations categorized by error type" ); describe_gauge!("rustfs_trusted_proxy_chain_length", "Current length of proxy chains being validated"); describe_histogram!( "rustfs_trusted_proxy_validation_duration_seconds", "Time taken to validate a proxy chain in seconds" ); describe_gauge!( "rustfs_trusted_proxy_cache_size", "Current number of entries in the proxy validation cache" ); describe_counter!("rustfs_trusted_proxy_cache_hits_total", "Total number of cache hits for proxy validation"); describe_counter!( "rustfs_trusted_proxy_cache_misses_total", "Total number of cache misses for proxy validation" ); } /// Increments the total number of validation attempts. pub fn increment_validation_attempts(&self) { if !self.enabled { return; } counter!( "rustfs_trusted_proxy_validation_attempts_total", "app" => self.app_name.clone() ) .increment(1); } /// Records a successful validation. pub fn record_validation_success(&self, from_trusted_proxy: bool, proxy_hops: usize, duration: Duration) { if !self.enabled { return; } counter!( "rustfs_trusted_proxy_validation_success_total", "app" => self.app_name.clone(), "trusted" => from_trusted_proxy.to_string() ) .increment(1); gauge!( "rustfs_trusted_proxy_chain_length", "app" => self.app_name.clone() ) .set(proxy_hops as f64); histogram!( "rustfs_trusted_proxy_validation_duration_seconds", "app" => self.app_name.clone() ) .record(duration.as_secs_f64()); } /// Records a failed validation with the specific error type. pub fn record_validation_failure(&self, error: &ProxyError, duration: Duration) { if !self.enabled { return; } let error_type = match error { ProxyError::InvalidXForwardedFor(_) => "invalid_x_forwarded_for", ProxyError::InvalidForwardedHeader(_) => "invalid_forwarded_header", ProxyError::ChainValidationFailed(_) => "chain_validation_failed", ProxyError::ChainTooLong(_, _) => "chain_too_long", ProxyError::UntrustedProxy(_) => "untrusted_proxy", ProxyError::ChainNotContinuous => "chain_not_continuous", ProxyError::IpParseError(_) => "ip_parse_error", ProxyError::HeaderParseError(_) => "header_parse_error", ProxyError::Timeout => "timeout", ProxyError::Internal(_) => "internal", }; counter!( "rustfs_trusted_proxy_validation_failure_total", "app" => self.app_name.clone(), "error_type" => error_type ) .increment(1); counter!( "rustfs_trusted_proxy_validation_failure_by_type_total", "app" => self.app_name.clone(), "error_type" => error_type ) .increment(1); histogram!( "rustfs_trusted_proxy_validation_duration_seconds", "app" => self.app_name.clone(), "error_type" => error_type ) .record(duration.as_secs_f64()); } /// Records the validation mode currently in use. pub fn record_validation_mode(&self, mode: ValidationMode) { if !self.enabled { return; } gauge!( "rustfs_trusted_proxy_validation_mode", "app" => self.app_name.clone(), "mode" => mode.as_str() ) .set(match mode { ValidationMode::Lenient => 0.0, ValidationMode::Strict => 1.0, ValidationMode::HopByHop => 2.0, }); } /// Records a cache hit. pub fn record_cache_hit(&self) { if !self.enabled { return; } counter!("rustfs_trusted_proxy_cache_hits_total", "app" => self.app_name.clone()).increment(1); } /// Records a cache miss. pub fn record_cache_miss(&self) { if !self.enabled { return; } counter!("rustfs_trusted_proxy_cache_misses_total", "app" => self.app_name.clone()).increment(1); } /// Updates only the cache size gauge. pub fn set_cache_size(&self, size: usize) { if !self.enabled { return; } gauge!("rustfs_trusted_proxy_cache_size", "app" => self.app_name.clone()).set(size as f64); } /// Records cache performance metrics. pub fn record_cache_metrics(&self, hits: u64, misses: u64, size: usize) { if !self.enabled { return; } counter!("rustfs_trusted_proxy_cache_hits_total", "app" => self.app_name.clone()).increment(hits); counter!("rustfs_trusted_proxy_cache_misses_total", "app" => self.app_name.clone()).increment(misses); gauge!("rustfs_trusted_proxy_cache_size", "app" => self.app_name.clone()).set(size as f64); } /// Prints a summary of enabled metrics to the log. pub fn print_summary(&self) { if !self.enabled { info!( event = "trusted_proxies.metrics", component = "trusted_proxies", subsystem = "metrics", state = "disabled", app = %self.app_name, "trusted proxies metrics state changed" ); return; } info!( event = "trusted_proxies.metrics", component = "trusted_proxies", subsystem = "metrics", state = "enabled", app = %self.app_name, metric_count = 9, "trusted proxies metrics state changed" ); } } /// Default application name for metrics. const DEFAULT_APP_NAME: &str = "trusted-proxy"; /// Creates a default `ProxyMetrics` collector. pub fn default_proxy_metrics(enabled: bool) -> ProxyMetrics { ProxyMetrics::new(DEFAULT_APP_NAME, enabled) }