-
Notifications
You must be signed in to change notification settings - Fork 384
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
NEWRELIC-5279 refactored Error Collector to reduce complexity #1431
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,13 +5,12 @@ | |
|
||
'use strict' | ||
|
||
/* eslint sonarjs/cognitive-complexity: ["error", 25] -- TODO: https://issues.newrelic.com/browse/NEWRELIC-5252 */ | ||
|
||
const errorsModule = require('./index') | ||
|
||
const logger = require('../logger').child({ component: 'error_tracer' }) | ||
const urltils = require('../util/urltils') | ||
const Exception = require('../errors').Exception | ||
const Transaction = require('../transaction') | ||
const errorHelper = require('./helper') | ||
const createError = errorsModule.createError | ||
const createEvent = errorsModule.createEvent | ||
|
@@ -75,6 +74,7 @@ class ErrorCollector { | |
* | ||
* @param {?Transaction} transaction - | ||
* @param {Error} exception - The error to be checked. | ||
* @returns {boolean} whether or not the exception has already been tracked | ||
*/ | ||
_haveSeen(transaction, exception) { | ||
const txId = transaction ? transaction.id : 'Unknown' | ||
|
@@ -109,12 +109,78 @@ class ErrorCollector { | |
return false | ||
} | ||
|
||
/** | ||
* Helper method for processing errors that are created with .noticeError() | ||
* | ||
* @param {Transaction} transaction the collected exception's transaction | ||
* @param {number} collectedErrors the number of errors we've successfully .collect()-ed | ||
* @returns {number} the new number of errors successfully .collect()-ed | ||
*/ | ||
_processUserErrors(transaction, collectedErrors) { | ||
if (transaction.userErrors.length) { | ||
for (let i = 0; i < transaction.userErrors.length; i++) { | ||
const exception = transaction.userErrors[i] | ||
if (this.collect(transaction, exception)) { | ||
++collectedErrors | ||
} | ||
} | ||
} | ||
|
||
return collectedErrors | ||
} | ||
|
||
/** | ||
* Helper method for processing exceptions on the transaction (transaction.exceptions array) | ||
* | ||
* @param {Transaction} transaction the transaction being processed | ||
* @param {number} collectedErrors the number of errors successfully .collect()-ed | ||
* @param {number} expectedErrors the number of collected errors that were expected | ||
* @returns {Array.<number>} the updated [collectedErrors, expectedErrors] numbers post-processing | ||
*/ | ||
_processTransactionExceptions(transaction, collectedErrors, expectedErrors) { | ||
for (let i = 0; i < transaction.exceptions.length; i++) { | ||
const exception = transaction.exceptions[i] | ||
if (this.collect(transaction, exception)) { | ||
++collectedErrors | ||
// if we could collect it, then check if expected | ||
if ( | ||
urltils.isExpectedError(this.config, transaction.statusCode) || | ||
errorHelper.isExpectedException(transaction, exception.error, this.config, urltils) | ||
) { | ||
++expectedErrors | ||
} | ||
} | ||
} | ||
|
||
return [collectedErrors, expectedErrors] | ||
} | ||
|
||
/** | ||
* Helper method for processing an inferred error based on Transaction metadata | ||
* | ||
* @param {Transaction} transaction the transaction being processed | ||
* @param {number} collectedErrors the number of errors successfully .collect()-ed | ||
* @param {number} expectedErrors the number of collected errors that were expected | ||
* @returns {Array.<number>} the updated [collectedErrors, expectedErrors] numbers post-processing | ||
*/ | ||
_processTransactionErrors(transaction, collectedErrors, expectedErrors) { | ||
if (this.collect(transaction)) { | ||
++collectedErrors | ||
if (urltils.isExpectedError(this.config, transaction.statusCode)) { | ||
++expectedErrors | ||
} | ||
} | ||
|
||
return [collectedErrors, expectedErrors] | ||
} | ||
|
||
/** | ||
* Every finished transaction goes through this handler, so do as little as | ||
* possible. | ||
* | ||
* @param {Transaction} transaction | ||
* @returns {number} The number of unexpected errors | ||
* TODO: Prob shouldn't do any work if errors fully disabled. | ||
* | ||
* @param {Transaction} transaction the completed transaction | ||
*/ | ||
onTransactionFinished(transaction) { | ||
if (!transaction) { | ||
|
@@ -124,48 +190,30 @@ class ErrorCollector { | |
return | ||
} | ||
|
||
// TODO: Prob shouldn't do any work if errors fully disabled. | ||
|
||
// collect user errors even if status code is ignored | ||
let collectedErrors = 0 | ||
let expectedErrors = 0 | ||
|
||
// errors from noticeError are currently exempt from | ||
// ignore and exclude rules | ||
if (transaction.userErrors.length) { | ||
for (let i = 0; i < transaction.userErrors.length; i++) { | ||
const exception = transaction.userErrors[i] | ||
if (this.collect(transaction, exception)) { | ||
++collectedErrors | ||
} | ||
} | ||
} | ||
collectedErrors = this._processUserErrors(transaction, collectedErrors) | ||
|
||
const isErroredTransaction = urltils.isError(this.config, transaction.statusCode) | ||
const isIgnoredErrorStatusCode = urltils.isIgnoredError(this.config, transaction.statusCode) | ||
|
||
const isExpectedErrorStatusCode = urltils.isExpectedError(this.config, transaction.statusCode) | ||
|
||
// collect other exceptions only if status code is not ignored | ||
if (transaction.exceptions.length && !isIgnoredErrorStatusCode) { | ||
for (let i = 0; i < transaction.exceptions.length; i++) { | ||
const exception = transaction.exceptions[i] | ||
if (this.collect(transaction, exception)) { | ||
++collectedErrors | ||
// if we could collect it, then check if expected | ||
if ( | ||
isExpectedErrorStatusCode || | ||
errorHelper.isExpectedException(transaction, exception.error, this.config, urltils) | ||
) { | ||
++expectedErrors | ||
} | ||
} | ||
} | ||
} else if (isErroredTransaction && this.collect(transaction)) { | ||
++collectedErrors | ||
if (isExpectedErrorStatusCode) { | ||
++expectedErrors | ||
} | ||
;[collectedErrors, expectedErrors] = this._processTransactionExceptions( | ||
transaction, | ||
collectedErrors, | ||
expectedErrors | ||
) | ||
} else if (isErroredTransaction) { | ||
;[collectedErrors, expectedErrors] = this._processTransactionErrors( | ||
transaction, | ||
collectedErrors, | ||
expectedErrors | ||
) | ||
} | ||
|
||
const unexpectedErrors = collectedErrors - expectedErrors | ||
|
@@ -226,9 +274,8 @@ class ErrorCollector { | |
* NOTE: this interface is unofficial and may change in future. | ||
* | ||
* @param {?Transaction} transaction Transaction associated with the error. | ||
* @param {Exception} exception The Exception to be traced. | ||
* @param error | ||
* @param customAttributes | ||
* @param {*} error The error passed into `API#noticeError()` | ||
* @param {object} customAttributes custom attributes to add to the error | ||
*/ | ||
addUserError(transaction, error, customAttributes) { | ||
if (!error) { | ||
|
@@ -263,13 +310,38 @@ class ErrorCollector { | |
* | ||
* @param {?Transaction} transaction Transaction associated with the error. | ||
* @param {?Exception} exception The Exception object to be traced. | ||
* @returns {bool} True if the error was collected. | ||
* @returns {boolean} True if the error was collected. | ||
*/ | ||
collect(transaction, exception) { | ||
if (!exception) { | ||
exception = new Exception({}) | ||
collect(transaction, exception = new Exception({})) { | ||
if (!this._isValidException(exception, transaction)) { | ||
return false | ||
} | ||
|
||
const errorTrace = createError(transaction, exception, this.config) | ||
this._maybeRecordErrorMetrics(errorTrace, transaction) | ||
|
||
// defaults true in config/index. can be modified server-side | ||
if (this.config.collect_errors) { | ||
this.traceAggregator.add(errorTrace) | ||
} | ||
|
||
if (this.config.error_collector.capture_events === true) { | ||
const priority = (transaction && transaction.priority) || Math.random() | ||
const event = createEvent(transaction, errorTrace, exception.timestamp, this.config) | ||
this.eventAggregator.add(event, priority) | ||
} | ||
|
||
return true | ||
} | ||
|
||
/** | ||
* Helper method for ensuring that a collected exception/transaction combination can be collected | ||
* | ||
* @param {object} exception the exception to validate | ||
* @param {Transaction} transaction the Transaction to validate, if exception is malformed we'll try to fallback to transaction data | ||
* @returns {boolean} whether or not the exception/transaction combo has everything needed for processing | ||
*/ | ||
_isValidException(exception, transaction) { | ||
if (exception.error) { | ||
if (this._haveSeen(transaction, exception.error)) { | ||
return false | ||
|
@@ -288,10 +360,20 @@ class ErrorCollector { | |
|
||
if (exception.error) { | ||
logger.trace(exception.error, 'Got exception to trace:') | ||
} else { | ||
logger.trace(transaction, 'Got transaction error to trace:') | ||
bizob2828 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
const errorTrace = createError(transaction, exception, this.config) | ||
return true | ||
} | ||
|
||
/** | ||
* Helper method for recording metrics about errors depending on the type of error that happened | ||
* | ||
* @param {Array} errorTrace list of error information | ||
* @param {Transaction} transaction the transaction associated with the trace | ||
*/ | ||
_maybeRecordErrorMetrics(errorTrace, transaction) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. line 384 and 385 can be combined right? I thought we had a rule eslint rule to catch that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So we do have a rule, but it only kicks in if the logic doesn't change from the combining, in this case, the logic would change in that we would start logging "other" error metrics if the transaction doesn't exist. All that being said, I think it would be safe to combine regardless. Thoughts? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. never mind. this should stay. other has a special meaning within a transaction |
||
const isExpectedError = true === errorTrace[4].intrinsics['error.expected'] | ||
|
||
if (isExpectedError) { | ||
|
@@ -307,19 +389,6 @@ class ErrorCollector { | |
} | ||
} | ||
} | ||
|
||
// defaults true in config/index. can be modified server-side | ||
if (this.config.collect_errors) { | ||
this.traceAggregator.add(errorTrace) | ||
} | ||
|
||
if (this.config.error_collector.capture_events === true) { | ||
const priority = (transaction && transaction.priority) || Math.random() | ||
const event = createEvent(transaction, errorTrace, exception.timestamp, this.config) | ||
this.eventAggregator.add(event, priority) | ||
} | ||
|
||
return true | ||
} | ||
|
||
// TODO: ideally, this becomes unnecessary | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What are these semicolons doing here? Did eslint put them there?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since we do not use semi-colons, this is necessary for destructuring of let variables. The AST parser does not know how to handle it without it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, I don't think this is necessary. It's only needed when object destructuring
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is an AST parser issue but how ESLint parses code without semi colons