errorreporter.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. // Copyright 2009 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview Definition of the ErrorReporter class, which creates an error
  16. * handler that reports any errors raised to a URL.
  17. *
  18. */
  19. goog.provide('goog.debug.ErrorReporter');
  20. goog.provide('goog.debug.ErrorReporter.ExceptionEvent');
  21. goog.require('goog.asserts');
  22. goog.require('goog.debug');
  23. goog.require('goog.debug.Error');
  24. goog.require('goog.debug.ErrorHandler');
  25. goog.require('goog.debug.entryPointRegistry');
  26. goog.require('goog.events');
  27. goog.require('goog.events.Event');
  28. goog.require('goog.events.EventTarget');
  29. goog.require('goog.log');
  30. goog.require('goog.net.XhrIo');
  31. goog.require('goog.object');
  32. goog.require('goog.string');
  33. goog.require('goog.uri.utils');
  34. goog.require('goog.userAgent');
  35. /**
  36. * Constructs an error reporter. Internal Use Only. To install an error
  37. * reporter see the {@see #install} method below.
  38. *
  39. * @param {string} handlerUrl The URL to which all errors will be reported.
  40. * @param {function(!Error, !Object<string, string>)=}
  41. * opt_contextProvider When a report is to be sent to the server,
  42. * this method will be called, and given an opportunity to modify the
  43. * context object before submission to the server.
  44. * @param {boolean=} opt_noAutoProtect Whether to automatically add handlers for
  45. * onerror and to protect entry points. If apps have other error reporting
  46. * facilities, it may make sense for them to set these up themselves and use
  47. * the ErrorReporter just for transmission of reports.
  48. * @constructor
  49. * @extends {goog.events.EventTarget}
  50. */
  51. goog.debug.ErrorReporter = function(
  52. handlerUrl, opt_contextProvider, opt_noAutoProtect) {
  53. goog.debug.ErrorReporter.base(this, 'constructor');
  54. /**
  55. * Context provider, if one was provided.
  56. * @type {?function(!Error, !Object<string, string>)}
  57. * @private
  58. */
  59. this.contextProvider_ = opt_contextProvider || null;
  60. /**
  61. * The string prefix of any optional context parameters logged with the error.
  62. * @private {string}
  63. */
  64. this.contextPrefix_ = 'context.';
  65. /**
  66. * The number of bytes after which the ErrorReporter truncates the POST body.
  67. * If null, the ErrorReporter won't truncate the body.
  68. * @private {?number}
  69. */
  70. this.truncationLimit_ = null;
  71. /**
  72. * Additional arguments to append to URL before sending XHR.
  73. * @private {!Object<string,string>}
  74. */
  75. this.additionalArguments_ = {};
  76. /**
  77. * XHR sender.
  78. * @type {function(string, string, string, (Object|goog.structs.Map)=)}
  79. * @private
  80. */
  81. this.xhrSender_ = goog.debug.ErrorReporter.defaultXhrSender;
  82. /**
  83. * The URL at which all errors caught by this handler will be logged.
  84. *
  85. * @type {string}
  86. * @private
  87. */
  88. this.handlerUrl_ = handlerUrl;
  89. if (goog.debug.ErrorReporter.ALLOW_AUTO_PROTECT) {
  90. if (!opt_noAutoProtect) {
  91. /**
  92. * The internal error handler used to catch all errors.
  93. *
  94. * @private {goog.debug.ErrorHandler}
  95. */
  96. this.errorHandler_ = null;
  97. this.setup_();
  98. }
  99. } else if (!opt_noAutoProtect) {
  100. goog.asserts.fail(
  101. 'opt_noAutoProtect cannot be false while ' +
  102. 'goog.debug.ErrorReporter.ALLOW_AUTO_PROTECT is false. Setting ' +
  103. 'ALLOW_AUTO_PROTECT to false removes the necessary auto-protect code ' +
  104. 'in compiled/optimized mode.');
  105. }
  106. };
  107. goog.inherits(goog.debug.ErrorReporter, goog.events.EventTarget);
  108. /**
  109. * @define {boolean} If true, the code that provides additional entry point
  110. * protection and setup is exposed in this file. Set to false to avoid
  111. * bringing in a lot of code from ErrorHandler and entryPointRegistry in
  112. * compiled mode.
  113. */
  114. goog.define('goog.debug.ErrorReporter.ALLOW_AUTO_PROTECT', true);
  115. /**
  116. * Event broadcast when an exception is logged.
  117. * @param {Error} error The exception that was was reported.
  118. * @param {!Object<string, string>} context The context values sent to the
  119. * server alongside this error.
  120. * @constructor
  121. * @extends {goog.events.Event}
  122. * @final
  123. */
  124. goog.debug.ErrorReporter.ExceptionEvent = function(error, context) {
  125. goog.events.Event.call(this, goog.debug.ErrorReporter.ExceptionEvent.TYPE);
  126. /**
  127. * The error that was reported.
  128. * @type {Error}
  129. */
  130. this.error = error;
  131. /**
  132. * Context values sent to the server alongside this report.
  133. * @type {!Object<string, string>}
  134. */
  135. this.context = context;
  136. };
  137. goog.inherits(goog.debug.ErrorReporter.ExceptionEvent, goog.events.Event);
  138. /**
  139. * Event type for notifying of a logged exception.
  140. * @type {string}
  141. */
  142. goog.debug.ErrorReporter.ExceptionEvent.TYPE =
  143. goog.events.getUniqueId('exception');
  144. /**
  145. * Extra headers for the error-reporting XHR.
  146. * @type {Object|goog.structs.Map|undefined}
  147. * @private
  148. */
  149. goog.debug.ErrorReporter.prototype.extraHeaders_;
  150. /**
  151. * Logging object.
  152. *
  153. * @type {goog.log.Logger}
  154. * @private
  155. */
  156. goog.debug.ErrorReporter.logger_ =
  157. goog.log.getLogger('goog.debug.ErrorReporter');
  158. /**
  159. * Installs an error reporter to catch all JavaScript errors raised.
  160. *
  161. * @param {string} loggingUrl The URL to which the errors caught will be
  162. * reported.
  163. * @param {function(!Error, !Object<string, string>)=}
  164. * opt_contextProvider When a report is to be sent to the server,
  165. * this method will be called, and given an opportunity to modify the
  166. * context object before submission to the server.
  167. * @param {boolean=} opt_noAutoProtect Whether to automatically add handlers for
  168. * onerror and to protect entry points. If apps have other error reporting
  169. * facilities, it may make sense for them to set these up themselves and use
  170. * the ErrorReporter just for transmission of reports.
  171. * @return {!goog.debug.ErrorReporter} The error reporter.
  172. */
  173. goog.debug.ErrorReporter.install = function(
  174. loggingUrl, opt_contextProvider, opt_noAutoProtect) {
  175. var instance = new goog.debug.ErrorReporter(
  176. loggingUrl, opt_contextProvider, opt_noAutoProtect);
  177. return instance;
  178. };
  179. /**
  180. * Default implementation of XHR sender interface.
  181. *
  182. * @param {string} uri URI to make request to.
  183. * @param {string} method Send method.
  184. * @param {string} content Post data.
  185. * @param {Object|goog.structs.Map=} opt_headers Map of headers to add to the
  186. * request.
  187. */
  188. goog.debug.ErrorReporter.defaultXhrSender = function(
  189. uri, method, content, opt_headers) {
  190. goog.net.XhrIo.send(uri, null, method, content, opt_headers);
  191. };
  192. /**
  193. * Installs exception protection for an entry point function in addition
  194. * to those that are protected by default.
  195. * Has no effect in IE because window.onerror is used for reporting
  196. * exceptions in that case.
  197. *
  198. * @this {goog.debug.ErrorReporter}
  199. * @param {Function} fn An entry point function to be protected.
  200. * @return {Function} A protected wrapper function that calls the entry point
  201. * function or null if the entry point could not be protected.
  202. */
  203. goog.debug.ErrorReporter.prototype.protectAdditionalEntryPoint =
  204. goog.debug.ErrorReporter.ALLOW_AUTO_PROTECT ? function(fn) {
  205. if (this.errorHandler_) {
  206. return this.errorHandler_.protectEntryPoint(fn);
  207. }
  208. return null;
  209. } : function(fn) {
  210. goog.asserts.fail(
  211. 'Cannot call protectAdditionalEntryPoint while ALLOW_AUTO_PROTECT ' +
  212. 'is false. If ALLOW_AUTO_PROTECT is false, the necessary ' +
  213. 'auto-protect code in compiled/optimized mode is removed.');
  214. return null;
  215. };
  216. if (goog.debug.ErrorReporter.ALLOW_AUTO_PROTECT) {
  217. /**
  218. * Sets up the error reporter.
  219. *
  220. * @private
  221. */
  222. goog.debug.ErrorReporter.prototype.setup_ = function() {
  223. if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('10')) {
  224. // Use "onerror" because caught exceptions in IE don't provide line
  225. // number.
  226. goog.debug.catchErrors(
  227. goog.bind(this.handleException, this), false, null);
  228. } else {
  229. // "onerror" doesn't work with FF2 or Chrome
  230. this.errorHandler_ =
  231. new goog.debug.ErrorHandler(goog.bind(this.handleException, this));
  232. this.errorHandler_.protectWindowSetTimeout();
  233. this.errorHandler_.protectWindowSetInterval();
  234. this.errorHandler_.protectWindowRequestAnimationFrame();
  235. goog.debug.entryPointRegistry.monitorAll(this.errorHandler_);
  236. }
  237. };
  238. }
  239. /**
  240. * Add headers to the logging url.
  241. * @param {Object|goog.structs.Map} loggingHeaders Extra headers to send
  242. * to the logging URL.
  243. */
  244. goog.debug.ErrorReporter.prototype.setLoggingHeaders = function(
  245. loggingHeaders) {
  246. this.extraHeaders_ = loggingHeaders;
  247. };
  248. /**
  249. * Set the function used to send error reports to the server.
  250. * @param {function(string, string, string, (Object|goog.structs.Map)=)}
  251. * xhrSender If provided, this will be used to send a report to the
  252. * server instead of the default method. The function will be given the URI,
  253. * HTTP method request content, and (optionally) request headers to be
  254. * added.
  255. */
  256. goog.debug.ErrorReporter.prototype.setXhrSender = function(xhrSender) {
  257. this.xhrSender_ = xhrSender;
  258. };
  259. /**
  260. * Handler for caught exceptions. Sends report to the LoggingServlet and
  261. * notifies any listeners.
  262. *
  263. * @param {Object} e The exception.
  264. * @param {!Object<string, string>=} opt_context Context values to optionally
  265. * include in the error report.
  266. */
  267. goog.debug.ErrorReporter.prototype.handleException = function(e, opt_context) {
  268. var error = /** @type {!Error} */ (goog.debug.normalizeErrorObject(e));
  269. // Construct the context, possibly from the one provided in the argument, and
  270. // pass it to the context provider if there is one.
  271. var context = opt_context ? goog.object.clone(opt_context) : {};
  272. if (this.contextProvider_) {
  273. try {
  274. this.contextProvider_(error, context);
  275. } catch (err) {
  276. goog.log.error(
  277. goog.debug.ErrorReporter.logger_,
  278. 'Context provider threw an exception: ' + err.message);
  279. }
  280. }
  281. // Truncate message to a reasonable length, since it will be sent in the URL.
  282. // The entire URL length historically needed to be 2,083 or less, so leave
  283. // some room for the rest of the URL.
  284. var message = error.message.substring(0, 1900);
  285. if (!(e instanceof goog.debug.Error) || e.reportErrorToServer) {
  286. this.sendErrorReport(
  287. message, error.fileName, error.lineNumber, error.stack, context);
  288. }
  289. try {
  290. this.dispatchEvent(
  291. new goog.debug.ErrorReporter.ExceptionEvent(error, context));
  292. } catch (ex) {
  293. // Swallow exception to avoid infinite recursion.
  294. }
  295. };
  296. /**
  297. * Sends an error report to the logging URL. This will not consult the context
  298. * provider, the report will be sent exactly as specified.
  299. *
  300. * @param {string} message Error description.
  301. * @param {string} fileName URL of the JavaScript file with the error.
  302. * @param {number} line Line number of the error.
  303. * @param {string=} opt_trace Call stack trace of the error.
  304. * @param {!Object<string, string>=} opt_context Context information to include
  305. * in the request.
  306. */
  307. goog.debug.ErrorReporter.prototype.sendErrorReport = function(
  308. message, fileName, line, opt_trace, opt_context) {
  309. try {
  310. // Create the logging URL.
  311. var requestUrl = goog.uri.utils.appendParams(
  312. this.handlerUrl_, 'script', fileName, 'error', message, 'line', line);
  313. if (!goog.object.isEmpty(this.additionalArguments_)) {
  314. requestUrl = goog.uri.utils.appendParamsFromMap(
  315. requestUrl, this.additionalArguments_);
  316. }
  317. var queryMap = {};
  318. queryMap['trace'] = opt_trace;
  319. // Copy context into query data map
  320. if (opt_context) {
  321. for (var entry in opt_context) {
  322. queryMap[this.contextPrefix_ + entry] = opt_context[entry];
  323. }
  324. }
  325. // Copy query data map into request.
  326. var queryData = goog.uri.utils.buildQueryDataFromMap(queryMap);
  327. // Truncate if truncationLimit set.
  328. if (goog.isNumber(this.truncationLimit_)) {
  329. queryData = queryData.substring(0, this.truncationLimit_);
  330. }
  331. // Send the request with the contents of the error.
  332. this.xhrSender_(requestUrl, 'POST', queryData, this.extraHeaders_);
  333. } catch (e) {
  334. var logMessage = goog.string.buildString(
  335. 'Error occurred in sending an error report.\n\n', 'script:', fileName,
  336. '\n', 'line:', line, '\n', 'error:', message, '\n', 'trace:',
  337. opt_trace);
  338. goog.log.info(goog.debug.ErrorReporter.logger_, logMessage);
  339. }
  340. };
  341. /**
  342. * @param {string} prefix The prefix to appear prepended to all context
  343. * variables in the error report body.
  344. */
  345. goog.debug.ErrorReporter.prototype.setContextPrefix = function(prefix) {
  346. this.contextPrefix_ = prefix;
  347. };
  348. /**
  349. * @param {?number} limit Size in bytes to begin truncating POST body. Set to
  350. * null to prevent truncation. The limit must be >= 0.
  351. */
  352. goog.debug.ErrorReporter.prototype.setTruncationLimit = function(limit) {
  353. goog.asserts.assert(
  354. !goog.isNumber(limit) || limit >= 0,
  355. 'Body limit must be valid number >= 0 or null');
  356. this.truncationLimit_ = limit;
  357. };
  358. /**
  359. * @param {!Object<string,string>} urlArgs Set of key-value pairs to append
  360. * to handlerUrl_ before sending XHR.
  361. */
  362. goog.debug.ErrorReporter.prototype.setAdditionalArguments = function(urlArgs) {
  363. this.additionalArguments_ = urlArgs;
  364. };
  365. /** @override */
  366. goog.debug.ErrorReporter.prototype.disposeInternal = function() {
  367. if (goog.debug.ErrorReporter.ALLOW_AUTO_PROTECT) {
  368. goog.dispose(this.errorHandler_);
  369. }
  370. goog.debug.ErrorReporter.base(this, 'disposeInternal');
  371. };