newrelic 5.10.0 → 5.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,9 +3,8 @@
3
3
  const errorsModule = require('./index')
4
4
  const EventAggregator = require('../event-aggregator')
5
5
  const logger = require('../logger').child({component: 'error_tracer'})
6
- const NAMES = require('../metrics/names')
7
6
  const urltils = require('../util/urltils')
8
-
7
+ const errorHelper = require('../errors/helper')
9
8
  const createError = errorsModule.createError
10
9
  const createEvent = errorsModule.createEvent
11
10
 
@@ -37,6 +36,11 @@ class ErrorAggregator extends EventAggregator {
37
36
  this.errorCount = 0
38
37
  this.webTransactionErrorCount = 0
39
38
  this.otherTransactionErrorCount = 0
39
+
40
+ this.expectedErrorCount = 0
41
+ this.expectedWebTransactionErrorCount = 0
42
+ this.expectedOtherTransactionErrorCount = 0
43
+
40
44
  this.errors = []
41
45
  this.seenObjectsByTransaction = Object.create(null)
42
46
  this.seenStringsByTransaction = Object.create(null)
@@ -47,17 +51,21 @@ class ErrorAggregator extends EventAggregator {
47
51
  * possible.
48
52
  *
49
53
  * @param {Transaction} transaction
50
- * @param {Metrics} metrics
54
+ *
55
+ * @return {number} The number of unexpected errors
51
56
  */
52
- onTransactionFinished(transaction, metrics) {
57
+ onTransactionFinished(transaction) {
53
58
  if (!transaction) throw new Error('Error collector got a blank transaction.')
54
- if (!metrics) throw new Error('Error collector requires metrics to count errors.')
55
59
  if (transaction.ignore) {
56
60
  return
57
61
  }
58
62
 
59
63
  // collect user errors even if status code is ignored
60
64
  let collectedErrors = 0
65
+ let expectedErrors = 0
66
+
67
+ // errors from noticeError are currently exempt from
68
+ // ignore and exclude rules
61
69
  if (transaction.userErrors.length) {
62
70
  for (let i = 0; i < transaction.userErrors.length; i++) {
63
71
  const exception = transaction.userErrors[i]
@@ -68,28 +76,38 @@ class ErrorAggregator extends EventAggregator {
68
76
  }
69
77
 
70
78
  const isErroredTransaction = urltils.isError(this.config, transaction.statusCode)
71
- const isIgnoredErrorStatusCode = urltils.isIgnoredError(
79
+ const isExpectedErrorStatusCode = urltils.isExpectedError(
72
80
  this.config,
73
81
  transaction.statusCode
74
82
  )
75
83
 
76
84
  // collect other exceptions only if status code is not ignored
77
- if (transaction.exceptions.length && !isIgnoredErrorStatusCode) {
85
+ if (transaction.exceptions.length) {
78
86
  for (let i = 0; i < transaction.exceptions.length; i++) {
79
87
  const exception = transaction.exceptions[i]
80
- if (this._collect(transaction, exception[0], exception[1], exception[2])) {
88
+ if (this.collect(transaction, exception[0], exception[1], exception[2])) {
81
89
  ++collectedErrors
90
+ // if we could collect it, then check if expected
91
+ if (isExpectedErrorStatusCode ||
92
+ errorHelper.isExpectedException(
93
+ transaction,
94
+ exception[0],
95
+ this.config,
96
+ urltils
97
+ )
98
+ ) {
99
+ ++expectedErrors
100
+ }
82
101
  }
83
102
  }
84
- } else if (isErroredTransaction && this._collect(transaction)) {
103
+ } else if (isErroredTransaction && this.collect(transaction)) {
85
104
  ++collectedErrors
105
+ if (isExpectedErrorStatusCode) {
106
+ ++expectedErrors
107
+ }
86
108
  }
87
109
 
88
- // the metric should be incremented only if the error was actually collected
89
- if (collectedErrors > 0) {
90
- metrics.getOrCreateMetric(NAMES.ERRORS.PREFIX + transaction.getFullName())
91
- .incrementCallCount(collectedErrors)
92
- }
110
+ return collectedErrors - expectedErrors
93
111
  }
94
112
 
95
113
  /**
@@ -115,7 +133,7 @@ class ErrorAggregator extends EventAggregator {
115
133
  if (transaction) {
116
134
  transaction.addException(exception, customAttributes, timestamp)
117
135
  } else {
118
- this._collect(transaction, exception, customAttributes, timestamp)
136
+ this.collect(transaction, exception, customAttributes, timestamp)
119
137
  }
120
138
  }
121
139
 
@@ -192,6 +210,34 @@ class ErrorAggregator extends EventAggregator {
192
210
  return false
193
211
  }
194
212
 
213
+ /**
214
+ * Wrapper for _collect, include logic for whether an error should
215
+ * be ignored or not. Exists to allow userErrors to bypass ignore
216
+ * logic.
217
+ *
218
+ * NOTE: this interface is unofficial and may change in future.
219
+ *
220
+ * @param {?Transaction} transaction
221
+ * Transaction associated with the error.
222
+ *
223
+ * @param {?Error} exception
224
+ * The error to be traced.
225
+ *
226
+ * @param {?object} customAttributes
227
+ * Any custom attributes associated with the request.
228
+ *
229
+ * @param {number} timestamp
230
+ *
231
+ * @return {bool} True if the error was collected.
232
+ */
233
+ collect(transaction, exception, customAttributes, timestamp) {
234
+ if (errorHelper.shouldIgnoreError(transaction, exception, this.config)) {
235
+ logger.trace("Ignoring error")
236
+ return
237
+ }
238
+ return this._collect(transaction, exception, customAttributes, timestamp)
239
+ }
240
+
195
241
  /**
196
242
  * Collects the error and also creates the error event.
197
243
  *
@@ -235,16 +281,6 @@ class ErrorAggregator extends EventAggregator {
235
281
  return
236
282
  }
237
283
 
238
- this.errorCount++
239
-
240
- if (transaction) {
241
- if (transaction.isWeb()) {
242
- this.webTransactionErrorCount++
243
- } else {
244
- this.otherTransactionErrorCount++
245
- }
246
- }
247
-
248
284
  // allow enabling & disabling the error tracer at runtime
249
285
  // TODO: it would be better to check config in the public add() to prevents collecting
250
286
  // errors on the transaction unnecessarily
@@ -260,8 +296,29 @@ class ErrorAggregator extends EventAggregator {
260
296
  logger.trace(exception, 'Got exception to trace:')
261
297
  }
262
298
 
299
+ this.errorCount++
300
+
301
+ if (transaction) {
302
+ if (transaction.isWeb()) {
303
+ this.webTransactionErrorCount++
304
+ } else {
305
+ this.otherTransactionErrorCount++
306
+ }
307
+ }
308
+
263
309
  var error = createError(transaction, exception, customAttributes, this.config)
264
310
 
311
+ if (true === error[4].intrinsics['error.expected'] ) {
312
+ this.expectedErrorCount++
313
+ if (transaction) {
314
+ if (transaction.isWeb()) {
315
+ this.expectedWebTransactionErrorCount++
316
+ } else {
317
+ this.expectedOtherTransactionErrorCount++
318
+ }
319
+ }
320
+ }
321
+
265
322
  if (this.errors.length < MAX_ERRORS) {
266
323
  logger.debug(error, 'Error to be sent to collector.')
267
324
  this.errors.push(error)
@@ -288,24 +345,32 @@ class ErrorAggregator extends EventAggregator {
288
345
  }
289
346
 
290
347
  /**
291
- * Returns total number of collected errors.
348
+ * Returns total number of errors that matched `expected_*` configuration.
349
+ * Expected errors do not impact standard error counts.
350
+ */
351
+ getTotalExpectedErrorCount() {
352
+ return this.expectedErrorCount
353
+ }
354
+
355
+ /**
356
+ * Returns total number of unexpected errors.
292
357
  */
293
- getTotalErrorCount() {
294
- return this.errorCount
358
+ getTotalUnexpectedErrorCount() {
359
+ return this.errorCount - this.expectedErrorCount
295
360
  }
296
361
 
297
362
  /**
298
- * Returns total number of errors collected during web transactions.
363
+ * Returns total number of unexpected errors collected during web transactions.
299
364
  */
300
- getWebTransactionsErrorCount() {
301
- return this.webTransactionErrorCount
365
+ getUnexpectedWebTransactionsErrorCount() {
366
+ return this.webTransactionErrorCount - this.expectedWebTransactionErrorCount
302
367
  }
303
368
 
304
369
  /**
305
- * Returns total number of errors collected during background transactions.
370
+ * Returns total number of unexpected errors collected during background transactions.
306
371
  */
307
- getOtherTransactionsErrorCount() {
308
- return this.otherTransactionErrorCount
372
+ getUnexpectedOtherTransactionsErrorCount() {
373
+ return this.otherTransactionErrorCount - this.expectedOtherTransactionErrorCount
309
374
  }
310
375
 
311
376
  /**
@@ -0,0 +1,137 @@
1
+ 'use strict'
2
+
3
+ module.exports = {
4
+ isExpected: function isExpected(type, message, transaction, config, urltils) {
5
+ let isExpectedTransactionCode = false
6
+ if (transaction && urltils.isExpectedError(config, transaction.statusCode)) {
7
+ isExpectedTransactionCode = true
8
+ }
9
+ return this.isExpectedErrorMessage(config, type, message) ||
10
+ this.isExpectedErrorClass(config, type) ||
11
+ isExpectedTransactionCode
12
+ },
13
+ isExpectedErrorMessage: function isExpectedErrorMessage(config, type, message) {
14
+ if (!config.error_collector.expected_messages[type]) {
15
+ return false
16
+ }
17
+ if (config.error_collector.expected_messages[type].length > 0) {
18
+ if (-1 !== config.error_collector.expected_messages[type].indexOf(message)) {
19
+ return true
20
+ }
21
+ }
22
+ return false
23
+ },
24
+ isExpectedErrorClass: function isExpectedErrorClass(config, className) {
25
+ if (config.error_collector.expected_classes.length > 0) {
26
+ if (-1 !== config.error_collector.expected_classes.indexOf(className)) {
27
+ return true
28
+ }
29
+ }
30
+ return false
31
+ },
32
+ isExpectedException: function isExpectedException(
33
+ transaction,
34
+ exception,
35
+ config,
36
+ urltils
37
+ ) {
38
+ let {type, message} = this.extractErrorInformation(
39
+ transaction,
40
+ exception,
41
+ config,
42
+ urltils
43
+ )
44
+
45
+ return this.isExpectedErrorClass(config, type) ||
46
+ this.isExpectedErrorMessage(config, type, message)
47
+ },
48
+
49
+ extractErrorInformation: function extractErrorInformation(
50
+ transaction,
51
+ exception,
52
+ config,
53
+ urltils
54
+ ) {
55
+ let name = 'Unknown'
56
+ let message = ''
57
+ let type = 'Error'
58
+
59
+ // String errors do not provide us with as much information to provide to the
60
+ // user, but it is a common pattern.
61
+ if (typeof exception === 'string') {
62
+ message = exception
63
+ } else if (
64
+ exception !== null &&
65
+ typeof exception === 'object' &&
66
+ exception.message &&
67
+ !config.high_security &&
68
+ !config.strip_exception_messages.enabled
69
+ ) {
70
+ message = exception.message
71
+
72
+ if (exception.name) {
73
+ type = exception.name
74
+ } else if (exception.constructor && exception.constructor.name) {
75
+ type = exception.constructor.name
76
+ }
77
+ } else if (transaction && transaction.statusCode &&
78
+ urltils.isError(config, transaction.statusCode)) {
79
+ message = 'HttpError ' + transaction.statusCode
80
+ }
81
+
82
+ if (transaction) {
83
+ // transaction.getName is expensive due to running normalizers and ignore
84
+ // rules if a name hasn't been assigned yet.
85
+ const txName = transaction.getFullName()
86
+ if (txName) {
87
+ name = txName
88
+ }
89
+ }
90
+
91
+ return {
92
+ name:name,
93
+ message:message,
94
+ type:type,
95
+ }
96
+ },
97
+
98
+ shouldIgnoreError: function shouldIgnoreError(transaction, exception, config) {
99
+ // extract _just_ the error information, not transaction stuff
100
+ let errorInfo = this.extractErrorInformation(null, exception, config, null)
101
+
102
+ return this.shouldIgnoreErrorClass(errorInfo, config) ||
103
+ this.shouldIgnoreErrorMessage(errorInfo, config) ||
104
+ this.shouldIgnoreStatusCode(transaction, config)
105
+ },
106
+
107
+ shouldIgnoreStatusCode: function shouldIgnoreStatusCode(transaction, config) {
108
+ if (!transaction) {
109
+ return false
110
+ }
111
+ return config.error_collector.ignore_status_codes.indexOf(
112
+ transaction.statusCode
113
+ ) !== -1
114
+ },
115
+
116
+ shouldIgnoreErrorClass: function shouldIgnoreErrorClass(errorInfo, config) {
117
+ if (config.error_collector.ignore_classes.length < 1) {
118
+ return false
119
+ }
120
+
121
+ return -1 !== config.error_collector.ignore_classes.indexOf(errorInfo.type)
122
+ },
123
+
124
+ shouldIgnoreErrorMessage: function shouldIgnoreErrorMessage(errorInfo, config) {
125
+ let configIgnoreMessages = config.error_collector.ignore_messages[errorInfo.type]
126
+ if (!configIgnoreMessages) {
127
+ return false
128
+ }
129
+
130
+ if (configIgnoreMessages.length > 0) {
131
+ if (-1 !== configIgnoreMessages.indexOf(errorInfo.message)) {
132
+ return true
133
+ }
134
+ }
135
+ return false
136
+ }
137
+ }
@@ -4,6 +4,7 @@ var DESTINATIONS = require('../config/attribute-filter').DESTINATIONS
4
4
  var NAMES = require('../metrics/names')
5
5
  var props = require('../util/properties')
6
6
  var urltils = require('../util/urltils')
7
+ const errorHelper = require('../errors/helper')
7
8
 
8
9
  module.exports.createError = createError
9
10
  module.exports.createEvent = createEvent
@@ -23,46 +24,20 @@ module.exports.createEvent = createEvent
23
24
  * the request (optional).
24
25
  */
25
26
  function createError(transaction, exception, customAttributes, config) {
26
- var name = 'Unknown'
27
- var message = ''
28
- var type = 'Error'
27
+ let {name, message, type} = errorHelper.extractErrorInformation(
28
+ transaction,
29
+ exception,
30
+ config,
31
+ urltils
32
+ )
33
+
29
34
  var params = {
30
35
  userAttributes: Object.create(null),
31
36
  agentAttributes: Object.create(null),
32
37
  intrinsics: Object.create(null)
33
38
  }
34
39
 
35
- // String errors do not provide us with as much information to provide to the
36
- // user, but it is a common pattern.
37
- if (typeof exception === 'string') {
38
- message = exception
39
- } else if (
40
- exception !== null &&
41
- typeof exception === 'object' &&
42
- exception.message &&
43
- !config.high_security &&
44
- !config.strip_exception_messages.enabled
45
- ) {
46
- message = exception.message
47
-
48
- if (exception.name) {
49
- type = exception.name
50
- } else if (exception.constructor && exception.constructor.name) {
51
- type = exception.constructor.name
52
- }
53
- } else if (transaction && transaction.statusCode &&
54
- urltils.isError(config, transaction.statusCode)) {
55
- message = 'HttpError ' + transaction.statusCode
56
- }
57
-
58
40
  if (transaction) {
59
- // transaction.getName is expensive due to running normalizers and ignore
60
- // rules if a name hasn't been assigned yet.
61
- var txName = transaction.getFullName()
62
- if (txName) {
63
- name = txName
64
- }
65
-
66
41
  // Copy all of the parameters off of the transaction.
67
42
  params.agentAttributes = transaction.trace.attributes.get(DESTINATIONS.ERROR_EVENT)
68
43
  params.intrinsics = transaction.getIntrinsicAttributes()
@@ -97,6 +72,11 @@ function createError(transaction, exception, customAttributes, config) {
97
72
  }
98
73
  }
99
74
 
75
+ params.intrinsics['error.expected'] = false
76
+ if (errorHelper.isExpected(type, message, transaction, config, urltils)) {
77
+ params.intrinsics['error.expected'] = true
78
+ }
79
+
100
80
  var res = [0, name, message, type, params]
101
81
  if (transaction) {
102
82
  res.transaction = transaction.id
@@ -117,6 +97,7 @@ function createEvent(transaction, error, timestamp, config) {
117
97
  transaction,
118
98
  errorClass,
119
99
  message,
100
+ errorParams.intrinsics['error.expected'],
120
101
  timestamp,
121
102
  config
122
103
  )
@@ -135,7 +116,15 @@ function createEvent(transaction, error, timestamp, config) {
135
116
  return errorEvent
136
117
  }
137
118
 
138
- function _getErrorEventIntrinsicAttrs(transaction, errorClass, message, timestamp, conf) {
119
+ // eslint-disable-next-line max-params
120
+ function _getErrorEventIntrinsicAttrs(
121
+ transaction,
122
+ errorClass,
123
+ message,
124
+ expected,
125
+ timestamp,
126
+ conf
127
+ ) {
139
128
  // the server expects seconds instead of milliseconds
140
129
  if (timestamp) timestamp = timestamp / 1000
141
130
 
@@ -143,7 +132,8 @@ function _getErrorEventIntrinsicAttrs(transaction, errorClass, message, timestam
143
132
  type: "TransactionError",
144
133
  "error.class": errorClass,
145
134
  "error.message": conf.high_security ? '' : message,
146
- timestamp: timestamp
135
+ timestamp: timestamp,
136
+ 'error.expected': expected
147
137
  }
148
138
 
149
139
  if (transaction) {
package/lib/harvest.js CHANGED
@@ -170,6 +170,9 @@ class HarvestStep {
170
170
  if (!err) {
171
171
  self.success = true
172
172
  }
173
+
174
+ logger.trace('Finished sending payloads for ' + self._endpoint)
175
+
173
176
  callback(err)
174
177
  })
175
178
  }
@@ -196,7 +199,7 @@ class HarvestStep {
196
199
  // Do we need to retry this endpoint right now?
197
200
  if (response.retryAfter) {
198
201
  const delay = response.retryAfter
199
- logger.debug('Retrying sending to %s in %d ms', self._endpoint, delay)
202
+ logger.info('Retrying sending to %s in %d ms', self._endpoint, delay)
200
203
  setTimeout(() => self._doSend(payload, i, callback), delay)
201
204
  return
202
205
  }
@@ -288,18 +291,24 @@ class ErrorTraceHarvest extends HarvestStep {
288
291
  const errorAggr = harvest.agent.errors
289
292
  super(harvest, 'errorData', errorAggr.getErrors())
290
293
 
294
+ const metrics = harvest.agent.metrics
295
+
291
296
  // Generate metrics for collected errors.
292
- if (errorAggr.getTotalErrorCount() > 0) {
293
- const metrics = harvest.agent.metrics
294
- let count = errorAggr.getTotalErrorCount()
297
+ if (errorAggr.getTotalUnexpectedErrorCount() > 0) {
298
+ let count = errorAggr.getTotalUnexpectedErrorCount()
295
299
  metrics.getOrCreateMetric(NAMES.ERRORS.ALL).incrementCallCount(count)
296
300
 
297
- count = errorAggr.getWebTransactionsErrorCount()
301
+ count = errorAggr.getUnexpectedWebTransactionsErrorCount()
298
302
  metrics.getOrCreateMetric(NAMES.ERRORS.WEB).incrementCallCount(count)
299
303
 
300
- count = errorAggr.getOtherTransactionsErrorCount()
304
+ count = errorAggr.getUnexpectedOtherTransactionsErrorCount()
301
305
  metrics.getOrCreateMetric(NAMES.ERRORS.OTHER).incrementCallCount(count)
302
306
  }
307
+
308
+ const expectedCount = errorAggr.getTotalExpectedErrorCount()
309
+ if (expectedCount > 0) {
310
+ metrics.getOrCreateMetric(NAMES.ERRORS.EXPECTED).incrementCallCount(expectedCount)
311
+ }
303
312
  }
304
313
 
305
314
  preparePayloadsSync(runId, errors) {
@@ -594,8 +603,9 @@ class Harvest {
594
603
  a.map(this._steps, function eachHarvestStep(step, cb) {
595
604
  logger.trace('Doing harvest step %s.', step.name)
596
605
  if (!self.agent.collector.isConnected()) {
597
- logger.debug('Connection to New Relic lost during harvest.')
598
- return setImmediate(callback, new Error('Not connected to New Relic!'))
606
+ const connectionLostMessage = 'Connection to New Relic lost during harvest.'
607
+ logger.warn(connectionLostMessage)
608
+ return setImmediate(callback, new Error(connectionLostMessage))
599
609
  }
600
610
  a.series([
601
611
  step.prepare.bind(step),
@@ -0,0 +1,3 @@
1
+ 'use strict'
2
+
3
+ module.exports = require('../hapi')
@@ -13,6 +13,7 @@ module.exports = function instrumentations() {
13
13
  'director': {type: MODULE_TYPE.WEB_FRAMEWORK},
14
14
  'express': {type: MODULE_TYPE.WEB_FRAMEWORK},
15
15
  'generic-pool': {type: MODULE_TYPE.GENERIC},
16
+ '@hapi/hapi': {type: MODULE_TYPE.WEB_FRAMEWORK},
16
17
  'hapi': {type: MODULE_TYPE.WEB_FRAMEWORK},
17
18
  'ioredis': {type: MODULE_TYPE.DATASTORE},
18
19
  'koa': {module: '@newrelic/koa'},
@@ -20,6 +20,7 @@ const SUPPORTABILITY = {
20
20
  const ERRORS = {
21
21
  PREFIX: 'Errors/',
22
22
  ALL: 'Errors/' + ALL,
23
+ EXPECTED: 'ErrorsExpected/' + ALL,
23
24
  WEB: 'Errors/allWeb',
24
25
  OTHER: 'Errors/allOther'
25
26
  }
package/lib/shimmer.js CHANGED
@@ -340,14 +340,14 @@ var shimmer = module.exports = {
340
340
  patchModule: function patchModule(agent) {
341
341
  logger.trace("Wrapping module loader.")
342
342
  var Module = require('module')
343
- var filepathStack = []
343
+ var filepathMap = {}
344
344
 
345
345
  shimmer.wrapMethod(Module, 'Module', '_resolveFilename', function wrapRes(resolve) {
346
- return function wrappedResolveFilename() {
346
+ return function wrappedResolveFilename(file) {
347
347
  // This is triggered by the load call, so record the path that has been seen so
348
348
  // we can examine it after the load call has returned.
349
349
  const resolvedFilepath = resolve.apply(this, arguments)
350
- filepathStack.push(resolvedFilepath)
350
+ filepathMap[file] = resolvedFilepath
351
351
  return resolvedFilepath
352
352
  }
353
353
  })
@@ -358,7 +358,7 @@ var shimmer = module.exports = {
358
358
  // load function calls resolve, we must mirror the recursive load calls with a
359
359
  // stack.
360
360
  const m = load.apply(this, arguments)
361
- return _postLoad(agent, m, file, filepathStack.pop())
361
+ return _postLoad(agent, m, file, filepathMap[file])
362
362
  }
363
363
  })
364
364
  },
@@ -1,5 +1,6 @@
1
1
  'use strict'
2
2
 
3
+ const errorHelper = require('../errors/helper')
3
4
  var hashes = require('../util/hashes')
4
5
  var logger = require('../logger').child({component: 'transaction'})
5
6
  var Metrics = require('../metrics')
@@ -603,7 +604,20 @@ Transaction.prototype.measure = function measure(name, scope, duration, exclusiv
603
604
  */
604
605
  Transaction.prototype._setApdex = function _setApdex(name, duration, keyApdexInMillis) {
605
606
  var apdexStats = this.metrics.getOrCreateApdexMetric(name, null, keyApdexInMillis)
606
- if (urltils.isError(this.agent.config, this.statusCode)) {
607
+
608
+
609
+ // if we have an error-like status code, and all the errors are
610
+ // expected, we know the status code was caused by an expected
611
+ // error, so we will not report "frustrating". Otherwise, we
612
+ // don't know which error triggered the error-like status code,
613
+ // and will still incrementing frustrating. If this is an issue,
614
+ // users can either set a status code as expected, or ignore the
615
+ // specific error to avoid incrementing to frustrating
616
+ if (
617
+ urltils.isError(this.agent.config, this.statusCode) &&
618
+ !urltils.isExpectedError(this.agent.config, this.statusCode) &&
619
+ !this.hasOnlyExpectedErrors()
620
+ ) {
607
621
  apdexStats.incrementFrustrating()
608
622
  } else {
609
623
  apdexStats.recordValueInMillis(duration, keyApdexInMillis)
@@ -687,6 +701,29 @@ Transaction.prototype.hasErrors = function _hasErrors() {
687
701
  return (transactionHasExceptions || transactionHasuserErrors || isErroredTransaction)
688
702
  }
689
703
 
704
+ /**
705
+ * Returns true if all the errors/exceptions collected so far
706
+ * are expected errors.
707
+ */
708
+ Transaction.prototype.hasOnlyExpectedErrors = function hasOnlyExpectedErrors() {
709
+ if (0 === this.exceptions.length) {
710
+ return false
711
+ }
712
+
713
+ for (let i = 0;i < this.exceptions.length;i++) {
714
+ const exception = this.exceptions[i]
715
+ if (!errorHelper.isExpectedException(
716
+ this,
717
+ exception[0],
718
+ this.agent.config,
719
+ urltils
720
+ )) {
721
+ return false
722
+ }
723
+ }
724
+ return true
725
+ }
726
+
690
727
  /**
691
728
  * Returns agent intrinsic attribute for this transaction.
692
729
  */
@@ -61,6 +61,19 @@ module.exports = {
61
61
  return code >= 400 && isIgnoredStatusCodeForErrors(config, code)
62
62
  },
63
63
 
64
+ /**
65
+ * Returns true if the status code is configured to be expected
66
+ *
67
+ * @param {Config} config The configuration containing the error list.
68
+ * @param {string} code The HTTP status code to check.
69
+ *
70
+ * @returns {bool} Whether the status code is expected.
71
+ *
72
+ */
73
+ isExpectedError: function isExpectedError(config, code) {
74
+ return isExpectedStatusCodeForErrors(config, code)
75
+ },
76
+
64
77
  /**
65
78
  * Get back the pieces of the URL that New Relic cares about. Apply these
66
79
  * restrictions, in order:
@@ -197,3 +210,13 @@ function isIgnoredStatusCodeForErrors(config, code) {
197
210
  }
198
211
  return codes.indexOf(parseInt(code, 10)) >= 0
199
212
  }
213
+
214
+ function isExpectedStatusCodeForErrors(config, code) {
215
+ var codes = []
216
+ if (config &&
217
+ config.error_collector &&
218
+ config.error_collector.expected_status_codes) {
219
+ codes = config.error_collector.expected_status_codes
220
+ }
221
+ return codes.indexOf(parseInt(code, 10)) >= 0
222
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "newrelic",
3
- "version": "5.10.0",
3
+ "version": "5.11.0",
4
4
  "author": "New Relic Node.js agent team <nodejs@newrelic.com>",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "contributors": [
@@ -154,8 +154,8 @@
154
154
  "generic-pool": "^3.6.1",
155
155
  "glob": "^7.1.2",
156
156
  "got": "^8.0.1",
157
- "jsdoc": "^3.4.0",
158
- "lodash": "^4.17.5",
157
+ "jsdoc": "^3.6.3",
158
+ "lodash": "^4.17.14",
159
159
  "memcached": ">=0.2.8",
160
160
  "minami": "^1.1.1",
161
161
  "mocha": "^5.2.0",