use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use std::sync::{Arc, Mutex};
#[derive(Debug, Clone, serde::Serialize)]
pub struct RecordedSpan {
pub name: String,
pub start_time_ms: f64,
pub end_time_ms: Option<f64>,
pub attributes: HashMap<String, String>,
pub events: Vec<SpanEvent>,
pub status: SpanStatus,
pub parent_id: Option<String>,
pub span_id: String,
pub baggage: HashMap<String, String>,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct SpanEvent {
pub name: String,
pub timestamp_ms: f64,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize)]
pub enum SpanStatus {
Unset,
Ok,
Error(String),
}
#[derive(Clone)]
pub struct Sampler {
sample_rate: f64,
always_sample_errors: bool,
}
impl Sampler {
pub fn always_on() -> Self {
Self {
sample_rate: 1.0,
always_sample_errors: false,
}
}
pub fn always_off() -> Self {
Self {
sample_rate: 0.0,
always_sample_errors: false,
}
}
pub fn probability(sample_rate: f64) -> Self {
let rate = sample_rate.clamp(0.0, 1.0);
Self {
sample_rate: rate,
always_sample_errors: false,
}
}
pub fn with_error_sampling(mut self, enabled: bool) -> Self {
self.always_sample_errors = enabled;
self
}
pub fn should_sample(&self, span_name: &str, attributes: &HashMap<String, String>) -> bool {
if self.always_sample_errors && attributes.get("error").is_some() {
return true;
}
if self.sample_rate >= 1.0 {
return true;
}
if self.sample_rate <= 0.0 {
return false;
}
let hash = self.hash_string(span_name);
let threshold = (self.sample_rate * u64::MAX as f64) as u64;
hash < threshold
}
fn hash_string(&self, s: &str) -> u64 {
let mut hash: u64 = 0xcbf29ce484222325;
for byte in s.bytes() {
hash ^= byte as u64;
hash = hash.wrapping_mul(0x100000001b3);
}
hash
}
}
impl Default for Sampler {
fn default() -> Self {
Self::always_on()
}
}
#[derive(Clone)]
pub struct SpanRecorder {
spans: Arc<Mutex<Vec<RecordedSpan>>>,
enabled: bool,
sampler: Option<Sampler>,
}
impl SpanRecorder {
pub fn new() -> Self {
Self {
spans: Arc::new(Mutex::new(Vec::new())),
enabled: true,
sampler: None,
}
}
pub fn disabled() -> Self {
Self {
spans: Arc::new(Mutex::new(Vec::new())),
enabled: false,
sampler: None,
}
}
pub fn with_sampler(mut self, sampler: Sampler) -> Self {
self.sampler = Some(sampler);
self
}
pub fn record_span(&self, span: RecordedSpan) {
if !self.enabled {
return;
}
if let Some(ref sampler) = self.sampler {
let mut attributes = span.attributes.clone();
if matches!(span.status, SpanStatus::Error(_)) {
attributes.insert("error".to_string(), "true".to_string());
}
if !sampler.should_sample(&span.name, &attributes) {
return; }
}
let mut spans = self.spans.lock().unwrap();
spans.push(span);
}
pub fn get_spans(&self) -> Vec<RecordedSpan> {
let spans = self.spans.lock().unwrap();
spans.clone()
}
pub fn get_spans_by_name(&self, name: &str) -> Vec<RecordedSpan> {
let spans = self.spans.lock().unwrap();
spans.iter().filter(|s| s.name == name).cloned().collect()
}
pub fn get_latest_span(&self) -> Option<RecordedSpan> {
let spans = self.spans.lock().unwrap();
spans.last().cloned()
}
pub fn clear(&self) {
let mut spans = self.spans.lock().unwrap();
spans.clear();
}
pub fn span_count(&self) -> usize {
let spans = self.spans.lock().unwrap();
spans.len()
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
}
impl Default for SpanRecorder {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone)]
pub struct SpanContext {
span_stack: Rc<RefCell<Vec<String>>>,
baggage: Rc<RefCell<HashMap<String, String>>>,
}
impl SpanContext {
pub fn new() -> Self {
Self {
span_stack: Rc::new(RefCell::new(Vec::new())),
baggage: Rc::new(RefCell::new(HashMap::new())),
}
}
pub fn enter_span(&self, span_id: String) {
let mut stack = self.span_stack.borrow_mut();
stack.push(span_id);
}
pub fn exit_span(&self) {
let mut stack = self.span_stack.borrow_mut();
stack.pop();
}
pub fn current_span_id(&self) -> Option<String> {
let stack = self.span_stack.borrow();
stack.last().cloned()
}
pub fn set_baggage(&self, key: impl Into<String>, value: impl Into<String>) {
let mut baggage = self.baggage.borrow_mut();
baggage.insert(key.into(), value.into());
}
pub fn get_baggage(&self, key: &str) -> Option<String> {
let baggage = self.baggage.borrow();
baggage.get(key).cloned()
}
pub fn get_all_baggage(&self) -> HashMap<String, String> {
let baggage = self.baggage.borrow();
baggage.clone()
}
pub fn clear_baggage(&self) {
let mut baggage = self.baggage.borrow_mut();
baggage.clear();
}
}
impl Default for SpanContext {
fn default() -> Self {
Self::new()
}
}
pub struct SpanBuilder {
name: String,
start_time_ms: f64,
attributes: HashMap<String, String>,
parent_id: Option<String>,
baggage: HashMap<String, String>,
}
impl SpanBuilder {
pub fn new(name: String) -> Self {
#[cfg(target_arch = "wasm32")]
let start_time_ms = js_sys::Date::now();
#[cfg(not(target_arch = "wasm32"))]
let start_time_ms = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis() as f64;
Self {
name,
start_time_ms,
attributes: HashMap::new(),
parent_id: None,
baggage: HashMap::new(),
}
}
pub fn with_attribute(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.attributes.insert(key.into(), value.into());
self
}
pub fn with_parent(mut self, parent_id: impl Into<String>) -> Self {
self.parent_id = Some(parent_id.into());
self
}
pub fn with_context(mut self, context: &SpanContext) -> Self {
if let Some(parent_id) = context.current_span_id() {
self.parent_id = Some(parent_id);
}
self
}
pub fn with_baggage_from_context(mut self, context: &SpanContext) -> Self {
self.baggage = context.get_all_baggage();
self
}
pub fn build(self) -> RecordedSpan {
let span_id = format!("span_{}", self.start_time_ms as u64);
RecordedSpan {
name: self.name,
start_time_ms: self.start_time_ms,
end_time_ms: None,
attributes: self.attributes,
events: Vec::new(),
status: SpanStatus::Unset,
parent_id: self.parent_id,
span_id,
baggage: self.baggage,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_span_recorder_new() {
let recorder = SpanRecorder::new();
assert!(recorder.is_enabled());
assert_eq!(recorder.span_count(), 0);
}
#[test]
fn test_span_recorder_disabled() {
let recorder = SpanRecorder::disabled();
assert!(!recorder.is_enabled());
}
#[test]
fn test_record_span() {
let recorder = SpanRecorder::new();
let span = SpanBuilder::new("test_operation".to_string())
.with_attribute("key", "value")
.build();
recorder.record_span(span.clone());
assert_eq!(recorder.span_count(), 1);
let recorded = recorder.get_latest_span().unwrap();
assert_eq!(recorded.name, "test_operation");
}
#[test]
fn test_get_spans_by_name() {
let recorder = SpanRecorder::new();
let span1 = SpanBuilder::new("query".to_string()).build();
let span2 = SpanBuilder::new("sync".to_string()).build();
let span3 = SpanBuilder::new("query".to_string()).build();
recorder.record_span(span1);
recorder.record_span(span2);
recorder.record_span(span3);
let query_spans = recorder.get_spans_by_name("query");
assert_eq!(query_spans.len(), 2);
}
#[test]
fn test_clear_spans() {
let recorder = SpanRecorder::new();
let span = SpanBuilder::new("test".to_string()).build();
recorder.record_span(span);
assert_eq!(recorder.span_count(), 1);
recorder.clear();
assert_eq!(recorder.span_count(), 0);
}
#[test]
fn test_disabled_recorder_no_op() {
let recorder = SpanRecorder::disabled();
let span = SpanBuilder::new("test".to_string()).build();
recorder.record_span(span);
assert_eq!(recorder.span_count(), 0);
}
}