Skip to content

Commit 2ac6987

Browse files
committed
Add automatic & manual tracking of JS errors to JavaScript tracker (close snowplow#16)
1 parent caf403b commit 2ac6987

4 files changed

Lines changed: 171 additions & 4 deletions

File tree

src/js/errors.js

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/*
2+
* JavaScript tracker for Snowplow: tracker.js
3+
*
4+
* Significant portions copyright 2010 Anthon Pang. Remainder copyright
5+
* 2012-2016 Snowplow Analytics Ltd. All rights reserved.
6+
*
7+
* Redistribution and use in source and binary forms, with or without
8+
* modification, are permitted provided that the following conditions are
9+
* met:
10+
*
11+
* * Redistributions of source code must retain the above copyright
12+
* notice, this list of conditions and the following disclaimer.
13+
*
14+
* * Redistributions in binary form must reproduce the above copyright
15+
* notice, this list of conditions and the following disclaimer in the
16+
* documentation and/or other materials provided with the distribution.
17+
*
18+
* * Neither the name of Anthon Pang nor Snowplow Analytics Ltd nor the
19+
* names of their contributors may be used to endorse or promote products
20+
* derived from this software without specific prior written permission.
21+
*
22+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
25+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
26+
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
28+
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29+
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
30+
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33+
*/
34+
35+
var lodash = require('./lib_managed/lodash'),
36+
helpers = require('./lib/helpers'),
37+
object = typeof exports !== 'undefined' ? exports : this,
38+
windowAlias = window;
39+
40+
41+
object.errorManager = function (core) {
42+
43+
/**
44+
* Send error as self-describing event
45+
*
46+
* @param message string Message appeared in console
47+
* @param filename string Source file (not used)
48+
* @param lineno number Line number
49+
* @param colno number Column number (not used)
50+
* @param error Error error object (not present in all browsers)
51+
* @param contexts Array of custom contexts
52+
*/
53+
function track(message, filename, lineno, colno, error, contexts) {
54+
var stack = (error && error.stack) ? error.stack : null;
55+
56+
core.trackSelfDescribingEvent({
57+
schema: 'iglu:com.snowplowanalytics.snowplow/application_error/jsonschema/1-0-1',
58+
data: {
59+
programmingLanguage: "JAVASCRIPT",
60+
message: message || "JS Exception. Browser doesn't support ErrorEvent API",
61+
stackTrace: stack,
62+
lineNumber: lineno,
63+
lineColumn: colno,
64+
fileName: filename
65+
}
66+
}, contexts)
67+
}
68+
69+
/**
70+
* Attach custom contexts using `contextAdder`
71+
*
72+
*
73+
* @param contextsAdder function to get details from internal browser state
74+
* @returns {Array} custom contexts
75+
*/
76+
function sendError(errorEvent, commonContexts, contextsAdder) {
77+
var contexts;
78+
if (lodash.isFunction(contextsAdder)) {
79+
contexts = commonContexts.concat(contextsAdder(errorEvent));
80+
} else {
81+
contexts = commonContexts;
82+
}
83+
84+
track(errorEvent.message, errorEvent.filename, errorEvent.lineno, errorEvent.colno, errorEvent.error, contexts)
85+
}
86+
87+
return {
88+
89+
/**
90+
* Track unhandled exception.
91+
* This method supposed to be used inside try/catch block or with window.onerror
92+
* (contexts won't be attached), but NOT with `addEventListener` - use
93+
* `enableErrorTracker` for this
94+
*
95+
* @param message string Message appeared in console
96+
* @param filename string Source file (not used)
97+
* @param lineno number Line number
98+
* @param colno number Column number (not used)
99+
* @param error Error error object (not present in all browsers)
100+
* @param contexts Array of custom contexts
101+
*/
102+
trackError: track,
103+
104+
/**
105+
* Curried function to enable tracking of unhandled exceptions.
106+
* Listen for `error` event and
107+
*
108+
* @param filter Function ErrorEvent => Bool to check whether error should be tracker
109+
* @param contextsAdder Function ErrorEvent => Array<Context> to add custom contexts with
110+
* internal state based on particular error
111+
*/
112+
enableErrorTracking: function (filter, contextsAdder, contexts) {
113+
/**
114+
* Closure callback to filter, contextualize and track unhandled exceptions
115+
*
116+
* @param errorEvent ErrorEvent passed to event listener
117+
*/
118+
function captureError (errorEvent) {
119+
if (lodash.isFunction(filter) && filter(errorEvent) || filter == null) {
120+
sendError(errorEvent, contexts, contextsAdder)
121+
}
122+
}
123+
124+
helpers.addEventListener(windowAlias, 'error', captureError, true);
125+
}
126+
}
127+
};

src/js/tracker.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
sha1 = require('sha1'),
4444
links = require('./links'),
4545
forms = require('./forms'),
46+
errors = require('./errors'),
4647
requestQueue = require('./out_queue'),
4748
coreConstructor = require('snowplow-tracker-core').trackerCore,
4849
uuid = require('uuid'),
@@ -240,6 +241,9 @@
240241
// Manager for automatic form tracking
241242
formTrackingManager = forms.getFormTrackingManager(core, trackerId, addCommonContexts),
242243

244+
// Manager for tracking unhandled exceptions
245+
errorManager = errors.errorManager(core),
246+
243247
// Manager for local storage queue
244248
outQueueManager = new requestQueue.OutQueueManager(
245249
functionName,
@@ -2171,6 +2175,33 @@
21712175
currency: currency
21722176
}
21732177
});
2178+
},
2179+
2180+
/**
2181+
* Enable tracking of unhandled exceptions with custom contexts
2182+
*
2183+
* @param filter Function ErrorEvent => Bool to check whether error should be tracker
2184+
* @param contextsAdder Function ErrorEvent => Array<Context> to add custom contexts with
2185+
* internal state based on particular error
2186+
*/
2187+
enableErrorTracking: function (filter, contextsAdder) {
2188+
errorManager.enableErrorTracking(filter, contextsAdder, addCommonContexts())
2189+
},
2190+
2191+
/**
2192+
* Track unhandled exception.
2193+
* This method supposed to be used inside try/catch block
2194+
*
2195+
* @param message string Message appeared in console
2196+
* @param filename string Source file (not used)
2197+
* @param lineno number Line number
2198+
* @param colno number Column number (not used)
2199+
* @param error Error error object (not present in all browsers)
2200+
* @param contexts Array of custom contexts
2201+
*/
2202+
trackError: function (message, filename, lineno, colno, error, contexts) {
2203+
var enrichedContexts = addCommonContexts(contexts);
2204+
errorManager.trackError(message, filename, lineno, colno, error, enrichedContexts);
21742205
}
21752206
};
21762207
};

tests/integration/integration.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ define([
6565
}
6666

6767
/**
68-
* Check if expected payload was exists in `log`
68+
* Check if expected payload exists in `log`
6969
*/
7070
function checkExistenceOfExpectedQuerystring(expected) {
7171
function compare(e, other) { // e === expected
@@ -190,9 +190,12 @@ define([
190190

191191
'Check an unhandled exception was sent': function () {
192192
assert.isTrue(checkExistenceOfExpectedQuerystring({
193-
e: 'ue',
194-
ue_px: function (line) {
195-
return true;
193+
ue_px: function (ue) {
194+
var event = JSON.parse(decodeBase64(ue)).data;
195+
// We cannot test more because implementations vary much in old browsers (FF27,IE9)
196+
return (event.schema === 'iglu:com.snowplowanalytics.snowplow/application_error/jsonschema/1-0-1') &&
197+
(event.data.programmingLanguage === 'JAVASCRIPT') &&
198+
(event.data.message != null)
196199
}
197200
}));
198201
}

tests/pages/integration-template.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@
7474
// trackTrans sends the transaction to Snowplow tracking servers.
7575
// Must be called last to commit the transaction.
7676
window.snowplow('trackTrans');
77+
78+
// track unhandled exception
79+
window.snowplow("enableErrorTracking");
80+
function raiseException() { notExiststentObject.notExistentProperty(); }
81+
setTimeout(raiseException, 2500);
82+
7783
</script>
7884

7985
</body>

0 commit comments

Comments
 (0)