newrelic 11.0.0 → 11.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/NEWS.md CHANGED
@@ -1,3 +1,47 @@
1
+ ### v11.2.0 (2023-10-11)
2
+
3
+ #### Features
4
+
5
+ * Added instrumentation for ElasticSearch ([#1785](https://github.com/newrelic/node-newrelic/pull/1785)) ([a748b84](https://github.com/newrelic/node-newrelic/commit/a748b84c6a706ed0cea2e07c9fae8807a82dbc95))
6
+
7
+ #### Bug Fixes
8
+
9
+ * Updated agent to create a stub api when running in a worker thread to avoid Next.js early return errors ([#1800](https://github.com/newrelic/node-newrelic/pull/1800)) ([636e8f0](https://github.com/newrelic/node-newrelic/commit/636e8f068a2ec2f989e28f6646f844b951ee636d))
10
+ * Updated shimmer to allow registering instrumentation for different versions of the same module ([#1799](https://github.com/newrelic/node-newrelic/pull/1799)) ([30f0b16](https://github.com/newrelic/node-newrelic/commit/30f0b16e13e9621c86481c5d5409a799dbbb8d57))
11
+
12
+ #### Miscellaneous Chores
13
+
14
+ * **deps:** Updated @newrelic/security-agent to v0.3.0 ([#1786](https://github.com/newrelic/node-newrelic/pull/1786)) ([9ca78ae](https://github.com/newrelic/node-newrelic/commit/9ca78aede72ddecd3eb8271d349ad35cc4ce05f9))
15
+ * Separated running versioned tests between local and external ([#1792](https://github.com/newrelic/node-newrelic/pull/1792)) ([6ffe2f6](https://github.com/newrelic/node-newrelic/commit/6ffe2f6e16c769c3fb8de767e9336992b1cca797))
16
+ * **dev-deps** Updated get-func-name to fix CVE ([#1790](https://github.com/newrelic/node-newrelic/pull/1790)) ([f378c77](https://github.com/newrelic/node-newrelic/commit/f378c77eb09fcd3453850a1d177faea41422f637))
17
+
18
+ #### Tests
19
+
20
+ * Updated elasticsearch container to ignore highwatermark to function on a constrained environment ([#1803](https://github.com/newrelic/node-newrelic/pull/1803)) ([8380e9d](https://github.com/newrelic/node-newrelic/commit/8380e9d527f3ffc61d1f3e32199e81ed25837185))
21
+ * Updated invalid json pino test as this was fixed in 8.15.1 of the pino library. ([#1782](https://github.com/newrelic/node-newrelic/pull/1782)) ([3e65fdc](https://github.com/newrelic/node-newrelic/commit/3e65fdcc68999e09d9ef3c341ff7c1159b65ab37))
22
+
23
+ #### Continuous Integration
24
+
25
+ * Added steps to zip up container logs on versioned test failure ([#1804](https://github.com/newrelic/node-newrelic/pull/1804)) ([0a54729](https://github.com/newrelic/node-newrelic/commit/0a54729f1d8187bb50508659716ec55349e9ee31))
26
+
27
+ ### v11.1.0 (2023-09-07)
28
+
29
+ #### Features
30
+
31
+ * Added instrumentation for fetch. ([#1776](https://github.com/newrelic/node-newrelic/pull/1776)) ([980b4dc](https://github.com/newrelic/node-newrelic/commit/980b4dc31d085cafc45961a157f76cb73768741a))
32
+ * Removed undici feature flag. This will now instrument undici by default. ([#1772](https://github.com/newrelic/node-newrelic/pull/1772)) ([683b254](https://github.com/newrelic/node-newrelic/commit/683b254c1c4686527a37e628f80eed9fcc03cb37))
33
+ * Updated undici instrumentation to create segment on request create instead of after connection and opening socket to more accurately record time it takes to make an undici request. ([#1770](https://github.com/newrelic/node-newrelic/pull/1770)) ([3f494f8](https://github.com/newrelic/node-newrelic/commit/3f494f8ad8dd1350fbe034e9c4a582a40228bc45))
34
+
35
+ #### Performance Improvements
36
+
37
+ * **Trace:** optimise getTotalTimeDurationInMillis function to push to an array instead of concat arrays. ([#1774](https://github.com/newrelic/node-newrelic/pull/1774)) ([6ecde97](https://github.com/newrelic/node-newrelic/commit/6ecde97a5efeef4d816b2e82ba762f2130914658))
38
+ * Thanks @infinity-naveen for your contribution 🎉
39
+
40
+ #### Miscellaneous Chores
41
+
42
+ * Updated Next.js plugin versioned tests to use main now that require-in-the-middle fixes have been merged. ([#1768](https://github.com/newrelic/node-newrelic/pull/1768)) ([6a72d1f](https://github.com/newrelic/node-newrelic/commit/6a72d1fb452ea2655c35d7cd008fa78935821ab8))
43
+ * Upgraded `@newrelic/test-utilities` and removed unused shimmer.patchModule and shimmer.unpatchModule ([#1769](https://github.com/newrelic/node-newrelic/pull/1769)) ([96620ef](https://github.com/newrelic/node-newrelic/commit/96620efa725f0f548b683825cb3cefff270fb252))
44
+
1
45
  ### v11.0.0 (2023-08-28)
2
46
 
3
47
  #### ⚠ BREAKING CHANGES
@@ -26,6 +26,7 @@ code, the source code can be found at [https://github.com/newrelic/node-newrelic
26
26
  * [import-in-the-middle](#import-in-the-middle)
27
27
  * [json-bigint](#json-bigint)
28
28
  * [json-stringify-safe](#json-stringify-safe)
29
+ * [module-details-from-path](#module-details-from-path)
29
30
  * [readable-stream](#readable-stream)
30
31
  * [require-in-the-middle](#require-in-the-middle)
31
32
  * [semver](#semver)
@@ -91,7 +92,7 @@ code, the source code can be found at [https://github.com/newrelic/node-newrelic
91
92
 
92
93
  ### @grpc/grpc-js
93
94
 
94
- This product includes source derived from [@grpc/grpc-js](https://github.com/grpc/grpc-node/tree/master/packages/grpc-js) ([v1.8.14](https://github.com/grpc/grpc-node/tree/master/packages/grpc-js/tree/v1.8.14)), distributed under the [Apache-2.0 License](https://github.com/grpc/grpc-node/tree/master/packages/grpc-js/blob/v1.8.14/LICENSE):
95
+ This product includes source derived from [@grpc/grpc-js](https://github.com/grpc/grpc-node/tree/master/packages/grpc-js) ([v1.9.2](https://github.com/grpc/grpc-node/tree/master/packages/grpc-js/tree/v1.9.2)), distributed under the [Apache-2.0 License](https://github.com/grpc/grpc-node/tree/master/packages/grpc-js/blob/v1.9.2/LICENSE):
95
96
 
96
97
  ```
97
98
  Apache License
@@ -300,7 +301,7 @@ This product includes source derived from [@grpc/grpc-js](https://github.com/grp
300
301
 
301
302
  ### @grpc/proto-loader
302
303
 
303
- This product includes source derived from [@grpc/proto-loader](https://github.com/grpc/grpc-node) ([v0.7.7](https://github.com/grpc/grpc-node/tree/v0.7.7)), distributed under the [Apache-2.0 License](https://github.com/grpc/grpc-node/blob/v0.7.7/LICENSE):
304
+ This product includes source derived from [@grpc/proto-loader](https://github.com/grpc/grpc-node) ([v0.7.9](https://github.com/grpc/grpc-node/tree/v0.7.9)), distributed under the [Apache-2.0 License](https://github.com/grpc/grpc-node/blob/v0.7.9/LICENSE):
304
305
 
305
306
  ```
306
307
  Apache License
@@ -926,7 +927,7 @@ Apache License
926
927
 
927
928
  ### @newrelic/security-agent
928
929
 
929
- This product includes source derived from [@newrelic/security-agent](https://github.com/newrelic/csec-node-agent) ([v0.2.1](https://github.com/newrelic/csec-node-agent/tree/v0.2.1)), distributed under the [New Relic Pre-Release License](https://github.com/newrelic/csec-node-agent/blob/v0.2.1/LICENSE):
930
+ This product includes source derived from [@newrelic/security-agent](https://github.com/newrelic/csec-node-agent) ([v0.3.0](https://github.com/newrelic/csec-node-agent/tree/v0.3.0)), distributed under the [New Relic Pre-Release License](https://github.com/newrelic/csec-node-agent/blob/v0.3.0/LICENSE):
930
931
 
931
932
  ```
932
933
  ## New Relic Pre-Release Software Notice
@@ -1246,7 +1247,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1246
1247
 
1247
1248
  ### https-proxy-agent
1248
1249
 
1249
- This product includes source derived from [https-proxy-agent](https://github.com/TooTallNate/proxy-agents) ([v7.0.1](https://github.com/TooTallNate/proxy-agents/tree/v7.0.1)), distributed under the [MIT License](https://github.com/TooTallNate/proxy-agents/blob/v7.0.1/README.md):
1250
+ This product includes source derived from [https-proxy-agent](https://github.com/TooTallNate/proxy-agents) ([v7.0.2](https://github.com/TooTallNate/proxy-agents/tree/v7.0.2)), distributed under the [MIT License](https://github.com/TooTallNate/proxy-agents/blob/v7.0.2/README.md):
1250
1251
 
1251
1252
  ```
1252
1253
  MIT License
@@ -1332,6 +1333,35 @@ IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1332
1333
 
1333
1334
  ```
1334
1335
 
1336
+ ### module-details-from-path
1337
+
1338
+ This product includes source derived from [module-details-from-path](https://github.com/watson/module-details-from-path) ([v1.0.3](https://github.com/watson/module-details-from-path/tree/v1.0.3)), distributed under the [MIT License](https://github.com/watson/module-details-from-path/blob/v1.0.3/LICENSE):
1339
+
1340
+ ```
1341
+ The MIT License (MIT)
1342
+
1343
+ Copyright (c) 2016 Thomas Watson Steen
1344
+
1345
+ Permission is hereby granted, free of charge, to any person obtaining a copy
1346
+ of this software and associated documentation files (the "Software"), to deal
1347
+ in the Software without restriction, including without limitation the rights
1348
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1349
+ copies of the Software, and to permit persons to whom the Software is
1350
+ furnished to do so, subject to the following conditions:
1351
+
1352
+ The above copyright notice and this permission notice shall be included in all
1353
+ copies or substantial portions of the Software.
1354
+
1355
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1356
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1357
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1358
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1359
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1360
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1361
+ SOFTWARE.
1362
+
1363
+ ```
1364
+
1335
1365
  ### readable-stream
1336
1366
 
1337
1367
  This product includes source derived from [readable-stream](https://github.com/nodejs/readable-stream) ([v3.6.2](https://github.com/nodejs/readable-stream/tree/v3.6.2)), distributed under the [MIT License](https://github.com/nodejs/readable-stream/blob/v3.6.2/LICENSE):
@@ -1892,7 +1922,7 @@ This product includes source derived from [@newrelic/newrelic-oss-cli](https://g
1892
1922
 
1893
1923
  ### @newrelic/test-utilities
1894
1924
 
1895
- This product includes source derived from [@newrelic/test-utilities](https://github.com/newrelic/node-test-utilities) ([v8.0.0](https://github.com/newrelic/node-test-utilities/tree/v8.0.0)), distributed under the [Apache-2.0 License](https://github.com/newrelic/node-test-utilities/blob/v8.0.0/LICENSE):
1925
+ This product includes source derived from [@newrelic/test-utilities](https://github.com/newrelic/node-test-utilities) ([v8.1.0](https://github.com/newrelic/node-test-utilities/tree/v8.1.0)), distributed under the [Apache-2.0 License](https://github.com/newrelic/node-test-utilities/blob/v8.1.0/LICENSE):
1896
1926
 
1897
1927
  ```
1898
1928
  Apache License
@@ -2131,7 +2161,7 @@ THE SOFTWARE.
2131
2161
 
2132
2162
  ### @slack/bolt
2133
2163
 
2134
- This product includes source derived from [@slack/bolt](https://github.com/slackapi/bolt) ([v3.13.1](https://github.com/slackapi/bolt/tree/v3.13.1)), distributed under the [MIT License](https://github.com/slackapi/bolt/blob/v3.13.1/LICENSE):
2164
+ This product includes source derived from [@slack/bolt](https://github.com/slackapi/bolt) ([v3.13.3](https://github.com/slackapi/bolt/tree/v3.13.3)), distributed under the [MIT License](https://github.com/slackapi/bolt/blob/v3.13.3/LICENSE):
2135
2165
 
2136
2166
  ```
2137
2167
  The MIT License (MIT)
@@ -2218,7 +2248,7 @@ THE SOFTWARE.
2218
2248
 
2219
2249
  ### c8
2220
2250
 
2221
- This product includes source derived from [c8](https://github.com/bcoe/c8) ([v7.14.0](https://github.com/bcoe/c8/tree/v7.14.0)), distributed under the [ISC License](https://github.com/bcoe/c8/blob/v7.14.0/LICENSE.txt):
2251
+ This product includes source derived from [c8](https://github.com/bcoe/c8) ([v8.0.1](https://github.com/bcoe/c8/tree/v8.0.1)), distributed under the [ISC License](https://github.com/bcoe/c8/blob/v8.0.1/LICENSE.txt):
2222
2252
 
2223
2253
  ```
2224
2254
  Copyright (c) 2017, Contributors
@@ -2240,7 +2270,7 @@ ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
2240
2270
 
2241
2271
  ### chai
2242
2272
 
2243
- This product includes source derived from [chai](https://github.com/chaijs/chai) ([v4.3.7](https://github.com/chaijs/chai/tree/v4.3.7)), distributed under the [MIT License](https://github.com/chaijs/chai/blob/v4.3.7/LICENSE):
2273
+ This product includes source derived from [chai](https://github.com/chaijs/chai) ([v4.3.8](https://github.com/chaijs/chai/tree/v4.3.8)), distributed under the [MIT License](https://github.com/chaijs/chai/blob/v4.3.8/LICENSE):
2244
2274
 
2245
2275
  ```
2246
2276
  MIT License
@@ -2269,7 +2299,7 @@ SOFTWARE.
2269
2299
 
2270
2300
  ### clean-jsdoc-theme
2271
2301
 
2272
- This product includes source derived from [clean-jsdoc-theme](https://github.com/ankitskvmdam/clean-jsdoc-theme) ([v4.2.7](https://github.com/ankitskvmdam/clean-jsdoc-theme/tree/v4.2.7)), distributed under the [MIT License](https://github.com/ankitskvmdam/clean-jsdoc-theme/blob/v4.2.7/LICENSE):
2302
+ This product includes source derived from [clean-jsdoc-theme](https://github.com/ankitskvmdam/clean-jsdoc-theme) ([v4.2.10](https://github.com/ankitskvmdam/clean-jsdoc-theme/tree/v4.2.10)), distributed under the [MIT License](https://github.com/ankitskvmdam/clean-jsdoc-theme/blob/v4.2.10/LICENSE):
2273
2303
 
2274
2304
  ```
2275
2305
  MIT License
@@ -2409,7 +2439,7 @@ SOFTWARE.
2409
2439
 
2410
2440
  ### eslint-config-prettier
2411
2441
 
2412
- This product includes source derived from [eslint-config-prettier](https://github.com/prettier/eslint-config-prettier) ([v8.8.0](https://github.com/prettier/eslint-config-prettier/tree/v8.8.0)), distributed under the [MIT License](https://github.com/prettier/eslint-config-prettier/blob/v8.8.0/LICENSE):
2442
+ This product includes source derived from [eslint-config-prettier](https://github.com/prettier/eslint-config-prettier) ([v8.10.0](https://github.com/prettier/eslint-config-prettier/tree/v8.10.0)), distributed under the [MIT License](https://github.com/prettier/eslint-config-prettier/blob/v8.10.0/LICENSE):
2413
2443
 
2414
2444
  ```
2415
2445
  The MIT License (MIT)
@@ -2748,7 +2778,7 @@ Library.
2748
2778
 
2749
2779
  ### eslint
2750
2780
 
2751
- This product includes source derived from [eslint](https://github.com/eslint/eslint) ([v8.40.0](https://github.com/eslint/eslint/tree/v8.40.0)), distributed under the [MIT License](https://github.com/eslint/eslint/blob/v8.40.0/LICENSE):
2781
+ This product includes source derived from [eslint](https://github.com/eslint/eslint) ([v8.48.0](https://github.com/eslint/eslint/tree/v8.48.0)), distributed under the [MIT License](https://github.com/eslint/eslint/blob/v8.48.0/LICENSE):
2752
2782
 
2753
2783
  ```
2754
2784
  Copyright OpenJS Foundation and other contributors, <www.openjsf.org>
@@ -3050,7 +3080,7 @@ SOFTWARE.
3050
3080
 
3051
3081
  ### lockfile-lint
3052
3082
 
3053
- This product includes source derived from [lockfile-lint](https://github.com/lirantal/lockfile-lint) ([v4.10.1](https://github.com/lirantal/lockfile-lint/tree/v4.10.1)), distributed under the [Apache-2.0 License](https://github.com/lirantal/lockfile-lint/blob/v4.10.1/LICENSE):
3083
+ This product includes source derived from [lockfile-lint](https://github.com/lirantal/lockfile-lint) ([v4.12.1](https://github.com/lirantal/lockfile-lint/tree/v4.12.1)), distributed under the [Apache-2.0 License](https://github.com/lirantal/lockfile-lint/blob/v4.12.1/LICENSE):
3054
3084
 
3055
3085
  ```
3056
3086
 
@@ -9440,7 +9470,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
9440
9470
 
9441
9471
  ### tap
9442
9472
 
9443
- This product includes source derived from [tap](https://github.com/tapjs/node-tap) ([v16.3.4](https://github.com/tapjs/node-tap/tree/v16.3.4)), distributed under the [ISC License](https://github.com/tapjs/node-tap/blob/v16.3.4/LICENSE):
9473
+ This product includes source derived from [tap](https://github.com/tapjs/node-tap) ([v16.3.8](https://github.com/tapjs/node-tap/tree/v16.3.8)), distributed under the [ISC License](https://github.com/tapjs/node-tap/blob/v16.3.8/LICENSE):
9444
9474
 
9445
9475
  ```
9446
9476
  The ISC License
@@ -9525,7 +9555,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
9525
9555
 
9526
9556
  ### @contrast/fn-inspect
9527
9557
 
9528
- This product includes source derived from [@contrast/fn-inspect](https://github.com/Contrast-Security-Inc/node-fn-inspect) ([v3.3.1](https://github.com/Contrast-Security-Inc/node-fn-inspect/tree/v3.3.1)), distributed under the [MIT License](https://github.com/Contrast-Security-Inc/node-fn-inspect/blob/v3.3.1/LICENSE):
9558
+ This product includes source derived from [@contrast/fn-inspect](https://github.com/Contrast-Security-Inc/node-fn-inspect) ([v3.4.0](https://github.com/Contrast-Security-Inc/node-fn-inspect/tree/v3.4.0)), distributed under the [MIT License](https://github.com/Contrast-Security-Inc/node-fn-inspect/blob/v3.4.0/LICENSE):
9529
9559
 
9530
9560
  ```
9531
9561
  Copyright 2022 Contrast Security, Inc
package/api.js CHANGED
@@ -29,6 +29,7 @@ const MODULE_TYPE = require('./lib/shim/constants').MODULE_TYPE
29
29
  const NAMES = require('./lib/metrics/names')
30
30
  const obfuscate = require('./lib/util/sql/obfuscate')
31
31
  const { DESTINATIONS } = require('./lib/config/attribute-filter')
32
+ const parse = require('module-details-from-path')
32
33
 
33
34
  /*
34
35
  *
@@ -1495,8 +1496,9 @@ API.prototype.instrumentLoadedModule = function instrumentLoadedModule(moduleNam
1495
1496
 
1496
1497
  try {
1497
1498
  const resolvedName = require.resolve(moduleName)
1499
+ const parsed = parse(resolvedName)
1498
1500
 
1499
- return shimmer.instrumentPostLoad(this.agent, module, moduleName, resolvedName)
1501
+ return shimmer.instrumentPostLoad(this.agent, module, moduleName, parsed.basedir)
1500
1502
  } catch (error) {
1501
1503
  logger.error('instrumentLoadedModule encountered an error, module not instrumented: %s', error)
1502
1504
  }
package/index.js CHANGED
@@ -20,28 +20,35 @@ const NAMES = require('./lib/metrics/names')
20
20
  const isESMSupported = psemver.satisfies('>=16.2.0')
21
21
 
22
22
  const pkgJSON = require('./package.json')
23
- if (!isMainThread) {
23
+ if (isMainThread) {
24
+ logger.info(
25
+ 'Using New Relic for Node.js. Agent version: %s; Node version: %s.',
26
+ pkgJSON.version,
27
+ process.version
28
+ )
29
+
30
+ if (require.cache.__NR_cache) {
31
+ logger.warn(
32
+ 'Attempting to load a second copy of newrelic from %s, using cache instead',
33
+ __dirname
34
+ )
35
+ if (require.cache.__NR_cache.agent) {
36
+ require.cache.__NR_cache.agent.recordSupportability('Agent/DoubleLoad')
37
+ }
38
+ module.exports = require.cache.__NR_cache
39
+ } else {
40
+ initialize()
41
+ }
42
+ } else {
24
43
  logger.warn('Using New Relic for Node.js in worker_threads is not supported. Not starting!')
25
- return
44
+ initApi({ apiPath: './stub_api' })
26
45
  }
27
46
 
28
- logger.info(
29
- 'Using New Relic for Node.js. Agent version: %s; Node version: %s.',
30
- pkgJSON.version,
31
- process.version
32
- )
47
+ function initApi({ agent, apiPath }) {
48
+ const API = require(apiPath)
33
49
 
34
- if (require.cache.__NR_cache) {
35
- logger.warn(
36
- 'Attempting to load a second copy of newrelic from %s, using cache instead',
37
- __dirname
38
- )
39
- if (require.cache.__NR_cache.agent) {
40
- require.cache.__NR_cache.agent.recordSupportability('Agent/DoubleLoad')
41
- }
42
- module.exports = require.cache.__NR_cache
43
- } else {
44
- initialize()
50
+ const api = new API(agent)
51
+ require.cache.__NR_cache = module.exports = api
45
52
  }
46
53
 
47
54
  function initialize() {
@@ -103,15 +110,7 @@ function initialize() {
103
110
  /* eslint-enable no-console */
104
111
  }
105
112
 
106
- let API = null
107
- if (agent) {
108
- API = require('./api')
109
- } else {
110
- API = require('./stub_api')
111
- }
112
-
113
- const api = new API(agent)
114
- require.cache.__NR_cache = module.exports = api
113
+ const api = agent ? initApi({ agent, apiPath: './api' }) : initApi({ apiPath: './stub_api' })
115
114
 
116
115
  // If we loaded an agent, record a startup time for the agent.
117
116
  // NOTE: Metrics are recorded in seconds, so divide the value by 1000.
@@ -10,7 +10,6 @@ exports.prerelease = {
10
10
  express5: false,
11
11
  promise_segments: false,
12
12
  reverse_naming_rules: false,
13
- undici_instrumentation: false,
14
13
  undici_async_tracking: true,
15
14
  unresolved_promise_cleanup: true,
16
15
  legacy_context_manager: false
@@ -33,7 +32,8 @@ exports.released = [
33
32
  'fastify_instrumentation',
34
33
  'await_support',
35
34
  'certificate_bundle',
36
- 'async_local_context'
35
+ 'async_local_context',
36
+ 'undici_instrumentation'
37
37
  ]
38
38
 
39
39
  // flags that are no longer used for unreleased features
@@ -0,0 +1,144 @@
1
+ /*
2
+ * Copyright 2023 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+ const logger = require('../../logger').child({ component: 'ElasticSearch' })
8
+
9
+ /**
10
+ * Instruments the `@elastic/elasticsearch` module. This function is
11
+ * passed to `onRequire` when instantiating instrumentation.
12
+ *
13
+ * @param {object} _agent New Relic agent
14
+ * @param {object} elastic resolved module
15
+ * @param {string} _moduleName string representation of require/import path
16
+ * @param {object} shim New Relic shim
17
+ * @returns {void}
18
+ */
19
+ module.exports = function initialize(_agent, elastic, _moduleName, shim) {
20
+ shim.setDatastore(shim.ELASTICSEARCH)
21
+ shim.setParser(queryParser)
22
+
23
+ shim.recordQuery(elastic.Transport.prototype, 'request', function wrapQuery(shim, _, __, args) {
24
+ const ctx = this
25
+ return {
26
+ query: JSON.stringify(args?.[0]),
27
+ promise: true,
28
+ opaque: true,
29
+ inContext: function inContext() {
30
+ getConnection.call(ctx, shim)
31
+ }
32
+ }
33
+ })
34
+ }
35
+
36
+ /**
37
+ * Parses the parameters sent to elasticsearch for collection,
38
+ * method, and query
39
+ *
40
+ * @param {object} params Query object received by the datashim.
41
+ * Required properties: path {string}, method {string}.
42
+ * Optional properties: querystring {string}, body {object}, and
43
+ * bulkBody {object}
44
+ * @returns {object} consisting of collection {string}, operation {string},
45
+ * and query {string}
46
+ */
47
+ function queryParser(params) {
48
+ params = JSON.parse(params)
49
+
50
+ const { collection, operation } = parsePath(params.path, params.method)
51
+
52
+ // the substance of the query may be in querystring or in body.
53
+ let queryParam = {}
54
+ if (typeof params.querystring === 'object' && Object.keys(params.querystring).length > 0) {
55
+ queryParam = params.querystring
56
+ }
57
+ // let body or bulkBody override querystring, as some requests have both
58
+ if (typeof params.body === 'object' && Object.keys(params.body).length > 0) {
59
+ queryParam = params.body
60
+ } else if (typeof params.bulkBody === 'object' && Object.keys(params.bulkBody).length > 0) {
61
+ queryParam = params.bulkBody
62
+ }
63
+
64
+ const query = JSON.stringify(queryParam)
65
+
66
+ return {
67
+ collection,
68
+ operation,
69
+ query
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Convenience function for parsing the params.path sent to the queryParser
75
+ * for normalized collection and operation
76
+ *
77
+ * @param {string} pathString params.path supplied to the query parser
78
+ * @param {string} method http method called by @elastic/elasticsearch
79
+ * @returns {object} consisting of collection {string} and operation {string}
80
+ */
81
+ function parsePath(pathString, method) {
82
+ let collection
83
+ let operation
84
+ const defaultCollection = 'any'
85
+ const actions = {
86
+ GET: 'get',
87
+ PUT: 'create',
88
+ POST: 'create',
89
+ DELETE: 'delete',
90
+ HEAD: 'exists'
91
+ }
92
+ const suffix = actions[method]
93
+
94
+ try {
95
+ const path = pathString.split('/')
96
+ if (method === 'PUT' && path.length === 2) {
97
+ collection = path?.[1] || defaultCollection
98
+ operation = `index.create`
99
+ return { collection, operation }
100
+ }
101
+ path.forEach((segment, idx) => {
102
+ const prev = idx - 1
103
+ let opname
104
+ if (segment === '_search') {
105
+ collection = path?.[prev] || defaultCollection
106
+ operation = `search`
107
+ } else if (segment[0] === '_') {
108
+ opname = segment.substring(1)
109
+ collection = path?.[prev] || defaultCollection
110
+ operation = `${opname}.${suffix}`
111
+ }
112
+ })
113
+ if (!operation && !collection) {
114
+ // likely creating an index--no underscore segments
115
+ collection = path?.[1] || defaultCollection
116
+ operation = `index.${suffix}`
117
+ }
118
+ } catch (e) {
119
+ logger.warn('Failed to parse path for operation and collection. Using defaults')
120
+ logger.warn(e)
121
+ collection = defaultCollection
122
+ operation = 'unknown'
123
+ }
124
+
125
+ return { collection, operation }
126
+ }
127
+
128
+ /**
129
+ * Convenience function for deriving connection information from
130
+ * elasticsearch
131
+ *
132
+ * @param {object} shim The New Relic datastore-shim
133
+ * @returns {Function} captureInstanceAttributes method of shim
134
+ */
135
+ function getConnection(shim) {
136
+ const connectionPool = this.connectionPool.connections[0]
137
+ const host = connectionPool.url.host.split(':')
138
+ const port = connectionPool.url.port || host?.[1]
139
+ return shim.captureInstanceAttributes(host[0], port)
140
+ }
141
+
142
+ module.exports.queryParser = queryParser
143
+ module.exports.parsePath = parsePath
144
+ module.exports.getConnection = getConnection
@@ -12,48 +12,38 @@ const NAMES = require('../metrics/names')
12
12
  const NEWRELIC_SYNTHETICS_HEADER = 'x-newrelic-synthetics'
13
13
  const symbols = require('../symbols')
14
14
  const { executionAsyncResource } = require('async_hooks')
15
- const tls = require('tls')
16
- const net = require('net')
17
-
18
- let diagnosticsChannel = null
19
- try {
20
- diagnosticsChannel = require('diagnostics_channel')
21
- } catch (e) {
22
- // quick check to see if module exists
23
- // module was not added until v15.x
24
- }
15
+ const diagnosticsChannel = require('diagnostics_channel')
25
16
 
26
- module.exports = function addUndiciChannels(agent, _undici, _modName, shim) {
27
- if (!diagnosticsChannel || !agent.config.feature_flag.undici_instrumentation) {
28
- logger.warn(
29
- 'diagnostics_channel or feature_flag.undici_instrumentation = false. Skipping undici instrumentation.'
30
- )
31
- return
32
- }
33
-
34
- registerHookPoints(shim)
35
- }
17
+ const channels = [
18
+ { channel: diagnosticsChannel.channel('undici:request:create'), hook: requestCreateHook },
19
+ { channel: diagnosticsChannel.channel('undici:request:headers'), hook: requestHeadersHook },
20
+ { channel: diagnosticsChannel.channel('undici:request:trailers'), hook: endAndRestoreSegment },
21
+ { channel: diagnosticsChannel.channel('undici:request:error'), hook: endAndRestoreSegment }
22
+ ]
36
23
 
37
24
  /**
38
25
  * Subscribes to all relevant undici hook points
39
26
  * See: https://github.com/nodejs/undici/blob/main/docs/api/DiagnosticsChannel.md
40
27
  *
41
- * @param {Shim} shim instance of shim
28
+ * @param agent
29
+ * @param _undici
30
+ * @param _modName
31
+ * @param shim
42
32
  */
43
- function registerHookPoints(shim) {
44
- diagnosticsChannel.channel('undici:request:create').subscribe(requestCreateHook.bind(null, shim))
45
- diagnosticsChannel
46
- .channel('undici:client:sendHeaders')
47
- .subscribe(responseHeadersHook.bind(null, shim))
48
- diagnosticsChannel
49
- .channel('undici:request:headers')
50
- .subscribe(requestHeadersHook.bind(null, shim))
51
- diagnosticsChannel
52
- .channel('undici:request:trailers')
53
- .subscribe(endAndRestoreSegment.bind(null, shim))
54
- diagnosticsChannel
55
- .channel('undici:request:error')
56
- .subscribe(endAndRestoreSegment.bind(null, shim))
33
+ module.exports = function addUndiciChannels(agent, _undici, _modName, shim) {
34
+ channels.forEach(({ channel, hook }) => {
35
+ if (!channel.hasSubscribers) {
36
+ channel.subscribe(hook.bind(null, shim))
37
+ }
38
+ })
39
+ }
40
+
41
+ module.exports.unsubscribe = function unsubscribe() {
42
+ channels.forEach(({ channel, hook }) => {
43
+ if (channel.hasSubscribers) {
44
+ channel.unsubscribe(hook)
45
+ }
46
+ })
57
47
  }
58
48
 
59
49
  /**
@@ -90,29 +80,14 @@ function getParentSegment(shim) {
90
80
  }
91
81
 
92
82
  /**
93
- * This event occurs after the Undici Request is created
94
- * We will check current segment for opaque and also attach
95
- * relevant headers to outgoing http request
83
+ * Injects relevant DT headers for the external request
96
84
  *
97
- * @param {Shim} shim instance of shim
98
- * @param {object} params object from undici hook
85
+ * @param {object} params object to fn
86
+ * @param {Shim} params.transaction current transaction
99
87
  * @param {object} params.request undici request object
88
+ * @param {object} params.config agent config
100
89
  */
101
- function requestCreateHook(shim, { request }) {
102
- const { config } = shim.agent
103
- const parent = getParentSegment(shim)
104
- request[symbols.parentSegment] = parent
105
- if (!parent || (parent && parent.opaque)) {
106
- logger.trace(
107
- 'Not capturing data for outbound request (%s) because parent segment opaque (%s)',
108
- request.path,
109
- parent && parent.name
110
- )
111
-
112
- return
113
- }
114
-
115
- const transaction = parent.transaction
90
+ function addDTHeaders({ transaction, config, request }) {
116
91
  const outboundHeaders = Object.create(null)
117
92
  if (config.encoding_key && transaction.syntheticsHeader) {
118
93
  outboundHeaders[NEWRELIC_SYNTHETICS_HEADER] = transaction.syntheticsHeader
@@ -133,37 +108,19 @@ function requestCreateHook(shim, { request }) {
133
108
  }
134
109
 
135
110
  /**
136
- * This event occurs right before the data is written to the socket.
137
- * Undici has some abstracted headers that are only created at this time, one
138
- * is the `host` header which we need to name the Undici segment. So in this
139
- * handler we create, start and set the segment active, name it, and
140
- * attach the url/procedure/request.parameters
111
+ * Creates the external segment with url, procedure and request.parameters attributes
141
112
  *
142
- * @param {Shim} shim instance of shim
143
- * @param {object} params object from undici hook
113
+ * @param {object} params object to fn
114
+ * @param {Shim} params.shim instance of shim
144
115
  * @param {object} params.request undici request object
145
- * @param {tls.TLSSocket | net.Socket} params.socket active socket connection
116
+ * @param {object} params.parentSegment current active, about to be parent of external segment
146
117
  */
147
- function responseHeadersHook(shim, { request, socket }) {
148
- const parentSegment = request[symbols.parentSegment]
149
- if (!parentSegment || (parentSegment && parentSegment.opaque)) {
150
- return
151
- }
152
-
153
- const port = socket.remotePort
154
- const isHttps = socket.servername
155
- let urlString
156
- if (isHttps) {
157
- urlString = `https://${socket.servername}`
158
- urlString += port === 443 ? request.path : `:${port}${request.path}`
159
- } else {
160
- urlString = `http://${socket._host}`
161
- urlString += port === 80 ? request.path : `:${port}${request.path}`
162
- }
163
-
164
- const url = new URL(urlString)
165
-
118
+ function createExternalSegment({ shim, request, parentSegment }) {
119
+ const url = new URL(request.origin + request.path)
166
120
  const name = NAMES.EXTERNAL.PREFIX + url.host + url.pathname
121
+ // Metrics for `External/<host>` will have a suffix of undici
122
+ // We will have to see if this matters for people only using fetch
123
+ // It's undici under the hood so ¯\_(ツ)_/¯
167
124
  const segment = shim.createSegment(name, recordExternal(url.host, 'undici'), parentSegment)
168
125
  if (segment) {
169
126
  segment.start()
@@ -178,6 +135,38 @@ function responseHeadersHook(shim, { request, socket }) {
178
135
  }
179
136
  }
180
137
 
138
+ /**
139
+ * This event occurs after the Undici Request is created.
140
+ * We will check current segment for opaque before creating the
141
+ * external segment with the standard url/procedure/request.parameters
142
+ * attributes. We will also attach relevant DT headers to outgoing http request.
143
+ *
144
+ * @param {Shim} shim instance of shim
145
+ * @param {object} params object from undici hook
146
+ * @param {object} params.request undici request object
147
+ */
148
+ function requestCreateHook(shim, { request }) {
149
+ const { config } = shim.agent
150
+ const parentSegment = getParentSegment(shim)
151
+ request[symbols.parentSegment] = parentSegment
152
+ if (!parentSegment || (parentSegment && parentSegment.opaque)) {
153
+ logger.trace(
154
+ 'Not capturing data for outbound request (%s) because parent segment opaque (%s)',
155
+ request.path,
156
+ parentSegment && parentSegment.name
157
+ )
158
+
159
+ return
160
+ }
161
+
162
+ try {
163
+ addDTHeaders({ transaction: parentSegment.transaction, config, request })
164
+ createExternalSegment({ shim, request, parentSegment })
165
+ } catch (err) {
166
+ logger.warn(err, 'Unable to create external segment')
167
+ }
168
+ }
169
+
181
170
  /**
182
171
  * This event occurs after the response headers have been received.
183
172
  * We will add the relevant http response attributes to active segment.
@@ -17,6 +17,7 @@ module.exports = function instrumentations() {
17
17
  'bluebird': { type: MODULE_TYPE.PROMISE },
18
18
  'bunyan': { type: MODULE_TYPE.GENERIC },
19
19
  'director': { type: MODULE_TYPE.WEB_FRAMEWORK },
20
+ '@elastic/elasticsearch': { type: MODULE_TYPE.DATASTORE },
20
21
  'express': { type: MODULE_TYPE.WEB_FRAMEWORK },
21
22
  'fastify': { type: MODULE_TYPE.WEB_FRAMEWORK },
22
23
  'generic-pool': { type: MODULE_TYPE.GENERIC },
@@ -36,7 +37,6 @@ module.exports = function instrumentations() {
36
37
  '@redis/client': { type: MODULE_TYPE.DATASTORE },
37
38
  'restify': { type: MODULE_TYPE.WEB_FRAMEWORK },
38
39
  'superagent': { module: '@newrelic/superagent' },
39
- 'undici': { type: MODULE_TYPE.TRANSACTION },
40
40
  '@hapi/vision': { type: MODULE_TYPE.WEB_FRAMEWORK },
41
41
  'when': { module: './instrumentation/when' },
42
42
  'winston': { type: MODULE_TYPE.GENERIC },
@@ -29,6 +29,7 @@ const util = require('util')
29
29
  const DATASTORE_NAMES = {
30
30
  CASSANDRA: 'Cassandra',
31
31
  DYNAMODB: 'DynamoDB',
32
+ ELASTICSEARCH: 'ElasticSearch',
32
33
  MEMCACHED: 'Memcache',
33
34
  MONGODB: 'MongoDB',
34
35
  MYSQL: 'MySQL',
package/lib/shimmer.js CHANGED
@@ -10,7 +10,6 @@ const semver = require('semver')
10
10
  const fs = require('./util/unwrapped-core').fs
11
11
  const logger = require('./logger').child({ component: 'shimmer' })
12
12
  const INSTRUMENTATIONS = require('./instrumentations')()
13
- const properties = require('./util/properties')
14
13
  const shims = require('./shim')
15
14
  const { Hook } = require('require-in-the-middle')
16
15
  const IitmHook = require('import-in-the-middle')
@@ -18,6 +17,7 @@ let pkgsToHook = []
18
17
 
19
18
  const NAMES = require('./metrics/names')
20
19
  const symbols = require('./symbols')
20
+ const { unsubscribe } = require('./instrumentation/undici')
21
21
 
22
22
  const MODULE_TYPE = shims.constants.MODULE_TYPE
23
23
 
@@ -91,7 +91,7 @@ const shimmer = (module.exports = {
91
91
  * Detects if the given function has already been wrapped.
92
92
  *
93
93
  * @param {Function} fn - The function to look for a wrapper on.
94
- * @returns {bool} True if `fn` exists and has an attached original, else false.
94
+ * @returns {boolean} True if `fn` exists and has an attached original, else false.
95
95
  */
96
96
  isWrapped: function isWrapped(fn) {
97
97
  return !!(fn && fn[symbols.original])
@@ -182,7 +182,7 @@ const shimmer = (module.exports = {
182
182
  * helpful than you'd think.
183
183
  * @param {string} property The property to replace with the accessor.
184
184
  * @param {Function} options Optional getter and setter to use for the accessor.
185
- * @returns {object} The original value of the property.
185
+ * @returns {object|undefined} returns original function
186
186
  */
187
187
  wrapDeprecated: function wrapDeprecated(nodule, noduleName, property, options) {
188
188
  if (!property) {
@@ -328,6 +328,12 @@ const shimmer = (module.exports = {
328
328
  const globalsFilepath = path.join(__dirname, 'instrumentation', 'core', 'globals.js')
329
329
  _firstPartyInstrumentation(agent, globalsFilepath, globalShim, global, 'globals')
330
330
 
331
+ // Since this just registers subscriptions to diagnostics_channel events from undici
332
+ // We register this as core and it'll work for both fetch and undici
333
+ const undiciPath = path.join(__dirname, 'instrumentation', 'undici.js')
334
+ const undiciShim = shims.createShimFromType(MODULE_TYPE.TRANSACTION, agent, 'undici', 'undici')
335
+ _firstPartyInstrumentation(agent, undiciPath, undiciShim)
336
+
331
337
  // Instrument each of the core modules.
332
338
  for (const [mojule, core] of Object.entries(CORE_INSTRUMENTATION)) {
333
339
  const filePath = path.join(__dirname, 'instrumentation', 'core', core.file)
@@ -344,14 +350,6 @@ const shimmer = (module.exports = {
344
350
  _firstPartyInstrumentation(agent, filePath, shim, uninstrumented, mojule)
345
351
  }
346
352
  },
347
- // TODO: remove the following methods once test-utilities have been updated and released
348
- // with the new registerHooks and removeHooks methods
349
- patchModule(agent) {
350
- shimmer.registerHooks(agent)
351
- },
352
- unpatchModule() {
353
- shimmer.removeHooks()
354
- },
355
353
  registerHooks(agent) {
356
354
  this._ritm = new Hook(pkgsToHook, function onHook(exports, name, basedir) {
357
355
  return _postLoad(agent, exports, name, basedir)
@@ -372,6 +370,7 @@ const shimmer = (module.exports = {
372
370
  }
373
371
 
374
372
  pkgsToHook = []
373
+ unsubscribe()
375
374
  },
376
375
 
377
376
  bootstrapInstrumentation: function bootstrapInstrumentation(agent) {
@@ -396,6 +395,8 @@ const shimmer = (module.exports = {
396
395
  }
397
396
  }
398
397
 
398
+ opts[symbols.instrumented] = new Set()
399
+ opts[symbols.instrumentedErrored] = new Set()
399
400
  shimmer.registeredInstrumentations[opts.moduleName].push({ ...opts })
400
401
  },
401
402
 
@@ -418,8 +419,9 @@ const shimmer = (module.exports = {
418
419
  *
419
420
  * Use this to re-apply any applicable instrumentation.
420
421
  *
421
- * @param agent
422
- * @param modulePath
422
+ * @param {object} agent NR agent
423
+ * @param {string} modulePath path to module getting required
424
+ * @returns {object} exported module
423
425
  */
424
426
  reinstrument: function reinstrument(agent, modulePath) {
425
427
  return _postLoad(agent, require(modulePath), modulePath)
@@ -430,7 +432,8 @@ const shimmer = (module.exports = {
430
432
  * instrumentation. These two things are usually, but not always,
431
433
  * the same.
432
434
  *
433
- * @param moduleName
435
+ * @param {string} moduleName name of module getting instrumented
436
+ * @returns {string} name unless pg.js and then returns pg
434
437
  */
435
438
  getInstrumentationNameFromModuleName(moduleName) {
436
439
  // XXX When updating these special cases, also update `uninstrumented`.
@@ -447,12 +450,13 @@ const shimmer = (module.exports = {
447
450
  * only if every hook succeeded.
448
451
  *
449
452
  * @param {string} moduleName name of registered instrumentation
450
- * @returns {boolean}
453
+ * @returns {boolean} if all instrumentation hooks ran for a given version
451
454
  */
452
455
  isInstrumented(moduleName) {
456
+ const pkgVersion = shimmer.getPackageVersion(moduleName)
453
457
  const instrumentations = shimmer.registeredInstrumentations[moduleName]
454
- const didInstrument = instrumentations.filter(
455
- (instrumentation) => instrumentation[symbols.instrumented]
458
+ const didInstrument = instrumentations.filter((instrumentation) =>
459
+ instrumentation[symbols.instrumented].has(pkgVersion)
456
460
  )
457
461
  return didInstrument.length === instrumentations.length
458
462
  },
@@ -463,6 +467,23 @@ const shimmer = (module.exports = {
463
467
  // previously it would just call instrumentation
464
468
  // and not check the result
465
469
  return returnModule ? result : shimmer.isInstrumented(moduleName)
470
+ },
471
+
472
+ /**
473
+ * Gets the version of a given package by parsing it from package.json
474
+ *
475
+ * @param {string} moduleName name of module
476
+ * @returns {string} version, defaults to Node.js version when it cannot parse
477
+ */
478
+ getPackageVersion(moduleName) {
479
+ try {
480
+ const { basedir } = shimmer.registeredInstrumentations[moduleName]
481
+ const pkg = require(path.resolve(basedir, 'package.json'))
482
+ return pkg.version
483
+ } catch (err) {
484
+ logger.debug('Failed to get version for `%s`, reason: %s', moduleName, err.message)
485
+ return process.version
486
+ }
466
487
  }
467
488
  })
468
489
 
@@ -486,23 +507,23 @@ function applyDebugState(shim, nodule, inEsm) {
486
507
  * initialization function that takes the agent and the module to be
487
508
  * instrumented.
488
509
  *
489
- * @param agent
490
- * @param nodule
491
- * @param moduleName
492
- * @param resolvedName
493
- * @param esmResolver
510
+ * @param {object} agent NR agent
511
+ * @param {object} nodule Class or module containing the function to wrap.
512
+ * @param {string} moduleName name of module
513
+ * @param {string} resolvedName fully qualified path to module
514
+ * @param {boolean} esmResolver indicates if it came from esm loader
515
+ * @returns {object|undefined} returns exported module unless already instrumented
494
516
  */
495
517
  function instrumentPostLoad(agent, nodule, moduleName, resolvedName, esmResolver) {
518
+ // default to Node.js version, this occurs for core libraries
519
+ const pkgVersion = resolvedName ? shimmer.getPackageVersion(moduleName) : process.version
496
520
  const instrumentations = shimmer.registeredInstrumentations[moduleName]
497
521
  instrumentations.forEach((instrumentation) => {
498
- if (
499
- properties.hasOwn(instrumentation, symbols.instrumented) ||
500
- properties.hasOwn(instrumentation, symbols.instrumentedErrored)
501
- ) {
502
- logger.trace(
503
- 'Already instrumented or failed to instrument %s, skipping redundant instrumentation',
504
- moduleName
505
- )
522
+ const isInstrumented = instrumentation[symbols.instrumented].has(pkgVersion)
523
+ const failedInstrumentation = instrumentation[symbols.instrumentedErrored].has(pkgVersion)
524
+ if (isInstrumented || failedInstrumentation) {
525
+ const msg = isInstrumented ? 'Already instrumented' : 'Failed to instrument'
526
+ logger.trace(`${msg} ${moduleName}@${pkgVersion}, skipping registering instrumentation`)
506
527
  return
507
528
  }
508
529
 
@@ -530,38 +551,71 @@ function instrumentPostLoad(agent, nodule, moduleName, resolvedName, esmResolver
530
551
  // that occur directly above this. No reason to attempt to load instrumentation
531
552
  // as it does not exist.
532
553
  if (instrumentation.type === MODULE_TYPE.TRACKING) {
533
- instrumentation[symbols.instrumented] = true
554
+ instrumentation[symbols.instrumented].add(pkgVersion)
534
555
  return
535
556
  }
536
557
 
537
- try {
538
- if (instrumentation.onRequire(shim, resolvedNodule, moduleName) !== false) {
539
- nodule = shim.getExport(nodule)
540
- instrumentation[symbols.instrumented] = true
541
- }
542
- } catch (instrumentationError) {
543
- instrumentation[symbols.instrumentedErrored] = true
544
- if (instrumentation.onError) {
545
- try {
546
- instrumentation.onError(instrumentationError)
547
- } catch (e) {
548
- logger.warn(
549
- e,
550
- instrumentationError,
551
- 'Custom instrumentation for %s failed, then the onError handler threw an error',
552
- moduleName
553
- )
554
- }
555
- } else {
558
+ nodule = loadInstrumentation({
559
+ shim,
560
+ resolvedNodule,
561
+ pkgVersion,
562
+ moduleName,
563
+ nodule,
564
+ instrumentation
565
+ })
566
+ })
567
+
568
+ return nodule
569
+ }
570
+
571
+ /**
572
+ * Attempts to execute an onRequire hook for a given module.
573
+ * If it fails it will call an onError hook and log warnings accordingly
574
+ *
575
+ * @param {object} params wrapping object to function
576
+ * @param {*} params.shim The instance of the shim used to instrument the module.
577
+ * @param {object} params.nodule Class or module containing the function to wrap.
578
+ * @param {object} params.resolvedNodule returns xport of the default property
579
+ * @param {string} params.pkgVersion version of module
580
+ * @param {string} params.moduleName module name
581
+ * @param {object} params.instrumentation hooks for a give module
582
+ * @returns {object} updated xport module
583
+ */
584
+ function loadInstrumentation({
585
+ shim,
586
+ resolvedNodule,
587
+ pkgVersion,
588
+ moduleName,
589
+ nodule,
590
+ instrumentation
591
+ }) {
592
+ try {
593
+ if (instrumentation.onRequire(shim, resolvedNodule, moduleName) !== false) {
594
+ nodule = shim.getExport(nodule)
595
+ instrumentation[symbols.instrumented].add(pkgVersion)
596
+ }
597
+ } catch (instrumentationError) {
598
+ instrumentation[symbols.instrumentedErrored].add(pkgVersion)
599
+ if (instrumentation.onError) {
600
+ try {
601
+ instrumentation.onError(instrumentationError)
602
+ } catch (e) {
556
603
  logger.warn(
604
+ e,
557
605
  instrumentationError,
558
- 'Custom instrumentation for %s failed. Please report this to the ' +
559
- 'maintainers of the custom instrumentation.',
606
+ 'Custom instrumentation for %s failed, then the onError handler threw an error',
560
607
  moduleName
561
608
  )
562
609
  }
610
+ } else {
611
+ logger.warn(
612
+ instrumentationError,
613
+ 'Custom instrumentation for %s failed. Please report this to the ' +
614
+ 'maintainers of the custom instrumentation.',
615
+ moduleName
616
+ )
563
617
  }
564
- })
618
+ }
565
619
 
566
620
  return nodule
567
621
  }
@@ -589,10 +643,12 @@ function _postLoad(agent, nodule, name, resolvedName, esmResolver) {
589
643
  const hasPostLoadInstrumentation =
590
644
  registeredInstrumentation &&
591
645
  registeredInstrumentation.length &&
592
- registeredInstrumentation.filter((instrumentation) => instrumentation.onRequire).length
646
+ registeredInstrumentation.filter((hook) => hook.onRequire).length
593
647
 
594
648
  // Check if this is a known instrumentation and then run it.
595
649
  if (hasPostLoadInstrumentation) {
650
+ // Add the basedir to the instrumentation to be used later to parse version from package.json
651
+ registeredInstrumentation.basedir = resolvedName
596
652
  logger.trace('Instrumenting %s with onRequire (module loaded) hook.', name)
597
653
  return instrumentPostLoad(agent, nodule, instrumentation, resolvedName, esmResolver)
598
654
  }
package/lib/symbols.js CHANGED
@@ -18,6 +18,7 @@ module.exports = {
18
18
  offTheRecord: Symbol('offTheRecord'),
19
19
  original: Symbol('original'),
20
20
  wrapped: Symbol('shimWrapped'),
21
+ parentSegment: Symbol('parentSegment'),
21
22
  prismaConnection: Symbol('prismaConnection'),
22
23
  prismaModelCall: Symbol('modelCall'),
23
24
  segment: Symbol('segment'),
@@ -218,13 +218,13 @@ Trace.prototype.getTotalTimeDurationInMillis = function getTotalTimeDurationInMi
218
218
  if (this.root.children.length === 0) {
219
219
  return 0
220
220
  }
221
- let segments = this.root.getChildren()
221
+ const segments = this.root.getChildren()
222
222
  let totalTimeInMillis = 0
223
223
 
224
224
  while (segments.length !== 0) {
225
225
  const segment = segments.pop()
226
226
  totalTimeInMillis += segment.getExclusiveDurationInMillis()
227
- segments = segments.concat(segment.getChildren())
227
+ segment.getChildren().forEach((childSegment) => segments.push(childSegment))
228
228
  }
229
229
 
230
230
  if (!this.transaction.isActive()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "newrelic",
3
- "version": "11.0.0",
3
+ "version": "11.2.0",
4
4
  "author": "New Relic Node.js agent team <nodejs@newrelic.com>",
5
5
  "license": "Apache-2.0",
6
6
  "contributors": [
@@ -153,7 +153,7 @@
153
153
  "lint:lockfile": "lockfile-lint --path package-lock.json --type npm --allowed-hosts npm --validate-https --validate-integrity",
154
154
  "public-docs": "jsdoc -c ./jsdoc-conf.json --tutorials examples/shim api.js lib/shim/ lib/transaction/handle.js && cp examples/shim/*.png out/",
155
155
  "publish-docs": "./bin/publish-docs.sh",
156
- "services": "./bin/docker-services.sh",
156
+ "services": "docker compose up -d --wait",
157
157
  "smoke": "npm run ssl && time tap test/smoke/**/**/*.tap.js --timeout=180 --no-coverage",
158
158
  "ssl": "./bin/ssl.sh",
159
159
  "sub-install": "node test/bin/install_sub_deps",
@@ -165,8 +165,12 @@
165
165
  "versioned-tests": "./bin/run-versioned-tests.sh",
166
166
  "update-changelog-version": "node ./bin/update-changelog-version",
167
167
  "checkout-external-versioned": "node ./test/versioned-external/checkout-external-tests.js",
168
+ "versioned:internal:major": "VERSIONED_MODE=--major npm run versioned:internal",
169
+ "versioned:internal": "npm run prepare-test && EXTERNAL_MODE=none time ./bin/run-versioned-tests.sh",
170
+ "versioned:external:major": "VERSIONED_MODE=--major npm run versioned:external",
171
+ "versioned:external": "npm run checkout-external-versioned && SKIP_C8=true EXTERNAL_MODE=only time ./bin/run-versioned-tests.sh",
168
172
  "versioned:major": "VERSIONED_MODE=--major npm run versioned",
169
- "versioned": "npm run checkout-external-versioned && npm run prepare-test && NPM7=1 time ./bin/run-versioned-tests.sh",
173
+ "versioned": "npm run checkout-external-versioned && npm run prepare-test && time ./bin/run-versioned-tests.sh",
170
174
  "versioned:legacy-context": "NEW_RELIC_FEATURE_FLAG_LEGACY_CONTEXT_MANAGER=1 npm run versioned",
171
175
  "versioned:legacy_context:major": "NEW_RELIC_FEATURE_FLAG_LEGACY_CONTEXT_MANAGER=1 npm run versioned:major",
172
176
  "versioned:security": "NEW_RELIC_SECURITY_AGENT_ENABLED=true npm run versioned",
@@ -181,7 +185,7 @@
181
185
  "@grpc/proto-loader": "^0.7.5",
182
186
  "@newrelic/aws-sdk": "^7.0.0",
183
187
  "@newrelic/koa": "^8.0.0",
184
- "@newrelic/security-agent": "0.2.1",
188
+ "@newrelic/security-agent": "0.3.0",
185
189
  "@newrelic/superagent": "^7.0.0",
186
190
  "@tyriar/fibonacci-heap": "^2.0.7",
187
191
  "concat-stream": "^2.0.0",
@@ -189,6 +193,7 @@
189
193
  "import-in-the-middle": "^1.4.2",
190
194
  "json-bigint": "^1.0.0",
191
195
  "json-stringify-safe": "^5.0.0",
196
+ "module-details-from-path": "^1.0.3",
192
197
  "readable-stream": "^3.6.1",
193
198
  "require-in-the-middle": "^7.2.0",
194
199
  "semver": "^7.5.2",
@@ -202,12 +207,12 @@
202
207
  "devDependencies": {
203
208
  "@newrelic/eslint-config": "^0.3.0",
204
209
  "@newrelic/newrelic-oss-cli": "^0.1.2",
205
- "@newrelic/test-utilities": "^8.0.0",
210
+ "@newrelic/test-utilities": "^8.1.0",
206
211
  "@octokit/rest": "^18.0.15",
207
212
  "@slack/bolt": "^3.7.0",
208
213
  "ajv": "^6.12.6",
209
214
  "async": "^3.2.4",
210
- "c8": "^7.14.0",
215
+ "c8": "^8.0.1",
211
216
  "chai": "^4.1.2",
212
217
  "clean-jsdoc-theme": "^4.2.4",
213
218
  "commander": "^7.0.0",