Skip to content

Commit e8040b1

Browse files
authored
captureMessage can attach stack trace via stacktrace: true option (#582)
1 parent 4a06c04 commit e8040b1

File tree

5 files changed

+101
-21
lines changed

5 files changed

+101
-21
lines changed

Gruntfile.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ module.exports = function(grunt) {
132132
dest: 'build/raven.test.js',
133133
options: {
134134
browserifyOptions: {
135-
debug: true // source maps
135+
debug: false// source maps
136136
},
137137
ignore: ['react-native'],
138138
plugin: [proxyquire.plugin]

example/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
<button onclick="divide(1, 0)">Sourcemap breakage</button>
3737
<button onclick="derp()">window.onerror</button>
3838
<button onclick="testOptions()">test options</button>
39+
<button onclick="testSynthetic()">test synthetic</button>
3940
<button onclick="throwString()">throw string</button>
4041
<button onclick="showDialog()">show dialog</button>
4142
<button onclick="blobExample()">blob example</button>

example/scratch.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ function showDialog() {
4646
Raven.showReportDialog();
4747
}
4848

49+
function testSynthetic() {
50+
Raven.captureMessage('synthetic', {
51+
stacktrace: true
52+
});
53+
}
54+
4955
function blobExample() {
5056
var xhr = new XMLHttpRequest();
5157
xhr.open('GET', 'stack.js');

src/raven.js

Lines changed: 67 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,12 @@ Raven.prototype = {
325325
*/
326326
captureException: function(ex, options) {
327327
// If not an Error is passed through, recall as a message instead
328-
if (!isError(ex)) return this.captureMessage(ex, options);
328+
if (!isError(ex)) {
329+
return this.captureMessage(ex, objectMerge({
330+
trimHeadFrames: 1,
331+
stacktrace: true // if we fall back to captureMessage, default to attempting a new trace
332+
}, options));
333+
}
329334

330335
// Store the raw exception object for potential debugging and introspection
331336
this._lastCapturedException = ex;
@@ -362,12 +367,41 @@ Raven.prototype = {
362367
return;
363368
}
364369

370+
var data = objectMerge({
371+
message: msg + '' // Make sure it's actually a string
372+
}, options);
373+
374+
if (options && options.stacktrace) {
375+
var ex;
376+
// create a stack trace from this point; just trim
377+
// off extra frames so they don't include this function call (or
378+
// earlier Raven.js library fn calls)
379+
try {
380+
throw new Error(msg);
381+
} catch (ex1) {
382+
ex = ex1;
383+
}
384+
385+
// null exception name so `Error` isn't prefixed to msg
386+
ex.name = null;
387+
388+
options = objectMerge({
389+
// fingerprint on msg, not stack trace (legacy behavior, could be
390+
// revisited)
391+
fingerprint: msg,
392+
trimHeadFrames: (options.trimHeadFrames || 0) + 1
393+
}, options);
394+
395+
var stack = TraceKit.computeStackTrace(ex);
396+
var frames = this._prepareFrames(stack, options);
397+
data.stacktrace = {
398+
// Sentry expects frames oldest to newest
399+
frames: frames.reverse()
400+
}
401+
}
402+
365403
// Fire away!
366-
this._send(
367-
objectMerge({
368-
message: msg + '' // Make sure it's actually a string
369-
}, options)
370-
);
404+
this._send(data);
371405

372406
return this;
373407
},
@@ -1066,17 +1100,7 @@ Raven.prototype = {
10661100
},
10671101

10681102
_handleStackInfo: function(stackInfo, options) {
1069-
var self = this;
1070-
var frames = [];
1071-
1072-
if (stackInfo.stack && stackInfo.stack.length) {
1073-
each(stackInfo.stack, function(i, stack) {
1074-
var frame = self._normalizeFrame(stack);
1075-
if (frame) {
1076-
frames.push(frame);
1077-
}
1078-
});
1079-
}
1103+
var frames = this._prepareFrames(stackInfo, options);
10801104

10811105
this._triggerEvent('handle', {
10821106
stackInfo: stackInfo,
@@ -1088,11 +1112,36 @@ Raven.prototype = {
10881112
stackInfo.message,
10891113
stackInfo.url,
10901114
stackInfo.lineno,
1091-
frames.slice(0, this._globalOptions.stackTraceLimit),
1115+
frames,
10921116
options
10931117
);
10941118
},
10951119

1120+
_prepareFrames: function(stackInfo, options) {
1121+
var self = this;
1122+
var frames = [];
1123+
if (stackInfo.stack && stackInfo.stack.length) {
1124+
each(stackInfo.stack, function(i, stack) {
1125+
var frame = self._normalizeFrame(stack);
1126+
if (frame) {
1127+
frames.push(frame);
1128+
}
1129+
});
1130+
1131+
// e.g. frames captured via captureMessage throw
1132+
if (options && options.trimHeadFrames) {
1133+
for (var j = 0; j < options.trimHeadFrames && j < frames.length; j++) {
1134+
frames[j].in_app = false;
1135+
}
1136+
// ... delete to prevent from appearing in outbound payload
1137+
delete options.trimHeadFrames;
1138+
}
1139+
}
1140+
frames = frames.slice(0, this._globalOptions.stackTraceLimit);
1141+
return frames;
1142+
},
1143+
1144+
10961145
_normalizeFrame: function(frame) {
10971146
if (!frame.url) return;
10981147

@@ -1118,7 +1167,6 @@ Raven.prototype = {
11181167

11191168
_processException: function(type, message, fileurl, lineno, frames, options) {
11201169
var stacktrace;
1121-
11221170
if (!!this._globalOptions.ignoreErrors.test && this._globalOptions.ignoreErrors.test(message)) return;
11231171

11241172
message += '';

test/raven.test.js

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1757,7 +1757,9 @@ describe('Raven (public API)', function() {
17571757
Raven.context({foo: 'bar'}, broken);
17581758
}, error);
17591759
assert.isTrue(Raven.captureException.called);
1760-
assert.deepEqual(Raven.captureException.lastCall.args, [error, {'foo': 'bar'}]);
1760+
assert.deepEqual(Raven.captureException.lastCall.args, [error, {
1761+
'foo': 'bar'
1762+
}]);
17611763
});
17621764

17631765
it('should capture the exception without options', function() {
@@ -2022,6 +2024,29 @@ describe('Raven (public API)', function() {
20222024
});
20232025
});
20242026

2027+
it('should include a synthetic stacktrace if stacktrace:true is passed', function () {
2028+
this.sinon.stub(Raven, 'isSetup').returns(true);
2029+
this.sinon.stub(Raven, '_send');
2030+
2031+
function foo() {
2032+
Raven.captureMessage('foo', {
2033+
stacktrace: true
2034+
});
2035+
}
2036+
2037+
foo();
2038+
var frames = Raven._send.lastCall.args[0].stacktrace.frames;
2039+
2040+
// Raven.captureMessage
2041+
var last = frames[frames.length - 1];
2042+
assert.isTrue(/(captureMessage|^\?)$/.test(last.function)); // loose equality check because differs per-browser
2043+
assert.equal(last.in_app, false);
2044+
2045+
// foo
2046+
var secondLast = frames[frames.length - 2];
2047+
assert.equal(secondLast.function, 'foo');
2048+
assert.equal(secondLast.in_app, true);
2049+
});
20252050
});
20262051

20272052
describe('.captureException', function() {

0 commit comments

Comments
 (0)