provide_telemetry/
tracer.rs1use std::collections::BTreeMap;
7use std::sync::atomic::{AtomicU64, Ordering};
8
9use crate::backpressure::{release, try_acquire, QueueTicket};
10use crate::consent::should_allow;
11use crate::context::{set_trace_context_internal, trace_snapshot, ContextGuard};
12use crate::health::increment_emitted;
13use crate::sampling::{should_sample, Signal};
14
15pub struct NoopSpan {
16 trace_id: String,
17 span_id: String,
18 guard: Option<ContextGuard>,
19}
20
21struct ActiveTrace {
22 ticket: Option<QueueTicket>,
23 noop_span: Option<NoopSpan>,
24 #[cfg(feature = "otel")]
25 otel_span: Option<crate::otel::traces::OtelSpanGuard>,
26}
27
28#[derive(Clone, Debug, PartialEq, Eq)]
29pub struct Tracer {
30 name: String,
31}
32
33pub static tracer: std::sync::LazyLock<Tracer> = std::sync::LazyLock::new(|| Tracer::new(None));
34
35impl Tracer {
36 pub fn new(name: Option<&str>) -> Self {
37 Self {
38 name: name.unwrap_or("provide.telemetry").to_string(),
39 }
40 }
41
42 pub fn name(&self) -> &str {
43 &self.name
44 }
45
46 pub fn start_span(&self, _name: &str) -> NoopSpan {
47 let trace_id = next_hex(32);
48 let span_id = next_hex(16);
49 let guard = set_trace_context(Some(trace_id.clone()), Some(span_id.clone()));
50 NoopSpan {
51 trace_id,
52 span_id,
53 guard: Some(guard),
54 }
55 }
56}
57
58static TRACE_COUNTER: AtomicU64 = AtomicU64::new(1);
59
60fn next_hex(len: usize) -> String {
61 let seed = TRACE_COUNTER.fetch_add(1, Ordering::Relaxed);
62 let mut value = format!("{seed:016x}");
63 for _ in (16..len).step_by(16) {
67 let snapshot = TRACE_COUNTER.fetch_add(1, Ordering::Relaxed);
68 value.push_str(&format!("{snapshot:016x}"));
69 }
70 value.truncate(len);
71 value
72}
73
74pub fn get_tracer(name: Option<&str>) -> Tracer {
75 Tracer::new(name)
76}
77
78pub fn set_trace_context(trace_id: Option<String>, span_id: Option<String>) -> ContextGuard {
79 set_trace_context_internal(trace_id, span_id)
80}
81
82pub fn get_trace_context() -> BTreeMap<String, Option<String>> {
83 let snapshot = trace_snapshot();
84 BTreeMap::from([
85 ("trace_id".to_string(), snapshot.trace_id),
86 ("span_id".to_string(), snapshot.span_id),
87 ])
88}
89
90fn begin_trace(name: &str) -> Option<ActiveTrace> {
91 if !should_allow("traces", None) {
92 return None;
93 }
94 #[cfg(feature = "otel")]
104 let otel_live = crate::otel::traces_provider_effective();
105 #[cfg(not(feature = "otel"))]
106 let otel_live = false;
107 if !otel_live && !should_sample(Signal::Traces, Some(name)).unwrap_or(true) {
108 return None;
109 }
110 let ticket = try_acquire(Signal::Traces)?;
111
112 #[cfg(feature = "otel")]
117 {
118 if otel_live {
119 increment_emitted(Signal::Traces, 1);
120 return Some(ActiveTrace {
121 ticket: Some(ticket),
122 noop_span: None,
123 otel_span: Some(crate::otel::traces::start_span(name)),
124 });
125 }
126 }
127
128 increment_emitted(Signal::Traces, 1);
129 Some(ActiveTrace {
130 ticket: Some(ticket),
131 noop_span: Some(tracer.start_span(name)),
132 #[cfg(feature = "otel")]
133 otel_span: None,
134 })
135}
136
137pub fn trace<T, F>(name: &str, callback: F) -> T
138where
139 F: FnOnce() -> T,
140{
141 let _active = begin_trace(name);
142 callback()
143}
144
145impl NoopSpan {
146 pub fn trace_id(&self) -> &str {
147 &self.trace_id
148 }
149
150 pub fn span_id(&self) -> &str {
151 &self.span_id
152 }
153
154 pub fn set_attribute(&self, _key: &str, _value: &str) {}
155
156 pub fn record_error(&self, _error: &str) {}
157}
158
159impl Drop for NoopSpan {
160 #[cfg_attr(test, mutants::skip)] fn drop(&mut self) {
162 drop(self.guard.take());
163 }
164}
165
166impl Drop for ActiveTrace {
167 #[cfg_attr(test, mutants::skip)] fn drop(&mut self) {
169 #[cfg(feature = "otel")]
170 drop(self.otel_span.take());
171 drop(self.noop_span.take());
172 if let Some(ticket) = self.ticket.take() {
173 release(ticket);
174 }
175 }
176}
177
178#[cfg(test)]
179#[path = "tracer_tests.rs"]
180mod tests;