This repository has been archived by the owner on May 4, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 9
/
panic.js
244 lines (216 loc) · 7.56 KB
/
panic.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
/*
* panic.js: postmortem debugging for JavaScript
*
* A postmortem debugging facility is critical for root-causing issues that
* occur in production from the artifacts of a single failure. Without such a
* facility, tracking down problems in production becomes a tedious process of
* adding logging, trying to reproduce the problem, and repeating until enough
* information is gathered to root-cause the issue. For reproducible problems,
* this process is merely painful for developers, administrators, and customers
* alike. For unreproducible problems, this is untenable.
*
* Like most dynamic environments, JavaScript under Node/V8 has no built-in
* postmortem debugging facility, so we implement our own here. The basic idea
* is to maintain a global object that references all of the internal state we
* would want for debugging. Then when our application crashes, we dump this
* state to a file, and then exit.
*
* Note that while the program is panicking, we don't invoke any code inside
* other components; modules must register objects *before* the panic in order
* to have them saved during the panic. This is reasonable because we're only
* storing references, so consumers can continue modifying their objects after
* registering them. This is necessary to minimize the amount of code that must
* work correctly during the panic.
*/
var mod_fs = require('fs');
var mod_subr = require('./subr');
/*
* Configures the current program to dump saved program state before crashing.
*/
function caEnablePanicOnCrash()
{
process.on('uncaughtException', function (ex) {
caPanic('panic due to uncaught exception', ex);
});
}
/*
* caPanic is invoked when the program encounters a fatal error to log the error
* message and optional exception, dump all state previously registered via
* panicDbg to the file "ncore.<pid>", and then exit the program. This function
* is invoked either explicitly by the application or, if caEnablePanicOnCrash
* has been invoked, automatically when an uncaught exception bubbles back to
* the event loop. Since the program has effectively crashed at the point this
* function is called, we must not allow any other code to run, so we perform
* all filesystem operations synchronously and then exit immediately with a
* non-zero exit status.
*/
function caPanic(str, err)
{
var when, filename, msg;
if (!err) {
err = new Error(str);
str = 'explicit panic';
}
try {
when = new Date();
filename = 'ncore.' + process.pid;
msg = caPanicWriteSafeError('PANIC: ' + str, err);
panicDbg.set('panic.error', msg);
panicDbg.set('panic.time', when);
panicDbg.set('panic.time-ms', when.getTime());
panicDbg.set('panic.memusage', process.memoryUsage());
/*
* If we had child.execSync(), we could get pfiles. :(
*/
caPanicLog('writing core dump to ' + process.cwd() + '/' +
filename);
caPanicSave(filename);
caPanicLog('finished writing core dump');
} catch (ex) {
caPanicWriteSafeError('error during panic', ex);
}
/*
* If available, use process.abort() to dump core. Some systems
* (notably illumos-based systems) can debug Node core dumps.
*/
if (process.abort)
process.abort();
process.exit(1);
}
/*
* Log the given message and error without throwing an exception.
*/
function caPanicWriteSafeError(msg, err)
{
var errstr;
try {
errstr = mod_subr.caSprintf('%r', err);
} catch (ex) {
errstr = (err && err.message && err.stack) ?
err.message + '\n' + err.stack : '<unknown error>';
}
caPanicLog(msg + ': ' + errstr);
return (errstr);
}
/*
* Log the given raw message without throwing an exception.
*/
function caPanicLog(msg)
{
process.stderr.write('[' + mod_subr.caFormatDate(new Date()) + ']' +
' CRIT ' + msg + '\n');
}
/*
* Saves panicDbg state to the named file.
*/
function caPanicSave(filename)
{
var dump = panicDbg.dump();
mod_fs.writeFileSync(filename, dump);
}
/*
* Since we want all components to be able to save debugging state without
* having to pass context pointers around everywhere, we supply a global object
* called panicDbg to which program state can be attached via the following
* methods:
*
* set(name, state) Adds a new debugging key called "name" and
* associates "state" with that key. If "name" is
* already being used, the previous association is
* replaced with the new one. This key-value pair
* will be serialized and dumped when the program
* crashes. Assuming "state" is a reference type,
* the caller can modify this object later and such
* updates will be reflected in the serialized
* state when the program crashes.
*
* add(name, state) Like set(name, state), but ensures that the new
* key does not conflict with an existing key by
* adding a unique identifier to it. Returns the
* actual key that was used for subsequent use in
* "remove".
*
* remove(name) Removes an existing association.
*
* dump() Returns the serialized debug state. This should
* NOT be used except by the panic code itself and
* test code since it may modify the debug state.
*/
function caDebugState()
{
var now = new Date();
this.cds_state = {};
this.cds_ids = {};
this.set('dbg.format-version', '0.1');
this.set('init.process.argv', process.argv);
this.set('init.process.pid', process.pid);
this.set('init.process.cwd', process.cwd());
this.set('init.process.env', process.env);
this.set('init.process.version', process.version);
this.set('init.process.platform', process.platform);
this.set('init.time', now);
this.set('init.time-ms', now.getTime());
}
caDebugState.prototype.set = function (name, state)
{
this.cds_state[name] = state;
};
caDebugState.prototype.add = function (name, state)
{
var ii;
if (!this.cds_ids[name])
this.cds_ids[name] = 1;
for (ii = this.cds_ids[name]; ; ii++) {
if (!((name + ii) in this.cds_state))
break;
}
this.cds_ids[name] = ii + 1;
this.set(name + ii, state);
return (name + ii);
};
caDebugState.prototype.remove = function (name)
{
delete (this.cds_state[name]);
};
caDebugState.prototype.dump = function ()
{
/*
* JSON.stringify() does not deal with circular structures, so we have
* to explicitly remove such references here. It would be nice if we
* could encode these properly, but we'll need something more
* sophisticated than JSON. We're allowed to stomp on the state
* in-memory here because we're only invoked in the crash path.
*/
mod_subr.caRemoveCircularRefs(this.cds_state);
return (JSON.stringify(this.cds_state));
};
/*
* The public interface for this module is simple:
*
* caPanic(msg, [err]) Dumps all registered debug state and exits the
* program.
*
* caEnablePanicOnCrash() Configures the program to automatically invoke
* caPanic when an uncaught exception bubbles back
* to the event loop.
*
* [global] panicDbg Manages state to be dumped when caPanic is
* invoked.
*
* While global state is traditionally frowned upon in favor of reusable
* components, the global solution makes more sense for this module since
* there can be only one application running at a time and no matter how many
* components it contains there must only be one set of debugging state that
* gets dumped when the program crashes.
*/
if (!global.panicDbg)
global.panicDbg = new caDebugState();
/*
* We expose "caPanic" as a global for the "ncore" tool, which uses the debugger
* interface to invoke it.
*/
global.caPanic = caPanic;
exports.enablePanicOnCrash = caEnablePanicOnCrash;
exports.panic = caPanic;
exports.caPanicSave = caPanicSave; /* for testing only */
exports.caDebugState = caDebugState; /* for testing only */