use std::collections::HashMap;
use std::fmt;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Duration, Instant};
use parking_lot::Mutex;
use tracing::field::{Field, Visit};
use tracing::span::{Attributes, Id, Record};
use tracing::{Level, Subscriber};
use tracing_subscriber::layer::Context;
use tracing_subscriber::registry::LookupSpan;
pub const ROOT_FIELD: &str = "fibre.root";
pub const OUTCOME_FIELD: &str = "fibre.outcome";
pub const DEFAULT_RING: usize = 256;
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
Str(String),
Int(i64),
UInt(u64),
Float(f64),
Bool(bool),
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Value::Str(v) => f.write_str(v),
Value::Int(v) => write!(f, "{v}"),
Value::UInt(v) => write!(f, "{v}"),
Value::Float(v) => write!(f, "{v}"),
Value::Bool(v) => write!(f, "{v}"),
}
}
}
#[derive(Debug, Clone)]
pub struct Span {
pub name: &'static str,
pub target: String,
pub level: Level,
pub parent: Option<usize>,
pub depth: usize,
pub at: Duration,
pub duration: Duration,
pub fields: HashMap<String, Value>,
}
impl Span {
pub fn outcome(&self) -> Option<&str> {
match self.fields.get(OUTCOME_FIELD) {
Some(Value::Str(s)) => Some(s.as_str()),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct Trace {
pub id: u64,
pub spans: Vec<Span>,
pub duration: Duration,
}
impl Trace {
pub fn root(&self) -> Option<&Span> {
self.spans.first()
}
pub fn named<'a>(&'a self, name: &'a str) -> impl Iterator<Item = &'a Span> + 'a {
self.spans.iter().filter(move |s| s.name == name)
}
}
struct Open {
id: u64,
started: Instant,
spans: Vec<Span>,
index: HashMap<u64, usize>,
ids: Vec<u64>,
root: u64,
}
#[derive(Clone)]
pub struct Traces {
inner: Arc<Inner>,
}
struct Inner {
next_id: AtomicU64,
open: Mutex<HashMap<u64, Open>>,
belongs: Mutex<HashMap<u64, u64>>,
finished: Mutex<Vec<Trace>>,
ring: usize,
listeners: Mutex<Vec<Arc<dyn Fn(&Trace) + Send + Sync>>>,
}
impl Traces {
pub fn current(&self) -> Option<Trace> {
let id = tracing::Span::current().id()?;
self.of_span(id.into_u64())
}
pub fn of_span(&self, span: u64) -> Option<Trace> {
let root = *self.inner.belongs.lock().get(&span)?;
let open = self.inner.open.lock();
let held = open.get(&root)?;
Some(Trace { id: held.id, spans: held.spans.clone(), duration: Duration::ZERO })
}
pub fn recent(&self, n: usize) -> Vec<Trace> {
let held = self.inner.finished.lock();
let from = held.len().saturating_sub(n);
held[from..].to_vec()
}
pub fn len(&self) -> usize {
self.inner.finished.lock().len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn clear(&self) {
self.inner.finished.lock().clear();
}
pub fn on_finish(&self, f: impl Fn(&Trace) + Send + Sync + 'static) {
self.inner.listeners.lock().push(Arc::new(f));
}
}
pub fn layer() -> (TraceLayer, Traces) {
with_ring(DEFAULT_RING)
}
pub fn with_ring(ring: usize) -> (TraceLayer, Traces) {
let traces = Traces {
inner: Arc::new(Inner {
next_id: AtomicU64::new(1),
open: Mutex::new(HashMap::new()),
belongs: Mutex::new(HashMap::new()),
finished: Mutex::new(Vec::new()),
ring,
listeners: Mutex::new(Vec::new()),
}),
};
(TraceLayer { traces: traces.clone() }, traces)
}
pub struct TraceLayer {
traces: Traces,
}
impl<S> tracing_subscriber::Layer<S> for TraceLayer
where
S: Subscriber + for<'a> LookupSpan<'a>,
{
fn max_level_hint(&self) -> Option<tracing::level_filters::LevelFilter> {
Some(tracing::level_filters::LevelFilter::TRACE)
}
fn on_new_span(&self, attrs: &Attributes<'_>, id: &Id, ctx: Context<'_, S>) {
let mut fields = Fields::default();
attrs.record(&mut fields);
let is_root = matches!(fields.map.get(ROOT_FIELD), Some(Value::Bool(true)));
let this = id.into_u64();
let parent_span = ctx.span(id).and_then(|s| s.parent().map(|p| p.id().into_u64()));
let root = match is_root {
true => this,
false => match parent_span.and_then(|p| self.traces.inner.belongs.lock().get(&p).copied()) {
Some(root) => root,
None => return,
},
};
let mut open = self.traces.inner.open.lock();
if is_root {
let trace_id = self.traces.inner.next_id.fetch_add(1, Ordering::Relaxed);
open.insert(this, Open { id: trace_id, started: Instant::now(), spans: Vec::new(), index: HashMap::new(), ids: Vec::new(), root: this });
}
let Some(held) = open.get_mut(&root) else { return };
let parent = parent_span.and_then(|p| held.index.get(&p).copied());
let depth = parent.map(|i| held.spans[i].depth + 1).unwrap_or(0);
let metadata = attrs.metadata();
held.index.insert(this, held.spans.len());
held.ids.push(this);
held.spans.push(Span {
name: metadata.name(),
target: metadata.target().to_owned(),
level: *metadata.level(),
parent,
depth,
at: held.started.elapsed(),
duration: Duration::ZERO,
fields: fields.map,
});
drop(open);
self.traces.inner.belongs.lock().insert(this, root);
}
fn on_record(&self, id: &Id, values: &Record<'_>, _ctx: Context<'_, S>) {
let this = id.into_u64();
let Some(root) = self.traces.inner.belongs.lock().get(&this).copied() else { return };
let mut fields = Fields::default();
values.record(&mut fields);
let mut open = self.traces.inner.open.lock();
let Some(held) = open.get_mut(&root) else { return };
let Some(at) = held.index.get(&this).copied() else { return };
held.spans[at].fields.extend(fields.map);
}
fn on_close(&self, id: Id, _ctx: Context<'_, S>) {
let this = id.into_u64();
let Some(root) = self.traces.inner.belongs.lock().remove(&this) else { return };
let mut open = self.traces.inner.open.lock();
let Some(held) = open.get_mut(&root) else { return };
if let Some(at) = held.index.get(&this).copied() {
held.spans[at].duration = held.started.elapsed().saturating_sub(held.spans[at].at);
}
if held.root != this {
return;
}
let Some(held) = open.remove(&root) else { return };
drop(open);
{
let mut belongs = self.traces.inner.belongs.lock();
for id in &held.ids {
belongs.remove(id);
}
}
let trace = Trace { id: held.id, duration: held.started.elapsed(), spans: held.spans };
for listener in self.traces.inner.listeners.lock().iter() {
listener(&trace);
}
if self.traces.inner.ring == 0 {
return;
}
let mut finished = self.traces.inner.finished.lock();
finished.push(trace);
let ring = self.traces.inner.ring;
if finished.len() > ring {
let over = finished.len() - ring;
finished.drain(..over);
}
}
}
#[derive(Default)]
struct Fields {
map: HashMap<String, Value>,
}
impl Visit for Fields {
fn record_str(&mut self, field: &Field, value: &str) {
self.map.insert(field.name().to_owned(), Value::Str(value.to_owned()));
}
fn record_i64(&mut self, field: &Field, value: i64) {
self.map.insert(field.name().to_owned(), Value::Int(value));
}
fn record_u64(&mut self, field: &Field, value: u64) {
self.map.insert(field.name().to_owned(), Value::UInt(value));
}
fn record_bool(&mut self, field: &Field, value: bool) {
self.map.insert(field.name().to_owned(), Value::Bool(value));
}
fn record_f64(&mut self, field: &Field, value: f64) {
self.map.insert(field.name().to_owned(), Value::Float(value));
}
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
self.map.insert(field.name().to_owned(), Value::Str(format!("{value:?}")));
}
}