Skip to content

Commit ad54665

Browse files
authored
Merge pull request #759 from rollbar/wj-domexception-trace-chain
Send DOMException as trace_chain; allow nested errors in browser SDK
2 parents c808ecd + 9811a96 commit ad54665

File tree

6 files changed

+161
-17
lines changed

6 files changed

+161
-17
lines changed

examples/error.html

+6
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111
// is true in the config.
1212
throw new Error('test error');
1313
};
14+
window.throwDomException = function throwDomException() {
15+
// Example error, which will be reported to rollbar when `captureUncaught`
16+
// is true in the config.
17+
throw new DOMException('test DOMException');
18+
};
1419
</script>
1520
</head>
1621
<body>
@@ -20,4 +25,5 @@ <h1>
2025
</h1>
2126
</div>
2227
<button id="throw-error" onclick="throwError()">Throw Error</button>
28+
<button id="throw-dom-exception" onclick="throwDomException()">Throw DOMException</button>
2329
</html>

src/browser/errorParser.js

+15-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,21 @@ function Stack(exception) {
7676

7777

7878
function parse(e) {
79-
return new Stack(e);
79+
var err = e;
80+
81+
if (err.nested) {
82+
var traceChain = [];
83+
while (err) {
84+
traceChain.push(new Stack(err));
85+
err = err.nested;
86+
}
87+
88+
// Return primary error with full trace chain attached.
89+
traceChain[0].traceChain = traceChain;
90+
return traceChain[0];
91+
} else {
92+
return new Stack(err);
93+
}
8094
}
8195

8296

src/browser/rollbar.js

+1
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,7 @@ Rollbar.prototype.captureLoad = function(e, ts) {
468468

469469
function addTransformsToNotifier(notifier, gWindow) {
470470
notifier
471+
.addTransform(transforms.handleDomException)
471472
.addTransform(transforms.handleItemWithError)
472473
.addTransform(transforms.ensureItemHasSomethingToSay)
473474
.addTransform(transforms.addBaseInfo)

src/browser/transforms.js

+63-14
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,18 @@ var _ = require('../utility');
22
var errorParser = require('./errorParser');
33
var logger = require('./logger');
44

5+
function handleDomException(item, options, callback) {
6+
if(item.err && errorParser.Stack(item.err).name === 'DOMException') {
7+
var originalError = new Error();
8+
originalError.name = item.err.name;
9+
originalError.message = item.err.message;
10+
originalError.stack = item.err.stack;
11+
originalError.nested = item.err;
12+
item.err = originalError;
13+
}
14+
callback(null, item);
15+
}
16+
517
function handleItemWithError(item, options, callback) {
618
item.data = item.data || {};
719
if (item.err) {
@@ -109,7 +121,11 @@ function addPluginInfo(window) {
109121

110122
function addBody(item, options, callback) {
111123
if (item.stackInfo) {
112-
addBodyTrace(item, options, callback);
124+
if (item.stackInfo.traceChain) {
125+
addBodyTraceChain(item, options, callback);
126+
} else {
127+
addBodyTrace(item, options, callback);
128+
}
113129
} else {
114130
addBodyMessage(item, options, callback);
115131
}
@@ -134,11 +150,51 @@ function addBodyMessage(item, options, callback) {
134150
callback(null, item);
135151
}
136152

153+
function stackFromItem(item) {
154+
// Transform a TraceKit stackInfo object into a Rollbar trace
155+
var stack = item.stackInfo.stack;
156+
if (stack && stack.length === 0 && item._unhandledStackInfo && item._unhandledStackInfo.stack) {
157+
stack = item._unhandledStackInfo.stack;
158+
}
159+
return stack;
160+
}
161+
162+
function addBodyTraceChain(item, options, callback) {
163+
var traceChain = item.stackInfo.traceChain;
164+
var traces = [];
165+
166+
var traceChainLength = traceChain.length;
167+
for (var i = 0; i < traceChainLength; i++) {
168+
var trace = buildTrace(item, traceChain[i], options);
169+
traces.push(trace);
170+
}
171+
172+
_.set(item, 'data.body', {trace_chain: traces});
173+
callback(null, item);
174+
}
137175

138176
function addBodyTrace(item, options, callback) {
139-
var description = item.data.description;
140-
var stackInfo = item.stackInfo;
141-
var custom = item.custom;
177+
var stack = stackFromItem(item);
178+
179+
if (stack) {
180+
var trace = buildTrace(item, item.stackInfo, options);
181+
_.set(item, 'data.body', {trace: trace});
182+
callback(null, item);
183+
} else {
184+
var stackInfo = item.stackInfo;
185+
var guess = errorParser.guessErrorClass(stackInfo.message);
186+
var className = stackInfo.name || guess[0];
187+
var message = guess[1];
188+
189+
item.message = className + ': ' + message;
190+
addBodyMessage(item, options, callback);
191+
}
192+
}
193+
194+
function buildTrace(item, stackInfo, options) {
195+
var description = item && item.data.description;
196+
var custom = item && item.custom;
197+
var stack = stackFromItem(item);
142198

143199
var guess = errorParser.guessErrorClass(stackInfo.message);
144200
var className = stackInfo.name || guess[0];
@@ -154,11 +210,6 @@ function addBodyTrace(item, options, callback) {
154210
trace.exception.description = description;
155211
}
156212

157-
// Transform a TraceKit stackInfo object into a Rollbar trace
158-
var stack = stackInfo.stack;
159-
if (stack && stack.length === 0 && item._unhandledStackInfo && item._unhandledStackInfo.stack) {
160-
stack = item._unhandledStackInfo.stack;
161-
}
162213
if (stack) {
163214
if (stack.length === 0) {
164215
trace.exception.stack = stackInfo.rawStack;
@@ -224,12 +275,9 @@ function addBodyTrace(item, options, callback) {
224275
if (custom) {
225276
trace.extra = _.merge(custom);
226277
}
227-
_.set(item, 'data.body', {trace: trace});
228-
callback(null, item);
229-
} else {
230-
item.message = className + ': ' + message;
231-
addBodyMessage(item, options, callback);
232278
}
279+
280+
return trace;
233281
}
234282

235283
function scrubPayload(item, options, callback) {
@@ -239,6 +287,7 @@ function scrubPayload(item, options, callback) {
239287
}
240288

241289
module.exports = {
290+
handleDomException: handleDomException,
242291
handleItemWithError: handleItemWithError,
243292
ensureItemHasSomethingToSay: ensureItemHasSomethingToSay,
244293
addBaseInfo: addBaseInfo,

test/browser.rollbar.test.js

+32-2
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ describe('options.captureUncaught', function() {
399399
done();
400400
});
401401

402-
it('should transmit duplicate errors when set in config', function(done) {
402+
it('should transmit duplicate errors when set in config', function(done) {
403403
var server = window.server;
404404
stubResponse(server);
405405
server.requests.length = 0;
@@ -434,7 +434,37 @@ describe('options.captureUncaught', function() {
434434
});
435435

436436
done();
437-
})
437+
});
438+
it('should send DOMException as trace_chain', function(done) {
439+
var server = window.server;
440+
stubResponse(server);
441+
server.requests.length = 0;
442+
443+
var options = {
444+
accessToken: 'POST_CLIENT_ITEM_TOKEN',
445+
captureUncaught: true
446+
};
447+
var rollbar = new Rollbar(options);
448+
449+
var element = document.getElementById('throw-dom-exception');
450+
element.click();
451+
server.respond();
452+
453+
var body = JSON.parse(server.requests[0].requestBody);
454+
455+
expect(body.access_token).to.eql('POST_CLIENT_ITEM_TOKEN');
456+
expect(body.data.body.trace_chain[0].exception.message).to.eql('test DOMException');
457+
458+
// karma doesn't unload the browser between tests, so the onerror handler
459+
// will remain installed. Unset captureUncaught so the onerror handler
460+
// won't affect other tests.
461+
rollbar.configure({
462+
captureUncaught: false
463+
});
464+
465+
done();
466+
});
467+
438468
});
439469

440470
describe('options.captureUnhandledRejections', function() {

test/browser.transforms.test.js

+44
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,30 @@ function itemFromArgs(args) {
3030
return item;
3131
}
3232

33+
describe('handleDomException', function() {
34+
it('should do nothing if not a DOMException', function(done) {
35+
var err = new Error('test');
36+
var args = ['a message', err];
37+
var item = itemFromArgs(args);
38+
var options = {};
39+
t.handleDomException(item, options, function(e, i) {
40+
expect(item.err).to.eql(item.err);
41+
expect(item.err.nested).to.not.be.ok();
42+
done(e);
43+
});
44+
});
45+
it('should create nested exception for DOMException', function(done) {
46+
var err = new DOMException('dom error');
47+
var args = ['a message', err];
48+
var item = itemFromArgs(args);
49+
var options = {};
50+
t.handleDomException(item, options, function(e, i) {
51+
expect(item.err.nested.constructor.name).to.eql('DOMException');
52+
expect(item.err.constructor.name).to.eql('Error');
53+
done(e);
54+
});
55+
});
56+
});
3357
describe('handleItemWithError', function() {
3458
it('should do nothing if there is no err', function(done) {
3559
var args = ['a message'];
@@ -297,6 +321,26 @@ describe('addBody', function() {
297321
});
298322
});
299323
});
324+
describe('with nested error', function() {
325+
it('should create trace_chain', function(done) {
326+
var nestedErr = new Error('nested error');
327+
var err = new Error('test error');
328+
err.nested = nestedErr;
329+
var args = ['a message', err];
330+
var item = itemFromArgs(args);
331+
var options = {};
332+
t.handleItemWithError(item, options, function(e, i) {
333+
expect(i.stackInfo).to.be.ok();
334+
});
335+
t.addBody(item, options, function(e, i) {
336+
console.log('body:', i.data.body)
337+
expect(i.data.body.trace_chain.length).to.eql(2);
338+
expect(i.data.body.trace_chain[0].exception.message).to.eql('test error');
339+
expect(i.data.body.trace_chain[1].exception.message).to.eql('nested error');
340+
done(e);
341+
});
342+
});
343+
});
300344
});
301345

302346
describe('scrubPayload', function() {

0 commit comments

Comments
 (0)