newrelic 11.6.1 → 11.7.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 +26 -0
- package/index.js +2 -2
- package/lib/agent.js +3 -3
- package/lib/errors/index.js +3 -3
- package/lib/feature_flags.js +2 -1
- package/lib/header-attributes.js +1 -0
- package/lib/instrumentation/core/http-outbound.js +2 -6
- package/lib/instrumentation/core/http.js +4 -122
- package/lib/instrumentation/restify.js +19 -4
- package/lib/instrumentation/undici.js +2 -4
- package/lib/synthetics.js +255 -0
- package/lib/transaction/index.js +4 -5
- package/lib/util/attributes.js +1 -16
- package/lib/util/camel-case.js +26 -0
- package/lib/util/logger.js +1 -1
- package/lib/util/snake-case.js +25 -0
- package/package.json +2 -2
package/NEWS.md
CHANGED
|
@@ -1,3 +1,29 @@
|
|
|
1
|
+
### v11.7.0 (2023-12-14)
|
|
2
|
+
|
|
3
|
+
#### Features
|
|
4
|
+
|
|
5
|
+
* Added deserialized X-NewRelic-Synthetics-Info header to transaction and transaction trace intrinsic attributes ([#1912](https://github.com/newrelic/node-newrelic/pull/1912)) ([7ed64bd](https://github.com/newrelic/node-newrelic/commit/7ed64bd1410d7eed6c4dbf153e0a67754e7bcdef))
|
|
6
|
+
* The attributes in transactions are: `nr.syntheticsType`, `nr.syntheticsInitiator`, and `nr.synthetics<attributeKey(s)>`
|
|
7
|
+
* The attributes in transaction traces are: `synthetics_type`, `synthetics_initiator`, and `synthetics_<attribute_key(s)>`
|
|
8
|
+
* It will also include the `X-NewRelic-Synthetics-Info` header in outgoing http requests
|
|
9
|
+
* Added instrumentation for Restify async handlers ([#1910](https://github.com/newrelic/node-newrelic/pull/1910)) ([1a3f87f](https://github.com/newrelic/node-newrelic/commit/1a3f87ff10dea1c25b35c349550338f327446cb1))
|
|
10
|
+
|
|
11
|
+
#### Bug fixes
|
|
12
|
+
|
|
13
|
+
* Update import-in-the-middle to fix ESM on Node 18.19.0 ([#1906](https://github.com/newrelic/node-newrelic/pull/1906)) ([e9c3748](https://github.com/newrelic/node-newrelic/commit/e9c37487a606d986d3568c11a014f6397f4f379e))
|
|
14
|
+
|
|
15
|
+
#### Code refactoring
|
|
16
|
+
|
|
17
|
+
* change dynamic import to work with bundlers ([#1905](https://github.com/newrelic/node-newrelic/pull/1905)) ([ee81429](https://github.com/newrelic/node-newrelic/commit/ee81429f5c34491392ed79026e6f716c76e2d735))
|
|
18
|
+
|
|
19
|
+
#### Miscellaneous chores
|
|
20
|
+
|
|
21
|
+
* Add AWS Bedrock feature flag ([#1913](https://github.com/newrelic/node-newrelic/pull/1913)) ([91019b0](https://github.com/newrelic/node-newrelic/commit/91019b0a45dcf8399cd8eadbe4e111e72ceab512))
|
|
22
|
+
|
|
23
|
+
#### Continuous integration
|
|
24
|
+
|
|
25
|
+
* removed step in post release process to update an internal system with the latest agent version ([#1909](https://github.com/newrelic/node-newrelic/pull/1909)) ([2080b1f](https://github.com/newrelic/node-newrelic/commit/2080b1fe03e65a4c6aaa073e8e338835c708680d))
|
|
26
|
+
|
|
1
27
|
### v11.6.1 (2023-12-07)
|
|
2
28
|
|
|
3
29
|
#### Bug fixes
|
package/index.js
CHANGED
|
@@ -40,7 +40,7 @@ if (require.cache.__NR_cache) {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
function initApi({ agent, apiPath }) {
|
|
43
|
-
const API = require(apiPath)
|
|
43
|
+
const API = require(`./${apiPath}`)
|
|
44
44
|
|
|
45
45
|
const api = new API(agent)
|
|
46
46
|
require.cache.__NR_cache = module.exports = api
|
|
@@ -115,7 +115,7 @@ function initialize() {
|
|
|
115
115
|
/* eslint-enable no-console */
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
const api = agent ? initApi({ agent, apiPath: '
|
|
118
|
+
const api = agent ? initApi({ agent, apiPath: 'api' }) : initApi({ apiPath: 'stub_api' })
|
|
119
119
|
|
|
120
120
|
// If we loaded an agent, record a startup time for the agent.
|
|
121
121
|
// NOTE: Metrics are recorded in seconds, so divide the value by 1000.
|
package/lib/agent.js
CHANGED
|
@@ -36,9 +36,9 @@ const {
|
|
|
36
36
|
addRequiredCATAttributes,
|
|
37
37
|
maybeAddExtraCATAttributes,
|
|
38
38
|
maybeAddParentAttributes,
|
|
39
|
-
maybeAddQueueAttributes
|
|
40
|
-
maybeAddSyntheticAttributes
|
|
39
|
+
maybeAddQueueAttributes
|
|
41
40
|
} = require('./util/attributes')
|
|
41
|
+
const synthetics = require('./synthetics')
|
|
42
42
|
|
|
43
43
|
// Map of valid states to whether or not data collection is valid
|
|
44
44
|
const STATES = {
|
|
@@ -747,7 +747,7 @@ function _addIntrinsicAttrsFromTransaction(transaction) {
|
|
|
747
747
|
maybeAddQueueAttributes(transaction, intrinsicAttributes)
|
|
748
748
|
maybeAddExternalAttributes(transaction, intrinsicAttributes)
|
|
749
749
|
maybeAddDatabaseAttributes(transaction, intrinsicAttributes)
|
|
750
|
-
|
|
750
|
+
synthetics.assignTransactionAttrs(transaction, intrinsicAttributes)
|
|
751
751
|
|
|
752
752
|
if (this.config.distributed_tracing.enabled) {
|
|
753
753
|
transaction.addDistributedTraceIntrinsics(intrinsicAttributes)
|
package/lib/errors/index.js
CHANGED
|
@@ -13,9 +13,9 @@ const errorHelper = require('../errors/helper')
|
|
|
13
13
|
const {
|
|
14
14
|
maybeAddQueueAttributes,
|
|
15
15
|
maybeAddExternalAttributes,
|
|
16
|
-
maybeAddDatabaseAttributes
|
|
17
|
-
maybeAddSyntheticAttributes
|
|
16
|
+
maybeAddDatabaseAttributes
|
|
18
17
|
} = require('../util/attributes')
|
|
18
|
+
const synthetics = require('../synthetics')
|
|
19
19
|
const Transaction = require('../transaction')
|
|
20
20
|
const ERROR_EXPECTED_PATH = 'error.expected'
|
|
21
21
|
|
|
@@ -230,7 +230,7 @@ function _getErrorEventIntrinsicAttrs(transaction, errorClass, message, expected
|
|
|
230
230
|
maybeAddQueueAttributes(transaction, attributes)
|
|
231
231
|
maybeAddExternalAttributes(transaction, attributes)
|
|
232
232
|
maybeAddDatabaseAttributes(transaction, attributes)
|
|
233
|
-
|
|
233
|
+
synthetics.assignTransactionAttrs(transaction, attributes)
|
|
234
234
|
|
|
235
235
|
if (transaction.agent.config.distributed_tracing.enabled) {
|
|
236
236
|
transaction.addDistributedTraceIntrinsics(attributes)
|
package/lib/feature_flags.js
CHANGED
|
@@ -12,7 +12,8 @@ exports.prerelease = {
|
|
|
12
12
|
reverse_naming_rules: false,
|
|
13
13
|
undici_async_tracking: true,
|
|
14
14
|
unresolved_promise_cleanup: true,
|
|
15
|
-
legacy_context_manager: false
|
|
15
|
+
legacy_context_manager: false,
|
|
16
|
+
aws_bedrock_instrumentation: false
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
// flags that are no longer used for released features
|
package/lib/header-attributes.js
CHANGED
|
@@ -60,6 +60,7 @@ const HEADER_ATTR_NAMES = {
|
|
|
60
60
|
'x-newrelic-app-data': 'xNewrelicAppData',
|
|
61
61
|
'x-newrelic-id': 'xNewrelicId',
|
|
62
62
|
'x-newrelic-synthetics': 'xNewrelicSynthetics',
|
|
63
|
+
'x-newrelic-synthetics-info': 'xNewrelicSyntheticsInfo',
|
|
63
64
|
'x-newrelic-transaction': 'xNewrelicTransaction',
|
|
64
65
|
'x-powered-by': 'xPoweredBy',
|
|
65
66
|
'x-queue-start': 'xQueueStart',
|
|
@@ -14,6 +14,7 @@ const url = require('url')
|
|
|
14
14
|
const copy = require('../../util/copy')
|
|
15
15
|
const symbols = require('../../symbols')
|
|
16
16
|
const http = require('http')
|
|
17
|
+
const synthetics = require('../../synthetics')
|
|
17
18
|
|
|
18
19
|
const NAMES = require('../../metrics/names')
|
|
19
20
|
|
|
@@ -21,8 +22,6 @@ const DEFAULT_HOST = 'localhost'
|
|
|
21
22
|
const DEFAULT_HTTP_PORT = 80
|
|
22
23
|
const DEFAULT_SSL_PORT = 443
|
|
23
24
|
|
|
24
|
-
const NEWRELIC_SYNTHETICS_HEADER = 'x-newrelic-synthetics'
|
|
25
|
-
|
|
26
25
|
/**
|
|
27
26
|
* Determines the default port to 80 if protocol is undefined or http:
|
|
28
27
|
* Otherwise it assigns it as 443
|
|
@@ -139,10 +138,7 @@ function instrumentRequest(agent, opts, makeRequest, hostname, segment) {
|
|
|
139
138
|
const transaction = segment.transaction
|
|
140
139
|
const outboundHeaders = Object.create(null)
|
|
141
140
|
|
|
142
|
-
|
|
143
|
-
outboundHeaders[NEWRELIC_SYNTHETICS_HEADER] = transaction.syntheticsHeader
|
|
144
|
-
}
|
|
145
|
-
|
|
141
|
+
synthetics.assignHeadersToOutgoingRequest(agent.config, transaction, outboundHeaders)
|
|
146
142
|
maybeAddDtCatHeaders(agent, transaction, outboundHeaders, opts?.headers)
|
|
147
143
|
opts.headers = assignOutgoingHeaders(opts.headers, outboundHeaders)
|
|
148
144
|
|
|
@@ -10,26 +10,19 @@
|
|
|
10
10
|
const shimmer = require('../../shimmer')
|
|
11
11
|
const logger = require('../../logger').child({ component: 'http' })
|
|
12
12
|
const recordWeb = require('../../metrics/recorders/http')
|
|
13
|
-
const hashes = require('../../util/hashes')
|
|
14
13
|
const cat = require('../../util/cat')
|
|
15
14
|
const instrumentOutbound = require('./http-outbound')
|
|
16
15
|
const url = require('url')
|
|
17
16
|
const urltils = require('../../util/urltils')
|
|
18
17
|
const headerAttributes = require('../../header-attributes')
|
|
19
18
|
const headerProcessing = require('../../header-processing')
|
|
19
|
+
const synthetics = require('../../synthetics')
|
|
20
20
|
|
|
21
21
|
const NAMES = require('../../metrics/names')
|
|
22
22
|
const DESTS = require('../../config/attribute-filter').DESTINATIONS
|
|
23
23
|
|
|
24
24
|
const symbols = require('../../symbols')
|
|
25
25
|
|
|
26
|
-
/*
|
|
27
|
-
*
|
|
28
|
-
* CONSTANTS
|
|
29
|
-
*
|
|
30
|
-
*/
|
|
31
|
-
const NEWRELIC_SYNTHETICS_HEADER = 'x-newrelic-synthetics'
|
|
32
|
-
|
|
33
26
|
// For incoming requests this instrumentation functions by wrapping
|
|
34
27
|
// `http.createServer` and `http.Server#addListener`. The former merely sets the
|
|
35
28
|
// agent dispatcher to 'http' and the latter wraps any event handlers bound to
|
|
@@ -119,16 +112,7 @@ function wrapEmitWithTransaction(agent, emit, isHTTPS) {
|
|
|
119
112
|
transaction.queueTime = Date.now() - queueTimeStamp
|
|
120
113
|
}
|
|
121
114
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if (synthHeader && agent.config.trusted_account_ids && agent.config.encoding_key) {
|
|
125
|
-
handleSyntheticsHeader(
|
|
126
|
-
synthHeader,
|
|
127
|
-
agent.config.encoding_key,
|
|
128
|
-
agent.config.trusted_account_ids,
|
|
129
|
-
transaction
|
|
130
|
-
)
|
|
131
|
-
}
|
|
115
|
+
synthetics.assignHeadersToTransaction(agent.config, transaction, request.headers)
|
|
132
116
|
|
|
133
117
|
if (agent.config.distributed_tracing.enabled) {
|
|
134
118
|
// Node http headers are automatically lowercase
|
|
@@ -278,9 +262,8 @@ function wrapWriteHead(agent, writeHead) {
|
|
|
278
262
|
logger.trace('No transaction - not adding response CAT headers')
|
|
279
263
|
return writeHead.apply(this, arguments)
|
|
280
264
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
}
|
|
265
|
+
|
|
266
|
+
synthetics.assignHeadersToResponse(this, transaction)
|
|
284
267
|
|
|
285
268
|
if (!transaction.incomingCatId) {
|
|
286
269
|
logger.trace('No incoming CAT ID - not adding response CAT headers')
|
|
@@ -487,104 +470,3 @@ module.exports = function initialize(agent, http, moduleName) {
|
|
|
487
470
|
}
|
|
488
471
|
)
|
|
489
472
|
}
|
|
490
|
-
|
|
491
|
-
/**
|
|
492
|
-
* Take the X-NewRelic-Synthetics header and apply any appropriate data to the
|
|
493
|
-
* transaction for later use. This is the gate keeper for attributes being
|
|
494
|
-
* added onto the transaction object for synthetics.
|
|
495
|
-
*
|
|
496
|
-
* @param {string} header - The raw X-NewRelic-Synthetics header
|
|
497
|
-
* @param {string} encKey - Encoding key handed down from the server
|
|
498
|
-
* @param {Array.<number>} trustedIds - Array of accounts to trust the header from.
|
|
499
|
-
* @param {object} transaction - Where the synthetics data is attached to.
|
|
500
|
-
*/
|
|
501
|
-
function handleSyntheticsHeader(header, encKey, trustedIds, transaction) {
|
|
502
|
-
const synthData = parseSyntheticsHeader(header, encKey, trustedIds)
|
|
503
|
-
if (!synthData) {
|
|
504
|
-
return
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
transaction.syntheticsData = synthData
|
|
508
|
-
transaction.syntheticsHeader = header
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
/**
|
|
512
|
-
* Parse out and verify the the pieces of the X-NewRelic-Synthetics header.
|
|
513
|
-
*
|
|
514
|
-
* @param {string} header - The raw X-NewRelic-Synthetics header
|
|
515
|
-
* @param {string} encKey - Encoding key handed down from the server
|
|
516
|
-
* @param {Array.<number>} trustedIds - Array of accounts to trust the header from.
|
|
517
|
-
* @returns {object | null} - On successful parse and verification an object of
|
|
518
|
-
* synthetics data is returned, otherwise null is
|
|
519
|
-
* returned.
|
|
520
|
-
*/
|
|
521
|
-
function parseSyntheticsHeader(header, encKey, trustedIds) {
|
|
522
|
-
// Eagerly declare this object because we know what it should look like and
|
|
523
|
-
// can use that for header verification.
|
|
524
|
-
const parsedData = {
|
|
525
|
-
version: null,
|
|
526
|
-
accountId: null,
|
|
527
|
-
resourceId: null,
|
|
528
|
-
jobId: null,
|
|
529
|
-
monitorId: null
|
|
530
|
-
}
|
|
531
|
-
let synthData = null
|
|
532
|
-
try {
|
|
533
|
-
synthData = JSON.parse(hashes.deobfuscateNameUsingKey(header, encKey))
|
|
534
|
-
} catch (e) {
|
|
535
|
-
logger.trace(e, 'Got unparsable synthetics header: %s', header)
|
|
536
|
-
return
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
if (!Array.isArray(synthData)) {
|
|
540
|
-
logger.trace('Synthetics data is not an array: %s (%s)', synthData, typeof synthData)
|
|
541
|
-
return
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
if (synthData.length < Object.keys(parsedData).length) {
|
|
545
|
-
logger.trace(
|
|
546
|
-
'Synthetics header length is %s, expected at least %s',
|
|
547
|
-
synthData.length,
|
|
548
|
-
Object.keys(parsedData).length
|
|
549
|
-
)
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
parsedData.version = synthData[0]
|
|
553
|
-
if (parsedData.version !== 1) {
|
|
554
|
-
logger.trace('Synthetics header version is not 1, got: %s (%s)', parsedData.version, synthData)
|
|
555
|
-
return
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
parsedData.accountId = synthData[1]
|
|
559
|
-
if (parsedData.accountId) {
|
|
560
|
-
if (trustedIds.indexOf(parsedData.accountId) === -1) {
|
|
561
|
-
logger.trace(
|
|
562
|
-
'Synthetics header account ID is not in trusted account IDs: %s (%s)',
|
|
563
|
-
parsedData.accountId,
|
|
564
|
-
trustedIds
|
|
565
|
-
)
|
|
566
|
-
return
|
|
567
|
-
}
|
|
568
|
-
} else {
|
|
569
|
-
logger.trace('Synthetics header account ID missing.')
|
|
570
|
-
return
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
parsedData.resourceId = synthData[2]
|
|
574
|
-
if (!parsedData.resourceId) {
|
|
575
|
-
logger.trace('Synthetics resource ID is missing.')
|
|
576
|
-
return
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
parsedData.jobId = synthData[3]
|
|
580
|
-
if (!parsedData.jobId) {
|
|
581
|
-
logger.trace('Synthetics job ID is missing.')
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
parsedData.monitorId = synthData[4]
|
|
585
|
-
if (!parsedData.monitorId) {
|
|
586
|
-
logger.trace('Synthetics monitor ID is missing.')
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
return parsedData
|
|
590
|
-
}
|
|
@@ -55,12 +55,20 @@ module.exports = function initialize(agent, restify, moduleName, shim) {
|
|
|
55
55
|
if (shim.isWrapped(middleware)) {
|
|
56
56
|
return middleware
|
|
57
57
|
}
|
|
58
|
-
|
|
58
|
+
const spec = {
|
|
59
59
|
matchArity: true,
|
|
60
60
|
route,
|
|
61
61
|
req: shim.FIRST,
|
|
62
62
|
next: shim.LAST
|
|
63
|
-
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const wrappedMw = shim.recordMiddleware(middleware, spec)
|
|
66
|
+
if (middleware.constructor.name === 'AsyncFunction') {
|
|
67
|
+
return async function asyncShim() {
|
|
68
|
+
return wrappedMw.apply(this, arguments)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return wrappedMw
|
|
64
72
|
}
|
|
65
73
|
})
|
|
66
74
|
|
|
@@ -70,11 +78,18 @@ module.exports = function initialize(agent, restify, moduleName, shim) {
|
|
|
70
78
|
if (shim.isWrapped(middleware)) {
|
|
71
79
|
return middleware
|
|
72
80
|
}
|
|
73
|
-
|
|
81
|
+
const spec = {
|
|
74
82
|
matchArity: true,
|
|
75
83
|
req: shim.FIRST,
|
|
76
84
|
next: shim.LAST
|
|
77
|
-
}
|
|
85
|
+
}
|
|
86
|
+
const wrappedMw = shim.recordMiddleware(middleware, spec)
|
|
87
|
+
if (middleware.constructor.name === 'AsyncFunction') {
|
|
88
|
+
return async function asyncShim() {
|
|
89
|
+
return wrappedMw.apply(this, arguments)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return wrappedMw
|
|
78
93
|
})
|
|
79
94
|
}
|
|
80
95
|
}
|
|
@@ -9,10 +9,10 @@ const cat = require('../util/cat')
|
|
|
9
9
|
const recordExternal = require('../metrics/recorders/http_external')
|
|
10
10
|
const logger = require('../logger').child({ component: 'undici' })
|
|
11
11
|
const NAMES = require('../metrics/names')
|
|
12
|
-
const NEWRELIC_SYNTHETICS_HEADER = 'x-newrelic-synthetics'
|
|
13
12
|
const symbols = require('../symbols')
|
|
14
13
|
const { executionAsyncResource } = require('async_hooks')
|
|
15
14
|
const diagnosticsChannel = require('diagnostics_channel')
|
|
15
|
+
const synthetics = require('../synthetics')
|
|
16
16
|
|
|
17
17
|
const channels = [
|
|
18
18
|
{ channel: diagnosticsChannel.channel('undici:request:create'), hook: requestCreateHook },
|
|
@@ -89,9 +89,7 @@ function getParentSegment(shim) {
|
|
|
89
89
|
*/
|
|
90
90
|
function addDTHeaders({ transaction, config, request }) {
|
|
91
91
|
const outboundHeaders = Object.create(null)
|
|
92
|
-
|
|
93
|
-
outboundHeaders[NEWRELIC_SYNTHETICS_HEADER] = transaction.syntheticsHeader
|
|
94
|
-
}
|
|
92
|
+
synthetics.assignHeadersToOutgoingRequest(config, transaction, outboundHeaders)
|
|
95
93
|
|
|
96
94
|
if (config.distributed_tracing.enabled) {
|
|
97
95
|
transaction.insertDistributedTraceHeaders(outboundHeaders)
|
|
@@ -0,0 +1,255 @@
|
|
|
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: 'synthetics' })
|
|
8
|
+
const hashes = require('./util/hashes')
|
|
9
|
+
const { isNotEmpty } = require('./util/objects')
|
|
10
|
+
const toSnakeCase = require('./util/snake-case')
|
|
11
|
+
const toCamelCase = require('./util/camel-case')
|
|
12
|
+
const synthetics = module.exports
|
|
13
|
+
const NEWRELIC_SYNTHETICS_HEADER = 'x-newrelic-synthetics'
|
|
14
|
+
const NEWRELIC_SYNTHETICS_INFO_HEADER = 'x-newrelic-synthetics-info'
|
|
15
|
+
|
|
16
|
+
const KEYS = ['version', 'accountId', 'resourceId', 'jobId', 'monitorId']
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Decode the X-NewRelic-Synthetics and X-NewRelic-Synthetics-Info headers
|
|
20
|
+
* and assign to transactions as properties both the raw and decoded values
|
|
21
|
+
*
|
|
22
|
+
* @param {object} config agent config
|
|
23
|
+
* @param {object} transaction The current transaction
|
|
24
|
+
* @param {object} headers raw http headers
|
|
25
|
+
*/
|
|
26
|
+
synthetics.assignHeadersToTransaction = function processHeaders(config, transaction, headers) {
|
|
27
|
+
const synthHeader = headers[NEWRELIC_SYNTHETICS_HEADER]
|
|
28
|
+
const synthInfoHeader = headers[NEWRELIC_SYNTHETICS_INFO_HEADER]
|
|
29
|
+
|
|
30
|
+
if (synthHeader && config.trusted_account_ids && config.encoding_key) {
|
|
31
|
+
assignSyntheticsHeader(
|
|
32
|
+
synthHeader,
|
|
33
|
+
config.encoding_key,
|
|
34
|
+
config.trusted_account_ids,
|
|
35
|
+
transaction
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (synthInfoHeader && config.encoding_key) {
|
|
40
|
+
assignSyntheticsInfoHeader(synthInfoHeader, config.encoding_key, transaction)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Take the X-NewRelic-Synthetics header and apply any appropriate data to the
|
|
46
|
+
* transaction for later use. This is the gate keeper for attributes being
|
|
47
|
+
* added onto the transaction object for synthetics.
|
|
48
|
+
*
|
|
49
|
+
* @param {string} header - The raw X-NewRelic-Synthetics header
|
|
50
|
+
* @param {string} encKey - Encoding key handed down from the server
|
|
51
|
+
* @param {Array.<number>} trustedIds - Array of accounts to trust the header from.
|
|
52
|
+
* @param {object} transaction - Where the synthetics data is attached to.
|
|
53
|
+
*/
|
|
54
|
+
function assignSyntheticsHeader(header, encKey, trustedIds, transaction) {
|
|
55
|
+
const synthData = parseSyntheticsHeader(header, encKey, trustedIds)
|
|
56
|
+
if (!synthData) {
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
transaction.syntheticsData = synthData
|
|
61
|
+
transaction.syntheticsHeader = header
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Take the X-NewRelic-Synthetics-Info header and apply any appropriate data to the
|
|
66
|
+
* transaction for later use. This is the gate keeper for attributes being
|
|
67
|
+
* added onto the transaction object for synthetics info.
|
|
68
|
+
*
|
|
69
|
+
* @param {string} header - The raw X-NewRelic-Synthetics-Info header
|
|
70
|
+
* @param {string} encKey - Encoding key handed down from the server
|
|
71
|
+
* @param {object} transaction - Where the synthetics data is attached to.
|
|
72
|
+
*/
|
|
73
|
+
function assignSyntheticsInfoHeader(header, encKey, transaction) {
|
|
74
|
+
const synthInfoData = parseSyntheticsInfoHeader(header, encKey)
|
|
75
|
+
if (!synthInfoData) {
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
transaction.syntheticsInfoData = synthInfoData
|
|
80
|
+
transaction.syntheticsInfoHeader = header
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Parse out and verify the the pieces of the X-NewRelic-Synthetics-Info header.
|
|
85
|
+
*
|
|
86
|
+
* @param {string} header - The raw X-NewRelic-Synthetics-Info header
|
|
87
|
+
* @param {string} encKey - Encoding key handed down from the server
|
|
88
|
+
* @returns {object | null} - On successful parse and verification an object of
|
|
89
|
+
* synthetics info data is returned, otherwise null is
|
|
90
|
+
* returned.
|
|
91
|
+
*/
|
|
92
|
+
function parseSyntheticsInfoHeader(header, encKey) {
|
|
93
|
+
let synthInfoData = null
|
|
94
|
+
try {
|
|
95
|
+
synthInfoData = JSON.parse(hashes.deobfuscateNameUsingKey(header, encKey))
|
|
96
|
+
logger.trace('Parsed synthetics info header: %s', synthInfoData)
|
|
97
|
+
} catch (e) {
|
|
98
|
+
logger.trace(e, 'Cannot parse synthetics info header: %s', header)
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!isNotEmpty(synthInfoData)) {
|
|
103
|
+
logger.trace('Synthetics info data is not an object.')
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const { version } = synthInfoData
|
|
108
|
+
|
|
109
|
+
if (version !== 1) {
|
|
110
|
+
logger.trace('Synthetics info header version is not 1, got: %s', version)
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return synthInfoData
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Parse out and verify the the pieces of the X-NewRelic-Synthetics header.
|
|
119
|
+
*
|
|
120
|
+
* @param {string} header - The raw X-NewRelic-Synthetics header
|
|
121
|
+
* @param {string} encKey - Encoding key handed down from the server
|
|
122
|
+
* @param {Array.<number>} trustedIds - Array of accounts to trust the header from.
|
|
123
|
+
* @returns {object | null} - On successful parse and verification an object of
|
|
124
|
+
* synthetics data is returned, otherwise null is
|
|
125
|
+
* returned.
|
|
126
|
+
*/
|
|
127
|
+
function parseSyntheticsHeader(header, encKey, trustedIds) {
|
|
128
|
+
let synthData = null
|
|
129
|
+
try {
|
|
130
|
+
synthData = JSON.parse(hashes.deobfuscateNameUsingKey(header, encKey))
|
|
131
|
+
logger.trace('Parsed synthetics header: %s', synthData)
|
|
132
|
+
} catch (e) {
|
|
133
|
+
logger.trace(e, 'Cannot parse synthetics header: %s', header)
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!Array.isArray(synthData)) {
|
|
138
|
+
logger.trace('Synthetics data is not an array.')
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (synthData.length < KEYS.length) {
|
|
143
|
+
logger.trace(
|
|
144
|
+
'Synthetics header length is %s, expected at least %s',
|
|
145
|
+
synthData.length,
|
|
146
|
+
KEYS.length
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const [version, accountId, resourceId, jobId, monitorId] = synthData
|
|
151
|
+
|
|
152
|
+
if (version !== 1) {
|
|
153
|
+
logger.trace('Synthetics header version is not 1, got: %s', version)
|
|
154
|
+
return
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (accountId && !trustedIds.includes(accountId)) {
|
|
158
|
+
logger.trace(
|
|
159
|
+
'Synthetics header account ID is not in trusted account IDs: %s (%s)',
|
|
160
|
+
accountId,
|
|
161
|
+
trustedIds.toString()
|
|
162
|
+
)
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
version,
|
|
168
|
+
accountId,
|
|
169
|
+
resourceId,
|
|
170
|
+
jobId,
|
|
171
|
+
monitorId
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Helper method for adding relevant synthetics intrinsics to transaction traces
|
|
177
|
+
*
|
|
178
|
+
* @param {object} transaction The current transaction
|
|
179
|
+
*/
|
|
180
|
+
synthetics.assignIntrinsicsToTransaction = function assignIntrinsicsToTransaction(transaction) {
|
|
181
|
+
if (transaction.syntheticsData) {
|
|
182
|
+
transaction._intrinsicAttributes.synthetics_resource_id = transaction.syntheticsData?.resourceId
|
|
183
|
+
transaction._intrinsicAttributes.synthetics_job_id = transaction.syntheticsData?.jobId
|
|
184
|
+
transaction._intrinsicAttributes.synthetics_monitor_id = transaction.syntheticsData?.monitorId
|
|
185
|
+
|
|
186
|
+
if (transaction.syntheticsInfoData) {
|
|
187
|
+
transaction._intrinsicAttributes.synthetics_type = transaction.syntheticsInfoData?.type
|
|
188
|
+
transaction._intrinsicAttributes.synthetics_initiator =
|
|
189
|
+
transaction.syntheticsInfoData?.initiator
|
|
190
|
+
|
|
191
|
+
for (const [key, value] of Object.entries(transaction.syntheticsInfoData.attributes)) {
|
|
192
|
+
transaction._intrinsicAttributes[`synthetics_${toSnakeCase(key)}`] = value
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Helper method for modifying attributes by reference if transaction has Synthetics metrics
|
|
200
|
+
*
|
|
201
|
+
* @param {object} transaction The current transaction
|
|
202
|
+
* @param {object} attributes The attributes object to modify (by reference)
|
|
203
|
+
*/
|
|
204
|
+
synthetics.assignTransactionAttrs = function assignTransactionAttrs(transaction, attributes) {
|
|
205
|
+
if (transaction.syntheticsData) {
|
|
206
|
+
attributes['nr.syntheticsResourceId'] = transaction.syntheticsData?.resourceId
|
|
207
|
+
attributes['nr.syntheticsJobId'] = transaction.syntheticsData?.jobId
|
|
208
|
+
attributes['nr.syntheticsMonitorId'] = transaction.syntheticsData?.monitorId
|
|
209
|
+
|
|
210
|
+
if (transaction.syntheticsInfoData) {
|
|
211
|
+
attributes['nr.syntheticsType'] = transaction.syntheticsInfoData?.type
|
|
212
|
+
attributes['nr.syntheticsInitiator'] = transaction.syntheticsInfoData?.initiator
|
|
213
|
+
for (const [key, value] of Object.entries(transaction.syntheticsInfoData.attributes)) {
|
|
214
|
+
const attr = toCamelCase(`synthetics_${key}`)
|
|
215
|
+
attributes[`nr.${attr}`] = value
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Helper method for assign the X-NewRelic-Synthetics and X-NewRelic-Synthetics-Info headers to outbound http requests
|
|
223
|
+
*
|
|
224
|
+
* @param {object} config agent config
|
|
225
|
+
* @param {object} transaction The current transaction
|
|
226
|
+
* @param {object} headers outgoing headers
|
|
227
|
+
*/
|
|
228
|
+
synthetics.assignHeadersToOutgoingRequest = function addHeadersToOutgoingRequest(
|
|
229
|
+
config,
|
|
230
|
+
transaction,
|
|
231
|
+
headers
|
|
232
|
+
) {
|
|
233
|
+
if (config.encoding_key && transaction.syntheticsHeader) {
|
|
234
|
+
headers[NEWRELIC_SYNTHETICS_HEADER] = transaction.syntheticsHeader
|
|
235
|
+
|
|
236
|
+
if (transaction.syntheticsInfoHeader) {
|
|
237
|
+
headers[NEWRELIC_SYNTHETICS_INFO_HEADER] = transaction.syntheticsInfoHeader
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Helper method for assigning the X-NewRelic-Synthetics and X-NewRelic-Synthetics-Info headers to the response
|
|
244
|
+
*
|
|
245
|
+
* @param {object} response http response object
|
|
246
|
+
* @param {object} transaction The current transaction
|
|
247
|
+
*/
|
|
248
|
+
synthetics.assignHeadersToResponse = function assignHeadersToResponse(response, transaction) {
|
|
249
|
+
if (transaction.syntheticsHeader) {
|
|
250
|
+
response.setHeader(NEWRELIC_SYNTHETICS_HEADER, transaction.syntheticsHeader)
|
|
251
|
+
if (transaction.syntheticsInfoHeader) {
|
|
252
|
+
response.setHeader(NEWRELIC_SYNTHETICS_INFO_HEADER, transaction.syntheticsInfoHeader)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
package/lib/transaction/index.js
CHANGED
|
@@ -14,6 +14,7 @@ const NameState = require('./name-state')
|
|
|
14
14
|
const props = require('../util/properties')
|
|
15
15
|
const Timer = require('../timer')
|
|
16
16
|
const Trace = require('./trace')
|
|
17
|
+
const synthetics = require('../synthetics')
|
|
17
18
|
const url = require('url')
|
|
18
19
|
const urltils = require('../util/urltils')
|
|
19
20
|
const TraceContext = require('./tracecontext').TraceContext
|
|
@@ -120,7 +121,9 @@ function Transaction(agent) {
|
|
|
120
121
|
this.invalidIncomingExternalTransaction = false
|
|
121
122
|
this.statusCode = null
|
|
122
123
|
this.syntheticsHeader = null
|
|
124
|
+
this.syntheticsInfoHeader = null
|
|
123
125
|
this.syntheticsData = null
|
|
126
|
+
this.syntheticsInfoData = null
|
|
124
127
|
this.url = null
|
|
125
128
|
this.parsedUrl = null
|
|
126
129
|
this.verb = null
|
|
@@ -916,11 +919,7 @@ Transaction.prototype.getIntrinsicAttributes = function getIntrinsicAttributes()
|
|
|
916
919
|
}
|
|
917
920
|
}
|
|
918
921
|
|
|
919
|
-
|
|
920
|
-
this._intrinsicAttributes.synthetics_resource_id = this.syntheticsData.resourceId
|
|
921
|
-
this._intrinsicAttributes.synthetics_job_id = this.syntheticsData.jobId
|
|
922
|
-
this._intrinsicAttributes.synthetics_monitor_id = this.syntheticsData.monitorId
|
|
923
|
-
}
|
|
922
|
+
synthetics.assignIntrinsicsToTransaction(this)
|
|
924
923
|
}
|
|
925
924
|
return Object.assign(Object.create(null), this._intrinsicAttributes)
|
|
926
925
|
}
|
package/lib/util/attributes.js
CHANGED
|
@@ -134,26 +134,11 @@ function calculateApdexZone(duration, apdexT) {
|
|
|
134
134
|
return 'F' // frustrated
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
/**
|
|
138
|
-
* Helper method for modifying attributes by reference if transaction has Synthetics metrics
|
|
139
|
-
*
|
|
140
|
-
* @param {object} transaction The current transaction
|
|
141
|
-
* @param {object} attributes The attributes object to modify (by reference)
|
|
142
|
-
*/
|
|
143
|
-
function maybeAddSyntheticAttributes(transaction, attributes) {
|
|
144
|
-
if (transaction.syntheticsData) {
|
|
145
|
-
attributes['nr.syntheticsResourceId'] = transaction.syntheticsData.resourceId
|
|
146
|
-
attributes['nr.syntheticsJobId'] = transaction.syntheticsData.jobId
|
|
147
|
-
attributes['nr.syntheticsMonitorId'] = transaction.syntheticsData.monitorId
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
137
|
module.exports = {
|
|
152
138
|
maybeAddQueueAttributes,
|
|
153
139
|
maybeAddExternalAttributes,
|
|
154
140
|
maybeAddDatabaseAttributes,
|
|
155
141
|
maybeAddParentAttributes,
|
|
156
142
|
addRequiredCATAttributes,
|
|
157
|
-
maybeAddExtraCATAttributes
|
|
158
|
-
maybeAddSyntheticAttributes
|
|
143
|
+
maybeAddExtraCATAttributes
|
|
159
144
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2023 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
// lodash ripoff: https://github.com/Tcdian/Lodash/blob/master/source/string/words.ts
|
|
8
|
+
const wordPattern = new RegExp(
|
|
9
|
+
['[A-Z][a-z]+', '[A-Z]+(?=[A-Z][a-z])', '[A-Z]+', '[a-z]+', '[0-9]+'].join('|'),
|
|
10
|
+
'g'
|
|
11
|
+
)
|
|
12
|
+
const words = (string) => string.match(wordPattern) || []
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Converts a string to camelCase
|
|
16
|
+
*
|
|
17
|
+
* @param {string} string value to convert to camel case
|
|
18
|
+
* @returns {string} camel cased string
|
|
19
|
+
*/
|
|
20
|
+
module.exports = function toCamelCase(string) {
|
|
21
|
+
return words(string)
|
|
22
|
+
.map((word, index) =>
|
|
23
|
+
index === 0 ? word.toLowerCase() : word.slice(0, 1).toUpperCase() + word.slice(1)
|
|
24
|
+
)
|
|
25
|
+
.join('')
|
|
26
|
+
}
|
package/lib/util/logger.js
CHANGED
|
@@ -261,7 +261,7 @@ Logger.prototype.write = function write(level, args, extra) {
|
|
|
261
261
|
data = stringify(entry) + '\n'
|
|
262
262
|
} catch (err) {
|
|
263
263
|
// eslint-disable-line no-unused-vars
|
|
264
|
-
this.debug('
|
|
264
|
+
this.debug('Unable to stringify log message')
|
|
265
265
|
}
|
|
266
266
|
|
|
267
267
|
if (this.reading) {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2023 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
// lodash ripoff: https://github.com/Tcdian/Lodash/blob/master/source/string/words.ts
|
|
8
|
+
const wordPattern = new RegExp(
|
|
9
|
+
['[A-Z][a-z]+', '[A-Z]+(?=[A-Z][a-z])', '[A-Z]+', '[a-z]+', '[0-9]+'].join('|'),
|
|
10
|
+
'g'
|
|
11
|
+
)
|
|
12
|
+
const words = (string) => string.match(wordPattern) || []
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Converts a string to snake_case
|
|
16
|
+
*
|
|
17
|
+
* @param {string} string value to convert to snake case
|
|
18
|
+
* @returns {string} snake cased string
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
module.exports = function toSnakeCase(string) {
|
|
22
|
+
return words(string)
|
|
23
|
+
.map((word) => word.toLowerCase())
|
|
24
|
+
.join('_')
|
|
25
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "newrelic",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.7.0",
|
|
4
4
|
"author": "New Relic Node.js agent team <nodejs@newrelic.com>",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"contributors": [
|
|
@@ -190,7 +190,7 @@
|
|
|
190
190
|
"@tyriar/fibonacci-heap": "^2.0.7",
|
|
191
191
|
"concat-stream": "^2.0.0",
|
|
192
192
|
"https-proxy-agent": "^7.0.1",
|
|
193
|
-
"import-in-the-middle": "^1.
|
|
193
|
+
"import-in-the-middle": "^1.6.0",
|
|
194
194
|
"json-bigint": "^1.0.0",
|
|
195
195
|
"json-stringify-safe": "^5.0.0",
|
|
196
196
|
"module-details-from-path": "^1.0.3",
|