panic_context/lib.rs
1//! This library allows to print manually-maintained messages on panic.
2//!
3//! When your program panics, it prints backtrace. However, if panic
4//! occurs inside loop, it is not clear which iteration was the cause.
5//! It is possible to use log, but printing slows down execution considerably
6//! and you get lots of entries, while only last ones are required.
7//!
8//! Panic context lets you set value which is remembered, but not printed anywhere
9//! until panic occurs. It is also automatically forgotten at the end of scope.
10//!
11//! # Example
12//!
13//! ```should_panic
14//! #[macro_use] extern crate panic_context;
15//!
16//! use panic_context::panic_context;
17//!
18//! static ITEMS: &[&str] = &["foo", "bar", "yo", "nope"];
19//!
20//! fn get_len(item: &str) -> usize { item.len() }
21//! fn calc_sig(item: &str) -> &str { &item[3..] }
22//!
23//! fn main() {
24//! let step = panic_context("step: ");
25//!
26//! step.update("calculate lengths");
27//! for item in ITEMS {
28//! panic_context!("item: {}", item);
29//! get_len(item);
30//! }
31//!
32//! step.update("calculate signatures");
33//! for item in ITEMS {
34//! panic_context!("item: {}", item);
35//! calc_sig(item);
36//! }
37//!
38//! panic!("boom!");
39//! }
40//! ```
41//!
42//! When this code panics inside `calc_sig`, you will see:
43//!
44//! ```text
45//! Panic context:
46//! step: calculate signatures
47//! item: yo
48//! thread 'main' panicked at '...', src/libcore/str/mod.rs:2162
49//! note: Run with `RUST_BACKTRACE=1` for a backtrace.
50//! ```
51
52#![doc(html_root_url="https://docs.rs/panic-context/0.1.0")]
53
54#[macro_use] extern crate lazy_static;
55
56use std::panic;
57use std::collections::BTreeMap;
58use std::cell::RefCell;
59use std::sync::Mutex;
60
61use std::io::Write;
62
63lazy_static! {
64 static ref INITIALIZED: Mutex<bool> = Mutex::new(false);
65}
66
67struct Values {
68 next_id: usize,
69 values: BTreeMap<usize, String>,
70}
71thread_local! {
72 static VALUES: RefCell<Values> = RefCell::new(Values {
73 next_id: 0,
74 values: BTreeMap::new(),
75 });
76}
77
78/// Initializes the panic hook.
79///
80/// After this method is called, all panics will be logged rather than printed
81/// to standard error.
82fn init() {
83 let previous_hook = panic::take_hook();
84 panic::set_hook(Box::new(move |info| {
85 VALUES.with(|traces| {
86 let traces = traces.borrow();
87 let stderr = std::io::stderr();
88 let mut handle = stderr.lock();
89 let _ = handle.write(b"Panic context:\n");
90 for (_, value) in traces.values.iter() {
91 let _ = handle.write(value.as_bytes()).unwrap();
92 let _ = handle.write(b"\n").unwrap();
93 }
94 });
95 previous_hook(info);
96 }));
97}
98
99fn ensure_initialized() {
100 let mut initialized = INITIALIZED.lock().unwrap();
101 if !*initialized {
102 init();
103 *initialized = true;
104 }
105}
106
107fn add_entry(value: Option<String>) -> usize {
108 VALUES.with(move |values| {
109 let mut values = values.borrow_mut();
110 let id = values.next_id;
111 values.next_id += 1;
112 if let Some(v) = value {
113 values.values.insert(id, v);
114 }
115 id
116 })
117}
118
119fn update_entry(id: usize, value: String) {
120 VALUES.with(|values| {
121 let mut values = values.borrow_mut();
122 values.values.insert(id, value);
123 })
124}
125
126#[must_use]
127pub struct UpdatablePanicContext {
128 id: usize,
129 prefix: &'static str,
130}
131impl UpdatablePanicContext {
132 pub fn new(prefix: &'static str) -> Self {
133 ensure_initialized();
134 let id = add_entry(None);
135 UpdatablePanicContext { id, prefix }
136 }
137
138 pub fn update<T: Into<String>>(&self, value: T) {
139 let mut buf = self.prefix.to_string();
140 buf += &value.into();
141 update_entry(self.id, buf);
142 }
143}
144
145#[must_use]
146pub struct PanicContext {
147 id: usize,
148}
149impl PanicContext {
150 pub fn new<T: Into<String>>(msg: T) -> Self {
151 ensure_initialized();
152
153 let id = VALUES.with(|values| {
154 let mut values = values.borrow_mut();
155 let id = values.next_id;
156 values.next_id += 1;
157 values.values.insert(id, msg.into());
158 id
159 });
160 PanicContext { id }
161 }
162}
163
164impl Drop for PanicContext {
165 fn drop(&mut self) {
166 VALUES.with(|values| {
167 let mut values = values.borrow_mut();
168 values.values.remove(&self.id)
169 });
170 }
171}
172
173/// Creates panic context whose message may be updated.
174///
175/// `prefix` string will be prepended to each value provided to
176/// `update` method.
177///
178/// # Usage
179///
180/// Bind panic context and use `update` method to update message.
181/// Message is automatically removed when panic context goes out
182/// of scope.
183///
184/// # Example
185///
186/// ```no_run
187/// # use panic_context::panic_context;
188/// # fn main() {
189/// let step = panic_context("step: ");
190/// step.update("calculate lengths");
191/// // ...
192/// step.update("calculate signatures");
193/// // ...
194/// panic!("boom!");
195/// # }
196/// ```
197///
198/// Result:
199///
200/// ```text
201/// Panic context:
202/// step: calculate signatures
203/// thread 'main' panicked at '...', src/libcore/str/mod.rs:2162
204/// note: Run with `RUST_BACKTRACE=1` for a backtrace.
205/// ```
206
207pub fn panic_context(prefix: &'static str) -> UpdatablePanicContext {
208 UpdatablePanicContext::new(prefix)
209}
210
211/// Remembers message to show it on panic inside current scope.
212///
213/// All arguments are passed to `format!` in order to compose message.
214/// Context is forgotten at the end of current scope.
215///
216/// # Uses
217///
218/// Message is always remembered in both debug and release builds.
219/// See [`debug_panic_context!`] for context which is not enabled in
220/// release builds by default.
221///
222/// [`debug_panic_context!`]: macro.debug_panic_context.html
223///
224/// # Examples
225///
226/// ```no_run
227/// #[macro_use] extern crate panic_context;
228/// # fn main() {
229/// # let items: &[&str] = &[];
230/// # fn process_item(_: &str) {}
231/// for item in items {
232/// panic_context!("item: {}", item);
233/// process_item(item);
234/// }
235/// # }
236/// ```
237///
238/// If panic occurs during item processing, then panic context will
239/// be printed:
240///
241/// ```text
242/// Panic context:
243/// item: cucumber
244/// thread 'main' panicked at '...', src/libcore/str/mod.rs:2162
245/// note: Run with `RUST_BACKTRACE=1` for a backtrace.
246/// ```
247#[macro_export]
248macro_rules! panic_context {
249 ($($arg:tt)+) => (
250 let _panic_context = $crate::PanicContext::new(format!($($arg)+));
251 )
252}
253
254/// Remembers message to show it on panic inside current scope in debug build only.
255///
256/// Message is forgotten at the end of current scope.
257///
258/// Unlike [`panic_context!`], `debug_panic_context!` is only enabled in
259/// non-optimized builds by default. An optimized build will omit
260/// all `debug_panic_context!` statements unless '-C debug-assertions'
261/// is passed to the compiler or 'keep-debug-context' crate feature is
262/// requested.
263///
264/// [`panic_context!`]: macro.panic_context.html
265#[macro_export]
266macro_rules! debug_panic_context {
267 ($($arg:tt)+) => (
268 #[cfg(or(debug-assertions, keep-debug-context))]
269 let _panic_context = $crate::PanicContext::new_with_msg(format!($($arg)+));
270 )
271}