use anyhow::Result;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TelemetryConfig {
pub enabled: bool,
pub service_name: String,
pub service_version: String,
pub export_endpoint: Option<String>,
pub export_interval_seconds: u64,
pub tracing_enabled: bool,
pub metrics_enabled: bool,
pub sampling_rate: f64,
}
impl Default for TelemetryConfig {
fn default() -> Self {
Self {
enabled: false,
service_name: "kindly-guard".to_string(),
service_version: env!("CARGO_PKG_VERSION").to_string(),
export_endpoint: None,
export_interval_seconds: 60,
tracing_enabled: true,
metrics_enabled: true,
sampling_rate: 0.1,
}
}
}
#[derive(Debug, Clone)]
pub struct TelemetrySpan {
pub name: String,
pub start_time: std::time::Instant,
pub attributes: Vec<(String, String)>,
}
#[derive(Debug, Clone)]
pub enum MetricValue {
Counter(u64),
Gauge(f64),
Histogram(f64),
}
#[derive(Debug, Clone)]
pub struct TelemetryMetric {
pub name: String,
pub value: MetricValue,
pub labels: Vec<(String, String)>,
}
#[async_trait]
pub trait TelemetryProvider: Send + Sync {
fn start_span(&self, name: &str) -> TelemetrySpan;
fn end_span(&self, span: TelemetrySpan);
fn record_metric(&self, metric: TelemetryMetric);
fn add_event(&self, name: &str, attributes: Vec<(&str, &str)>);
fn set_status(&self, span: &TelemetrySpan, is_error: bool, message: Option<&str>);
async fn flush(&self) -> Result<()>;
async fn shutdown(&self) -> Result<()>;
}
#[derive(Debug, Clone)]
pub struct TelemetryContext {
pub trace_id: String,
pub span_id: String,
pub parent_span_id: Option<String>,
pub baggage: Vec<(String, String)>,
}
impl Default for TelemetryContext {
fn default() -> Self {
Self::new()
}
}
impl TelemetryContext {
pub fn new() -> Self {
Self {
trace_id: uuid::Uuid::new_v4().to_string(),
span_id: uuid::Uuid::new_v4().to_string(),
parent_span_id: None,
baggage: vec![],
}
}
pub fn child(&self) -> Self {
Self {
trace_id: self.trace_id.clone(),
span_id: uuid::Uuid::new_v4().to_string(),
parent_span_id: Some(self.span_id.clone()),
baggage: self.baggage.clone(),
}
}
}
pub trait TelemetryProviderFactory: Send + Sync {
fn create(&self, config: &TelemetryConfig) -> Result<Arc<dyn TelemetryProvider>>;
}
pub struct SecureTelemetry {
provider: Arc<dyn TelemetryProvider>,
}
impl SecureTelemetry {
pub fn new(provider: Arc<dyn TelemetryProvider>) -> Self {
Self { provider }
}
pub fn record_security_event(&self, event_type: &str, client_id: &str, threat_level: &str) {
let sanitized_client = if client_id.len() > 8 {
format!("{}...", &client_id[..8])
} else {
"anonymous".to_string()
};
self.provider.add_event(
"security.event",
vec![
("event.type", event_type),
("client.id", &sanitized_client),
("threat.level", threat_level),
],
);
}
pub fn record_performance(&self, operation: &str, duration_ms: f64) {
self.provider.record_metric(TelemetryMetric {
name: format!("kindly_guard.{operation}.duration"),
value: MetricValue::Histogram(duration_ms),
labels: vec![("operation".to_string(), operation.to_string())],
});
}
pub fn record_rate_limit(&self, client_id: &str, allowed: bool) {
let sanitized_client = if client_id.len() > 8 {
format!("{}...", &client_id[..8])
} else {
"anonymous".to_string()
};
self.provider.record_metric(TelemetryMetric {
name: "kindly_guard.rate_limit.decisions".to_string(),
value: MetricValue::Counter(1),
labels: vec![
("client.id".to_string(), sanitized_client),
(
"decision".to_string(),
if allowed { "allow" } else { "deny" }.to_string(),
),
],
});
}
pub fn record_neutralization(
&self,
threat_type: &str,
action: &str,
duration_ms: f64,
success: bool,
) {
self.provider.record_metric(TelemetryMetric {
name: "kindly_guard.neutralization.total".to_string(),
value: MetricValue::Counter(1),
labels: vec![
("threat.type".to_string(), threat_type.to_string()),
("action".to_string(), action.to_string()),
(
"status".to_string(),
if success { "success" } else { "failure" }.to_string(),
),
],
});
if success {
self.provider.record_metric(TelemetryMetric {
name: "kindly_guard.neutralization.duration_ms".to_string(),
value: MetricValue::Histogram(duration_ms),
labels: vec![
("threat.type".to_string(), threat_type.to_string()),
("action".to_string(), action.to_string()),
],
});
}
self.provider.add_event(
"neutralization",
vec![
("threat.type", threat_type),
("action", action),
("duration_ms", &duration_ms.to_string()),
("success", if success { "true" } else { "false" }),
],
);
}
pub fn record_neutralization_batch(
&self,
total_threats: usize,
neutralized: usize,
duration_ms: f64,
) {
self.provider.record_metric(TelemetryMetric {
name: "kindly_guard.neutralization.batch.size".to_string(),
value: MetricValue::Histogram(total_threats as f64),
labels: vec![],
});
self.provider.record_metric(TelemetryMetric {
name: "kindly_guard.neutralization.batch.success_rate".to_string(),
value: MetricValue::Gauge(if total_threats > 0 {
(neutralized as f64 / total_threats as f64) * 100.0
} else {
0.0
}),
labels: vec![],
});
self.provider.record_metric(TelemetryMetric {
name: "kindly_guard.neutralization.batch.duration_ms".to_string(),
value: MetricValue::Histogram(duration_ms),
labels: vec![],
});
}
}
pub mod distributed;
#[cfg(feature = "enhanced")]
pub mod enhanced;
pub mod metrics;
pub mod standard;
pub use distributed::{
ContextPropagator, DistributedSpan, DistributedTracingProvider, ProbabilitySampler,
SpanBuilder, SpanKind, SpanStatus, StatusCode, TracingSampler, W3CTraceContextPropagator,
};
pub use metrics::{CommandMetrics, MetricsCollector, MetricsSnapshot};
pub use standard::StandardTelemetryProvider;
pub fn create_telemetry_provider(_config: &crate::config::Config) -> Arc<dyn TelemetryProvider> {
let telemetry_config = TelemetryConfig::default();
if telemetry_config.enabled {
Arc::new(StandardTelemetryProvider::new(telemetry_config))
} else {
Arc::new(NoOpTelemetryProvider)
}
}
struct NoOpTelemetryProvider;
#[async_trait]
impl TelemetryProvider for NoOpTelemetryProvider {
fn start_span(&self, _name: &str) -> TelemetrySpan {
TelemetrySpan {
name: String::new(),
start_time: std::time::Instant::now(),
attributes: vec![],
}
}
fn end_span(&self, _span: TelemetrySpan) {}
fn record_metric(&self, _metric: TelemetryMetric) {}
fn add_event(&self, _name: &str, _attributes: Vec<(&str, &str)>) {}
fn set_status(&self, _span: &TelemetrySpan, _is_error: bool, _message: Option<&str>) {}
async fn flush(&self) -> Result<()> {
Ok(())
}
async fn shutdown(&self) -> Result<()> {
Ok(())
}
}