Skip to content

captureMessage can attach stack trace via stacktrace: true option #582

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Aug 26, 2016
2 changes: 1 addition & 1 deletion Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ module.exports = function(grunt) {
dest: 'build/raven.test.js',
options: {
browserifyOptions: {
debug: true // source maps
debug: false// source maps
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Source map generation for tests is/has been broken so I just disabled it to make my life easier debugging this. I'll revisit in future.

},
ignore: ['react-native'],
plugin: [proxyquire.plugin]
Expand Down
1 change: 1 addition & 0 deletions example/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
<button onclick="divide(1, 0)">Sourcemap breakage</button>
<button onclick="derp()">window.onerror</button>
<button onclick="testOptions()">test options</button>
<button onclick="testSynthetic()">test synthetic</button>
<button onclick="throwString()">throw string</button>
<button onclick="showDialog()">show dialog</button>
<button onclick="blobExample()">blob example</button>
Expand Down
6 changes: 6 additions & 0 deletions example/scratch.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ function showDialog() {
Raven.showReportDialog();
}

function testSynthetic() {
Raven.captureMessage('synthetic', {
stacktrace: true
});
}

function blobExample() {
var xhr = new XMLHttpRequest();
xhr.open('GET', 'stack.js');
Expand Down
86 changes: 67 additions & 19 deletions src/raven.js
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,12 @@ Raven.prototype = {
*/
captureException: function(ex, options) {
// If not an Error is passed through, recall as a message instead
if (!isError(ex)) return this.captureMessage(ex, options);
if (!isError(ex)) {
return this.captureMessage(ex, objectMerge({
trimHeadFrames: 1,
stacktrace: true // if we fall back to captureMessage, default to attempting a new trace
}, options));
}

// Store the raw exception object for potential debugging and introspection
this._lastCapturedException = ex;
Expand Down Expand Up @@ -362,12 +367,41 @@ Raven.prototype = {
return;
}

var data = objectMerge({
message: msg + '' // Make sure it's actually a string
}, options);

if (options && options.stacktrace) {
var ex;
// create a stack trace from this point; just trim
// off extra frames so they don't include this function call (or
// earlier Raven.js library fn calls)
try {
throw new Error(msg);
} catch (ex1) {
ex = ex1;
}

// null exception name so `Error` isn't prefixed to msg
ex.name = null;

options = objectMerge({
// fingerprint on msg, not stack trace (legacy behavior, could be
// revisited)
fingerprint: msg,
trimHeadFrames: (options.trimHeadFrames || 0) + 1
}, options);

var stack = TraceKit.computeStackTrace(ex);
var frames = this._prepareFrames(stack, options);
data.stacktrace = {
// Sentry expects frames oldest to newest
frames: frames.reverse()
}
}

// Fire away!
this._send(
objectMerge({
message: msg + '' // Make sure it's actually a string
}, options)
);
this._send(data);

return this;
},
Expand Down Expand Up @@ -1065,17 +1099,7 @@ Raven.prototype = {
},

_handleStackInfo: function(stackInfo, options) {
var self = this;
var frames = [];

if (stackInfo.stack && stackInfo.stack.length) {
each(stackInfo.stack, function(i, stack) {
var frame = self._normalizeFrame(stack);
if (frame) {
frames.push(frame);
}
});
}
var frames = this._prepareFrames(stackInfo, options);

this._triggerEvent('handle', {
stackInfo: stackInfo,
Expand All @@ -1087,11 +1111,36 @@ Raven.prototype = {
stackInfo.message,
stackInfo.url,
stackInfo.lineno,
frames.slice(0, this._globalOptions.stackTraceLimit),
frames,
options
);
},

_prepareFrames: function(stackInfo, options) {
var self = this;
var frames = [];
if (stackInfo.stack && stackInfo.stack.length) {
each(stackInfo.stack, function(i, stack) {
var frame = self._normalizeFrame(stack);
if (frame) {
frames.push(frame);
}
});

// e.g. frames captured via captureMessage throw
if (options && options.trimHeadFrames) {
for (var j = 0; j < options.trimHeadFrames && j < frames.length; j++) {
frames[j].in_app = false;
}
// ... delete to prevent from appearing in outbound payload
delete options.trimHeadFrames;
}
}
frames = frames.slice(0, this._globalOptions.stackTraceLimit);
return frames;
},


_normalizeFrame: function(frame) {
if (!frame.url) return;

Expand All @@ -1117,7 +1166,6 @@ Raven.prototype = {

_processException: function(type, message, fileurl, lineno, frames, options) {
var stacktrace;

if (!!this._globalOptions.ignoreErrors.test && this._globalOptions.ignoreErrors.test(message)) return;

message += '';
Expand Down
27 changes: 26 additions & 1 deletion test/raven.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1757,7 +1757,9 @@ describe('Raven (public API)', function() {
Raven.context({foo: 'bar'}, broken);
}, error);
assert.isTrue(Raven.captureException.called);
assert.deepEqual(Raven.captureException.lastCall.args, [error, {'foo': 'bar'}]);
assert.deepEqual(Raven.captureException.lastCall.args, [error, {
'foo': 'bar'
}]);
});

it('should capture the exception without options', function() {
Expand Down Expand Up @@ -2022,6 +2024,29 @@ describe('Raven (public API)', function() {
});
});

it('should include a synthetic stacktrace if stacktrace:true is passed', function () {
this.sinon.stub(Raven, 'isSetup').returns(true);
this.sinon.stub(Raven, '_send');

function foo() {
Raven.captureMessage('foo', {
stacktrace: true
});
}

foo();
var frames = Raven._send.lastCall.args[0].stacktrace.frames;

// Raven.captureMessage
var last = frames[frames.length - 1];
assert.isTrue(/(captureMessage|^\?)$/.test(last.function)); // loose equality check because differs per-browser
assert.equal(last.in_app, false);

// foo
var secondLast = frames[frames.length - 2];
assert.equal(secondLast.function, 'foo');
assert.equal(secondLast.in_app, true);
});
});

describe('.captureException', function() {
Expand Down