PageRenderTime 40ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/node_modules/newrelic/lib/error.js

https://github.com/andrew-m/thoughtworks-email-signature-generator
JavaScript | 226 lines | 108 code | 22 blank | 96 comment | 31 complexity | 7aac6ae4e42aa95f37b910d3a1937d9b MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, MIT, 0BSD, JSON
  1. 'use strict';
  2. var path = require('path')
  3. , urltils = require(path.join(__dirname, 'util', 'urltils'))
  4. , logger = require(path.join(__dirname, 'logger')).child({component : 'error_tracer'})
  5. , NAMES = require(path.join(__dirname, 'metrics', 'names'))
  6. ;
  7. /*
  8. *
  9. * CONSTANTS
  10. *
  11. */
  12. var MAX_ERRORS = 20;
  13. /**
  14. * Given either or both of a transaction and an exception, generate an error
  15. * trace in the JSON format expected by the collector. Since this will be
  16. * used by both the HTTP instrumentation, which uses HTTP status codes to
  17. * determine whether a transaction is in error, and the domain-based error
  18. * handler, which traps actual instances of Error, try to set sensible
  19. * defaults for everything.
  20. *
  21. * @param {Transaction} transaction The agent transaction, presumably
  22. * coming out of the instrumentation.
  23. * @param {Error} exception Something trapped by an error listener.
  24. * @param {object} customParameters Any custom parameters associated with
  25. * the request (optional).
  26. */
  27. function createError(transaction, exception, customParameters, config) {
  28. // the collector throws this out, so don't bother setting it
  29. var timestamp = 0
  30. , name = 'WebTransaction/Uri/*'
  31. , message = ''
  32. , type = 'Error'
  33. , params = {
  34. userAttributes : {},
  35. agentAttributes : {},
  36. intrinsics : {},
  37. }
  38. ;
  39. if (transaction && transaction.name) name = transaction.name;
  40. // NB: anything throwing / emitting strings is buggy, but it happens
  41. if (typeof exception === 'string') {
  42. message = exception;
  43. }
  44. else if (exception && exception.message) {
  45. message = exception.message;
  46. // only care about extracting the type if it's Error-like.
  47. if (exception && exception.constructor && exception.constructor.name) {
  48. type = exception.constructor.name;
  49. }
  50. }
  51. else if (transaction &&
  52. transaction.statusCode &&
  53. urltils.isError(config, transaction.statusCode)) {
  54. message = 'HttpError ' + transaction.statusCode;
  55. }
  56. if (transaction && transaction.url) {
  57. var url = transaction.url
  58. , statusCode = transaction.statusCode || 500
  59. ;
  60. /* We need a name for this transaction, but since error-tracing can happen
  61. * in the middle of the request, and it's possible that the user will
  62. * recover from the error, name the transaction now, preserving the
  63. * necessary state to maintain any user-provided naming information.
  64. */
  65. if (!transaction.name) {
  66. var partialName = transaction.partialName;
  67. transaction.setName(url, statusCode);
  68. transaction.partialName = partialName;
  69. }
  70. var custom = transaction.getTrace().custom;
  71. Object.keys(custom).forEach(function (param) {
  72. params.userAttributes[param] = custom[param];
  73. });
  74. name = transaction.name;
  75. params.request_uri = url;
  76. if (config.capture_params) {
  77. var reqParams = transaction.getTrace().parameters;
  78. var urlParams = urltils.parseParameters(url);
  79. // clear out ignored params
  80. config.ignored_params.forEach(function cb_forEach(k) {
  81. // polymorphic hidden classes aren't an issue with data bags
  82. delete urlParams[k];
  83. delete reqParams[k];
  84. });
  85. Object.keys(urlParams).forEach(function (param) {
  86. params.agentAttributes[param] = urlParams[param];
  87. });
  88. Object.keys(reqParams).forEach(function (param) {
  89. params.agentAttributes[param] = reqParams[param];
  90. });
  91. }
  92. }
  93. // this needs to be done *after* pulling custom params from transaction
  94. var isHighSec = config.high_security;
  95. if (customParameters && !isHighSec) {
  96. var ignored = [];
  97. if (transaction) ignored = config.ignored_params;
  98. Object.keys(customParameters).forEach(function cb_forEach(param) {
  99. if (ignored.indexOf(param) === -1) params.userAttributes[param] = customParameters[param];
  100. });
  101. }
  102. var stack = exception && exception.stack;
  103. // FIXME: doing this work should not be the agent's responsibility
  104. if (stack) params.stack_trace = ('' + stack).split(/[\n\r]/g);
  105. return [timestamp, name, message, type, params];
  106. }
  107. /**
  108. * This is a fairly simple-minded tracer that converts errored-out HTTP
  109. * transactions and JS Errors into the error traces expected by the collector.
  110. *
  111. * It also acts as a collector for the traced errors.
  112. */
  113. function ErrorTracer(config) {
  114. this.config = config;
  115. this.errorCount = 0;
  116. this.errors = [];
  117. this.seen = [];
  118. }
  119. /**
  120. * Every finished transaction goes through this handler, so do as
  121. * little as possible.
  122. */
  123. ErrorTracer.prototype.onTransactionFinished = function onTransactionFinished(transaction, metrics) {
  124. if (!transaction) throw new Error("Error collector got a blank transaction.");
  125. if (!metrics) throw new Error("Error collector requires metrics to count errors.");
  126. if (urltils.isError(this.config, transaction.statusCode) ||
  127. (transaction.statusCode < 1 && transaction.exceptions.length > 0)) {
  128. if (transaction.exceptions.length > 0) {
  129. transaction.exceptions.forEach(function cb_forEach(exception) {
  130. this.add(transaction, exception);
  131. }, this);
  132. }
  133. else {
  134. this.add(transaction);
  135. }
  136. var count = metrics.getOrCreateMetric(NAMES.ERRORS.PREFIX + transaction.name);
  137. count.incrementCallCount(1);
  138. }
  139. };
  140. /**
  141. * This function uses an array of seen exceptions to ensure errors don't get
  142. * double-counted. It can also be used as an unofficial means of marking that
  143. * user errors shouldn't be traced.
  144. *
  145. * For an error to be traced, at least one of the transaction or the error
  146. * must be present.
  147. *
  148. * NOTE: this interface is unofficial and may change in future.
  149. *
  150. * @param {Transaction} transaction Transaction associated with the error
  151. * (optional).
  152. * @param {Error} exception The error to be traced (optional).
  153. * @param {object} customParameters Any custom parameters associated with
  154. * the request (optional).
  155. */
  156. ErrorTracer.prototype.add = function add(transaction, exception, customParameters) {
  157. if (!exception) {
  158. if (!transaction) return;
  159. if (!transaction.statusCode) return;
  160. if (transaction.error) return;
  161. }
  162. else {
  163. if (this.seen.indexOf(exception) !== -1) return;
  164. if (typeof exception !== 'string' && !exception.message && !exception.stack) {
  165. return;
  166. }
  167. }
  168. this.errorCount++;
  169. // allow enabling & disabling the error tracer at runtime
  170. if (!this.config.collect_errors ||
  171. !this.config.error_collector || !this.config.error_collector.enabled) return;
  172. if (exception) {
  173. logger.trace(exception, "Got exception to trace:");
  174. // put the error on the transaction to show we've already traced it
  175. if (transaction) transaction.error = exception;
  176. this.seen.push(exception);
  177. }
  178. if (this.errors.length < MAX_ERRORS) {
  179. var error = createError(transaction, exception, customParameters, this.config);
  180. logger.debug({error : error}, "Error to be sent to collector:");
  181. this.errors.push(error);
  182. }
  183. else {
  184. logger.debug("Already have %d errors to send to collector, not keeping.",
  185. MAX_ERRORS);
  186. }
  187. };
  188. /**
  189. * If the connection to the collector fails, retain as many as will fit without
  190. * overflowing the current error list.
  191. *
  192. * @param array errors Previously harvested errors.
  193. */
  194. ErrorTracer.prototype.merge = function merge(errors) {
  195. if (!errors) return;
  196. var len = Math.min(errors.length, MAX_ERRORS - this.errors.length);
  197. logger.warn("Merging %s (of %s) errors for next delivery.", len, errors.length);
  198. for (var i = 0; i < len; i++) this.errors.push(errors[i]);
  199. };
  200. module.exports = ErrorTracer;