use std::{
collections::HashMap,
sync::{
Arc, Mutex,
atomic::{AtomicU64, Ordering},
},
};
use tokio::sync::mpsc;
use tracing::{
Metadata, Subscriber,
field::{Field, Visit},
span::{Attributes, Id, Record},
};
use tracing_core::subscriber::Interest;
use crate::{ArcEvent, Event};
#[derive(Debug, Clone)]
struct SpanInfo {
name: String,
target: String,
module_path: Option<String>,
file: Option<String>,
line: Option<u32>,
parent_id: Option<u64>,
fields: HashMap<String, String>,
metadata: &'static Metadata<'static>,
}
thread_local! {
static SPAN_STACK: std::cell::RefCell<Vec<u64>> = const { std::cell::RefCell::new(Vec::new()) };
}
pub struct TracingSubscriber {
sender: mpsc::UnboundedSender<ArcEvent>,
id_counter: Arc<AtomicU64>,
span_storage: Arc<Mutex<HashMap<u64, SpanInfo>>>,
}
impl TracingSubscriber {
pub fn new(sender: mpsc::UnboundedSender<ArcEvent>, id_counter: Arc<AtomicU64>) -> Self {
Self {
sender,
id_counter,
span_storage: Arc::new(Mutex::new(HashMap::new())),
}
}
fn current_span_id(&self) -> Option<u64> {
SPAN_STACK.with(|stack| stack.borrow().last().copied())
}
fn build_span_hierarchy(&self, span_id: u64) -> String {
let storage = self.span_storage.lock().unwrap();
let mut hierarchy = Vec::new();
let mut current_id = Some(span_id);
while let Some(id) = current_id {
if let Some(span_info) = storage.get(&id) {
hierarchy.push(span_info.name.clone());
current_id = span_info.parent_id;
} else {
break;
}
}
hierarchy.reverse();
hierarchy.join("::")
}
fn get_span_info(&self, span_id: u64) -> Option<SpanInfo> {
let storage = self.span_storage.lock().unwrap();
storage.get(&span_id).cloned()
}
fn extract_fields(&self, attributes: &Attributes<'_>) -> HashMap<String, String> {
let mut visitor = FieldVisitor::default();
attributes.record(&mut visitor);
visitor.fields
}
fn extract_record_fields(&self, record: &Record<'_>) -> HashMap<String, String> {
let mut visitor = FieldVisitor::default();
record.record(&mut visitor);
visitor.fields
}
}
#[derive(Default)]
struct FieldVisitor {
fields: HashMap<String, String>,
message: Option<String>,
}
impl Visit for FieldVisitor {
fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
if field.name() == "message" {
self.message = Some(format!("{value:?}"));
} else {
self.fields
.insert(field.name().to_string(), format!("{value:?}"));
}
}
fn record_str(&mut self, field: &Field, value: &str) {
if field.name() == "message" {
self.message = Some(value.to_string());
} else {
self.fields
.insert(field.name().to_string(), value.to_string());
}
}
fn record_i64(&mut self, field: &Field, value: i64) {
self.fields
.insert(field.name().to_string(), value.to_string());
}
fn record_u64(&mut self, field: &Field, value: u64) {
self.fields
.insert(field.name().to_string(), value.to_string());
}
fn record_bool(&mut self, field: &Field, value: bool) {
self.fields
.insert(field.name().to_string(), value.to_string());
}
fn record_f64(&mut self, field: &Field, value: f64) {
self.fields
.insert(field.name().to_string(), value.to_string());
}
}
impl Subscriber for TracingSubscriber {
fn enabled(&self, _metadata: &Metadata<'_>) -> bool {
true
}
fn new_span(&self, span: &Attributes<'_>) -> Id {
let span_id_u64 = self.id_counter.fetch_add(1, Ordering::SeqCst) + 1;
let span_id = Id::from_u64(span_id_u64);
let metadata = span.metadata();
let parent_id = self.current_span_id();
let fields = self.extract_fields(span);
let span_info = SpanInfo {
name: metadata.name().to_string(),
target: metadata.target().to_string(),
module_path: metadata.module_path().map(|s| s.to_string()),
file: metadata.file().map(|s| s.to_string()),
line: metadata.line(),
parent_id,
fields,
metadata,
};
{
let mut storage = self.span_storage.lock().unwrap();
storage.insert(span_id_u64, span_info);
}
span_id
}
fn record(&self, span: &Id, values: &Record<'_>) {
let span_id_u64 = span.into_u64();
let new_fields = self.extract_record_fields(values);
if let Ok(mut storage) = self.span_storage.lock() {
if let Some(span_info) = storage.get_mut(&span_id_u64) {
span_info.fields.extend(new_fields);
}
}
}
fn record_follows_from(&self, _span: &Id, _follows: &Id) {
}
fn event(&self, tracing_event: &tracing::Event<'_>) {
let event_id = self.id_counter.fetch_add(1, Ordering::SeqCst);
let mut event = Event::new(event_id, tracing_event);
if let Some(current_span_id) = self.current_span_id() {
if let Some(span_info) = self.get_span_info(current_span_id) {
event.span_name = Some(span_info.name.clone());
event.span_hierarchy = Some(self.build_span_hierarchy(current_span_id));
if event.module_path.is_none() && span_info.module_path.is_some() {
event.module_path = span_info.module_path;
}
if event.file.is_none() && span_info.file.is_some() {
event.file = span_info.file;
}
if event.line.is_none() && span_info.line.is_some() {
event.line = span_info.line;
}
let mut combined_fields = span_info.fields;
combined_fields.extend(event.fields.clone());
event.fields = combined_fields;
if event.target.is_empty() || event.target == "unknown" {
event.target = span_info.target;
}
}
}
let trace_event = Arc::new(event);
let _ = self.sender.send(trace_event);
}
fn enter(&self, span: &Id) {
let span_id_u64 = span.into_u64();
SPAN_STACK.with(|stack| {
stack.borrow_mut().push(span_id_u64);
});
}
fn exit(&self, span: &Id) {
let span_id_u64 = span.into_u64();
SPAN_STACK.with(|stack| {
let mut stack = stack.borrow_mut();
if let Some(pos) = stack.iter().rposition(|&id| id == span_id_u64) {
stack.remove(pos);
}
});
}
fn clone_span(&self, id: &Id) -> Id {
id.clone()
}
fn drop_span(&self, id: Id) {
let span_id_u64 = id.into_u64();
if let Ok(mut storage) = self.span_storage.lock() {
storage.remove(&span_id_u64);
}
}
fn try_close(&self, _id: Id) -> bool {
true
}
fn current_span(&self) -> tracing_core::span::Current {
if let Some(span_id) = self.current_span_id() {
if let Ok(storage) = self.span_storage.lock() {
if let Some(span_info) = storage.get(&span_id) {
return tracing_core::span::Current::new(
Id::from_u64(span_id),
span_info.metadata,
);
}
}
}
tracing_core::span::Current::none()
}
fn register_callsite(&self, _metadata: &'static Metadata<'static>) -> Interest {
Interest::always()
}
fn max_level_hint(&self) -> Option<tracing_core::LevelFilter> {
None
}
}