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.
- package/NEWS.md +28 -0
- package/lib/agent.js +47 -4
- package/lib/collector/api.js +6 -0
- package/lib/collector/serverless.js +2 -0
- package/lib/config/default.js +7 -1
- package/lib/config/env.js +7 -1
- package/lib/config/index.js +218 -25
- package/lib/config/merge-server-config.js +56 -0
- package/lib/errors/aggregator.js +99 -34
- package/lib/errors/helper.js +137 -0
- package/lib/errors/index.js +25 -35
- package/lib/harvest.js +18 -8
- package/lib/instrumentation/@hapi/hapi.js +3 -0
- package/lib/instrumentations.js +1 -0
- package/lib/metrics/names.js +1 -0
- package/lib/shimmer.js +4 -4
- package/lib/transaction/index.js +38 -1
- package/lib/util/urltils.js +23 -0
- package/package.json +3 -3
package/NEWS.md
CHANGED
|
@@ -1,3 +1,31 @@
|
|
|
1
|
+
### 5.11.0 (2019-07-29):
|
|
2
|
+
|
|
3
|
+
* Implements Expected and Ignored Errors functionality
|
|
4
|
+
|
|
5
|
+
* Bumps jsdoc and lodash dev dependency to avoid upstream vulnerability warning.
|
|
6
|
+
|
|
7
|
+
* Added support for scoped package name introduced in hapi v18 (@hapi/hapi).
|
|
8
|
+
|
|
9
|
+
This will provide functionality at parity with instrumentation for hapi v17. Any
|
|
10
|
+
new features may not yet be supported.
|
|
11
|
+
|
|
12
|
+
Huge shoutout to Aori Nevo (@aorinevo) for this contribution.
|
|
13
|
+
|
|
14
|
+
* Fixed bug where agent would count errors towards error metrics even if they were
|
|
15
|
+
dropped due to the error collector being disabled.
|
|
16
|
+
|
|
17
|
+
* The agent will now properly track cached paths to files in loaded modules on Node
|
|
18
|
+
versions >10.
|
|
19
|
+
|
|
20
|
+
As of Node v11, the path to a file in a module being loaded will only be resolved
|
|
21
|
+
on the first load; subsequent resolution of that file will use a cached value.
|
|
22
|
+
The agent records this resolved path and uses it for relative file look ups in
|
|
23
|
+
order to deep link into modules using `Shim#require`. Since the agent couldn't
|
|
24
|
+
reliably get at the path on the subsequent calls to require, it now replicates
|
|
25
|
+
the caching logic and hold onto the resolved path for a given file.
|
|
26
|
+
|
|
27
|
+
* Adds detailed logging through harvest/collector code to increase supportability.
|
|
28
|
+
|
|
1
29
|
### 5.10.0 (2019-06-11):
|
|
2
30
|
|
|
3
31
|
* The agent now allows installation on node v11 and v12.
|
package/lib/agent.js
CHANGED
|
@@ -218,6 +218,7 @@ Agent.prototype.stop = function stop(callback) {
|
|
|
218
218
|
const agent = this
|
|
219
219
|
|
|
220
220
|
this.setState('stopping')
|
|
221
|
+
|
|
221
222
|
this._stopHarvester()
|
|
222
223
|
sampler.stop()
|
|
223
224
|
|
|
@@ -234,6 +235,8 @@ Agent.prototype.stop = function stop(callback) {
|
|
|
234
235
|
callback(error)
|
|
235
236
|
})
|
|
236
237
|
} else {
|
|
238
|
+
logger.trace('Collector was not connected, invoking callback.')
|
|
239
|
+
|
|
237
240
|
process.nextTick(callback)
|
|
238
241
|
}
|
|
239
242
|
}
|
|
@@ -331,6 +334,8 @@ Agent.prototype.reset = function reset() {
|
|
|
331
334
|
* struggling.
|
|
332
335
|
*/
|
|
333
336
|
Agent.prototype.harvest = function harvest(callback) {
|
|
337
|
+
logger.trace('Peparing to harvest.')
|
|
338
|
+
|
|
334
339
|
if (!callback) {
|
|
335
340
|
throw new TypeError('callback required!')
|
|
336
341
|
}
|
|
@@ -338,7 +343,10 @@ Agent.prototype.harvest = function harvest(callback) {
|
|
|
338
343
|
// Generate metrics for this harvest and then check we are connected to the
|
|
339
344
|
// collector.
|
|
340
345
|
this._generateHarvestMetrics()
|
|
346
|
+
|
|
341
347
|
if (!this.collector.isConnected()) {
|
|
348
|
+
logger.trace('Collector not connected.')
|
|
349
|
+
|
|
342
350
|
return setImmediate(function immediatelyError() {
|
|
343
351
|
callback(new Error('Not connected to New Relic!'))
|
|
344
352
|
})
|
|
@@ -346,6 +354,8 @@ Agent.prototype.harvest = function harvest(callback) {
|
|
|
346
354
|
|
|
347
355
|
// We have a connection, create a new harvest.
|
|
348
356
|
this.emit('harvestStarted')
|
|
357
|
+
logger.info('Harvest started.')
|
|
358
|
+
|
|
349
359
|
this._lastHarvest = new Harvest(this)
|
|
350
360
|
this._lastHarvest.prepare(Harvest.ALL_ENDPOINTS)
|
|
351
361
|
|
|
@@ -355,30 +365,40 @@ Agent.prototype.harvest = function harvest(callback) {
|
|
|
355
365
|
// Send the harvest!
|
|
356
366
|
const collector = this.collector
|
|
357
367
|
const agent = this
|
|
368
|
+
|
|
369
|
+
logger.trace('Sending harvest data...')
|
|
358
370
|
this._lastHarvest.send(function afterHarvest(err, agentRunAction) {
|
|
359
371
|
// Do we need to do anything to the agent run?
|
|
360
372
|
if (agentRunAction === AGENT_RUN_BEHAVIOR.SHUTDOWN) {
|
|
361
373
|
agent.emit('harvestFinished')
|
|
374
|
+
logger.info('Harvest finished. Shutdown requested.')
|
|
375
|
+
|
|
362
376
|
agent.stop(function afterStop(stopError) {
|
|
363
377
|
const shutdownError = stopError || err
|
|
364
378
|
callback(shutdownError)
|
|
365
379
|
})
|
|
366
380
|
} else if (agentRunAction === AGENT_RUN_BEHAVIOR.RESTART) {
|
|
381
|
+
logger.info('Restart requested. Harvest will complete upon restart finish.')
|
|
382
|
+
|
|
367
383
|
collector.restart(function afterRestart(restartError) {
|
|
368
384
|
// TODO: What if preconnect/connect respond with shutdown here?
|
|
369
385
|
if (restartError) {
|
|
370
386
|
logger.warn('Failed to restart agent run after harvest')
|
|
371
387
|
callback(restartError)
|
|
372
388
|
} else {
|
|
389
|
+
logger.trace('Restart succeeded. Finishing harvest.')
|
|
373
390
|
_finish(err)
|
|
374
391
|
}
|
|
375
392
|
})
|
|
376
393
|
} else {
|
|
394
|
+
logger.trace('Data sent successfully. Finishing harvest.')
|
|
377
395
|
_finish(err)
|
|
378
396
|
}
|
|
379
397
|
|
|
380
398
|
function _finish(error) {
|
|
381
399
|
agent.emit('harvestFinished')
|
|
400
|
+
logger.info('Harvest finished.')
|
|
401
|
+
|
|
382
402
|
agent._scheduleHarvester(agent.config.data_report_period)
|
|
383
403
|
callback(error)
|
|
384
404
|
}
|
|
@@ -391,16 +411,20 @@ Agent.prototype.harvest = function harvest(callback) {
|
|
|
391
411
|
* NOTE: this doesn't currently work outside of serverless mode.
|
|
392
412
|
*/
|
|
393
413
|
Agent.prototype.harvestSync = function harvestSync() {
|
|
414
|
+
logger.trace('Peparing to harvest.')
|
|
415
|
+
|
|
394
416
|
// Generate metrics for this harvest and then check we are connected to the
|
|
395
417
|
// collector.
|
|
396
418
|
this._generateHarvestMetrics()
|
|
397
419
|
|
|
398
420
|
if (!this.collector.isConnected()) {
|
|
399
|
-
throw new Error('
|
|
421
|
+
throw new Error('Sync harvest not connected/enabled!')
|
|
400
422
|
}
|
|
401
423
|
|
|
402
424
|
// We have a connection, create a new harvest.
|
|
403
425
|
this.emit('harvestStarted')
|
|
426
|
+
logger.info('Harvest started.')
|
|
427
|
+
|
|
404
428
|
this._lastHarvest = new Harvest(this)
|
|
405
429
|
this._lastHarvest.prepare(Harvest.ALL_ENDPOINTS)
|
|
406
430
|
|
|
@@ -414,7 +438,9 @@ Agent.prototype.harvestSync = function harvestSync() {
|
|
|
414
438
|
const payloads = this._lastHarvest.getPayloads()
|
|
415
439
|
collector.populateDataSync(payloads)
|
|
416
440
|
collector.flushPayloadSync()
|
|
441
|
+
|
|
417
442
|
agent.emit('harvestFinished')
|
|
443
|
+
logger.info('Harvest finished.')
|
|
418
444
|
}
|
|
419
445
|
|
|
420
446
|
Agent.prototype._generateHarvestMetrics = function _generateHarvestMetrics() {
|
|
@@ -489,7 +515,7 @@ Agent.prototype.setState = function setState(newState) {
|
|
|
489
515
|
throw new TypeError('Invalid state ' + newState)
|
|
490
516
|
}
|
|
491
517
|
|
|
492
|
-
logger.
|
|
518
|
+
logger.info('Agent state changed from %s to %s.', this._state, newState)
|
|
493
519
|
this._state = newState
|
|
494
520
|
this.emit(this._state)
|
|
495
521
|
}
|
|
@@ -541,6 +567,8 @@ function _harvesterIntervalChange(interval, callback) {
|
|
|
541
567
|
* @param {number} harvestSeconds How many seconds between harvests.
|
|
542
568
|
*/
|
|
543
569
|
Agent.prototype._restartHarvester = function _restartHarvester(harvestSeconds) {
|
|
570
|
+
logger.trace('Restarting harvester.')
|
|
571
|
+
|
|
544
572
|
this._stopHarvester()
|
|
545
573
|
this._scheduleHarvester(harvestSeconds)
|
|
546
574
|
}
|
|
@@ -549,6 +577,8 @@ Agent.prototype._restartHarvester = function _restartHarvester(harvestSeconds) {
|
|
|
549
577
|
* Safely stop the harvest cycle timer.
|
|
550
578
|
*/
|
|
551
579
|
Agent.prototype._stopHarvester = function _stopHarvester() {
|
|
580
|
+
logger.trace('Stopping harvester.')
|
|
581
|
+
|
|
552
582
|
if (this.harvesterHandle) {
|
|
553
583
|
clearTimeout(this.harvesterHandle)
|
|
554
584
|
}
|
|
@@ -569,11 +599,17 @@ Agent.prototype._scheduleHarvester = function _scheduleHarvester(harvestSeconds)
|
|
|
569
599
|
|
|
570
600
|
// If there was a previous harvest, we want to schedule the next one based on
|
|
571
601
|
// its start time.
|
|
572
|
-
|
|
602
|
+
const lastHarvestStart = this._lastHarvest && this._lastHarvest.startTime
|
|
603
|
+
if (lastHarvestStart) {
|
|
573
604
|
const timeSinceHarvest = Date.now() - this._lastHarvest.startTime
|
|
574
605
|
harvestDelay = Math.max(0, harvestDelay - timeSinceHarvest)
|
|
575
606
|
}
|
|
576
607
|
|
|
608
|
+
logger.trace(
|
|
609
|
+
'Scheduling harvester. ' +
|
|
610
|
+
`Last harvest start: ${lastHarvestStart}. Next harvest delay: ${harvestDelay}`
|
|
611
|
+
)
|
|
612
|
+
|
|
577
613
|
this.harvesterHandle = setTimeout(function doHarvest() {
|
|
578
614
|
// Agent#harvest handles scheduling the next harvest and properly reacting to
|
|
579
615
|
// any errors or commands. All we need to do is note any errors it spits out.
|
|
@@ -583,6 +619,7 @@ Agent.prototype._scheduleHarvester = function _scheduleHarvester(harvestSeconds)
|
|
|
583
619
|
}
|
|
584
620
|
})
|
|
585
621
|
}, harvestDelay)
|
|
622
|
+
|
|
586
623
|
this.harvesterHandle.unref()
|
|
587
624
|
}
|
|
588
625
|
|
|
@@ -735,7 +772,13 @@ Agent.prototype._transactionFinished = function _transactionFinished(transaction
|
|
|
735
772
|
logger.debug('Explicitly not ignoring %s (%s).', transaction.name, transaction.id)
|
|
736
773
|
}
|
|
737
774
|
this.metrics.merge(transaction.metrics)
|
|
738
|
-
this.errors.onTransactionFinished(transaction
|
|
775
|
+
let collectedErrors = this.errors.onTransactionFinished(transaction)
|
|
776
|
+
|
|
777
|
+
// the metric should be incremented only if the error was actually collected
|
|
778
|
+
if (collectedErrors > 0) {
|
|
779
|
+
this.metrics.getOrCreateMetric(NAMES.ERRORS.PREFIX + transaction.getFullName())
|
|
780
|
+
.incrementCallCount(collectedErrors)
|
|
781
|
+
}
|
|
739
782
|
this.traces.add(transaction)
|
|
740
783
|
|
|
741
784
|
const trace = transaction.trace
|
package/lib/collector/api.js
CHANGED
|
@@ -73,6 +73,8 @@ CollectorAPI.prototype.connect = function connect(callback) {
|
|
|
73
73
|
throw new TypeError('callback is required')
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
logger.trace('Starting collector.')
|
|
77
|
+
|
|
76
78
|
this._agent.setState('connecting')
|
|
77
79
|
|
|
78
80
|
const api = this
|
|
@@ -422,6 +424,8 @@ CollectorAPI.prototype.shutdown = function shutdown(callback) {
|
|
|
422
424
|
throw new TypeError('callback is required')
|
|
423
425
|
}
|
|
424
426
|
|
|
427
|
+
logger.info('Shutting down collector.')
|
|
428
|
+
|
|
425
429
|
var agent = this._agent
|
|
426
430
|
this._methods.shutdown.invoke(null, this._reqHeadersMap, onShutdown)
|
|
427
431
|
|
|
@@ -442,6 +446,8 @@ CollectorAPI.prototype.shutdown = function shutdown(callback) {
|
|
|
442
446
|
}
|
|
443
447
|
|
|
444
448
|
CollectorAPI.prototype.restart = function restart(callback) {
|
|
449
|
+
logger.info('Restarting collector.')
|
|
450
|
+
|
|
445
451
|
var api = this
|
|
446
452
|
this.shutdown(function reconnect() {
|
|
447
453
|
api.connect(callback)
|
|
@@ -64,6 +64,8 @@ class ServerlessCollector {
|
|
|
64
64
|
* @param {Function} cb The callback to invoke upon disabling the collector.
|
|
65
65
|
*/
|
|
66
66
|
shutdown(cb) {
|
|
67
|
+
logger.trace('Disabling serverless collector.')
|
|
68
|
+
|
|
67
69
|
this.enabled = false
|
|
68
70
|
setImmediate(cb, null, CollectorResponse.success(null))
|
|
69
71
|
}
|
package/lib/config/default.js
CHANGED
|
@@ -298,7 +298,13 @@ exports.config = () => ({
|
|
|
298
298
|
* your own server's sake. The payload of events is compressed, but if it
|
|
299
299
|
* grows too large the New Relic servers may reject it.
|
|
300
300
|
*/
|
|
301
|
-
max_event_samples_stored: 100
|
|
301
|
+
max_event_samples_stored: 100,
|
|
302
|
+
|
|
303
|
+
expected_classes: [],
|
|
304
|
+
expected_messages: {},
|
|
305
|
+
expected_status_codes: [],
|
|
306
|
+
ignore_classes:[],
|
|
307
|
+
ignore_messages:{}
|
|
302
308
|
},
|
|
303
309
|
/**
|
|
304
310
|
* Error message redaction
|
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',
|
package/lib/config/index.js
CHANGED
|
@@ -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(
|
|
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 =
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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.
|
|
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
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
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
|
-
|
|
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
|
|
817
|
-
this._fromPassed(node, internal[key],
|
|
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(
|
|
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
|