1extern crate alloc;
7
8#[cfg(feature = "std")]
9use alloc::boxed::Box;
10use alloc::vec::Vec;
11
12use crate::span::ResourceSpan;
13
14#[cfg(feature = "std")]
15static GLOBAL_SPAN_EXPORTER: std::sync::OnceLock<Box<dyn SpanExporter + Send + Sync>> =
16 std::sync::OnceLock::new();
17
18pub trait SpanExporter {
20 fn export(&self, spans: &[ResourceSpan]);
23
24 fn flush(&self);
26}
27
28#[cfg(feature = "std")]
35pub fn set_global_span_exporter(exporter: Box<dyn SpanExporter + Send + Sync>) -> bool {
36 GLOBAL_SPAN_EXPORTER.set(exporter).is_ok()
37}
38
39#[cfg(feature = "std")]
44pub fn emit_span(span: ResourceSpan) {
45 if let Some(exporter) = GLOBAL_SPAN_EXPORTER.get() {
46 exporter.export(&[span]);
47 }
48}
49
50#[cfg(feature = "std")]
52pub fn flush_global_span_exporter() {
53 if let Some(exporter) = GLOBAL_SPAN_EXPORTER.get() {
54 exporter.flush();
55 }
56}
57
58#[cfg(not(feature = "std"))]
60pub fn emit_span(_span: ResourceSpan) {}
61
62pub struct NullExporter {
85 #[cfg(feature = "std")]
86 spans: std::sync::Mutex<Vec<ResourceSpan>>,
87 #[cfg(not(feature = "std"))]
88 spans: core::cell::RefCell<Vec<ResourceSpan>>,
89}
90
91impl NullExporter {
92 pub fn new() -> Self {
94 NullExporter {
95 #[cfg(feature = "std")]
96 spans: std::sync::Mutex::new(Vec::new()),
97 #[cfg(not(feature = "std"))]
98 spans: core::cell::RefCell::new(Vec::new()),
99 }
100 }
101
102 pub fn len(&self) -> usize {
104 #[cfg(feature = "std")]
105 {
106 self.spans.lock().unwrap().len()
107 }
108 #[cfg(not(feature = "std"))]
109 {
110 self.spans.borrow().len()
111 }
112 }
113
114 pub fn is_empty(&self) -> bool {
116 self.len() == 0
117 }
118
119 pub fn drain(&self) -> Vec<ResourceSpan> {
121 #[cfg(feature = "std")]
122 {
123 let mut guard = self.spans.lock().unwrap();
124 core::mem::take(&mut *guard)
125 }
126 #[cfg(not(feature = "std"))]
127 {
128 let mut guard = self.spans.borrow_mut();
129 core::mem::take(&mut *guard)
130 }
131 }
132
133 pub fn snapshot(&self) -> Vec<ResourceSpan> {
135 #[cfg(feature = "std")]
136 {
137 self.spans.lock().unwrap().clone()
138 }
139 #[cfg(not(feature = "std"))]
140 {
141 self.spans.borrow().clone()
142 }
143 }
144}
145
146impl Default for NullExporter {
147 fn default() -> Self {
148 Self::new()
149 }
150}
151
152impl SpanExporter for NullExporter {
153 fn export(&self, spans: &[ResourceSpan]) {
154 #[cfg(feature = "std")]
155 {
156 let mut guard = self.spans.lock().unwrap();
157 guard.extend(spans.iter().cloned());
158 }
159 #[cfg(not(feature = "std"))]
160 {
161 let mut guard = self.spans.borrow_mut();
162 guard.extend(spans.iter().cloned());
163 }
164 }
165
166 fn flush(&self) {
167 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174 use crate::trace::TraceContext;
175 #[cfg(feature = "std")]
176 use std::sync::{Arc, Mutex};
177
178 #[test]
179 fn null_exporter_collects_and_drains() {
180 let exporter = NullExporter::new();
181 assert!(exporter.is_empty());
182
183 let span1 = ResourceSpan::new("op1", TraceContext::default());
184 let span2 = ResourceSpan::new("op2", TraceContext::default());
185 exporter.export(&[span1, span2]);
186
187 assert_eq!(exporter.len(), 2);
188
189 let drained = exporter.drain();
190 assert_eq!(drained.len(), 2);
191 assert_eq!(drained[0].name, "op1");
192 assert_eq!(drained[1].name, "op2");
193 assert!(exporter.is_empty());
194 }
195
196 #[test]
197 fn null_exporter_snapshot_does_not_drain() {
198 let exporter = NullExporter::new();
199 let span = ResourceSpan::new("snap", TraceContext::default());
200 exporter.export(&[span]);
201
202 let snap = exporter.snapshot();
203 assert_eq!(snap.len(), 1);
204 assert_eq!(exporter.len(), 1); }
206
207 #[test]
208 fn null_exporter_multiple_exports() {
209 let exporter = NullExporter::new();
210 for i in 0..10 {
211 let name = alloc::format!("span_{i}");
212 let span = ResourceSpan::new(&name, TraceContext::default());
213 exporter.export(&[span]);
214 }
215 assert_eq!(exporter.len(), 10);
216 }
217
218 #[cfg(feature = "std")]
219 #[derive(Clone)]
220 struct SharedExporter {
221 spans: Arc<Mutex<Vec<ResourceSpan>>>,
222 }
223
224 #[cfg(feature = "std")]
225 impl SpanExporter for SharedExporter {
226 fn export(&self, spans: &[ResourceSpan]) {
227 self.spans.lock().unwrap().extend(spans.iter().cloned());
228 }
229
230 fn flush(&self) {}
231 }
232
233 #[cfg(feature = "std")]
238 #[test]
239 fn global_span_exporter_receives_emit_span() {
240 let spans = Arc::new(Mutex::new(Vec::new()));
241 let exporter = SharedExporter {
242 spans: spans.clone(),
243 };
244
245 let _ = set_global_span_exporter(Box::new(exporter));
246 emit_span(ResourceSpan::new(
247 crate::contract::PHASE_219_SPAN_NAMES[0],
248 TraceContext::default(),
249 ));
250
251 let captured = spans.lock().unwrap();
252 assert!(
253 captured
254 .iter()
255 .any(|span| span.name == crate::contract::PHASE_219_SPAN_NAMES[0]),
256 "global span exporter must receive spans emitted through emit_span()"
257 );
258 }
259}