use std::borrow::Cow;
use std::ptr::NonNull;
use std::sync::Arc;
use super::collector::SpanCollector;
use super::context::{SpanContext, SpanEnterGuard};
use super::ids::{SpanId, TraceId};
#[derive(Clone, Copy)]
pub(crate) struct CollectorRef(NonNull<SpanCollector>);
unsafe impl Send for CollectorRef {}
unsafe impl Sync for CollectorRef {}
impl CollectorRef {
pub(crate) fn from_arc(arc: &Arc<SpanCollector>) -> Self {
Self(NonNull::from(arc.as_ref()))
}
#[inline]
pub(crate) fn as_ref(&self) -> &SpanCollector {
unsafe { self.0.as_ref() }
}
}
static CLOCK_ANCHOR: std::sync::LazyLock<(std::time::Instant, u64)> =
std::sync::LazyLock::new(|| {
let wall = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64;
(std::time::Instant::now(), wall)
});
#[inline]
pub(crate) fn now_nanos() -> u64 {
let (ref mono, wall) = *CLOCK_ANCHOR;
wall + mono.elapsed().as_nanos() as u64
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SpanKind {
Internal,
Server,
Client,
Producer,
Consumer,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub enum SpanStatus {
#[default]
Unset,
Ok,
Error { message: Cow<'static, str> },
}
pub struct SpanAttribute {
pub key: Cow<'static, str>,
pub value: SpanValue,
}
impl SpanAttribute {
pub fn new(key: impl Into<Cow<'static, str>>, value: impl Into<SpanValue>) -> Self {
Self {
key: key.into(),
value: value.into(),
}
}
}
pub enum SpanValue {
String(Cow<'static, str>),
I64(i64),
F64(f64),
Bool(bool),
Uuid(uuid::Uuid),
}
impl From<&'static str> for SpanValue {
fn from(s: &'static str) -> Self {
Self::String(Cow::Borrowed(s))
}
}
impl From<String> for SpanValue {
fn from(s: String) -> Self {
Self::String(Cow::Owned(s))
}
}
impl From<Cow<'static, str>> for SpanValue {
fn from(s: Cow<'static, str>) -> Self {
Self::String(s)
}
}
impl From<i64> for SpanValue {
fn from(v: i64) -> Self {
Self::I64(v)
}
}
impl From<f64> for SpanValue {
fn from(v: f64) -> Self {
Self::F64(v)
}
}
impl From<bool> for SpanValue {
fn from(v: bool) -> Self {
Self::Bool(v)
}
}
impl From<uuid::Uuid> for SpanValue {
fn from(v: uuid::Uuid) -> Self {
Self::Uuid(v)
}
}
pub struct SpanEvent {
pub name: Cow<'static, str>,
pub time_ns: u64,
pub attributes: Vec<SpanAttribute>,
}
pub struct CompletedSpan {
pub trace_id: TraceId,
pub span_id: SpanId,
pub parent_span_id: SpanId,
pub name: Cow<'static, str>,
pub kind: SpanKind,
pub start_time_ns: u64,
pub end_time_ns: u64,
pub status: SpanStatus,
pub attributes: Vec<SpanAttribute>,
pub events: Vec<SpanEvent>,
}
#[must_use = "span is submitted to collector on drop — bind it to a variable"]
pub struct Span {
data: Option<CompletedSpan>,
collector: CollectorRef,
_enter_guard: Option<SpanEnterGuard>,
}
impl Span {
#[inline]
pub(crate) fn noop(collector: CollectorRef) -> Self {
Self {
data: None,
collector,
_enter_guard: None,
}
}
#[inline]
pub(crate) fn new_root(
name: impl Into<Cow<'static, str>>,
kind: SpanKind,
collector: CollectorRef,
) -> Self {
let data = CompletedSpan {
trace_id: TraceId::random(),
span_id: SpanId::random(),
parent_span_id: SpanId::INVALID,
name: name.into(),
kind,
start_time_ns: now_nanos(),
end_time_ns: 0,
status: SpanStatus::Unset,
attributes: Vec::new(),
events: Vec::new(),
};
Self {
data: Some(data),
collector,
_enter_guard: None,
}
}
pub(crate) fn new_from_remote(
name: impl Into<Cow<'static, str>>,
kind: SpanKind,
parent_ctx: SpanContext,
collector: CollectorRef,
) -> Self {
let data = CompletedSpan {
trace_id: parent_ctx.trace_id,
span_id: SpanId::random(),
parent_span_id: parent_ctx.span_id,
name: name.into(),
kind,
start_time_ns: now_nanos(),
end_time_ns: 0,
status: SpanStatus::Unset,
attributes: Vec::new(),
events: Vec::new(),
};
Self {
data: Some(data),
collector,
_enter_guard: None,
}
}
#[inline]
pub fn child(&self, name: impl Into<Cow<'static, str>>, kind: SpanKind) -> Span {
let Some(parent_data) = self.data.as_ref() else {
return Span::noop(self.collector);
};
let data = CompletedSpan {
trace_id: parent_data.trace_id,
span_id: SpanId::random(),
parent_span_id: parent_data.span_id,
name: name.into(),
kind,
start_time_ns: now_nanos(),
end_time_ns: 0,
status: SpanStatus::Unset,
attributes: Vec::new(),
events: Vec::new(),
};
Span {
data: Some(data),
collector: self.collector,
_enter_guard: None,
}
}
pub fn enter(&mut self) -> &mut Self {
if self._enter_guard.is_none()
&& let Some(data) = self.data.as_ref()
{
self._enter_guard = Some(SpanEnterGuard::enter(SpanContext {
trace_id: data.trace_id,
span_id: data.span_id,
trace_flags: 0x01, }));
}
self
}
pub fn add_event(
&mut self,
name: impl Into<Cow<'static, str>>,
attributes: Vec<SpanAttribute>,
) {
if let Some(data) = self.data.as_mut() {
data.events.push(SpanEvent {
name: name.into(),
time_ns: now_nanos(),
attributes,
});
}
}
pub fn add_simple_event(&mut self, name: impl Into<Cow<'static, str>>) {
if let Some(data) = self.data.as_mut() {
data.events.push(SpanEvent {
name: name.into(),
time_ns: now_nanos(),
attributes: Vec::new(),
});
}
}
#[inline]
pub fn set_attribute(
&mut self,
key: impl Into<Cow<'static, str>>,
value: impl Into<SpanValue>,
) {
if let Some(data) = self.data.as_mut() {
data.attributes.push(SpanAttribute {
key: key.into(),
value: value.into(),
});
}
}
#[inline]
pub fn set_status(&mut self, status: SpanStatus) {
if let Some(data) = self.data.as_mut() {
data.status = status;
}
}
pub fn trace_id(&self) -> TraceId {
self.data.as_ref().map_or(TraceId::INVALID, |d| d.trace_id)
}
pub fn span_id(&self) -> SpanId {
self.data.as_ref().map_or(SpanId::INVALID, |d| d.span_id)
}
pub fn traceparent(&self) -> String {
let Some(data) = self.data.as_ref() else {
return String::new();
};
let ctx = SpanContext {
trace_id: data.trace_id,
span_id: data.span_id,
trace_flags: 0x01, };
ctx.to_traceparent()
}
pub fn end(self) {
drop(self);
}
}
impl Drop for Span {
fn drop(&mut self) {
if let Some(mut completed) = self.data.take() {
completed.end_time_ns = now_nanos();
self.collector.as_ref().submit(completed);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_collector() -> Arc<SpanCollector> {
Arc::new(SpanCollector::new(1, 64))
}
fn flush_and_drain(collector: &SpanCollector, buf: &mut Vec<CompletedSpan>) {
collector.flush_local();
collector.drain_into(buf);
}
#[test]
fn root_span_lifecycle() {
let collector = test_collector();
{
let mut span = Span::new_root(
"test_op",
SpanKind::Server,
CollectorRef::from_arc(&collector),
);
span.set_attribute("key", "value");
span.add_simple_event("checkpoint");
span.set_status(SpanStatus::Ok);
}
let mut buf = Vec::new();
flush_and_drain(&collector, &mut buf);
assert_eq!(buf.len(), 1);
let completed = &buf[0];
assert!(!completed.trace_id.is_invalid());
assert!(!completed.span_id.is_invalid());
assert!(completed.parent_span_id.is_invalid()); assert_eq!(completed.name, "test_op");
assert_eq!(completed.kind, SpanKind::Server);
assert_eq!(completed.status, SpanStatus::Ok);
assert_eq!(completed.attributes.len(), 1);
assert_eq!(completed.events.len(), 1);
assert!(completed.end_time_ns >= completed.start_time_ns);
}
#[test]
fn child_inherits_trace_id() {
let collector = test_collector();
let root_trace_id;
let root_span_id;
{
let root = Span::new_root(
"parent",
SpanKind::Server,
CollectorRef::from_arc(&collector),
);
root_trace_id = root.trace_id();
root_span_id = root.span_id();
{
let _child = root.child("child_op", SpanKind::Client);
}
}
let mut buf = Vec::new();
flush_and_drain(&collector, &mut buf);
assert_eq!(buf.len(), 2);
let child = &buf[0];
let parent = &buf[1];
assert_eq!(child.trace_id, root_trace_id);
assert_eq!(child.parent_span_id, root_span_id);
assert_eq!(parent.trace_id, root_trace_id);
assert!(parent.parent_span_id.is_invalid());
}
#[test]
fn explicit_end() {
let collector = test_collector();
let span = Span::new_root(
"explicit",
SpanKind::Internal,
CollectorRef::from_arc(&collector),
);
span.end();
let mut buf = Vec::new();
flush_and_drain(&collector, &mut buf);
assert_eq!(buf.len(), 1);
}
#[test]
fn traceparent_encoding() {
let collector = test_collector();
let span = Span::new_root(
"tp_test",
SpanKind::Server,
CollectorRef::from_arc(&collector),
);
let tp = span.traceparent();
assert!(tp.starts_with("00-"));
assert!(tp.ends_with("-01"));
assert_eq!(tp.len(), 55);
}
#[test]
fn from_remote_parent() {
let collector = test_collector();
let remote_ctx = SpanContext::from_traceparent(
"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
)
.expect("valid traceparent");
let span = Span::new_from_remote(
"server_handler",
SpanKind::Server,
remote_ctx,
CollectorRef::from_arc(&collector),
);
assert_eq!(span.trace_id(), remote_ctx.trace_id);
drop(span);
let mut buf = Vec::new();
flush_and_drain(&collector, &mut buf);
let completed = &buf[0];
assert_eq!(completed.trace_id, remote_ctx.trace_id);
assert_eq!(completed.parent_span_id, remote_ctx.span_id);
}
}