newrelic 10.0.0 → 10.1.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 +17 -0
- package/THIRD_PARTY_NOTICES.md +0 -63
- package/lib/collector/serverless.js +7 -5
- package/lib/config/default.js +8 -0
- package/lib/grpc/connection.js +47 -23
- package/lib/grpc/endpoints/infinite-tracing/v1.proto +10 -0
- package/lib/instrumentation/@nestjs/core.js +38 -0
- package/lib/instrumentations.js +1 -1
- package/lib/metrics/names.js +3 -1
- package/lib/serverless/aws-lambda.js +59 -51
- package/lib/shim/webframework-shim.js +1 -0
- package/lib/spans/base-span-streamer.js +135 -0
- package/lib/spans/batch-span-streamer.js +79 -0
- package/lib/spans/create-span-event-aggregator.js +25 -8
- package/lib/spans/span-streamer.js +13 -105
- package/package.json +1 -2
package/NEWS.md
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
### v10.1.0 (2023-05-04)
|
|
2
|
+
|
|
3
|
+
* Added batching and compression to infinite tracing.
|
|
4
|
+
* These options are on by default.
|
|
5
|
+
* To restore uncompressed spans set `config.infinite_tracing.compression` to `false`.
|
|
6
|
+
* To send single spans vs batches set `config.infinite_tracing.batching` to `false`.
|
|
7
|
+
* The environment variables for these new configurations are: `NEW_RELIC_INFINITE_TRACING_BATCHING` and `NEW_RELIC_INFINITE_TRACING_COMPRESSION`.
|
|
8
|
+
|
|
9
|
+
* Added support to record Nest.js error stack traces.
|
|
10
|
+
* Nest.js is officially supported via underlying instrumentation of express or fastify.
|
|
11
|
+
|
|
12
|
+
* Added job to create a release notes PR in the post-release workflow.
|
|
13
|
+
|
|
14
|
+
* Removed request library and updated helper to use http/https to make requests in tests.
|
|
15
|
+
|
|
16
|
+
* Reduced cognitive complexity in lib/serverless/aws-lambda.js
|
|
17
|
+
|
|
1
18
|
### v10.0.0 (2023-04-19)
|
|
2
19
|
|
|
3
20
|
* **BREAKING** - Updated the default of `config.transaction_tracer.record_sql` from `off` to `obfuscated`. This means that sql statements will be captured but obfuscated.
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -62,7 +62,6 @@ code, the source code can be found at [https://github.com/newrelic/node-newrelic
|
|
|
62
62
|
* [prettier](#prettier)
|
|
63
63
|
* [proxyquire](#proxyquire)
|
|
64
64
|
* [q](#q)
|
|
65
|
-
* [request](#request)
|
|
66
65
|
* [rimraf](#rimraf)
|
|
67
66
|
* [should](#should)
|
|
68
67
|
* [sinon](#sinon)
|
|
@@ -9165,68 +9164,6 @@ IN THE SOFTWARE.
|
|
|
9165
9164
|
|
|
9166
9165
|
```
|
|
9167
9166
|
|
|
9168
|
-
### request
|
|
9169
|
-
|
|
9170
|
-
This product includes source derived from [request](https://github.com/request/request) ([v2.88.2](https://github.com/request/request/tree/v2.88.2)), distributed under the [Apache-2.0 License](https://github.com/request/request/blob/v2.88.2/LICENSE):
|
|
9171
|
-
|
|
9172
|
-
```
|
|
9173
|
-
Apache License
|
|
9174
|
-
|
|
9175
|
-
Version 2.0, January 2004
|
|
9176
|
-
|
|
9177
|
-
http://www.apache.org/licenses/
|
|
9178
|
-
|
|
9179
|
-
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
9180
|
-
|
|
9181
|
-
1. Definitions.
|
|
9182
|
-
|
|
9183
|
-
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
|
|
9184
|
-
|
|
9185
|
-
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
|
|
9186
|
-
|
|
9187
|
-
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
|
|
9188
|
-
|
|
9189
|
-
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
|
|
9190
|
-
|
|
9191
|
-
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
|
|
9192
|
-
|
|
9193
|
-
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
|
|
9194
|
-
|
|
9195
|
-
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
|
|
9196
|
-
|
|
9197
|
-
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
|
|
9198
|
-
|
|
9199
|
-
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
|
|
9200
|
-
|
|
9201
|
-
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
|
|
9202
|
-
|
|
9203
|
-
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
|
|
9204
|
-
|
|
9205
|
-
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
|
|
9206
|
-
|
|
9207
|
-
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
|
|
9208
|
-
|
|
9209
|
-
You must give any other recipients of the Work or Derivative Works a copy of this License; and
|
|
9210
|
-
|
|
9211
|
-
You must cause any modified files to carry prominent notices stating that You changed the files; and
|
|
9212
|
-
|
|
9213
|
-
You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
|
|
9214
|
-
|
|
9215
|
-
If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
|
|
9216
|
-
|
|
9217
|
-
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
|
|
9218
|
-
|
|
9219
|
-
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
|
|
9220
|
-
|
|
9221
|
-
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
|
|
9222
|
-
|
|
9223
|
-
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
|
|
9224
|
-
|
|
9225
|
-
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
|
|
9226
|
-
|
|
9227
|
-
END OF TERMS AND CONDITIONS
|
|
9228
|
-
```
|
|
9229
|
-
|
|
9230
9167
|
### rimraf
|
|
9231
9168
|
|
|
9232
9169
|
This product includes source derived from [rimraf](https://github.com/isaacs/rimraf) ([v2.7.1](https://github.com/isaacs/rimraf/tree/v2.7.1)), distributed under the [ISC License](https://github.com/isaacs/rimraf/blob/v2.7.1/LICENSE):
|
|
@@ -23,10 +23,10 @@ class ServerlessCollector {
|
|
|
23
23
|
* Constructs a new serverless collector instance with the give agent.
|
|
24
24
|
*
|
|
25
25
|
* @class
|
|
26
|
-
* @param pipePath
|
|
27
26
|
* @classdesc
|
|
28
27
|
* A helper class for wrapping modules with segments
|
|
29
28
|
* @param {Agent} agent - The agent this collector will use
|
|
29
|
+
* @param {string} pipePath path of the named pipe to the Lambda extension, if it's enabled
|
|
30
30
|
*/
|
|
31
31
|
constructor(agent, pipePath) {
|
|
32
32
|
this._agent = agent
|
|
@@ -45,7 +45,7 @@ class ServerlessCollector {
|
|
|
45
45
|
/**
|
|
46
46
|
* Sets the ARN to be sent up in the metadata.
|
|
47
47
|
*
|
|
48
|
-
* @param arn
|
|
48
|
+
* @param {string} arn Amazon Resource Name of the function
|
|
49
49
|
*/
|
|
50
50
|
setLambdaArn(arn) {
|
|
51
51
|
this.metadata.arn = arn
|
|
@@ -54,7 +54,7 @@ class ServerlessCollector {
|
|
|
54
54
|
/**
|
|
55
55
|
* Sets the function_version to be sent up in the metadata.
|
|
56
56
|
*
|
|
57
|
-
* @param function_version
|
|
57
|
+
* @param {string} function_version version indicator for Lambda function
|
|
58
58
|
*/
|
|
59
59
|
setLambdaFunctionVersion(function_version) {
|
|
60
60
|
this.metadata.function_version = function_version
|
|
@@ -84,7 +84,7 @@ class ServerlessCollector {
|
|
|
84
84
|
/**
|
|
85
85
|
* There is nothing to actually restart for serverless, so we do nothing.
|
|
86
86
|
*
|
|
87
|
-
* @param cb
|
|
87
|
+
* @param {Function} cb callback function, if any
|
|
88
88
|
*/
|
|
89
89
|
restart(cb) {
|
|
90
90
|
setImmediate(cb, null, CollectorResponse.success(null))
|
|
@@ -216,6 +216,7 @@ class ServerlessCollector {
|
|
|
216
216
|
* Constructs, serializes, and prints the final consolidated payload to stdout.
|
|
217
217
|
*
|
|
218
218
|
* @param {Function} cb The callback to invoke when finished.
|
|
219
|
+
* @returns {boolean} indicating if callback was defined and successfully executed
|
|
219
220
|
*/
|
|
220
221
|
flushPayload(cb) {
|
|
221
222
|
if (!this.enabled) {
|
|
@@ -270,7 +271,8 @@ class ServerlessCollector {
|
|
|
270
271
|
/**
|
|
271
272
|
* Writes payload to pipe
|
|
272
273
|
*
|
|
273
|
-
* @param payload
|
|
274
|
+
* @param {string} payload serialized stringified-JSON payload to flush
|
|
275
|
+
* @returns {boolean} whether or not flush was successful
|
|
274
276
|
*/
|
|
275
277
|
flushToPipeSync(payload) {
|
|
276
278
|
try {
|
package/lib/config/default.js
CHANGED
|
@@ -1122,6 +1122,14 @@ defaultConfig.definition = () => ({
|
|
|
1122
1122
|
formatter: int,
|
|
1123
1123
|
default: 10000
|
|
1124
1124
|
}
|
|
1125
|
+
},
|
|
1126
|
+
batching: {
|
|
1127
|
+
formatter: boolean,
|
|
1128
|
+
default: true
|
|
1129
|
+
},
|
|
1130
|
+
compression: {
|
|
1131
|
+
formatter: boolean,
|
|
1132
|
+
default: true
|
|
1125
1133
|
}
|
|
1126
1134
|
},
|
|
1127
1135
|
/**
|
package/lib/grpc/connection.js
CHANGED
|
@@ -41,15 +41,17 @@ class GrpcConnection extends EventEmitter {
|
|
|
41
41
|
* Standard property setting/initialization, and sets an initial
|
|
42
42
|
* connection state of disconnected
|
|
43
43
|
*
|
|
44
|
-
* @param {object}
|
|
44
|
+
* @param {object} infiniteTracingConfig config item config.infinite_tracing
|
|
45
45
|
* @param {MetricAggregator} metrics metric aggregator, for supportability metrics
|
|
46
46
|
* @param {number} [reconnectDelayMs=15000] number of milliseconds to wait before reconnecting
|
|
47
47
|
* for error states that require a reconnect delay.
|
|
48
48
|
*/
|
|
49
|
-
constructor(
|
|
49
|
+
constructor(infiniteTracingConfig, metrics, reconnectDelayMs = DEFAULT_RECONNECT_DELAY_MS) {
|
|
50
50
|
super()
|
|
51
|
+
this._compression = infiniteTracingConfig.compression
|
|
52
|
+
this._method = infiniteTracingConfig.batching ? 'recordSpanBatch' : 'recordSpan'
|
|
51
53
|
|
|
52
|
-
this._reconnectDelayMs = reconnectDelayMs
|
|
54
|
+
this._reconnectDelayMs = reconnectDelayMs
|
|
53
55
|
|
|
54
56
|
this._metrics = metrics
|
|
55
57
|
this._setState(connectionStates.disconnected)
|
|
@@ -58,6 +60,7 @@ class GrpcConnection extends EventEmitter {
|
|
|
58
60
|
this._runId = null
|
|
59
61
|
this._requestHeadersMap = null
|
|
60
62
|
|
|
63
|
+
const traceObserverConfig = infiniteTracingConfig.trace_observer
|
|
61
64
|
this._endpoint = this.getTraceObserverEndpoint(traceObserverConfig)
|
|
62
65
|
|
|
63
66
|
this._client = null
|
|
@@ -70,18 +73,17 @@ class GrpcConnection extends EventEmitter {
|
|
|
70
73
|
* Allows setting of connection details _after_ object constructions
|
|
71
74
|
* but before the actual connection.
|
|
72
75
|
*
|
|
73
|
-
* @param {string} endpoint the GRPC server's endpoint
|
|
74
76
|
* @param {string} licenseKey the agent license key
|
|
75
77
|
* @param {string} runId the current agent run id (also called agent run token)
|
|
76
78
|
* @param {object} requestHeadersMap request headers map received from server connect.
|
|
77
79
|
* @param {string} [rootCerts] string of root (ca) certificates to attach to the connection.
|
|
80
|
+
* @returns {GrpcConnection} the instance of grpc connection
|
|
78
81
|
*/
|
|
79
82
|
setConnectionDetails(licenseKey, runId, requestHeadersMap, rootCerts) {
|
|
80
83
|
this._licenseKey = licenseKey
|
|
81
84
|
this._runId = runId
|
|
82
85
|
this._requestHeadersMap = requestHeadersMap
|
|
83
86
|
this._rootCerts = rootCerts
|
|
84
|
-
|
|
85
87
|
return this
|
|
86
88
|
}
|
|
87
89
|
|
|
@@ -95,9 +97,8 @@ class GrpcConnection extends EventEmitter {
|
|
|
95
97
|
* Used to indicate a transition from one connection state to
|
|
96
98
|
* the next. Also responsible for emitting the connect state event
|
|
97
99
|
*
|
|
98
|
-
* @param {
|
|
99
|
-
* @param {
|
|
100
|
-
* @param stream
|
|
100
|
+
* @param {number} state The connection state (See connectionStates above)
|
|
101
|
+
* @param {object} stream duplex stream
|
|
101
102
|
*/
|
|
102
103
|
_setState(state, stream = null) {
|
|
103
104
|
this._state = state
|
|
@@ -145,10 +146,11 @@ class GrpcConnection extends EventEmitter {
|
|
|
145
146
|
/**
|
|
146
147
|
* Method returns GRPC metadata for initial connection
|
|
147
148
|
*
|
|
148
|
-
* @param {string} licenseKey
|
|
149
|
-
* @param {string} runId
|
|
150
|
-
* @param requestHeadersMap
|
|
151
|
-
* @param env
|
|
149
|
+
* @param {string} licenseKey agent key
|
|
150
|
+
* @param {string} runId agent run id
|
|
151
|
+
* @param {object} requestHeadersMap map of request headers to include
|
|
152
|
+
* @param {object} env process.env
|
|
153
|
+
* @returns {object} grpc metadata
|
|
152
154
|
*/
|
|
153
155
|
_getMetadata(licenseKey, runId, requestHeadersMap, env) {
|
|
154
156
|
const metadata = new grpc.Metadata()
|
|
@@ -172,8 +174,8 @@ class GrpcConnection extends EventEmitter {
|
|
|
172
174
|
* Adds test metadata used to simulate connectivity issues
|
|
173
175
|
* when appropriate env vars are set
|
|
174
176
|
*
|
|
175
|
-
* @param {
|
|
176
|
-
* @param env
|
|
177
|
+
* @param {object} metadata metadata to set
|
|
178
|
+
* @param {object} env process.env
|
|
177
179
|
*/
|
|
178
180
|
_setTestMetadata(metadata, env) {
|
|
179
181
|
for (const [key, envVar] of Object.entries(GRPC_TEST_META)) {
|
|
@@ -236,7 +238,7 @@ class GrpcConnection extends EventEmitter {
|
|
|
236
238
|
* Events from the GRPC stream (a ClientDuplexStreamImpl) are the main way
|
|
237
239
|
* we communicate with the GRPC server.
|
|
238
240
|
*
|
|
239
|
-
* @param {
|
|
241
|
+
* @param {object} stream duplex stream
|
|
240
242
|
*/
|
|
241
243
|
_setupSpanStreamObservers(stream) {
|
|
242
244
|
// Node streams require all data sent by the server to be read before the end
|
|
@@ -263,7 +265,7 @@ class GrpcConnection extends EventEmitter {
|
|
|
263
265
|
// that the versioned Trace Observer is no longer available. Agents
|
|
264
266
|
// MUST NOT attempt to reconnect in this case
|
|
265
267
|
logger.info(
|
|
266
|
-
'[UNIMPLEMENTED]: Trace
|
|
268
|
+
'[UNIMPLEMENTED]: Trace Observer is no longer available. Shutting down connection.'
|
|
267
269
|
)
|
|
268
270
|
this._disconnect()
|
|
269
271
|
} else if (grpc.status[grpc.status.OK] === grpcStatusName) {
|
|
@@ -297,7 +299,8 @@ class GrpcConnection extends EventEmitter {
|
|
|
297
299
|
/**
|
|
298
300
|
* Creates the GRPC credentials needed
|
|
299
301
|
*
|
|
300
|
-
* @param grpcApi
|
|
302
|
+
* @param {object} grpcApi grpc lib
|
|
303
|
+
* @returns {object} ssl credentials
|
|
301
304
|
*/
|
|
302
305
|
_generateCredentials(grpcApi) {
|
|
303
306
|
let certBuffer = null
|
|
@@ -325,15 +328,17 @@ class GrpcConnection extends EventEmitter {
|
|
|
325
328
|
*
|
|
326
329
|
* Contains the actual logic that connects to the GRPC service.
|
|
327
330
|
* "Connection" can be a somewhat misleading term here. This method
|
|
328
|
-
* invokes the
|
|
331
|
+
* invokes the either `recordSpan` or `recordSpanBatch` remote procedure call. Behind the scenes
|
|
329
332
|
* this makes an http2 request with the metadata, and then returns
|
|
330
333
|
* a stream for further writing.
|
|
334
|
+
*
|
|
335
|
+
* @returns {object} stream duplex stream
|
|
331
336
|
*/
|
|
332
337
|
_connectSpans() {
|
|
333
338
|
if (!this._client) {
|
|
334
339
|
// Only create once to avoid potential memory leak.
|
|
335
340
|
// We create here (currently) for consistent error handling.
|
|
336
|
-
this._client = this._createClient(
|
|
341
|
+
this._client = this._createClient()
|
|
337
342
|
}
|
|
338
343
|
|
|
339
344
|
const metadata = this._getMetadata(
|
|
@@ -343,7 +348,7 @@ class GrpcConnection extends EventEmitter {
|
|
|
343
348
|
process.env
|
|
344
349
|
)
|
|
345
350
|
|
|
346
|
-
const stream = this._client.
|
|
351
|
+
const stream = this._client[this._method](metadata)
|
|
347
352
|
this._setupSpanStreamObservers(stream)
|
|
348
353
|
|
|
349
354
|
return stream
|
|
@@ -357,9 +362,10 @@ class GrpcConnection extends EventEmitter {
|
|
|
357
362
|
* the stream is closed and we do not have a handle to the client. Currently
|
|
358
363
|
* impacting grpc-js@1.2.11 and several earlier versions.
|
|
359
364
|
*
|
|
360
|
-
* @
|
|
365
|
+
* @returns {object} protobuf API for IngestService
|
|
361
366
|
*/
|
|
362
|
-
_createClient(
|
|
367
|
+
_createClient() {
|
|
368
|
+
const endpoint = this._endpoint
|
|
363
369
|
logger.trace('Creating gRPC client for: ', endpoint)
|
|
364
370
|
|
|
365
371
|
const packageDefinition = protoLoader.loadSync(PROTO_DEFINITION_PATH, PROTO_OPTIONS)
|
|
@@ -369,7 +375,25 @@ class GrpcConnection extends EventEmitter {
|
|
|
369
375
|
const traceApi = protoDescriptor.com.newrelic.trace.v1
|
|
370
376
|
|
|
371
377
|
const credentials = this._generateCredentials(grpc)
|
|
372
|
-
|
|
378
|
+
|
|
379
|
+
// If you want to use mock server use insecure creds
|
|
380
|
+
// const credentials = grpc.credentials.createInsecure()
|
|
381
|
+
|
|
382
|
+
const opts = {}
|
|
383
|
+
if (this._compression) {
|
|
384
|
+
// 2 = gzip compression
|
|
385
|
+
// see: https://github.com/grpc/grpc-node/blob/master/packages/grpc-js/src/compression-algorithms.ts#L21
|
|
386
|
+
opts['grpc.default_compression_algorithm'] = 2
|
|
387
|
+
this._metrics
|
|
388
|
+
.getOrCreateMetric(`${NAMES.INFINITE_TRACING.COMPRESSION}/enabled`)
|
|
389
|
+
.incrementCallCount()
|
|
390
|
+
} else {
|
|
391
|
+
this._metrics
|
|
392
|
+
.getOrCreateMetric(`${NAMES.INFINITE_TRACING.COMPRESSION}/disabled`)
|
|
393
|
+
.incrementCallCount()
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return new traceApi.IngestService(endpoint, credentials, opts)
|
|
373
397
|
}
|
|
374
398
|
}
|
|
375
399
|
|
|
@@ -6,6 +6,15 @@ service IngestService {
|
|
|
6
6
|
// Accepts a stream of Span messages, and returns an irregular stream of
|
|
7
7
|
// RecordStatus messages.
|
|
8
8
|
rpc RecordSpan(stream Span) returns (stream RecordStatus) {}
|
|
9
|
+
|
|
10
|
+
// Accepts a stream of SpanBatch messages, and returns an irregular
|
|
11
|
+
// stream of RecordStatus messages. This endpoint can be used to improve
|
|
12
|
+
// throughput when Span messages are small
|
|
13
|
+
rpc RecordSpanBatch(stream SpanBatch) returns (stream RecordStatus) {}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
message SpanBatch {
|
|
17
|
+
repeated Span spans = 1;
|
|
9
18
|
}
|
|
10
19
|
|
|
11
20
|
message Span {
|
|
@@ -27,3 +36,4 @@ message AttributeValue {
|
|
|
27
36
|
message RecordStatus {
|
|
28
37
|
uint64 messages_seen = 1;
|
|
29
38
|
}
|
|
39
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
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: 'nestjs' })
|
|
8
|
+
const semver = require('semver')
|
|
9
|
+
|
|
10
|
+
module.exports = function initialize(agent, core, moduleName, shim) {
|
|
11
|
+
const nestJsVersion = shim.require('./package.json').version
|
|
12
|
+
shim.setFramework(shim.NEST)
|
|
13
|
+
// Earliest version that runs in the tests
|
|
14
|
+
if (semver.lt(nestJsVersion, '8.0.0')) {
|
|
15
|
+
logger.debug(
|
|
16
|
+
`Not instrumenting Nest.js version ${nestJsVersion}; minimum instrumentable version is 8.0.0`
|
|
17
|
+
)
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
shim.wrap(core.BaseExceptionFilter.prototype, 'handleUnknownError', (shim, original) => {
|
|
22
|
+
return function wrappedHandleUnknownError(exception) {
|
|
23
|
+
const segment = shim.getActiveSegment()
|
|
24
|
+
const transaction = segment?.transaction
|
|
25
|
+
if (transaction) {
|
|
26
|
+
shim.agent.errors.add(transaction, exception)
|
|
27
|
+
logger.trace(exception, 'Captured error handled by Nest.js exception filter.')
|
|
28
|
+
} else {
|
|
29
|
+
logger.trace(
|
|
30
|
+
exception,
|
|
31
|
+
'Ignoring error handled by Nest.js exception filter: not in a transaction'
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return original.apply(this, arguments)
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
}
|
package/lib/instrumentations.js
CHANGED
|
@@ -27,6 +27,7 @@ module.exports = function instrumentations() {
|
|
|
27
27
|
'memcached': { type: MODULE_TYPE.DATASTORE },
|
|
28
28
|
'mongodb': { type: MODULE_TYPE.DATASTORE },
|
|
29
29
|
'mysql': { module: './instrumentation/mysql' },
|
|
30
|
+
'@nestjs/core': { type: MODULE_TYPE.WEB_FRAMEWORK },
|
|
30
31
|
'pino': { module: './instrumentation/pino' },
|
|
31
32
|
'pg': { type: MODULE_TYPE.DATASTORE },
|
|
32
33
|
'q': { type: null },
|
|
@@ -52,7 +53,6 @@ module.exports = function instrumentations() {
|
|
|
52
53
|
'npmlog': { type: MODULE_TYPE.TRACKING },
|
|
53
54
|
'fancy-log': { type: MODULE_TYPE.TRACKING },
|
|
54
55
|
'@prisma/client': { type: MODULE_TYPE.DATASTORE },
|
|
55
|
-
'@nestjs/core': { type: MODULE_TYPE.TRACKING },
|
|
56
56
|
'knex': { type: MODULE_TYPE.TRACKING }
|
|
57
57
|
}
|
|
58
58
|
}
|
package/lib/metrics/names.js
CHANGED
|
@@ -264,7 +264,9 @@ const INFINITE_TRACING = {
|
|
|
264
264
|
SPAN_RESPONSE_GRPC_STATUS: SUPPORTABILITY.INFINITE_TRACING + '/Span/gRPC/%s',
|
|
265
265
|
QUEUE_CAPACITY: SUPPORTABILITY.INFINITE_TRACING + '/Span/QueueCapacity',
|
|
266
266
|
QUEUE_SIZE: SUPPORTABILITY.INFINITE_TRACING + '/Span/QueueSize',
|
|
267
|
-
DRAIN_DURATION: SUPPORTABILITY.INFINITE_TRACING + '/Drain/Duration'
|
|
267
|
+
DRAIN_DURATION: SUPPORTABILITY.INFINITE_TRACING + '/Drain/Duration',
|
|
268
|
+
COMPRESSION: `${SUPPORTABILITY.INFINITE_TRACING}/gRPC/Compression`,
|
|
269
|
+
BATCHING: `${SUPPORTABILITY.INFINITE_TRACING}/gRPC/Batching`
|
|
268
270
|
}
|
|
269
271
|
|
|
270
272
|
const FEATURES = {
|
|
@@ -5,8 +5,6 @@
|
|
|
5
5
|
|
|
6
6
|
'use strict'
|
|
7
7
|
|
|
8
|
-
/* eslint sonarjs/cognitive-complexity: ["error", 29] -- TODO: https://issues.newrelic.com/browse/NEWRELIC-5252 */
|
|
9
|
-
|
|
10
8
|
const apiGateway = require('./api-gateway')
|
|
11
9
|
const headerAttributes = require('../header-attributes')
|
|
12
10
|
const get = require('../util/get')
|
|
@@ -67,6 +65,45 @@ class AwsLambda {
|
|
|
67
65
|
return null
|
|
68
66
|
}
|
|
69
67
|
|
|
68
|
+
wrapEnders() {
|
|
69
|
+
const shim = this.shim
|
|
70
|
+
// There is no prependListener in node 4, so we wrap emit to look for 'beforeExit'
|
|
71
|
+
// NOTE: This may be converted to holding onto a single ender function if only
|
|
72
|
+
// one invocation is executing at a time.
|
|
73
|
+
shim.wrap(process, 'emit', function wrapEmit(shim, emit) {
|
|
74
|
+
return function wrappedEmit(ev, error) {
|
|
75
|
+
// need to add error as uncaughtException to be used
|
|
76
|
+
// later to add to transaction errors
|
|
77
|
+
if (ev === 'unhandledRejection') {
|
|
78
|
+
uncaughtException = error
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (['beforeExit', 'unhandledRejection'].includes(ev)) {
|
|
82
|
+
transactionEnders.forEach((ender) => {
|
|
83
|
+
ender()
|
|
84
|
+
})
|
|
85
|
+
transactionEnders = []
|
|
86
|
+
}
|
|
87
|
+
return emit.apply(process, arguments)
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
wrapFatal() {
|
|
93
|
+
const shim = this.shim
|
|
94
|
+
shim.wrap(process, '_fatalException', function wrapper(shim, original) {
|
|
95
|
+
return function wrappedFatalException(error) {
|
|
96
|
+
// logic placed before the _fatalException call, since it ends the invocation
|
|
97
|
+
uncaughtException = error
|
|
98
|
+
transactionEnders.forEach((ender) => {
|
|
99
|
+
ender()
|
|
100
|
+
})
|
|
101
|
+
transactionEnders = []
|
|
102
|
+
return original.apply(this, arguments)
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
70
107
|
patchLambdaHandler(handler) {
|
|
71
108
|
const awsLambda = this
|
|
72
109
|
const shim = this.shim
|
|
@@ -80,38 +117,8 @@ class AwsLambda {
|
|
|
80
117
|
// Only wrap emit on process the first time patch is called.
|
|
81
118
|
patchCalled = true
|
|
82
119
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
// one invocation is executing at a time.
|
|
86
|
-
shim.wrap(process, 'emit', function wrapEmit(shim, emit) {
|
|
87
|
-
return function wrappedEmit(ev, error) {
|
|
88
|
-
// need to add error as uncaughtException to be used
|
|
89
|
-
// later to add to transaction errors
|
|
90
|
-
if (ev === 'unhandledRejection') {
|
|
91
|
-
uncaughtException = error
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (['beforeExit', 'unhandledRejection'].includes(ev)) {
|
|
95
|
-
transactionEnders.forEach((ender) => {
|
|
96
|
-
ender()
|
|
97
|
-
})
|
|
98
|
-
transactionEnders = []
|
|
99
|
-
}
|
|
100
|
-
return emit.apply(process, arguments)
|
|
101
|
-
}
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
shim.wrap(process, '_fatalException', function wrapper(shim, original) {
|
|
105
|
-
return function wrappedFatalException(error) {
|
|
106
|
-
// logic placed before the _fatalException call, since it ends the invocation
|
|
107
|
-
uncaughtException = error
|
|
108
|
-
transactionEnders.forEach((ender) => {
|
|
109
|
-
ender()
|
|
110
|
-
})
|
|
111
|
-
transactionEnders = []
|
|
112
|
-
return original.apply(this, arguments)
|
|
113
|
-
}
|
|
114
|
-
})
|
|
120
|
+
this.wrapEnders()
|
|
121
|
+
this.wrapFatal()
|
|
115
122
|
}
|
|
116
123
|
|
|
117
124
|
return shim.bindCreateTransaction(wrappedHandler, { type: shim.BG })
|
|
@@ -155,7 +162,7 @@ class AwsLambda {
|
|
|
155
162
|
|
|
156
163
|
transactionEnders.push(txnEnder)
|
|
157
164
|
|
|
158
|
-
args[cbIndex] = wrapCallbackAndCaptureError(
|
|
165
|
+
args[cbIndex] = awsLambda.wrapCallbackAndCaptureError(
|
|
159
166
|
transaction,
|
|
160
167
|
txnEnder,
|
|
161
168
|
args[cbIndex],
|
|
@@ -164,8 +171,8 @@ class AwsLambda {
|
|
|
164
171
|
|
|
165
172
|
// context.{done,fail,succeed} are all considered deprecated by
|
|
166
173
|
// AWS, but are considered functional.
|
|
167
|
-
context.done = wrapCallbackAndCaptureError(transaction, txnEnder, context.done)
|
|
168
|
-
context.fail = wrapCallbackAndCaptureError(transaction, txnEnder, context.fail)
|
|
174
|
+
context.done = awsLambda.wrapCallbackAndCaptureError(transaction, txnEnder, context.done)
|
|
175
|
+
context.fail = awsLambda.wrapCallbackAndCaptureError(transaction, txnEnder, context.fail)
|
|
169
176
|
shim.wrap(context, 'succeed', function wrapSucceed(shim, original) {
|
|
170
177
|
return function wrappedSucceed() {
|
|
171
178
|
txnEnder()
|
|
@@ -215,25 +222,26 @@ class AwsLambda {
|
|
|
215
222
|
)
|
|
216
223
|
return prom
|
|
217
224
|
}
|
|
225
|
+
}
|
|
218
226
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
227
|
+
wrapCallbackAndCaptureError(transaction, txnEnder, cb, processResult) {
|
|
228
|
+
const shim = this.shim
|
|
229
|
+
return function wrappedCallback() {
|
|
230
|
+
let err = arguments[0]
|
|
231
|
+
if (typeof err === 'string') {
|
|
232
|
+
err = new Error(err)
|
|
233
|
+
}
|
|
225
234
|
|
|
226
|
-
|
|
235
|
+
shim.agent.errors.add(transaction, err)
|
|
227
236
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
237
|
+
if (processResult) {
|
|
238
|
+
const result = arguments[1]
|
|
239
|
+
processResult(result)
|
|
240
|
+
}
|
|
232
241
|
|
|
233
|
-
|
|
242
|
+
txnEnder()
|
|
234
243
|
|
|
235
|
-
|
|
236
|
-
}
|
|
244
|
+
return cb.apply(this, arguments)
|
|
237
245
|
}
|
|
238
246
|
}
|
|
239
247
|
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2020 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
|
|
8
|
+
const logger = require('../logger').child({ component: 'base-span-streamer' })
|
|
9
|
+
const NAMES = require('../metrics/names').INFINITE_TRACING
|
|
10
|
+
const SPAN_DROP_MSG_INTERVAL_MS = 30000
|
|
11
|
+
const SPAN_DROP_MSG =
|
|
12
|
+
'Queue full, dropping spans. ' +
|
|
13
|
+
`Will not warn again for ${SPAN_DROP_MSG_INTERVAL_MS / 1000} seconds.`
|
|
14
|
+
|
|
15
|
+
class BaseSpanStreamer {
|
|
16
|
+
constructor(licenseKey, connection, metrics, queueSize) {
|
|
17
|
+
this.stream = null
|
|
18
|
+
this.license_key = licenseKey
|
|
19
|
+
this.connection = connection
|
|
20
|
+
this.queue_size = queueSize
|
|
21
|
+
this.spans = []
|
|
22
|
+
this._metrics = metrics
|
|
23
|
+
this._writable = false
|
|
24
|
+
|
|
25
|
+
// 'connected' indicates a safely writeable stream.
|
|
26
|
+
// May still be mid-connect to gRPC server.
|
|
27
|
+
this.connection.on('connected', (stream) => {
|
|
28
|
+
logger.info('Span streamer connected')
|
|
29
|
+
this.stream = stream
|
|
30
|
+
this._writable = true
|
|
31
|
+
this.sendQueue()
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
this.connection.on('disconnected', () => {
|
|
35
|
+
logger.info('Span streamer disconnected')
|
|
36
|
+
this.stream = null
|
|
37
|
+
this._writable = false
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
addToQueue() {
|
|
42
|
+
throw new Error('addToQueue is not implemented')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
sendQueue() {
|
|
46
|
+
throw new Error('sendQueue is not implemented')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* Accepts a span and either writes it to the stream, queues it to be sent,
|
|
50
|
+
* or drops it depending on stream/queue state
|
|
51
|
+
*/
|
|
52
|
+
write(span) {
|
|
53
|
+
this._metrics.getOrCreateMetric(NAMES.SEEN).incrementCallCount()
|
|
54
|
+
|
|
55
|
+
// If not writeable (because of backpressure) queue the span
|
|
56
|
+
if (!this._writable) {
|
|
57
|
+
if (this.spans.length < this.queue_size) {
|
|
58
|
+
this.addToQueue(span)
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// While this can be directionally calculated between seen/sent the
|
|
63
|
+
// queue makes that a bit more disconnected. This will be a bit more specific.
|
|
64
|
+
this._metrics.getOrCreateMetric(NAMES.DROPPED).incrementCallCount()
|
|
65
|
+
|
|
66
|
+
// If the queue is full drop the span
|
|
67
|
+
logger.infoOncePer(
|
|
68
|
+
'SPAN_DROP_MSG', // key for the OncePer
|
|
69
|
+
SPAN_DROP_MSG_INTERVAL_MS,
|
|
70
|
+
SPAN_DROP_MSG
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Sends the data(spans or span) over the stream. Spans are only sent here if the stream is
|
|
77
|
+
* in a writable state. If the stream becomes unwritable after sending the
|
|
78
|
+
* span, a drain event handler is setup to continue writing when possible.
|
|
79
|
+
*
|
|
80
|
+
* @param {*} data spans or span
|
|
81
|
+
* @param {number} [spanLen=1] number of spans sent in a stream(defaults to 1)
|
|
82
|
+
*/
|
|
83
|
+
send(data, spanLen = 1) {
|
|
84
|
+
// false indicates the stream has reached the highWaterMark
|
|
85
|
+
// and future writes should be avoided until drained. written items,
|
|
86
|
+
// including the one that returned false, will still be buffered.
|
|
87
|
+
this._writable = this.stream.write(data)
|
|
88
|
+
this._metrics.getOrCreateMetric(NAMES.SENT).incrementCallCount(spanLen)
|
|
89
|
+
|
|
90
|
+
if (!this._writable) {
|
|
91
|
+
const waitDrainStart = Date.now()
|
|
92
|
+
const onDrain = this.drain.bind(this, waitDrainStart)
|
|
93
|
+
this.stream.once('drain', onDrain)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Drains the span queue that built up when the connection was
|
|
99
|
+
* back-pressured or disconnected. `waitDrainStart` is when the stream
|
|
100
|
+
* initially blocked, used to time how long the stream was blocked. If this
|
|
101
|
+
* is not defined, it is assumed this is being called after a reconnect,
|
|
102
|
+
* and the metric is not used.
|
|
103
|
+
*
|
|
104
|
+
* @param {number} waitDrainStart time that drain started
|
|
105
|
+
*/
|
|
106
|
+
drain(waitDrainStart) {
|
|
107
|
+
// Metric can be used to see how frequently completing drains as well as
|
|
108
|
+
// average time to drain from when we first notice.
|
|
109
|
+
const drainCompleted = Date.now()
|
|
110
|
+
const drainDurationMs = drainCompleted - waitDrainStart
|
|
111
|
+
this._metrics.getOrCreateMetric(NAMES.DRAIN_DURATION).recordValue(drainDurationMs / 1000)
|
|
112
|
+
|
|
113
|
+
// Once the 'drain' event fires we can begin writing to the stream again
|
|
114
|
+
this._writable = true
|
|
115
|
+
|
|
116
|
+
this.sendQueue()
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
connect(agentRunId, requestHeadersMap) {
|
|
120
|
+
this.connection.setConnectionDetails(this.license_key, agentRunId, requestHeadersMap)
|
|
121
|
+
|
|
122
|
+
this.connection.connectSpans(this.method)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
disconnect() {
|
|
126
|
+
this.connection.disconnect()
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
createMetrics() {
|
|
130
|
+
this._metrics.getOrCreateMetric(NAMES.QUEUE_CAPACITY).recordValue(this.queue_size)
|
|
131
|
+
this._metrics.getOrCreateMetric(NAMES.QUEUE_SIZE).recordValue(this.spans.length)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
module.exports = BaseSpanStreamer
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2023 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
|
|
8
|
+
const logger = require('../logger').child({ component: 'batch-span-streamer' })
|
|
9
|
+
const BaseSpanStreamer = require('./base-span-streamer')
|
|
10
|
+
|
|
11
|
+
class BatchSpanStreamer extends BaseSpanStreamer {
|
|
12
|
+
constructor(licenseKey, connection, metrics, queueSize) {
|
|
13
|
+
super(licenseKey, connection, metrics, queueSize)
|
|
14
|
+
this.sendTimer = null
|
|
15
|
+
this.batchSize = 750
|
|
16
|
+
this.connection.on('connected', () => {
|
|
17
|
+
logger.debug('Setting up batch interval')
|
|
18
|
+
this.sendTimer = setInterval(this.sendQueue.bind(this), 5000)
|
|
19
|
+
this.sendTimer.unref()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
this.connection.on('disconnected', () => {
|
|
23
|
+
logger.debug('Clearing batch interval')
|
|
24
|
+
if (this.sendTimer) {
|
|
25
|
+
clearInterval(this.sendTimer)
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
addToQueue(span) {
|
|
31
|
+
this.spans.push(span.toStreamingFormat())
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* Accepts a span and either writes it to the stream, queues it to be sent,
|
|
35
|
+
* or drops it depending on stream/queue state
|
|
36
|
+
*/
|
|
37
|
+
write(span) {
|
|
38
|
+
super.write(span)
|
|
39
|
+
|
|
40
|
+
if (!this._writable) {
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
this.addToQueue(span)
|
|
45
|
+
|
|
46
|
+
if (this.spans.length < this.queue_size) {
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
this.sendQueue()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Chunks the span queue into n per batch(currently at 750).
|
|
55
|
+
* The avg span is generally 1kb, picking an option slightly under to avoid
|
|
56
|
+
* being over 1MB uncompressed limit being imposed on the gRPC server.
|
|
57
|
+
* Since the processing happens asyc it'll be very hard to split further
|
|
58
|
+
* if a span batch is too big. We are being conservative here and other
|
|
59
|
+
* language agents transmit even smaller batches(100 per batch).
|
|
60
|
+
*/
|
|
61
|
+
sendQueue() {
|
|
62
|
+
if (!this.spans.length) {
|
|
63
|
+
logger.trace('Queue is empty, not sending spans.')
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
for (let i = 0; i < this.spans.length; i += this.batchSize) {
|
|
68
|
+
const spans = this.spans.slice(i, i + this.batchSize)
|
|
69
|
+
logger.trace('Sending spans from queue: %s', spans.length)
|
|
70
|
+
this.send({ spans }, spans.length)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
this.spans = []
|
|
74
|
+
|
|
75
|
+
logger.trace('Finished sending spans from queue.')
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = BatchSpanStreamer
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
const logger = require('../logger')
|
|
9
9
|
const SpanEventAggregator = require('./span-event-aggregator')
|
|
10
10
|
const StreamingSpanEventAggregator = require('./streaming-span-event-aggregator')
|
|
11
|
+
const NAMES = require('../metrics/names').INFINITE_TRACING
|
|
11
12
|
|
|
12
13
|
function createSpanEventAggregator(config, collector, metrics) {
|
|
13
14
|
const traceObserver = config.infinite_tracing.trace_observer
|
|
@@ -42,16 +43,32 @@ function createSpanEventAggregator(config, collector, metrics) {
|
|
|
42
43
|
function createStreamingAggregator(config, collector, metrics) {
|
|
43
44
|
logger.trace('Creating streaming span event aggregator for infinite tracing.')
|
|
44
45
|
const GrpcConnection = require('../grpc/connection')
|
|
45
|
-
const SpanStreamer = require('./span-streamer')
|
|
46
46
|
|
|
47
|
-
const connection = new GrpcConnection(config.infinite_tracing
|
|
48
|
-
|
|
49
|
-
config.license_key,
|
|
50
|
-
connection,
|
|
51
|
-
metrics,
|
|
52
|
-
config.infinite_tracing.span_events.queue_size
|
|
53
|
-
)
|
|
47
|
+
const connection = new GrpcConnection(config.infinite_tracing, metrics)
|
|
48
|
+
let spanStreamer
|
|
54
49
|
|
|
50
|
+
if (config.infinite_tracing.batching) {
|
|
51
|
+
const BatchSpanStreamer = require('./batch-span-streamer')
|
|
52
|
+
spanStreamer = new BatchSpanStreamer(
|
|
53
|
+
config.license_key,
|
|
54
|
+
connection,
|
|
55
|
+
metrics,
|
|
56
|
+
config.infinite_tracing.span_events.queue_size
|
|
57
|
+
)
|
|
58
|
+
metrics.getOrCreateMetric(`${NAMES.BATCHING}/enabled`).incrementCallCount()
|
|
59
|
+
} else {
|
|
60
|
+
const SpanStreamer = require('./span-streamer')
|
|
61
|
+
spanStreamer = new SpanStreamer(
|
|
62
|
+
config.license_key,
|
|
63
|
+
connection,
|
|
64
|
+
metrics,
|
|
65
|
+
config.infinite_tracing.span_events.queue_size
|
|
66
|
+
)
|
|
67
|
+
metrics.getOrCreateMetric(`${NAMES.BATCHING}/disabled`).incrementCallCount()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// this periodMs has no affect on gRPC calls
|
|
71
|
+
// the send method on StreamingSpanEventAggregator is a no-op
|
|
55
72
|
const opts = {
|
|
56
73
|
periodMs: 1000,
|
|
57
74
|
limit: 50000,
|
|
@@ -1,68 +1,29 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright
|
|
2
|
+
* Copyright 2023 New Relic Corporation. All rights reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
'use strict'
|
|
7
7
|
|
|
8
8
|
const logger = require('../logger').child({ component: 'span-streamer' })
|
|
9
|
-
const
|
|
9
|
+
const BaseSpanStreamer = require('./base-span-streamer')
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
const SPAN_DROP_MSG =
|
|
13
|
-
'Queue full, dropping spans. ' +
|
|
14
|
-
`Will not warn again for ${SPAN_DROP_MSG_INTERVAL_MS / 1000} seconds.`
|
|
15
|
-
|
|
16
|
-
class SpanStreamer {
|
|
11
|
+
class SpanStreamer extends BaseSpanStreamer {
|
|
17
12
|
constructor(licenseKey, connection, metrics, queueSize) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
this.connection = connection
|
|
21
|
-
this.queue_size = queueSize
|
|
22
|
-
this.spans = []
|
|
23
|
-
this._metrics = metrics
|
|
24
|
-
this._writable = false
|
|
25
|
-
|
|
26
|
-
// 'connected' indicates a safely writeable stream.
|
|
27
|
-
// May still be mid-connect to gRPC server.
|
|
28
|
-
this.connection.on('connected', (stream) => {
|
|
29
|
-
logger.info('Span streamer connected')
|
|
30
|
-
this.stream = stream
|
|
31
|
-
this._writable = true
|
|
32
|
-
this.sendQueue()
|
|
33
|
-
})
|
|
13
|
+
super(licenseKey, connection, metrics, queueSize)
|
|
14
|
+
}
|
|
34
15
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
this.stream = null
|
|
38
|
-
this._writable = false
|
|
39
|
-
})
|
|
16
|
+
addToQueue(span) {
|
|
17
|
+
this.spans.push(span)
|
|
40
18
|
}
|
|
41
19
|
|
|
42
20
|
/* Accepts a span and either writes it to the stream, queues it to be sent,
|
|
43
21
|
* or drops it depending on stream/queue state
|
|
44
22
|
*/
|
|
45
23
|
write(span) {
|
|
46
|
-
|
|
24
|
+
super.write(span)
|
|
47
25
|
|
|
48
|
-
// If not writeable (because of backpressure) queue the span
|
|
49
26
|
if (!this._writable) {
|
|
50
|
-
if (this.spans.length < this.queue_size) {
|
|
51
|
-
this.spans.push(span)
|
|
52
|
-
return
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// While this can be directionally calculated between seen/sent the
|
|
56
|
-
// queue makes that a bit more disconnected. This will be a bit more specific.
|
|
57
|
-
this._metrics.getOrCreateMetric(NAMES.DROPPED).incrementCallCount()
|
|
58
|
-
|
|
59
|
-
// If the queue is full drop the span
|
|
60
|
-
logger.infoOncePer(
|
|
61
|
-
'SPAN_DROP_MSG', // key for the OncePer
|
|
62
|
-
SPAN_DROP_MSG_INTERVAL_MS,
|
|
63
|
-
SPAN_DROP_MSG
|
|
64
|
-
)
|
|
65
|
-
|
|
66
27
|
return
|
|
67
28
|
}
|
|
68
29
|
|
|
@@ -79,51 +40,13 @@ class SpanStreamer {
|
|
|
79
40
|
}
|
|
80
41
|
}
|
|
81
42
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
*
|
|
87
|
-
* @param span
|
|
88
|
-
*/
|
|
89
|
-
send(span) {
|
|
90
|
-
// false indicates the stream has reached the highWaterMark
|
|
91
|
-
// and future writes should be avoided until drained. written items,
|
|
92
|
-
// including the one that returned false, will still be buffered.
|
|
93
|
-
this._writable = this.stream.write(span)
|
|
94
|
-
this._metrics.getOrCreateMetric(NAMES.SENT).incrementCallCount()
|
|
95
|
-
|
|
96
|
-
if (!this._writable) {
|
|
97
|
-
const waitDrainStart = Date.now()
|
|
98
|
-
const onDrain = this.drain.bind(this, waitDrainStart)
|
|
99
|
-
this.stream.once('drain', onDrain)
|
|
43
|
+
sendQueue() {
|
|
44
|
+
if (!this.spans.length) {
|
|
45
|
+
logger.trace('Queue is empty, not sending spans.')
|
|
46
|
+
return
|
|
100
47
|
}
|
|
101
|
-
}
|
|
102
48
|
|
|
103
|
-
|
|
104
|
-
* Drains the span queue that built up when the connection was
|
|
105
|
-
* back-pressured or disconnected. `waitDrainStart` is when the stream
|
|
106
|
-
* initially blocked, used to time how long the stream was blocked. If this
|
|
107
|
-
* is not defined, it is assumed this is being called after a reconnect,
|
|
108
|
-
* and the metric is not used.
|
|
109
|
-
*
|
|
110
|
-
* @param waitDrainStart
|
|
111
|
-
*/
|
|
112
|
-
drain(waitDrainStart) {
|
|
113
|
-
// Metric can be used to see how frequently completing drains as well as
|
|
114
|
-
// average time to drain from when we first notice.
|
|
115
|
-
const drainCompleted = Date.now()
|
|
116
|
-
const drainDurationMs = drainCompleted - waitDrainStart
|
|
117
|
-
this._metrics.getOrCreateMetric(NAMES.DRAIN_DURATION).recordValue(drainDurationMs / 1000)
|
|
118
|
-
|
|
119
|
-
// Once the 'drain' event fires we can begin writing to the stream again
|
|
120
|
-
this._writable = true
|
|
121
|
-
|
|
122
|
-
this.sendQueue()
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
sendQueue() {
|
|
126
|
-
logger.trace('Sending spans from queue.')
|
|
49
|
+
logger.trace('Sending spans from queue: %s.', this.spans.length)
|
|
127
50
|
|
|
128
51
|
// Continue sending the spans that were in the queue. _writable is checked
|
|
129
52
|
// so that if a send fails while clearing the queue, this drain handler can
|
|
@@ -136,21 +59,6 @@ class SpanStreamer {
|
|
|
136
59
|
|
|
137
60
|
logger.trace('Finished sending spans from queue. Items left in queue: %s', this.spans.length)
|
|
138
61
|
}
|
|
139
|
-
|
|
140
|
-
connect(agentRunId, requestHeadersMap) {
|
|
141
|
-
this.connection.setConnectionDetails(this.license_key, agentRunId, requestHeadersMap)
|
|
142
|
-
|
|
143
|
-
this.connection.connectSpans()
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
disconnect() {
|
|
147
|
-
this.connection.disconnect()
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
createMetrics() {
|
|
151
|
-
this._metrics.getOrCreateMetric(NAMES.QUEUE_CAPACITY).recordValue(this.queue_size)
|
|
152
|
-
this._metrics.getOrCreateMetric(NAMES.QUEUE_SIZE).recordValue(this.spans.length)
|
|
153
|
-
}
|
|
154
62
|
}
|
|
155
63
|
|
|
156
64
|
module.exports = SpanStreamer
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "newrelic",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.1.0",
|
|
4
4
|
"author": "New Relic Node.js agent team <nodejs@newrelic.com>",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"contributors": [
|
|
@@ -231,7 +231,6 @@
|
|
|
231
231
|
"prettier": "^2.3.2",
|
|
232
232
|
"proxyquire": "^1.8.0",
|
|
233
233
|
"q": "*",
|
|
234
|
-
"request": "^2.88.2",
|
|
235
234
|
"rimraf": "^2.6.3",
|
|
236
235
|
"should": "*",
|
|
237
236
|
"sinon": "^4.5.0",
|