// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
///|
/// Makes spans, and the one place ids and the sampling decision are settled.
///
/// A tracer holds three things it cannot invent for itself: where randomness comes
/// from, where finished spans go, and who decides whether a trace is wanted. The
/// clock is the fourth, and it does not even reach the tracer — every call that
/// needs a time is handed one.
pub struct Tracer {
rand : (Int32) -> Bytes
sink : &Sink
sampler : &Sampler
limits : Limits
}
///|
/// A tracer drawing ids from `rand`, handing finished spans to `sink`, and asking
/// `sampler` about every span it starts.
///
/// `rand(n)` must return exactly `n` octets and must not return all zeros: eight
/// octets of zeros is not a valid span id, and a generator that produces one is
/// broken rather than unlucky. A call that breaks either rule makes `start`
/// refuse; see `Refused::Ids` and `Refused::Zero`.
pub fn Tracer::new(
rand~ : (Int32) -> Bytes,
sink~ : &Sink,
sampler~ : &Sampler,
limits~ : Limits = trace_limits,
) -> Tracer {
{ rand, sink, sampler, limits, }
}
///|
/// Start a span.
///
/// With a `parent` whose context is valid, the new span joins that trace: it takes
/// the parent's trace id and trace state, and `parent_based` sampling follows the
/// parent's decision. Without one it starts a trace of its own, with a fresh trace
/// id and no vendor state.
///
/// `at` is the start of the operation in nanoseconds. `attributes` and `links` are
/// set before the span is handed back; a sampler sees them, which is why they are
/// parameters here rather than calls on the span afterwards.
pub fn Tracer::start(
self : Tracer,
name : String,
parent? : TraceContext,
kind~ : SpanKind = Internal,
at~ : Int64,
attributes~ : Array[(String, Value)] = [],
links~ : Array[Link] = [],
) -> Span raise Refused {
let parent = match parent {
Some(p) if p.is_valid() => Some(p)
_ => None
}
// A trace id is needed before the sampler is asked, because the sampler's
// answer for a ratio sampler is a function of it.
let trace_id = match parent {
Some(p) => p.trace_id
None => self.mint(16)
}
let decision = self.sampler.should_sample({ parent, trace_id, name, kind, })
// Minted whatever the decision: a dropped span still has an identity, so
// logs and events recorded elsewhere can name it.
let span_id = self.mint(8)
let flags = match parent {
Some(p) => p.with_sampled(sampled=decision.sampled()).flags
None => if decision.sampled() { sampled_flag } else { b'\x00' }
}
let state = match parent {
Some(p) => p.state
None => empty_trace_state
}
let span = Span::new(
{ trace_id, span_id, flags, state, remote: false, },
match parent {
Some(p) => Some(p.span_id)
None => None
},
kind,
at,
name,
decision.recording(),
self.sink,
self.limits,
)
span.set_attributes(attributes)
for link in links {
span.add_link(link)
}
span
}
///|
/// `width` octets from the caller's generator, refused unless the shape is one an
/// id can take.
fn Tracer::mint(self : Tracer, width : Int32) -> Bytes raise Refused {
let bytes = (self.rand)(width)
if bytes.length() != width {
raise Refused::Ids(want=width, got=bytes.length())
}
let mut non_zero = false
for i in 0..