Skip to main content

cbtop/observability_backend/
types.rs

1//! Types, enums, configs, and metric definitions for observability backends.
2
3use std::collections::HashMap;
4use std::time::Instant;
5
6/// Result type for observability operations
7pub type ObservabilityResult<T> = Result<T, ObservabilityError>;
8
9/// Errors in observability operations
10#[derive(Debug, Clone, PartialEq)]
11pub enum ObservabilityError {
12    /// Backend connection failed
13    ConnectionFailed { backend: String, reason: String },
14    /// Authentication failed
15    AuthenticationFailed { backend: String },
16    /// Invalid configuration
17    InvalidConfig { reason: String },
18    /// Rate limited by backend
19    RateLimited {
20        backend: String,
21        retry_after_sec: u64,
22    },
23    /// Export failed
24    ExportFailed { backend: String, reason: String },
25    /// Backend not configured
26    BackendNotConfigured { backend: String },
27    /// Serialization error
28    SerializationError { reason: String },
29}
30
31impl std::fmt::Display for ObservabilityError {
32    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33        match self {
34            Self::ConnectionFailed { backend, reason } => {
35                write!(f, "Connection to {} failed: {}", backend, reason)
36            }
37            Self::AuthenticationFailed { backend } => {
38                write!(f, "Authentication failed for {}", backend)
39            }
40            Self::InvalidConfig { reason } => {
41                write!(f, "Invalid configuration: {}", reason)
42            }
43            Self::RateLimited {
44                backend,
45                retry_after_sec,
46            } => {
47                write!(
48                    f,
49                    "{} rate limited, retry after {}s",
50                    backend, retry_after_sec
51                )
52            }
53            Self::ExportFailed { backend, reason } => {
54                write!(f, "Export to {} failed: {}", backend, reason)
55            }
56            Self::BackendNotConfigured { backend } => {
57                write!(f, "Backend {} not configured", backend)
58            }
59            Self::SerializationError { reason } => {
60                write!(f, "Serialization error: {}", reason)
61            }
62        }
63    }
64}
65
66impl std::error::Error for ObservabilityError {}
67
68/// Supported observability backends
69#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
70pub enum ObservabilityBackend {
71    /// Datadog (DogStatsD)
72    Datadog,
73    /// New Relic
74    NewRelic,
75    /// Honeycomb
76    Honeycomb,
77    /// OpenTelemetry Collector
78    Otlp,
79    /// Generic webhook
80    Webhook,
81}
82
83impl ObservabilityBackend {
84    /// Get backend name
85    pub fn name(&self) -> &'static str {
86        match self {
87            Self::Datadog => "Datadog",
88            Self::NewRelic => "NewRelic",
89            Self::Honeycomb => "Honeycomb",
90            Self::Otlp => "OTLP",
91            Self::Webhook => "Webhook",
92        }
93    }
94}
95
96/// Datadog configuration
97#[derive(Debug, Clone)]
98pub struct DatadogConfig {
99    /// DogStatsD host (default: localhost)
100    pub host: String,
101    /// DogStatsD port (default: 8125)
102    pub port: u16,
103    /// API key for Datadog API
104    pub api_key: Option<String>,
105    /// Default tags to add to all metrics
106    pub default_tags: Vec<String>,
107    /// Metric prefix
108    pub prefix: String,
109}
110
111impl Default for DatadogConfig {
112    fn default() -> Self {
113        Self {
114            host: "localhost".to_string(),
115            port: 8125,
116            api_key: None,
117            default_tags: Vec::new(),
118            prefix: "cbtop".to_string(),
119        }
120    }
121}
122
123/// New Relic configuration
124#[derive(Debug, Clone)]
125pub struct NewRelicConfig {
126    /// API endpoint (default: US)
127    pub endpoint: String,
128    /// API key (required)
129    pub api_key: String,
130    /// Account ID
131    pub account_id: String,
132    /// Default attributes
133    pub default_attributes: HashMap<String, String>,
134}
135
136impl NewRelicConfig {
137    /// Create New Relic config with required fields
138    pub fn new(api_key: impl Into<String>, account_id: impl Into<String>) -> Self {
139        Self {
140            endpoint: "https://metric-api.newrelic.com/metric/v1".to_string(),
141            api_key: api_key.into(),
142            account_id: account_id.into(),
143            default_attributes: HashMap::new(),
144        }
145    }
146
147    /// Set custom endpoint (e.g., EU region)
148    pub fn with_endpoint(mut self, endpoint: impl Into<String>) -> Self {
149        self.endpoint = endpoint.into();
150        self
151    }
152}
153
154/// Honeycomb configuration
155#[derive(Debug, Clone)]
156pub struct HoneycombConfig {
157    /// API endpoint
158    pub endpoint: String,
159    /// API key (required)
160    pub api_key: String,
161    /// Dataset name
162    pub dataset: String,
163    /// Service name
164    pub service_name: String,
165}
166
167impl HoneycombConfig {
168    /// Create Honeycomb config with required fields
169    pub fn new(api_key: impl Into<String>, dataset: impl Into<String>) -> Self {
170        Self {
171            endpoint: "https://api.honeycomb.io/1/events".to_string(),
172            api_key: api_key.into(),
173            dataset: dataset.into(),
174            service_name: "cbtop".to_string(),
175        }
176    }
177}
178
179/// OpenTelemetry Collector configuration
180#[derive(Debug, Clone)]
181pub struct OtlpConfig {
182    /// OTLP endpoint (default: localhost:4317)
183    pub endpoint: String,
184    /// Use HTTP instead of gRPC
185    pub use_http: bool,
186    /// Headers to send with requests
187    pub headers: HashMap<String, String>,
188    /// Resource attributes
189    pub resource_attributes: HashMap<String, String>,
190}
191
192impl Default for OtlpConfig {
193    fn default() -> Self {
194        let mut resource_attributes = HashMap::new();
195        resource_attributes.insert("service.name".to_string(), "cbtop".to_string());
196
197        Self {
198            endpoint: "http://localhost:4317".to_string(),
199            use_http: false,
200            headers: HashMap::new(),
201            resource_attributes,
202        }
203    }
204}
205
206/// Webhook configuration
207#[derive(Debug, Clone)]
208pub struct WebhookConfig {
209    /// Webhook URL
210    pub url: String,
211    /// HTTP method (default: POST)
212    pub method: String,
213    /// Headers to send
214    pub headers: HashMap<String, String>,
215    /// Authentication token
216    pub auth_token: Option<String>,
217}
218
219impl WebhookConfig {
220    /// Create webhook config with URL
221    pub fn new(url: impl Into<String>) -> Self {
222        Self {
223            url: url.into(),
224            method: "POST".to_string(),
225            headers: HashMap::new(),
226            auth_token: None,
227        }
228    }
229
230    /// Set authentication token
231    pub fn with_auth(mut self, token: impl Into<String>) -> Self {
232        self.auth_token = Some(token.into());
233        self
234    }
235}
236
237/// A metric to export
238#[derive(Debug, Clone)]
239pub struct ExportMetric {
240    /// Metric name
241    pub name: String,
242    /// Metric value
243    pub value: f64,
244    /// Metric type (gauge, counter, histogram)
245    pub metric_type: MetricExportType,
246    /// Tags/labels
247    pub tags: HashMap<String, String>,
248    /// Timestamp in nanoseconds
249    pub timestamp_ns: u64,
250    /// Optional unit
251    pub unit: Option<String>,
252}
253
254impl ExportMetric {
255    /// Create a gauge metric
256    pub fn gauge(name: impl Into<String>, value: f64) -> Self {
257        Self {
258            name: name.into(),
259            value,
260            metric_type: MetricExportType::Gauge,
261            tags: HashMap::new(),
262            timestamp_ns: std::time::SystemTime::now()
263                .duration_since(std::time::UNIX_EPOCH)
264                .map(|d| d.as_nanos() as u64)
265                .unwrap_or(0),
266            unit: None,
267        }
268    }
269
270    /// Create a counter metric
271    pub fn counter(name: impl Into<String>, value: f64) -> Self {
272        Self {
273            name: name.into(),
274            value,
275            metric_type: MetricExportType::Counter,
276            tags: HashMap::new(),
277            timestamp_ns: std::time::SystemTime::now()
278                .duration_since(std::time::UNIX_EPOCH)
279                .map(|d| d.as_nanos() as u64)
280                .unwrap_or(0),
281            unit: None,
282        }
283    }
284
285    /// Add a tag
286    pub fn with_tag(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
287        self.tags.insert(key.into(), value.into());
288        self
289    }
290
291    /// Set unit
292    pub fn with_unit(mut self, unit: impl Into<String>) -> Self {
293        self.unit = Some(unit.into());
294        self
295    }
296}
297
298/// Metric type for export
299#[derive(Debug, Clone, Copy, PartialEq, Eq)]
300pub enum MetricExportType {
301    /// Gauge (point-in-time value)
302    Gauge,
303    /// Counter (monotonically increasing)
304    Counter,
305    /// Histogram (distribution)
306    Histogram,
307}
308
309/// Export result from a backend
310#[derive(Debug, Clone)]
311pub struct ExportResult {
312    /// Backend that was exported to
313    pub backend: ObservabilityBackend,
314    /// Whether export succeeded
315    pub success: bool,
316    /// Number of metrics exported
317    pub metrics_exported: usize,
318    /// Export duration
319    pub duration_ms: u64,
320    /// Error message if failed
321    pub error: Option<String>,
322}
323
324/// Health status of a backend
325#[derive(Debug, Clone)]
326pub struct BackendHealth {
327    /// Backend type
328    pub backend: ObservabilityBackend,
329    /// Whether backend is healthy
330    pub healthy: bool,
331    /// Last successful export time
332    pub last_success: Option<Instant>,
333    /// Consecutive failures
334    pub consecutive_failures: u32,
335    /// Average export latency
336    pub avg_latency_ms: f64,
337}