workflow_panic_hook/
lib.rs

1//! # `console_error_panic_hook`
2//!
3//! [<img alt="github" src="https://img.shields.io/badge/github-workflow--rs-8da0cb?style=for-the-badge&labelColor=555555&color=8da0cb&logo=github" height="20">](https://github.com/workflow-rs/workflow-rs)
4//! [<img alt="crates.io" src="https://img.shields.io/crates/v/workflow-panic-hook.svg?maxAge=2592000&style=for-the-badge&color=fc8d62&logo=rust" height="20">](https://crates.io/crates/workflow-panic-hook)
5//! [<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-workflow--panic--hook-56c2a5?maxAge=2592000&style=for-the-badge&logo=docs.rs" height="20">](https://docs.rs/workflow-panic-hook)
6//! <img alt="license" src="https://img.shields.io/crates/l/workflow-panic-hook.svg?maxAge=2592000&color=6ac&style=for-the-badge&logoColor=fff" height="20">
7//! <img src="https://img.shields.io/badge/platform- wasm32/browser -informational?style=for-the-badge&color=50a0f0" height="20">
8//! <img src="https://img.shields.io/badge/platform- wasm32/node.js -informational?style=for-the-badge&color=50a0f0" height="20">
9//!
10//! This crate is based on [console_error_panic_hook](https://crates.io/crates/console_error_panic_hook) but
11//! provides two configuration modes - console output and full-page output, where the panic will create
12//! a full-screen DIV element dumping the stack info in it.  This is useful when debugging on devices
13//! without access to console output.
14//!
15//! ## Error.stackTraceLimit
16//!
17//! 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).
18//!
19
20#[macro_use]
21extern crate cfg_if;
22
23use std::panic;
24
25cfg_if! {
26    if #[cfg(target_arch = "wasm32")] {
27        extern crate wasm_bindgen;
28        use wasm_bindgen::prelude::*;
29        mod logger;
30
31        #[wasm_bindgen]
32        extern {
33            #[wasm_bindgen(js_namespace = console, js_name="error")]
34            fn console_error(msg: String);
35
36            type Error;
37
38            #[wasm_bindgen(constructor)]
39            fn new() -> Error;
40
41            #[wasm_bindgen(structural, method, getter)]
42            fn stack(error: &Error) -> String;
43        }
44
45        fn process(info: &panic::PanicInfo) -> String{
46            let mut msg = info.to_string();
47
48            // Add the error stack to our message.
49            //
50            // This ensures that even if the `console` implementation doesn't
51            // include stacks for `console.error`, the stack is still available
52            // for the user. Additionally, Firefox's console tries to clean up
53            // stack traces, and ruins Rust symbols in the process
54            // (https://bugzilla.mozilla.org/show_bug.cgi?id=1519569) but since
55            // it only touches the logged message's associated stack, and not
56            // the message's contents, by including the stack in the message
57            // contents we make sure it is available to the user.
58            msg.push_str("\n\nStack:\n\n");
59            let e = Error::new();
60            let stack = e.stack();
61            msg.push_str(&stack);
62
63            // Safari's devtools, on the other hand, _do_ mess with logged
64            // messages' contents, so we attempt to break their heuristics for
65            // doing that by appending some whitespace.
66            // https://github.com/rustwasm/console_error_panic_hook/issues/7
67            msg.push_str("\n\n");
68
69            msg
70        }
71
72
73        fn console_hook(info: &panic::PanicInfo){
74            // Finally, log the panic with `console.error`!
75            console_error(process(info));
76        }
77        fn popup_hook(info: &panic::PanicInfo){
78            // Finally, log the panic with `logger::error`!
79            logger::error(process(info));
80        }
81
82        fn init(logger_type:Type){
83            match logger_type {
84                Type::Console=>{
85                    panic::set_hook(Box::new(console_hook));
86                }
87
88                Type::Popup=>{
89                    logger::init_logger();
90                    panic::set_hook(Box::new(popup_hook));
91                }
92                Type::Native=>{
93                    panic!("Native logger not supported under wasm");
94                }
95            }
96
97        }
98        pub use logger::show_logs;
99    } else {
100        use std::io::{self, Write};
101
102        fn hook(info: &panic::PanicInfo) {
103            let _ = writeln!(io::stderr(), "{info}");
104        }
105
106        fn init(_logger_type:Type){
107            panic::set_hook(Box::new(hook));
108        }
109
110        pub fn show_logs(){
111            panic!("Native (non-WASM) platform build doesn't support panic logs");
112        }
113    }
114}
115
116pub enum Type {
117    Console,
118    Popup,
119    Native,
120}
121/// Set the `console.error` panic hook the first time this is called. Subsequent
122/// invocations do nothing.
123#[inline]
124pub fn set_once(logger_type: Type) {
125    use std::sync::Once;
126    static SET_HOOK: Once = Once::new();
127    SET_HOOK.call_once(|| init(logger_type));
128}