Skip to main content

obs_core/scope/
builder.rs

1//! `ScopeFrameBuilder` — public, programmatic API for pushing a scope
2//! frame from outside `obs-core`.
3//!
4//! `obs::scope!` is the canonical user-facing API and resolves to a
5//! macro that captures the call site at compile time. External crates
6//! that need to push a frame from a generic context (the tracing
7//! bridge, `obs-tower`, user-defined middleware) cannot use the macro
8//! because the field set is computed at runtime. This builder fills
9//! that gap. Spec 13 § 4 (D7-3 in spec 94).
10
11use super::{ScopeField, ScopeFrame, ScopeGuard, ScopeKind};
12
13/// Programmatic builder for pushing an `obs::scope!`-shaped frame.
14///
15/// External crates use this when they need to push a frame whose
16/// fields are decided at runtime (e.g. the tracing bridge stamps
17/// `(trace_id, span_id, parent_span_id)` from a span extension; the
18/// HTTP middleware stamps the same fields from extracted W3C
19/// `traceparent` headers).
20///
21/// The builder is consumed by [`Self::push`], which returns a
22/// [`ScopeGuard`] that pops the frame on drop. To carry the frame
23/// across an async boundary, use [`Self::into_frame`] and feed the
24/// resulting [`ScopeFrame`] to
25/// [`crate::instrumented::Instrument::instrument`].
26#[derive(Debug)]
27pub struct ScopeFrameBuilder {
28    fields: Vec<ScopeField>,
29    kind: ScopeKind,
30    tail_capacity: u16,
31    traceparent_sampled: Option<bool>,
32    span_identity: Option<(&'static str, &'static str)>,
33}
34
35impl Default for ScopeFrameBuilder {
36    fn default() -> Self {
37        Self::new()
38    }
39}
40
41impl ScopeFrameBuilder {
42    /// New builder defaulting to a `Scope` frame with a 64-deep
43    /// tail-on-error buffer (matches the `obs::scope!` defaults).
44    #[must_use]
45    pub fn new() -> Self {
46        Self {
47            fields: Vec::new(),
48            kind: ScopeKind::Scope,
49            tail_capacity: 64,
50            traceparent_sampled: None,
51            span_identity: None,
52        }
53    }
54
55    /// Switch to a `Context` frame (no tail-on-error buffer cost).
56    /// Equivalent to `obs::context!`.
57    #[must_use]
58    pub fn context(mut self) -> Self {
59        self.kind = ScopeKind::Context;
60        self.tail_capacity = 0;
61        self
62    }
63
64    /// Override the tail-on-error capacity (only meaningful for
65    /// `Scope` kind; ignored for `Context`).
66    #[must_use]
67    pub fn tail_capacity(mut self, capacity: u16) -> Self {
68        self.tail_capacity = capacity;
69        self
70    }
71
72    /// Set `trace_id` on the frame so emitted envelopes inherit it
73    /// via [`super::auto_fill_envelope`].
74    #[must_use]
75    pub fn trace_id(mut self, value: impl Into<String>) -> Self {
76        self.fields.push(ScopeField::TraceId(value.into()));
77        self
78    }
79
80    /// Set `span_id` on the frame.
81    #[must_use]
82    pub fn span_id(mut self, value: impl Into<String>) -> Self {
83        self.fields.push(ScopeField::SpanId(value.into()));
84        self
85    }
86
87    /// Set `parent_span_id` on the frame.
88    #[must_use]
89    pub fn parent_span_id(mut self, value: impl Into<String>) -> Self {
90        self.fields.push(ScopeField::ParentSpanId(value.into()));
91        self
92    }
93
94    /// Add a `(name, value)` label pair. The `name` must be a static
95    /// `&'static str` so it can round-trip through the envelope's
96    /// `labels` map without an allocation. Spec 13 § 2.1.
97    #[must_use]
98    pub fn label(mut self, name: &'static str, value: impl Into<String>) -> Self {
99        self.fields.push(ScopeField::Label(name, value.into()));
100        self
101    }
102
103    /// Inbound `traceparent.sampled` decision. Spec 13 § 6.
104    #[must_use]
105    pub fn traceparent_sampled(mut self, sampled: bool) -> Self {
106        self.traceparent_sampled = Some(sampled);
107        self
108    }
109
110    /// Bridged tracing-span identity for `obs::SpanTrace` rendering.
111    /// Spec 13 § 9.
112    #[must_use]
113    pub fn span_identity(mut self, name: &'static str, target: &'static str) -> Self {
114        self.span_identity = Some((name, target));
115        self
116    }
117
118    /// Push the frame onto the active task's scope stack and return
119    /// the RAII guard. Drop the guard to pop the frame.
120    pub fn push(self) -> ScopeGuard {
121        let frame = self.into_frame();
122        ScopeGuard::enter_with_frame(frame)
123    }
124
125    /// Build the frame without pushing it. Useful when the caller
126    /// wants to attach it to a future via
127    /// [`crate::instrumented::Instrument::instrument`] so the frame
128    /// is re-entered on every poll.
129    #[must_use]
130    pub fn into_frame(self) -> ScopeFrame {
131        let mut frame = ScopeFrame::new(self.fields, self.kind, self.tail_capacity);
132        if let Some(sampled) = self.traceparent_sampled {
133            frame.set_traceparent_sampled(sampled);
134        }
135        if let Some((name, target)) = self.span_identity {
136            frame.set_span_identity(name, target);
137        }
138        frame
139    }
140}