newrelic 4.10.0 → 4.13.1

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.
Files changed (55) hide show
  1. package/.eslintrc +2 -0
  2. package/.npmignore +4 -0
  3. package/NEWS.md +89 -0
  4. package/api.js +88 -47
  5. package/bin/travis-setup.sh +14 -6
  6. package/index.js +12 -10
  7. package/lib/adaptive-sampler.js +28 -5
  8. package/lib/agent.js +199 -77
  9. package/lib/collector/api.js +228 -178
  10. package/lib/collector/facts.js +29 -9
  11. package/lib/collector/parse-response.js +24 -76
  12. package/lib/collector/remote-method.js +102 -72
  13. package/lib/collector/response.js +61 -0
  14. package/lib/collector/serverless.js +205 -0
  15. package/lib/config/attribute-filter.js +3 -1
  16. package/lib/config/default.js +14 -3
  17. package/lib/config/env.js +11 -3
  18. package/lib/config/index.js +103 -56
  19. package/lib/db/tracer.js +7 -0
  20. package/lib/errors/aggregator.js +9 -2
  21. package/lib/errors/index.js +6 -6
  22. package/lib/feature_flags.js +2 -0
  23. package/lib/harvest.js +526 -444
  24. package/lib/header-attributes.js +152 -0
  25. package/lib/instrumentation/amqplib.js +4 -3
  26. package/lib/instrumentation/aws-sdk.js +25 -0
  27. package/lib/instrumentation/core/http-outbound.js +12 -4
  28. package/lib/instrumentation/core/http.js +20 -126
  29. package/lib/instrumentation/restify.js +23 -4
  30. package/lib/instrumentations.js +1 -0
  31. package/lib/metrics/names.js +7 -6
  32. package/lib/metrics/normalizer.js +1 -2
  33. package/lib/metrics/recorders/http.js +4 -4
  34. package/lib/serverless/api-gateway.js +62 -0
  35. package/lib/serverless/aws-lambda.js +273 -0
  36. package/lib/serverless/config.js +0 -0
  37. package/lib/shim/constants.js +13 -1
  38. package/lib/shim/shim.js +16 -10
  39. package/lib/shim/specs/index.js +2 -2
  40. package/lib/shim/webframework-shim.js +7 -3
  41. package/lib/shimmer.js +15 -8
  42. package/lib/spans/aggregator.js +2 -2
  43. package/lib/system-info.js +15 -14
  44. package/lib/timer.js +7 -3
  45. package/lib/transaction/index.js +27 -5
  46. package/lib/transaction/trace/attributes.js +1 -4
  47. package/lib/util/label-parser.js +1 -1
  48. package/package-lock.json +6572 -0
  49. package/package.json +6 -13
  50. package/stub_api.js +6 -0
  51. package/lib/collector/constants.js +0 -10
  52. package/lib/metrics/recorders/cassandra.js +0 -40
  53. package/lib/metrics/recorders/express.js +0 -15
  54. package/lib/metrics/recorders/memcached.js +0 -48
  55. package/lib/metrics/recorders/redis.js +0 -48
package/lib/agent.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  const AdaptiveSampler = require('./adaptive-sampler')
4
4
  const CollectorAPI = require('./collector/api')
5
+ const ServerlessCollector = require('./collector/serverless')
5
6
  const DESTINATIONS = require('./config/attribute-filter').DESTINATIONS
6
7
  const ErrorAggregator = require('./errors/aggregator')
7
8
  const EventEmitter = require('events').EventEmitter
@@ -22,9 +23,11 @@ const TxSegmentNormalizer = require('./metrics/normalizer/tx_segment')
22
23
  const uninstrumented = require('./uninstrumented')
23
24
  const util = require('util')
24
25
 
26
+ const AGENT_RUN_BEHAVIOR = require('./collector/response').AGENT_RUN_BEHAVIOR
25
27
  const STATES = [
26
28
  'stopped', // start state
27
- 'starting', // handshaking with NR
29
+ 'starting', // starting agent
30
+ 'connecting', // handshaking with NR
28
31
  'connected', // connected to collector
29
32
  'disconnected', // disconnected from collector
30
33
  'started', // up and running
@@ -34,6 +37,7 @@ const STATES = [
34
37
 
35
38
  // just to make clear what's going on
36
39
  const TO_MILLIS = 1e3
40
+ const SERVERLESS_SAMPLING_LIMIT = Infinity
37
41
 
38
42
  /**
39
43
  * There's a lot of stuff in this constructor, due to Agent acting as the
@@ -53,7 +57,12 @@ function Agent(config) {
53
57
  this.config = config
54
58
  this.environment = require('./environment')
55
59
  this.version = this.config.version
56
- this.collector = new CollectorAPI(this)
60
+
61
+ if (config.serverless_mode.enabled) {
62
+ this.collector = new ServerlessCollector(this)
63
+ } else {
64
+ this.collector = new CollectorAPI(this)
65
+ }
57
66
 
58
67
  // Reset the agent to add all the sub-objects it needs. These object are the
59
68
  // ones that get re-created if the agent is told to restart from the collector.
@@ -74,6 +83,8 @@ function Agent(config) {
74
83
  this.tracer = new Tracer(this)
75
84
  this.traces = new TraceAggregator(this.config)
76
85
  this.transactionSampler = new AdaptiveSampler({
86
+ agent: this,
87
+ serverless: config.serverless_mode.enabled,
77
88
  period: config.sampling_target_period_in_seconds * 1000,
78
89
  target: config.sampling_target
79
90
  })
@@ -112,7 +123,7 @@ util.inherits(Agent, EventEmitter)
112
123
  Agent.prototype.start = function start(callback) {
113
124
  if (!callback) throw new TypeError('callback required!')
114
125
 
115
- var agent = this
126
+ const agent = this
116
127
 
117
128
  this.setState('starting')
118
129
 
@@ -124,50 +135,75 @@ Agent.prototype.start = function start(callback) {
124
135
  return process.nextTick(callback)
125
136
  }
126
137
 
127
- if (!(this.config.license_key)) {
138
+ sampler.start(agent)
139
+
140
+ if (this.config.serverless_mode.enabled) {
141
+ return this._serverlessModeStart(callback)
142
+ }
143
+
144
+ if (!this.config.license_key) {
128
145
  logger.error('A valid account license key cannot be found. ' +
129
146
  'Has a license key been specified in the agent configuration ' +
130
147
  'file or via the NEW_RELIC_LICENSE_KEY environment variable?')
131
148
 
132
149
  this.setState('errored')
150
+ sampler.stop()
133
151
  return process.nextTick(function onNextTick() {
134
152
  callback(new Error('Not starting without license key!'))
135
153
  })
136
154
  }
137
155
 
138
- sampler.start(agent)
139
-
140
156
  logger.info('Starting New Relic for Node.js connection process.')
141
157
 
142
- this.collector.connect(function onConnect(error, config) {
143
- if (error) {
158
+ this.collector.connect(function onConnect(error, response) {
159
+ if (error || response.shouldShutdownRun()) {
144
160
  agent.setState('errored')
145
161
  sampler.stop()
146
- return callback(error, config)
162
+ callback(
163
+ error || new Error('Failed to connect to collector'),
164
+ response && response.payload
165
+ )
166
+ return
147
167
  }
148
168
 
149
- if (agent.collector.isConnected() && !agent.config.no_immediate_harvest) {
150
- // harvest immediately for quicker data display, but after at least 1
151
- // second or the collector will throw away the data.
152
- //
153
- // NOTE: this setTimeout is deliberately NOT unref'd due to it being
154
- // the last step in the Agent startup process
155
- setTimeout(function afterTimeout() {
156
- agent.harvest(function onHarvest(harvestError) {
157
- agent._startHarvester(agent.config.data_report_period)
158
-
159
- agent.setState('started')
160
- callback(harvestError, config)
161
- })
162
- }, 1000)
163
- } else {
164
- process.nextTick(function onNextTick() {
169
+ if (agent.collector.isConnected()) {
170
+ agent.setState('started')
171
+ const config = response.payload
172
+ if (agent.config.no_immediate_harvest) {
173
+ agent._scheduleHarvester(agent.config.data_report_period)
165
174
  callback(null, config)
166
- })
175
+ } else {
176
+ // Harvest immediately for quicker data display, but after at least 1
177
+ // second or the collector will throw away the data.
178
+ //
179
+ // NOTE: this setTimeout is deliberately NOT unref'd due to it being
180
+ // the last step in the Agent startup process
181
+ setTimeout(function afterTimeout() {
182
+ agent.harvest(function onHarvest(harvestError) {
183
+ callback(harvestError, config)
184
+ })
185
+ }, 1000)
186
+ }
187
+ } else {
188
+ callback(new Error('Collector did not connect and did not error'))
167
189
  }
168
190
  })
169
191
  }
170
192
 
193
+ /**
194
+ * Bypasses standard collector connection by immediately invoking the startup
195
+ * callback, after gathering local environment details.
196
+ *
197
+ * @param {Function} callback
198
+ */
199
+ Agent.prototype._serverlessModeStart = function _serverlessModeStart(callback) {
200
+ logger.info(
201
+ 'New Relic for Node.js starting in serverless mode -- skipping connection process.'
202
+ )
203
+
204
+ setImmediate(() => callback(null, this.config))
205
+ }
206
+
171
207
  /**
172
208
  * Any memory claimed by the agent will be retained after stopping.
173
209
  *
@@ -178,7 +214,7 @@ Agent.prototype.start = function start(callback) {
178
214
  Agent.prototype.stop = function stop(callback) {
179
215
  if (!callback) throw new TypeError('callback required!')
180
216
 
181
- var agent = this
217
+ const agent = this
182
218
 
183
219
  this.setState('stopping')
184
220
  this._stopHarvester()
@@ -233,7 +269,13 @@ Agent.prototype._resetEvents = function resetEvents() {
233
269
  if (!this.events) {
234
270
  this.events = new PriorityQueue()
235
271
  }
236
- this.events.setLimit(this.config.transaction_events.max_samples_per_minute)
272
+
273
+ this.events.setLimit(
274
+ this.config.serverless_mode.enabled
275
+ ? SERVERLESS_SAMPLING_LIMIT
276
+ : this.config.transaction_events.max_samples_per_minute
277
+ )
278
+
237
279
  if (!this.customEvents) {
238
280
  this.customEvents = new PriorityQueue()
239
281
  }
@@ -249,7 +291,12 @@ Agent.prototype._resetCustomEvents = function resetCustomEvents(forceReset) {
249
291
  if (!this.customEvents || forceReset) {
250
292
  this.customEvents = new PriorityQueue()
251
293
  }
252
- this.customEvents.setLimit(this.config.custom_insights_events.max_samples_stored)
294
+
295
+ this.customEvents.setLimit(
296
+ this.config.serverless_mode.enabled
297
+ ? SERVERLESS_SAMPLING_LIMIT
298
+ : this.config.custom_insights_events.max_samples_stored
299
+ )
253
300
  }
254
301
 
255
302
  /**
@@ -264,7 +311,11 @@ Agent.prototype.reset = function reset() {
264
311
  this._resetErrors()
265
312
 
266
313
  // Open tracing.
267
- this.spans = new SpanAggregator()
314
+ this.spans = new SpanAggregator(
315
+ this.config.serverless_mode.enabled
316
+ ? SERVERLESS_SAMPLING_LIMIT
317
+ : null
318
+ )
268
319
 
269
320
  // Metrics.
270
321
  this.mapper = new MetricMapper()
@@ -305,31 +356,54 @@ Agent.prototype.harvest = function harvest(callback) {
305
356
  }
306
357
 
307
358
  // We have a connection, create a new harvest.
359
+ this.emit('harvestStarted')
308
360
  this._lastHarvest = new Harvest(this)
361
+ this._lastHarvest.prepare(Harvest.ALL_ENDPOINTS)
309
362
 
310
363
  // Reset all our collections. The harvest has all the data it needs at this point.
311
- // TODO: Make each aggregator able to compose its own payload and clean itself
312
- // up. Then the Harvest class can just iterate over all aggregations without
313
- // having to know bespoke reset information.
314
- this.metrics = new Metrics(
315
- this.config.apdex_t,
316
- this.mapper,
317
- this.metricNameNormalizer
318
- )
319
- this.events = new PriorityQueue(
320
- this.config.transaction_events.max_samples_per_minute
321
- )
322
- this.customEvents = new PriorityQueue(
323
- this.config.custom_insights_events.max_samples_stored
324
- )
325
- this.errors.clearEvents()
326
- this.errors.clearErrors()
327
- this.traces.reset()
328
- this.queries = new QueryTracer(this.config)
329
- this.spans.clearEvents()
364
+ this._resetHarvestables()
330
365
 
331
366
  // Send the harvest!
332
- this._lastHarvest.send(callback)
367
+ const collector = this.collector
368
+ const agent = this
369
+ this._lastHarvest.send(function afterHarvest(err, agentRunAction) {
370
+ // The serverless collector will never tell us anything interesting to do,
371
+ // but has an awkward, final step that the normal collector does not.
372
+ if (collector instanceof ServerlessCollector) {
373
+ return collector.flushPayload(function afterFlush(flushError) {
374
+ agent.emit('harvestFinished')
375
+ const serverlessError = flushError || err
376
+ callback(serverlessError)
377
+ })
378
+ }
379
+
380
+ // Do we need to do anything to the agent run?
381
+ if (agentRunAction === AGENT_RUN_BEHAVIOR.SHUTDOWN) {
382
+ agent.emit('harvestFinished')
383
+ agent.stop(function afterStop(stopError) {
384
+ const shutdownError = stopError || err
385
+ callback(shutdownError)
386
+ })
387
+ } else if (agentRunAction === AGENT_RUN_BEHAVIOR.RESTART) {
388
+ collector.restart(function afterRestart(restartError) {
389
+ // TODO: What if preconnect/connect respond with shutdown here?
390
+ if (restartError) {
391
+ logger.warn('Failed to restart agent run after harvest')
392
+ callback(restartError)
393
+ } else {
394
+ _finish(err)
395
+ }
396
+ })
397
+ } else {
398
+ _finish(err)
399
+ }
400
+
401
+ function _finish(error) {
402
+ agent.emit('harvestFinished')
403
+ agent._scheduleHarvester(agent.config.data_report_period)
404
+ callback(error)
405
+ }
406
+ })
333
407
  }
334
408
 
335
409
  Agent.prototype._generateHarvestMetrics = function _generateHarvestMetrics() {
@@ -358,6 +432,32 @@ Agent.prototype._generateHarvestMetrics = function _generateHarvestMetrics() {
358
432
  this.transactionsCreatedInHarvest = 0
359
433
  }
360
434
 
435
+ Agent.prototype._resetHarvestables = function _resetHarvestables() {
436
+ // TODO: Make each aggregator able to compose its own payload and clean itself
437
+ // up. Then the Harvest class can just iterate over all aggregations without
438
+ // having to know bespoke reset information.
439
+ this.metrics = new Metrics(
440
+ this.config.apdex_t,
441
+ this.mapper,
442
+ this.metricNameNormalizer
443
+ )
444
+ this.events = new PriorityQueue(
445
+ this.config.serverless_mode.enabled
446
+ ? SERVERLESS_SAMPLING_LIMIT
447
+ : this.config.transaction_events.max_samples_per_minute
448
+ )
449
+ this.customEvents = new PriorityQueue(
450
+ this.config.serverless_mode.enabled
451
+ ? SERVERLESS_SAMPLING_LIMIT
452
+ : this.config.custom_insights_events.max_samples_stored
453
+ )
454
+ this.errors.clearEvents()
455
+ this.errors.clearErrors()
456
+ this.traces.reset()
457
+ this.queries = new QueryTracer(this.config)
458
+ this.spans.clearEvents()
459
+ }
460
+
361
461
  /**
362
462
  * Public interface for passing configuration data from the collector
363
463
  * on to the configuration, in an effort to keep them at least somewhat
@@ -407,7 +507,7 @@ Agent.prototype._apdexTChange = function _apdexTChange(apdexT) {
407
507
  Agent.prototype._harvesterIntervalChange = _harvesterIntervalChange
408
508
 
409
509
  function _harvesterIntervalChange(interval, callback) {
410
- var agent = this
510
+ const agent = this
411
511
 
412
512
  // only change the setup if the harvester is currently running
413
513
  if (this.harvesterHandle) {
@@ -428,14 +528,17 @@ function _harvesterIntervalChange(interval, callback) {
428
528
  */
429
529
  Agent.prototype._restartHarvester = function _restartHarvester(harvestSeconds) {
430
530
  this._stopHarvester()
431
- this._startHarvester(harvestSeconds)
531
+ this._scheduleHarvester(harvestSeconds)
432
532
  }
433
533
 
434
534
  /**
435
535
  * Safely stop the harvest cycle timer.
436
536
  */
437
537
  Agent.prototype._stopHarvester = function _stopHarvester() {
438
- if (this.harvesterHandle) clearInterval(this.harvesterHandle)
538
+ if (this.harvesterHandle) {
539
+ clearTimeout(this.harvesterHandle)
540
+ }
541
+ this._lastHarvest = null
439
542
  this.harvesterHandle = null
440
543
  }
441
544
 
@@ -444,22 +547,28 @@ Agent.prototype._stopHarvester = function _stopHarvester() {
444
547
  * cycle won't keep an application from exiting if nothing else is
445
548
  * happening to keep it up.
446
549
  *
447
- * @param {number} harvestSeconds How many seconds between harvests.
550
+ * @param {number} harvestSeconds - How many seconds between harvests.
448
551
  */
449
- Agent.prototype._startHarvester = function _startHarvester(harvestSeconds) {
450
- var agent = this
451
-
452
- function onError(error) {
453
- if (error) {
454
- logger.info(error, 'Error on submission to New Relic (data held for redelivery):')
455
- }
552
+ Agent.prototype._scheduleHarvester = function _scheduleHarvester(harvestSeconds) {
553
+ const agent = this
554
+ let harvestDelay = harvestSeconds * TO_MILLIS
555
+
556
+ // If there was a previous harvest, we want to schedule the next one based on
557
+ // its start time.
558
+ if (this._lastHarvest && this._lastHarvest.startTime) {
559
+ const timeSinceHarvest = Date.now() - this._lastHarvest.startTime
560
+ harvestDelay = Math.max(0, harvestDelay - timeSinceHarvest)
456
561
  }
457
562
 
458
- function harvester() {
459
- agent.harvest(onError)
460
- }
461
-
462
- this.harvesterHandle = setInterval(harvester, harvestSeconds * TO_MILLIS)
563
+ this.harvesterHandle = setTimeout(function doHarvest() {
564
+ // Agent#harvest handles scheduling the next harvest and properly reacting to
565
+ // any errors or commands. All we need to do is note any errors it spits out.
566
+ agent.harvest(function harvestError(error) {
567
+ if (error) {
568
+ logger.warn(error, 'Error on submission to New Relic.')
569
+ }
570
+ })
571
+ }, harvestDelay)
463
572
  this.harvesterHandle.unref()
464
573
  }
465
574
 
@@ -487,17 +596,17 @@ Agent.prototype._configChange = function _configChange() {
487
596
  Agent.prototype._addIntrinsicAttrsFromTransaction = _addIntrinsicAttrsFromTransaction
488
597
 
489
598
  function _addIntrinsicAttrsFromTransaction(transaction) {
490
- var intrinsicAttributes = {
599
+ const intrinsicAttributes = {
491
600
  webDuration: transaction.timer.getDurationInMillis() / 1000,
492
601
  timestamp: transaction.timer.start,
493
602
  name: transaction.getFullName(),
494
603
  duration: transaction.timer.getDurationInMillis() / 1000,
495
- totalTime: transaction.trace.getTotalTimeDurationInMillis(),
604
+ totalTime: transaction.trace.getTotalTimeDurationInMillis() / 1000,
496
605
  type: 'Transaction',
497
606
  error: transaction.hasErrors()
498
607
  }
499
608
 
500
- var metric = transaction.metrics.getMetric(NAMES.QUEUETIME)
609
+ let metric = transaction.metrics.getMetric(NAMES.QUEUETIME)
501
610
  if (metric) {
502
611
  intrinsicAttributes.queueDuration = metric.total
503
612
  }
@@ -582,11 +691,11 @@ function calculateApdexZone(duration, apdexT) {
582
691
  Agent.prototype._addEventFromTransaction = function _addEventFromTransaction(tx) {
583
692
  if (!this.config.transaction_events.enabled) return
584
693
 
585
- var intrinsicAttributes = this._addIntrinsicAttrsFromTransaction(tx)
586
- var userAttributes = tx.trace.custom.get(DESTINATIONS.TRANS_EVENT)
587
- var agentAttributes = tx.trace.attributes.get(DESTINATIONS.TRANS_EVENT)
694
+ const intrinsicAttributes = this._addIntrinsicAttrsFromTransaction(tx)
695
+ const userAttributes = tx.trace.custom.get(DESTINATIONS.TRANS_EVENT)
696
+ const agentAttributes = tx.trace.attributes.get(DESTINATIONS.TRANS_EVENT)
588
697
 
589
- var event = [
698
+ const event = [
590
699
  intrinsicAttributes,
591
700
  userAttributes,
592
701
  agentAttributes
@@ -615,7 +724,7 @@ Agent.prototype._transactionFinished = function _transactionFinished(transaction
615
724
  this.errors.onTransactionFinished(transaction, this.metrics)
616
725
  this.traces.add(transaction)
617
726
 
618
- var trace = transaction.trace
727
+ const trace = transaction.trace
619
728
  trace.intrinsics = transaction.getIntrinsicAttributes()
620
729
 
621
730
  this._addEventFromTransaction(transaction)
@@ -628,6 +737,20 @@ Agent.prototype._transactionFinished = function _transactionFinished(transaction
628
737
  --this.activeTransactions
629
738
  this.totalActiveSegments -= transaction.numSegments
630
739
  this.segmentsClearedInHarvest += transaction.numSegments
740
+
741
+ if (this.config.serverless_mode.enabled) {
742
+ this.harvest(function onServerlessHarvest(err) {
743
+ if (err) {
744
+ logger.error('Serverless mode harvest error', err)
745
+ }
746
+ })
747
+ }
748
+ }
749
+
750
+ Agent.prototype.setLambdaArn = function setLambdaArn(arn) {
751
+ if (this.collector instanceof ServerlessCollector) {
752
+ this.collector.setLambdaArn(arn)
753
+ }
631
754
  }
632
755
 
633
756
  /**
@@ -640,7 +763,7 @@ Agent.prototype.getTransaction = function getTransaction() {
640
763
  }
641
764
 
642
765
  Agent.prototype.recordSupportability = function recordSupportability(name, value) {
643
- var metric = this.metrics.getOrCreateMetric(NAMES.SUPPORTABILITY.PREFIX + name)
766
+ const metric = this.metrics.getOrCreateMetric(NAMES.SUPPORTABILITY.PREFIX + name)
644
767
  if (value != null) {
645
768
  metric.recordValue(value)
646
769
  } else {
@@ -649,9 +772,8 @@ Agent.prototype.recordSupportability = function recordSupportability(name, value
649
772
  }
650
773
 
651
774
  Agent.prototype._listenForConfigChanges = function _listenForConfigChanges() {
652
- var self = this
775
+ const self = this
653
776
  this.config.on('apdex_t', this._apdexTChange.bind(this))
654
- this.config.on('data_report_period', this._harvesterIntervalChange.bind(this))
655
777
  this.config.on('agent_enabled', this._enabledChange.bind(this))
656
778
  this.config.on('change', this._configChange.bind(this))
657
779
  this.config.on('metric_name_rules', function updateMetricNameNormalizer() {