newrelic 4.6.0 → 4.7.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 CHANGED
@@ -1,3 +1,40 @@
1
+ ### 4.7.0 (2018-07-31):
2
+
3
+ * Added support for distributed tracing.
4
+
5
+ Distributed tracing lets you see the path that a request takes as it travels
6
+ through your distributed system. By showing the distributed activity through a
7
+ unified view, you can troubleshoot and understand a complex system better than
8
+ ever before.
9
+
10
+ Distributed tracing is available with an APM Pro or equivalent subscription.
11
+ To see a complete distributed trace, you need to enable the feature on a set of
12
+ neighboring services. Enabling distributed tracing changes the behavior of some
13
+ New Relic features, so carefully consult the [transition guide](https://docs.newrelic.com/docs/transition-guide-distributed-tracing) before
14
+ you enable this feature.
15
+
16
+ To enable distributed tracing, set `distributed_tracing.enabled` to `true` in
17
+ your `newrelic.js` file, or set `NEW_RELIC_DISTRIBUTED_TRACING_ENABLED` in your
18
+ environment.
19
+
20
+ * Added a warning for too-new versions of Node.js during agent startup.
21
+
22
+ * Appropriately obfuscated SQL statements will now be included in all transaction
23
+ traces.
24
+
25
+ Previously, the agent would only include the SQL statements if the corresponding
26
+ query was sufficiently slow.
27
+
28
+ * Added ability to execute instrumentation functions in the context of the segment
29
+ the segment descriptor is describing.
30
+
31
+ All `record*` methods supplied by all instrumentation shim classes now allow for
32
+ a function to be executed under the context of the segment the record call will
33
+ produce. This may be done by supplying a function in the `inContext` key for the
34
+ segment descriptor passed to the record method.
35
+
36
+ * Reservoirs will now respect setting their size to 0.
37
+
1
38
  ### 4.6.0 (2018-07-24):
2
39
 
3
40
  * Added full support for Node v10.
@@ -11,6 +48,8 @@
11
48
  * Updated tests to use `asyncResource.runInAsyncScope` instead of `emitBefore` and
12
49
  `emitAfter`
13
50
 
51
+ * Pulled `distributed_tracing` config value from behind `feature_flag`.
52
+
14
53
  ### 4.5.1 (2018-07-18):
15
54
 
16
55
  - The agent will now properly remerge event data on collection failure.
@@ -33,6 +72,12 @@
33
72
 
34
73
  * Updated Hapi v17 instrumentation to wrap `server` export, in addition to `Server`.
35
74
 
75
+ * `ROOT` segment no longer turns into a span event.
76
+
77
+ * Fixed span collection when transactions are `sampled=false`.
78
+
79
+ * Removed `grandparentId` from spans.
80
+
36
81
  ### 4.4.0 (2018-07-12):
37
82
 
38
83
  * Added config `utilization` env vars to the `BOOLEAN_VARS` set.
@@ -44,6 +89,29 @@
44
89
 
45
90
  * Added node v10 to the test matrix.
46
91
 
92
+ * Converted distributed trace `x-newrelic-trace` header name to `newrelic`.
93
+
94
+ * Added support for different transport types in distributed tracing.
95
+
96
+ * Added more tests around priority/sampled attributes on traces and events.
97
+
98
+ * Lazily calculate transaction priority only when needed.
99
+
100
+ * Transaction priority is now truncated to 6 decimal places on generation.
101
+
102
+ * Adaptive sampling now uses the `sampling_target` and
103
+ `sampling_target_period_in_seconds` configuration values.
104
+
105
+ With these configurations, the adaptive sampling window is separated from the
106
+ harvest window.
107
+
108
+ * Removed `nr.tripId` attribute from distributed trace intrinsics.
109
+
110
+ * Default span events to enabled.
111
+
112
+ These are still protected behind `feature_flag.distributed_tracing` which defaults
113
+ to `false`.
114
+
47
115
  ### 4.3.0 (2018-07-09):
48
116
 
49
117
  * Added `nonce` option for `newrelic.getBrowserTimingHeader()`
@@ -65,7 +133,7 @@
65
133
 
66
134
  * Fixed issue with tracking external requests to default ports.
67
135
 
68
- Special thanks to Ryan King for pinpointing the cause of this issue.
136
+ Special thanks to Ryan King for pinpointing the cause of this issue.
69
137
 
70
138
  * Added extra check for handling arrays of functions when wrapping middleware
71
139
  mounters.
@@ -103,6 +171,25 @@
103
171
 
104
172
  * No longer download gcc on test suites that do not require it.
105
173
 
174
+ * Added `url` parameter to `http` external segments.
175
+
176
+ * Renamed request parameters on external segments.
177
+
178
+ Previously these were named just the parameter name (e.g. `/foo?bar=baz` would
179
+ become the parameter `"bar": "baz"`). Now they are prefixed with
180
+ `request.parameter`. (e.g. `"request.parameter.bar": "baz"`).
181
+
182
+ * Added `EventAggregator` base class.
183
+
184
+ The `ErrorAggregator` class was refactored and most generic event aggregation
185
+ logic was moved to the new `EventAggregator` class.
186
+
187
+ * Added `SpanEvent` and `SpanAggregator` classes.
188
+
189
+ * Added Span event generation to the trace `end` method.
190
+
191
+ * Added Span events to harvest cycle steps.
192
+
106
193
  ### 4.1.5 (2018-06-11):
107
194
 
108
195
  * Make `require()` statements explicitly reference `package.json` as a `.json` file.
@@ -145,6 +232,11 @@
145
232
  result in a false `uninstrumented` status, because the agent would interpret
146
233
  `redis.js` as the module itself.
147
234
 
235
+ * Moved `computeSampled` call to `Transaction` constructor.
236
+
237
+ Previously it was only called in `createDistributedTracePayload`, but this
238
+ gives all transactions a `sampled` value, and potentially a boosted priority.
239
+
148
240
  ### 4.1.4 (2018-06-04):
149
241
 
150
242
  * Transaction stubs are now created properly in `api#getTransaction`
@@ -168,6 +260,25 @@
168
260
 
169
261
  * Modularlized configuration constants to improve readability.
170
262
 
263
+ * Added `distributed_tracing` feature flag.
264
+
265
+ * Added `acceptDistributedTracePayload` method to `Transaction`.
266
+
267
+ * Added `createDistributedTracePayload` method to `Transaction`.
268
+
269
+ * Updated `Agent#recordSupportability` to not include `Nodejs/` in the default metric name.
270
+
271
+ * Added distributed tracing methods to `TransactionHandle`.
272
+
273
+ * Added distributed tracing cases for `http` and `other` metric recorders.
274
+
275
+ * Implemented `_addDistributedTraceInstrinsics` on `Transaction`.
276
+
277
+ If the `distributed_tracing` feature flag is enabled, the agent will ignore old
278
+ CAT attributes in favor of distributed trace–related ones.
279
+
280
+ * Added integration tests around better CAT functionality.
281
+
171
282
  ### 4.1.2 (2018-05-22):
172
283
 
173
284
  * Fixed access to properties on promisified methods.
package/api.js CHANGED
@@ -702,6 +702,13 @@ function createTracer(name, callback) {
702
702
  return arity.fixArity(callback, tracer.bindFunction(callback, segment, true))
703
703
  }
704
704
 
705
+ /**
706
+ * @callback startSegmentCallback
707
+ * @param {function} cb
708
+ * The function to time with the created segment.
709
+ * @return {Promise=} Returns a promise if cb returns a promise.
710
+ */
711
+
705
712
  /**
706
713
  * Wraps the given handler in a segment which may optionally be turned into a
707
714
  * metric.
@@ -720,7 +727,7 @@ function createTracer(name, callback) {
720
727
  * up on the transaction breakdown table and server breakdown graph. Segments
721
728
  * just show up in transaction traces.
722
729
  *
723
- * @param {function(cb) -> ?Promise} handler
730
+ * @param {startSegmentCallback} handler
724
731
  * The function to track as a segment.
725
732
  *
726
733
  * @param {function} [callback]
package/index.js CHANGED
@@ -1,21 +1,22 @@
1
1
  'use strict'
2
2
 
3
3
  // Record opening times before loading any other files.
4
- var preAgentTime = process.uptime()
5
- var agentStart = Date.now()
4
+ const preAgentTime = process.uptime()
5
+ const agentStart = Date.now()
6
6
 
7
7
  // Load unwrapped core now to ensure it gets the freshest properties.
8
8
  require('./lib/util/unwrapped-core')
9
9
 
10
- var featureFlags = require('./lib/feature_flags').prerelease
11
- var logger = require('./lib/logger')
12
- var psemver = require('./lib/util/process-version')
10
+ const featureFlags = require('./lib/feature_flags').prerelease
11
+ const psemver = require('./lib/util/process-version')
12
+ let logger = require('./lib/logger') // Gets re-loaded after initialization.
13
13
 
14
14
 
15
- var agentVersion = require('./package.json').version
15
+ const pkgJSON = require('./package.json')
16
16
  logger.info(
17
- "Using New Relic for Node.js. Agent version: %s; Node version: %s.",
18
- agentVersion, process.version
17
+ 'Using New Relic for Node.js. Agent version: %s; Node version: %s.',
18
+ pkgJSON.version,
19
+ process.version
19
20
  )
20
21
 
21
22
  if (require.cache.__NR_cache) {
@@ -49,6 +50,13 @@ function initialize() {
49
50
 
50
51
  logger.error(message)
51
52
  throw new Error(message)
53
+ } else if (!psemver.satisfies(pkgJSON.engines.node)) {
54
+ logger.warn(
55
+ 'New Relic for Node.js %s has not been tested on Node.js %s. Please ' +
56
+ 'update the agent or downgrade your version of Node.js',
57
+ pkgJSON.version,
58
+ process.version
59
+ )
52
60
  }
53
61
 
54
62
  logger.debug("Current working directory at module load is %s.", process.cwd())
@@ -793,6 +793,11 @@ exports.config = {
793
793
 
794
794
  /**
795
795
  * Controls the method of cross agent tracing in the agent.
796
+ * Distributed tracing lets you see the path that a request takes through your
797
+ * distributed system. Enabling distributed tracing changes the behavior of some
798
+ * New Relic features, so carefully consult the transition guide before you enable
799
+ * this feature: https://docs.newrelic.com/docs/transition-guide-distributed-tracing
800
+ * Default is false.
796
801
  */
797
802
  distributed_tracing: {
798
803
  /**
package/lib/db/tracer.js CHANGED
@@ -46,27 +46,36 @@ QueryTracer.prototype.merge = function merge(tracer) {
46
46
 
47
47
  QueryTracer.prototype.addQuery = function addQuery(segment, type, query, trace) {
48
48
  const ttConfig = this.config.transaction_tracer
49
- if (segment.getDurationInMillis() < ttConfig.explain_threshold) {
50
- return
51
- }
52
- const slowQuery = new SlowQuery(segment, type, query, trace)
53
49
 
50
+ // If DT is enabled and the segment is part of a sampled transaction
51
+ // (i.e. we are creating a span event for this segment), then we need
52
+ // to collect the sql trace.
53
+ var slowQuery
54
54
  switch (ttConfig.record_sql) {
55
55
  case 'raw':
56
+ slowQuery = new SlowQuery(segment, type, query, trace)
56
57
  logger.trace('recording raw sql')
57
58
  segment.parameters.sql = slowQuery.query
58
59
  break
59
60
  case 'obfuscated':
61
+ slowQuery = new SlowQuery(segment, type, query, trace)
60
62
  logger.trace('recording obfuscated sql')
61
63
  segment.parameters.sql_obfuscated = slowQuery.obfuscated
62
64
  break
63
65
  default:
64
66
  logger.trace(
65
- 'not collecting slow-query because transaction_tracer.record_sql was set to %s',
67
+ 'not recording sql statement, transaction_tracer.record_sql was set to %s',
66
68
  ttConfig.record_sql
67
69
  )
68
70
  return
69
71
  }
72
+
73
+ if (segment.getDurationInMillis() < ttConfig.explain_threshold) {
74
+ return
75
+ }
76
+
77
+ slowQuery = slowQuery || new SlowQuery(segment, type, query, trace)
78
+
70
79
  segment.parameters.backtrace = slowQuery.trace
71
80
 
72
81
  if (!this.config.slow_sql.enabled) {
@@ -305,7 +305,7 @@ function getGlobalPackages(cb) {
305
305
  * package appears at most once, with all the versions joined into a
306
306
  * comma-delimited list.
307
307
  *
308
- * @return {Array.<String>[]} Sorted list of [name, version] pairs.
308
+ * @return {Array.<string[]>} Sorted list of [name, version] pairs.
309
309
  */
310
310
  function flattenVersions(packages) {
311
311
  var info = Object.create(null)
@@ -693,7 +693,7 @@ module.exports = function initialize(agent, http, moduleName) {
693
693
  *
694
694
  * @param {string} header - The raw X-NewRelic-Synthetics header
695
695
  * @param {string} encKey - Encoding key handed down from the server
696
- * @param {̄Array} trustedIds - Array of accounts to trust the header from.
696
+ * @param {Array.<number>} trustedIds - Array of accounts to trust the header from.
697
697
  * @param {Transaction} transaction - Where the synthetics data is attached to.
698
698
  */
699
699
  function handleSyntheticsHeader(header, encKey, trustedIds, transaction) {
@@ -711,8 +711,8 @@ function handleSyntheticsHeader(header, encKey, trustedIds, transaction) {
711
711
  *
712
712
  * @param {string} header - The raw X-NewRelic-Synthetics header
713
713
  * @param {string} encKey - Encoding key handed down from the server
714
- * @param {̄Array} trustedIds - Array of accounts to trust the header from.
715
- * @return {Object or null} - On successful parse and verification an object of
714
+ * @param {Array.<number>} trustedIds - Array of accounts to trust the header from.
715
+ * @return {Object|null} - On successful parse and verification an object of
716
716
  * synthetics data is returned, otherwise null is
717
717
  * returned.
718
718
  */
@@ -3,7 +3,7 @@
3
3
  var Heap = require('@tyriar/fibonacci-heap').FibonacciHeap
4
4
 
5
5
  function PriorityQueue(limit) {
6
- this.limit = limit || 10
6
+ this.limit = limit == null ? 10 : limit
7
7
  this.seen = 0
8
8
  this._data = new Heap()
9
9
 
@@ -24,8 +24,11 @@ PriorityQueue.prototype.getMinimumPriority = function getMinimumPriority() {
24
24
  }
25
25
 
26
26
  PriorityQueue.prototype.add = function add(value, priority) {
27
- priority = priority || Math.random()
28
27
  this.seen++
28
+ if (this.limit <= 0) {
29
+ return false
30
+ }
31
+ priority = priority || Math.random()
29
32
  if (this.length === this.limit) {
30
33
  return this._replace(value, priority)
31
34
  }
@@ -333,10 +333,6 @@ function recordProduce(nodule, properties, recordNamer) {
333
333
  }
334
334
 
335
335
  var name = _nameMessageSegment(shim, msgDesc, shim._metrics.PRODUCE)
336
- if (msgDesc.headers) {
337
- shim.insertCATRequestHeaders(msgDesc.headers, true)
338
- }
339
-
340
336
  if (!shim.agent.config.message_tracer.segment_parameters.enabled) {
341
337
  delete msgDesc.parameters
342
338
  } else if (msgDesc.routingKey) {
@@ -350,6 +346,11 @@ function recordProduce(nodule, properties, recordNamer) {
350
346
  promise: msgDesc.promise || false,
351
347
  callback: msgDesc.callback || null,
352
348
  recorder: genericRecorder,
349
+ inContext: function generateCATHeaders() {
350
+ if (msgDesc.headers) {
351
+ shim.insertCATRequestHeaders(msgDesc.headers, true)
352
+ }
353
+ },
353
354
  parameters: msgDesc.parameters || null
354
355
  }
355
356
  })
package/lib/shim/shim.js CHANGED
@@ -946,7 +946,7 @@ function record(nodule, properties, recordNamer) {
946
946
  var promised = false
947
947
  var ret
948
948
  try {
949
- ret = shim.applySegment(fn, segment, true, ctx, args)
949
+ ret = shim.applySegment(fn, segment, true, ctx, args, segDesc.inContext)
950
950
  if (segDesc.after && segDesc.promise && shim.isPromise(ret)) {
951
951
  promised = true
952
952
  return ret.then(function onThen(val) {
@@ -1286,7 +1286,7 @@ function storeSegment(obj, segment) {
1286
1286
  * Sets the given segment as the active one for the duration of the function's
1287
1287
  * execution.
1288
1288
  *
1289
- * - `applySegment(func, segment, full, context, args)`
1289
+ * - `applySegment(func, segment, full, context, args[, inContextCB])`
1290
1290
  *
1291
1291
  * @memberof Shim.prototype
1292
1292
  *
@@ -1305,9 +1305,15 @@ function storeSegment(obj, segment) {
1305
1305
  * @param {Array.<*>} args
1306
1306
  * The arguments to be passed into the function.
1307
1307
  *
1308
+ * @param {Function} [inContextCB]
1309
+ * The function used to do more instrumentation work. This function is
1310
+ * guaranteed to be executed with the segment associated with.
1311
+ *
1312
+ *
1308
1313
  * @return {*} Whatever value `func` returned.
1309
1314
  */
1310
- function applySegment(func, segment, full, context, args) {
1315
+ /* eslint-disable max-params */
1316
+ function applySegment(func, segment, full, context, args, inContextCB) {
1311
1317
  // Exist fast for bad arguments.
1312
1318
  if (!this.isFunction(func)) {
1313
1319
  return
@@ -1327,6 +1333,10 @@ function applySegment(func, segment, full, context, args) {
1327
1333
  segment.start()
1328
1334
  }
1329
1335
 
1336
+ if (typeof inContextCB === 'function') {
1337
+ inContextCB()
1338
+ }
1339
+
1330
1340
  // Execute the function and then return the tracer segment to the old one.
1331
1341
  try {
1332
1342
  return func.apply(context, args)
@@ -1337,6 +1347,7 @@ function applySegment(func, segment, full, context, args) {
1337
1347
  tracer.segment = prevSegment
1338
1348
  }
1339
1349
  }
1350
+ /* eslint-enable max-params */
1340
1351
 
1341
1352
  /**
1342
1353
  * Creates a new segment.
@@ -45,6 +45,7 @@ function WrapSpec(spec) {
45
45
  function SegmentSpec(spec) {
46
46
  this.name = hasOwnProperty(spec, 'name') ? spec.name : null
47
47
  this.recorder = hasOwnProperty(spec, 'recorder') ? spec.recorder : null
48
+ this.inContext = hasOwnProperty(spec, 'inContext') ? spec.inContext : null
48
49
  this.parent = hasOwnProperty(spec, 'parent') ? spec.parent : null
49
50
  this.parameters = hasOwnProperty(spec, 'parameters') ? spec.parameters : null
50
51
  this.internal = hasOwnProperty(spec, 'internal') ? spec.internal : false
@@ -7,6 +7,12 @@ const DT_VERSION_MAJOR = 0
7
7
  const DT_VERSION_MINOR = 1
8
8
 
9
9
  module.exports = class DistributedTracePayload {
10
+ /**
11
+ * The class reponsible for producing distributed trace payloads.
12
+ * Created by calling {@link TransactionHandle#createDistributedTracePayload}.
13
+ *
14
+ * @constructor
15
+ */
10
16
  constructor(payload) {
11
17
  logger.trace('DistributedTracePayload created with %s', payload)
12
18
  this.plainTextPayload = JSON.stringify({
@@ -16,11 +22,21 @@ module.exports = class DistributedTracePayload {
16
22
  this.base64Payload = null
17
23
  }
18
24
 
25
+ /**
26
+ * @returns {String} The base64 encoded JSON representation of the
27
+ * distributed trace payload.
28
+ */
19
29
  text() {
20
30
  logger.trace('DistributedTracePayload text: %s', this.plainTextPayload)
21
31
  return this.plainTextPayload
22
32
  }
23
33
 
34
+ /**
35
+ * Construct a payload suitable for HTTP transport.
36
+ *
37
+ * @returns {String} The base64 encoded JSON representation of the
38
+ * distributed trace payload.
39
+ */
24
40
  httpSafe() {
25
41
  if (!this.base64Payload) {
26
42
  this.base64Payload = makeBuffer(this.plainTextPayload, 'utf-8').toString('base64')
@@ -33,7 +33,7 @@ module.exports = class TransactionHandle {
33
33
  *
34
34
  * Proxy method for Transaction#createDistrubtedTracePayload.
35
35
  *
36
- * @returns {DistributedTracePayload} The created payload.
36
+ * @returns {DistributedTracePayload} The created payload object.
37
37
  *
38
38
  */
39
39
  createDistributedTracePayload() {
@@ -193,6 +193,10 @@ Transaction.prototype.end = function end(done) {
193
193
  transaction.record()
194
194
  }
195
195
 
196
+ // This is function currently must be called after all recorders have been fired due
197
+ // to some of the recorders (namely the db recorders) add parameters to the segments.
198
+ transaction.trace.generateSpanEvents()
199
+
196
200
  transaction.agent.emit('transactionFinished', transaction)
197
201
  if (typeof done === 'function') {
198
202
  done(transaction)
@@ -49,30 +49,48 @@ function Trace(transaction) {
49
49
  * segments that support recording.
50
50
  */
51
51
  Trace.prototype.end = function end() {
52
+ var segments = [this.root]
53
+
54
+ while (segments.length) {
55
+ var segment = segments.pop()
56
+ _softEndSegment(segment)
57
+ Array.prototype.push.apply(segments, segment.getChildren())
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Iterates over the trace tree and generates a span event for each segment.
63
+ */
64
+ Trace.prototype.generateSpanEvents = function generateSpanEvents() {
52
65
  var config = this.transaction.agent.config
53
- var segments = []
54
- var processFn
55
66
 
56
67
  if (
57
68
  this.transaction.sampled &&
58
69
  config.span_events.enabled &&
59
70
  config.distributed_tracing.enabled
60
71
  ) {
72
+ var toProcess = []
61
73
  // Root segment does not become a span, so we need to process it separately.
62
- _softEndSegment(this.root)
74
+ const spanAggregator = this.transaction.agent.spans
63
75
  const children = this.root.getChildren()
64
76
  for (let i = 0; i < children.length; ++i) {
65
- segments.push(new DTTraceNode(children[i], this.transaction.parentSpanId))
77
+ toProcess.push(new DTTraceNode(children[i], this.transaction.parentSpanId))
66
78
  }
67
- processFn = makeDTNodeProcessor(this.transaction.agent.spans)
68
- } else {
69
- segments.push(this.root)
70
- processFn = processNode
71
- }
72
79
 
73
- while (segments.length) {
74
- var segment = segments.pop()
75
- processFn(segment, segments)
80
+ while (toProcess.length) {
81
+ var segmentInfo = toProcess.pop()
82
+ var segment = segmentInfo.segment
83
+
84
+ // Even though at some point we might want to stop adding events
85
+ // because all the priorities should be the same, we need to count
86
+ // the spans as seen.
87
+ spanAggregator.addSegment(segment, segmentInfo.parentId)
88
+
89
+ Array.prototype.push.apply(
90
+ toProcess,
91
+ segment.getChildren().map(child => new DTTraceNode(child, segment.id))
92
+ )
93
+ }
76
94
  }
77
95
  }
78
96
 
@@ -90,31 +108,6 @@ function _softEndSegment(segment) {
90
108
  }
91
109
  }
92
110
 
93
- function makeDTNodeProcessor(spanAggregator) {
94
- var addMoreSpans = true
95
- return function processNodeDT(segmentInfo, segments) {
96
- var segment = segmentInfo.segment
97
- _softEndSegment(segment)
98
-
99
- // This assumes that spans will have their transaction's priority. If insertion of
100
- // one event fails, all following will fail due to having the same priority.
101
- if (addMoreSpans && segment.name !== 'ROOT') {
102
- addMoreSpans = spanAggregator.addSegment(segment, segmentInfo.parentId)
103
- }
104
-
105
- var children = segment.getChildren()
106
- Array.prototype.push.apply(
107
- segments,
108
- children.map(child => new DTTraceNode(child, segment.id))
109
- )
110
- }
111
- }
112
-
113
- function processNode(segment, segments) {
114
- _softEndSegment(segment)
115
- Array.prototype.push.apply(segments, segment.getChildren())
116
- }
117
-
118
111
  /**
119
112
  * Add a child to the list of segments.
120
113
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "newrelic",
3
- "version": "4.6.0",
3
+ "version": "4.7.0",
4
4
  "author": "New Relic Node.js agent team <nodejs@newrelic.com>",
5
5
  "licenses": [
6
6
  {
@@ -96,7 +96,7 @@
96
96
  ],
97
97
  "homepage": "http://github.com/newrelic/node-newrelic",
98
98
  "engines": {
99
- "node": ">=4.0.0",
99
+ "node": ">=4.0.0 <11.0.0",
100
100
  "npm": ">=2.0.0"
101
101
  },
102
102
  "directories": {