1use std::fmt;
7
8use crate::scope::{ScopeField, ScopeFrame, with_frames_innermost_first};
9
10#[derive(Debug, Clone)]
13struct CapturedFrame {
14 name: Option<String>,
15 target: Option<String>,
16 fields: Vec<(String, String)>,
17}
18
19#[derive(Debug, Clone, Default)]
21pub struct SpanTrace {
22 frames: Vec<CapturedFrame>,
23}
24
25impl SpanTrace {
26 #[must_use]
30 pub fn capture() -> Self {
31 let mut frames: Vec<CapturedFrame> =
32 with_frames_innermost_first(|stack| stack.iter().map(capture_frame).collect());
33 frames.shrink_to_fit();
37 Self { frames }
38 }
39
40 #[must_use]
42 pub fn is_empty(&self) -> bool {
43 self.frames.is_empty()
44 }
45
46 #[must_use]
48 pub fn len(&self) -> usize {
49 self.frames.len()
50 }
51}
52
53fn capture_frame(frame: &ScopeFrame) -> CapturedFrame {
54 let span = frame.as_span_frame();
55 CapturedFrame {
56 name: span.map(|s| s.name.to_string()),
57 target: span.map(|s| s.target.to_string()),
58 fields: frame
59 .fields()
60 .iter()
61 .map(|f| match f {
62 ScopeField::TraceId(v) => ("trace_id".to_string(), v.clone()),
63 ScopeField::SpanId(v) => ("span_id".to_string(), v.clone()),
64 ScopeField::ParentSpanId(v) => ("parent_span_id".to_string(), v.clone()),
65 ScopeField::Label(k, v) => ((*k).to_string(), v.clone()),
66 })
67 .collect(),
68 }
69}
70
71impl fmt::Display for SpanTrace {
72 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73 if self.frames.is_empty() {
74 return f.write_str("(no obs scope active)");
75 }
76 for (i, frame) in self.frames.iter().enumerate().rev() {
79 let depth = self.frames.len() - 1 - i;
80 write!(
81 f,
82 " {depth}: {}",
83 frame.name.as_deref().unwrap_or("<scope>")
84 )?;
85 if let Some(t) = frame.target.as_deref() {
86 write!(f, " @ {t}")?;
87 }
88 if !frame.fields.is_empty() {
89 write!(f, " [")?;
90 for (j, (k, v)) in frame.fields.iter().enumerate() {
91 if j > 0 {
92 write!(f, ", ")?;
93 }
94 write!(f, "{k}={v}")?;
95 }
96 write!(f, "]")?;
97 }
98 writeln!(f)?;
99 }
100 Ok(())
101 }
102}