pub mod alerting;
pub mod logging;
pub mod metrics;
pub mod tracing;
pub use alerting::{AlertConfig, AlertRule, AlertSeverity, AlertState};
pub use logging::{LogConfig, LogLevel, StructuredLogger};
pub use metrics::{KernelMetrics, MetricsConfig, MetricsExporter};
pub use tracing::{KernelSpan, SpanContext, TracingConfig};
pub use ringkernel_core::alerting as ring_alerting;
pub use ringkernel_core::logging as ring_logging;
pub use ringkernel_core::observability as ring_observability;
pub use ringkernel_core::telemetry as ring_telemetry;
pub use ringkernel_core::telemetry_pipeline as ring_telemetry_pipeline;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ObservabilityConfig {
pub metrics: Option<MetricsConfig>,
pub tracing: Option<TracingConfig>,
pub logging: LogConfig,
pub alerting: Option<AlertConfig>,
pub service_name: String,
pub service_version: String,
pub environment: String,
}
impl Default for ObservabilityConfig {
fn default() -> Self {
Self {
metrics: None,
tracing: None,
logging: LogConfig::default(),
alerting: None,
service_name: "rustkernels".to_string(),
service_version: env!("CARGO_PKG_VERSION").to_string(),
environment: "development".to_string(),
}
}
}
impl ObservabilityConfig {
pub fn builder() -> ObservabilityConfigBuilder {
ObservabilityConfigBuilder::default()
}
pub fn development() -> Self {
Self {
logging: LogConfig {
level: LogLevel::Debug,
structured: false,
..Default::default()
},
environment: "development".to_string(),
..Default::default()
}
}
pub fn production() -> Self {
Self {
metrics: Some(MetricsConfig::default()),
tracing: Some(TracingConfig::default()),
logging: LogConfig {
level: LogLevel::Info,
structured: true,
..Default::default()
},
alerting: Some(AlertConfig::default()),
environment: "production".to_string(),
..Default::default()
}
}
#[cfg(feature = "metrics")]
pub async fn init(&self) -> crate::error::Result<()> {
self.logging.init()?;
if let Some(ref metrics) = self.metrics {
metrics.init().await?;
}
if let Some(ref tracing) = self.tracing {
tracing.init().await?;
}
Ok(())
}
#[cfg(not(feature = "metrics"))]
pub async fn init(&self) -> crate::error::Result<()> {
self.logging.init()?;
Ok(())
}
}
#[derive(Default)]
pub struct ObservabilityConfigBuilder {
config: ObservabilityConfig,
}
impl ObservabilityConfigBuilder {
pub fn with_metrics(mut self, config: MetricsConfig) -> Self {
self.config.metrics = Some(config);
self
}
pub fn with_tracing(mut self, config: TracingConfig) -> Self {
self.config.tracing = Some(config);
self
}
pub fn with_logging(mut self, config: LogConfig) -> Self {
self.config.logging = config;
self
}
pub fn with_alerting(mut self, config: AlertConfig) -> Self {
self.config.alerting = Some(config);
self
}
pub fn service_name(mut self, name: impl Into<String>) -> Self {
self.config.service_name = name.into();
self
}
pub fn service_version(mut self, version: impl Into<String>) -> Self {
self.config.service_version = version.into();
self
}
pub fn environment(mut self, env: impl Into<String>) -> Self {
self.config.environment = env.into();
self
}
pub fn build(self) -> ObservabilityConfig {
self.config
}
}
#[derive(Debug, Clone, Default)]
pub struct MetricLabels {
pub kernel_id: Option<String>,
pub domain: Option<String>,
pub tenant_id: Option<String>,
pub extra: std::collections::HashMap<String, String>,
}
impl MetricLabels {
pub fn new() -> Self {
Self::default()
}
pub fn with_kernel(mut self, id: impl Into<String>) -> Self {
self.kernel_id = Some(id.into());
self
}
pub fn with_domain(mut self, domain: impl Into<String>) -> Self {
self.domain = Some(domain.into());
self
}
pub fn with_tenant(mut self, tenant: impl Into<String>) -> Self {
self.tenant_id = Some(tenant.into());
self
}
pub fn with_label(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.extra.insert(key.into(), value.into());
self
}
pub fn to_pairs(&self) -> Vec<(&str, String)> {
let mut pairs = Vec::new();
if let Some(ref id) = self.kernel_id {
pairs.push(("kernel_id", id.clone()));
}
if let Some(ref domain) = self.domain {
pairs.push(("domain", domain.clone()));
}
if let Some(ref tenant) = self.tenant_id {
pairs.push(("tenant_id", tenant.clone()));
}
for (k, v) in &self.extra {
pairs.push((k.as_str(), v.clone()));
}
pairs
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = ObservabilityConfig::default();
assert_eq!(config.service_name, "rustkernels");
assert_eq!(config.environment, "development");
assert!(config.metrics.is_none());
}
#[test]
fn test_production_config() {
let config = ObservabilityConfig::production();
assert_eq!(config.environment, "production");
assert!(config.metrics.is_some());
assert!(config.tracing.is_some());
}
#[test]
fn test_builder() {
let config = ObservabilityConfig::builder()
.service_name("test-service")
.environment("testing")
.build();
assert_eq!(config.service_name, "test-service");
assert_eq!(config.environment, "testing");
}
#[test]
fn test_metric_labels() {
let labels = MetricLabels::new()
.with_kernel("graph/pagerank")
.with_domain("GraphAnalytics")
.with_tenant("tenant-123");
let pairs = labels.to_pairs();
assert_eq!(pairs.len(), 3);
}
}