1use std::{
9 panic::{self, PanicHookInfo},
10 sync::atomic::{AtomicBool, Ordering},
11 time::Duration,
12};
13
14use obs_proto::obs::v1::{ObsEnvelope, Severity as PSeverity, Tier as PTier};
15
16static INSTALLED: AtomicBool = AtomicBool::new(false);
17
18pub fn install_panic_hook() {
26 if INSTALLED.swap(true, Ordering::SeqCst) {
27 return;
28 }
29 let prev = panic::take_hook();
30 panic::set_hook(Box::new(move |info: &PanicHookInfo<'_>| {
31 emit_panicked(info);
32 let observer = crate::observer::observer();
34 observer.shutdown_blocking(Duration::from_secs(2));
35 prev(info);
36 }));
37}
38
39fn emit_panicked(info: &PanicHookInfo<'_>) {
40 let message = panic_message(info);
41 let location = info
42 .location()
43 .map(|l| format!("{}:{}:{}", l.file(), l.line(), l.column()))
44 .unwrap_or_default();
45
46 let mut env = ObsEnvelope {
47 full_name: "obs.runtime.v1.ObsPanicked".to_string(),
48 tier: ::buffa::EnumValue::Known(PTier::TIER_LOG),
49 sev: ::buffa::EnumValue::Known(PSeverity::SEVERITY_FATAL),
50 ts_ns: now_ns(),
51 sampling_reason: ::buffa::EnumValue::Known(
52 obs_proto::obs::v1::SamplingReason::SAMPLING_REASON_OVERRIDE,
53 ),
54 ..Default::default()
55 };
56 env.labels
57 .insert("message".to_string(), truncate(&message, 1024));
58 env.labels.insert("location".to_string(), location);
59 crate::scope::auto_fill_envelope(&mut env);
63
64 let observer = crate::observer::observer();
65 observer.emit_envelope(env);
66}
67
68fn panic_message(info: &PanicHookInfo<'_>) -> String {
69 if let Some(s) = info.payload().downcast_ref::<&'static str>() {
70 return (*s).to_string();
71 }
72 if let Some(s) = info.payload().downcast_ref::<String>() {
73 return s.clone();
74 }
75 "panic with non-string payload".to_string()
76}
77
78fn truncate(s: &str, max: usize) -> String {
79 if s.len() <= max {
80 s.to_string()
81 } else {
82 let mut t = s[..max].to_string();
83 t.push('…');
84 t
85 }
86}
87
88fn now_ns() -> u64 {
89 use std::time::{SystemTime, UNIX_EPOCH};
90 SystemTime::now()
91 .duration_since(UNIX_EPOCH)
92 .map(|d| d.as_nanos() as u64)
93 .unwrap_or(0)
94}