Skip to content

Commit 1405ee8

Browse files
committed
Add StackTrace.report() for easy stack reporting.
1 parent 7d9ffda commit 1405ee8

File tree

3 files changed

+97
-11
lines changed

3 files changed

+97
-11
lines changed

README.md

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ StackTrace.deinstrument(interestingFn)
6767
```
6868
npm install stacktrace-js
6969
bower install stacktrace-js
70-
https://cdnjs.cloudflare.com/ajax/libs/stacktrace.js/1.0.0/stacktrace.min.js
70+
component install stacktracejs/stacktrace.js
71+
http://cdnjs.com/libraries/stacktrace.js
7172
```
7273

7374
## API
@@ -99,18 +100,35 @@ Use [stack-generator](https://github.com/stacktracejs/stack-generator) to genera
99100
* *sourceCache: Object (String URL => String Source)* - Pre-populate source cache to avoid network requests
100101
* *offline: Boolean (default: false)* - Set to `true` to prevent all network requests
101102

102-
#### `StackTrace.instrument(fn, callback, /*optional*/ errback)` => Boolean
103-
Call callback with a _stack trace_ anytime `fn` is called. Returns `true` if given Function is successfully instrumented
103+
#### `StackTrace.instrument(fn, callback, /*optional*/ errback)` => Function
104+
* Given a function, wrap it such that invocations trigger a callback that is called with a stack trace.
104105

105106
* **fn: Function** - to wrap, call callback on invocation and call-through
106107
* **callback: Function** - to call with stack trace (generated by `StackTrace.get()`) when fn is called
107108
* **(Optional) errback: Function** - to call with Error object if there was a problem getting a stack trace.
108109
Fails silently (though `fn` is still called) if a stack trace couldn't be generated.
109110

110-
#### `StackTrace.deinstrument(fn)` => Boolean
111-
Remove StackTrace instrumentation on `fn`. Returns `true` if de-instrumentation succeeds.
111+
#### `StackTrace.deinstrument(fn)` => Function
112+
Given a function that has been instrumented, revert the function to it's original (non-instrumented) state.
112113

113-
* **fn: Function** - Previously wrapped Function
114+
* **fn: Function** - Instrumented Function
115+
116+
#### `StackTrace.report(stackframes, url)` => [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)(String)
117+
Given an Array of StackFrames, serialize and POST to given URL. Promise is resolved with response text from POST request.
118+
119+
Example JSON POST data:
120+
```
121+
{
122+
stack: [
123+
{functionName: 'fn', fileName: 'file.js', lineNumber: 32, columnNumber: 1},
124+
{functionName: 'fn2', fileName: 'file.js', lineNumber: 543, columnNumber: 32},
125+
{functionName: 'fn3', fileName: 'file.js', lineNumber: 8, columnNumber: 1}
126+
]
127+
}
128+
```
129+
130+
* **stackframes: Array([StackFrame](https://github.com/stacktracejs/stackframe))** - Previously wrapped Function
131+
* **url: String** - URL to POST stack JSON to
114132

115133
## Browser Support
116134
* Chrome 1+

spec/stacktrace-spec.js

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ describe('StackTrace', function () {
2424
describe('#get', function () {
2525
it('gets stacktrace from current location', function () {
2626
runs(function testStackTraceGet() {
27-
StackTrace.get().then(callback, errback)['catch'](debugErrback);
27+
StackTrace.get().then(callback, errback)['catch'](errback);
2828
});
2929
waits(100);
3030
runs(function () {
@@ -60,7 +60,7 @@ describe('StackTrace', function () {
6060
runs(function () {
6161
server.respondWith('GET', 'http://path/to/file.js', [404, {'Content-Type': 'text/plain'}, '']);
6262
StackTrace.fromError(Errors.IE_11)
63-
.then(callback, debugErrback)['catch'](debugErrback);
63+
.then(callback, errback)['catch'](errback);
6464
server.respond();
6565
});
6666
waits(100);
@@ -81,7 +81,7 @@ describe('StackTrace', function () {
8181

8282
server.respondWith('GET', 'http://path/to/file.js', [404, {'Content-Type': 'text/plain'}, '']);
8383
StackTrace.fromError(Errors.IE_11, {filter: onlyFoos})
84-
.then(callback, debugErrback)['catch'](debugErrback);
84+
.then(callback, errback)['catch'](errback);
8585
server.respond();
8686
});
8787
waits(100);
@@ -103,7 +103,7 @@ describe('StackTrace', function () {
103103
server.respondWith('GET', 'test.js.map', [200, {'Content-Type': 'application/json'}, sourceMap]);
104104

105105
var stack = 'TypeError: Unable to get property \'undef\' of undefined or null reference\n at foo (http://path/to/file.js:45:13)';
106-
StackTrace.fromError({stack: stack}).then(callback, errback)['catch'](debugErrback);
106+
StackTrace.fromError({stack: stack}).then(callback, errback)['catch'](errback);
107107
server.respond();
108108
});
109109
waits(100);
@@ -129,7 +129,7 @@ describe('StackTrace', function () {
129129
stackFrame.getFunctionName().indexOf('testGenerateArtificially') > -1;
130130
};
131131
StackTrace.generateArtificially({filter: stackFrameFilter})
132-
.then(callback, errback)['catch'](debugErrback);
132+
.then(callback, errback)['catch'](errback);
133133
});
134134
waits(100);
135135
runs(function () {
@@ -209,4 +209,47 @@ describe('StackTrace', function () {
209209
expect(unwrapped).toEqual(interestingFn);
210210
});
211211
});
212+
213+
describe('#report', function () {
214+
var server;
215+
beforeEach(function () {
216+
server = sinon.fakeServer.create();
217+
});
218+
afterEach(function () {
219+
server.restore();
220+
});
221+
222+
it('sends POST request to given URL', function () {
223+
var url = 'http://domain.ext/endpoint';
224+
var stackframes = [new StackFrame('fn', undefined, 'file.js', 32, 1)];
225+
226+
runs(function () {
227+
server.respondWith('POST', url, [201, {'Content-Type': 'text/plain'}, 'OK']);
228+
StackTrace.report(stackframes, url).then(callback, errback)['catch'](errback);
229+
server.respond();
230+
});
231+
waits(100);
232+
runs(function () {
233+
expect(server.requests[0].requestBody).toEqual({stack: stackframes});
234+
expect(server.requests[0].url).toEqual(url);
235+
expect(callback).toHaveBeenCalledWith('OK');
236+
expect(errback).not.toHaveBeenCalled();
237+
});
238+
});
239+
240+
it('rejects if POST request fails', function () {
241+
runs(function () {
242+
var url = 'http://domain.ext/endpoint';
243+
var stackframes = [new StackFrame('fn', undefined, 'file.js', 32, 1)];
244+
server.respondWith('POST', url, [404, {'Content-Type': 'text/plain'}, '']);
245+
StackTrace.report(stackframes, url).then(callback, errback)['catch'](errback);
246+
server.respond();
247+
});
248+
waits(100);
249+
runs(function () {
250+
expect(callback).not.toHaveBeenCalled();
251+
expect(errback).toHaveBeenCalled();
252+
});
253+
});
254+
});
212255
});

stacktrace.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,31 @@
155155
// Function not instrumented, return original
156156
return fn;
157157
}
158+
},
159+
160+
/**
161+
* Given an Array of StackFrames, serialize and POST to given URL.
162+
*
163+
* @param stackframes - Array[StackFrame]
164+
* @param url - URL as String
165+
*/
166+
report: function StackTrace$$report(stackframes, url) {
167+
return new Promise(function (resolve, reject) {
168+
var req = new XMLHttpRequest();
169+
req.onerror = reject;
170+
req.onreadystatechange = function onreadystatechange() {
171+
if (req.readyState === 4) {
172+
if (req.status >= 200 && req.status < 400) {
173+
resolve(req.responseText);
174+
} else {
175+
reject(new Error('POST to ' + url + ' failed with status: ' + req.status));
176+
}
177+
}
178+
};
179+
req.open('post', url);
180+
req.setRequestHeader('Content-Type', 'application/json');
181+
req.send({stack: stackframes});
182+
});
158183
}
159184
};
160185
}));

0 commit comments

Comments
 (0)