newrelic 6.7.0 → 6.10.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 +83 -0
- package/README.md +2 -0
- package/THIRD_PARTY_NOTICES.md +2 -0
- package/api.js +26 -12
- package/bin/run-versioned-tests.sh +10 -1
- package/bin/ssl.sh +3 -3
- package/lib/config/index.js +9 -0
- package/lib/errors/error-collector.js +30 -14
- package/lib/grpc/connection.js +60 -90
- package/lib/header-attributes.js +4 -0
- package/lib/instrumentation/core/http-outbound.js +3 -3
- package/lib/instrumentation/core/http.js +32 -14
- package/lib/metrics/normalizer.js +5 -5
- package/lib/prioritized-attributes.js +196 -0
- package/lib/serverless/aws-lambda.js +37 -4
- package/lib/serverless/event-sources.json +1 -1
- package/lib/shim/message-shim.js +16 -0
- package/lib/shim/shim.js +3 -3
- package/lib/spans/span-context.js +36 -0
- package/lib/spans/span-event.js +8 -1
- package/lib/spans/span-streamer.js +3 -2
- package/lib/spans/streaming-span-event-aggregator.js +11 -1
- package/lib/spans/streaming-span-event.js +7 -1
- package/lib/transaction/index.js +83 -15
- package/lib/transaction/trace/index.js +22 -0
- package/lib/transaction/trace/segment.js +28 -9
- package/lib/transaction/tracer/index.js +5 -0
- package/package.json +24 -9
- package/.eslintignore +0 -1
- package/.eslintrc.js +0 -67
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -5
- package/.travis.yml +0 -40
package/NEWS.md
CHANGED
|
@@ -1,3 +1,86 @@
|
|
|
1
|
+
### 6.10.0 (2020-06-22):
|
|
2
|
+
|
|
3
|
+
* Additional Transaction Information applied to Span Events
|
|
4
|
+
* When Distributed Tracing and/or Infinite Tracing are enabled, the Agent will now incorporate additional information from the Transaction Event on to the currently available Span Event of the transaction.
|
|
5
|
+
* The following items are affected:
|
|
6
|
+
* `aws-lambda` related attributes
|
|
7
|
+
* `error.message`
|
|
8
|
+
* `error.class`
|
|
9
|
+
* `error.expected`
|
|
10
|
+
* `http.statusCode`
|
|
11
|
+
* `http.statusText`
|
|
12
|
+
* `message.*`
|
|
13
|
+
* `parent.type`
|
|
14
|
+
* `parent.app`
|
|
15
|
+
* `parent.account`
|
|
16
|
+
* `parent.transportType`
|
|
17
|
+
* `parent.transportDuration`
|
|
18
|
+
* Request Parameters `request.parameters.*`
|
|
19
|
+
* `request.header.*`
|
|
20
|
+
* `request.method`
|
|
21
|
+
* `request.uri`
|
|
22
|
+
* Custom Attributes
|
|
23
|
+
* Custom transaction attributes added via `API.addCustomAttribute` or `API.addCustomAttributes` will now be propagated to the currently active span, if available.
|
|
24
|
+
* **Security Recommendation:**
|
|
25
|
+
* Review your Transaction Event attributes configuration. Any attribute include or exclude setting specific to Transaction Events should be applied to your Span Attributes configuration or global attributes configuration. Please see [Node.js agent attributes](https://docs.newrelic.com/docs/agents/nodejs-agent/attributes/nodejs-agent-attributes#configure-attributes) for more on how to configure.
|
|
26
|
+
* Upgraded @grpc/grpc-js from 1.0.3 to 1.0.4
|
|
27
|
+
* Modified redis callback-less versioned test to use `commandQueueLength` as indicator redis command has completed and test can continue. This is in effort to further reduce these test flickers. Additionally, added wait for client 'ready' before moving on to tests.
|
|
28
|
+
* Updated force secret test runs to run on branch pushes to the main repository.
|
|
29
|
+
|
|
30
|
+
### 6.9.0 (2020-06-08):
|
|
31
|
+
|
|
32
|
+
* Added AWS API Gateway V2 Support to lambda instrumentation.
|
|
33
|
+
|
|
34
|
+
* Added 'transaction.name' intrinsic to active span at time transaction name is finalized.
|
|
35
|
+
|
|
36
|
+
This enables finding transaction name for traces that may not have a matching transaction event.
|
|
37
|
+
|
|
38
|
+
* Added 'error.expected' attribute to span active at time expected error was noticed.
|
|
39
|
+
|
|
40
|
+
* Dropped errors earlier during collection when error collection is disabled.
|
|
41
|
+
|
|
42
|
+
Error attributes will no longer show up on spans when error collection has been disabled. Other unnecessary work will also be avoided.
|
|
43
|
+
|
|
44
|
+
* Removed allocation of logging-only objects used by transaction naming when those log levels are disabled.
|
|
45
|
+
|
|
46
|
+
* Upgraded escodegen from 1.12.0 to 1.14.1.
|
|
47
|
+
|
|
48
|
+
* Upgraded readable-stream from 3.4.0 to 3.6.0.
|
|
49
|
+
|
|
50
|
+
* Upgraded @grpc/proto-loader from 0.5.3 to 0.5.4.
|
|
51
|
+
|
|
52
|
+
* Converted facts unit test to use tap API.
|
|
53
|
+
|
|
54
|
+
* Converted transaction 'finalizeName...' unit tests to use tap API.
|
|
55
|
+
|
|
56
|
+
* Added several items to .npmignore to prevent accidental publishing.
|
|
57
|
+
|
|
58
|
+
* Fixed Redis client w/o callback versioned test flicker.
|
|
59
|
+
|
|
60
|
+
Doesn't end transaction until error encountered. Increases wait time for first operation which has to complete for the second operation to be successful.
|
|
61
|
+
|
|
62
|
+
### 6.8.0 (2020-05-21):
|
|
63
|
+
|
|
64
|
+
* Bumped @newrelic/native-metrics to ^5.1.0.
|
|
65
|
+
|
|
66
|
+
Upgraded nan to ^2.14.1 to resolve 'GetContents' deprecation warning with Node 14. This version of the native metrics module is tested against Node 14 and includes a pre-built binary download backup for Node 14.
|
|
67
|
+
|
|
68
|
+
* Added whitespace trimming of license key configuration values.
|
|
69
|
+
|
|
70
|
+
Previously, when a license key was entered with leading or trailing whitespace, it would be used as-is and result in a validation failure. This most commonly occurred with environment variable based configuration.
|
|
71
|
+
|
|
72
|
+
* Moved to GitHub actions for CI.
|
|
73
|
+
|
|
74
|
+
* Updated PR template and added initial issue templates.
|
|
75
|
+
|
|
76
|
+
* Converted most of the collector API unit tests to use the tap API. Split larger test groupings into their own test files.
|
|
77
|
+
|
|
78
|
+
### 6.7.1 (2020-05-14):
|
|
79
|
+
|
|
80
|
+
* Added synthetics headers to transaction event intrinsics for DT
|
|
81
|
+
|
|
82
|
+
* Fixed stale comment documentation with regards to segment recording
|
|
83
|
+
|
|
1
84
|
### 6.7.0 (2020-05-06):
|
|
2
85
|
|
|
3
86
|
* Added a configurable-length span queue to Infinite Tracing:
|
package/README.md
CHANGED
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -18,6 +18,8 @@ can be found at https://github.com/newrelic/node-newrelic.
|
|
|
18
18
|
* [json-stringify-safe](#json-stringify-safe)
|
|
19
19
|
* [readable-stream](#readable-stream)
|
|
20
20
|
* [semver](#semver)
|
|
21
|
+
* [@grpc/grpc-js](#@grpc/grpc-js)
|
|
22
|
+
* [@grpc/proto-loader](#@grpc/proto-loader)
|
|
21
23
|
|
|
22
24
|
## async
|
|
23
25
|
|
package/api.js
CHANGED
|
@@ -272,7 +272,7 @@ API.prototype.setControllerName = function setControllerName(name, action) {
|
|
|
272
272
|
* @param {string} value The value you want displayed. Must be serializable.
|
|
273
273
|
*/
|
|
274
274
|
API.prototype.addCustomAttribute = function addCustomAttribute(key, value) {
|
|
275
|
-
|
|
275
|
+
const metric = this.agent.metrics.getOrCreateMetric(
|
|
276
276
|
NAMES.SUPPORTABILITY.API + '/addCustomAttribute'
|
|
277
277
|
)
|
|
278
278
|
metric.incrementCallCount()
|
|
@@ -291,24 +291,36 @@ API.prototype.addCustomAttribute = function addCustomAttribute(key, value) {
|
|
|
291
291
|
return false
|
|
292
292
|
}
|
|
293
293
|
|
|
294
|
-
|
|
294
|
+
const transaction = this.agent.tracer.getTransaction()
|
|
295
295
|
if (!transaction) {
|
|
296
|
-
|
|
296
|
+
logger.warn('No transaction found for custom attributes.')
|
|
297
|
+
return false
|
|
297
298
|
}
|
|
298
299
|
|
|
299
|
-
|
|
300
|
+
const trace = transaction.trace
|
|
300
301
|
if (!trace.custom) {
|
|
301
|
-
|
|
302
|
+
logger.warn(
|
|
302
303
|
'Could not add attribute %s to nonexistent custom attributes.',
|
|
303
304
|
key
|
|
304
305
|
)
|
|
306
|
+
return false
|
|
305
307
|
}
|
|
306
308
|
|
|
307
309
|
if (CUSTOM_DENYLIST.has(key)) {
|
|
308
|
-
|
|
310
|
+
logger.warn('Not overwriting value of NR-only attribute %s.', key)
|
|
311
|
+
return false
|
|
309
312
|
}
|
|
310
313
|
|
|
311
314
|
trace.addCustomAttribute(key, value)
|
|
315
|
+
|
|
316
|
+
const spanContext = this.agent.tracer.getSpanContext()
|
|
317
|
+
if (!spanContext) {
|
|
318
|
+
logger.debug('No span found for custom attributes.')
|
|
319
|
+
// success/failure is ambiguous here. since at least 1 attempt tried, not returning false
|
|
320
|
+
return
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
spanContext.addCustomAttribute(key, value, spanContext.ATTRIBUTE_PRIORITY.LOW)
|
|
312
324
|
}
|
|
313
325
|
|
|
314
326
|
/**
|
|
@@ -395,20 +407,22 @@ API.prototype.addCustomSpanAttribute = function addCustomSpanAttribute(key, valu
|
|
|
395
407
|
return false
|
|
396
408
|
}
|
|
397
409
|
|
|
398
|
-
const
|
|
410
|
+
const spanContext = this.agent.tracer.getSpanContext()
|
|
399
411
|
|
|
400
|
-
if (!
|
|
401
|
-
|
|
402
|
-
'Could not add attribute %s. No available span
|
|
412
|
+
if (!spanContext) {
|
|
413
|
+
logger.debug(
|
|
414
|
+
'Could not add attribute %s. No available span.',
|
|
403
415
|
key
|
|
404
416
|
)
|
|
417
|
+
return false
|
|
405
418
|
}
|
|
406
419
|
|
|
407
420
|
if (CUSTOM_DENYLIST.has(key)) {
|
|
408
|
-
|
|
421
|
+
logger.warn('Not overwriting value of NR-only attribute %s.', key)
|
|
422
|
+
return false
|
|
409
423
|
}
|
|
410
424
|
|
|
411
|
-
|
|
425
|
+
spanContext.addCustomAttribute(key, value)
|
|
412
426
|
}
|
|
413
427
|
|
|
414
428
|
API.prototype.setIgnoreTransaction = util.deprecate(
|
|
@@ -21,5 +21,14 @@ fi
|
|
|
21
21
|
|
|
22
22
|
export AGENT_PATH=`pwd`
|
|
23
23
|
|
|
24
|
+
# @koa/router 8.03+ breaks segment naming for nested routes
|
|
25
|
+
skip="koa"
|
|
26
|
+
|
|
27
|
+
# Don't run the aws-sdk tests if we don't have the keys set
|
|
28
|
+
if [[ -z "$AWS_ACCESS_KEY_ID" ]]; then
|
|
29
|
+
skip="${skip},aws-sdk"
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
|
|
24
33
|
# This is meant to be temporary. Remove once new major version with fixes rolled into agent.
|
|
25
|
-
time ./node_modules/.bin/versioned-tests $VERSIONED_MODE -i 2 -s
|
|
34
|
+
time ./node_modules/.bin/versioned-tests $VERSIONED_MODE -i 2 -s $skip ${directories[@]}
|
package/bin/ssl.sh
CHANGED
|
@@ -8,7 +8,7 @@ set -x # be chatty and show the lines we're running
|
|
|
8
8
|
# that are mysterious and not understood, so let
|
|
9
9
|
# bail early if we detect that's the case
|
|
10
10
|
ENGINE_OPENSSL=`openssl version | awk '{print $(1)}'`
|
|
11
|
-
if [ "$ENGINE_OPENSSL"
|
|
11
|
+
if [ "$ENGINE_OPENSSL" = "LibreSSL" ]
|
|
12
12
|
then
|
|
13
13
|
echo "LibreSSL is not supported, please install a stock openssl and \n"
|
|
14
14
|
echo "make sure that openssl binary is in your PATH"
|
|
@@ -25,7 +25,7 @@ CERTIFICATE="test/lib/self-signed-test-certificate.crt"
|
|
|
25
25
|
|
|
26
26
|
# USAGE: ./bin/ssl.sh clear
|
|
27
27
|
# a sub command to remove all the generated files and start over
|
|
28
|
-
if [ "$1"
|
|
28
|
+
if [ "$1" = "clear" ]
|
|
29
29
|
then
|
|
30
30
|
rm $SSLKEY
|
|
31
31
|
rm $CACERT
|
|
@@ -37,7 +37,7 @@ fi
|
|
|
37
37
|
|
|
38
38
|
# if there's already a certificate, then exit, but
|
|
39
39
|
# exit with a success code so build continue
|
|
40
|
-
if [ -
|
|
40
|
+
if [ -e $CERTIFICATE ]; then
|
|
41
41
|
exit 0;
|
|
42
42
|
fi
|
|
43
43
|
|
package/lib/config/index.js
CHANGED
|
@@ -281,6 +281,11 @@ Config.prototype._fromServer = function _fromServer(params, key) {
|
|
|
281
281
|
this.run_id = params.agent_run_id
|
|
282
282
|
break
|
|
283
283
|
|
|
284
|
+
// if it's undefined or null, so be it
|
|
285
|
+
case 'request_headers_map':
|
|
286
|
+
this.request_headers_map = params.request_headers_map
|
|
287
|
+
break
|
|
288
|
+
|
|
284
289
|
// handled by config.onConnect
|
|
285
290
|
case 'high_security':
|
|
286
291
|
break
|
|
@@ -1323,6 +1328,10 @@ Config.prototype._canonicalize = function _canonicalize() {
|
|
|
1323
1328
|
|
|
1324
1329
|
this.serverless_mode.enabled = this.serverless_mode.enabled
|
|
1325
1330
|
&& this.feature_flag.serverless_mode
|
|
1331
|
+
|
|
1332
|
+
if (this.license_key) {
|
|
1333
|
+
this.license_key = this.license_key.trim()
|
|
1334
|
+
}
|
|
1326
1335
|
}
|
|
1327
1336
|
|
|
1328
1337
|
function _parseCodes(codes) {
|
|
@@ -193,6 +193,12 @@ class ErrorCollector {
|
|
|
193
193
|
return
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
+
const shouldCollectErrors = this._shouldCollectErrors()
|
|
197
|
+
if (!shouldCollectErrors) {
|
|
198
|
+
logger.trace('error_collector.enabled is false, dropping application error.')
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
|
|
196
202
|
if (errorHelper.shouldIgnoreError(transaction, error, this.config)) {
|
|
197
203
|
logger.trace('Ignoring error')
|
|
198
204
|
return
|
|
@@ -223,7 +229,15 @@ class ErrorCollector {
|
|
|
223
229
|
* @param {Exception} exception The Exception to be traced.
|
|
224
230
|
*/
|
|
225
231
|
addUserError(transaction, error, customAttributes) {
|
|
226
|
-
if (!error)
|
|
232
|
+
if (!error) {
|
|
233
|
+
return
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const shouldCollectErrors = this._shouldCollectErrors()
|
|
237
|
+
if (!shouldCollectErrors) {
|
|
238
|
+
logger.trace('error_collector.enabled is false, dropping user reported error.')
|
|
239
|
+
return
|
|
240
|
+
}
|
|
227
241
|
|
|
228
242
|
const timestamp = Date.now()
|
|
229
243
|
const exception = new Exception({error, timestamp, customAttributes})
|
|
@@ -270,18 +284,6 @@ class ErrorCollector {
|
|
|
270
284
|
return false
|
|
271
285
|
}
|
|
272
286
|
|
|
273
|
-
// allow enabling & disabling the error tracer at runtime
|
|
274
|
-
// TODO: it would be better to check config in the public add() to prevents collecting
|
|
275
|
-
// errors on the transaction unnecessarily. Should we allow the work above or
|
|
276
|
-
// short-circuit sooner?
|
|
277
|
-
if (
|
|
278
|
-
!this.config.collect_errors ||
|
|
279
|
-
!this.config.error_collector ||
|
|
280
|
-
!this.config.error_collector.enabled
|
|
281
|
-
) {
|
|
282
|
-
return false
|
|
283
|
-
}
|
|
284
|
-
|
|
285
287
|
if (exception.error) {
|
|
286
288
|
logger.trace(exception.error, 'Got exception to trace:')
|
|
287
289
|
}
|
|
@@ -304,7 +306,10 @@ class ErrorCollector {
|
|
|
304
306
|
}
|
|
305
307
|
}
|
|
306
308
|
|
|
307
|
-
|
|
309
|
+
// defaults true in config/index. can be modified server-side
|
|
310
|
+
if (this.config.collect_errors) {
|
|
311
|
+
this.traceAggregator.add(errorTrace)
|
|
312
|
+
}
|
|
308
313
|
|
|
309
314
|
if (this.config.error_collector.capture_events === true) {
|
|
310
315
|
const priority = transaction && transaction.priority || Math.random()
|
|
@@ -328,6 +333,17 @@ class ErrorCollector {
|
|
|
328
333
|
this.seenObjectsByTransaction = Object.create(null)
|
|
329
334
|
}
|
|
330
335
|
|
|
336
|
+
_shouldCollectErrors() {
|
|
337
|
+
const errorCollectorEnabled =
|
|
338
|
+
this.config.error_collector && this.config.error_collector.enabled
|
|
339
|
+
|
|
340
|
+
const shouldCaptureTraceOrEvent =
|
|
341
|
+
this.config.collect_errors || // are traces enabled
|
|
342
|
+
(this.config.error_collector && this.config.error_collector.capture_events)
|
|
343
|
+
|
|
344
|
+
return errorCollectorEnabled && shouldCaptureTraceOrEvent
|
|
345
|
+
}
|
|
346
|
+
|
|
331
347
|
reconfigure(config) {
|
|
332
348
|
this.config = config
|
|
333
349
|
|
package/lib/grpc/connection.js
CHANGED
|
@@ -19,10 +19,7 @@ const protoOptions = {keepCase: true,
|
|
|
19
19
|
const pathProtoDefinition = __dirname +
|
|
20
20
|
'../../../lib/grpc/endpoints/infinite-tracing/v1.proto'
|
|
21
21
|
|
|
22
|
-
const
|
|
23
|
-
initialSeconds: 0,
|
|
24
|
-
seconds:15
|
|
25
|
-
}
|
|
22
|
+
const DEFAULT_RECONNECT_DELAY_MS = 15 * 1000
|
|
26
23
|
|
|
27
24
|
/**
|
|
28
25
|
* Class for managing the GRPC connection
|
|
@@ -41,31 +38,21 @@ class GrpcConnection extends EventEmitter {
|
|
|
41
38
|
*
|
|
42
39
|
* @param {Object} traceObserverConfig config item config.infinite_tracing.trace_observer
|
|
43
40
|
* @param {MetricAggregator} metrics metric aggregator, for supportability metrics
|
|
44
|
-
* @param {
|
|
41
|
+
* @param {Number} [reconnectDelayMs=15000] number of milliseconds to wait before reconnecting
|
|
42
|
+
* for error states that require a reconnect delay.
|
|
45
43
|
*/
|
|
46
|
-
constructor(traceObserverConfig, metrics,
|
|
44
|
+
constructor(traceObserverConfig, metrics, reconnectDelayMs) {
|
|
47
45
|
super()
|
|
48
46
|
|
|
49
|
-
|
|
50
|
-
if ( (backoffOpts.initialSeconds || backoffOpts.initialSeconds === 0) &&
|
|
51
|
-
(backoffOpts.seconds || backoffOpts.seconds === 0)) {
|
|
52
|
-
this._backoffOpts = backoffOpts
|
|
53
|
-
} else {
|
|
54
|
-
this._backoffOpts = defaultBackoffOpts
|
|
55
|
-
logger.trace(
|
|
56
|
-
'invalid backoff information passed to constructor, using default values'
|
|
57
|
-
)
|
|
58
|
-
}
|
|
47
|
+
this._reconnectDelayMs = reconnectDelayMs || DEFAULT_RECONNECT_DELAY_MS
|
|
59
48
|
|
|
60
|
-
// initial "stream connection" has a 0 second backoff, unless
|
|
61
|
-
// different values are injected via _backoffOpts
|
|
62
|
-
this._streamBackoffSeconds = this._backoffOpts.initialSeconds
|
|
63
49
|
this._metrics = metrics
|
|
64
50
|
this._setState(connectionStates.disconnected)
|
|
65
51
|
|
|
66
52
|
this._endpoint = this.getTraceObserverEndpoint(traceObserverConfig)
|
|
67
53
|
this._licensekey = null
|
|
68
54
|
this._runId = null
|
|
55
|
+
this._requestHeadersMap = null
|
|
69
56
|
|
|
70
57
|
this.stream = null
|
|
71
58
|
}
|
|
@@ -79,11 +66,13 @@ class GrpcConnection extends EventEmitter {
|
|
|
79
66
|
* @param {string} endpoint the GRPC server's endpoint
|
|
80
67
|
* @param {string} license_key the agent license key
|
|
81
68
|
* @param {string} run_id the current agent run id (also called agent run token)
|
|
69
|
+
* @param {object} requestHeadersMap request headers map received from server connect.
|
|
82
70
|
* @param {string} [rootCerts] string of root (ca) certificates to attach to the connection.
|
|
83
71
|
*/
|
|
84
|
-
setConnectionDetails(license_key, run_id, rootCerts) {
|
|
72
|
+
setConnectionDetails(license_key, run_id, requestHeadersMap, rootCerts) {
|
|
85
73
|
this._licenseKey = license_key
|
|
86
74
|
this._runId = run_id
|
|
75
|
+
this._requestHeadersMap = requestHeadersMap
|
|
87
76
|
this._rootCerts = rootCerts
|
|
88
77
|
|
|
89
78
|
return this
|
|
@@ -118,35 +107,21 @@ class GrpcConnection extends EventEmitter {
|
|
|
118
107
|
}
|
|
119
108
|
|
|
120
109
|
this._setState(connectionStates.connecting)
|
|
121
|
-
logger.trace('
|
|
122
|
-
|
|
123
|
-
setTimeout(() => {
|
|
124
|
-
try {
|
|
125
|
-
this.stream = this._connectSpans(this._endpoint, this._licenseKey, this._runId)
|
|
110
|
+
logger.trace('Connecting to gRPC endpoint.')
|
|
126
111
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
this._setState(connectionStates.connected, this.stream)
|
|
112
|
+
try {
|
|
113
|
+
this.stream = this._connectSpans()
|
|
130
114
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}, this._getBackoffSeconds() * 1000)
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
_setStreamBackoffAfterInitialStreamSetup() {
|
|
145
|
-
this._streamBackoffSeconds = this._backoffOpts.seconds
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
_setStreamBackoffToInitialValue() {
|
|
149
|
-
this._streamBackoffSeconds = this._backoffOpts.initialSeconds
|
|
115
|
+
// May not actually be "connected" at this point but we can write to the stream
|
|
116
|
+
// immediately.
|
|
117
|
+
this._setState(connectionStates.connected, this.stream)
|
|
118
|
+
} catch (err) {
|
|
119
|
+
logger.trace(
|
|
120
|
+
err,
|
|
121
|
+
'Unexpected error establishing gRPC stream, will not attempt reconnect.'
|
|
122
|
+
)
|
|
123
|
+
this._disconnect()
|
|
124
|
+
}
|
|
150
125
|
}
|
|
151
126
|
|
|
152
127
|
/**
|
|
@@ -159,14 +134,7 @@ class GrpcConnection extends EventEmitter {
|
|
|
159
134
|
return
|
|
160
135
|
}
|
|
161
136
|
|
|
162
|
-
this.
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Calculates backoff seconds
|
|
167
|
-
*/
|
|
168
|
-
_getBackoffSeconds() {
|
|
169
|
-
return this._streamBackoffSeconds
|
|
137
|
+
this._disconnect()
|
|
170
138
|
}
|
|
171
139
|
|
|
172
140
|
/**
|
|
@@ -175,11 +143,19 @@ class GrpcConnection extends EventEmitter {
|
|
|
175
143
|
* @param {string} license_key
|
|
176
144
|
* @param {string} run_id
|
|
177
145
|
*/
|
|
178
|
-
_getMetadata(license_key, run_id, env) {
|
|
146
|
+
_getMetadata(license_key, run_id, requestHeadersMap, env) {
|
|
179
147
|
const metadata = new grpc.Metadata()
|
|
180
148
|
metadata.add('license_key', license_key)
|
|
181
149
|
metadata.add('agent_run_token', run_id)
|
|
182
150
|
|
|
151
|
+
// p17 spec: If request_headers_map is empty or absent,
|
|
152
|
+
// the agent SHOULD NOT apply anything to its requests.
|
|
153
|
+
if (requestHeadersMap) {
|
|
154
|
+
for (const [key, value] of Object.entries(requestHeadersMap)) {
|
|
155
|
+
metadata.add(key.toLowerCase(), value) // keys MUST be lowercase for Infinite Tracing
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
183
159
|
// check environment variables for testing parameters and
|
|
184
160
|
// pass to server via meta-data.
|
|
185
161
|
const flaky = parseInt(env.NEWRELIC_GRPCCONNECTION_METADATA_FLAKY, 10)
|
|
@@ -197,18 +173,23 @@ class GrpcConnection extends EventEmitter {
|
|
|
197
173
|
}
|
|
198
174
|
|
|
199
175
|
/**
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
* Called when we receive a stream status that indicates a potential
|
|
203
|
-
* problem with the stream. We set the state (which will emit an event),
|
|
204
|
-
* increment tries to honor the backoff, and then reconnect.
|
|
176
|
+
* Disconnects from gRPC endpoint and schedules establishing a new connection.
|
|
177
|
+
* @param {number} reconnectDelayMs number of milliseconds to wait before reconnecting.
|
|
205
178
|
*/
|
|
206
|
-
_reconnect() {
|
|
179
|
+
_reconnect(reconnectDelayMs = 0) {
|
|
207
180
|
this._disconnect()
|
|
208
|
-
|
|
181
|
+
|
|
182
|
+
logger.trace('Reconnecting to gRPC endpoint in [%s] seconds', reconnectDelayMs)
|
|
183
|
+
|
|
184
|
+
setTimeout(
|
|
185
|
+
this.connectSpans.bind(this),
|
|
186
|
+
reconnectDelayMs
|
|
187
|
+
)
|
|
209
188
|
}
|
|
210
189
|
|
|
211
190
|
_disconnect() {
|
|
191
|
+
logger.trace('Disconnecting from gRPC endpoint.')
|
|
192
|
+
|
|
212
193
|
if (this.stream) {
|
|
213
194
|
this.stream.removeAllListeners()
|
|
214
195
|
|
|
@@ -221,17 +202,6 @@ class GrpcConnection extends EventEmitter {
|
|
|
221
202
|
this._setState(connectionStates.disconnected)
|
|
222
203
|
}
|
|
223
204
|
|
|
224
|
-
/**
|
|
225
|
-
* Disconnects Without Reconnect
|
|
226
|
-
*
|
|
227
|
-
* Certain GRPC statuses require us to disconnect and _not_
|
|
228
|
-
* attempt a stream reconnect.
|
|
229
|
-
*/
|
|
230
|
-
_disconnectWithoutReconnect() {
|
|
231
|
-
this._disconnect()
|
|
232
|
-
this._setStreamBackoffToInitialValue()
|
|
233
|
-
}
|
|
234
|
-
|
|
235
205
|
/**
|
|
236
206
|
* Central location to setup stream observers
|
|
237
207
|
*
|
|
@@ -244,14 +214,14 @@ class GrpcConnection extends EventEmitter {
|
|
|
244
214
|
// listen for responses from server and log
|
|
245
215
|
if (logger.traceEnabled()) {
|
|
246
216
|
stream.on('data', function data(response) {
|
|
247
|
-
logger.trace("
|
|
217
|
+
logger.trace("gRPC span response stream: %s", JSON.stringify(response))
|
|
248
218
|
})
|
|
249
219
|
}
|
|
250
220
|
|
|
251
221
|
// listen for status that indicate stream has ended,
|
|
252
222
|
// and we need to disconnect
|
|
253
223
|
stream.on('status', (grpcStatus) => {
|
|
254
|
-
logger.trace('
|
|
224
|
+
logger.trace('gRPC Status Received [%s]: %s', grpcStatus.code, grpcStatus.details)
|
|
255
225
|
const grpcStatusName =
|
|
256
226
|
grpc.status[grpcStatus.code] ? grpc.status[grpcStatus.code] : 'UNKNOWN'
|
|
257
227
|
|
|
@@ -263,19 +233,15 @@ class GrpcConnection extends EventEmitter {
|
|
|
263
233
|
// per the spec, An UNIMPLEMENTED status code from gRPC indicates
|
|
264
234
|
// that the versioned Trace Observer is no longer available. Agents
|
|
265
235
|
// MUST NOT attempt to reconnect in this case
|
|
266
|
-
this.
|
|
236
|
+
this._disconnect()
|
|
237
|
+
} else if (grpc.status[grpc.status.OK] === grpcStatusName) {
|
|
238
|
+
this._reconnect()
|
|
267
239
|
} else {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
// which indicates the stream is over.
|
|
272
|
-
if (grpc.status[grpc.status.OK] !== grpcStatusName) {
|
|
273
|
-
this._metrics.getOrCreateMetric(
|
|
274
|
-
util.format(NAMES.INFINITE_TRACING.SPAN_RESPONSE_GRPC_STATUS, grpcStatusName)
|
|
275
|
-
).incrementCallCount()
|
|
276
|
-
}
|
|
240
|
+
this._metrics.getOrCreateMetric(
|
|
241
|
+
util.format(NAMES.INFINITE_TRACING.SPAN_RESPONSE_GRPC_STATUS, grpcStatusName)
|
|
242
|
+
).incrementCallCount()
|
|
277
243
|
|
|
278
|
-
this._reconnect()
|
|
244
|
+
this._reconnect(this._reconnectDelayMs)
|
|
279
245
|
}
|
|
280
246
|
})
|
|
281
247
|
|
|
@@ -322,7 +288,7 @@ class GrpcConnection extends EventEmitter {
|
|
|
322
288
|
* this makes an http2 request with the metadata, and then returns
|
|
323
289
|
* a stream for further writing.
|
|
324
290
|
*/
|
|
325
|
-
_connectSpans(
|
|
291
|
+
_connectSpans() {
|
|
326
292
|
const packageDefinition = protoLoader.loadSync(pathProtoDefinition, protoOptions)
|
|
327
293
|
|
|
328
294
|
const serviceDefinition = grpc.loadPackageDefinition(
|
|
@@ -331,14 +297,18 @@ class GrpcConnection extends EventEmitter {
|
|
|
331
297
|
|
|
332
298
|
const credentials = this._generateCredentials(grpc)
|
|
333
299
|
const client = new serviceDefinition.IngestService(
|
|
334
|
-
|
|
300
|
+
this._endpoint,
|
|
335
301
|
credentials
|
|
336
302
|
)
|
|
337
|
-
|
|
303
|
+
|
|
304
|
+
const metadata =
|
|
305
|
+
this._getMetadata(this._licenseKey, this._runId, this._requestHeadersMap, process.env)
|
|
306
|
+
|
|
338
307
|
const stream = client.recordSpan(metadata)
|
|
339
308
|
this._setupSpanStreamObservers(stream)
|
|
340
309
|
|
|
341
310
|
return stream
|
|
342
311
|
}
|
|
343
312
|
}
|
|
313
|
+
|
|
344
314
|
module.exports = GrpcConnection
|
package/lib/header-attributes.js
CHANGED
|
@@ -110,6 +110,8 @@ function _collectHeaders(headers, nameMap, prefix, transaction) {
|
|
|
110
110
|
? COLLECTED_REQUEST_HEADERS
|
|
111
111
|
: Object.keys(headers)
|
|
112
112
|
|
|
113
|
+
const segment = transaction.agent.tracer.getSegment()
|
|
114
|
+
|
|
113
115
|
for (var i = 0; i < headerKeys.length; i++) {
|
|
114
116
|
var headerKey = headerKeys[i]
|
|
115
117
|
var header = headers[headerKey]
|
|
@@ -128,6 +130,8 @@ function _collectHeaders(headers, nameMap, prefix, transaction) {
|
|
|
128
130
|
attributeName,
|
|
129
131
|
header
|
|
130
132
|
)
|
|
133
|
+
|
|
134
|
+
segment.addSpanAttribute(attributeName, header)
|
|
131
135
|
}
|
|
132
136
|
}
|
|
133
137
|
}
|
|
@@ -133,7 +133,7 @@ module.exports = function instrumentOutbound(agent, opts, makeRequest) {
|
|
|
133
133
|
if (parsed.parameters) {
|
|
134
134
|
// Scrub and parse returns on object with a null prototype.
|
|
135
135
|
for (let key in parsed.parameters) { // eslint-disable-line guard-for-in
|
|
136
|
-
segment.
|
|
136
|
+
segment.addSpanAttribute(`request.parameters.${key}`, parsed.parameters[key])
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
segment.addAttribute('url', `${proto}//${hostname}${parsed.path}`)
|
|
@@ -196,8 +196,8 @@ function handleError(segment, req, error) {
|
|
|
196
196
|
*/
|
|
197
197
|
function handleResponse(segment, hostname, req, res) {
|
|
198
198
|
// Add response attributes for spans
|
|
199
|
-
segment.
|
|
200
|
-
segment.
|
|
199
|
+
segment.addSpanAttribute('http.statusCode', res.statusCode)
|
|
200
|
+
segment.addSpanAttribute('http.statusText', res.statusMessage)
|
|
201
201
|
|
|
202
202
|
// If CAT is enabled, grab those headers!
|
|
203
203
|
const agent = segment.transaction.agent
|