forked from GoogleCloudPlatform/stackdriver-errors-js
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstackdriver-errors.js
194 lines (175 loc) · 6.76 KB
/
stackdriver-errors.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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
/**
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var StackTrace = require('stacktrace-js');
/**
* URL endpoint of the Stackdriver Error Reporting report API.
*/
var baseAPIUrl = 'https://clouderrorreporting.googleapis.com/v1beta1/projects/';
/**
* An Error handler that sends errors to the Stackdriver Error Reporting API.
*/
var StackdriverErrorReporter = function() {};
/**
* Initialize the StackdriverErrorReporter object.
* @param {Object} config - the init configuration.
* @param {Object} [config.context={}] - the context in which the error occurred.
* @param {string} [config.context.user] - the user who caused or was affected by the error.
* @param {String} config.key - the API key to use to call the API.
* @param {String} config.projectId - the Google Cloud Platform project ID to report errors to.
* @param {String} [config.service=web] - service identifier.
* @param {String} [config.version] - version identifier.
* @param {Boolean} [config.reportUncaughtExceptions=true] - Set to false to stop reporting unhandled exceptions.
* @param {Boolean} [config.disabled=false] - Set to true to not report errors when calling report(), this can be used when developping locally.
*/
StackdriverErrorReporter.prototype.start = function(config) {
if (!config.key && !config.targetUrl) {
throw new Error('Cannot initialize: No API key or target url provided.');
}
if (!config.projectId && !config.targetUrl) {
throw new Error('Cannot initialize: No project ID or target url provided.');
}
this.apiKey = config.key;
this.projectId = config.projectId;
this.targetUrl = config.targetUrl;
this.context = config.context || {};
this.serviceContext = {service: config.service || 'web'};
if (config.version) {
this.serviceContext.version = config.version;
}
this.reportUncaughtExceptions = config.reportUncaughtExceptions !== false;
this.reportUnhandledPromiseRejections = config.reportUnhandledPromiseRejections !== false;
this.disabled = !!config.disabled;
registerHandlers(this);
};
function registerHandlers(reporter) {
// Register as global error handler if requested
var noop = function() {};
if (reporter.reportUncaughtExceptions) {
var oldErrorHandler = window.onerror || noop;
window.onerror = function(message, source, lineno, colno, error) {
if (error) {
reporter.report(error).catch(noop);
}
oldErrorHandler(message, source, lineno, colno, error);
return true;
};
}
if (reporter.reportUnhandledPromiseRejections) {
var oldPromiseRejectionHandler = window.onunhandledrejection || noop;
window.onunhandledrejection = function(promiseRejectionEvent) {
if (promiseRejectionEvent) {
reporter.report(promiseRejectionEvent.reason).catch(noop);
}
oldPromiseRejectionHandler(promiseRejectionEvent.reason);
return true;
};
}
}
/**
* Report an error to the Stackdriver Error Reporting API
* @param {Error|String} err - The Error object or message string to report.
* @param {Object} options - Configuration for this report.
* @param {number} [options.skipLocalFrames=1] - Omit number of frames if creating stack.
* @returns {Promise} A promise that completes when the report has been sent.
*/
StackdriverErrorReporter.prototype.report = function(err, options) {
if (this.disabled) {
return Promise.resolve(null);
}
if (!err) {
return Promise.reject(new Error('no error to report'));
}
options = options || {};
var payload = {};
payload.serviceContext = this.serviceContext;
payload.context = this.context;
payload.context.httpRequest = {
userAgent: window.navigator.userAgent,
url: window.location.href,
};
var firstFrameIndex = 0;
if (typeof err == 'string' || err instanceof String) {
// Transform the message in an error, use try/catch to make sure the stacktrace is populated.
try {
throw new Error(err);
} catch (e) {
err = e;
}
// the first frame when using report() is always this library
firstFrameIndex = options.skipLocalFrames || 1;
}
var reportUrl = this.targetUrl || (
baseAPIUrl + this.projectId + '/events:report?key=' + this.apiKey);
return resolveError(err, firstFrameIndex)
.then(function(message) {
payload.message = message;
return sendErrorPayload(reportUrl, payload);
});
};
function resolveError(err, firstFrameIndex) {
// This will use sourcemaps and normalize the stack frames
return StackTrace.fromError(err).then(function(stack) {
var lines = [err.toString()];
// Reconstruct to a JS stackframe as expected by Error Reporting parsers.
for (var s = firstFrameIndex; s < stack.length; s++) {
// Cannot use stack[s].source as it is not populated from source maps.
lines.push([
' at ',
// If a function name is not available '<anonymous>' will be used.
stack[s].getFunctionName() || '<anonymous>', ' (',
stack[s].getFileName(), ':',
stack[s].getLineNumber(), ':',
stack[s].getColumnNumber(), ')',
].join(''));
}
return lines.join('\n');
}, function(reason) {
// Failure to extract stacktrace
return [
'Error extracting stack trace: ', reason, '\n',
err.toString(), '\n',
' (', err.file, ':', err.line, ':', err.column, ')',
].join('');
});
}
function sendErrorPayload(url, payload) {
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
return new Promise(function(resolve, reject) {
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
var code = xhr.status;
if (code >= 200 && code < 300) {
resolve({message: payload.message});
} else {
var condition = code ? code + ' http response' : 'network error';
reject(new Error(condition + ' on stackdriver report'));
}
}
};
xhr.send(JSON.stringify(payload));
});
}
/**
* Set the user for the current context.
*
* @param {string} user - the unique identifier of the user (can be ID, email or custom token) or `undefined` if not logged in.
*/
StackdriverErrorReporter.prototype.setUser = function(user) {
this.context.user = user;
};
module.exports = StackdriverErrorReporter;