Skip to content

Commit ed198bb

Browse files
trevtrichsteren
authored andcommitted
Added ability to configure the url where the errors get sent (#29)
- This is primarily necessary to support web clients that would like to avoid using api keys.
1 parent d8f9ae8 commit ed198bb

3 files changed

Lines changed: 89 additions & 53 deletions

File tree

README.md

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Here's an introductory video:
1616

1717
1. You need a [Google Cloud project](https://console.cloud.google.com).
1818
1. [Enable the Stackdriver Error Reporting API](https://console.cloud.google.com/apis/api/clouderrorreporting.googleapis.com/overview) for your project. We highly recommend to restrict the usage of the key to your website URL only using an 'HTTP referrer' restriction.
19-
1. Create a browser API key: Follow [these instructions](https://support.google.com/cloud/answer/6158862) to get an API key for your project.
19+
1. Create a browser API key: Follow [these instructions](https://support.google.com/cloud/answer/6158862) to get an API key for your project. If this is not an option for your team, you can [use a custom url](#configuring-without-an-api-key) to send your errors to.
2020

2121
## Quickstart
2222

@@ -132,10 +132,25 @@ If you wish, you can manually delegate exceptions, e.g. `try { ... } catch(e) {
132132

133133
## Setup for ReactJS
134134

135-
Follow the general instructions denoted in _Setup for JavaScript_ to load and initialize the library.
135+
Follow the general instructions denoted in _Setup for JavaScript_ to load and initialize the library.
136136

137137
There is nothing specific that needs to be done with React, other than making sure to initialize the library in your root entry point(typically `index.js`).
138138

139+
## Configuring without an API key
140+
141+
If you are in a situation where an API key is not an option but you already have an acceptable way to communicate with the Stackdriver API (e.g., a secure back end service running in App Engine), you can configure the endpoint that errors are sent to with the following:
142+
143+
```javascript
144+
const errorHandler = new StackdriverErrorReporter();
145+
errorHandler.start({
146+
targetUrl: '<my-custom-url>',
147+
service: '<my-service>', // (optional)
148+
version: '<my-service-version>' // (optional)
149+
});
150+
```
151+
152+
where `targetUrl` is the url you'd like to send errors to and can be relative or absolute. This endpoint will need to support the [Report API endpoint](https://cloud.google.com/error-reporting/reference/rest/v1beta1/projects.events/report).
153+
139154
## Best Practices
140155

141156
### Only reporting in the production environment with Webpack
@@ -168,7 +183,7 @@ if (environment === 'production') {
168183
service: '<my-service>', // (optional)
169184
version: '<my-service-version>' // (optional)
170185
});
171-
}
186+
}
172187
```
173188

174189
### Usage as a utility

stackdriver-errors.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,16 @@
4040
* @param {Boolean} [config.disabled=false] - Set to true to not report errors when calling report(), this can be used when developping locally.
4141
*/
4242
StackdriverErrorReporter.prototype.start = function(config) {
43-
if(!config.key) {
44-
throw new Error('Cannot initialize: No API key provided.');
43+
if(!config.key && !config.targetUrl) {
44+
throw new Error('Cannot initialize: No API key or target url provided.');
4545
}
46-
if(!config.projectId) {
47-
throw new Error('Cannot initialize: No project ID provided.');
46+
if(!config.projectId && !config.targetUrl) {
47+
throw new Error('Cannot initialize: No project ID or target url provided.');
4848
}
4949

5050
this.apiKey = config.key;
5151
this.projectId = config.projectId;
52+
this.targetUrl = config.targetUrl;
5253
this.context = config.context || {};
5354
this.serviceContext = {service: config.service || 'web'};
5455
if(config.version) {
@@ -129,7 +130,8 @@
129130
};
130131

131132
StackdriverErrorReporter.prototype.sendErrorPayload = function(payload, callback) {
132-
var url = baseAPIUrl + this.projectId + "/events:report?key=" + this.apiKey;
133+
var defaultUrl = baseAPIUrl + this.projectId + "/events:report?key=" + this.apiKey;
134+
var url = this.targetUrl || defaultUrl;
133135

134136
var xhr = new XMLHttpRequest();
135137
xhr.open('POST', url, true);

test/test.js

Lines changed: 64 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,18 @@ describe('Initialization', function () {
6969
expect(errorHandler.reportUncaughtExceptions).to.equal(true);
7070
});
7171

72-
it('should fail if no API key', function () {
72+
it('should fail if no API key or custom url', function () {
7373
expect(function() {errorHandler.start({projectId:'projectId'});}).to.throw(Error, /API/);
7474
});
7575

76-
it('should fail if no project ID', function () {
76+
it('should fail if no project ID or custom url', function () {
7777
expect(function() {errorHandler.start({key:'key'});}).to.throw(Error, /project/);
7878
});
7979

80+
it('should succeed if custom target url provided without API key or project id', function () {
81+
expect(function() {errorHandler.start({targetUrl:'custom-url'});}).to.not.throw();
82+
});
83+
8084
it('should have default context', function () {
8185
errorHandler.start({key:'key', projectId:'projectId'});
8286
expect(errorHandler.context).to.eql({});
@@ -102,60 +106,75 @@ describe('Disabling', function () {
102106
});
103107

104108
describe('Reporting errors', function () {
105-
beforeEach(function() {
106-
errorHandler.start({key:'key', projectId:'projectId'});
107-
});
108-
109-
it('should report error messages with location', function (done) {
110-
var message = 'Something broke!';
111-
errorHandler.report(message, function() {
112-
expectRequestWithMessage(message);
113-
done();
109+
describe('Default configuration', function() {
110+
beforeEach(function() {
111+
errorHandler.start({key:'key', projectId:'projectId'});
114112
});
115-
});
116-
117113

118-
it('should extract and send stack traces from Errors', function (done) {
119-
var message = 'custom message';
120-
// PhantomJS only attaches a stack to thrown errors
121-
try {
122-
throw new TypeError(message);
123-
} catch(e) {
124-
errorHandler.report(e, function() {
114+
it('should report error messages with location', function (done) {
115+
var message = 'Something broke!';
116+
errorHandler.report(message, function() {
125117
expectRequestWithMessage(message);
126118
done();
127119
});
128-
}
129-
});
120+
});
130121

131-
it('should extract and send functionName in stack traces', function (done) {
132-
var message = 'custom message';
133-
// PhantomJS only attaches a stack to thrown errors
134-
try {
135-
throwError(message)
136-
} catch(e) {
137-
errorHandler.report(e, function() {
138-
expectRequestWithMessage('throwError');
139-
done();
140-
});
141-
}
122+
it('should extract and send stack traces from Errors', function (done) {
123+
var message = 'custom message';
124+
// PhantomJS only attaches a stack to thrown errors
125+
try {
126+
throw new TypeError(message);
127+
} catch(e) {
128+
errorHandler.report(e, function() {
129+
expectRequestWithMessage(message);
130+
done();
131+
});
132+
}
133+
});
134+
135+
it('should extract and send functionName in stack traces', function (done) {
136+
var message = 'custom message';
137+
// PhantomJS only attaches a stack to thrown errors
138+
try {
139+
throwError(message)
140+
} catch(e) {
141+
errorHandler.report(e, function() {
142+
expectRequestWithMessage('throwError');
143+
done();
144+
});
145+
}
146+
});
147+
148+
it('should set in stack traces when frame is anonymous', function (done) {
149+
var message = 'custom message';
150+
// PhantomJS only attaches a stack to thrown errors
151+
try {
152+
(function () {
153+
throw new TypeError(message);
154+
})()
155+
} catch(e) {
156+
errorHandler.report(e, function() {
157+
expectRequestWithMessage('<anonymous>');
158+
done();
159+
});
160+
}
161+
});
142162
});
143163

144-
it('should set in stack traces when frame is anonymous', function (done) {
145-
var message = 'custom message';
146-
// PhantomJS only attaches a stack to thrown errors
147-
try {
148-
(function () {
149-
throw new TypeError(message);
150-
})()
151-
} catch(e) {
152-
errorHandler.report(e, function() {
153-
expectRequestWithMessage('<anonymous>');
164+
describe('Custom target url configuration', function() {
165+
it('should report error messages with custom url config', function (done) {
166+
var targetUrl = 'config-uri-clouderrorreporting';
167+
errorHandler.start({targetUrl:targetUrl});
168+
169+
var message = 'Something broke!';
170+
errorHandler.report(message, function() {
171+
expectRequestWithMessage(message);
172+
expect(requests[0].url).to.equal(targetUrl);
173+
154174
done();
155175
});
156-
}
176+
});
157177
});
158-
159178
});
160179

161180
describe('Unhandled exceptions', function () {

0 commit comments

Comments
 (0)