newrelic 9.1.0 → 9.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NEWS.md +46 -20
- package/README.md +41 -0
- package/THIRD_PARTY_NOTICES.md +27 -4
- package/api.js +3 -1
- package/esm-loader.mjs +124 -14
- package/lib/collector/remote-method.js +3 -0
- package/lib/config/default.js +10 -2
- package/lib/config/env.js +4 -1
- package/lib/esm-shim.mjs +36 -0
- package/lib/instrumentation/bunyan.js +110 -0
- package/lib/instrumentations.js +1 -0
- package/lib/metrics/names.js +4 -1
- package/lib/shimmer.js +2 -2
- package/newrelic.js +0 -8
- package/package.json +8 -7
package/NEWS.md
CHANGED
|
@@ -1,27 +1,53 @@
|
|
|
1
|
-
### v9.
|
|
1
|
+
### v9.3.0 (2022-10-17)
|
|
2
2
|
|
|
3
|
-
* Added
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
* Added instrumentation to bunyan to support application logging use cases: forwarding, local decorating, and metrics.
|
|
4
|
+
|
|
5
|
+
Big thanks to @brianphillips for his contribution 🚀
|
|
6
6
|
|
|
7
|
-
*
|
|
8
|
-
* Added new metrics to track usage of ESM loader(`Supportability/Features/ESM/Loader` and `Supportability/Features/ESM/UnsupportedLoader`).
|
|
9
|
-
* Updated instrumentation map to include an optional "friendly name" for tracking metrics.
|
|
7
|
+
* Added c8 to track code coverage.
|
|
10
8
|
|
|
11
|
-
*
|
|
12
|
-
|
|
13
|
-
* Fixed an issue with mongodb instrumentation where IPv6 address([::1]) was not getting mapped to localhost when setting the host attribute on the segment.
|
|
14
|
-
|
|
15
|
-
* Added a test ESM loader to properly mock out agent in versioned tests.
|
|
16
|
-
|
|
17
|
-
* Added ESM versioned tests for: `express`, `pg`, `mongodb`, and `@grpc/grpc-js`.
|
|
18
|
-
|
|
19
|
-
### v9.0.3 (2022-09-06)
|
|
20
|
-
|
|
21
|
-
* Updated gRPC client instrumentation to respect `grpc.record_errors` when deciding to log errors on gRPC client requests.
|
|
22
|
-
|
|
23
|
-
* Fixed transaction name finalization to properly copy the appropriate transaction name to root segment.
|
|
9
|
+
* Added documentation about custom instrumentation in ES module applications
|
|
24
10
|
|
|
11
|
+
### v9.2.0 (2022-10-06)
|
|
12
|
+
|
|
13
|
+
* Added ability to instrument ES Modules with the New Relic ESM Loader.
|
|
14
|
+
* [Example ESM application](https://github.com/newrelic/newrelic-node-examples/tree/main/esm-app)
|
|
15
|
+
|
|
16
|
+
* Added support for custom ESM instrumentation.
|
|
17
|
+
* There is structure to registering custom ESM instrumentation. Set the relative path to the instrumentation entry point via `api.esm.custom_instrumentation_entrypoint`
|
|
18
|
+
* [Sample custom ESM instrumentation entrypoint](https://github.com/newrelic/newrelic-node-examples/blob/main/esm-app/custom-instrumentation/index.js)
|
|
19
|
+
* All the `newrelic.instrument*` methods will still work except `newrelic.instrumentLoadedModule`. This is because it is geared toward CommonJS modules.
|
|
20
|
+
|
|
21
|
+
* Added test for asserting ESM loader functionality on ESM-only package
|
|
22
|
+
|
|
23
|
+
* Added supportability metric of `Supportability/Nodejs/Collector/MaxPayloadSizeLimit/<endpoint>` when `max_payload_size_in_bytes` configuration value is exceeded.
|
|
24
|
+
|
|
25
|
+
* Removed `application_logging.forwarding.enabled` stanza from sample config as the feature is now enabled by default.
|
|
26
|
+
|
|
27
|
+
### v9.1.0 (2022-09-22)
|
|
28
|
+
|
|
29
|
+
* Added [experimental loader](https://nodejs.org/api/esm.html#loaders) to support instrumentation of CommonJS packages in ECMAScript Module(ESM) applications.
|
|
30
|
+
* It only supports versions of Node.js >= `16.12.0`.
|
|
31
|
+
* It is subject to change due to its experimental stability.
|
|
32
|
+
|
|
33
|
+
* Enhanced supportability metrics for ESM support.
|
|
34
|
+
* Added new metrics to track usage of ESM loader(`Supportability/Features/ESM/Loader` and `Supportability/Features/ESM/UnsupportedLoader`).
|
|
35
|
+
* Updated instrumentation map to include an optional "friendly name" for tracking metrics.
|
|
36
|
+
|
|
37
|
+
* Enabled re-throwing ESM import errors of `newrelic.js` so that the user is informed to rename it to `newrelic.cjs`
|
|
38
|
+
|
|
39
|
+
* Fixed an issue with mongodb instrumentation where IPv6 address([::1]) was not getting mapped to localhost when setting the host attribute on the segment.
|
|
40
|
+
|
|
41
|
+
* Added a test ESM loader to properly mock out agent in versioned tests.
|
|
42
|
+
|
|
43
|
+
* Added ESM versioned tests for: `express`, `pg`, `mongodb`, and `@grpc/grpc-js`.
|
|
44
|
+
|
|
45
|
+
### v9.0.3 (2022-09-06)
|
|
46
|
+
|
|
47
|
+
* Updated gRPC client instrumentation to respect `grpc.record_errors` when deciding to log errors on gRPC client requests.
|
|
48
|
+
|
|
49
|
+
* Fixed transaction name finalization to properly copy the appropriate transaction name to root segment.
|
|
50
|
+
|
|
25
51
|
### v9.0.2 (2022-08-23)
|
|
26
52
|
|
|
27
53
|
* Added unit test suite for `lib/logger.js`.
|
package/README.md
CHANGED
|
@@ -84,6 +84,47 @@ $ node --experimental-loader newrelic/esm-loader.mjs your-program.js
|
|
|
84
84
|
|
|
85
85
|
**Note**: Unlike the CommonJS methods listed above, there are no alternatives to running the agent without the `--experimental-loader` flag.
|
|
86
86
|
|
|
87
|
+
### Custom Instrumentation
|
|
88
|
+
|
|
89
|
+
The agent supports adding your own custom instrumentation to ES module applications. In order to load custom instrumentation in an ES module app, you'll need to update your `newrelic.cjs` file to include the following:
|
|
90
|
+
|
|
91
|
+
```js
|
|
92
|
+
/* File: newrelic.cjs */
|
|
93
|
+
'use strict'
|
|
94
|
+
/**
|
|
95
|
+
* New Relic agent configuration.
|
|
96
|
+
*
|
|
97
|
+
* See lib/config/default.js in the agent distribution for a more complete
|
|
98
|
+
* description of configuration variables and their potential values.
|
|
99
|
+
*/
|
|
100
|
+
exports.config = {
|
|
101
|
+
app_name: ['Your application or service name'],
|
|
102
|
+
license_key: 'your new relic license key',
|
|
103
|
+
api: {
|
|
104
|
+
esm: {
|
|
105
|
+
custom_instrumentation_entrypoint: '/path/to/my/instrumentation.js'
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/* ... rest of configuration .. */
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
If you do not use a configuration file, then use the environment variable `NEW_RELIC_API_ESM_CUSTOM_INSTRUMENTATION_ENTRYPOINT` instead.
|
|
113
|
+
|
|
114
|
+
By updating the configuration, the agent's ES module loader will ensure that your custom instrumentation is added at module load. This is required in ES module applications due to the immutability of module export bindings: we are unable to apply our instrumentation after loading is complete.
|
|
115
|
+
|
|
116
|
+
We support the following custom instrumentation API methods in ES module apps:
|
|
117
|
+
|
|
118
|
+
* `newrelic.instrument`
|
|
119
|
+
* `newrelic.instrumentConglomerate`
|
|
120
|
+
* `newrelic.instrumentDatastore`
|
|
121
|
+
* `newrelic.instrumentMessages`
|
|
122
|
+
* `newrelic.instrumentWebframework`
|
|
123
|
+
|
|
124
|
+
Note that we _do not_ support `newrelic.instrumentLoadedModule`, for the same issue of immutability mentioned above.
|
|
125
|
+
|
|
126
|
+
If you want to see an example of how to write custom instrumentation in an ES module app, check out our [examples](https://github.com/newrelic/newrelic-node-examples/tree/main/esm-app) repo for a working demo.
|
|
127
|
+
|
|
87
128
|
## Getting Started
|
|
88
129
|
|
|
89
130
|
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).
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -37,6 +37,7 @@ code, the source code can be found at [https://github.com/newrelic/node-newrelic
|
|
|
37
37
|
* [@slack/bolt](#slackbolt)
|
|
38
38
|
* [ajv](#ajv)
|
|
39
39
|
* [async](#async)
|
|
40
|
+
* [c8](#c8)
|
|
40
41
|
* [chai](#chai)
|
|
41
42
|
* [commander](#commander)
|
|
42
43
|
* [eslint-config-prettier](#eslint-config-prettier)
|
|
@@ -1334,7 +1335,7 @@ SOFTWARE.
|
|
|
1334
1335
|
|
|
1335
1336
|
### @newrelic/eslint-config
|
|
1336
1337
|
|
|
1337
|
-
This product includes source derived from [@newrelic/eslint-config](https://github.com/newrelic/eslint-config-newrelic) ([v0.
|
|
1338
|
+
This product includes source derived from [@newrelic/eslint-config](https://github.com/newrelic/eslint-config-newrelic) ([v0.2.0](https://github.com/newrelic/eslint-config-newrelic/tree/v0.2.0)), distributed under the [Apache-2.0 License](https://github.com/newrelic/eslint-config-newrelic/blob/v0.2.0/LICENSE):
|
|
1338
1339
|
|
|
1339
1340
|
```
|
|
1340
1341
|
Apache License
|
|
@@ -2091,6 +2092,28 @@ THE SOFTWARE.
|
|
|
2091
2092
|
|
|
2092
2093
|
```
|
|
2093
2094
|
|
|
2095
|
+
### c8
|
|
2096
|
+
|
|
2097
|
+
This product includes source derived from [c8](https://github.com/bcoe/c8) ([v7.12.0](https://github.com/bcoe/c8/tree/v7.12.0)), distributed under the [ISC License](https://github.com/bcoe/c8/blob/v7.12.0/LICENSE.txt):
|
|
2098
|
+
|
|
2099
|
+
```
|
|
2100
|
+
Copyright (c) 2017, Contributors
|
|
2101
|
+
|
|
2102
|
+
Permission to use, copy, modify, and/or distribute this software
|
|
2103
|
+
for any purpose with or without fee is hereby granted, provided
|
|
2104
|
+
that the above copyright notice and this permission notice
|
|
2105
|
+
appear in all copies.
|
|
2106
|
+
|
|
2107
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
2108
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
|
|
2109
|
+
OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE
|
|
2110
|
+
LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
|
|
2111
|
+
OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
|
|
2112
|
+
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
|
2113
|
+
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
2114
|
+
|
|
2115
|
+
```
|
|
2116
|
+
|
|
2094
2117
|
### chai
|
|
2095
2118
|
|
|
2096
2119
|
This product includes source derived from [chai](https://github.com/chaijs/chai) ([v4.3.6](https://github.com/chaijs/chai/tree/v4.3.6)), distributed under the [MIT License](https://github.com/chaijs/chai/blob/v4.3.6/LICENSE):
|
|
@@ -2224,7 +2247,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
|
|
2224
2247
|
|
|
2225
2248
|
### eslint-plugin-jsdoc
|
|
2226
2249
|
|
|
2227
|
-
This product includes source derived from [eslint-plugin-jsdoc](https://github.com/gajus/eslint-plugin-jsdoc) ([
|
|
2250
|
+
This product includes source derived from [eslint-plugin-jsdoc](https://github.com/gajus/eslint-plugin-jsdoc) ([v39.3.6](https://github.com/gajus/eslint-plugin-jsdoc/tree/v39.3.6)), distributed under the [BSD-3-Clause License](https://github.com/gajus/eslint-plugin-jsdoc/blob/v39.3.6/LICENSE):
|
|
2228
2251
|
|
|
2229
2252
|
```
|
|
2230
2253
|
Copyright (c) 2018, Gajus Kuizinas (http://gajus.com/)
|
|
@@ -2318,10 +2341,10 @@ OTHER DEALINGS IN THE SOFTWARE.
|
|
|
2318
2341
|
|
|
2319
2342
|
### eslint
|
|
2320
2343
|
|
|
2321
|
-
This product includes source derived from [eslint](https://github.com/eslint/eslint) ([
|
|
2344
|
+
This product includes source derived from [eslint](https://github.com/eslint/eslint) ([v8.24.0](https://github.com/eslint/eslint/tree/v8.24.0)), distributed under the [MIT License](https://github.com/eslint/eslint/blob/v8.24.0/LICENSE):
|
|
2322
2345
|
|
|
2323
2346
|
```
|
|
2324
|
-
Copyright
|
|
2347
|
+
Copyright OpenJS Foundation and other contributors, <www.openjsf.org>
|
|
2325
2348
|
|
|
2326
2349
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
2327
2350
|
of this software and associated documentation files (the "Software"), to deal
|
package/api.js
CHANGED
|
@@ -1298,7 +1298,9 @@ API.prototype.instrumentMessages = function instrumentMessages(moduleName, onReq
|
|
|
1298
1298
|
}
|
|
1299
1299
|
|
|
1300
1300
|
/**
|
|
1301
|
-
* Applies an instrumentation to an already loaded module.
|
|
1301
|
+
* Applies an instrumentation to an already loaded CommonJs module.
|
|
1302
|
+
*
|
|
1303
|
+
* Note: This function will not work for ESM packages.
|
|
1302
1304
|
*
|
|
1303
1305
|
* // oh no, express was loaded before newrelic
|
|
1304
1306
|
* const express = require('express')
|
package/esm-loader.mjs
CHANGED
|
@@ -8,14 +8,30 @@ import shimmer from './lib/shimmer.js'
|
|
|
8
8
|
import loggingModule from './lib/logger.js'
|
|
9
9
|
import NAMES from './lib/metrics/names.js'
|
|
10
10
|
import semver from 'semver'
|
|
11
|
+
import path from 'node:path'
|
|
11
12
|
|
|
12
13
|
const isSupportedVersion = () => semver.gte(process.version, 'v16.12.0')
|
|
14
|
+
// This check will prevent resolve hooks executing from within this file
|
|
15
|
+
// If I do `import('foo')` in here it'll hit the resolve hook multiple times
|
|
16
|
+
const isFromEsmLoader = (context) =>
|
|
17
|
+
context && context.parentURL && context.parentURL.includes('newrelic/esm-loader.mjs')
|
|
18
|
+
|
|
13
19
|
const logger = loggingModule.child({ component: 'esm-loader' })
|
|
20
|
+
const esmShimPath = new URL('./lib/esm-shim.mjs', import.meta.url)
|
|
21
|
+
const customEntryPoint = newrelic?.agent?.config?.api.esm.custom_instrumentation_entrypoint
|
|
14
22
|
|
|
15
|
-
|
|
16
|
-
|
|
23
|
+
// Hook point within agent for customers to register their custom instrumentation.
|
|
24
|
+
if (customEntryPoint) {
|
|
25
|
+
const resolvedEntryPoint = path.resolve(customEntryPoint)
|
|
26
|
+
logger.debug('Registering custom ESM instrumentation at %s', resolvedEntryPoint)
|
|
27
|
+
await import(resolvedEntryPoint)
|
|
17
28
|
}
|
|
18
29
|
|
|
30
|
+
addESMSupportabilityMetrics(newrelic.agent)
|
|
31
|
+
|
|
32
|
+
// exporting for testing purposes
|
|
33
|
+
export const registeredSpecifiers = new Map()
|
|
34
|
+
|
|
19
35
|
/**
|
|
20
36
|
* Hook chain responsible for resolving a file URL for a given module specifier
|
|
21
37
|
*
|
|
@@ -24,17 +40,14 @@ if (newrelic.agent) {
|
|
|
24
40
|
*
|
|
25
41
|
* Docs: https://nodejs.org/api/esm.html#resolvespecifier-context-nextresolve
|
|
26
42
|
*
|
|
27
|
-
* @param {string} specifier
|
|
28
|
-
*
|
|
29
|
-
* @param {object} context
|
|
30
|
-
* Metadata about the specifier, including url of the parent module and any import assertions
|
|
43
|
+
* @param {string} specifier string identifier in an import statement or import() expression
|
|
44
|
+
* @param {object} context metadata about the specifier, including url of the parent module and any import assertions
|
|
31
45
|
* Optional argument that only needs to be passed when changed
|
|
32
|
-
* @param {Function} nextResolve
|
|
33
|
-
* The Node.js default resolve hook
|
|
46
|
+
* @param {Function} nextResolve The subsequent resolve hook in the chain, or the Node.js default resolve hook after the last user-supplied resolve hook
|
|
34
47
|
* @returns {Promise} Promise object representing the resolution of a given specifier
|
|
35
48
|
*/
|
|
36
49
|
export async function resolve(specifier, context, nextResolve) {
|
|
37
|
-
if (!newrelic.agent || !isSupportedVersion()) {
|
|
50
|
+
if (!newrelic.agent || !isSupportedVersion() || isFromEsmLoader(context)) {
|
|
38
51
|
return nextResolve(specifier, context, nextResolve)
|
|
39
52
|
}
|
|
40
53
|
|
|
@@ -49,14 +62,15 @@ export async function resolve(specifier, context, nextResolve) {
|
|
|
49
62
|
const instrumentationDefinition = shimmer.registeredInstrumentations[instrumentationName]
|
|
50
63
|
|
|
51
64
|
if (instrumentationDefinition) {
|
|
52
|
-
|
|
65
|
+
const { url, format } = resolvedModule
|
|
66
|
+
logger.debug(`Instrumentation exists for ${specifier} ${format} package.`)
|
|
53
67
|
|
|
54
|
-
if (
|
|
68
|
+
if (format === 'commonjs') {
|
|
55
69
|
// ES Modules translate import statements into fully qualified filepaths, so we create a copy of our instrumentation under this filepath
|
|
56
70
|
const instrumentationDefinitionCopy = Object.assign({}, instrumentationDefinition)
|
|
57
71
|
|
|
58
72
|
// Stripping the prefix is necessary because the code downstream gets this url without it
|
|
59
|
-
instrumentationDefinitionCopy.moduleName =
|
|
73
|
+
instrumentationDefinitionCopy.moduleName = url.replace('file://', '')
|
|
60
74
|
|
|
61
75
|
// Added to keep our Supportability metrics from exploding/including customer info via full filepath
|
|
62
76
|
instrumentationDefinitionCopy.specifier = specifier
|
|
@@ -66,14 +80,72 @@ export async function resolve(specifier, context, nextResolve) {
|
|
|
66
80
|
logger.debug(
|
|
67
81
|
`Registered CommonJS instrumentation for ${specifier} under ${instrumentationDefinitionCopy.moduleName}`
|
|
68
82
|
)
|
|
83
|
+
} else if (format === 'module') {
|
|
84
|
+
registeredSpecifiers.set(url, specifier)
|
|
85
|
+
const modifiedUrl = new URL(url)
|
|
86
|
+
// add a query param to the resolved url so the load hook below knows
|
|
87
|
+
// to rewrite and wrap the source code
|
|
88
|
+
modifiedUrl.searchParams.set('hasNrInstrumentation', 'true')
|
|
89
|
+
resolvedModule.url = modifiedUrl.href
|
|
69
90
|
} else {
|
|
70
|
-
logger.debug(`${specifier} is not a CommonJS
|
|
91
|
+
logger.debug(`${specifier} is not a CommonJS nor ESM package, skipping for now.`)
|
|
71
92
|
}
|
|
72
93
|
}
|
|
73
94
|
|
|
74
95
|
return resolvedModule
|
|
75
96
|
}
|
|
76
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Hook chain responsible for determining how a URL should be interpreted, retrieved, and parsed.
|
|
100
|
+
*
|
|
101
|
+
* Our loader has to be the last user-supplied loader if chaining is happening,
|
|
102
|
+
* as we rely on `nextLoad` being the default Node.js resolve hook to load the ESM.
|
|
103
|
+
*
|
|
104
|
+
* Docs: https://nodejs.org/dist/latest-v18.x/docs/api/esm.html#loadurl-context-nextload
|
|
105
|
+
*
|
|
106
|
+
* @param {string} url the URL returned by the resolve chain
|
|
107
|
+
* @param {object} context metadata about the url, including conditions, format and import assertions
|
|
108
|
+
* @param {Function} nextLoad the subsequent load hook in the chain, or the Node.js default load hook after the last user-supplied load hook
|
|
109
|
+
* @returns {Promise} Promise object representing the load of a given url
|
|
110
|
+
*/
|
|
111
|
+
export async function load(url, context, nextLoad) {
|
|
112
|
+
if (!newrelic.agent || !isSupportedVersion()) {
|
|
113
|
+
return nextLoad(url, context, nextLoad)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
let parsedUrl
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
parsedUrl = new URL(url)
|
|
120
|
+
} catch (err) {
|
|
121
|
+
logger.error('Unable to parse url: %s, msg: %s', url, err.message)
|
|
122
|
+
return nextLoad(url, context, nextLoad)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const hasNrInstrumentation = parsedUrl.searchParams.get('hasNrInstrumentation')
|
|
126
|
+
|
|
127
|
+
if (!hasNrInstrumentation) {
|
|
128
|
+
return nextLoad(url, context, nextLoad)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* undo the work we did in the resolve hook above
|
|
133
|
+
* so we can properly rewrite source and not get in an infinite loop
|
|
134
|
+
*/
|
|
135
|
+
parsedUrl.searchParams.delete('hasNrInstrumentation')
|
|
136
|
+
|
|
137
|
+
const originalUrl = parsedUrl.href
|
|
138
|
+
const specifier = registeredSpecifiers.get(originalUrl)
|
|
139
|
+
const rewrittenSource = await wrapEsmSource(originalUrl, specifier)
|
|
140
|
+
logger.debug(`Registered module instrumentation for ${specifier}.`)
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
format: 'module',
|
|
144
|
+
source: rewrittenSource,
|
|
145
|
+
shortCircuit: true
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
77
149
|
/**
|
|
78
150
|
* Helper function for determining which of our Supportability metrics to use for the current loader invocation
|
|
79
151
|
*
|
|
@@ -81,7 +153,11 @@ export async function resolve(specifier, context, nextResolve) {
|
|
|
81
153
|
* instantiation of the New Relic agent
|
|
82
154
|
* @returns {void}
|
|
83
155
|
*/
|
|
84
|
-
|
|
156
|
+
function addESMSupportabilityMetrics(agent) {
|
|
157
|
+
if (!agent) {
|
|
158
|
+
return
|
|
159
|
+
}
|
|
160
|
+
|
|
85
161
|
if (isSupportedVersion()) {
|
|
86
162
|
agent.metrics.getOrCreateMetric(NAMES.FEATURES.ESM.LOADER).incrementCallCount()
|
|
87
163
|
} else {
|
|
@@ -91,4 +167,38 @@ export function addESMSupportabilityMetrics(agent) {
|
|
|
91
167
|
)
|
|
92
168
|
agent.metrics.getOrCreateMetric(NAMES.FEATURES.ESM.UNSUPPORTED_LOADER).incrementCallCount()
|
|
93
169
|
}
|
|
170
|
+
|
|
171
|
+
if (customEntryPoint) {
|
|
172
|
+
agent.metrics.getOrCreateMetric(NAMES.FEATURES.ESM.CUSTOM_INSTRUMENTATION).incrementCallCount()
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Rewrites the source code of a ES module we want to instrument.
|
|
178
|
+
* This is done by injecting the ESM shim which proxies every property on the exported
|
|
179
|
+
* module and registers the module with shimmer so instrumentation can be registered properly.
|
|
180
|
+
*
|
|
181
|
+
* @param {string} url the URL returned by the resolve chain
|
|
182
|
+
* @param {string} specifier string identifier in an import statement or import() expression
|
|
183
|
+
* @returns {string} source code rewritten to wrap with our esm-shim
|
|
184
|
+
*/
|
|
185
|
+
async function wrapEsmSource(url, specifier) {
|
|
186
|
+
const pkg = await import(url)
|
|
187
|
+
const props = Object.keys(pkg)
|
|
188
|
+
const trimmedUrl = url.replace('file://', '')
|
|
189
|
+
|
|
190
|
+
return `
|
|
191
|
+
import wrapModule from '${esmShimPath.href}'
|
|
192
|
+
import * as _originalModule from '${url}'
|
|
193
|
+
const _wrappedModule = wrapModule(_originalModule, '${specifier}', '${trimmedUrl}')
|
|
194
|
+
${props
|
|
195
|
+
.map((propName) => {
|
|
196
|
+
const propertyExportSource = `
|
|
197
|
+
let _${propName} = _wrappedModule.${propName}
|
|
198
|
+
export { _${propName} as ${propName} }`
|
|
199
|
+
|
|
200
|
+
return propertyExportSource
|
|
201
|
+
})
|
|
202
|
+
.join('\n')}
|
|
203
|
+
`
|
|
94
204
|
}
|
|
@@ -235,6 +235,9 @@ RemoteMethod.prototype._safeRequest = function _safeRequest(options) {
|
|
|
235
235
|
let level = 'trace'
|
|
236
236
|
|
|
237
237
|
if (!isValidLength(options.body, maxPayloadSize)) {
|
|
238
|
+
this._agent.metrics
|
|
239
|
+
.getOrCreateMetric(`${DATA_USAGE.PREFIX}/MaxPayloadSizeLimit/${this.name}`)
|
|
240
|
+
.incrementCallCount()
|
|
238
241
|
logger.warn(
|
|
239
242
|
'The payload size %d being sent to method %s exceeded the maximum size of %d',
|
|
240
243
|
Buffer.byteLength(options.body, 'utf8'),
|
package/lib/config/default.js
CHANGED
|
@@ -26,7 +26,7 @@ exports.config = () => ({
|
|
|
26
26
|
license_key: '',
|
|
27
27
|
/**
|
|
28
28
|
*
|
|
29
|
-
* Enables/Disables security
|
|
29
|
+
* Enables/Disables security policies. Paste your security policies
|
|
30
30
|
* token from the New Relic Admin below.
|
|
31
31
|
*
|
|
32
32
|
* @env NEW_RELIC_SECURITY_POLICIES_TOKEN
|
|
@@ -623,7 +623,15 @@ exports.config = () => ({
|
|
|
623
623
|
*
|
|
624
624
|
* @env NEW_RELIC_API_NOTICE_ERROR
|
|
625
625
|
*/
|
|
626
|
-
notice_error_enabled: true
|
|
626
|
+
notice_error_enabled: true,
|
|
627
|
+
/**
|
|
628
|
+
* Sets the path to custom ESM instrumentation
|
|
629
|
+
*
|
|
630
|
+
* @env NEW_RELIC_API_ESM_CUSTOM_INSTRUMENTATION_ENTRYPOINT
|
|
631
|
+
*/
|
|
632
|
+
esm: {
|
|
633
|
+
custom_instrumentation_entrypoint: null
|
|
634
|
+
}
|
|
627
635
|
},
|
|
628
636
|
/**
|
|
629
637
|
* Transaction Events
|
package/lib/config/env.js
CHANGED
|
@@ -114,7 +114,10 @@ const ENV_MAPPING = {
|
|
|
114
114
|
api: {
|
|
115
115
|
custom_attributes_enabled: 'NEW_RELIC_API_CUSTOM_ATTRIBUTES',
|
|
116
116
|
custom_events_enabled: 'NEW_RELIC_API_CUSTOM_EVENTS',
|
|
117
|
-
notice_error_enabled: 'NEW_RELIC_API_NOTICE_ERROR'
|
|
117
|
+
notice_error_enabled: 'NEW_RELIC_API_NOTICE_ERROR',
|
|
118
|
+
esm: {
|
|
119
|
+
custom_instrumentation_entrypoint: 'NEW_RELIC_API_ESM_CUSTOM_INSTRUMENTATION_ENTRYPOINT'
|
|
120
|
+
}
|
|
118
121
|
},
|
|
119
122
|
datastore_tracer: {
|
|
120
123
|
instance_reporting: {
|
package/lib/esm-shim.mjs
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2022 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import shimmer from './shimmer.js'
|
|
7
|
+
import newrelic from '../index.js'
|
|
8
|
+
const { agent } = newrelic
|
|
9
|
+
|
|
10
|
+
export default function wrapModule(module, moduleName, resolvedPath) {
|
|
11
|
+
const proxiedProps = Object.assign(Object.create(null), module)
|
|
12
|
+
const proxiedModule = new Proxy(module, {
|
|
13
|
+
get: (target, key) => {
|
|
14
|
+
return proxiedProps[key] || target[key]
|
|
15
|
+
},
|
|
16
|
+
set: (target, key, val) => {
|
|
17
|
+
return (proxiedProps[key] = val)
|
|
18
|
+
},
|
|
19
|
+
/**
|
|
20
|
+
* This is normally not allowed.
|
|
21
|
+
* We have some things that define property, unsure if at the module level.
|
|
22
|
+
* Ideally, we can get away from doing this sort of thing at all in the future
|
|
23
|
+
* in a post AsyncLocalStorage world, etc.
|
|
24
|
+
* If we never do at the module level, perhaps we can not define this or defer upstream
|
|
25
|
+
*
|
|
26
|
+
* @param target
|
|
27
|
+
* @param key
|
|
28
|
+
* @param descriptor
|
|
29
|
+
*/
|
|
30
|
+
defineProperty: (target, key, descriptor) => {
|
|
31
|
+
return Object.defineProperty(proxiedProps, key, descriptor)
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
return shimmer.instrumentPostLoad(agent, proxiedModule, moduleName, resolvedPath, true)
|
|
36
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2022 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
|
|
8
|
+
const {
|
|
9
|
+
isApplicationLoggingEnabled,
|
|
10
|
+
isLogForwardingEnabled,
|
|
11
|
+
isLocalDecoratingEnabled,
|
|
12
|
+
isMetricsEnabled,
|
|
13
|
+
createModuleUsageMetric,
|
|
14
|
+
incrementLoggingLinesMetrics
|
|
15
|
+
} = require('../util/application-logging')
|
|
16
|
+
|
|
17
|
+
const MAX_LENGTH = 1021
|
|
18
|
+
const OUTPUT_LENGTH = 1024
|
|
19
|
+
const truncate = (str) => {
|
|
20
|
+
if (str && str.length > OUTPUT_LENGTH) {
|
|
21
|
+
return str.substring(0, MAX_LENGTH) + '...'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return str
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const logger = require('../logger').child({ component: 'bunyan' })
|
|
28
|
+
|
|
29
|
+
function augmentLogData(originalLog, agent, nameFromLevel) {
|
|
30
|
+
// shallow copy, since we're modifying things
|
|
31
|
+
const newLog = Object.assign({}, originalLog)
|
|
32
|
+
newLog.timestamp = Date.now()
|
|
33
|
+
newLog.level = nameFromLevel[originalLog.level]
|
|
34
|
+
|
|
35
|
+
// put log message into a consistent spot and ensure it's not too long
|
|
36
|
+
newLog.message = truncate(newLog.msg)
|
|
37
|
+
|
|
38
|
+
// tidy up the error output to help with max length restrictions
|
|
39
|
+
if (newLog.err) {
|
|
40
|
+
newLog['error.message'] = truncate(newLog.err.message)
|
|
41
|
+
newLog['error.stack'] = truncate(newLog.err.stack)
|
|
42
|
+
newLog['error.class'] =
|
|
43
|
+
newLog.err.name === 'Error' ? newLog.err.constructor.name : newLog.err.name
|
|
44
|
+
// clear out the old error message
|
|
45
|
+
delete newLog.err
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Add the metadata to the object being logged
|
|
49
|
+
const metadata = agent.getLinkingMetadata(true)
|
|
50
|
+
Object.keys(metadata).forEach((m) => {
|
|
51
|
+
newLog[m] = metadata[m]
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
return newLog
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function createLoggerWrapper(shim, fn, fnName, bunyanLogger, nameFromLevel) {
|
|
58
|
+
const agent = shim.agent
|
|
59
|
+
|
|
60
|
+
createModuleUsageMetric('bunyan', agent.metrics)
|
|
61
|
+
|
|
62
|
+
// forward logs via the agent logs aggregator
|
|
63
|
+
bunyanLogger.addStream({
|
|
64
|
+
name: 'NRLogForwarder',
|
|
65
|
+
type: 'raw',
|
|
66
|
+
level: bunyanLogger.level(),
|
|
67
|
+
stream: {
|
|
68
|
+
write: function nrLogWrite(logLine) {
|
|
69
|
+
agent.logs.add(augmentLogData(logLine, agent, nameFromLevel))
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
// no return here means the original return value is preserved
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = function instrument(agent, bunyan, _, shim) {
|
|
77
|
+
const config = agent.config
|
|
78
|
+
|
|
79
|
+
if (!isApplicationLoggingEnabled(config)) {
|
|
80
|
+
logger.debug('Application logging not enabled. Not instrumenting bunyan.')
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const logForwardingEnabled = isLogForwardingEnabled(config, agent)
|
|
85
|
+
const localDecoratingEnabled = isLocalDecoratingEnabled(config)
|
|
86
|
+
const metricsEnabled = isMetricsEnabled(config)
|
|
87
|
+
|
|
88
|
+
if (logForwardingEnabled) {
|
|
89
|
+
shim.wrapReturn(bunyan, 'createLogger', createLoggerWrapper, [bunyan.nameFromLevel])
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (metricsEnabled || localDecoratingEnabled) {
|
|
93
|
+
shim.wrap(bunyan.prototype, '_emit', function wrapEmit(_shim, emit) {
|
|
94
|
+
return function wrappedEmit() {
|
|
95
|
+
const args = shim.argsToArray.apply(shim, arguments)
|
|
96
|
+
const rec = args[0]
|
|
97
|
+
|
|
98
|
+
if (metricsEnabled) {
|
|
99
|
+
incrementLoggingLinesMetrics(bunyan.nameFromLevel[rec.level], agent.metrics)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (localDecoratingEnabled) {
|
|
103
|
+
rec.message = truncate(rec.msg) + agent.getNRLinkingMetadata()
|
|
104
|
+
}
|
|
105
|
+
args[0] = rec
|
|
106
|
+
return emit.apply(this, args)
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
}
|
package/lib/instrumentations.js
CHANGED
|
@@ -15,6 +15,7 @@ module.exports = function instrumentations() {
|
|
|
15
15
|
'cassandra-driver': { type: MODULE_TYPE.DATASTORE },
|
|
16
16
|
'connect': { type: MODULE_TYPE.WEB_FRAMEWORK },
|
|
17
17
|
'bluebird': { type: MODULE_TYPE.PROMISE },
|
|
18
|
+
'bunyan': { type: MODULE_TYPE.GENERIC },
|
|
18
19
|
'director': { type: MODULE_TYPE.WEB_FRAMEWORK },
|
|
19
20
|
'express': { type: MODULE_TYPE.WEB_FRAMEWORK },
|
|
20
21
|
'fastify': { type: MODULE_TYPE.WEB_FRAMEWORK },
|
package/lib/metrics/names.js
CHANGED
|
@@ -259,7 +259,8 @@ const INFINITE_TRACING = {
|
|
|
259
259
|
const FEATURES = {
|
|
260
260
|
ESM: {
|
|
261
261
|
LOADER: `${SUPPORTABILITY.FEATURES}/ESM/Loader`,
|
|
262
|
-
UNSUPPORTED_LOADER: `${SUPPORTABILITY.FEATURES}/ESM/UnsupportedLoader
|
|
262
|
+
UNSUPPORTED_LOADER: `${SUPPORTABILITY.FEATURES}/ESM/UnsupportedLoader`,
|
|
263
|
+
CUSTOM_INSTRUMENTATION: `${SUPPORTABILITY.FEATURES}/ESM/CustomInstrumentation`
|
|
263
264
|
},
|
|
264
265
|
CERTIFICATES: SUPPORTABILITY.FEATURES + '/Certificates',
|
|
265
266
|
INSTRUMENTATION: {
|
|
@@ -276,11 +277,13 @@ const LOGGING = {
|
|
|
276
277
|
INFO: `${LOGGING_LINES_PREFIX}/INFO`,
|
|
277
278
|
WARN: `${LOGGING_LINES_PREFIX}/WARN`,
|
|
278
279
|
ERROR: `${LOGGING_LINES_PREFIX}/ERROR`,
|
|
280
|
+
FATAL: `${LOGGING_LINES_PREFIX}/FATAL`,
|
|
279
281
|
DEBUG: `${LOGGING_LINES_PREFIX}/DEBUG`,
|
|
280
282
|
TRACE: `${LOGGING_LINES_PREFIX}/TRACE`,
|
|
281
283
|
UNKNOWN: `${LOGGING_LINES_PREFIX}/UNKNOWN`
|
|
282
284
|
},
|
|
283
285
|
LIBS: {
|
|
286
|
+
BUNYAN: `${SUPPORTABILITY.LOGGING}/${NODEJS.PREFIX}bunyan/enabled`,
|
|
284
287
|
PINO: `${SUPPORTABILITY.LOGGING}/${NODEJS.PREFIX}pino/enabled`,
|
|
285
288
|
WINSTON: `${SUPPORTABILITY.LOGGING}/${NODEJS.PREFIX}winston/enabled`
|
|
286
289
|
},
|
package/lib/shimmer.js
CHANGED
|
@@ -467,12 +467,12 @@ const shimmer = (module.exports = {
|
|
|
467
467
|
return instrumentation
|
|
468
468
|
},
|
|
469
469
|
|
|
470
|
-
instrumentPostLoad(agent, module, moduleName, resolvedName) {
|
|
470
|
+
instrumentPostLoad(agent, module, moduleName, resolvedName, returnModule = false) {
|
|
471
471
|
const result = _postLoad(agent, module, moduleName, resolvedName)
|
|
472
472
|
// This is to not break the public API
|
|
473
473
|
// previously it would just call instrumentation
|
|
474
474
|
// and not check the result
|
|
475
|
-
return !!result.__NR_instrumented
|
|
475
|
+
return returnModule ? result : !!result.__NR_instrumented
|
|
476
476
|
}
|
|
477
477
|
})
|
|
478
478
|
|
package/newrelic.js
CHANGED
|
@@ -28,14 +28,6 @@ exports.config = {
|
|
|
28
28
|
* attributes include/exclude lists.
|
|
29
29
|
*/
|
|
30
30
|
allow_all_headers: true,
|
|
31
|
-
application_logging: {
|
|
32
|
-
forwarding: {
|
|
33
|
-
/**
|
|
34
|
-
* Toggles whether the agent gathers log records for sending to New Relic.
|
|
35
|
-
*/
|
|
36
|
-
enabled: true
|
|
37
|
-
}
|
|
38
|
-
},
|
|
39
31
|
attributes: {
|
|
40
32
|
/**
|
|
41
33
|
* Prefix of attributes to exclude from all destinations. Allows * as wildcard
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "newrelic",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.3.0",
|
|
4
4
|
"author": "New Relic Node.js agent team <nodejs@newrelic.com>",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"contributors": [
|
|
@@ -146,7 +146,7 @@
|
|
|
146
146
|
"bench": "node ./bin/run-bench.js",
|
|
147
147
|
"docker-env": "./bin/docker-env-vars.sh",
|
|
148
148
|
"docs": "npm ci && jsdoc -c ./jsdoc-conf.json --private -r .",
|
|
149
|
-
"integration": "npm run prepare-test && npm run sub-install && time tap test/integration/**/**/*.tap.js --timeout=180 --no-coverage --reporter classic",
|
|
149
|
+
"integration": "npm run prepare-test && npm run sub-install && time c8 -o ./coverage/integration tap test/integration/**/**/*.tap.js --timeout=180 --no-coverage --reporter classic",
|
|
150
150
|
"prepare-test": "npm run ssl && npm run docker-env",
|
|
151
151
|
"lint": "eslint ./*.{js,mjs} lib test bin examples",
|
|
152
152
|
"lint:fix": "eslint --fix, ./*.{js,mjs} lib test bin examples",
|
|
@@ -158,8 +158,8 @@
|
|
|
158
158
|
"sub-install": "node test/bin/install_sub_deps",
|
|
159
159
|
"test": "npm run integration && npm run unit && npm run unit:esm",
|
|
160
160
|
"third-party-updates": "oss third-party manifest --includeOptDeps && oss third-party notices --includeOptDeps && git add THIRD_PARTY_NOTICES.md third_party_manifest.json",
|
|
161
|
-
"unit": "rm -f newrelic_agent.log && time tap --test-regex='(\\/|^test\\/unit\\/.*\\.test\\.js)$' --timeout=180 --no-coverage --reporter classic",
|
|
162
|
-
"unit:esm": "rm -f newrelic_agent.log && time tap --test-regex='(\\/|^test\\/unit\\/.*\\.test\\.mjs)$' --timeout=180 --no-coverage --reporter classic --node-arg=--experimental-loader=testdouble",
|
|
161
|
+
"unit": "rm -f newrelic_agent.log && time c8 -o ./coverage/unit tap --test-regex='(\\/|^test\\/unit\\/.*\\.test\\.js)$' --timeout=180 --no-coverage --reporter classic",
|
|
162
|
+
"unit:esm": "rm -f newrelic_agent.log && time c8 -o ./coverage/esm-unit --include *.mjs --include **/*.mjs --exclude false tap --test-regex='(\\/|^test\\/unit\\/.*\\.test\\.mjs)$' --timeout=180 --no-coverage --reporter classic --node-arg=--experimental-loader=testdouble",
|
|
163
163
|
"update-cross-agent-tests": "./bin/update-cats.sh",
|
|
164
164
|
"versioned-tests": "./bin/run-versioned-tests.sh",
|
|
165
165
|
"update-changelog-version": "node ./bin/update-changelog-version",
|
|
@@ -191,7 +191,7 @@
|
|
|
191
191
|
"@newrelic/native-metrics": "^9.0.0"
|
|
192
192
|
},
|
|
193
193
|
"devDependencies": {
|
|
194
|
-
"@newrelic/eslint-config": "^0.
|
|
194
|
+
"@newrelic/eslint-config": "^0.2.0",
|
|
195
195
|
"@newrelic/newrelic-oss-cli": "^0.1.2",
|
|
196
196
|
"@newrelic/proxy": "^2.0.0",
|
|
197
197
|
"@newrelic/test-utilities": "^7.1.1",
|
|
@@ -199,13 +199,14 @@
|
|
|
199
199
|
"@slack/bolt": "^3.7.0",
|
|
200
200
|
"ajv": "^6.12.6",
|
|
201
201
|
"async": "^3.2.4",
|
|
202
|
+
"c8": "^7.12.0",
|
|
202
203
|
"chai": "^4.1.2",
|
|
203
204
|
"commander": "^7.0.0",
|
|
204
|
-
"eslint": "^
|
|
205
|
+
"eslint": "^8.24.0",
|
|
205
206
|
"eslint-config-prettier": "^8.3.0",
|
|
206
207
|
"eslint-plugin-disable": "^2.0.1",
|
|
207
208
|
"eslint-plugin-header": "^3.1.1",
|
|
208
|
-
"eslint-plugin-jsdoc": "^
|
|
209
|
+
"eslint-plugin-jsdoc": "^39.3.6",
|
|
209
210
|
"eslint-plugin-node": "^11.1.0",
|
|
210
211
|
"eslint-plugin-prettier": "^3.4.0",
|
|
211
212
|
"express": "*",
|