use std::collections::HashMap;
use std::time::Instant;
pub type ObservabilityResult<T> = Result<T, ObservabilityError>;
#[derive(Debug, Clone, PartialEq)]
pub enum ObservabilityError {
ConnectionFailed { backend: String, reason: String },
AuthenticationFailed { backend: String },
InvalidConfig { reason: String },
RateLimited {
backend: String,
retry_after_sec: u64,
},
ExportFailed { backend: String, reason: String },
BackendNotConfigured { backend: String },
SerializationError { reason: String },
}
impl std::fmt::Display for ObservabilityError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ConnectionFailed { backend, reason } => {
write!(f, "Connection to {} failed: {}", backend, reason)
}
Self::AuthenticationFailed { backend } => {
write!(f, "Authentication failed for {}", backend)
}
Self::InvalidConfig { reason } => {
write!(f, "Invalid configuration: {}", reason)
}
Self::RateLimited {
backend,
retry_after_sec,
} => {
write!(
f,
"{} rate limited, retry after {}s",
backend, retry_after_sec
)
}
Self::ExportFailed { backend, reason } => {
write!(f, "Export to {} failed: {}", backend, reason)
}
Self::BackendNotConfigured { backend } => {
write!(f, "Backend {} not configured", backend)
}
Self::SerializationError { reason } => {
write!(f, "Serialization error: {}", reason)
}
}
}
}
impl std::error::Error for ObservabilityError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ObservabilityBackend {
Datadog,
NewRelic,
Honeycomb,
Otlp,
Webhook,
}
impl ObservabilityBackend {
pub fn name(&self) -> &'static str {
match self {
Self::Datadog => "Datadog",
Self::NewRelic => "NewRelic",
Self::Honeycomb => "Honeycomb",
Self::Otlp => "OTLP",
Self::Webhook => "Webhook",
}
}
}
#[derive(Debug, Clone)]
pub struct DatadogConfig {
pub host: String,
pub port: u16,
pub api_key: Option<String>,
pub default_tags: Vec<String>,
pub prefix: String,
}
impl Default for DatadogConfig {
fn default() -> Self {
Self {
host: "localhost".to_string(),
port: 8125,
api_key: None,
default_tags: Vec::new(),
prefix: "cbtop".to_string(),
}
}
}
#[derive(Debug, Clone)]
pub struct NewRelicConfig {
pub endpoint: String,
pub api_key: String,
pub account_id: String,
pub default_attributes: HashMap<String, String>,
}
impl NewRelicConfig {
pub fn new(api_key: impl Into<String>, account_id: impl Into<String>) -> Self {
Self {
endpoint: "https://metric-api.newrelic.com/metric/v1".to_string(),
api_key: api_key.into(),
account_id: account_id.into(),
default_attributes: HashMap::new(),
}
}
pub fn with_endpoint(mut self, endpoint: impl Into<String>) -> Self {
self.endpoint = endpoint.into();
self
}
}
#[derive(Debug, Clone)]
pub struct HoneycombConfig {
pub endpoint: String,
pub api_key: String,
pub dataset: String,
pub service_name: String,
}
impl HoneycombConfig {
pub fn new(api_key: impl Into<String>, dataset: impl Into<String>) -> Self {
Self {
endpoint: "https://api.honeycomb.io/1/events".to_string(),
api_key: api_key.into(),
dataset: dataset.into(),
service_name: "cbtop".to_string(),
}
}
}
#[derive(Debug, Clone)]
pub struct OtlpConfig {
pub endpoint: String,
pub use_http: bool,
pub headers: HashMap<String, String>,
pub resource_attributes: HashMap<String, String>,
}
impl Default for OtlpConfig {
fn default() -> Self {
let mut resource_attributes = HashMap::new();
resource_attributes.insert("service.name".to_string(), "cbtop".to_string());
Self {
endpoint: "http://localhost:4317".to_string(),
use_http: false,
headers: HashMap::new(),
resource_attributes,
}
}
}
#[derive(Debug, Clone)]
pub struct WebhookConfig {
pub url: String,
pub method: String,
pub headers: HashMap<String, String>,
pub auth_token: Option<String>,
}
impl WebhookConfig {
pub fn new(url: impl Into<String>) -> Self {
Self {
url: url.into(),
method: "POST".to_string(),
headers: HashMap::new(),
auth_token: None,
}
}
pub fn with_auth(mut self, token: impl Into<String>) -> Self {
self.auth_token = Some(token.into());
self
}
}
#[derive(Debug, Clone)]
pub struct ExportMetric {
pub name: String,
pub value: f64,
pub metric_type: MetricExportType,
pub tags: HashMap<String, String>,
pub timestamp_ns: u64,
pub unit: Option<String>,
}
impl ExportMetric {
pub fn gauge(name: impl Into<String>, value: f64) -> Self {
Self {
name: name.into(),
value,
metric_type: MetricExportType::Gauge,
tags: HashMap::new(),
timestamp_ns: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0),
unit: None,
}
}
pub fn counter(name: impl Into<String>, value: f64) -> Self {
Self {
name: name.into(),
value,
metric_type: MetricExportType::Counter,
tags: HashMap::new(),
timestamp_ns: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0),
unit: None,
}
}
pub fn with_tag(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.tags.insert(key.into(), value.into());
self
}
pub fn with_unit(mut self, unit: impl Into<String>) -> Self {
self.unit = Some(unit.into());
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MetricExportType {
Gauge,
Counter,
Histogram,
}
#[derive(Debug, Clone)]
pub struct ExportResult {
pub backend: ObservabilityBackend,
pub success: bool,
pub metrics_exported: usize,
pub duration_ms: u64,
pub error: Option<String>,
}
#[derive(Debug, Clone)]
pub struct BackendHealth {
pub backend: ObservabilityBackend,
pub healthy: bool,
pub last_success: Option<Instant>,
pub consecutive_failures: u32,
pub avg_latency_ms: f64,
}