newrelic 5.8.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.
package/lib/config/env.js CHANGED
@@ -36,7 +36,10 @@ const ENV_MAPPING = {
36
36
  enabled: 'NEW_RELIC_ERROR_COLLECTOR_ATTRIBUTES_ENABLED'
37
37
  },
38
38
  enabled: 'NEW_RELIC_ERROR_COLLECTOR_ENABLED',
39
- ignore_status_codes: 'NEW_RELIC_ERROR_COLLECTOR_IGNORE_ERROR_CODES'
39
+ ignore_status_codes: 'NEW_RELIC_ERROR_COLLECTOR_IGNORE_ERROR_CODES',
40
+ ignore_classes: 'NEW_RELIC_ERROR_COLLECTOR_IGNORE_ERRORS',
41
+ expected_status_codes: 'NEW_RELIC_ERROR_COLLECTOR_EXPECTED_ERROR_CODES',
42
+ expected_classes: 'NEW_RELIC_ERROR_COLLECTOR_EXPECTED_ERRORS',
40
43
  },
41
44
  strip_exception_messages: {
42
45
  enabled: 'NEW_RELIC_STRIP_EXCEPTION_MESSAGES_ENABLED'
@@ -147,6 +150,9 @@ const LIST_VARS = new Set([
147
150
  'NEW_RELIC_ATTRIBUTES_EXCLUDE',
148
151
  'NEW_RELIC_ATTRIBUTES_INCLUDE',
149
152
  'NEW_RELIC_ERROR_COLLECTOR_IGNORE_ERROR_CODES',
153
+ 'NEW_RELIC_ERROR_COLLECTOR_IGNORE_ERRORS',
154
+ 'NEW_RELIC_ERROR_COLLECTOR_EXPECTED_ERROR_CODES',
155
+ 'NEW_RELIC_ERROR_COLLECTOR_EXPECTED_ERRORS',
150
156
  'NEW_RELIC_ERROR_COLLECTOR_ATTRIBUTES_EXCLUDE',
151
157
  'NEW_RELIC_ERROR_COLLECTOR_ATTRIBUTES_INCLUDE',
152
158
  'NEW_RELIC_TRANSACTION_TRACER_ATTRIBUTES_EXCLUDE',
@@ -2,6 +2,7 @@
2
2
 
3
3
  const AttributeFilter = require('./attribute-filter')
4
4
  const CollectorResponse = require('../collector/response')
5
+ const MergeServerConfig = require('./merge-server-config')
5
6
  const copy = require('../util/copy')
6
7
  const defaultConfig = require('./default').config
7
8
  const EventEmitter = require('events').EventEmitter
@@ -31,9 +32,11 @@ const CONFIG_FILE_LOCATIONS = [
31
32
  path.join(__dirname, '../../../..') // above node_modules
32
33
  ]
33
34
 
34
- const HAS_ARBITRARY_KEYS = [
35
+ const HAS_ARBITRARY_KEYS = new Set([
36
+ 'ignore_messages',
37
+ 'expected_messages',
35
38
  'labels'
36
- ]
39
+ ])
37
40
 
38
41
  const LASP_MAP = require('./lasp').LASP_MAP
39
42
  const ENV = require('./env')
@@ -72,7 +75,7 @@ function fromObjectList(setting) {
72
75
  try {
73
76
  return JSON.parse('[' + setting + ']')
74
77
  } catch (error) {
75
- logger.error("New Relic configurator could not deserialize object list:")
78
+ logger.error('New Relic configurator could not deserialize object list:')
76
79
  logger.error(error.stack)
77
80
  }
78
81
  }
@@ -96,6 +99,8 @@ function _findConfigFile() {
96
99
  function Config(config) {
97
100
  EventEmitter.call(this)
98
101
 
102
+ // helper object for merging server side values
103
+ this.mergeServerConfig = new MergeServerConfig(this)
99
104
  // 1. start by cloning the defaults
100
105
  try {
101
106
  Object.assign(this, defaultConfig())
@@ -151,7 +156,7 @@ function Config(config) {
151
156
  this.browser_monitoring.loader_version = ''
152
157
 
153
158
  // Settings to play nice with DLPs (see NODE-1044).
154
- this.compressed_content_encoding = "deflate" // Deflate or gzip
159
+ this.compressed_content_encoding = 'deflate' // Deflate or gzip
155
160
  this.simple_compression = false // Disables subcomponent compression
156
161
  this.put_for_data_send = false // Changes http verb for harvest
157
162
 
@@ -178,10 +183,10 @@ function Config(config) {
178
183
  if (this.high_security) {
179
184
  if (this.security_policies_token) {
180
185
  throw new Error(
181
- "Security Policies and High Security Mode cannot both be present " +
182
- "in the agent configuration. If Security Policies have been set " +
183
- "for your account, please ensure the security_policies_token is " +
184
- "set but high_security is disabled (default)."
186
+ 'Security Policies and High Security Mode cannot both be present ' +
187
+ 'in the agent configuration. If Security Policies have been set ' +
188
+ 'for your account, please ensure the security_policies_token is ' +
189
+ 'set but high_security is disabled (default).'
185
190
  )
186
191
  }
187
192
  this._applyHighSecurity()
@@ -398,7 +403,7 @@ Config.prototype._fromServer = function _fromServer(params, key) {
398
403
  )
399
404
  break
400
405
  case 'error_collector.ignore_status_codes':
401
- this._updateNestedIfChanged(
406
+ this._validateThenUpdateStatusCodes(
402
407
  params,
403
408
  this.error_collector,
404
409
  'error_collector.ignore_status_codes',
@@ -406,6 +411,47 @@ Config.prototype._fromServer = function _fromServer(params, key) {
406
411
  )
407
412
  this._canonicalize()
408
413
  break
414
+ case 'error_collector.expected_status_codes':
415
+ this._validateThenUpdateStatusCodes(
416
+ params,
417
+ this.error_collector,
418
+ 'error_collector.expected_status_codes',
419
+ 'expected_status_codes'
420
+ )
421
+ this._canonicalize()
422
+ break
423
+ case 'error_collector.ignore_classes':
424
+ this._validateThenUpdateErrorClasses(
425
+ params,
426
+ this.error_collector,
427
+ 'error_collector.ignore_classes',
428
+ 'ignore_classes'
429
+ )
430
+ break
431
+ case 'error_collector.expected_classes':
432
+ this._validateThenUpdateErrorClasses(
433
+ params,
434
+ this.error_collector,
435
+ 'error_collector.expected_classes',
436
+ 'expected_classes'
437
+ )
438
+ break
439
+ case 'error_collector.ignore_messages':
440
+ this._validateThenUpdateErrorMessages(
441
+ params,
442
+ this.error_collector,
443
+ 'error_collector.ignore_messages',
444
+ 'ignore_messages'
445
+ )
446
+ break
447
+ case 'error_collector.expected_messages':
448
+ this._validateThenUpdateErrorMessages(
449
+ params,
450
+ this.error_collector,
451
+ 'error_collector.expected_messages',
452
+ 'expected_messages'
453
+ )
454
+ break
409
455
  case 'error_collector.capture_events':
410
456
  this._updateNestedIfChanged(
411
457
  params,
@@ -550,6 +596,153 @@ Config.prototype._updateIfChanged = function _updateIfChanged(json, key) {
550
596
  this._updateNestedIfChanged(json, this, key, key)
551
597
  }
552
598
 
599
+ /**
600
+ * Expected and Ignored status code configuration values should look like this
601
+ *
602
+ * [500,'501','503-507']
603
+ *
604
+ * If the server side config is not in this format, it might put the agent
605
+ * in a world of hurt. So, before we pass everything on to
606
+ * _updateNestedIfChanged, we'll do some validation.
607
+ *
608
+ * @param {object} remote JSON sent from New Relic.
609
+ * @param {object} local A portion of this configuration object.
610
+ * @param {string} remoteKey The name sent by New Relic.
611
+ * @param {string} localKey The local name.
612
+ */
613
+ Config.prototype._validateThenUpdateStatusCodes = _validateThenUpdateStatusCodes
614
+ function _validateThenUpdateStatusCodes(remote, local, remoteKey, localKey) {
615
+ let valueToTest = remote[remoteKey]
616
+ if (!Array.isArray(valueToTest)) {
617
+ logger.warn(
618
+ 'Saw SSC (ignore|expect)_status_codes that is not an array, will not merge: %s',
619
+ valueToTest
620
+ )
621
+ return
622
+ }
623
+
624
+ let valid = true
625
+ valueToTest.forEach(function validateArray(thingToTest) {
626
+ if (!('string' === (typeof thingToTest) || 'number' === (typeof thingToTest))) {
627
+ logger.warn(
628
+ 'Saw SSC (ignore|expect)_status_code that is not a number or string,' +
629
+ 'will not merge: %s', thingToTest
630
+ )
631
+ valid = false
632
+ }
633
+ })
634
+ if (!valid) {
635
+ return
636
+ }
637
+
638
+ return this._updateNestedIfChanged(remote, local, remoteKey, localKey)
639
+ }
640
+
641
+ /**
642
+ * Expected and Ignored classes configuration values should look like this
643
+ *
644
+ * ['Error','Again']
645
+ *
646
+ * If the server side config is not in this format, it might put the agent
647
+ * in a world of hurt. So, before we pass everything on to
648
+ * _updateNestedIfChanged, we'll do some validation.
649
+ *
650
+ * @param {object} remote JSON sent from New Relic.
651
+ * @param {object} local A portion of this configuration object.
652
+ * @param {string} remoteKey The name sent by New Relic.
653
+ * @param {string} localKey The local name.
654
+ */
655
+ Config.prototype._validateThenUpdateErrorClasses = _validateThenUpdateErrorClasses
656
+
657
+ function _validateThenUpdateErrorClasses(remote, local, remoteKey, localKey) {
658
+ let valueToTest = remote[remoteKey]
659
+ if (!Array.isArray(valueToTest)) {
660
+ logger.warn(
661
+ 'Saw SSC (ignore|expect)_classes that is not an array, will not merge: %s',
662
+ valueToTest
663
+ )
664
+ return
665
+ }
666
+
667
+ let valid = true
668
+ Object.keys(valueToTest).forEach(function validateArray(key) {
669
+ let thingToTest = valueToTest[key]
670
+ if ('string' !== (typeof thingToTest)) {
671
+ logger.warn(
672
+ 'Saw SSC (ignore|expect)_class that is not a string, will not merge: %s',
673
+ thingToTest
674
+ )
675
+ valid = false
676
+ }
677
+ })
678
+ if (!valid) {
679
+ return
680
+ }
681
+
682
+ return this._updateNestedIfChanged(remote, local, remoteKey, localKey)
683
+ }
684
+
685
+ /**
686
+ * Expected and Ignore messages configuration values should look like this
687
+ *
688
+ * {'ErrorType':['Error Message']}
689
+ *
690
+ * If the server side config is not in this format, it might put the agent
691
+ * in a world of hurt. So, before we pass everything on to
692
+ * _updateNestedIfChanged, we'll do some validation.
693
+ *
694
+ * @param {object} remote JSON sent from New Relic.
695
+ * @param {object} local A portion of this configuration object.
696
+ * @param {string} remoteKey The name sent by New Relic.
697
+ * @param {string} localKey The local name.
698
+ */
699
+ Config.prototype._validateThenUpdateErrorMessages = _validateThenUpdateErrorMessages
700
+
701
+ function _validateThenUpdateErrorMessages(remote, local, remoteKey, localKey) {
702
+ let valueToTest = remote[remoteKey]
703
+ if (Array.isArray(valueToTest)) {
704
+ logger.warn(
705
+ 'Saw SSC (ignore|expect)_message that is an Array, will not merge: %s',
706
+ valueToTest
707
+ )
708
+ return
709
+ }
710
+
711
+ if (!valueToTest) {
712
+ logger.warn('SSC ignore|expect_message is null or undefined, will not merge')
713
+ return
714
+ }
715
+
716
+ if ('object' !== typeof valueToTest) {
717
+ logger.warn(
718
+ 'Saw SSC (ignore|expect)_message that is primitive/scaler, will not merge: %s',
719
+ valueToTest
720
+ )
721
+ return
722
+ }
723
+
724
+ if (!valueToTest) {
725
+ logger.warn('SSC ignore|expect_message is null or undefined, will not merge')
726
+ return
727
+ }
728
+
729
+ let valid = true
730
+ Object.keys(valueToTest).forEach(function validateArray(key) {
731
+ let arrayToTest = valueToTest[key]
732
+ if (!Array.isArray(arrayToTest)) {
733
+ logger.warn(
734
+ 'Saw SSC message array that is not an array, will not merge: %s',
735
+ arrayToTest
736
+ )
737
+ valid = false
738
+ }
739
+ })
740
+ if (!valid) {
741
+ return
742
+ }
743
+
744
+ return this._updateNestedIfChanged(remote, local, remoteKey, localKey)
745
+ }
553
746
  /**
554
747
  * Some parameter values are nested, need a simple way to change them as well.
555
748
  * Will merge local and remote if and only if both are arrays.
@@ -572,18 +765,13 @@ function _updateNestedIfChanged(remote, local, remoteKey, localKey) {
572
765
  Config.prototype._updateNestedIfChangedRaw = _updateNestedIfChangedRaw
573
766
 
574
767
  function _updateNestedIfChangedRaw(remote, local, remoteKey, localKey) {
575
- var value = remote[remoteKey]
576
- if (value != null && local[localKey] !== value) {
577
- if (Array.isArray(value) && Array.isArray(local[localKey])) {
578
- value.forEach(function pushIfNew(element) {
579
- if (local[localKey].indexOf(element) === -1) local[localKey].push(element)
580
- })
581
- } else {
582
- local[localKey] = value
583
- }
584
- this.emit(remoteKey, value)
585
- logger.debug('Configuration of %s was changed to %s by New Relic.', remoteKey, value)
586
- }
768
+ return this.mergeServerConfig.updateNestedIfChanged(
769
+ remote,
770
+ local,
771
+ remoteKey,
772
+ localKey,
773
+ logger
774
+ )
587
775
  }
588
776
 
589
777
  /**
@@ -645,7 +833,7 @@ Config.prototype.logUnsupported = function logUnsupported(json, key) {
645
833
  Config.prototype.logUnknown = function logUnknown(json, key) {
646
834
  var value = json[key]
647
835
  logger.debug(
648
- "New Relic sent unknown configuration parameter %s with value %s.",
836
+ 'New Relic sent unknown configuration parameter %s with value %s.',
649
837
  key,
650
838
  value
651
839
  )
@@ -813,8 +1001,8 @@ Config.prototype._fromPassed = function _fromPassed(external, internal, arbitrar
813
1001
 
814
1002
  if (typeof node === 'object' && !Array.isArray(node)) {
815
1003
  // is top level and can have arbitrary keys
816
- var isTop = internal === this && HAS_ARBITRARY_KEYS.indexOf(key) !== -1
817
- this._fromPassed(node, internal[key], isTop)
1004
+ var allowArbitrary = internal === this || HAS_ARBITRARY_KEYS.has(key)
1005
+ this._fromPassed(node, internal[key], allowArbitrary)
818
1006
  } else {
819
1007
  internal[key] = node
820
1008
  }
@@ -994,6 +1182,11 @@ Config.prototype._canonicalize = function _canonicalize() {
994
1182
  this.error_collector.ignore_status_codes = _parseCodes(statusCodes)
995
1183
  }
996
1184
 
1185
+ const expectedCodes = this.error_collector && this.error_collector.expected_status_codes
1186
+ if (statusCodes) {
1187
+ this.error_collector.expected_status_codes = _parseCodes(expectedCodes)
1188
+ }
1189
+
997
1190
  var logAliases = {
998
1191
  verbose: 'trace',
999
1192
  debugging: 'debug',
@@ -1327,7 +1520,7 @@ function initialize(config) {
1327
1520
 
1328
1521
  config = new Config(userConf)
1329
1522
  config.config_file_path = filepath
1330
- logger.debug("Using configuration file %s.", filepath)
1523
+ logger.debug('Using configuration file %s.', filepath)
1331
1524
 
1332
1525
  config.validateFlags()
1333
1526
 
@@ -0,0 +1,56 @@
1
+ 'use strict'
2
+ class MergeServerConfig {
3
+ constructor(config) {
4
+ this.config = config
5
+ }
6
+
7
+ updateNestedIfChanged(remote, local, remoteKey, localKey, logger) {
8
+ var value = remote[remoteKey]
9
+
10
+ // if the value hasn't changed, skip this work
11
+ if (value === null || local[localKey] === value) {
12
+ return
13
+ }
14
+
15
+ // we need different update/merge logic if the server
16
+ // value is an array, a simple object, or anything else
17
+ if (Array.isArray(value) && Array.isArray(local[localKey])) {
18
+ this.updateArray(value, local, localKey)
19
+ } else if (this.isSimpleObject(value) && this.isSimpleObject(local[localKey])) {
20
+ this.updateObject(value, local, localKey)
21
+ } else {
22
+ local[localKey] = value
23
+ }
24
+ this.config.emit(remoteKey, value)
25
+ logger.debug('Configuration of %s was changed to %s by New Relic.', remoteKey, value)
26
+ }
27
+
28
+ updateArray(value, local, localKey) {
29
+ value.forEach( (element) => {
30
+ if (local[localKey].indexOf(element) === -1) local[localKey].push(element)
31
+ })
32
+ }
33
+
34
+ updateObject(value, local, localKey) {
35
+ // go through each key of the object and update it
36
+ Object.keys(value).forEach( (element) => {
37
+ if (Array.isArray(local[localKey][element]) && Array.isArray(value[element])) {
38
+ // if both key-values are arrays, push the remote value onto the local array
39
+ value[element].forEach( (elementValue) => {
40
+ if (-1 === local[localKey][element].indexOf(elementValue)) {
41
+ local[localKey][element].push(elementValue)
42
+ }
43
+ })
44
+ } else {
45
+ // otherwise, replace the local value with the server value
46
+ local[localKey][element] = value[element]
47
+ }
48
+ })
49
+ }
50
+
51
+ isSimpleObject(thing) {
52
+ return 'object' === (typeof thing) && this !== null
53
+ }
54
+ }
55
+
56
+ module.exports = MergeServerConfig
@@ -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
  /**