use std::{
panic::{self, PanicHookInfo},
sync::atomic::{AtomicBool, Ordering},
time::Duration,
};
use obs_proto::obs::v1::{ObsEnvelope, Severity as PSeverity, Tier as PTier};
static INSTALLED: AtomicBool = AtomicBool::new(false);
pub fn install_panic_hook() {
if INSTALLED.swap(true, Ordering::SeqCst) {
return;
}
let prev = panic::take_hook();
panic::set_hook(Box::new(move |info: &PanicHookInfo<'_>| {
emit_panicked(info);
let observer = crate::observer::observer();
observer.shutdown_blocking(Duration::from_secs(2));
prev(info);
}));
}
fn emit_panicked(info: &PanicHookInfo<'_>) {
let message = panic_message(info);
let location = info
.location()
.map(|l| format!("{}:{}:{}", l.file(), l.line(), l.column()))
.unwrap_or_default();
let mut env = ObsEnvelope {
full_name: "obs.runtime.v1.ObsPanicked".to_string(),
tier: ::buffa::EnumValue::Known(PTier::TIER_LOG),
sev: ::buffa::EnumValue::Known(PSeverity::SEVERITY_FATAL),
ts_ns: now_ns(),
sampling_reason: ::buffa::EnumValue::Known(
obs_proto::obs::v1::SamplingReason::SAMPLING_REASON_OVERRIDE,
),
..Default::default()
};
env.labels
.insert("message".to_string(), truncate(&message, 1024));
env.labels.insert("location".to_string(), location);
crate::scope::auto_fill_envelope(&mut env);
let observer = crate::observer::observer();
observer.emit_envelope(env);
}
fn panic_message(info: &PanicHookInfo<'_>) -> String {
if let Some(s) = info.payload().downcast_ref::<&'static str>() {
return (*s).to_string();
}
if let Some(s) = info.payload().downcast_ref::<String>() {
return s.clone();
}
"panic with non-string payload".to_string()
}
fn truncate(s: &str, max: usize) -> String {
if s.len() <= max {
s.to_string()
} else {
let mut t = s[..max].to_string();
t.push('…');
t
}
}
fn now_ns() -> u64 {
use std::time::{SystemTime, UNIX_EPOCH};
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0)
}