-
Notifications
You must be signed in to change notification settings - Fork 26
/
instrument.js
160 lines (139 loc) · 4.21 KB
/
instrument.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
var xhr = require('xhr')
var shoe = require('shoe')('/smokestack')
var slice = require('sliced')
var isDom = require('is-dom')
var format = require('util').format
var convert = require('convert-source-map')
var console = window.console
var SourceMap = require('source-map').SourceMapConsumer
var mapErrors = require('source-map-support')
var cachedSourceMap
var cachedBody
// keep around so can call
// console methods without sending data to server
var nativeConsole = {}
;['error'
, 'info'
, 'log'
, 'debug'
, 'warn'
].forEach(function(k) {
var nativeMethod = console[k]
nativeConsole[k] = nativeMethod.bind(console)
var prefix = k
console[k] = function() {
// keep original args so browser can log as usual
var args = slice(arguments)
write(prefix, args)
return nativeMethod.apply(this, args)
}
})
function write(prefix, args) {
// prepare args for transport
var cleanArgs = args.map(function(item) {
// no sensible default for stringifying
// DOM Elements nicely so just toString and let
// whoever is logging handle stringification.
if (item && isDom(item)) return item.toString()
return item
})
var output = format.apply(null, cleanArgs)
var data = JSON.stringify([prefix].concat(output))
shoe.write(data)
shoe.write('\n')
}
var close = window.close
window.close = function() {
setTimeout(function() {
shoe.write(JSON.stringify({ end: true }))
shoe.write('\n')
shoe.once('data', function() {
shoe.end()
})
})
}
shoe.on('end', function() {
close()
})
xhr('script.js', function(err, resp) {
if (err) return console.error(err)
// large sourcemaps will fail to parse, this is suprisingly common.
// so, we'll use the "large source map option", which is faster anyway
cachedBody = resp.body || ''
cachedSourceMap = convert.fromSource(cachedBody, true)
// magical auto-correction of error stack traces in v8
if (!cachedSourceMap) {
mapErrors.install({handleUncaughtExceptions: false})
} else {
mapErrors.install({
handleUncaughtExceptions: false
, retrieveSourceMap: function(source) {
return {
url: source
, map: cachedSourceMap && cachedSourceMap.sourcemap || ''
}
}
})
}
start()
})
window.onerror = function(message, filename, line, col, error) {
var supportsError = !!error
if (!supportsError) {
var error = new Error(message)
error.stack = 'Error: '+message+'\n at '+filename+':'+line+':'+col
}
error.fileName = error.fileName || filename
error.lineNumber = error.lineNumber || line
error.columnNumber = error.columnNumber || col
if (supportsError && cachedSourceMap) {
var sm = cachedSourceMap.toObject()
var smc = new SourceMap(sm)
var original = smc.originalPositionFor({line: error.lineNumber, column: error.columnNumber})
var source = smc.sourceContentFor(original.source)
logError(source, error, original)
}
else {
logError(cachedBody, error, {
source: filename
, line: line
, column: col
})
}
window.close()
}
function logError(src, error, position) {
var lines = src.trim().split('\n')
position = position || {
line: error.lineNumber,
column: error.columnNumber,
source: error.filename
}
var errorLine = '' + lines[position.line - 1]
var spaces = ''
// write in tap format so that test fail when there's an uncaught exception
write('error', ['not ok - ' + error.message])
write('error', [position.source + ':' + position.line])
for (var i = 0; i < position.column; i++) {
spaces += ' '
}
var columnLine = spaces + '^'
var errorLines = [errorLine, columnLine]
// write different data to remote console
// and window console.
write('error', [errorLines.join('\n') + '\n'])
// it's possible that we can fail to get a stack
try {
write('error', [error.stack || error.message])
} catch (e) {
write('error', ['Could not get stack trace because:', e.stack || e.message])
}
nativeConsole.error(error)
}
function start() {
// now that we've gotten the source map, we can run the tests
var script = document.createElement("script")
script.type = "text\/javascript"
document.body.appendChild(script)
script.src = 'script.js'
}