osproxy_observe/export.rs
1//! The span-export seam: where assembled OTLP payloads go.
2//!
3//! Export is **read-only and off the request's critical path** (`docs/05`): the
4//! pipeline hands a finished payload to [`SpanExporter::export`], which returns
5//! immediately, a concrete exporter ships it in the background and an export
6//! failure never affects the request. When no exporter is configured the default
7//! [`NoopExporter`] reports [`SpanExporter::enabled`] `false`, so the pipeline
8//! skips even *encoding* the payload, "Off" is near-zero cost.
9
10use serde_json::Value;
11
12/// Receives finished OTLP span payloads for delivery to a collector.
13///
14/// Implementations MUST NOT block: `export` is called inline on the request path,
15/// so any network I/O belongs in a spawned task. They MUST NOT panic.
16pub trait SpanExporter: Send + Sync + 'static {
17 /// Whether this exporter will do anything. The pipeline checks this before
18 /// building a payload, so a disabled exporter costs only this call.
19 fn enabled(&self) -> bool {
20 true
21 }
22
23 /// Hands off a finished OTLP `ResourceSpans` payload (from
24 /// [`resource_spans`](crate::resource_spans)) for background delivery.
25 /// Returns immediately; delivery is best-effort.
26 fn export(&self, payload: Value);
27}
28
29/// The default exporter: export is disabled, so the pipeline never encodes a
30/// payload and nothing is shipped.
31#[derive(Clone, Copy, Debug, Default)]
32pub struct NoopExporter;
33
34impl SpanExporter for NoopExporter {
35 fn enabled(&self) -> bool {
36 false
37 }
38
39 fn export(&self, _payload: Value) {}
40}
41
42#[cfg(test)]
43mod tests {
44 use super::*;
45
46 #[test]
47 fn the_noop_exporter_is_disabled() {
48 assert!(!NoopExporter.enabled());
49 NoopExporter.export(serde_json::json!({})); // no panic, no effect
50 }
51}