newrelic 7.2.1 → 7.5.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,19 +1,87 @@
1
+ ### v7.5.0 (2021-06-01)
2
+
3
+ * Added default support for config files with a 'cjs' extension (`newrelic.cjs`) in addition to `newrelic.js`.
4
+
5
+ Thank you to @Maddemacher for the contribution!
6
+
7
+ * Added ability to specify a custom config file name with the `NEW_RELIC_CONFIG_FILENAME` environment variable.
8
+
9
+ Thank you to @Maddemacher for the contribution!
10
+
11
+ * Fixed issue when using the 'new_promise_tracking' feature flag where segment mapping may not get cleaned up for promises which never resolve but have all references removed (and thus get cleaned up by GC).
12
+
13
+ Adds segment cleanup on 'destroy' when using 'new_promise_tracking' feature flag in addition to the existing 'promiseResolve' hook. Unfortunately, preventing leaks for this edge-case does come with additional overhead due to adding another hook. Memory gains from feature flag usage should still be worth the trade-off and reduced garbage collection may offset perf/CPU impacts or event still result in net gain, depending on the application.
14
+
15
+ * Bumped `@newrelic/test-utilities` to ^5.1.0.
16
+
17
+ * Replaced deprecated `util.isArray` with `Array.isArray`.
18
+
19
+ * Removed unused `listenerCount` method on `Shim`.
20
+
21
+ * Properly bootstraped husky as a `prepare` script.
22
+
23
+ * Removed commented-out console log from fastify instrumentation.
24
+
25
+ ### v7.4.0 (2021-05-11)
26
+
27
+ * Updated third party notices and manifest for husky and lint-staged.
28
+
29
+ * Updated redis versioned tests to use unique DB indexes per file to avoid collisions and flushing of in-progress tests.
30
+
31
+ * Pinned hapi 17 versioned tests to only minor/patch versions within 17.x.
32
+
33
+ * Bumped timeout for redis versioned tests.
34
+
35
+ * Wired up husky + lint staged to execute linting on all changed files in pre-commit hook.
36
+
37
+ * Handled a proxy misconfiguration of collector and log an actionable warning message.
38
+
39
+ * Added `flaky_code` and `success_delay_ms` handling of flaky grpc connections to infinite tracing.
40
+
41
+ * Added resources to README to highlight external modules that customers should be aware of and possibly use for their applications.
42
+
43
+ * Logged all New Relic metadata env vars at startup.
44
+
45
+ * Fixed images for improved reader experience.
46
+
47
+ Thank you to @henryjw for the contribution.
48
+
49
+ ### v7.3.1 (2021-04-14)
50
+
51
+ * Fixed issue with 'new_promise_tracking' feature flag functionality where segments for ended transactions would get propagated in certain cases by promises that had no continuations scheduled (via await or manually).
52
+
53
+ If you are experiencing high overhead levels with your promise usage and the agent attached, we recommend testing your application with 'new_promise_tracking' set to true to see if overhead is reduced. You'll also want to verify your data is still being captured correctly in case it falls into a known or unknown limitation of this approach. **NOTE: chaining of promise continuations onto an already resolved promise across an async hop (scheduled timer) will result in state-loss with this new functionality turned on. This is a less-common use-case but worth considering with your applications.**
54
+
55
+ **Deprecation Warning:** The certificate bundle automatically included by New Relic when using the 'certificates' configuration (commonly with proxies) will be disabled by default in the next major version. This is currently targeted for sometime in May. The bundle will be fully removed in later major versions. We recommend testing with the 'certificate_bundle' feature flag set to `false` to determine if you will need to modify your environment or setup your own appropriate bundle. Example configuration: `feature_flag: { certificate_bundle: false }`.
56
+
57
+ ### v7.3.0 (2021-04-06)
58
+
59
+ * Added new feature-flag 'new_promise_tracking' which enables cleaning up of segment references on native promise resolve instead of destroy. Includes usage of async-await. This can be enabled via `feature_flag: { new_promise_tracking: true }` in the config file or `NEW_RELIC_FEATURE_FLAG_NEW_PROMISE_TRACKING=1` in your ENV vars.
60
+
61
+ Applications with heavy promise usage or high-throughput applications with some promise usage should see moderate to high reduction in memory usage and may see a slight reduction in CPU usage. A bump in throughput may also be noticed in some cases. Results will vary by application.
62
+
63
+ If you are experiencing high overhead levels with your promise usage and the agent attached, we recommend testing your application with 'new_promise_tracking' set to true to see if overhead is reduced. You'll also want to verify your data is still being captured correctly in case it falls into a known or unknown limitation of this approach. **NOTE: chaining of promise continuations onto an already resolved promise across an async hop (scheduled timer) will result in state-loss with this new functionality turned on. This is a less-common use-case but worth considering with your applications.**
64
+
65
+ * Fixed memory leak introduced when Infinite Tracing is enabled.
66
+
67
+ When Infinite Tracing endpoints reconnected they would instantiate a new gRPC client prior to calling `client.recordSpan()`. It appears several objects created by grpc-js (`ChannelImplementation` and child objects, promises, etc.) are held in memory indefinitely due to scheduled timers even when the client is no-longer referenced and the associated stream closed. We now avoid this situation by only creating the client once and then reusing it to establish new stream connections.
68
+
1
69
  ### v7.2.1 (2021-03-29)
2
70
 
3
71
  * Dev-only sub-dependency bump of 'y18n' to clear npm audit warnings.
4
72
 
5
- * Bumped @grpc/grpc-js to ^1.2.11.
6
-
73
+ * Bumped @grpc/grpc-js to ^1.2.11.
74
+
7
75
  * Bumped @grpc/proto-loader to ^0.5.6.
8
76
 
9
- * Agent no longer propagates segments for promises via async-hooks when the transaction associated with the parentSegment has ended.
10
-
77
+ * Agent no longer propagates segments for promises via async-hooks when the transaction associated with the parentSegment has ended.
78
+
11
79
  This change reduces the amount of context tracking work needed for certain rare edge-case scenarios involving promises.
12
80
 
13
- * Fixed issue where capturing axios request errors could result in a memory leak.
14
-
15
- The agent now clears error references on transaction end, which are not used for later processing. Errors returned from 'axios' requests contain a reference to the request object which deeper down has a handle to a promise in `handleRequestError`. The TraceSegment associated with that promise has a handle to the transaction, which through the error capture ultimately kept the promise in memory and prevented it from being destroyed to free-up the TraceSegment from the segment map. This change also has the benefit of freeing up some memory early for transactions held onto for transaction traces.
16
-
81
+ * Fixed issue where capturing axios request errors could result in a memory leak.
82
+
83
+ The agent now clears error references on transaction end, which are not used for later processing. Errors returned from 'axios' requests contain a reference to the request object which deeper down has a handle to a promise in `handleRequestError`. The TraceSegment associated with that promise has a handle to the transaction, which through the error capture ultimately kept the promise in memory and prevented it from being destroyed to free-up the TraceSegment from the segment map. This change also has the benefit of freeing up some memory early for transactions held onto for transaction traces.
84
+
17
85
  * Added active transaction check to `wrappedResEnd` to prevent unecessary work for ended transactions in the case of multiple `Response.prototype.end()` invocations.
18
86
 
19
87
  ### v7.2.0 (2021-03-23)
package/README.md CHANGED
@@ -52,7 +52,7 @@ To use New Relic's Node.js agent entails these three steps, which are described
52
52
  /* ... the rest of your program ... */
53
53
  ```
54
54
 
55
- If you're compiling your JavaScript and can't control the final `require` order, the Node,js agent will work with node's `-r/--require` flag.
55
+ If you're compiling your JavaScript and can't control the final `require` order, the Node.js agent will work with node's `-r/--require` flag.
56
56
 
57
57
  $ node -r newrelic your-program.js
58
58
  $ node --require newrelic your-program.js
@@ -61,11 +61,26 @@ If you're compiling your JavaScript and can't control the final `require` order,
61
61
 
62
62
  For more information on getting started, [check the Node.js docs](https://docs.newrelic.com/docs/agents/nodejs-agent/getting-started/introduction-new-relic-nodejs).
63
63
 
64
+ ### External Modules
65
+
66
+ There are several modules that can be installed and configured to accompany the Node.js agent:
67
+
68
+ * [@newrelic/apollo-server-plugin](https://github.com/newrelic/newrelic-node-apollo-server-plugin): New Relic's official Apollo Server plugin for use with the Node.js agent.
69
+ * [@newrelic/winston-enricher](https://github.com/newrelic/newrelic-winston-logenricher-node): Provides distributed trace and span information output as JSON-formatted log messages. This is most commonly used with the New Relic Logs product.
70
+ * [@newrelic/mysql](https://github.com/newrelic/node-newrelic-mysql)(**Experimental**): Standalone instrumentation for [mysql2](https://github.com/sidorares/node-mysql2#readme) and [mysql2/promise](https://github.com/sidorares/node-mysql2/blob/master/documentation/Promise-Wrapper.md) as well as the same instrumentation for [mysql](https://github.com/mysqljs/mysql#readme) within the Node.js agent.
71
+
72
+ There are several modules included within the Node.js agent to add more instrumentation for 3rd party modules:
73
+
74
+ * [@newrelic/aws-sdk](https://github.com/newrelic/node-newrelic-aws-sdk): Provides instrumentation for the [AWS SDK](https://www.npmjs.com/package/aws-sdk) npm package.
75
+ * [@newrelic/koa](https://github.com/newrelic/node-newrelic-koa): Provides instrumentation for [koa](https://koajs.com/), [koa-router](https://github.com/ZijianHe/koa-router), [@koa/router](https://github.com/koajs/router), and [koa-route](https://github.com/koajs/route) npm packages.
76
+ * [@newrelic/superagent](https://github.com/newrelic/node-newrelic-superagent): Provides instrumentation for [superagent](https://github.com/visionmedia/superagent) npm package.
77
+ * [@newrelic/native-metrics](https://github.com/newrelic/node-native-metrics): Provides hooks into the native v8 layer of Node.js to provide metrics to the Node.js agent.
78
+
64
79
  ## Usage
65
80
 
66
81
  ### Using the API
67
82
 
68
- The `newrelic` module returns an object with the Node agent's API methods attached.
83
+ The `newrelic` module returns an object with the Node.js agent's API methods attached.
69
84
 
70
85
  ```js
71
86
  const newrelic = require('newrelic')
@@ -49,7 +49,9 @@ code, the source code can be found at [https://github.com/newrelic/node-newrelic
49
49
  * [glob](#glob)
50
50
  * [got](#got)
51
51
  * [http-errors](#http-errors)
52
+ * [husky](#husky)
52
53
  * [jsdoc](#jsdoc)
54
+ * [lint-staged](#lint-staged)
53
55
  * [memcached](#memcached)
54
56
  * [minami](#minami)
55
57
  * [mongodb](#mongodb)
@@ -1927,7 +1929,7 @@ SOFTWARE.
1927
1929
 
1928
1930
  ### commander
1929
1931
 
1930
- This product includes source derived from [commander](https://github.com/tj/commander.js) ([v7.0.0](https://github.com/tj/commander.js/tree/v7.0.0)), distributed under the [MIT License](https://github.com/tj/commander.js/blob/v7.0.0/LICENSE):
1932
+ This product includes source derived from [commander](https://github.com/tj/commander.js) ([v7.2.0](https://github.com/tj/commander.js/tree/v7.2.0)), distributed under the [MIT License](https://github.com/tj/commander.js/blob/v7.2.0/LICENSE):
1931
1933
 
1932
1934
  ```
1933
1935
  (The MIT License)
@@ -2016,7 +2018,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2016
2018
 
2017
2019
  ### fastify
2018
2020
 
2019
- This product includes source derived from [fastify](https://github.com/fastify/fastify) ([v2.14.1](https://github.com/fastify/fastify/tree/v2.14.1)), distributed under the [MIT License](https://github.com/fastify/fastify/blob/v2.14.1/LICENSE):
2021
+ 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):
2020
2022
 
2021
2023
  ```
2022
2024
  MIT License
@@ -2139,6 +2141,35 @@ THE SOFTWARE.
2139
2141
 
2140
2142
  ```
2141
2143
 
2144
+ ### husky
2145
+
2146
+ This product includes source derived from [husky](https://github.com/typicode/husky) ([v6.0.0](https://github.com/typicode/husky/tree/v6.0.0)), distributed under the [MIT License](https://github.com/typicode/husky/blob/v6.0.0/LICENSE):
2147
+
2148
+ ```
2149
+ MIT License
2150
+
2151
+ Copyright (c) 2021 typicode
2152
+
2153
+ Permission is hereby granted, free of charge, to any person obtaining a copy
2154
+ of this software and associated documentation files (the "Software"), to deal
2155
+ in the Software without restriction, including without limitation the rights
2156
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
2157
+ copies of the Software, and to permit persons to whom the Software is
2158
+ furnished to do so, subject to the following conditions:
2159
+
2160
+ The above copyright notice and this permission notice shall be included in all
2161
+ copies or substantial portions of the Software.
2162
+
2163
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
2164
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
2165
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
2166
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
2167
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2168
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2169
+ SOFTWARE.
2170
+
2171
+ ```
2172
+
2142
2173
  ### jsdoc
2143
2174
 
2144
2175
  This product includes source derived from [jsdoc](https://github.com/jsdoc/jsdoc) ([v3.6.3](https://github.com/jsdoc/jsdoc/tree/v3.6.3)), distributed under the [Apache-2.0 License](https://github.com/jsdoc/jsdoc/blob/v3.6.3/LICENSE.md):
@@ -2249,6 +2280,35 @@ https://github.com/jmblog/color-themes-for-google-code-prettify
2249
2280
 
2250
2281
  ```
2251
2282
 
2283
+ ### lint-staged
2284
+
2285
+ This product includes source derived from [lint-staged](https://github.com/okonet/lint-staged) ([v11.0.0](https://github.com/okonet/lint-staged/tree/v11.0.0)), distributed under the [MIT License](https://github.com/okonet/lint-staged/blob/v11.0.0/LICENSE):
2286
+
2287
+ ```
2288
+ The MIT License (MIT)
2289
+
2290
+ Copyright (c) 2016 Andrey Okonetchnikov
2291
+
2292
+ Permission is hereby granted, free of charge, to any person obtaining a copy
2293
+ of this software and associated documentation files (the "Software"), to deal
2294
+ in the Software without restriction, including without limitation the rights
2295
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
2296
+ copies of the Software, and to permit persons to whom the Software is
2297
+ furnished to do so, subject to the following conditions:
2298
+
2299
+ The above copyright notice and this permission notice shall be included in all
2300
+ copies or substantial portions of the Software.
2301
+
2302
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
2303
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
2304
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
2305
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
2306
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2307
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2308
+ SOFTWARE.
2309
+
2310
+ ```
2311
+
2252
2312
  ### memcached
2253
2313
 
2254
2314
  This product includes source derived from [memcached](https://github.com/3rd-Eden/node-memcached) ([v2.2.2](https://github.com/3rd-Eden/node-memcached/tree/v2.2.2)), distributed under the [MIT License](https://github.com/3rd-Eden/node-memcached/blob/v2.2.2/LICENSE):
@@ -117,6 +117,22 @@ CollectorAPI.prototype.connect = function connect(callback) {
117
117
  this._reqHeadersMap = null
118
118
  }
119
119
 
120
+ /**
121
+ * Checks if proxy is configured to connect via `proxy_host` and `proxy_port`
122
+ * and if error code is EPROTO or ECONNRESET. This is an indication their proxy
123
+ * server only accepts HTTP connections, and we should provide an actionable warning to
124
+ * fix the misconfiguration by setting `proxy` to a fully qualified URL
125
+ *
126
+ * @param {Error} error
127
+ * @returns {Boolean}
128
+ */
129
+ function isProxyMisconfigured(error) {
130
+ const config = api._agent.config
131
+ return error &&
132
+ ['EPROTO', 'ECONNRESET'].includes(error.code) &&
133
+ config.proxy_host && config.proxy_port && !config.proxy
134
+ }
135
+
120
136
  function retry(error, response) {
121
137
  metric.incrementCallCount()
122
138
 
@@ -142,6 +158,14 @@ CollectorAPI.prototype.connect = function connect(callback) {
142
158
  ' (status code %s)',
143
159
  response.status
144
160
  )
161
+ } else if (isProxyMisconfigured(error)) {
162
+ logger.warn(
163
+ error,
164
+ 'Your proxy server appears to be configured to accept connections over http. ' +
165
+ 'When setting `proxy_host` and `proxy_port` New Relic attempts to connect over ' +
166
+ 'SSL(https). If your proxy is configured to accept connections over http, try ' +
167
+ 'setting `proxy` to a fully qualified URL(e.g http://proxy-host:8080).'
168
+ )
145
169
  }
146
170
 
147
171
  let backoff = BACKOFFS[Math.min(attempts, max) - 1]
@@ -55,6 +55,8 @@ function facts(agent, callback) {
55
55
  }, {})
56
56
  }
57
57
 
58
+ logger.debug('New Relic metadata %o', results.metadata)
59
+
58
60
  results.event_harvest_config = {
59
61
  harvest_limits: {
60
62
  analytic_event_data: agent.config.transaction_events.max_samples_stored,
@@ -32,14 +32,6 @@ const DEFAULT_MAX_PAYLOAD_SIZE_IN_BYTES = 1000000
32
32
  const DEFAULT_CONFIG_PATH = require.resolve('./default')
33
33
  const SPAN_EVENT_LIMIT = 1000
34
34
  const BASE_CONFIG_PATH = require.resolve('../../newrelic')
35
- const DEFAULT_FILENAME = 'newrelic.js'
36
- const CONFIG_FILE_LOCATIONS = [
37
- process.env.NEW_RELIC_HOME,
38
- process.cwd(),
39
- process.env.HOME,
40
- path.join(__dirname, '../../../..') // above node_modules
41
- ]
42
-
43
35
  const HAS_ARBITRARY_KEYS = new Set([
44
36
  'ignore_messages',
45
37
  'expected_messages',
@@ -54,9 +46,22 @@ const exists = fs.existsSync || path.existsSync
54
46
  let logger = null // Lazy-loaded in `initialize`.
55
47
  let _configInstance = null
56
48
 
49
+ const getConfigFileNames = () => [
50
+ process.env.NEW_RELIC_CONFIG_FILENAME,
51
+ 'newrelic.js',
52
+ 'newrelic.cjs'
53
+ ].filter(Boolean)
54
+
55
+ const getConfigFileLocations = () => [
56
+ process.env.NEW_RELIC_HOME,
57
+ process.cwd(),
58
+ process.env.HOME,
59
+ path.join(__dirname, '../../../..') // above node_modules
60
+ ].filter(Boolean)
61
+
57
62
  // the REPL has no main module
58
63
  if (process.mainModule && process.mainModule.filename) {
59
- CONFIG_FILE_LOCATIONS.splice(2, 0, path.dirname(process.mainModule.filename))
64
+ getConfigFileLocations().splice(2, 0, path.dirname(process.mainModule.filename))
60
65
  }
61
66
 
62
67
  function isTruthular(setting) {
@@ -89,19 +94,15 @@ function fromObjectList(setting) {
89
94
  }
90
95
 
91
96
  function _findConfigFile() {
92
- var candidate
93
- var filepath
94
-
95
-
96
- for (var i = 0; i < CONFIG_FILE_LOCATIONS.length; i++) {
97
- candidate = CONFIG_FILE_LOCATIONS[i]
98
- if (!candidate) continue
97
+ const configFileCandidates = getConfigFileLocations().reduce((files, configPath) => {
98
+ const configFiles = getConfigFileNames().map(filename =>
99
+ path.join(path.resolve(configPath), filename)
100
+ )
99
101
 
100
- filepath = path.join(path.resolve(candidate), DEFAULT_FILENAME)
101
- if (!exists(filepath)) continue
102
+ return files.concat(configFiles)
103
+ }, [])
102
104
 
103
- return fs.realpathSync(filepath)
104
- }
105
+ return configFileCandidates.find(exists)
105
106
  }
106
107
 
107
108
  function Config(config) {
@@ -15,7 +15,8 @@ exports.prerelease = {
15
15
  fastify_instrumentation: false,
16
16
  // Starts by defaulting true as introducing for deprecation/removal purposes.
17
17
  // Will eventually set false prior full removal of feature.
18
- certificate_bundle: true
18
+ certificate_bundle: true,
19
+ new_promise_tracking: false
19
20
  }
20
21
 
21
22
  // flags that are no longer used for released features
@@ -11,17 +11,23 @@ const logger = require('../logger').child({component: 'grpc_connection'})
11
11
  const EventEmitter = require('events')
12
12
  const NAMES = require('../metrics/names')
13
13
  const util = require('util')
14
+ const GRPC_TEST_META = {
15
+ flaky: 'NEWRELIC_GRPCCONNECTION_METADATA_FLAKY',
16
+ delay: 'NEWRELIC_GRPCCONNECTION_METADATA_DELAY',
17
+ flaky_code: 'NEWRELIC_GRPCCONNECTION_METADATA_FLAKY_CODE',
18
+ success_delay_ms: 'NEWRELIC_GRPCCONNECTION_METADATA_SUCCESS_DELAY_MS'
19
+ }
14
20
 
15
21
  const connectionStates = require('./connection/states')
16
22
 
17
- const protoOptions = {keepCase: true,
23
+ const PROTO_OPTIONS = {keepCase: true,
18
24
  longs: String,
19
25
  enums: String,
20
26
  defaults: true,
21
27
  oneofs: true
22
28
  }
23
29
 
24
- const pathProtoDefinition = __dirname +
30
+ const PROTO_DEFINITION_PATH = __dirname +
25
31
  '../../../lib/grpc/endpoints/infinite-tracing/v1.proto'
26
32
 
27
33
  const DEFAULT_RECONNECT_DELAY_MS = 15 * 1000
@@ -54,11 +60,13 @@ class GrpcConnection extends EventEmitter {
54
60
  this._metrics = metrics
55
61
  this._setState(connectionStates.disconnected)
56
62
 
57
- this._endpoint = this.getTraceObserverEndpoint(traceObserverConfig)
58
63
  this._licensekey = null
59
64
  this._runId = null
60
65
  this._requestHeadersMap = null
61
66
 
67
+ this._endpoint = this.getTraceObserverEndpoint(traceObserverConfig)
68
+
69
+ this._client = null
62
70
  this.stream = null
63
71
  }
64
72
 
@@ -161,22 +169,27 @@ class GrpcConnection extends EventEmitter {
161
169
  }
162
170
  }
163
171
 
164
- // check environment variables for testing parameters and
165
- // pass to server via meta-data.
166
- const flaky = parseInt(env.NEWRELIC_GRPCCONNECTION_METADATA_FLAKY, 10)
167
- const delay = parseInt(env.NEWRELIC_GRPCCONNECTION_METADATA_DELAY, 10)
168
- if (flaky) {
169
- logger.trace('Adding flaky metadata: %s', flaky)
170
- metadata.add('flaky', flaky)
171
- }
172
- if (delay) {
173
- logger.trace('Adding delay metadata: %s', delay)
174
- metadata.add('delay',delay)
175
- }
172
+ this._setTestMetadata(metadata, env)
176
173
 
177
174
  return metadata
178
175
  }
179
176
 
177
+ /**
178
+ * Adds test metadata used to simulate connectivity issues
179
+ * when appropriate env vars are set
180
+ *
181
+ * @param {Metadata} metadata
182
+ */
183
+ _setTestMetadata(metadata, env) {
184
+ for (const [key, envVar] of Object.entries(GRPC_TEST_META)) {
185
+ const value = parseInt(env[envVar], 10)
186
+ if (value) {
187
+ logger.trace('Adding %s metadata: %s', key, value)
188
+ metadata.add(key, value)
189
+ }
190
+ }
191
+ }
192
+
180
193
  /**
181
194
  * Disconnects from gRPC endpoint and schedules establishing a new connection.
182
195
  * @param {number} reconnectDelayMs number of milliseconds to wait before reconnecting.
@@ -320,26 +333,48 @@ class GrpcConnection extends EventEmitter {
320
333
  * a stream for further writing.
321
334
  */
322
335
  _connectSpans() {
323
- const packageDefinition = protoLoader.loadSync(pathProtoDefinition, protoOptions)
324
-
325
- const serviceDefinition = grpc.loadPackageDefinition(
326
- packageDefinition
327
- ).com.newrelic.trace.v1
328
-
329
- const credentials = this._generateCredentials(grpc)
330
- const client = new serviceDefinition.IngestService(
331
- this._endpoint,
332
- credentials
333
- )
336
+ if (!this._client) {
337
+ // Only create once to avoid potential memory leak.
338
+ // We create here (currently) for consistent error handling.
339
+ this._client = this._createClient(this._endpoint)
340
+ }
334
341
 
335
342
  const metadata =
336
343
  this._getMetadata(this._licenseKey, this._runId, this._requestHeadersMap, process.env)
337
344
 
338
- const stream = client.recordSpan(metadata)
345
+ const stream = this._client.recordSpan(metadata)
339
346
  this._setupSpanStreamObservers(stream)
340
347
 
341
348
  return stream
342
349
  }
350
+
351
+ /**
352
+ * Creates gRPC service client to use for establishing gRPC streams.
353
+ *
354
+ * WARNING: creating a client more than once can result in a memory leak.
355
+ * ChannelImplementation and related objects will stay in memory even after
356
+ * the stream is closed and we do not have a handle to the client. Currently
357
+ * impacting grpc-js@1.2.11 and several earlier versions.
358
+ */
359
+ _createClient(endpoint) {
360
+ logger.trace('Creating gRPC client for: ', endpoint)
361
+
362
+ const packageDefinition = protoLoader.loadSync(PROTO_DEFINITION_PATH, PROTO_OPTIONS)
363
+
364
+ const protoDescriptor = grpc.loadPackageDefinition(
365
+ packageDefinition
366
+ )
367
+
368
+ const traceApi = protoDescriptor.com.newrelic.trace.v1
369
+
370
+ const credentials = this._generateCredentials(grpc)
371
+ const client = new traceApi.IngestService(
372
+ endpoint,
373
+ credentials
374
+ )
375
+
376
+ return client
377
+ }
343
378
  }
344
379
 
345
380
  module.exports = GrpcConnection
@@ -40,7 +40,7 @@ function initialize(agent, shim) {
40
40
  }
41
41
 
42
42
  function tryAsyncHooks(agent, shim) {
43
- var asyncHooks = null
43
+ let asyncHooks = null
44
44
  try {
45
45
  asyncHooks = require('async_hooks')
46
46
  } catch (e) {
@@ -53,7 +53,25 @@ function tryAsyncHooks(agent, shim) {
53
53
  const segmentMap = new Map()
54
54
  module.exports.segmentMap = segmentMap
55
55
 
56
- var hook = asyncHooks.createHook({
56
+ let hookHandlers = getStandardHooks(segmentMap, agent, shim)
57
+
58
+ if (agent.config.feature_flag.new_promise_tracking) {
59
+ logger.info('Enabling new promise tracking style via new_promise_tracking feature flag.')
60
+ hookHandlers = getPromiseResolveStyleHooks(segmentMap, agent, shim)
61
+ }
62
+
63
+ const hook = asyncHooks.createHook(hookHandlers)
64
+ hook.enable()
65
+
66
+ agent.on('unload', function disableHook() {
67
+ hook.disable()
68
+ })
69
+
70
+ return true
71
+ }
72
+
73
+ function getStandardHooks(segmentMap, agent, shim) {
74
+ const hooks = {
57
75
  init: function initHook(id, type, triggerId, promiseWrap) {
58
76
  if (type !== 'PROMISE') {
59
77
  return
@@ -78,7 +96,7 @@ function tryAsyncHooks(agent, shim) {
78
96
  },
79
97
 
80
98
  before: function beforeHook(id) {
81
- var hookSegment = segmentMap.get(id)
99
+ const hookSegment = segmentMap.get(id)
82
100
 
83
101
  if (!hookSegment) {
84
102
  return
@@ -88,7 +106,7 @@ function tryAsyncHooks(agent, shim) {
88
106
  shim.setActiveSegment(hookSegment)
89
107
  },
90
108
  after: function afterHook(id) {
91
- var hookSegment = segmentMap.get(id)
109
+ const hookSegment = segmentMap.get(id)
92
110
 
93
111
  // hookSegment is the segment that was active before the promise
94
112
  // executed. If the promise is executing before a segment has been
@@ -104,11 +122,81 @@ function tryAsyncHooks(agent, shim) {
104
122
  destroy: function destHook(id) {
105
123
  segmentMap.delete(id)
106
124
  }
107
- }).enable()
125
+ }
108
126
 
109
- agent.on('unload', function disableHook() {
110
- hook.disable()
111
- })
127
+ return hooks
128
+ }
112
129
 
113
- return true
130
+ function getPromiseResolveStyleHooks(segmentMap, agent, shim) {
131
+ const hooks = {
132
+ init: function initHook(id, type, triggerId, asyncResource) {
133
+ if (type !== 'PROMISE') {
134
+ return
135
+ }
136
+
137
+ let parentSegment = segmentMap.get(triggerId)
138
+
139
+ if (parentSegment && !parentSegment.transaction.isActive()) {
140
+ // Stop propagating if the transaction was ended.
141
+ return
142
+ }
143
+
144
+ if (!parentSegment && !agent.getTransaction()) {
145
+ return
146
+ }
147
+
148
+ const activeSegment = shim.getActiveSegment() || parentSegment
149
+ if (asyncResource && asyncResource.promise) {
150
+ asyncResource.promise.__NR_id = id
151
+ }
152
+
153
+ segmentMap.set(id, activeSegment)
154
+ },
155
+
156
+ before: function beforeHook(id) {
157
+ const hookSegment = segmentMap.get(id)
158
+
159
+ if (!hookSegment) {
160
+ return
161
+ }
162
+
163
+ segmentMap.set(id, shim.getActiveSegment())
164
+ shim.setActiveSegment(hookSegment)
165
+ },
166
+ after: function afterHook(id) {
167
+ const hookSegment = segmentMap.get(id)
168
+
169
+ // hookSegment is the segment that was active before the promise
170
+ // executed. If the promise is executing before a segment has been
171
+ // restored, hookSegment will be null and should be restored. Thus
172
+ // undefined is the only invalid value here.
173
+ if (hookSegment === undefined) {
174
+ return
175
+ }
176
+
177
+ segmentMap.set(id, shim.getActiveSegment())
178
+ shim.setActiveSegment(hookSegment)
179
+ },
180
+ promiseResolve: function promiseResolveHandler(id) {
181
+ const hookSegment = segmentMap.get(id)
182
+ segmentMap.delete(id)
183
+
184
+ if (hookSegment === undefined) {
185
+ return
186
+ }
187
+
188
+ // Because the ID will no-longer be in memory until dispose to propagate the null
189
+ // we need to set it active here or else we may continue to propagate the wrong tree.
190
+ // May be some risk of setting this at the wrong time
191
+ if (hookSegment === null) {
192
+ shim.setActiveSegment(hookSegment)
193
+ }
194
+ },
195
+ destroy: function destroyHandler(id) {
196
+ // Clean up any unresolved promises that have been destroyed.
197
+ segmentMap.delete(id)
198
+ }
199
+ }
200
+
201
+ return hooks
114
202
  }
@@ -10,7 +10,6 @@ var urltils = require('../../util/urltils')
10
10
  var hashes = require('../../util/hashes')
11
11
  var logger = require('../../logger').child({component: 'outbound'})
12
12
  var shimmer = require('../../shimmer')
13
- var util = require('util')
14
13
  var url = require('url')
15
14
  var copy = require('../../util/copy')
16
15
 
@@ -110,7 +109,7 @@ module.exports = function instrumentOutbound(agent, opts, makeRequest) {
110
109
  logger.trace('CAT disabled, not adding headers!')
111
110
  }
112
111
 
113
- if (util.isArray(opts.headers)) {
112
+ if (Array.isArray(opts.headers)) {
114
113
  opts.headers = opts.headers.slice()
115
114
  Array.prototype.push.apply(
116
115
  opts.headers,
@@ -203,7 +202,7 @@ function handleResponse(segment, hostname, req, res) {
203
202
  // Add response attributes for spans
204
203
  segment.addSpanAttribute('http.statusCode', res.statusCode)
205
204
  segment.addSpanAttribute('http.statusText', res.statusMessage)
206
-
205
+
207
206
  // If CAT is enabled, grab those headers!
208
207
  const agent = segment.transaction.agent
209
208
  if (
@@ -11,7 +11,6 @@ const recordWeb = require('../../metrics/recorders/http')
11
11
  const hashes = require('../../util/hashes')
12
12
  const cat = require('../../util/cat')
13
13
  const instrumentOutbound = require('./http-outbound')
14
- const util = require('util')
15
14
  const url = require('url')
16
15
  const urltils = require('../../util/urltils')
17
16
  const properties = require('../../util/properties')
@@ -606,7 +605,7 @@ function parseSyntheticsHeader(header, encKey, trustedIds) {
606
605
  return
607
606
  }
608
607
 
609
- if (!util.isArray(synthData)) {
608
+ if (!Array.isArray(synthData)) {
610
609
  logger.trace(
611
610
  'Synthetics data is not an array: %s (%s)',
612
611
  synthData,
@@ -54,7 +54,7 @@ const setupMiddlewareHandlers = (shim, fastify) => {
54
54
  )
55
55
  // replace original function with our function
56
56
  args[0] = newMiddlewareFunction
57
- // console.log(middlewareFunction.__NR_original)
57
+
58
58
  return fn.apply(this, args)
59
59
  }
60
60
  })
package/lib/shim/shim.js CHANGED
@@ -7,7 +7,6 @@
7
7
 
8
8
  const arity = require('../util/arity')
9
9
  const constants = require('./constants')
10
- const events = require('events')
11
10
  const hasOwnProperty = require('../util/properties').hasOwn
12
11
  const logger = require('../logger').child({component: 'Shim'})
13
12
  const path = require('path')
@@ -127,7 +126,6 @@ Shim.prototype.isNull = isNull
127
126
  Shim.prototype.toArray = toArray
128
127
  Shim.prototype.argsToArray = argsToArray
129
128
  Shim.prototype.normalizeIndex = normalizeIndex
130
- Shim.prototype.listenerCount = listenerCount
131
129
  Shim.prototype.once = once
132
130
 
133
131
  Shim.prototype.setInternalProperty = setInternalProperty
@@ -1695,23 +1693,6 @@ function normalizeIndex(arrayLength, idx) {
1695
1693
  return (idx < 0 || idx >= arrayLength) ? null : idx
1696
1694
  }
1697
1695
 
1698
- /**
1699
- * Retrieves the number of listeners for the given event.
1700
- *
1701
- * @memberof Shim.prototype
1702
- *
1703
- * @param {object} emitter - The emitter to count the listeners on.
1704
- * @param {string} event - The event to count.
1705
- *
1706
- * @return {number} The number of listeners on the given event for this emitter.
1707
- */
1708
- function listenerCount(emitter, evnt) {
1709
- if (events.EventEmitter.listenerCount) {
1710
- return events.EventEmitter.listenerCount(emitter, evnt)
1711
- }
1712
- return emitter.listeners(evnt).length
1713
- }
1714
-
1715
1696
  /**
1716
1697
  * Wraps a function such that it will only be executed once.
1717
1698
  *
@@ -72,6 +72,7 @@ function createStandardAggregator(config, collector, metrics) {
72
72
  }
73
73
 
74
74
  function validateInfiniteTracing(trace_observer) {
75
+ // TODO: Remove semver check when Node 10 support dropped.
75
76
  if (!psemver.satisfies('>=10.10.0')) {
76
77
  logger.warn(
77
78
  'Infinite tracing disabled: this version of Node is not supported (must be >=10.10.0)'
package/lib/util/cat.js CHANGED
@@ -5,9 +5,8 @@
5
5
 
6
6
  'use strict'
7
7
 
8
- var util = require('util')
9
- var hashes = require('./hashes')
10
- var logger = require('../logger').child({component: 'cat'})
8
+ const hashes = require('./hashes')
9
+ const logger = require('../logger').child({component: 'cat'})
11
10
 
12
11
  module.exports.handleCatHeaders = handleCatHeaders
13
12
  module.exports.parsedHeadersToTrans = parsedHeadersToTrans
@@ -43,7 +42,7 @@ function parsedHeadersToTrans(parsedCatId, externalTrans, transaction) {
43
42
  transaction.incomingCatId = parsedCatId
44
43
  }
45
44
 
46
- if (util.isArray(externalTrans)) {
45
+ if (Array.isArray(externalTrans)) {
47
46
  transaction.referringTransactionGuid = externalTrans[0]
48
47
  if (typeof externalTrans[2] === 'string') {
49
48
  transaction.tripId = externalTrans[2]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "newrelic",
3
- "version": "7.2.1",
3
+ "version": "7.5.0",
4
4
  "author": "New Relic Node.js agent team <nodejs@newrelic.com>",
5
5
  "license": "Apache-2.0",
6
6
  "contributors": [
@@ -137,7 +137,8 @@
137
137
  "update-cross-agent-tests": "./bin/update-cats.sh",
138
138
  "versioned-tests": "./bin/run-versioned-tests.sh",
139
139
  "update-changelog-version": "node ./bin/update-changelog-version",
140
- "versioned": "npm run prepare-test && time ./bin/run-versioned-tests.sh"
140
+ "versioned": "npm run prepare-test && time ./bin/run-versioned-tests.sh",
141
+ "prepare": "husky install"
141
142
  },
142
143
  "bin": {
143
144
  "newrelic-naming-rules": "./bin/test-naming-rules.js"
@@ -161,7 +162,7 @@
161
162
  },
162
163
  "devDependencies": {
163
164
  "@newrelic/proxy": "^2.0.0",
164
- "@newrelic/test-utilities": "^5.0.0",
165
+ "@newrelic/test-utilities": "^5.1.0",
165
166
  "@octokit/rest": "^18.0.15",
166
167
  "JSV": "~4.0.2",
167
168
  "architect": "*",
@@ -171,12 +172,14 @@
171
172
  "commander": "^7.0.0",
172
173
  "eslint": "^6.8.0",
173
174
  "express": "*",
174
- "fastify": "^2.14.1",
175
+ "fastify": "^2.15.3",
175
176
  "generic-pool": "^3.6.1",
176
177
  "glob": "^7.1.2",
177
178
  "got": "^8.0.1",
178
179
  "http-errors": "^1.7.3",
180
+ "husky": "^6.0.0",
179
181
  "jsdoc": "^3.6.3",
182
+ "lint-staged": "^11.0.0",
180
183
  "memcached": ">=0.2.8",
181
184
  "minami": "^1.1.1",
182
185
  "mongodb": "^3.3.3",