Skip to content

Commit c75344d

Browse files
committed
Capture breadcrumbs from XHRs and transfer in _send
1 parent 3abd8ba commit c75344d

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];
@@ -344,6 +346,15 @@ Raven.prototype = {
344346
return this;
345347
},
346348

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

@@ -664,12 +675,35 @@ Raven.prototype = {
664675
});
665676

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

987+
data.breadcrumbs = this._breadcrumbs;
988+
953989
// If there are no tags/extra, strip the key from the payload alltogther.
954990
if (isEmptyObject(data.tags)) delete data.tags;
955991

test/raven.test.js

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -711,6 +711,9 @@ describe('globals', function() {
711711
logger: 'javascript',
712712
maxMessageLength: 100
713713
};
714+
Raven._breadcrumbs = [
715+
{ type: 'request', data: { method: 'POST', url: 'http://example.org/api/0/auth/' }}
716+
];
714717

715718
Raven._send({message: 'bar'});
716719
assert.deepEqual(Raven._makeRequest.lastCall.args[0].data, {
@@ -725,7 +728,8 @@ describe('globals', function() {
725728
},
726729
event_id: 'abc123',
727730
message: 'bar',
728-
extra: {'session:duration': 100}
731+
extra: {'session:duration': 100},
732+
breadcrumbs: [{ type: 'request', data: { method: 'POST', url: 'http://example.org/api/0/auth/' }}]
729733
});
730734
});
731735

@@ -760,7 +764,8 @@ describe('globals', function() {
760764
name: 'Matt'
761765
},
762766
message: 'bar',
763-
extra: {'session:duration': 100}
767+
extra: {'session:duration': 100},
768+
breadcrumbs: []
764769
});
765770
});
766771

@@ -793,7 +798,8 @@ describe('globals', function() {
793798
event_id: 'abc123',
794799
message: 'bar',
795800
tags: {tag1: 'value1', tag2: 'value2'},
796-
extra: {'session:duration': 100}
801+
extra: {'session:duration': 100},
802+
breadcrumbs: []
797803
});
798804

799805

@@ -835,7 +841,8 @@ describe('globals', function() {
835841

836842
event_id: 'abc123',
837843
message: 'bar',
838-
extra: {key1: 'value1', key2: 'value2', 'session:duration': 100}
844+
extra: {key1: 'value1', key2: 'value2', 'session:duration': 100},
845+
breadcrumbs: []
839846
});
840847

841848
assert.deepEqual(Raven._globalOptions, {
@@ -2071,6 +2078,43 @@ describe('Raven (public API)', function() {
20712078
});
20722079
});
20732080

2081+
describe('.captureBreadcrumb', function () {
2082+
it('should store the passed object in _breadcrumbs', function() {
2083+
var breadcrumb = {
2084+
type: 'request',
2085+
timestamp: 100,
2086+
data: {
2087+
url: 'http://example.org/api/0/auth/',
2088+
statusCode: 200
2089+
}
2090+
};
2091+
2092+
Raven.captureBreadcrumb(breadcrumb);
2093+
2094+
assert.equal(Raven._breadcrumbs[0], breadcrumb);
2095+
});
2096+
2097+
it('should dequeue the oldest breadcrumb when over limit', function() {
2098+
Raven._breadcrumbLimit = 5;
2099+
Raven._breadcrumbs = [
2100+
{ id: 1 },
2101+
{ id: 2 },
2102+
{ id: 3 },
2103+
{ id: 4 },
2104+
{ id: 5 }
2105+
];
2106+
2107+
Raven.captureBreadcrumb({ id: 6 });
2108+
assert.deepEqual(Raven._breadcrumbs, [
2109+
{ id: 2 },
2110+
{ id: 3 },
2111+
{ id: 4 },
2112+
{ id: 5 },
2113+
{ id: 6 }
2114+
]);
2115+
});
2116+
});
2117+
20742118
describe('.Raven.isSetup', function() {
20752119
it('should work as advertised', function() {
20762120
var isSetup = this.sinon.stub(Raven, 'isSetup');

0 commit comments

Comments
 (0)