newrelic 7.2.0 → 7.4.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 +62 -0
- package/README.md +17 -2
- package/THIRD_PARTY_NOTICES.md +64 -4
- package/lib/collector/api.js +24 -0
- package/lib/collector/facts.js +2 -0
- package/lib/feature_flags.js +2 -1
- package/lib/grpc/connection.js +62 -27
- package/lib/instrumentation/core/async_hooks.js +106 -14
- package/lib/instrumentation/core/http.js +5 -0
- package/lib/spans/create-span-event-aggregator.js +1 -0
- package/lib/transaction/index.js +29 -0
- package/package.json +6 -4
package/NEWS.md
CHANGED
|
@@ -1,3 +1,65 @@
|
|
|
1
|
+
### v7.4.0 (2021-05-11)
|
|
2
|
+
|
|
3
|
+
* Updated third party notices and manifest for husky and lint-staged.
|
|
4
|
+
|
|
5
|
+
* Updated redis versioned tests to use unique DB indexes per file to avoid collisions and flushing of in-progress tests.
|
|
6
|
+
|
|
7
|
+
* Pinned hapi 17 versioned tests to only minor/patch versions within 17.x.
|
|
8
|
+
|
|
9
|
+
* Bumped timeout for redis versioned tests.
|
|
10
|
+
|
|
11
|
+
* Wired up husky + lint staged to execute linting on all changed files in pre-commit hook.
|
|
12
|
+
|
|
13
|
+
* Handled a proxy misconfiguration of collector and log an actionable warning message.
|
|
14
|
+
|
|
15
|
+
* Added `flaky_code` and `success_delay_ms` handling of flaky grpc connections to infinite tracing.
|
|
16
|
+
|
|
17
|
+
* Added resources to README to highlight external modules that customers should be aware of and possibly use for their applications.
|
|
18
|
+
|
|
19
|
+
* Logged all New Relic metadata env vars at startup.
|
|
20
|
+
|
|
21
|
+
* Fixed images for improved reader experience.
|
|
22
|
+
|
|
23
|
+
Thank you to @henryjw for the contribution.
|
|
24
|
+
|
|
25
|
+
### v7.3.1 (2021-04-14)
|
|
26
|
+
|
|
27
|
+
* 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).
|
|
28
|
+
|
|
29
|
+
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.**
|
|
30
|
+
|
|
31
|
+
**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 }`.
|
|
32
|
+
|
|
33
|
+
### v7.3.0 (2021-04-06)
|
|
34
|
+
|
|
35
|
+
* 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.
|
|
36
|
+
|
|
37
|
+
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.
|
|
38
|
+
|
|
39
|
+
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.**
|
|
40
|
+
|
|
41
|
+
* Fixed memory leak introduced when Infinite Tracing is enabled.
|
|
42
|
+
|
|
43
|
+
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.
|
|
44
|
+
|
|
45
|
+
### v7.2.1 (2021-03-29)
|
|
46
|
+
|
|
47
|
+
* Dev-only sub-dependency bump of 'y18n' to clear npm audit warnings.
|
|
48
|
+
|
|
49
|
+
* Bumped @grpc/grpc-js to ^1.2.11.
|
|
50
|
+
|
|
51
|
+
* Bumped @grpc/proto-loader to ^0.5.6.
|
|
52
|
+
|
|
53
|
+
* Agent no longer propagates segments for promises via async-hooks when the transaction associated with the parentSegment has ended.
|
|
54
|
+
|
|
55
|
+
This change reduces the amount of context tracking work needed for certain rare edge-case scenarios involving promises.
|
|
56
|
+
|
|
57
|
+
* Fixed issue where capturing axios request errors could result in a memory leak.
|
|
58
|
+
|
|
59
|
+
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.
|
|
60
|
+
|
|
61
|
+
* Added active transaction check to `wrappedResEnd` to prevent unecessary work for ended transactions in the case of multiple `Response.prototype.end()` invocations.
|
|
62
|
+
|
|
1
63
|
### v7.2.0 (2021-03-23)
|
|
2
64
|
|
|
3
65
|
* Added feature flag to allow disabling of certificate bundle usage.
|
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ To use New Relic's Node.js agent entails these three steps, which are described
|
|
|
52
52
|
/* ... the rest of your program ... */
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
If you're compiling your JavaScript and can't control the final `require` order, the Node
|
|
55
|
+
If you're compiling your JavaScript and can't control the final `require` order, the Node.js agent will work with node's `-r/--require` flag.
|
|
56
56
|
|
|
57
57
|
$ node -r newrelic your-program.js
|
|
58
58
|
$ node --require newrelic your-program.js
|
|
@@ -61,11 +61,26 @@ If you're compiling your JavaScript and can't control the final `require` order,
|
|
|
61
61
|
|
|
62
62
|
For more information on getting started, [check the Node.js docs](https://docs.newrelic.com/docs/agents/nodejs-agent/getting-started/introduction-new-relic-nodejs).
|
|
63
63
|
|
|
64
|
+
### External Modules
|
|
65
|
+
|
|
66
|
+
There are several modules that can be installed and configured to accompany the Node.js agent:
|
|
67
|
+
|
|
68
|
+
* [@newrelic/apollo-server-plugin](https://github.com/newrelic/newrelic-node-apollo-server-plugin): New Relic's official Apollo Server plugin for use with the Node.js agent.
|
|
69
|
+
* [@newrelic/winston-enricher](https://github.com/newrelic/newrelic-winston-logenricher-node): Provides distributed trace and span information output as JSON-formatted log messages. This is most commonly used with the New Relic Logs product.
|
|
70
|
+
* [@newrelic/mysql](https://github.com/newrelic/node-newrelic-mysql)(**Experimental**): Standalone instrumentation for [mysql2](https://github.com/sidorares/node-mysql2#readme) and [mysql2/promise](https://github.com/sidorares/node-mysql2/blob/master/documentation/Promise-Wrapper.md) as well as the same instrumentation for [mysql](https://github.com/mysqljs/mysql#readme) within the Node.js agent.
|
|
71
|
+
|
|
72
|
+
There are several modules included within the Node.js agent to add more instrumentation for 3rd party modules:
|
|
73
|
+
|
|
74
|
+
* [@newrelic/aws-sdk](https://github.com/newrelic/node-newrelic-aws-sdk): Provides instrumentation for the [AWS SDK](https://www.npmjs.com/package/aws-sdk) npm package.
|
|
75
|
+
* [@newrelic/koa](https://github.com/newrelic/node-newrelic-koa): Provides instrumentation for [koa](https://koajs.com/), [koa-router](https://github.com/ZijianHe/koa-router), [@koa/router](https://github.com/koajs/router), and [koa-route](https://github.com/koajs/route) npm packages.
|
|
76
|
+
* [@newrelic/superagent](https://github.com/newrelic/node-newrelic-superagent): Provides instrumentation for [superagent](https://github.com/visionmedia/superagent) npm package.
|
|
77
|
+
* [@newrelic/native-metrics](https://github.com/newrelic/node-native-metrics): Provides hooks into the native v8 layer of Node.js to provide metrics to the Node.js agent.
|
|
78
|
+
|
|
64
79
|
## Usage
|
|
65
80
|
|
|
66
81
|
### Using the API
|
|
67
82
|
|
|
68
|
-
The `newrelic` module returns an object with the Node agent's API methods attached.
|
|
83
|
+
The `newrelic` module returns an object with the Node.js agent's API methods attached.
|
|
69
84
|
|
|
70
85
|
```js
|
|
71
86
|
const newrelic = require('newrelic')
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -49,7 +49,9 @@ code, the source code can be found at [https://github.com/newrelic/node-newrelic
|
|
|
49
49
|
* [glob](#glob)
|
|
50
50
|
* [got](#got)
|
|
51
51
|
* [http-errors](#http-errors)
|
|
52
|
+
* [husky](#husky)
|
|
52
53
|
* [jsdoc](#jsdoc)
|
|
54
|
+
* [lint-staged](#lint-staged)
|
|
53
55
|
* [memcached](#memcached)
|
|
54
56
|
* [minami](#minami)
|
|
55
57
|
* [mongodb](#mongodb)
|
|
@@ -75,7 +77,7 @@ code, the source code can be found at [https://github.com/newrelic/node-newrelic
|
|
|
75
77
|
|
|
76
78
|
### @grpc/grpc-js
|
|
77
79
|
|
|
78
|
-
This product includes source derived from [@grpc/grpc-js](https://github.com/grpc/grpc-node/tree/master/packages/grpc-js) ([v1.2.
|
|
80
|
+
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):
|
|
79
81
|
|
|
80
82
|
```
|
|
81
83
|
Apache License
|
|
@@ -284,7 +286,7 @@ This product includes source derived from [@grpc/grpc-js](https://github.com/grp
|
|
|
284
286
|
|
|
285
287
|
### @grpc/proto-loader
|
|
286
288
|
|
|
287
|
-
This product includes source derived from [@grpc/proto-loader](https://github.com/grpc/grpc-node) ([v0.5.
|
|
289
|
+
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):
|
|
288
290
|
|
|
289
291
|
```
|
|
290
292
|
Apache License
|
|
@@ -1927,7 +1929,7 @@ SOFTWARE.
|
|
|
1927
1929
|
|
|
1928
1930
|
### commander
|
|
1929
1931
|
|
|
1930
|
-
This product includes source derived from [commander](https://github.com/tj/commander.js) ([v7.
|
|
1932
|
+
This product includes source derived from [commander](https://github.com/tj/commander.js) ([v7.2.0](https://github.com/tj/commander.js/tree/v7.2.0)), distributed under the [MIT License](https://github.com/tj/commander.js/blob/v7.2.0/LICENSE):
|
|
1931
1933
|
|
|
1932
1934
|
```
|
|
1933
1935
|
(The MIT License)
|
|
@@ -2016,7 +2018,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
2016
2018
|
|
|
2017
2019
|
### fastify
|
|
2018
2020
|
|
|
2019
|
-
This product includes source derived from [fastify](https://github.com/fastify/fastify) ([v2.
|
|
2021
|
+
This product includes source derived from [fastify](https://github.com/fastify/fastify) ([v2.15.3](https://github.com/fastify/fastify/tree/v2.15.3)), distributed under the [MIT License](https://github.com/fastify/fastify/blob/v2.15.3/LICENSE):
|
|
2020
2022
|
|
|
2021
2023
|
```
|
|
2022
2024
|
MIT License
|
|
@@ -2139,6 +2141,35 @@ THE SOFTWARE.
|
|
|
2139
2141
|
|
|
2140
2142
|
```
|
|
2141
2143
|
|
|
2144
|
+
### husky
|
|
2145
|
+
|
|
2146
|
+
This product includes source derived from [husky](https://github.com/typicode/husky) ([v6.0.0](https://github.com/typicode/husky/tree/v6.0.0)), distributed under the [MIT License](https://github.com/typicode/husky/blob/v6.0.0/LICENSE):
|
|
2147
|
+
|
|
2148
|
+
```
|
|
2149
|
+
MIT License
|
|
2150
|
+
|
|
2151
|
+
Copyright (c) 2021 typicode
|
|
2152
|
+
|
|
2153
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
2154
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
2155
|
+
in the Software without restriction, including without limitation the rights
|
|
2156
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
2157
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
2158
|
+
furnished to do so, subject to the following conditions:
|
|
2159
|
+
|
|
2160
|
+
The above copyright notice and this permission notice shall be included in all
|
|
2161
|
+
copies or substantial portions of the Software.
|
|
2162
|
+
|
|
2163
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
2164
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
2165
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
2166
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
2167
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
2168
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
2169
|
+
SOFTWARE.
|
|
2170
|
+
|
|
2171
|
+
```
|
|
2172
|
+
|
|
2142
2173
|
### jsdoc
|
|
2143
2174
|
|
|
2144
2175
|
This product includes source derived from [jsdoc](https://github.com/jsdoc/jsdoc) ([v3.6.3](https://github.com/jsdoc/jsdoc/tree/v3.6.3)), distributed under the [Apache-2.0 License](https://github.com/jsdoc/jsdoc/blob/v3.6.3/LICENSE.md):
|
|
@@ -2249,6 +2280,35 @@ https://github.com/jmblog/color-themes-for-google-code-prettify
|
|
|
2249
2280
|
|
|
2250
2281
|
```
|
|
2251
2282
|
|
|
2283
|
+
### lint-staged
|
|
2284
|
+
|
|
2285
|
+
This product includes source derived from [lint-staged](https://github.com/okonet/lint-staged) ([v11.0.0](https://github.com/okonet/lint-staged/tree/v11.0.0)), distributed under the [MIT License](https://github.com/okonet/lint-staged/blob/v11.0.0/LICENSE):
|
|
2286
|
+
|
|
2287
|
+
```
|
|
2288
|
+
The MIT License (MIT)
|
|
2289
|
+
|
|
2290
|
+
Copyright (c) 2016 Andrey Okonetchnikov
|
|
2291
|
+
|
|
2292
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
2293
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
2294
|
+
in the Software without restriction, including without limitation the rights
|
|
2295
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
2296
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
2297
|
+
furnished to do so, subject to the following conditions:
|
|
2298
|
+
|
|
2299
|
+
The above copyright notice and this permission notice shall be included in all
|
|
2300
|
+
copies or substantial portions of the Software.
|
|
2301
|
+
|
|
2302
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
2303
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
2304
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
2305
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
2306
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
2307
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
2308
|
+
SOFTWARE.
|
|
2309
|
+
|
|
2310
|
+
```
|
|
2311
|
+
|
|
2252
2312
|
### memcached
|
|
2253
2313
|
|
|
2254
2314
|
This product includes source derived from [memcached](https://github.com/3rd-Eden/node-memcached) ([v2.2.2](https://github.com/3rd-Eden/node-memcached/tree/v2.2.2)), distributed under the [MIT License](https://github.com/3rd-Eden/node-memcached/blob/v2.2.2/LICENSE):
|
package/lib/collector/api.js
CHANGED
|
@@ -117,6 +117,22 @@ CollectorAPI.prototype.connect = function connect(callback) {
|
|
|
117
117
|
this._reqHeadersMap = null
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
/**
|
|
121
|
+
* Checks if proxy is configured to connect via `proxy_host` and `proxy_port`
|
|
122
|
+
* and if error code is EPROTO or ECONNRESET. This is an indication their proxy
|
|
123
|
+
* server only accepts HTTP connections, and we should provide an actionable warning to
|
|
124
|
+
* fix the misconfiguration by setting `proxy` to a fully qualified URL
|
|
125
|
+
*
|
|
126
|
+
* @param {Error} error
|
|
127
|
+
* @returns {Boolean}
|
|
128
|
+
*/
|
|
129
|
+
function isProxyMisconfigured(error) {
|
|
130
|
+
const config = api._agent.config
|
|
131
|
+
return error &&
|
|
132
|
+
['EPROTO', 'ECONNRESET'].includes(error.code) &&
|
|
133
|
+
config.proxy_host && config.proxy_port && !config.proxy
|
|
134
|
+
}
|
|
135
|
+
|
|
120
136
|
function retry(error, response) {
|
|
121
137
|
metric.incrementCallCount()
|
|
122
138
|
|
|
@@ -142,6 +158,14 @@ CollectorAPI.prototype.connect = function connect(callback) {
|
|
|
142
158
|
' (status code %s)',
|
|
143
159
|
response.status
|
|
144
160
|
)
|
|
161
|
+
} else if (isProxyMisconfigured(error)) {
|
|
162
|
+
logger.warn(
|
|
163
|
+
error,
|
|
164
|
+
'Your proxy server appears to be configured to accept connections over http. ' +
|
|
165
|
+
'When setting `proxy_host` and `proxy_port` New Relic attempts to connect over ' +
|
|
166
|
+
'SSL(https). If your proxy is configured to accept connections over http, try ' +
|
|
167
|
+
'setting `proxy` to a fully qualified URL(e.g http://proxy-host:8080).'
|
|
168
|
+
)
|
|
145
169
|
}
|
|
146
170
|
|
|
147
171
|
let backoff = BACKOFFS[Math.min(attempts, max) - 1]
|
package/lib/collector/facts.js
CHANGED
package/lib/feature_flags.js
CHANGED
|
@@ -15,7 +15,8 @@ exports.prerelease = {
|
|
|
15
15
|
fastify_instrumentation: false,
|
|
16
16
|
// Starts by defaulting true as introducing for deprecation/removal purposes.
|
|
17
17
|
// Will eventually set false prior full removal of feature.
|
|
18
|
-
certificate_bundle: true
|
|
18
|
+
certificate_bundle: true,
|
|
19
|
+
new_promise_tracking: false
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
// flags that are no longer used for released features
|
package/lib/grpc/connection.js
CHANGED
|
@@ -11,17 +11,23 @@ const logger = require('../logger').child({component: 'grpc_connection'})
|
|
|
11
11
|
const EventEmitter = require('events')
|
|
12
12
|
const NAMES = require('../metrics/names')
|
|
13
13
|
const util = require('util')
|
|
14
|
+
const GRPC_TEST_META = {
|
|
15
|
+
flaky: 'NEWRELIC_GRPCCONNECTION_METADATA_FLAKY',
|
|
16
|
+
delay: 'NEWRELIC_GRPCCONNECTION_METADATA_DELAY',
|
|
17
|
+
flaky_code: 'NEWRELIC_GRPCCONNECTION_METADATA_FLAKY_CODE',
|
|
18
|
+
success_delay_ms: 'NEWRELIC_GRPCCONNECTION_METADATA_SUCCESS_DELAY_MS'
|
|
19
|
+
}
|
|
14
20
|
|
|
15
21
|
const connectionStates = require('./connection/states')
|
|
16
22
|
|
|
17
|
-
const
|
|
23
|
+
const PROTO_OPTIONS = {keepCase: true,
|
|
18
24
|
longs: String,
|
|
19
25
|
enums: String,
|
|
20
26
|
defaults: true,
|
|
21
27
|
oneofs: true
|
|
22
28
|
}
|
|
23
29
|
|
|
24
|
-
const
|
|
30
|
+
const PROTO_DEFINITION_PATH = __dirname +
|
|
25
31
|
'../../../lib/grpc/endpoints/infinite-tracing/v1.proto'
|
|
26
32
|
|
|
27
33
|
const DEFAULT_RECONNECT_DELAY_MS = 15 * 1000
|
|
@@ -54,11 +60,13 @@ class GrpcConnection extends EventEmitter {
|
|
|
54
60
|
this._metrics = metrics
|
|
55
61
|
this._setState(connectionStates.disconnected)
|
|
56
62
|
|
|
57
|
-
this._endpoint = this.getTraceObserverEndpoint(traceObserverConfig)
|
|
58
63
|
this._licensekey = null
|
|
59
64
|
this._runId = null
|
|
60
65
|
this._requestHeadersMap = null
|
|
61
66
|
|
|
67
|
+
this._endpoint = this.getTraceObserverEndpoint(traceObserverConfig)
|
|
68
|
+
|
|
69
|
+
this._client = null
|
|
62
70
|
this.stream = null
|
|
63
71
|
}
|
|
64
72
|
|
|
@@ -161,22 +169,27 @@ class GrpcConnection extends EventEmitter {
|
|
|
161
169
|
}
|
|
162
170
|
}
|
|
163
171
|
|
|
164
|
-
|
|
165
|
-
// pass to server via meta-data.
|
|
166
|
-
const flaky = parseInt(env.NEWRELIC_GRPCCONNECTION_METADATA_FLAKY, 10)
|
|
167
|
-
const delay = parseInt(env.NEWRELIC_GRPCCONNECTION_METADATA_DELAY, 10)
|
|
168
|
-
if (flaky) {
|
|
169
|
-
logger.trace('Adding flaky metadata: %s', flaky)
|
|
170
|
-
metadata.add('flaky', flaky)
|
|
171
|
-
}
|
|
172
|
-
if (delay) {
|
|
173
|
-
logger.trace('Adding delay metadata: %s', delay)
|
|
174
|
-
metadata.add('delay',delay)
|
|
175
|
-
}
|
|
172
|
+
this._setTestMetadata(metadata, env)
|
|
176
173
|
|
|
177
174
|
return metadata
|
|
178
175
|
}
|
|
179
176
|
|
|
177
|
+
/**
|
|
178
|
+
* Adds test metadata used to simulate connectivity issues
|
|
179
|
+
* when appropriate env vars are set
|
|
180
|
+
*
|
|
181
|
+
* @param {Metadata} metadata
|
|
182
|
+
*/
|
|
183
|
+
_setTestMetadata(metadata, env) {
|
|
184
|
+
for (const [key, envVar] of Object.entries(GRPC_TEST_META)) {
|
|
185
|
+
const value = parseInt(env[envVar], 10)
|
|
186
|
+
if (value) {
|
|
187
|
+
logger.trace('Adding %s metadata: %s', key, value)
|
|
188
|
+
metadata.add(key, value)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
180
193
|
/**
|
|
181
194
|
* Disconnects from gRPC endpoint and schedules establishing a new connection.
|
|
182
195
|
* @param {number} reconnectDelayMs number of milliseconds to wait before reconnecting.
|
|
@@ -320,26 +333,48 @@ class GrpcConnection extends EventEmitter {
|
|
|
320
333
|
* a stream for further writing.
|
|
321
334
|
*/
|
|
322
335
|
_connectSpans() {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
const credentials = this._generateCredentials(grpc)
|
|
330
|
-
const client = new serviceDefinition.IngestService(
|
|
331
|
-
this._endpoint,
|
|
332
|
-
credentials
|
|
333
|
-
)
|
|
336
|
+
if (!this._client) {
|
|
337
|
+
// Only create once to avoid potential memory leak.
|
|
338
|
+
// We create here (currently) for consistent error handling.
|
|
339
|
+
this._client = this._createClient(this._endpoint)
|
|
340
|
+
}
|
|
334
341
|
|
|
335
342
|
const metadata =
|
|
336
343
|
this._getMetadata(this._licenseKey, this._runId, this._requestHeadersMap, process.env)
|
|
337
344
|
|
|
338
|
-
const stream =
|
|
345
|
+
const stream = this._client.recordSpan(metadata)
|
|
339
346
|
this._setupSpanStreamObservers(stream)
|
|
340
347
|
|
|
341
348
|
return stream
|
|
342
349
|
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Creates gRPC service client to use for establishing gRPC streams.
|
|
353
|
+
*
|
|
354
|
+
* WARNING: creating a client more than once can result in a memory leak.
|
|
355
|
+
* ChannelImplementation and related objects will stay in memory even after
|
|
356
|
+
* the stream is closed and we do not have a handle to the client. Currently
|
|
357
|
+
* impacting grpc-js@1.2.11 and several earlier versions.
|
|
358
|
+
*/
|
|
359
|
+
_createClient(endpoint) {
|
|
360
|
+
logger.trace('Creating gRPC client for: ', endpoint)
|
|
361
|
+
|
|
362
|
+
const packageDefinition = protoLoader.loadSync(PROTO_DEFINITION_PATH, PROTO_OPTIONS)
|
|
363
|
+
|
|
364
|
+
const protoDescriptor = grpc.loadPackageDefinition(
|
|
365
|
+
packageDefinition
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
const traceApi = protoDescriptor.com.newrelic.trace.v1
|
|
369
|
+
|
|
370
|
+
const credentials = this._generateCredentials(grpc)
|
|
371
|
+
const client = new traceApi.IngestService(
|
|
372
|
+
endpoint,
|
|
373
|
+
credentials
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
return client
|
|
377
|
+
}
|
|
343
378
|
}
|
|
344
379
|
|
|
345
380
|
module.exports = GrpcConnection
|
|
@@ -40,7 +40,7 @@ function initialize(agent, shim) {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
function tryAsyncHooks(agent, shim) {
|
|
43
|
-
|
|
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
|
-
|
|
53
|
+
const segmentMap = new Map()
|
|
54
54
|
module.exports.segmentMap = segmentMap
|
|
55
55
|
|
|
56
|
-
|
|
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
|
-
|
|
59
|
-
|
|
76
|
+
if (type !== 'PROMISE') {
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const parentSegment = segmentMap.get(triggerId)
|
|
60
81
|
|
|
61
|
-
if (
|
|
82
|
+
if (parentSegment && !parentSegment.transaction.isActive()) {
|
|
83
|
+
// Stop propagating if the transaction was ended.
|
|
62
84
|
return
|
|
63
85
|
}
|
|
64
86
|
|
|
65
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
125
|
+
}
|
|
100
126
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
})
|
|
127
|
+
return hooks
|
|
128
|
+
}
|
|
104
129
|
|
|
105
|
-
|
|
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
|
}
|
|
@@ -267,6 +267,11 @@ function wrapResponseEnd(agent, proto) {
|
|
|
267
267
|
return end.apply(this, arguments)
|
|
268
268
|
}
|
|
269
269
|
|
|
270
|
+
if (!txInfo.transaction.isActive()) {
|
|
271
|
+
logger.trace('wrappedResEnd invoked for ended transaction implying multiple invocations.')
|
|
272
|
+
return end.apply(this, arguments)
|
|
273
|
+
}
|
|
274
|
+
|
|
270
275
|
// If an error happened, add it to the aggregator.
|
|
271
276
|
if (txInfo.error) {
|
|
272
277
|
if (!txInfo.errorHandled || urltils.isError(agent.config, this.statusCode)) {
|
|
@@ -72,6 +72,7 @@ function createStandardAggregator(config, collector, metrics) {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
function validateInfiniteTracing(trace_observer) {
|
|
75
|
+
// TODO: Remove semver check when Node 10 support dropped.
|
|
75
76
|
if (!psemver.satisfies('>=10.10.0')) {
|
|
76
77
|
logger.warn(
|
|
77
78
|
'Infinite tracing disabled: this version of Node is not supported (must be >=10.10.0)'
|
package/lib/transaction/index.js
CHANGED
|
@@ -225,9 +225,28 @@ Transaction.prototype.end = function end() {
|
|
|
225
225
|
}
|
|
226
226
|
|
|
227
227
|
this.agent.emit('transactionFinished', this)
|
|
228
|
+
|
|
229
|
+
// Do after emit so all post-processing can complete
|
|
230
|
+
this._cleanUneededReferences()
|
|
231
|
+
|
|
228
232
|
return this
|
|
229
233
|
}
|
|
230
234
|
|
|
235
|
+
/**
|
|
236
|
+
* Cleans up references that will not be used later for processing such as
|
|
237
|
+
* transaction traces.
|
|
238
|
+
*
|
|
239
|
+
* Errors won't be needed for later processing but can contain extra details we
|
|
240
|
+
* don't want to hold in memory. Particularly, axios errors can result in indirect
|
|
241
|
+
* references to promises which will prevent them from being destroyed and result
|
|
242
|
+
* in a memory leak. This is due to the TraceSegment not getting removed from the
|
|
243
|
+
* async-hooks segmentMap because 'destroy' never fires.
|
|
244
|
+
*/
|
|
245
|
+
Transaction.prototype._cleanUneededReferences = function _cleanUneededReferences() {
|
|
246
|
+
this.userErrors = null
|
|
247
|
+
this.exceptions = null
|
|
248
|
+
}
|
|
249
|
+
|
|
231
250
|
/**
|
|
232
251
|
* For web transactions, this represents the time from when the request was received
|
|
233
252
|
* to when response was sent. For background transactions, it is equal to duration
|
|
@@ -768,6 +787,11 @@ function _linkExceptionToSegment(exception) {
|
|
|
768
787
|
Transaction.prototype.addException = _addException
|
|
769
788
|
|
|
770
789
|
function _addException(exception) {
|
|
790
|
+
if (!this.isActive()) {
|
|
791
|
+
logger.trace('Transaction is not active. Not capturing error: ', exception)
|
|
792
|
+
return
|
|
793
|
+
}
|
|
794
|
+
|
|
771
795
|
this._linkExceptionToSegment(exception)
|
|
772
796
|
this.exceptions.push(exception)
|
|
773
797
|
}
|
|
@@ -782,6 +806,11 @@ function _addException(exception) {
|
|
|
782
806
|
Transaction.prototype.addUserError = _addUserError
|
|
783
807
|
|
|
784
808
|
function _addUserError(exception) {
|
|
809
|
+
if (!this.isActive()) {
|
|
810
|
+
logger.trace('Transaction is not active. Not capturing user error: ', exception)
|
|
811
|
+
return
|
|
812
|
+
}
|
|
813
|
+
|
|
785
814
|
this._linkExceptionToSegment(exception)
|
|
786
815
|
this.userErrors.push(exception)
|
|
787
816
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "newrelic",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.4.0",
|
|
4
4
|
"author": "New Relic Node.js agent team <nodejs@newrelic.com>",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"contributors": [
|
|
@@ -143,8 +143,8 @@
|
|
|
143
143
|
"newrelic-naming-rules": "./bin/test-naming-rules.js"
|
|
144
144
|
},
|
|
145
145
|
"dependencies": {
|
|
146
|
-
"@grpc/grpc-js": "^1.2.
|
|
147
|
-
"@grpc/proto-loader": "^0.5.
|
|
146
|
+
"@grpc/grpc-js": "^1.2.11",
|
|
147
|
+
"@grpc/proto-loader": "^0.5.6",
|
|
148
148
|
"@newrelic/aws-sdk": "^3.1.0",
|
|
149
149
|
"@newrelic/koa": "^5.0.0",
|
|
150
150
|
"@newrelic/superagent": "^4.0.0",
|
|
@@ -171,12 +171,14 @@
|
|
|
171
171
|
"commander": "^7.0.0",
|
|
172
172
|
"eslint": "^6.8.0",
|
|
173
173
|
"express": "*",
|
|
174
|
-
"fastify": "^2.
|
|
174
|
+
"fastify": "^2.15.3",
|
|
175
175
|
"generic-pool": "^3.6.1",
|
|
176
176
|
"glob": "^7.1.2",
|
|
177
177
|
"got": "^8.0.1",
|
|
178
178
|
"http-errors": "^1.7.3",
|
|
179
|
+
"husky": "^6.0.0",
|
|
179
180
|
"jsdoc": "^3.6.3",
|
|
181
|
+
"lint-staged": "^11.0.0",
|
|
180
182
|
"memcached": ">=0.2.8",
|
|
181
183
|
"minami": "^1.1.1",
|
|
182
184
|
"mongodb": "^3.3.3",
|