use super::*;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct MonitoringConfig {
#[serde(default)]
pub metrics: MetricsConfig,
#[serde(default)]
pub tracing: TracingConfig,
#[serde(default)]
pub health: HealthConfig,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub logging: Option<LoggingConfig>,
}
impl MonitoringConfig {
pub fn merge(mut self, other: Self) -> Self {
self.metrics = self.metrics.merge(other.metrics);
self.tracing = self.tracing.merge(other.tracing);
self.health = self.health.merge(other.health);
if other.logging.is_some() {
self.logging = other.logging;
}
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetricsConfig {
#[serde(default = "default_true")]
pub enabled: bool,
#[serde(default = "default_metrics_port")]
pub port: u16,
#[serde(default = "default_metrics_path")]
pub path: String,
#[serde(default = "default_interval_seconds")]
pub interval_seconds: u64,
}
impl Default for MetricsConfig {
fn default() -> Self {
Self {
enabled: true,
port: default_metrics_port(),
path: default_metrics_path(),
interval_seconds: default_interval_seconds(),
}
}
}
impl MetricsConfig {
pub fn merge(mut self, other: Self) -> Self {
if !other.enabled {
self.enabled = other.enabled;
}
if other.port != default_metrics_port() {
self.port = other.port;
}
if other.path != default_metrics_path() {
self.path = other.path;
}
if other.interval_seconds != default_interval_seconds() {
self.interval_seconds = other.interval_seconds;
}
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TracingConfig {
#[serde(default)]
pub enabled: bool,
pub endpoint: Option<String>,
#[serde(default = "default_service_name")]
pub service_name: String,
#[serde(default = "default_sampling_rate")]
pub sampling_rate: f64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub jaeger: Option<JaegerConfig>,
}
impl Default for TracingConfig {
fn default() -> Self {
Self {
enabled: false,
endpoint: None,
service_name: default_service_name(),
sampling_rate: default_sampling_rate(),
jaeger: None,
}
}
}
impl TracingConfig {
pub fn merge(mut self, other: Self) -> Self {
if other.enabled {
self.enabled = other.enabled;
}
if other.endpoint.is_some() {
self.endpoint = other.endpoint;
}
if other.service_name != default_service_name() {
self.service_name = other.service_name;
}
if (other.sampling_rate - default_sampling_rate()).abs() > f64::EPSILON {
self.sampling_rate = other.sampling_rate;
}
if other.jaeger.is_some() {
self.jaeger = other.jaeger;
}
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JaegerConfig {
pub agent_endpoint: String,
pub service_name: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HealthConfig {
#[serde(default = "default_health_path")]
pub path: String,
#[serde(default = "default_true")]
pub detailed: bool,
}
impl Default for HealthConfig {
fn default() -> Self {
Self {
path: default_health_path(),
detailed: true,
}
}
}
impl HealthConfig {
pub fn merge(mut self, other: Self) -> Self {
if other.path != default_health_path() {
self.path = other.path;
}
if !other.detailed {
self.detailed = other.detailed;
}
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoggingConfig {
#[serde(default = "default_log_level")]
pub level: String,
#[serde(default = "default_log_format")]
pub format: LogFormat,
#[serde(default)]
pub outputs: Vec<LogOutput>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum LogFormat {
Text,
Json,
Structured,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum LogOutput {
#[serde(rename = "console")]
Console,
#[serde(rename = "file")]
File { path: String },
#[serde(rename = "syslog")]
Syslog { facility: String },
}
fn default_interval_seconds() -> u64 {
15
}
fn default_sampling_rate() -> f64 {
0.1
}
fn default_log_level() -> String {
"info".to_string()
}
fn default_log_format() -> LogFormat {
LogFormat::Json
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_metrics_config_default() {
let config = MetricsConfig::default();
assert!(config.enabled);
assert_eq!(config.port, 9090);
assert_eq!(config.path, "/metrics");
assert_eq!(config.interval_seconds, 15);
}
#[test]
fn test_metrics_config_structure() {
let config = MetricsConfig {
enabled: false,
port: 8080,
path: "/prometheus".to_string(),
interval_seconds: 30,
};
assert!(!config.enabled);
assert_eq!(config.port, 8080);
assert_eq!(config.path, "/prometheus");
assert_eq!(config.interval_seconds, 30);
}
#[test]
fn test_metrics_config_serialization() {
let config = MetricsConfig {
enabled: true,
port: 9100,
path: "/stats".to_string(),
interval_seconds: 60,
};
let json = serde_json::to_value(&config).unwrap();
assert_eq!(json["enabled"], true);
assert_eq!(json["port"], 9100);
assert_eq!(json["path"], "/stats");
assert_eq!(json["interval_seconds"], 60);
}
#[test]
fn test_metrics_config_deserialization() {
let json = r#"{"enabled": false, "port": 3000, "path": "/api/metrics"}"#;
let config: MetricsConfig = serde_json::from_str(json).unwrap();
assert!(!config.enabled);
assert_eq!(config.port, 3000);
assert_eq!(config.interval_seconds, 15); }
#[test]
fn test_metrics_config_merge_disabled() {
let base = MetricsConfig::default();
let other = MetricsConfig {
enabled: false,
..MetricsConfig::default()
};
let merged = base.merge(other);
assert!(!merged.enabled);
}
#[test]
fn test_metrics_config_merge_port() {
let base = MetricsConfig::default();
let other = MetricsConfig {
port: 8888,
..MetricsConfig::default()
};
let merged = base.merge(other);
assert_eq!(merged.port, 8888);
}
#[test]
fn test_metrics_config_merge_interval() {
let base = MetricsConfig::default();
let other = MetricsConfig {
interval_seconds: 60,
..MetricsConfig::default()
};
let merged = base.merge(other);
assert_eq!(merged.interval_seconds, 60);
}
#[test]
fn test_metrics_config_clone() {
let config = MetricsConfig::default();
let cloned = config.clone();
assert_eq!(config.enabled, cloned.enabled);
assert_eq!(config.port, cloned.port);
}
#[test]
fn test_tracing_config_default() {
let config = TracingConfig::default();
assert!(!config.enabled);
assert!(config.endpoint.is_none());
assert_eq!(config.service_name, "litellm-rs");
assert!((config.sampling_rate - 0.1).abs() < f64::EPSILON);
assert!(config.jaeger.is_none());
}
#[test]
fn test_tracing_config_structure() {
let config = TracingConfig {
enabled: true,
endpoint: Some("http://jaeger:14268".to_string()),
service_name: "my-gateway".to_string(),
sampling_rate: 0.5,
jaeger: None,
};
assert!(config.enabled);
assert_eq!(config.endpoint, Some("http://jaeger:14268".to_string()));
}
#[test]
fn test_tracing_config_with_jaeger() {
let config = TracingConfig {
enabled: true,
endpoint: None,
service_name: "gw".to_string(),
sampling_rate: 1.0,
jaeger: Some(JaegerConfig {
agent_endpoint: "localhost:6831".to_string(),
service_name: "my-service".to_string(),
}),
};
let jaeger = config.jaeger.unwrap();
assert_eq!(jaeger.agent_endpoint, "localhost:6831");
}
#[test]
fn test_tracing_config_serialization() {
let config = TracingConfig {
enabled: true,
endpoint: Some("http://otel:4317".to_string()),
service_name: "api-gateway".to_string(),
sampling_rate: 0.25,
jaeger: None,
};
let json = serde_json::to_value(&config).unwrap();
assert_eq!(json["enabled"], true);
assert_eq!(json["endpoint"], "http://otel:4317");
}
#[test]
fn test_tracing_config_deserialization() {
let json =
r#"{"enabled": true, "endpoint": "http://zipkin:9411", "service_name": "tracer"}"#;
let config: TracingConfig = serde_json::from_str(json).unwrap();
assert!(config.enabled);
assert_eq!(config.service_name, "tracer");
assert!((config.sampling_rate - 0.1).abs() < f64::EPSILON);
}
#[test]
fn test_tracing_config_merge_enabled() {
let base = TracingConfig::default();
let other = TracingConfig {
enabled: true,
..TracingConfig::default()
};
let merged = base.merge(other);
assert!(merged.enabled);
}
#[test]
fn test_tracing_config_merge_endpoint() {
let base = TracingConfig::default();
let other = TracingConfig {
endpoint: Some("http://collector:4317".to_string()),
..TracingConfig::default()
};
let merged = base.merge(other);
assert_eq!(merged.endpoint, Some("http://collector:4317".to_string()));
}
#[test]
fn test_tracing_config_merge_sampling_rate() {
let base = TracingConfig::default();
let other = TracingConfig {
sampling_rate: 0.5,
..TracingConfig::default()
};
let merged = base.merge(other);
assert!((merged.sampling_rate - 0.5).abs() < f64::EPSILON);
}
#[test]
fn test_tracing_config_clone() {
let config = TracingConfig::default();
let cloned = config.clone();
assert_eq!(config.enabled, cloned.enabled);
assert_eq!(config.service_name, cloned.service_name);
}
#[test]
fn test_health_config_default() {
let config = HealthConfig::default();
assert_eq!(config.path, "/health");
assert!(config.detailed);
}
#[test]
fn test_health_config_structure() {
let config = HealthConfig {
path: "/api/health".to_string(),
detailed: false,
};
assert_eq!(config.path, "/api/health");
assert!(!config.detailed);
}
#[test]
fn test_health_config_serialization() {
let config = HealthConfig {
path: "/status".to_string(),
detailed: true,
};
let json = serde_json::to_value(&config).unwrap();
assert_eq!(json["path"], "/status");
assert_eq!(json["detailed"], true);
}
#[test]
fn test_health_config_deserialization() {
let json = r#"{"path": "/ready", "detailed": false}"#;
let config: HealthConfig = serde_json::from_str(json).unwrap();
assert_eq!(config.path, "/ready");
assert!(!config.detailed);
}
#[test]
fn test_health_config_merge_path() {
let base = HealthConfig::default();
let other = HealthConfig {
path: "/healthz".to_string(),
detailed: true,
};
let merged = base.merge(other);
assert_eq!(merged.path, "/healthz");
}
#[test]
fn test_health_config_merge_detailed() {
let base = HealthConfig::default();
let other = HealthConfig {
path: "/health".to_string(),
detailed: false,
};
let merged = base.merge(other);
assert!(!merged.detailed);
}
#[test]
fn test_health_config_clone() {
let config = HealthConfig::default();
let cloned = config.clone();
assert_eq!(config.path, cloned.path);
assert_eq!(config.detailed, cloned.detailed);
}
#[test]
fn test_log_format_serialization() {
assert_eq!(serde_json::to_string(&LogFormat::Text).unwrap(), "\"text\"");
assert_eq!(serde_json::to_string(&LogFormat::Json).unwrap(), "\"json\"");
assert_eq!(
serde_json::to_string(&LogFormat::Structured).unwrap(),
"\"structured\""
);
}
#[test]
fn test_log_output_console() {
let json = r#"{"type": "console"}"#;
let output: LogOutput = serde_json::from_str(json).unwrap();
assert!(matches!(output, LogOutput::Console));
}
#[test]
fn test_log_output_file() {
let json = r#"{"type": "file", "path": "/tmp/test.log"}"#;
let output: LogOutput = serde_json::from_str(json).unwrap();
match output {
LogOutput::File { path } => assert_eq!(path, "/tmp/test.log"),
_ => panic!("Expected File"),
}
}
#[test]
fn test_monitoring_config_default() {
let config = MonitoringConfig::default();
assert!(config.metrics.enabled);
assert!(!config.tracing.enabled);
assert!(config.health.detailed);
assert!(config.logging.is_none());
}
#[test]
fn test_monitoring_config_with_logging() {
let config = MonitoringConfig {
logging: Some(LoggingConfig {
level: "debug".to_string(),
format: LogFormat::Json,
outputs: vec![LogOutput::Console],
}),
..MonitoringConfig::default()
};
assert!(config.logging.is_some());
assert_eq!(config.logging.unwrap().level, "debug");
}
#[test]
fn test_monitoring_config_serialization() {
let config = MonitoringConfig::default();
let json = serde_json::to_value(&config).unwrap();
assert!(json["metrics"].is_object());
assert!(json["tracing"].is_object());
assert!(json["health"].is_object());
}
#[test]
fn test_monitoring_config_merge() {
let base = MonitoringConfig::default();
let other = MonitoringConfig {
metrics: MetricsConfig {
enabled: false,
..MetricsConfig::default()
},
..MonitoringConfig::default()
};
let merged = base.merge(other);
assert!(!merged.metrics.enabled);
}
#[test]
fn test_monitoring_config_merge_logging() {
let base = MonitoringConfig::default();
let other = MonitoringConfig {
logging: Some(LoggingConfig {
level: "warn".to_string(),
format: LogFormat::Text,
outputs: vec![],
}),
..MonitoringConfig::default()
};
let merged = base.merge(other);
assert!(merged.logging.is_some());
}
#[test]
fn test_monitoring_config_clone() {
let config = MonitoringConfig::default();
let cloned = config.clone();
assert_eq!(config.metrics.enabled, cloned.metrics.enabled);
}
}