use std::sync::{Arc, Mutex, OnceLock};
use std::time::{Instant, SystemTime, UNIX_EPOCH};
#[derive(Clone)]
pub enum Sink {
Stderr,
Memory(Arc<Mutex<Vec<String>>>),
}
static START: OnceLock<Instant> = OnceLock::new();
pub fn line(holder: Option<&str>, ev: &str, fields: serde_json::Value) -> String {
let ts_ms = SystemTime::now().duration_since(UNIX_EPOCH).map(|d| d.as_millis() as u64).unwrap_or(0);
let mono_ms = START.get_or_init(Instant::now).elapsed().as_millis() as u64;
let head = format!(
"{{\"ts_ms\":{ts_ms},\"mono_ms\":{mono_ms},\"holder\":{},\"ev\":{}",
serde_json::to_string(&holder).unwrap_or_else(|_| "null".into()),
serde_json::to_string(ev).unwrap_or_else(|_| "\"?\"".into()),
);
let rest = match fields {
serde_json::Value::Object(m) if !m.is_empty() => {
let body = serde_json::to_string(&serde_json::Value::Object(m)).unwrap_or_default();
format!(",{}", &body[1..body.len() - 1])
}
_ => String::new(),
};
format!("{head}{rest}}}")
}
impl super::Syncer {
pub fn trace(&self, ev: &str, fields: serde_json::Value) {
let Some(sink) = &self.cfg.event_trace else { return };
let holder = self
.lease
.as_ref()
.map(|l| l.holder_id.clone())
.or_else(|| super::lease::incarnation(self).ok().map(|i| i.holder_id));
let l = line(holder.as_deref(), ev, fields);
match sink {
Sink::Stderr => eprintln!("{l}"),
Sink::Memory(buf) => buf.lock().unwrap().push(l),
}
}
pub fn trace_requests(&self) -> serde_json::Value {
match self.store.request_counts() {
Some(c) => serde_json::to_value(c).unwrap_or(serde_json::Value::Null),
None => serde_json::Value::Null,
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn a_line_starts_with_the_pinned_prefix_and_parses() {
let l = super::line(Some("h1"), "gc", serde_json::json!({"path": "a/b", "result": "deleted", "z": 1}));
assert!(l.starts_with("{\"ts_ms\":"), "{l}");
let v: serde_json::Value = serde_json::from_str(&l).expect("a trace line is JSON");
assert_eq!(v["holder"], "h1");
assert_eq!(v["ev"], "gc");
assert_eq!(v["path"], "a/b");
assert!(v["mono_ms"].is_u64());
let empty = super::line(None, "barrier_start", serde_json::json!({}));
let v: serde_json::Value = serde_json::from_str(&empty).expect("no fields still parses");
assert!(v["holder"].is_null());
}
}