unwind_context/lib.rs
1#![no_std]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3#![cfg_attr(test, allow(clippy::unwrap_used))]
4
5//! The `unwind-context` crate makes debugging panics easier
6//! by adding a colored panic context with a simple macro.
7#![doc = ""]
8#![doc = include_str!("../examples/demo.html")]
9#![doc = ""]
10//! # Introduction
11//!
12//! In Rust, panics are typically used when an
13//! [unrecoverable](https://doc.rust-lang.org/book/ch09-01-unrecoverable-errors-with-panic.html)
14//! error occurs or when writing examples, prototype code, or tests.
15//!
16//! However, it can be difficult to pinpoint the exact cause of a panic,
17//! especially if it happens deep in the code or within a loop. While adding
18//! logs can help, this may lead to a large number of log entries, making it
19//! challenging to identify which ones are related to the panic.
20//!
21//! # About
22//!
23//! The goal of this crate is to make the panic context addition simple, and the
24//! context itself detailed enough, and easy to read. Accordingly, it also makes
25//! it easier to add context to assertions in your tests. This crate provides
26//! [`unwind_context`] and [`debug_unwind_context`] macros and some other
27//! auxiliary types, traits, functions, and macros that help you define function
28//! or scope context and write it to [`std::io::stderr`] or another
29//! writeable target if panic occurs. If panic occurs, the context will be
30//! written in "reverse" chronological order during the unwinding process.
31//!
32//! This library adds very little overhead to compiled functions unless they are
33//! panicked:
34//! - First, it constructs a structure containing the context data, code
35//! location, writer, and color scheme on the stack. It also stores a
36//! reference to the custom panic detector, if specified.
37//! - And when this "context scope guard" structure is dropped, its destructor
38//! checks for [`std::thread::panicking`] and calls the cold print function if
39//! panic has been detected.
40//!
41//! This crate is intended for diagnostic use. The exact contents and format of
42//! the messages printed on panic are not specified, other than being a clear
43//! and compact description.
44//!
45//! Note that the context will only be printed if the
46//! [`panic`](https://doc.rust-lang.org/cargo/reference/profiles.html#panic)
47//! setting is set to `unwind`, which is the default for both
48//! [`dev`](https://doc.rust-lang.org/cargo/reference/profiles.html#dev)
49//! and
50//! [`release`](https://doc.rust-lang.org/cargo/reference/profiles.html#release)
51//! profiles.
52//!
53//! # Usage
54//!
55//! First, add the following to your `Cargo.toml`:
56//! ```toml
57//! [dependencies]
58//! unwind-context = "0.2.2"
59//! ```
60//!
61//! Then, add the macro call with the given function arguments or scope
62//! arguments to the beginning of the functions to be tracked and bind the
63//! result to some scope variable (otherwise the unwind context scope guard will
64//! be immediately dropped):
65#![cfg_attr(feature = "std", doc = "```rust")]
66#![cfg_attr(not(feature = "std"), doc = "```rust,compile_fail")]
67//! use unwind_context::unwind_context;
68//!
69//! fn func1(a: u32, b: &str, c: bool) {
70//! let _ctx = unwind_context!(fn(a, b, c));
71//! // ...
72//! for i in 0..10 {
73//! let _ctx = unwind_context!(i);
74//! // ...
75//! }
76//! // ...
77//! }
78#![doc = "```"]
79#![doc = ""]
80//! With `unwind_context!(a, b, c)` syntax, it will print code location,
81//! given argument names (stringified expressions), and values on unwind,
82//! whereas with `unwind_context!(fn(a, b, c))` it will also print function
83//! names as well. Note that it uses the [`core::fmt::Debug`] representation. If
84//! you want to use the [`core::fmt::Display`] representation, you can use the
85//! [`WithDisplay`] wrapper.
86//!
87//! You can use the [`set_colors_enabled`] function to unconditionally enable
88//! the 16-ANSI-color colorization. If you want to enable colorization only if
89//! supported by the terminal, you can use the [`enable_colors_if_supported`]
90//! function, which will require enabling the
91//! [`detect-color-support`](#feature-flags) feature flag:
92//! ```toml
93//! [dependencies.unwind-context]
94//! version = "0.2.2"
95//! features = [ "detect-color-support" ]
96//! ```
97#![cfg_attr(feature = "detect-color-support", doc = "```rust")]
98#![cfg_attr(not(feature = "detect-color-support"), doc = "```rust,compile_fail")]
99//! # /*
100//! fn main() {
101//! # */
102//! unwind_context::enable_colors_if_supported();
103//! # test();
104//! // ...
105//! # /*
106//! }
107//!
108//! # */
109//! # /*
110//! #[test]
111//! # */
112//! fn test() {
113//! unwind_context::enable_colors_if_supported()
114//! // ...
115//! }
116#![doc = "```"]
117#![doc = ""]
118//! If you want to specify a custom color scheme, you can use the
119//! [`set_default_color_scheme`] function.
120//! Also, colorization can be customized separately for each context scope guard
121//! with the [`unwind_context_with_io`] and [`unwind_context_with_fmt`] macros.
122//!
123//! This crate depends on the standard library by default that is needed to
124//! write to [`std::io::stderr`] and to detect panicking using
125//! [`std::thread::panicking`]. To use this crate in a `#![no_std]` context with
126//! your custom [`core::fmt::Write`] writer and custom [`PanicDetector`], use
127//! `default-features = false` in your `Cargo.toml` as shown below:
128//! ```toml
129//! [dependencies.unwind-context]
130//! version = "0.2.2"
131//! default-features = false
132//! ```
133//!
134//! # Examples
135//!
136//! The following crate example:
137#![cfg_attr(feature = "detect-color-support", doc = "```rust,should_panic")]
138#![cfg_attr(not(feature = "detect-color-support"), doc = "```rust,compile_fail")]
139#![doc = include_str!("../examples/demo.rs")]
140#![doc = "```"]
141//! will output:
142#![doc = ""]
143#![doc = include_str!("../examples/demo.html")]
144#![doc = ""]
145//! # Macro expansion
146//!
147//! The following function:
148#![cfg_attr(feature = "std", doc = "```rust")]
149#![cfg_attr(not(feature = "std"), doc = "```rust,compile_fail")]
150//! use unwind_context::unwind_context;
151//!
152//! fn foo(a: &str, b: Vec<u8>, c: bool, d: String) {
153//! let _ctx = unwind_context!(fn(a, &b, ..., d.clone()));
154//! // ...
155//! for i in 0..10 {
156//! let _ctx = unwind_context!(i);
157//! // ...
158//! }
159//! }
160#![doc = "```"]
161//! will partially expand into:
162#![cfg_attr(feature = "std", doc = "```rust")]
163#![cfg_attr(not(feature = "std"), doc = "```rust,compile_fail")]
164//! fn foo(a: u32, b: Vec<u8>, c: bool, d: String) {
165//! let _ctx = unwind_context::UnwindContextWithIo::new(
166//! unwind_context::UnwindContextFunc::new(
167//! {
168//! struct Item;
169//! let module_path = ::core::module_path!();
170//! let item_type_name = ::core::any::type_name::<Item>();
171//! unwind_context::func_name_from_item_type_name(
172//! module_path, item_type_name
173//! )
174//! },
175//! (
176//! unwind_context::UnwindContextArg::new(Some("a"), a),
177//! (
178//! unwind_context::UnwindContextArg::new(Some("&b"), &b),
179//! (
180//! unwind_context::UnwindContextArg::new(
181//! None,
182//! unwind_context::NonExhaustiveMarker,
183//! ),
184//! (
185//! unwind_context::UnwindContextArg::new(
186//! Some("d.clone()"), d.clone()
187//! ),
188//! (),
189//! ),
190//! ),
191//! ),
192//! ),
193//! ),
194//! ::std::io::stderr(),
195//! unwind_context::StdPanicDetector,
196//! unwind_context::get_default_color_scheme_if_enabled(),
197//! );
198//! // ...
199//! for i in 0..10 {
200//! let _ctx = unwind_context::UnwindContextWithIo::new(
201//! unwind_context::UnwindContextArgs::new((
202//! unwind_context::UnwindContextArg::new(Some("i"), i),
203//! (),
204//! )),
205//! ::std::io::stderr(),
206//! unwind_context::StdPanicDetector,
207//! unwind_context::get_default_color_scheme_if_enabled(),
208//! );
209//! // ...
210//! }
211//! }
212#![doc = "```"]
213#![doc = ""]
214//! # Feature Flags
215//!
216//! - `std` (enabled by default): Enables [`UnwindContextWithIo`] structure,
217//! [`unwind_context`], [`debug_unwind_context`], [`unwind_context_with_io`],
218//! and [`debug_unwind_context_with_io`] macros.
219//! - `detect-color-support`: Enables [`enable_colors_if_supported`] function
220//! and [`supports-color`] optional dependency.
221//! - `custom-default-colors`: Enables [`set_default_color_scheme`] function and
222//! [`atomic_ref`] optional dependency.
223//!
224//! # Similar crates
225//!
226//! - [`scopeguard`] allows you to run any code at the end of a scope. It has
227//! both success and unwind guard variants, and it doesn't require panic hook
228//! modification.
229//! - [`panic-context`] allows you to specify and modify panic context using a
230//! custom panic hook. It provides more fine-grained control over the output.
231//! However, it implicitly modifies the panic hook using a mutex for a
232//! one-time thread local initialization and doesn’t add any automatic context
233//! or colorization.
234//! - [`econtext`] allows you to specify panic context and automatically adds
235//! some context including function name and location. However, it requires
236//! panic hook modification via the init function and uses dynamic dispatch
237//! and some unsafe code.
238//!
239//! # License
240//!
241//! Licensed under either of
242//!
243//! - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or <https://www.apache.org/licenses/LICENSE-2.0>)
244//! - MIT license ([LICENSE-MIT](LICENSE-MIT) or <https://opensource.org/licenses/MIT>)
245//!
246//! at your option.
247//!
248//! # Contribution
249//!
250//! Unless you explicitly state otherwise, any contribution intentionally
251//! submitted for inclusion in the work by you, as defined in the Apache-2.0
252//! license, shall be dual licensed as above, without any
253//! additional terms or conditions.
254//!
255//! [`supports-color`]: https://crates.io/crates/supports-color
256//! [`atomic_ref`]: https://crates.io/crates/atomic_ref
257//! [`scopeguard`]: https://crates.io/crates/scopeguard
258//! [`panic-context`]: https://crates.io/crates/panic-context
259//! [`econtext`]: https://crates.io/crates/econtext
260
261#[cfg(feature = "std")]
262extern crate std;
263
264#[cfg(test)]
265use version_sync as _; // Used in integration tests.
266
267mod arg;
268mod args;
269mod color_scheme;
270mod colored;
271#[cfg(feature = "std")]
272#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
273mod context;
274mod context_data;
275mod context_with_fmt;
276#[cfg(feature = "std")]
277#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
278mod context_with_io;
279mod debug_with;
280mod func;
281mod func_name;
282mod non_exhaustive;
283mod panic_detector;
284mod set_colors;
285#[cfg(test)]
286mod test_common;
287#[cfg(test)]
288mod test_util;
289mod util_macros;
290
291pub use arg::*;
292pub use args::*;
293pub use color_scheme::*;
294pub use colored::*;
295pub use context_with_fmt::*;
296#[cfg(feature = "std")]
297#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
298pub use context_with_io::*;
299pub use debug_with::*;
300pub use func::*;
301pub use func_name::*;
302pub use non_exhaustive::*;
303pub use panic_detector::*;
304pub use set_colors::*;