osproxy_observe/diagnostic_sink.rs
1//! The diagnostic-capture sink: where directive-selected explanations go *off the
2//! instance* (`docs/05` ยง5).
3//!
4//! A proxy fleet serves a request on whichever instance the load balancer picked,
5//! so a captured `/debug/explain` document lands on *that* instance, its local
6//! break-glass ring and `/debug/explain` store are invisible to the others. To
7//! diagnose "why did request X route there" across a fleet, the capture must leave
8//! the instance, keyed by `trace_id` (which the explain doc already carries), so an
9//! external aggregator holds the whole fleet's record under one key.
10//!
11//! This seam is the **fleet-coherent counterpart of the break-glass ring**: the
12//! same `ring_buffer`/`capture` directive that fills the local tape also hands the
13//! shape-only explain doc to a [`DiagnosticSink`]. It is distinct from the
14//! per-request request-log (all-or-none), it pushes only the *directive-selected*
15//! captures. The doc is shape-only by construction (it is the explain doc), so the
16//! sink never carries a tenant value.
17
18use serde_json::Value;
19
20/// Receives directive-selected diagnostic documents (each a `/debug/explain` doc)
21/// for delivery to a fleet-wide store/aggregator, keyed by the `trace_id` the doc
22/// carries.
23///
24/// Implementations MUST NOT block: `emit` is called inline on the request path
25/// (only for captured requests), so any network I/O belongs in a spawned task.
26/// They MUST NOT panic. Delivery is best-effort, a slow or down sink must never
27/// affect the request.
28pub trait DiagnosticSink: Send + Sync + 'static {
29 /// Whether this sink will do anything. The pipeline checks this before building
30 /// a document, so a disabled sink costs only this call even when a capture
31 /// directive is active.
32 fn enabled(&self) -> bool {
33 true
34 }
35
36 /// Hands off one shape-only diagnostic document for background delivery.
37 /// Returns immediately; delivery is best-effort.
38 fn emit(&self, doc: Value);
39}
40
41/// The default sink: off-instance emission is disabled, so a directive-selected
42/// capture stays in the local break-glass ring only (single-instance).
43#[derive(Clone, Copy, Debug, Default)]
44pub struct NoopDiagnosticSink;
45
46impl DiagnosticSink for NoopDiagnosticSink {
47 fn enabled(&self) -> bool {
48 false
49 }
50
51 fn emit(&self, _doc: Value) {}
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57
58 #[test]
59 fn the_noop_sink_is_disabled() {
60 assert!(!NoopDiagnosticSink.enabled());
61 NoopDiagnosticSink.emit(serde_json::json!({})); // no panic, no effect
62 }
63}