console_error_panic_hook/lib.rs
1//! # `console_error_panic_hook`
2//!
3//! [](https://docs.rs/console_error_panic_hook/)
4//! [](https://crates.io/crates/console_error_panic_hook)
5//! [](https://crates.io/crates/console_error_panic_hook)
6//! [](https://travis-ci.org/rustwasm/console_error_panic_hook)
7//!
8//! This crate lets you debug panics on `wasm32-unknown-unknown` by providing a
9//! panic hook that forwards panic messages to
10//! [`console.error`](https://developer.mozilla.org/en-US/docs/Web/API/Console/error).
11//!
12//! When an error is reported with `console.error`, browser devtools and node.js
13//! will typically capture a stack trace and display it with the logged error
14//! message.
15//!
16//! Without `console_error_panic_hook` you just get something like *RuntimeError: Unreachable executed*
17//!
18//! Browser:
19//! 
20//!
21//! Node:
22//! 
23//!
24//! With this panic hook installed you will see the panic message
25//!
26//! Browser:
27//! 
28//!
29//! Node:
30//! 
31//!
32//! ## Usage
33//!
34//! There are two ways to install this panic hook.
35//!
36//! First, you can set the hook yourself by calling `std::panic::set_hook` in
37//! some initialization function:
38//!
39//! ```
40//! extern crate console_error_panic_hook;
41//! use std::panic;
42//!
43//! fn my_init_function() {
44//! panic::set_hook(Box::new(console_error_panic_hook::hook));
45//!
46//! // ...
47//! }
48//! ```
49//!
50//! Alternatively, use `set_once` on some common code path to ensure that
51//! `set_hook` is called, but only the one time. Under the hood, this uses
52//! `std::sync::Once`.
53//!
54//! ```
55//! extern crate console_error_panic_hook;
56//!
57//! struct MyBigThing;
58//!
59//! impl MyBigThing {
60//! pub fn new() -> MyBigThing {
61//! console_error_panic_hook::set_once();
62//!
63//! MyBigThing
64//! }
65//! }
66//! ```
67//!
68//! ## Error.stackTraceLimit
69//!
70//! Many browsers only capture the top 10 frames of a stack trace. In rust programs this is less likely to be enough. To see more frames, you can set the non-standard value `Error.stackTraceLimit`. For more information see the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Microsoft_Extensions/Error.stackTraceLimit) or [v8 docs](https://v8.dev/docs/stack-trace-api).
71//!
72
73#[macro_use]
74extern crate cfg_if;
75
76use std::panic;
77
78cfg_if! {
79 if #[cfg(target_arch = "wasm32")] {
80 extern crate wasm_bindgen;
81 use wasm_bindgen::prelude::*;
82
83 #[wasm_bindgen]
84 extern {
85 #[wasm_bindgen(js_namespace = console)]
86 fn error(msg: String);
87
88 type Error;
89
90 #[wasm_bindgen(constructor)]
91 fn new() -> Error;
92
93 #[wasm_bindgen(structural, method, getter)]
94 fn stack(error: &Error) -> String;
95 }
96
97 fn hook_impl(info: &panic::PanicInfo) {
98 let mut msg = info.to_string();
99
100 // Add the error stack to our message.
101 //
102 // This ensures that even if the `console` implementation doesn't
103 // include stacks for `console.error`, the stack is still available
104 // for the user. Additionally, Firefox's console tries to clean up
105 // stack traces, and ruins Rust symbols in the process
106 // (https://bugzilla.mozilla.org/show_bug.cgi?id=1519569) but since
107 // it only touches the logged message's associated stack, and not
108 // the message's contents, by including the stack in the message
109 // contents we make sure it is available to the user.
110 msg.push_str("\n\nStack:\n\n");
111 let e = Error::new();
112 let stack = e.stack();
113 msg.push_str(&stack);
114
115 // Safari's devtools, on the other hand, _do_ mess with logged
116 // messages' contents, so we attempt to break their heuristics for
117 // doing that by appending some whitespace.
118 // https://github.com/rustwasm/console_error_panic_hook/issues/7
119 msg.push_str("\n\n");
120
121 // Finally, log the panic with `console.error`!
122 error(msg);
123 }
124 } else {
125 use std::io::{self, Write};
126
127 fn hook_impl(info: &panic::PanicInfo) {
128 let _ = writeln!(io::stderr(), "{}", info);
129 }
130 }
131}
132
133/// A panic hook for use with
134/// [`std::panic::set_hook`](https://doc.rust-lang.org/nightly/std/panic/fn.set_hook.html)
135/// that logs panics into
136/// [`console.error`](https://developer.mozilla.org/en-US/docs/Web/API/Console/error).
137///
138/// On non-wasm targets, prints the panic to `stderr`.
139pub fn hook(info: &panic::PanicInfo) {
140 hook_impl(info);
141}
142
143/// Set the `console.error` panic hook the first time this is called. Subsequent
144/// invocations do nothing.
145#[inline]
146pub fn set_once() {
147 use std::sync::Once;
148 static SET_HOOK: Once = Once::new();
149 SET_HOOK.call_once(|| {
150 panic::set_hook(Box::new(hook));
151 });
152}