1use std::path::PathBuf;
2use std::sync::Arc;
3
4use serde::{Deserialize, Serialize};
5
6use crate::{DiagnosticSummary, SinkName, telemetry_health_provider_sealed};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10pub enum LoggingHealthState {
11 Healthy,
13 DegradedDropping,
15 Unavailable,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
21pub enum SinkHealthState {
22 Healthy,
24 DegradedDropping,
26 Unavailable,
28}
29
30#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
32pub struct SinkHealth {
33 pub name: SinkName,
35 pub state: SinkHealthState,
37 pub last_error: Option<DiagnosticSummary>,
39}
40
41#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
43pub struct LoggingHealthReport {
44 pub state: LoggingHealthState,
46 pub dropped_events_total: u64,
48 pub flush_errors_total: u64,
50 pub active_log_path: PathBuf,
52 pub sink_statuses: Vec<SinkHealth>,
54 pub query: Option<QueryHealthReport>,
56 pub last_error: Option<DiagnosticSummary>,
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
62pub enum QueryHealthState {
63 Healthy,
65 Degraded,
67 Unavailable,
69}
70
71#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
73pub struct QueryHealthReport {
74 pub state: QueryHealthState,
76 pub last_error: Option<DiagnosticSummary>,
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
82pub enum ObservationHealthState {
83 Healthy,
85 Degraded,
87 Unavailable,
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
93pub enum TelemetryHealthState {
94 Disabled,
96 Healthy,
98 Degraded,
100 Unavailable,
102}
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
106pub enum ExporterHealthState {
107 Healthy,
109 Degraded,
111 Unavailable,
113}
114
115#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
117pub struct ExporterHealth {
118 pub name: SinkName,
120 pub state: ExporterHealthState,
122 pub last_error: Option<DiagnosticSummary>,
124}
125
126#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
128pub struct TelemetryHealthReport {
129 pub state: TelemetryHealthState,
131 pub dropped_exports_total: u64,
133 pub malformed_spans_total: u64,
135 pub exporter_statuses: Vec<ExporterHealth>,
137 pub last_error: Option<DiagnosticSummary>,
139}
140
141pub trait ObservabilityHealthProvider:
143 telemetry_health_provider_sealed::Sealed + Send + Sync
144{
145 fn telemetry_health(&self) -> TelemetryHealthReport;
147}
148
149impl<T> telemetry_health_provider_sealed::Sealed for Arc<T>
150where
151 T: ObservabilityHealthProvider + ?Sized,
152{
153 fn token(&self) -> telemetry_health_provider_sealed::Token {
154 (**self).token()
155 }
156}
157
158impl<T> ObservabilityHealthProvider for Arc<T>
159where
160 T: ObservabilityHealthProvider + ?Sized,
161{
162 fn telemetry_health(&self) -> TelemetryHealthReport {
163 (**self).telemetry_health()
164 }
165}
166
167#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
169pub struct ObservabilityHealthReport {
170 pub state: ObservationHealthState,
172 pub dropped_observations_total: u64,
174 pub subscriber_failures_total: u64,
176 pub projection_failures_total: u64,
178 pub logging: Option<LoggingHealthReport>,
180 pub telemetry: Option<TelemetryHealthReport>,
182 pub last_error: Option<DiagnosticSummary>,
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189 use serde_json::{Map, json};
190
191 use crate::{
192 Diagnostic, ErrorCode, ExporterHealth, ExporterHealthState, LoggingHealthState,
193 QueryHealthState, Remediation, Timestamp,
194 };
195
196 fn diagnostic() -> Diagnostic {
197 Diagnostic {
198 timestamp: Timestamp::UNIX_EPOCH,
199 code: ErrorCode::new_static("SC_TEST_DIAGNOSTIC"),
200 message: "diagnostic invalid".to_string(),
201 cause: Some("invalid example".to_string()),
202 remediation: Remediation::recoverable(
203 "fix the input",
204 ["rerun the command", "review the docs"],
205 ),
206 docs: Some("https://example.test/docs".to_string()),
207 details: Map::from_iter([("key".to_string(), json!("value"))]),
208 }
209 }
210
211 #[test]
212 fn health_reports_round_trip_through_serde() {
213 let sink = SinkHealth {
214 name: SinkName::new("jsonl").expect("valid sink name"),
215 state: SinkHealthState::Healthy,
216 last_error: Some(DiagnosticSummary::from(&diagnostic())),
217 };
218 let logging = LoggingHealthReport {
219 state: LoggingHealthState::Healthy,
220 dropped_events_total: 0,
221 flush_errors_total: 0,
222 active_log_path: std::path::PathBuf::from("logs/service.log.jsonl"),
223 sink_statuses: vec![sink],
224 query: Some(QueryHealthReport {
225 state: QueryHealthState::Healthy,
226 last_error: None,
227 }),
228 last_error: None,
229 };
230 let telemetry = TelemetryHealthReport {
231 state: TelemetryHealthState::Healthy,
232 dropped_exports_total: 1,
233 malformed_spans_total: 0,
234 exporter_statuses: vec![ExporterHealth {
235 name: SinkName::new("otlp").expect("valid sink name"),
236 state: ExporterHealthState::Degraded,
237 last_error: Some(DiagnosticSummary::from(&diagnostic())),
238 }],
239 last_error: Some(DiagnosticSummary::from(&diagnostic())),
240 };
241 let report = ObservabilityHealthReport {
242 state: ObservationHealthState::Degraded,
243 dropped_observations_total: 2,
244 subscriber_failures_total: 3,
245 projection_failures_total: 4,
246 logging: Some(logging),
247 telemetry: Some(telemetry),
248 last_error: Some(DiagnosticSummary::from(&diagnostic())),
249 };
250
251 let encoded = serde_json::to_string(&report).expect("serialize observability health");
252 let decoded: ObservabilityHealthReport =
253 serde_json::from_str(&encoded).expect("deserialize observability health");
254 assert_eq!(decoded, report);
255 }
256}