use serde_json::Value;
const ROTATE_AT_BYTES: u64 = 8 * 1024 * 1024;
pub fn is_enabled() -> bool {
if std::env::var("WIRE_DIAG").map(|v| !v.is_empty()).unwrap_or(false) {
return true;
}
if let Ok(state) = crate::config::state_dir()
&& state.join("diag.enabled").exists()
{
return true;
}
false
}
pub fn emit(event_type: &str, payload: Value) {
if !is_enabled() {
return;
}
let state = match crate::config::state_dir() {
Ok(s) => s,
Err(_) => return,
};
if std::fs::create_dir_all(&state).is_err() {
return;
}
let path = state.join("diag.jsonl");
if let Ok(meta) = std::fs::metadata(&path)
&& meta.len() >= ROTATE_AT_BYTES
{
let _ = std::fs::rename(&path, state.join("diag.jsonl.1"));
}
let line = serde_json::json!({
"ts": std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0),
"pid": std::process::id(),
"version": env!("CARGO_PKG_VERSION"),
"type": event_type,
"payload": payload,
});
let bytes = match serde_json::to_vec(&line) {
Ok(mut b) => {
b.push(b'\n');
b
}
Err(_) => return,
};
use std::io::Write;
if let Ok(mut f) = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&path)
{
let _ = f.write_all(&bytes);
}
}
pub fn tail(n: usize) -> Vec<Value> {
let path = match crate::config::state_dir() {
Ok(s) => s.join("diag.jsonl"),
Err(_) => return Vec::new(),
};
let body = match std::fs::read_to_string(&path) {
Ok(b) => b,
Err(_) => return Vec::new(),
};
let mut out: Vec<Value> = body
.lines()
.filter(|l| !l.trim().is_empty())
.filter_map(|l| serde_json::from_str(l).ok())
.collect();
let start = out.len().saturating_sub(n);
out.drain(..start);
out
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn diag_is_noop_when_disabled() {
crate::config::test_support::with_temp_home(|| {
assert!(!is_enabled());
emit("pull", json!({"events": 3}));
let state = crate::config::state_dir().unwrap();
let diag = state.join("diag.jsonl");
assert!(!diag.exists(), "diag must not write when disabled");
});
}
#[test]
fn diag_emits_when_env_var_set() {
crate::config::test_support::with_temp_home(|| {
crate::config::ensure_dirs().unwrap();
unsafe { std::env::set_var("WIRE_DIAG", "1") };
emit("pull", json!({"events": 2, "rejected": 0}));
unsafe { std::env::remove_var("WIRE_DIAG") };
let lines = tail(10);
assert_eq!(lines.len(), 1);
assert_eq!(lines[0]["type"], "pull");
assert_eq!(lines[0]["payload"]["events"], 2);
assert!(lines[0]["ts"].as_u64().is_some());
assert!(lines[0]["pid"].as_u64().is_some());
});
}
#[test]
fn diag_emits_when_file_knob_present() {
crate::config::test_support::with_temp_home(|| {
crate::config::ensure_dirs().unwrap();
let state = crate::config::state_dir().unwrap();
std::fs::write(state.join("diag.enabled"), "1").unwrap();
assert!(is_enabled());
emit("push", json!({"peer": "willard"}));
let lines = tail(10);
assert_eq!(lines.len(), 1);
assert_eq!(lines[0]["type"], "push");
});
}
#[test]
fn diag_tail_returns_last_n_entries_in_order() {
crate::config::test_support::with_temp_home(|| {
crate::config::ensure_dirs().unwrap();
unsafe { std::env::set_var("WIRE_DIAG", "1") };
for i in 0..5u32 {
emit("test", json!({"i": i}));
}
unsafe { std::env::remove_var("WIRE_DIAG") };
let lines = tail(3);
assert_eq!(lines.len(), 3);
assert_eq!(lines[0]["payload"]["i"], 2);
assert_eq!(lines[1]["payload"]["i"], 3);
assert_eq!(lines[2]["payload"]["i"], 4);
});
}
}