newrelic 7.1.3 → 7.3.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 (45) hide show
  1. package/NEWS.md +72 -0
  2. package/THIRD_PARTY_NOTICES.md +33 -2
  3. package/bin/test-naming-rules.js +4 -4
  4. package/lib/collector/facts.js +22 -8
  5. package/lib/collector/http-agents.js +20 -7
  6. package/lib/collector/remote-method.js +18 -4
  7. package/lib/config/index.js +34 -42
  8. package/lib/feature_flags.js +6 -2
  9. package/lib/grpc/connection.js +39 -15
  10. package/lib/instrumentation/core/async_hooks.js +106 -14
  11. package/lib/instrumentation/core/http.js +5 -0
  12. package/lib/shim/shim.js +6 -1
  13. package/lib/spans/create-span-event-aggregator.js +1 -0
  14. package/lib/spans/span-event.js +6 -3
  15. package/lib/spans/streaming-span-event.js +6 -3
  16. package/lib/transaction/index.js +29 -0
  17. package/newrelic.js +1 -1
  18. package/package.json +19 -6
  19. package/CONTRIBUTING.md +0 -135
  20. package/Migration Guide.md +0 -271
  21. package/ROADMAP_Node.md +0 -24
  22. package/SECURITY.md +0 -5
  23. package/THIRD_PARTY_NOTICES_ADDENDUM.md +0 -213
  24. package/bin/ca-gen.js +0 -91
  25. package/bin/cassandra-setup.sh +0 -11
  26. package/bin/check-workflow-run.js +0 -74
  27. package/bin/clean.sh +0 -18
  28. package/bin/compare-bench-results.js +0 -172
  29. package/bin/create-github-release.js +0 -109
  30. package/bin/create-release-tag.js +0 -95
  31. package/bin/docker-env-vars.sh +0 -15
  32. package/bin/docker-services.sh +0 -49
  33. package/bin/git-commands.js +0 -110
  34. package/bin/github.js +0 -144
  35. package/bin/npm-commands.js +0 -31
  36. package/bin/prepare-release.js +0 -359
  37. package/bin/publish-docs.sh +0 -16
  38. package/bin/run-bench.js +0 -137
  39. package/bin/run-versioned-tests.sh +0 -24
  40. package/bin/smoke.sh +0 -8
  41. package/bin/ssl.sh +0 -87
  42. package/bin/update-ca-bundle.sh +0 -20
  43. package/bin/update-cats.sh +0 -8
  44. package/jsdoc-conf.json +0 -18
  45. package/third_party_manifest.json +0 -619
package/NEWS.md CHANGED
@@ -1,3 +1,75 @@
1
+ ### v7.3.1 (2021-04-14)
2
+
3
+ * 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).
4
+
5
+ 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.**
6
+
7
+ **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 }`.
8
+
9
+ ### v7.3.0 (2021-04-06)
10
+
11
+ * 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.
12
+
13
+ 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.
14
+
15
+ 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.**
16
+
17
+ * Fixed memory leak introduced when Infinite Tracing is enabled.
18
+
19
+ 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.
20
+
21
+ ### v7.2.1 (2021-03-29)
22
+
23
+ * Dev-only sub-dependency bump of 'y18n' to clear npm audit warnings.
24
+
25
+ * Bumped @grpc/grpc-js to ^1.2.11.
26
+
27
+ * Bumped @grpc/proto-loader to ^0.5.6.
28
+
29
+ * Agent no longer propagates segments for promises via async-hooks when the transaction associated with the parentSegment has ended.
30
+
31
+ This change reduces the amount of context tracking work needed for certain rare edge-case scenarios involving promises.
32
+
33
+ * Fixed issue where capturing axios request errors could result in a memory leak.
34
+
35
+ 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.
36
+
37
+ * Added active transaction check to `wrappedResEnd` to prevent unecessary work for ended transactions in the case of multiple `Response.prototype.end()` invocations.
38
+
39
+ ### v7.2.0 (2021-03-23)
40
+
41
+ * Added feature flag to allow disabling of certificate bundle usage.
42
+
43
+ **Deprecation Warning:** The certificate bundle included by New Relic will be disabled by default and then 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 }`.
44
+
45
+ * The `NEW_RELIC_NO_CONFIG_FILE` environment variable is no longer needed to run the agent without a configuration file.
46
+
47
+ * If a configuration file is used with agent configuration environment variables, the environment variables will override the corresponding configuration file settings.
48
+
49
+ * Fixed bug where applications with multiple names on a dynamically named host (UUID like) would have instances consolidated, losing per-host breakdowns.
50
+
51
+ Removed 'host' from agent 'identifier' override to prevent server safety mechanism from kicking in. Host will still be used to identify unique agent instances, so was unnecessary to include as part of the identifier. This also resulted in additional processing overhead on the back-end. The identifier override is still kept in place with multiple application names to continue to allow uniquely identifying instances on the same host with multiple application names where the first name may be identical. For example `app_name['myName', 'unique1']` and `app_name['myName', 'unique2']`. These names would consolidate down into a single instance on the same host without the identifier override.
52
+
53
+ * Fixed bug where truncated http (external) or datastore segments would generate generic spans instead of appropriate http or datastore spans.
54
+
55
+ * Set distributed tracing to enabled in the `newrelic.js` template configuration file supplied with the agent.
56
+
57
+ * Added module root to shim.require() logging to aid debugging.
58
+
59
+ * Migrated from .npmignore to 'files' list in package.json to control which files are packaged.
60
+
61
+ Thank you to @JamesPeiris for the initial nudge via PR to move in this direction.
62
+
63
+ * Converted remaining collector unit tests to use tap API.
64
+
65
+ * Added linting to scripts in /bin folder.
66
+
67
+ Linting rules added are slightly more permissive than production rules and allow full ecma 8.
68
+
69
+ * Added new developer documentation to /docs folder.
70
+
71
+ This information is ported over from private GHE wiki used prior to going open source. S/O @astorm for original versions of the function wrapping and module instrumentation docs.
72
+
1
73
  ### v7.1.3 (2021-03-09)
2
74
 
3
75
  * Bumped @grpc/grpc-js to ^1.2.7.
@@ -41,6 +41,7 @@ code, the source code can be found at [https://github.com/newrelic/node-newrelic
41
41
  * [benchmark](#benchmark)
42
42
  * [bluebird](#bluebird)
43
43
  * [chai](#chai)
44
+ * [commander](#commander)
44
45
  * [eslint](#eslint)
45
46
  * [express](#express)
46
47
  * [fastify](#fastify)
@@ -74,7 +75,7 @@ code, the source code can be found at [https://github.com/newrelic/node-newrelic
74
75
 
75
76
  ### @grpc/grpc-js
76
77
 
77
- This product includes source derived from [@grpc/grpc-js](https://github.com/grpc/grpc-node/tree/master/packages/grpc-js) ([v1.2.5](https://github.com/grpc/grpc-node/tree/master/packages/grpc-js/tree/v1.2.5)), distributed under the [Apache-2.0 License](https://github.com/grpc/grpc-node/tree/master/packages/grpc-js/blob/v1.2.5/LICENSE):
78
+ This product includes source derived from [@grpc/grpc-js](https://github.com/grpc/grpc-node/tree/master/packages/grpc-js) ([v1.2.11](https://github.com/grpc/grpc-node/tree/%40grpc/grpc-js%401.2.11/packages/grpc-js)), distributed under the [Apache-2.0 License](https://github.com/grpc/grpc-node/blob/%40grpc/grpc-js%401.2.11/packages/grpc-js/LICENSE):
78
79
 
79
80
  ```
80
81
  Apache License
@@ -283,7 +284,7 @@ This product includes source derived from [@grpc/grpc-js](https://github.com/grp
283
284
 
284
285
  ### @grpc/proto-loader
285
286
 
286
- This product includes source derived from [@grpc/proto-loader](https://github.com/grpc/grpc-node) ([v0.5.5](https://github.com/grpc/grpc-node/tree/v0.5.5)), distributed under the [Apache-2.0 License](https://github.com/grpc/grpc-node/blob/v0.5.5/LICENSE):
287
+ This product includes source derived from [@grpc/proto-loader](https://github.com/grpc/grpc-node) ([v0.5.6](https://github.com/grpc/grpc-node/tree/%40grpc/proto-loader%400.5.6/packages/proto-loader)), distributed under the [Apache-2.0 License](https://github.com/grpc/grpc-node/blob/%40grpc/proto-loader%400.5.6/packages/proto-loader/LICENSE):
287
288
 
288
289
  ```
289
290
  Apache License
@@ -1924,6 +1925,36 @@ SOFTWARE.
1924
1925
 
1925
1926
  ```
1926
1927
 
1928
+ ### commander
1929
+
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):
1931
+
1932
+ ```
1933
+ (The MIT License)
1934
+
1935
+ Copyright (c) 2011 TJ Holowaychuk <tj@vision-media.ca>
1936
+
1937
+ Permission is hereby granted, free of charge, to any person obtaining
1938
+ a copy of this software and associated documentation files (the
1939
+ 'Software'), to deal in the Software without restriction, including
1940
+ without limitation the rights to use, copy, modify, merge, publish,
1941
+ distribute, sublicense, and/or sell copies of the Software, and to
1942
+ permit persons to whom the Software is furnished to do so, subject to
1943
+ the following conditions:
1944
+
1945
+ The above copyright notice and this permission notice shall be
1946
+ included in all copies or substantial portions of the Software.
1947
+
1948
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
1949
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1950
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
1951
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
1952
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
1953
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
1954
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1955
+
1956
+ ```
1957
+
1927
1958
  ### eslint
1928
1959
 
1929
1960
  This product includes source derived from [eslint](https://github.com/eslint/eslint) ([v6.8.0](https://github.com/eslint/eslint/tree/v6.8.0)), distributed under the [MIT License](https://github.com/eslint/eslint/blob/v6.8.0/LICENSE):
@@ -15,13 +15,13 @@ var urltils = require('../lib/util/urltils')
15
15
  var MetricNormalizer = require('../lib/metrics/normalizer')
16
16
 
17
17
  var cwd = process.cwd()
18
- var opts = null
18
+ var options = null
19
19
  if (arrayContainsAny(process.argv, '-h', '-?', '--help')) {
20
20
  printHelp()
21
21
  } else if (process.argv.length === 3) {
22
- opts = {rules: null, urls: path.resolve(cwd, process.argv[2])}
22
+ options = {rules: null, urls: path.resolve(cwd, process.argv[2])}
23
23
  } else if (process.argv.length === 4) {
24
- opts = {
24
+ options = {
25
25
  rules: path.resolve(cwd, process.argv[2]),
26
26
  urls: path.resolve(cwd, process.argv[3])
27
27
  }
@@ -29,7 +29,7 @@ if (arrayContainsAny(process.argv, '-h', '-?', '--help')) {
29
29
  printHelp()
30
30
  }
31
31
 
32
- run(opts)
32
+ run(options)
33
33
 
34
34
  function printHelp() {
35
35
  console.log('Usage:')
@@ -64,14 +64,7 @@ function facts(agent, callback) {
64
64
  }
65
65
  }
66
66
 
67
- // TODO: After reconfiguring agent startup to wait for the server to start
68
- // or for the first transaction, add the `port` for the server too.
69
- // NOTE: The concat is necessary to prevent sort from happening in-place.
70
- results.identifier = [
71
- 'nodejs',
72
- results.host,
73
- results.app_name.concat([]).sort().join(',')
74
- ].join(':')
67
+ results.identifier = getIdentifierOverride(results.app_name)
75
68
 
76
69
  const ipAddresses = getAllIPAddresses()
77
70
  if (ipAddresses.length) {
@@ -112,3 +105,24 @@ function getAllIPAddresses() {
112
105
  return addresses
113
106
  }, [])
114
107
  }
108
+
109
+ /**
110
+ * Creates an identifier override to support customers who have multiple agents on the
111
+ * same host with the first app name that is identical.
112
+ * https://github.com/newrelic/node-newrelic/commit/c0901e6807a50ac3969d79ab48c31c8e0232a6b5#r18254962
113
+ * https://source.datanerd.us/collector-collective/connect-service/blob/1470c21109393a5b43c8788da88a37f41a300b98/src/main/java/com/nr/collector/methods/Connect.java#L1424-L1431
114
+ *
115
+ * IMPORTANT: we do not include host as it has negative consequences and is unnecessary.
116
+ * On the server, the host will still be used as part of the key to determine if two agent
117
+ * connections are the same real agent or a separate one.
118
+ * https://github.com/newrelic/node-newrelic/issues/654
119
+ */
120
+ function getIdentifierOverride(appNames) {
121
+ const identifier = [
122
+ 'nodejs',
123
+ // NOTE: The concat is necessary to prevent sort from happening in-place.
124
+ appNames.concat([]).sort().join(',')
125
+ ].join(':')
126
+
127
+ return identifier
128
+ }
@@ -103,14 +103,27 @@ function proxyOptions(config) {
103
103
  proxy_url: proxy_url
104
104
  }
105
105
 
106
- // merge user certificates with built-in certs
107
-
108
106
  if (config.certificates && config.certificates.length > 0) {
109
- logger.info(
110
- 'Using a proxy with a special cert. This enables our cert bundle which, combined ' +
111
- 'with some versions of node, exacerbates a leak in node core TLS.'
112
- )
113
- opts.certificates = config.certificates.concat(certificates)
107
+ opts.certificates = config.certificates
108
+
109
+ // merge user certificates with built-in certs
110
+ if (config.feature_flag.certificate_bundle) {
111
+ logger.info(
112
+ 'Using a proxy with a special cert. This enables our cert bundle which, combined ' +
113
+ 'with some versions of node, exacerbates a leak in node core TLS.'
114
+ )
115
+
116
+ const certWarningMessage = [
117
+ 'Deprecation Warning: The certificate bundle included by New Relic will be ',
118
+ 'disabled by default and then fully removed in later major versions. We recommend ',
119
+ 'testing with the certificate_bundle feature flag set to `false` to determine if ',
120
+ 'you will need to modify your environment or setup your own appropriate bundle. ',
121
+ 'Example configuration: feature_flag: { certificate_bundle: false }.'
122
+ ].join('')
123
+ logger.warnOnce('CERT_WARNING', certWarningMessage)
124
+
125
+ opts.certificates = config.certificates.concat(certificates)
126
+ }
114
127
  }
115
128
 
116
129
  return opts
@@ -263,10 +263,24 @@ RemoteMethod.prototype._request = function _request(options) {
263
263
  request = https.request(requestOptions)
264
264
  } else {
265
265
  if (this._config.certificates && this._config.certificates.length > 0) {
266
- logger.debug(
267
- 'Adding custom certificate to the cert bundle.'
268
- )
269
- requestOptions.ca = this._config.certificates.concat(certificates)
266
+ requestOptions.ca = this._config.certificates
267
+
268
+ if (this._config.feature_flag.certificate_bundle) {
269
+ logger.debug(
270
+ 'Adding custom certificate to the cert bundle.'
271
+ )
272
+
273
+ const certWarningMessage = [
274
+ 'Deprecation Warning: The certificate bundle included by New Relic will be ',
275
+ 'disabled by default and then fully removed in later major versions. We recommend ',
276
+ 'testing with the certificate_bundle feature flag set to `false` to determine if ',
277
+ 'you will need to modify your environment or setup your own appropriate bundle. ',
278
+ 'Example configuration: feature_flag: { certificate_bundle: false }.'
279
+ ].join('')
280
+ logger.warnOnce('CERT_WARNING', certWarningMessage)
281
+
282
+ requestOptions.ca = this._config.certificates.concat(certificates)
283
+ }
270
284
  }
271
285
  request = https.request(requestOptions)
272
286
  }
@@ -1098,6 +1098,8 @@ Config.prototype._fromEnvironment = function _fromEnvironment(metadata, data) {
1098
1098
  if (typeof node === 'string') {
1099
1099
  var setting = process.env[node]
1100
1100
  if (setting) {
1101
+ logger.debug(`${node} environment variable set.`)
1102
+
1101
1103
  if (ENV.LIST_VARS.has(node)) {
1102
1104
  let split = ENV.LIST_VARS_CUSTOM_DELIMITERS[node] || /,/
1103
1105
  data[value] = setting.split(split).map(function trimVal(k) {
@@ -1616,11 +1618,9 @@ Config.prototype.getAggregatorConfig = function getAggregatorConfig(method) {
1616
1618
  * 5. If this module is installed as a dependency, the directory above the
1617
1619
  * node_modules folder in which newrelic is installed.
1618
1620
  *
1619
- * For configuration via environment (useful on Joyent, Azure, Heroku, or
1620
- * other PaaS offerings), set NEW_RELIC_NO_CONFIG_FILE to something truthy
1621
- * and read README.md for details on what configuration variables are
1622
- * necessary, as well as a complete enumeration of the other available
1623
- * variables.
1621
+ * When node process environment varaibles and a config file are used,
1622
+ * the environment variables will override their corresponding
1623
+ * configuration file settings.
1624
1624
  *
1625
1625
  * @param {object} config Optional configuration to be used in place of a
1626
1626
  * config file.
@@ -1636,15 +1636,22 @@ function initialize(config) {
1636
1636
  if (config) return new Config(config)
1637
1637
 
1638
1638
  if (isTruthular(process.env.NEW_RELIC_NO_CONFIG_FILE)) {
1639
- config = new Config(Object.create(null))
1640
- if (config.newrelic_home) delete config.newrelic_home
1641
- return config
1639
+ logger.info('NEW_RELIC_NO_CONFIG_FILE set, deferring to environment variables.')
1640
+
1641
+ return createNewConfigObject(config)
1642
1642
  }
1643
1643
 
1644
1644
  var filepath = _findConfigFile()
1645
1645
  if (!filepath) {
1646
- _noConfigFile()
1647
- return null
1646
+ logger.info(['Unable to find configuration file.',
1647
+ 'If a configuration file is desired (common for non-containerized environments),',
1648
+ `a base configuration file can be copied from ${BASE_CONFIG_PATH}`,
1649
+ 'and renamed to "newrelic.js" in the directory from which you will start',
1650
+ 'your application.',
1651
+ 'Attempting to start agent using environment variables.'
1652
+ ].join(' '))
1653
+
1654
+ return createNewConfigObject(config)
1648
1655
  }
1649
1656
 
1650
1657
  var userConf
@@ -1653,12 +1660,16 @@ function initialize(config) {
1653
1660
  } catch (error) {
1654
1661
  logger.error(error)
1655
1662
 
1656
- throw new Error([
1657
- `Unable to read configuration file "${filepath}".`,
1658
- `A base configuration file can be copied from ${BASE_CONFIG_PATH}`,
1659
- 'and renamed to "newrelic.js" in the directory from which you will start',
1660
- 'your application.'
1661
- ].join('\n'))
1663
+ logger.warn([
1664
+ `Unable to read existing configuration file "${filepath}".`,
1665
+ 'To allow reading of the file (if desired),',
1666
+ 'please ensure the application has read access and the file is exporting valid JSON.',
1667
+ 'Attempting to start agent using enviornment variables.'
1668
+ ].join(' '))
1669
+ }
1670
+
1671
+ if (!userConf) {
1672
+ return createNewConfigObject(config)
1662
1673
  }
1663
1674
 
1664
1675
  config = new Config(userConf)
@@ -1670,32 +1681,13 @@ function initialize(config) {
1670
1681
  return config
1671
1682
  }
1672
1683
 
1673
- function _noConfigFile() {
1674
- const mainpath = path.resolve(path.join(process.cwd(), DEFAULT_FILENAME))
1675
- // If agent was loaded with -r flag, default to the path of the file being executed
1676
- const mainModule = process.mainModule && process.mainModule.filename || process.argv[1]
1677
- const altpath = path.resolve(
1678
- path.dirname(mainModule),
1679
- DEFAULT_FILENAME
1680
- )
1681
-
1682
- let locations = mainpath
1683
- if (mainpath !== altpath) {
1684
- locations += ' or ' + altpath
1685
- }
1686
-
1687
- const errorMessage = [
1688
- 'Unable to find New Relic module configuration. A base configuration file',
1689
- `can be copied from ${BASE_CONFIG_PATH}`,
1690
- `and put at ${locations}.`,
1691
- 'If you are not using file-based configuration, please set the environment',
1692
- 'variable `NEW_RELIC_NO_CONFIG_FILE=true`.'
1693
- ].join(' ')
1694
-
1695
- logger.error(errorMessage)
1696
- /* eslint-disable no-console */
1697
- console.error(errorMessage)
1698
- /* eslint-enable no-console */
1684
+ /**
1685
+ * This helper function creates an empty configuration object
1686
+ */
1687
+ function createNewConfigObject(config) {
1688
+ config = new Config(Object.create(null))
1689
+ if (config.newrelic_home) delete config.newrelic_home
1690
+ return config
1699
1691
  }
1700
1692
 
1701
1693
  /**
@@ -9,10 +9,14 @@
9
9
  exports.prerelease = {
10
10
  express5: false,
11
11
  await_support: true,
12
- serverless_mode: true,
12
+ serverless_mode: true, // TODO: Move to released & remove code checks next Major.
13
13
  promise_segments: false,
14
14
  reverse_naming_rules: false,
15
- fastify_instrumentation: false
15
+ fastify_instrumentation: false,
16
+ // Starts by defaulting true as introducing for deprecation/removal purposes.
17
+ // Will eventually set false prior full removal of feature.
18
+ certificate_bundle: true,
19
+ new_promise_tracking: false
16
20
  }
17
21
 
18
22
  // flags that are no longer used for released features
@@ -14,14 +14,14 @@ const util = require('util')
14
14
 
15
15
  const connectionStates = require('./connection/states')
16
16
 
17
- const protoOptions = {keepCase: true,
17
+ const PROTO_OPTIONS = {keepCase: true,
18
18
  longs: String,
19
19
  enums: String,
20
20
  defaults: true,
21
21
  oneofs: true
22
22
  }
23
23
 
24
- const pathProtoDefinition = __dirname +
24
+ const PROTO_DEFINITION_PATH = __dirname +
25
25
  '../../../lib/grpc/endpoints/infinite-tracing/v1.proto'
26
26
 
27
27
  const DEFAULT_RECONNECT_DELAY_MS = 15 * 1000
@@ -54,11 +54,13 @@ class GrpcConnection extends EventEmitter {
54
54
  this._metrics = metrics
55
55
  this._setState(connectionStates.disconnected)
56
56
 
57
- this._endpoint = this.getTraceObserverEndpoint(traceObserverConfig)
58
57
  this._licensekey = null
59
58
  this._runId = null
60
59
  this._requestHeadersMap = null
61
60
 
61
+ this._endpoint = this.getTraceObserverEndpoint(traceObserverConfig)
62
+
63
+ this._client = null
62
64
  this.stream = null
63
65
  }
64
66
 
@@ -320,26 +322,48 @@ class GrpcConnection extends EventEmitter {
320
322
  * a stream for further writing.
321
323
  */
322
324
  _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
- )
325
+ if (!this._client) {
326
+ // Only create once to avoid potential memory leak.
327
+ // We create here (currently) for consistent error handling.
328
+ this._client = this._createClient(this._endpoint)
329
+ }
334
330
 
335
331
  const metadata =
336
332
  this._getMetadata(this._licenseKey, this._runId, this._requestHeadersMap, process.env)
337
333
 
338
- const stream = client.recordSpan(metadata)
334
+ const stream = this._client.recordSpan(metadata)
339
335
  this._setupSpanStreamObservers(stream)
340
336
 
341
337
  return stream
342
338
  }
339
+
340
+ /**
341
+ * Creates gRPC service client to use for establishing gRPC streams.
342
+ *
343
+ * WARNING: creating a client more than once can result in a memory leak.
344
+ * ChannelImplementation and related objects will stay in memory even after
345
+ * the stream is closed and we do not have a handle to the client. Currently
346
+ * impacting grpc-js@1.2.11 and several earlier versions.
347
+ */
348
+ _createClient(endpoint) {
349
+ logger.trace('Creating gRPC client for: ', endpoint)
350
+
351
+ const packageDefinition = protoLoader.loadSync(PROTO_DEFINITION_PATH, PROTO_OPTIONS)
352
+
353
+ const protoDescriptor = grpc.loadPackageDefinition(
354
+ packageDefinition
355
+ )
356
+
357
+ const traceApi = protoDescriptor.com.newrelic.trace.v1
358
+
359
+ const credentials = this._generateCredentials(grpc)
360
+ const client = new traceApi.IngestService(
361
+ endpoint,
362
+ credentials
363
+ )
364
+
365
+ return client
366
+ }
343
367
  }
344
368
 
345
369
  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) {
@@ -50,19 +50,45 @@ function tryAsyncHooks(agent, shim) {
50
50
 
51
51
  // this map is reused to track the segment that was active when
52
52
  // the before callback is called to be replaced in the after callback
53
- var segmentMap = new Map()
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
- var transaction = agent.getTransaction()
59
- var parentSegment = segmentMap.get(triggerId)
76
+ if (type !== 'PROMISE') {
77
+ return
78
+ }
79
+
80
+ const parentSegment = segmentMap.get(triggerId)
60
81
 
61
- if (!parentSegment && !transaction || type !== 'PROMISE') {
82
+ if (parentSegment && !parentSegment.transaction.isActive()) {
83
+ // Stop propagating if the transaction was ended.
62
84
  return
63
85
  }
64
86
 
65
- var activeSegment = shim.getActiveSegment() || parentSegment
87
+ if (!parentSegment && !agent.getTransaction()) {
88
+ return
89
+ }
90
+
91
+ const activeSegment = shim.getActiveSegment() || parentSegment
66
92
  if (promiseWrap && promiseWrap.promise) {
67
93
  promiseWrap.promise.__NR_id = id
68
94
  }
@@ -70,7 +96,7 @@ function tryAsyncHooks(agent, shim) {
70
96
  },
71
97
 
72
98
  before: function beforeHook(id) {
73
- var hookSegment = segmentMap.get(id)
99
+ const hookSegment = segmentMap.get(id)
74
100
 
75
101
  if (!hookSegment) {
76
102
  return
@@ -80,7 +106,7 @@ function tryAsyncHooks(agent, shim) {
80
106
  shim.setActiveSegment(hookSegment)
81
107
  },
82
108
  after: function afterHook(id) {
83
- var hookSegment = segmentMap.get(id)
109
+ const hookSegment = segmentMap.get(id)
84
110
 
85
111
  // hookSegment is the segment that was active before the promise
86
112
  // executed. If the promise is executing before a segment has been
@@ -96,11 +122,77 @@ function tryAsyncHooks(agent, shim) {
96
122
  destroy: function destHook(id) {
97
123
  segmentMap.delete(id)
98
124
  }
99
- }).enable()
125
+ }
100
126
 
101
- agent.on('unload', function disableHook() {
102
- hook.disable()
103
- })
127
+ return hooks
128
+ }
104
129
 
105
- 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
+ }
196
+
197
+ return hooks
106
198
  }