1
+ // Copyright 2017-2019 Parity Technologies (UK) Ltd.
2
+ // This file is part of Substrate.
3
+
4
+ // Substrate is free software: you can redistribute it and/or modify
5
+ // it under the terms of the GNU General Public License as published by
6
+ // the Free Software Foundation, either version 3 of the License, or
7
+ // (at your option) any later version.
8
+
9
+ // Substrate is distributed in the hope that it will be useful,
10
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ // GNU General Public License for more details.
13
+
14
+ // You should have received a copy of the GNU General Public License
15
+ // along with Substrate. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ //! Custom panic hook with bug report link
18
+ //!
19
+ //! This crate provides the [`set`] function, which wraps around [`std::panic::set_hook`] and
20
+ //! sets up a panic hook that prints a backtrace and invites the user to open an issue to the
21
+ //! given URL.
22
+ //!
23
+ //! By default, the panic handler aborts the process by calling [`std::process::exit`]. This can
24
+ //! temporarily be disabled by using an [`AbortGuard`].
25
+ use backtrace:: Backtrace ;
26
+ use std:: io:: { self , Write } ;
27
+ use std:: marker:: PhantomData ;
28
+ use std:: panic:: { self , PanicInfo } ;
29
+ use std:: cell:: Cell ;
30
+ use std:: thread;
31
+ use std:: env;
32
+
33
+ thread_local ! {
34
+ static ABORT : Cell <bool > = Cell :: new( true ) ;
35
+ }
36
+
37
+ /// Set the panic hook.
38
+ ///
39
+ /// Calls [`std::panic::set_hook`] to set up the panic hook.
40
+ ///
41
+ /// The `bug_url` parameter is an invitation for users to visit that URL to submit a bug report
42
+ /// in the case where a panic happens.
43
+ pub fn set ( bug_url : & ' static str ) {
44
+ panic:: set_hook ( Box :: new ( move |c| panic_hook ( c, bug_url) ) ) ;
45
+ }
46
+
47
+ macro_rules! ABOUT_PANIC {
48
+ ( ) => ( "
49
+ This is a bug. Please report it at:
50
+
51
+ {}
52
+ " ) }
53
+
54
+ /// Set aborting flag. Returns previous value of the flag.
55
+ fn set_abort ( enabled : bool ) -> bool {
56
+ ABORT . with ( |flag| {
57
+ let prev = flag. get ( ) ;
58
+ flag. set ( enabled) ;
59
+ prev
60
+ } )
61
+ }
62
+
63
+ /// RAII guard for whether panics in the current thread should unwind or abort.
64
+ ///
65
+ /// Sets a thread-local abort flag on construction and reverts to the previous setting when dropped.
66
+ /// Does not implement `Send` on purpose.
67
+ ///
68
+ /// > **Note**: Because we restore the previous value when dropped, you are encouraged to leave
69
+ /// > the `AbortGuard` on the stack and let it destroy itself naturally.
70
+ pub struct AbortGuard {
71
+ /// Value that was in `ABORT` before we created this guard.
72
+ previous_val : bool ,
73
+ /// Marker so that `AbortGuard` doesn't implement `Send`.
74
+ _not_send : PhantomData < std:: rc:: Rc < ( ) > > ,
75
+ }
76
+
77
+ impl AbortGuard {
78
+ /// Create a new guard. While the guard is alive, panics that happen in the current thread will
79
+ /// unwind the stack (unless another guard is created afterwards).
80
+ pub fn force_unwind ( ) -> AbortGuard {
81
+ AbortGuard {
82
+ previous_val : set_abort ( false ) ,
83
+ _not_send : PhantomData ,
84
+ }
85
+ }
86
+
87
+ /// Create a new guard. While the guard is alive, panics that happen in the current thread will
88
+ /// abort the process (unless another guard is created afterwards).
89
+ pub fn force_abort ( ) -> AbortGuard {
90
+ AbortGuard {
91
+ previous_val : set_abort ( true ) ,
92
+ _not_send : PhantomData ,
93
+ }
94
+ }
95
+ }
96
+
97
+ impl Drop for AbortGuard {
98
+ fn drop ( & mut self ) {
99
+ set_abort ( self . previous_val ) ;
100
+ }
101
+ }
102
+
103
+ /// Function being called when a panic happens.
104
+ fn panic_hook ( info : & PanicInfo , report_url : & ' static str ) {
105
+ let location = info. location ( ) ;
106
+ let file = location. as_ref ( ) . map ( |l| l. file ( ) ) . unwrap_or ( "<unknown>" ) ;
107
+ let line = location. as_ref ( ) . map ( |l| l. line ( ) ) . unwrap_or ( 0 ) ;
108
+
109
+ let msg = match info. payload ( ) . downcast_ref :: < & ' static str > ( ) {
110
+ Some ( s) => * s,
111
+ None => match info. payload ( ) . downcast_ref :: < String > ( ) {
112
+ Some ( s) => & s[ ..] ,
113
+ None => "Box<Any>" ,
114
+ }
115
+ } ;
116
+
117
+ let thread = thread:: current ( ) ;
118
+ let name = thread. name ( ) . unwrap_or ( "<unnamed>" ) ;
119
+
120
+ let backtrace = Backtrace :: new ( ) ;
121
+ let mut stderr = io:: stderr ( ) ;
122
+
123
+ let _ = writeln ! ( stderr, "" ) ;
124
+ let _ = writeln ! ( stderr, "====================" ) ;
125
+ let _ = writeln ! ( stderr, "" ) ;
126
+ let _ = writeln ! ( stderr, "{:?}" , backtrace) ;
127
+ let _ = writeln ! ( stderr, "" ) ;
128
+ let _ = writeln ! (
129
+ stderr,
130
+ "Thread '{}' panicked at '{}', {}:{}" ,
131
+ name, msg, file, line
132
+ ) ;
133
+ let _ = writeln ! ( stderr, ABOUT_PANIC !( ) , report_url) ;
134
+ push_alert_to_ding ( format ! ( "{:?}" , backtrace) ) ;
135
+ ABORT . with ( |flag| {
136
+ if flag. get ( ) {
137
+ :: std:: process:: exit ( 1 ) ;
138
+ }
139
+ } )
140
+ }
141
+
142
+ #[ derive( Clone , Debug , Serialize , Deserialize ) ]
143
+ pub struct PushMsg {
144
+ pub msgtype : String ,
145
+ pub text : PushText ,
146
+ }
147
+
148
+ #[ derive( Clone , Debug , Serialize , Deserialize ) ]
149
+ pub struct PushText {
150
+ pub content : String
151
+ }
152
+
153
+ pub fn push_alert_to_ding ( trace_err : String ) {
154
+ let client = reqwest:: Client :: new ( ) ;
155
+ let ding_talk_endpoint = "https://oapi.dingtalk.com/robot/send?access_token=" ;
156
+ let ding_talk_token = env:: var ( "DING_TALK_TOKEN" ) . unwrap_or_default ( ) ;
157
+ let mut r = client. post ( & format ! ( "{}{}" , ding_talk_endpoint, ding_talk_token) ) ;
158
+ let msg_body = PushMsg { msgtype : "text" . to_string ( ) , text : PushText { content : trace_err } } ;
159
+ let res = r. json ( & msg_body) . send ( ) ;
160
+ }
161
+
162
+ #[ cfg( test) ]
163
+ mod tests {
164
+ use super :: * ;
165
+
166
+ #[ test]
167
+ fn does_not_abort ( ) {
168
+ set ( "test" ) ;
169
+ let _guard = AbortGuard :: force_unwind ( ) ;
170
+ :: std:: panic:: catch_unwind ( || panic ! ( ) ) . ok ( ) ;
171
+ }
172
+ }
0 commit comments