newrelic 8.2.0 → 8.3.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.
@@ -46,7 +46,6 @@ code, the source code can be found at [https://github.com/newrelic/node-newrelic
46
46
  * [eslint-plugin-prettier](#eslint-plugin-prettier)
47
47
  * [eslint](#eslint)
48
48
  * [express](#express)
49
- * [fastify](#fastify)
50
49
  * [generic-pool](#generic-pool)
51
50
  * [glob](#glob)
52
51
  * [got](#got)
@@ -2348,38 +2347,6 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2348
2347
 
2349
2348
  ```
2350
2349
 
2351
- ### fastify
2352
-
2353
- This product includes source derived from [fastify](https://github.com/fastify/fastify) ([v2.15.3](https://github.com/fastify/fastify/tree/v2.15.3)), distributed under the [MIT License](https://github.com/fastify/fastify/blob/v2.15.3/LICENSE):
2354
-
2355
- ```
2356
- MIT License
2357
-
2358
- Copyright (c) 2016-2020 The Fastify Team
2359
-
2360
- The Fastify team members are listed at https://github.com/fastify/fastify#team
2361
- and in the README file.
2362
-
2363
- Permission is hereby granted, free of charge, to any person obtaining a copy
2364
- of this software and associated documentation files (the "Software"), to deal
2365
- in the Software without restriction, including without limitation the rights
2366
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
2367
- copies of the Software, and to permit persons to whom the Software is
2368
- furnished to do so, subject to the following conditions:
2369
-
2370
- The above copyright notice and this permission notice shall be included in all
2371
- copies or substantial portions of the Software.
2372
-
2373
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
2374
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
2375
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
2376
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
2377
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2378
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2379
- SOFTWARE.
2380
-
2381
- ```
2382
-
2383
2350
  ### generic-pool
2384
2351
 
2385
2352
  This product includes source derived from [generic-pool](https://github.com/coopernurse/node-pool) ([v3.8.2](https://github.com/coopernurse/node-pool/tree/v3.8.2)), distributed under the [MIT License](https://github.com/coopernurse/node-pool/blob/v3.8.2/README.md):
@@ -10,7 +10,6 @@ const fetchSystemInfo = require('../system-info')
10
10
  const logger = require('../logger').child({ component: 'facts' })
11
11
  const os = require('os')
12
12
  const parseLabels = require('../util/label-parser')
13
- const Config = require('../config/')
14
13
 
15
14
  module.exports = facts
16
15
 
@@ -64,7 +63,7 @@ function facts(agent, callback) {
64
63
  analytic_event_data: agent.config.transaction_events.max_samples_stored,
65
64
  custom_event_data: agent.config.custom_insights_events.max_samples_stored,
66
65
  error_event_data: agent.config.error_collector.max_event_samples_stored,
67
- span_event_data: Config.SPAN_EVENT_LIMIT
66
+ span_event_data: agent.config.span_events.max_samples_stored
68
67
  }
69
68
  }
70
69
 
@@ -292,7 +292,7 @@ exports.config = () => ({
292
292
  */
293
293
  capture_events: true,
294
294
  /**
295
- * The agent will collect all error events up to this number per minute.
295
+ * The agent will collect all error events up to this number every 5 seconds.
296
296
  * If there are more than that, a statistical sampling will be collected.
297
297
  * Currently this uses a priority sampling algorithm.
298
298
  *
@@ -301,6 +301,9 @@ exports.config = () => ({
301
301
  * Relic servers. The memory concerns are something you should consider for
302
302
  * your own server's sake. The payload of events is compressed, but if it
303
303
  * grows too large the New Relic servers may reject it.
304
+ *
305
+ * @env NEW_RELIC_ERROR_COLLECTOR_MAX_EVENT_SAMPLES_STORED
306
+ *
304
307
  */
305
308
  max_event_samples_stored: 100,
306
309
 
@@ -648,8 +651,10 @@ exports.config = () => ({
648
651
  */
649
652
  enabled: true,
650
653
  /**
651
- * The agent will collect all events up to this number per minute. If
654
+ * The agent will collect all events up to this number every 5 seconds. If
652
655
  * there are more than that, a statistical sampling will be collected.
656
+ *
657
+ * @env NEW_RELIC_TRANSACTION_EVENTS_MAX_SAMPLES_STORED
653
658
  */
654
659
  max_samples_stored: 10000
655
660
  },
@@ -671,7 +676,7 @@ exports.config = () => ({
671
676
  */
672
677
  enabled: true,
673
678
  /**
674
- * The agent will collect all events up to this number per minute. If there
679
+ * The agent will collect all events up to this number every 5 seconds. If there
675
680
  * are more than that, a statistical sampling will be collected. Currently
676
681
  * this uses a priority sampling algorithm.
677
682
  *
@@ -680,6 +685,9 @@ exports.config = () => ({
680
685
  * Relic servers. The memory concerns are something you should consider for
681
686
  * your own server's sake. The payload of events is compressed, but if it
682
687
  * grows too large the New Relic servers may reject it.
688
+ *
689
+ * @env NEW_RELIC_CUSTOM_INSIGHTS_EVENTS_MAX_SAMPLES_STORED
690
+ *
683
691
  */
684
692
  max_samples_stored: 1000
685
693
  },
@@ -795,7 +803,14 @@ exports.config = () => ({
795
803
  * @env NEW_RELIC_SPAN_EVENTS_ATTRIBUTES_INCLUDE
796
804
  */
797
805
  include: []
798
- }
806
+ },
807
+ /**
808
+ * The agent will collect all events up to this number per minute. If
809
+ * there are more than that, a statistical sampling will be collected.
810
+ *
811
+ * @env NEW_RELIC_SPAN_EVENTS_MAX_SAMPLES_STORED
812
+ */
813
+ max_samples_stored: 2000
799
814
  },
800
815
 
801
816
  /**
@@ -840,7 +855,7 @@ exports.config = () => ({
840
855
  *
841
856
  * @env NEW_RELIC_DISTRIBUTED_TRACING_ENABLED
842
857
  */
843
- enabled: false,
858
+ enabled: true,
844
859
 
845
860
  /**
846
861
  * Excludes New Relic format distributed tracing header (`newrelic`) on
package/lib/config/env.js CHANGED
@@ -44,7 +44,8 @@ const ENV_MAPPING = {
44
44
  ignore_status_codes: 'NEW_RELIC_ERROR_COLLECTOR_IGNORE_ERROR_CODES',
45
45
  ignore_classes: 'NEW_RELIC_ERROR_COLLECTOR_IGNORE_ERRORS',
46
46
  expected_status_codes: 'NEW_RELIC_ERROR_COLLECTOR_EXPECTED_ERROR_CODES',
47
- expected_classes: 'NEW_RELIC_ERROR_COLLECTOR_EXPECTED_ERRORS'
47
+ expected_classes: 'NEW_RELIC_ERROR_COLLECTOR_EXPECTED_ERRORS',
48
+ max_event_samples_stored: 'NEW_RELIC_ERROR_COLLECTOR_MAX_EVENT_SAMPLES_STORED'
48
49
  },
49
50
  strip_exception_messages: {
50
51
  enabled: 'NEW_RELIC_STRIP_EXCEPTION_MESSAGES_ENABLED'
@@ -54,7 +55,11 @@ const ENV_MAPPING = {
54
55
  enabled: 'NEW_RELIC_TRANSACTION_EVENTS_ATTRIBUTES_ENABLED',
55
56
  exclude: 'NEW_RELIC_TRANSACTION_EVENTS_ATTRIBUTES_EXCLUDE',
56
57
  include: 'NEW_RELIC_TRANSACTION_EVENTS_ATTRIBUTES_INCLUDE'
57
- }
58
+ },
59
+ max_samples_stored: 'NEW_RELIC_TRANSACTION_EVENTS_MAX_SAMPLES_STORED'
60
+ },
61
+ custom_insights_events: {
62
+ max_samples_stored: 'NEW_RELIC_CUSTOM_INSIGHTS_EVENTS_MAX_SAMPLES_STORED'
58
63
  },
59
64
  transaction_tracer: {
60
65
  attributes: {
@@ -123,7 +128,8 @@ const ENV_MAPPING = {
123
128
  enabled: 'NEW_RELIC_SPAN_EVENTS_ATTRIBUTES_ENABLED',
124
129
  exclude: 'NEW_RELIC_SPAN_EVENTS_ATTRIBUTES_EXCLUDE',
125
130
  include: 'NEW_RELIC_SPAN_EVENTS_ATTRIBUTES_INCLUDE'
126
- }
131
+ },
132
+ max_samples_stored: 'NEW_RELIC_SPAN_EVENTS_MAX_SAMPLES_STORED'
127
133
  },
128
134
  transaction_segments: {
129
135
  attributes: {
@@ -242,7 +248,11 @@ const FLOAT_VARS = new Set(['NEW_RELIC_APDEX_T', 'NEW_RELIC_TRACER_THRESHOLD'])
242
248
  const INT_VARS = new Set([
243
249
  'NEW_RELIC_EXPLAIN_THRESHOLD',
244
250
  'NEW_RELIC_MAX_SQL_SAMPLES',
245
- 'NEW_RELIC_INFINITE_TRACING_SPAN_EVENTS_QUEUE_SIZE'
251
+ 'NEW_RELIC_INFINITE_TRACING_SPAN_EVENTS_QUEUE_SIZE',
252
+ 'NEW_RELIC_SPAN_EVENTS_MAX_SAMPLES_STORED',
253
+ 'NEW_RELIC_CUSTOM_INSIGHTS_EVENTS_MAX_SAMPLES_STORED',
254
+ 'NEW_RELIC_TRANSACTION_EVENTS_MAX_SAMPLES_STORED',
255
+ 'NEW_RELIC_ERROR_COLLECTOR_MAX_EVENT_SAMPLES_STORED'
246
256
  ])
247
257
 
248
258
  exports.ENV_MAPPING = ENV_MAPPING
@@ -29,7 +29,6 @@ const mergeServerConfig = new MergeServerConfig()
29
29
  const AZURE_APP_NAME = 'APP_POOL_ID'
30
30
  const DEFAULT_MAX_PAYLOAD_SIZE_IN_BYTES = 1000000
31
31
  const DEFAULT_CONFIG_PATH = require.resolve('./default')
32
- const SPAN_EVENT_LIMIT = 1000
33
32
  const BASE_CONFIG_PATH = require.resolve('../../newrelic')
34
33
  const HAS_ARBITRARY_KEYS = new Set(['ignore_messages', 'expected_messages', 'labels'])
35
34
 
@@ -183,7 +182,7 @@ function Config(config) {
183
182
  analytic_event_data: this.transaction_events.max_samples_stored,
184
183
  custom_event_data: this.custom_insights_events.max_samples_stored,
185
184
  error_event_data: this.error_collector.max_event_samples_stored,
186
- span_event_data: SPAN_EVENT_LIMIT
185
+ span_event_data: this.span_events.max_samples_stored
187
186
  }
188
187
  }
189
188
  }
@@ -544,6 +543,13 @@ Config.prototype._fromServer = function _fromServer(params, key) {
544
543
  this.logUnsupported(params, key)
545
544
  break
546
545
 
546
+ // DT span event harvest config limits
547
+ case 'span_event_harvest_config':
548
+ this.span_event_harvest_config = {
549
+ ...params[key]
550
+ }
551
+ break
552
+
547
553
  // These settings are not allowed from the server.
548
554
  case 'attributes.enabled':
549
555
  case 'attributes.exclude':
@@ -1711,6 +1717,5 @@ Config.initialize = initialize
1711
1717
  Config.getOrCreateInstance = getOrCreateInstance
1712
1718
  Config.getInstance = getInstance
1713
1719
  Config.createInstance = createInstance
1714
- Config.SPAN_EVENT_LIMIT = SPAN_EVENT_LIMIT
1715
1720
 
1716
1721
  module.exports = Config
@@ -43,12 +43,6 @@ const setupRouteHandler = (shim, fastify) => {
43
43
  }
44
44
 
45
45
  const setupMiddlewareHandlers = (shim, fastify) => {
46
- // Don't wrap use() in fastify v3+
47
- const fastifyVersion = shim.require('./package.json').version
48
- if (semver.satisfies(fastifyVersion, '>=3.0.0')) {
49
- return
50
- }
51
-
52
46
  shim.wrap(fastify, 'use', function wrapFastifyUse(shim, fn) {
53
47
  return function wrappedFastifyUser() {
54
48
  const args = shim.argsToArray.apply(shim, arguments)
@@ -70,10 +64,14 @@ module.exports = function initialize(agent, fastify, moduleName, shim) {
70
64
  return
71
65
  }
72
66
  shim.setFramework(shim.FASTIFY)
67
+
68
+ const fastifyVersion = shim.require('./package.json').version
69
+ const isv3Plus = semver.satisfies(fastifyVersion, '>=3.0.0')
70
+
73
71
  /**
74
72
  * Fastify exports a function, so we need to use wrapExport
75
73
  */
76
- shim.wrapExport(fastify, function wrapFastifyModule(shim, fn) {
74
+ const wrappedExport = shim.wrapExport(fastify, function wrapFastifyModule(shim, fn) {
77
75
  return function wrappedFastifyModule() {
78
76
  // normalize arguments
79
77
  const args = shim.argsToArray.apply(shim, arguments)
@@ -83,9 +81,33 @@ module.exports = function initialize(agent, fastify, moduleName, shim) {
83
81
 
84
82
  setupRouteHandler(shim, fastifyForWrapping)
85
83
 
86
- setupMiddlewareHandlers(shim, fastifyForWrapping)
84
+ // Don't wrap use() in fastify v3+
85
+ if (!isv3Plus) {
86
+ setupMiddlewareHandlers(shim, fastifyForWrapping)
87
+ }
87
88
 
88
89
  return fastifyForWrapping
89
90
  }
90
91
  })
92
+
93
+ if (isv3Plus) {
94
+ setupExports(fastify, wrappedExport)
95
+ }
96
+ }
97
+
98
+ /**
99
+ * module.exports = fastify
100
+ * module.exports.fastify = fastify
101
+ * module.exports.default = fastify
102
+ */
103
+ function setupExports(original, wrappedExport) {
104
+ wrappedExport.fastify = original.fastify
105
+
106
+ if (original.fastify) {
107
+ wrappedExport.fastify = wrappedExport
108
+ }
109
+
110
+ if (original.default) {
111
+ wrappedExport.default = wrappedExport
112
+ }
91
113
  }
@@ -5,9 +5,8 @@
5
5
 
6
6
  'use strict'
7
7
  const { CURSOR_OPS, COLLECTION_OPS, DB_OPS } = require('./constants')
8
- const { URL } = require('url')
9
-
10
8
  const common = module.exports
9
+ common.NR_ATTRS = Symbol('NR_ATTRS')
11
10
 
12
11
  /**
13
12
  * Instruments all methods from constants.CURSOR_OPS on a given
@@ -24,7 +23,7 @@ common.instrumentCursor = function instrumentCursor(shim, Cursor) {
24
23
  }
25
24
 
26
25
  shim.recordQuery(proto, 'each', common.makeQueryDescFunc(shim, 'each'))
27
- shim.recordOperation(proto, 'pipe')
26
+ shim.recordOperation(proto, 'pipe', { opaque: true })
28
27
  }
29
28
  }
30
29
 
@@ -97,18 +96,16 @@ common.captureAttributesOnStarted = function captureAttributesOnStarted(shim, in
97
96
  // out of an excess of caution.
98
97
  const connId = evnt.connectionId
99
98
  if (connId) {
100
- // Mongo sticks the path to the domain socket in the "host" slot, but we
101
- // want it in the "port", so if we have a domain socket we need to change
102
- // the order of our parameters.
103
- if (typeof connId === 'string') {
104
- const parts = connId.split(':')
105
- if (parts.length && parts[0][0] === '/') {
106
- shim.captureInstanceAttributes('localhost', parts[0], evnt.databaseName)
107
- } else {
108
- shim.captureInstanceAttributes(parts[0], parts[1], evnt.databaseName)
109
- }
99
+ // used in v3 when connection is a cluster pool
100
+ if (typeof connId === 'number') {
101
+ setHostPort(shim, evnt.address, evnt.databaseName, this.$MongoClient)
102
+ // used in v3 when connection is to 1 host
103
+ } else if (typeof connId === 'string') {
104
+ setHostPort(shim, connId, evnt.databaseName)
105
+ // v2 contains `domainSocket`, get socket connection from `host`
110
106
  } else if (connId.domainSocket) {
111
107
  shim.captureInstanceAttributes('localhost', connId.host, evnt.databaseName)
108
+ // v2 remote connection get `host` `port` from respective properties
112
109
  } else {
113
110
  shim.captureInstanceAttributes(connId.host, connId.port, evnt.databaseName)
114
111
  }
@@ -116,6 +113,41 @@ common.captureAttributesOnStarted = function captureAttributesOnStarted(shim, in
116
113
  })
117
114
  }
118
115
 
116
+ /**
117
+ * Extracts the host and port from a connection string
118
+ * This also handles if connection string is a domain socket
119
+ * Mongo sticks the path to the domain socket in the "host" slot, but we
120
+ * want it in the "port", so if we have a domain socket we need to change
121
+ * the order of our parameters.
122
+ *
123
+ * @param {Shim} shim
124
+ * @param {string} connStr
125
+ * @param {string} db database name
126
+ * @param {Object} client mongo client instance
127
+ */
128
+ function setHostPort(shim, connStr, db, client) {
129
+ const parts = connStr.split(':')
130
+ // in v3 when running with a cluster of socket connections
131
+ // the address is `undefined:undefined`. we will instead attempt
132
+ // to get connection details from the client symbol NR_ATTRS
133
+ // added in `lib/instrumentation/mongodb/v3-mongo` when a client connects
134
+ // with a URL string
135
+ if (parts.includes('undefined')) {
136
+ try {
137
+ const attrs = client[common.NR_ATTRS]
138
+ const socket = decodeURIComponent(attrs.split(',')[0].split('mongodb://')[1])
139
+ shim.captureInstanceAttributes('localhost', socket, db)
140
+ } catch (err) {
141
+ shim.logger.debug(err, 'Could not extract host/port from mongo command')
142
+ }
143
+ // connected using domain socket but the "host"(e.g: /path/to/mongo-socket-port.sock)
144
+ } else if (parts.length && parts[0][0] === '/') {
145
+ shim.captureInstanceAttributes('localhost', parts[0], db)
146
+ } else {
147
+ shim.captureInstanceAttributes(parts[0], parts[1], db)
148
+ }
149
+ }
150
+
119
151
  /**
120
152
  * Get the database_name, host, port_path_or_id
121
153
  * for the query segment. v4 refactored where the toplogy is stored.
@@ -136,13 +168,15 @@ function getInstanceAttributeParameters(shim, obj) {
136
168
  obj.s.db.s &&
137
169
  obj.s.db.s.client &&
138
170
  obj.s.db.s.client.s &&
139
- obj.s.db.s.client.s.url
171
+ obj.s.db.s.client.s.options &&
172
+ obj.s.db.s.client.s.options.hosts &&
173
+ obj.s.db.s.client.s.options.hosts.length
140
174
  ) {
141
- let { hostname: host, port } = new URL(obj.s.db.s.client.s.url)
142
- if (host.endsWith('.sock')) {
143
- // socket path is uri encoded
144
- // decode it to properly name attrs
145
- port = decodeURIComponent(host)
175
+ // hosts is an array but we will always pull the first for consistency
176
+ let [{ host, port, socketPath }] = obj.s.db.s.client.s.options.hosts
177
+
178
+ if (socketPath) {
179
+ port = socketPath
146
180
  host = 'localhost'
147
181
  }
148
182
  return {
@@ -160,12 +194,17 @@ function getInstanceAttributeParameters(shim, obj) {
160
194
  }
161
195
 
162
196
  function doCapture(conf, database) {
163
- let host = conf.host
164
- let port = conf.port
197
+ let host = null
198
+ let port = null
199
+
200
+ // servers is an array but we will always pull the first for consistency
201
+ if (conf.servers && conf.servers.length) {
202
+ ;[{ host, port }] = conf.servers
203
+ }
165
204
 
166
- // If using a domain socket, mongo stores the path as the host name, but we
167
- // pass it through the port value.
168
- if ((conf.socketOptions && conf.socketOptions.domainSocket) || /\.sock$/.test(host)) {
205
+ // host is a domain socket. set host as localhost and use the domain
206
+ // socket host as the port
207
+ if (host && host.endsWith('.sock')) {
169
208
  port = host
170
209
  host = 'localhost'
171
210
  }
@@ -9,7 +9,8 @@ const {
9
9
  captureAttributesOnStarted,
10
10
  instrumentCollection,
11
11
  instrumentCursor,
12
- instrumentDb
12
+ instrumentDb,
13
+ NR_ATTRS
13
14
  } = require('./common')
14
15
 
15
16
  /**
@@ -30,6 +31,23 @@ function queryParser(operation) {
30
31
  return { operation, collection }
31
32
  }
32
33
 
34
+ /**
35
+ * Records the `mongo.MongoClient.connect` operations. It also adds the first arg of connect(url)
36
+ * to a Symbol on the MongoClient to be used later to extract the host/port in cases where the topology
37
+ * is a cluster of domain sockets
38
+ *
39
+ * @param {Shim} shim
40
+ * @param {Object} mongodb resolved package
41
+ */
42
+ function instrumentClient(shim, mongodb) {
43
+ shim.recordOperation(mongodb.MongoClient, 'connect', function wrappedConnect(shim, _, __, args) {
44
+ // Add the connection url to the MongoClient to retrieve later in the `lib/instrumentation/mongo/common`
45
+ // captureAttributesOnStarted listener
46
+ this[NR_ATTRS] = args[0]
47
+ return { callback: shim.LAST }
48
+ })
49
+ }
50
+
33
51
  /**
34
52
  * Registers relevant instrumentation for mongo >= 3.0.6
35
53
  * In 3.0.6 they refactored their "APM" module which removed
@@ -44,6 +62,7 @@ function queryParser(operation) {
44
62
  */
45
63
  module.exports = function instrument(shim, mongodb) {
46
64
  shim.setParser(queryParser)
65
+ instrumentClient(shim, mongodb)
47
66
  const instrumenter = mongodb.instrument(Object.create(null), () => {})
48
67
  captureAttributesOnStarted(shim, instrumenter)
49
68
  instrumentCursor(shim, mongodb.Cursor)
@@ -36,15 +36,14 @@ function cmdStartedHandler(shim, evnt) {
36
36
  if (evnt.connectionId) {
37
37
  let [host, port] = evnt.address.split(':')
38
38
 
39
- // connection via socket get port from client.url
40
- // which looks like "mongodb://%2Ftmp%2Fmongodb-27017.sock"
39
+ // connection via socket get port from 1st host
40
+ // socketPath property
41
+ // which looks like mongodb:///tmp/mongodb-27017.sock"
41
42
  if (['undefined'].includes(host, port)) {
42
43
  host = 'localhost'
43
- // socket path is uri encoded
44
- // decode it to properly name attrs
45
- const socketPath = this.s.url.split(':')
46
- if (socketPath.length && socketPath[1].startsWith('//')) {
47
- port = decodeURIComponent(socketPath[1].substr(2))
44
+ const hosts = this.s.options.hosts
45
+ if (hosts && hosts.length && hosts[0].socketPath) {
46
+ port = hosts[0].socketPath
48
47
  }
49
48
  } else if (host === '127.0.0.1') {
50
49
  host = 'localhost'
@@ -230,10 +230,13 @@ const DISTRIBUTED_TRACE = {
230
230
  TRANSPORT: 'TransportDuration'
231
231
  }
232
232
 
233
+ const SPAN_EVENT_PREFIX = 'SpanEvent/'
234
+
233
235
  const SPAN_EVENTS = {
234
- SEEN: SUPPORTABILITY.PREFIX + 'SpanEvent/TotalEventsSeen',
235
- SENT: SUPPORTABILITY.PREFIX + 'SpanEvent/TotalEventsSent',
236
- DROPPED: SUPPORTABILITY.PREFIX + 'SpanEvent/Discarded'
236
+ SEEN: SUPPORTABILITY.PREFIX + SPAN_EVENT_PREFIX + 'TotalEventsSeen',
237
+ SENT: SUPPORTABILITY.PREFIX + SPAN_EVENT_PREFIX + 'TotalEventsSent',
238
+ DROPPED: SUPPORTABILITY.PREFIX + SPAN_EVENT_PREFIX + 'Discarded',
239
+ LIMIT: SUPPORTABILITY.PREFIX + SPAN_EVENT_PREFIX + 'Limit'
237
240
  }
238
241
 
239
242
  const INFINITE_TRACING = {
@@ -5,12 +5,14 @@
5
5
 
6
6
  'use strict'
7
7
 
8
- const logger = require('../logger')
9
- let spanLogger = null
8
+ const logger = require('../logger').child({ component: 'span_aggregator' })
10
9
  const EventAggregator = require('../aggregators/event-aggregator')
11
10
  const SpanEvent = require('./span-event')
12
11
  const NAMES = require('../metrics/names')
13
- const LIMIT = 1000
12
+
13
+ const DEFAULT_SPAN_EVENT_LIMIT = 2000
14
+ // Used only when server value missing
15
+ const SPAN_EVENT_FALLBACK_MAX_LIMIT = 10000
14
16
 
15
17
  class SpanEventAggregator extends EventAggregator {
16
18
  constructor(opts, collector, metrics) {
@@ -18,8 +20,6 @@ class SpanEventAggregator extends EventAggregator {
18
20
  opts.method = opts.method || 'span_event_data'
19
21
  opts.metricNames = opts.metricNames || NAMES.SPAN_EVENTS
20
22
 
21
- spanLogger = logger.child({ component: 'span_aggregator' })
22
-
23
23
  super(opts, collector, metrics)
24
24
  }
25
25
 
@@ -27,7 +27,7 @@ class SpanEventAggregator extends EventAggregator {
27
27
  const events = this.events
28
28
 
29
29
  if (events.length === 0) {
30
- spanLogger.debug('No span events to send.')
30
+ logger.debug('No span events to send.')
31
31
  return
32
32
  }
33
33
 
@@ -46,8 +46,8 @@ class SpanEventAggregator extends EventAggregator {
46
46
  }
47
47
 
48
48
  send() {
49
- if (spanLogger.traceEnabled()) {
50
- spanLogger.trace(
49
+ if (logger.traceEnabled()) {
50
+ logger.trace(
51
51
  {
52
52
  spansCollected: this.length,
53
53
  spansSeen: this.seen
@@ -64,7 +64,7 @@ class SpanEventAggregator extends EventAggregator {
64
64
  * @param {TraceSegment} segment - The segment to add.
65
65
  * @param {string} [parentId=null] - The GUID of the parent span.
66
66
  *
67
- * @return {bool} True if the segment was added, or false if it was discarded.
67
+ * @return {boolean} True if the segment was added, or false if it was discarded.
68
68
  */
69
69
  addSegment(segment, parentId, isRoot) {
70
70
  // Check if the priority would be accepted before creating the event object.
@@ -79,7 +79,59 @@ class SpanEventAggregator extends EventAggregator {
79
79
  const span = SpanEvent.fromSegment(segment, parentId || null, isRoot)
80
80
  return this.add(span, tx.priority)
81
81
  }
82
+
83
+ /**
84
+ * Reconfigure the `SpanEventAggregator` based on values from server
85
+ *
86
+ * @param {Config} config
87
+ */
88
+ reconfigure(config) {
89
+ super.reconfigure(config)
90
+
91
+ const { periodMs, limit } = this._getValidSpanConfiguration(config)
92
+
93
+ this.periodMs = periodMs
94
+ this.limit = limit
95
+ this._metrics.getOrCreateMetric(this._metricNames.LIMIT).recordValue(this.limit)
96
+ this._items.setLimit(this.limit)
97
+ }
98
+
99
+ /**
100
+ * Retrieves report period and harvest limits defined in `span_event_harvest_config`.
101
+ * When no `span_event_harvest_config` has been received from the server, applies an
102
+ * agent-defined fallback maximum to protect against collecting and sending too many spans.
103
+ *
104
+ * @param {Config} config
105
+ */
106
+ _getValidSpanConfiguration(config) {
107
+ const spanHarvestConfig = config.span_event_harvest_config
108
+ if (spanHarvestConfig) {
109
+ logger.trace('Using span_event_harvest_config values.')
110
+
111
+ return {
112
+ periodMs: spanHarvestConfig.report_period_ms,
113
+ limit: spanHarvestConfig.harvest_limit
114
+ }
115
+ }
116
+
117
+ const configuredLimit = config.span_events.max_samples_stored || DEFAULT_SPAN_EVENT_LIMIT
118
+
119
+ return {
120
+ periodMs: this.defaultPeriod,
121
+ limit: _enforceMaxLimit(configuredLimit, SPAN_EVENT_FALLBACK_MAX_LIMIT)
122
+ }
123
+ }
124
+ }
125
+
126
+ function _enforceMaxLimit(currentLimit, maxLimit) {
127
+ let spanLimit = currentLimit
128
+ if (spanLimit > maxLimit) {
129
+ spanLimit = maxLimit
130
+
131
+ logger.debug('Using maximum allowed span event limit of %s', maxLimit)
132
+ }
133
+
134
+ return spanLimit
82
135
  }
83
136
 
84
137
  module.exports = SpanEventAggregator
85
- module.exports.LIMIT = LIMIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "newrelic",
3
- "version": "8.2.0",
3
+ "version": "8.3.0",
4
4
  "author": "New Relic Node.js agent team <nodejs@newrelic.com>",
5
5
  "license": "Apache-2.0",
6
6
  "contributors": [
@@ -186,7 +186,6 @@
186
186
  "eslint-plugin-node": "^11.1.0",
187
187
  "eslint-plugin-prettier": "^3.4.0",
188
188
  "express": "*",
189
- "fastify": "^2.15.3",
190
189
  "generic-pool": "^3.6.1",
191
190
  "glob": "^7.1.2",
192
191
  "got": "^8.0.1",