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 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: './api' }) : initApi({ apiPath: './stub_api' })
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
- maybeAddSyntheticAttributes(transaction, intrinsicAttributes)
750
+ synthetics.assignTransactionAttrs(transaction, intrinsicAttributes)
751
751
 
752
752
  if (this.config.distributed_tracing.enabled) {
753
753
  transaction.addDistributedTraceIntrinsics(intrinsicAttributes)
@@ -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
- maybeAddSyntheticAttributes(transaction, attributes)
233
+ synthetics.assignTransactionAttrs(transaction, attributes)
234
234
 
235
235
  if (transaction.agent.config.distributed_tracing.enabled) {
236
236
  transaction.addDistributedTraceIntrinsics(attributes)
@@ -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
@@ -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
- if (agent.config.encoding_key && transaction.syntheticsHeader) {
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
- const synthHeader = request.headers[NEWRELIC_SYNTHETICS_HEADER]
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
- if (transaction.syntheticsHeader) {
282
- this.setHeader(NEWRELIC_SYNTHETICS_HEADER, transaction.syntheticsHeader)
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
- return shim.recordMiddleware(middleware, {
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
- return shim.recordMiddleware(middleware, {
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
- if (config.encoding_key && transaction.syntheticsHeader) {
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
+ }
@@ -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
- if (this.syntheticsData) {
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
  }
@@ -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
+ }
@@ -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('Unabled to stringify log message')
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.6.1",
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.4.2",
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",