use std::{
num::NonZeroU32,
sync::{
Arc,
atomic::{AtomicU64, Ordering},
},
};
use dashmap::DashMap;
use obs_proto::obs::v1::Severity;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
#[non_exhaustive]
pub enum CallsiteSource {
TracingEvent = 1,
TracingSpan = 2,
Forensic = 3,
Instrument = 4,
}
impl CallsiteSource {
#[must_use]
pub const fn as_str(&self) -> &'static str {
match self {
Self::TracingEvent => "TRACING_EVENT",
Self::TracingSpan => "TRACING_SPAN",
Self::Forensic => "FORENSIC",
Self::Instrument => "INSTRUMENT",
}
}
}
#[derive(Debug)]
pub struct CallsiteRecord {
pub id: u64,
pub source: CallsiteSource,
pub target: String,
pub name: String,
pub module_path: String,
pub file: String,
pub line: Option<NonZeroU32>,
pub sev: Severity,
pub field_names: Vec<String>,
pub template: String,
pub registered_ns: u64,
pub events_since_refresh: AtomicU64,
}
impl CallsiteRecord {
pub fn reset_count(&self) {
self.events_since_refresh.store(0, Ordering::Relaxed);
}
pub fn observe(&self) -> u64 {
self.events_since_refresh.fetch_add(1, Ordering::Relaxed) + 1
}
}
#[derive(Default)]
pub struct ObsCallsiteRegistry {
by_id: DashMap<u64, Arc<CallsiteRecord>>,
}
impl std::fmt::Debug for ObsCallsiteRegistry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ObsCallsiteRegistry")
.field("len", &self.by_id.len())
.finish_non_exhaustive()
}
}
impl ObsCallsiteRegistry {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn len(&self) -> usize {
self.by_id.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.by_id.is_empty()
}
pub fn insert_if_absent(&self, record: CallsiteRecord) -> (Arc<CallsiteRecord>, bool) {
let id = record.id;
if let Some(existing) = self.by_id.get(&id) {
return (Arc::clone(existing.value()), false);
}
let arc = Arc::new(record);
match self.by_id.entry(id) {
dashmap::Entry::Occupied(slot) => (Arc::clone(slot.get()), false),
dashmap::Entry::Vacant(slot) => {
slot.insert(Arc::clone(&arc));
(arc, true)
}
}
}
#[must_use]
pub fn get(&self, id: u64) -> Option<Arc<CallsiteRecord>> {
self.by_id.get(&id).map(|r| Arc::clone(r.value()))
}
#[must_use]
pub fn snapshot(&self) -> Vec<Arc<CallsiteRecord>> {
self.by_id.iter().map(|r| Arc::clone(r.value())).collect()
}
}
#[must_use]
pub fn callsite_id(
source: CallsiteSource,
target: &str,
file: &str,
line: Option<u32>,
level: Severity,
field_names: &[&str],
template: &str,
) -> u64 {
let mut h = blake3::Hasher::new();
h.update(&[source as u8]);
h.update(target.as_bytes());
h.update(file.as_bytes());
h.update(&line.unwrap_or(0).to_le_bytes());
h.update(&[severity_byte(level)]);
for name in field_names {
h.update(name.as_bytes());
h.update(b"\x00");
}
h.update(template.as_bytes());
let bytes = h.finalize();
let raw = bytes.as_bytes();
let head: [u8; 8] = raw.first_chunk::<8>().copied().unwrap_or([0; 8]);
let id = u64::from_le_bytes(head);
if id != 0 { id } else { perturb_to_nonzero(raw) }
}
const fn severity_byte(s: Severity) -> u8 {
match s {
Severity::Trace => 1,
Severity::Debug => 2,
Severity::Info => 3,
Severity::Warn => 4,
Severity::Error => 5,
Severity::Fatal => 6,
_ => 0,
}
}
#[must_use]
pub fn perturb_to_nonzero(blake_bytes: &[u8]) -> u64 {
let head2: [u8; 8] = blake_bytes
.get(8..16)
.and_then(|s| <[u8; 8]>::try_from(s).ok())
.unwrap_or([0; 8]);
u64::from_le_bytes(head2) | 1
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_callsite_id_should_be_deterministic() {
let a = callsite_id(
CallsiteSource::TracingEvent,
"sqlx::query",
"src/q.rs",
Some(42),
Severity::Info,
&["rows", "elapsed"],
"executed query",
);
let b = callsite_id(
CallsiteSource::TracingEvent,
"sqlx::query",
"src/q.rs",
Some(42),
Severity::Info,
&["rows", "elapsed"],
"executed query",
);
assert_eq!(a, b);
}
#[test]
fn test_callsite_id_should_never_be_zero_for_real_input() {
let id = callsite_id(
CallsiteSource::Forensic,
"site",
"",
None,
Severity::Info,
&[],
"",
);
assert_ne!(id, 0);
}
#[test]
fn test_registry_should_dedup_inserts() {
let reg = ObsCallsiteRegistry::new();
let rec = CallsiteRecord {
id: 1,
source: CallsiteSource::Forensic,
target: "t".into(),
name: "n".into(),
module_path: String::new(),
file: String::new(),
line: None,
sev: Severity::Info,
field_names: Vec::new(),
template: String::new(),
registered_ns: 0,
events_since_refresh: AtomicU64::new(0),
};
let (_a, new1) = reg.insert_if_absent(rec);
assert!(new1);
let rec2 = CallsiteRecord {
id: 1,
source: CallsiteSource::Forensic,
target: "t".into(),
name: "n".into(),
module_path: String::new(),
file: String::new(),
line: None,
sev: Severity::Info,
field_names: Vec::new(),
template: String::new(),
registered_ns: 0,
events_since_refresh: AtomicU64::new(0),
};
let (_b, new2) = reg.insert_if_absent(rec2);
assert!(!new2);
assert_eq!(reg.len(), 1);
}
}