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}