Skip to main content

starweaver_runtime/trace/
types.rs

1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use starweaver_core::TraceContext;
6
7/// Trace detail level used by spans and events.
8#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
9#[serde(rename_all = "snake_case")]
10pub enum TraceLevel {
11    /// Exported by default.
12    #[default]
13    Info,
14    /// Exported when debug telemetry is enabled.
15    Debug,
16}
17
18#[allow(clippy::trivially_copy_pass_by_ref)]
19const fn is_default_trace_level(level: &TraceLevel) -> bool {
20    matches!(level, TraceLevel::Info)
21}
22
23/// Span role mapped to OpenTelemetry span kinds.
24#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
25#[serde(rename_all = "snake_case")]
26pub enum SpanKind {
27    /// Internal runtime work.
28    #[default]
29    Internal,
30    /// Client call to a remote service.
31    Client,
32    /// Server-side request handling.
33    Server,
34}
35
36#[allow(clippy::trivially_copy_pass_by_ref)]
37const fn is_default_span_kind(kind: &SpanKind) -> bool {
38    matches!(kind, SpanKind::Internal)
39}
40
41/// Span lifecycle status.
42#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
43#[serde(rename_all = "snake_case")]
44pub enum SpanStatus {
45    /// Span is still open.
46    Open,
47    /// Span completed successfully.
48    Ok,
49    /// Span completed with an error type.
50    Error {
51        /// Error type.
52        error_type: String,
53    },
54}
55
56/// Span event record.
57#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
58pub struct SpanEvent {
59    /// Event name.
60    pub name: String,
61    /// Event detail level.
62    #[serde(default, skip_serializing_if = "is_default_trace_level")]
63    pub level: TraceLevel,
64    /// Event attributes.
65    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
66    pub attributes: BTreeMap<String, Value>,
67}
68
69impl SpanEvent {
70    /// Create a span event by name.
71    #[must_use]
72    pub fn new(name: impl Into<String>) -> Self {
73        Self {
74            name: name.into(),
75            level: TraceLevel::Info,
76            attributes: BTreeMap::new(),
77        }
78    }
79
80    /// Mark the event as debug-level telemetry.
81    #[must_use]
82    pub const fn debug(mut self) -> Self {
83        self.level = TraceLevel::Debug;
84        self
85    }
86
87    /// Attach one event attribute.
88    #[must_use]
89    pub fn with_attribute(mut self, key: impl Into<String>, value: Value) -> Self {
90        self.attributes.insert(key.into(), value);
91        self
92    }
93}
94
95/// Span start specification.
96#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
97pub struct SpanSpec {
98    /// Span name.
99    pub name: String,
100    /// Span role.
101    #[serde(default, skip_serializing_if = "is_default_span_kind")]
102    pub kind: SpanKind,
103    /// Span detail level.
104    #[serde(default, skip_serializing_if = "is_default_trace_level")]
105    pub level: TraceLevel,
106    /// Span attributes.
107    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
108    pub attributes: BTreeMap<String, Value>,
109}
110
111impl SpanSpec {
112    /// Create a span spec by name.
113    #[must_use]
114    pub fn new(name: impl Into<String>) -> Self {
115        Self {
116            name: name.into(),
117            kind: SpanKind::Internal,
118            level: TraceLevel::Info,
119            attributes: BTreeMap::new(),
120        }
121    }
122
123    /// Set the span role.
124    #[must_use]
125    pub const fn with_kind(mut self, kind: SpanKind) -> Self {
126        self.kind = kind;
127        self
128    }
129
130    /// Mark the span as debug-level telemetry.
131    #[must_use]
132    pub const fn debug(mut self) -> Self {
133        self.level = TraceLevel::Debug;
134        self
135    }
136
137    /// Attach one attribute.
138    #[must_use]
139    pub fn with_attribute(mut self, key: impl Into<String>, value: Value) -> Self {
140        self.attributes.insert(key.into(), value);
141        self
142    }
143}
144
145/// Recorded span snapshot.
146#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
147pub struct RecordedSpan {
148    /// Span id.
149    pub span_id: String,
150    /// Trace id.
151    pub trace_id: String,
152    /// Optional parent span id.
153    #[serde(default, skip_serializing_if = "Option::is_none")]
154    pub parent_span_id: Option<String>,
155    /// Span name.
156    pub name: String,
157    /// Span role.
158    #[serde(default, skip_serializing_if = "is_default_span_kind")]
159    pub kind: SpanKind,
160    /// Span detail level.
161    #[serde(default, skip_serializing_if = "is_default_trace_level")]
162    pub level: TraceLevel,
163    /// Span attributes.
164    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
165    pub attributes: BTreeMap<String, Value>,
166    /// Span events.
167    #[serde(default, skip_serializing_if = "Vec::is_empty")]
168    pub events: Vec<SpanEvent>,
169    /// Span status.
170    pub status: SpanStatus,
171}
172
173/// Active span handle.
174#[derive(Clone, Debug)]
175pub struct SpanHandle {
176    context: TraceContext,
177    span_id: String,
178}
179
180impl SpanHandle {
181    pub(super) fn new(context: TraceContext, span_id: impl Into<String>) -> Self {
182        Self {
183            context,
184            span_id: span_id.into(),
185        }
186    }
187
188    /// Return the span trace context.
189    #[must_use]
190    pub const fn context(&self) -> &TraceContext {
191        &self.context
192    }
193
194    /// Consume the handle into its trace context.
195    #[must_use]
196    pub fn into_context(self) -> TraceContext {
197        self.context
198    }
199
200    /// Return span id.
201    #[must_use]
202    pub fn span_id(&self) -> &str {
203        &self.span_id
204    }
205}