Skip to content

Commit e5c9bdf

Browse files
committed
Capture breadcrumbs from XHRs and transfer in _send
1 parent b09d766 commit e5c9bdf

File tree

2 files changed

+86
-6
lines changed

2 files changed

+86
-6
lines changed

src/raven.js

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ function Raven() {
5858
this._plugins = [];
5959
this._startTime = now();
6060
this._wrappedBuiltIns = [];
61+
this._breadcrumbs = [];
62+
this._breadcrumbLimit = 20;
6163

6264
for (var method in this._originalConsole) { // eslint-disable-line guard-for-in
6365
this._originalConsoleMethods[method] = this._originalConsole[method];
@@ -345,6 +347,15 @@ Raven.prototype = {
345347
return this;
346348
},
347349

350+
captureBreadcrumb: function (obj) {
351+
obj.timestamp = now();
352+
353+
this._breadcrumbs.push(obj);
354+
if (this._breadcrumbs.length > this._breadcrumbLimit) {
355+
this._breadcrumbs.shift();
356+
}
357+
},
358+
348359
addPlugin: function(plugin /*arg1, arg2, ... argN*/) {
349360
var pluginArgs = Array.prototype.slice.call(arguments, 1);
350361

@@ -665,12 +676,35 @@ Raven.prototype = {
665676
});
666677

667678
if ('XMLHttpRequest' in window) {
668-
fill(XMLHttpRequest.prototype, 'send', function(origSend) {
679+
var xhrproto = XMLHttpRequest.prototype;
680+
fill(xhrproto, 'open', function(origOpen) {
681+
return function (method, url) { // preserve arity
682+
this.__raven_xhr = {
683+
method: method,
684+
url: url,
685+
statusCode: null
686+
};
687+
return origOpen.apply(this, arguments);
688+
};
689+
});
690+
691+
fill(xhrproto, 'send', function(origSend) {
669692
return function (data) { // preserve arity
670693
var xhr = this;
671694
'onreadystatechange onload onerror onprogress'.replace(/\w+/g, function (prop) {
672-
if (prop in xhr && Object.prototype.toString.call(xhr[prop]) === '[object Function]') {
695+
if (prop in xhr && isFunction(xhr[prop])) {
673696
fill(xhr, prop, function (orig) {
697+
if (prop === 'onreadystatechange' && xhr.__raven_xhr && (xhr.readyState === 1 || xhr.readyState === 4)) {
698+
try {
699+
// touching statusCode in some platforms throws
700+
// an exception
701+
xhr.__raven_xhr.statusCode = xhr.status;
702+
} catch (e) { /* do nothing */ }
703+
self.captureBreadcrumb({
704+
type: 'request',
705+
data: xhr.__raven_xhr
706+
});
707+
}
674708
return self.wrap(orig);
675709
}, true /* noUndo */); // don't track filled methods on XHR instances
676710
}
@@ -952,6 +986,8 @@ Raven.prototype = {
952986
// Send along our own collected metadata with extra
953987
data.extra['session:duration'] = now() - this._startTime;
954988

989+
data.breadcrumbs = this._breadcrumbs;
990+
955991
// If there are no tags/extra, strip the key from the payload alltogther.
956992
if (isEmptyObject(data.tags)) delete data.tags;
957993

test/raven.test.js

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,9 @@ describe('globals', function() {
718718
logger: 'javascript',
719719
maxMessageLength: 100
720720
};
721+
Raven._breadcrumbs = [
722+
{ type: 'request', data: { method: 'POST', url: 'http://example.org/api/0/auth/' }}
723+
];
721724

722725
Raven._send({message: 'bar'});
723726
assert.deepEqual(Raven._makeRequest.lastCall.args[0].data, {
@@ -732,7 +735,8 @@ describe('globals', function() {
732735
},
733736
event_id: 'abc123',
734737
message: 'bar',
735-
extra: {'session:duration': 100}
738+
extra: {'session:duration': 100},
739+
breadcrumbs: [{ type: 'request', data: { method: 'POST', url: 'http://example.org/api/0/auth/' }}]
736740
});
737741
});
738742

@@ -767,7 +771,8 @@ describe('globals', function() {
767771
name: 'Matt'
768772
},
769773
message: 'bar',
770-
extra: {'session:duration': 100}
774+
extra: {'session:duration': 100},
775+
breadcrumbs: []
771776
});
772777
});
773778

@@ -800,7 +805,8 @@ describe('globals', function() {
800805
event_id: 'abc123',
801806
message: 'bar',
802807
tags: {tag1: 'value1', tag2: 'value2'},
803-
extra: {'session:duration': 100}
808+
extra: {'session:duration': 100},
809+
breadcrumbs: []
804810
});
805811

806812

@@ -842,7 +848,8 @@ describe('globals', function() {
842848

843849
event_id: 'abc123',
844850
message: 'bar',
845-
extra: {key1: 'value1', key2: 'value2', 'session:duration': 100}
851+
extra: {key1: 'value1', key2: 'value2', 'session:duration': 100},
852+
breadcrumbs: []
846853
});
847854

848855
assert.deepEqual(Raven._globalOptions, {
@@ -2144,6 +2151,43 @@ describe('Raven (public API)', function() {
21442151
});
21452152
});
21462153

2154+
describe('.captureBreadcrumb', function () {
2155+
it('should store the passed object in _breadcrumbs', function() {
2156+
var breadcrumb = {
2157+
type: 'request',
2158+
timestamp: 100,
2159+
data: {
2160+
url: 'http://example.org/api/0/auth/',
2161+
statusCode: 200
2162+
}
2163+
};
2164+
2165+
Raven.captureBreadcrumb(breadcrumb);
2166+
2167+
assert.equal(Raven._breadcrumbs[0], breadcrumb);
2168+
});
2169+
2170+
it('should dequeue the oldest breadcrumb when over limit', function() {
2171+
Raven._breadcrumbLimit = 5;
2172+
Raven._breadcrumbs = [
2173+
{ id: 1 },
2174+
{ id: 2 },
2175+
{ id: 3 },
2176+
{ id: 4 },
2177+
{ id: 5 }
2178+
];
2179+
2180+
Raven.captureBreadcrumb({ id: 6 });
2181+
assert.deepEqual(Raven._breadcrumbs, [
2182+
{ id: 2 },
2183+
{ id: 3 },
2184+
{ id: 4 },
2185+
{ id: 5 },
2186+
{ id: 6 }
2187+
]);
2188+
});
2189+
});
2190+
21472191
describe('.Raven.isSetup', function() {
21482192
it('should work as advertised', function() {
21492193
var isSetup = this.sinon.stub(Raven, 'isSetup');

0 commit comments

Comments
 (0)