newrelic 9.14.0 → 9.15.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,11 @@
1
+ ### v9.15.0 (2023-04-04)
2
+
3
+ * Added a new configuration option `heroku.use_dyno_names` to specify whether or not to use `process.env.DYNO` for naming the host name and display host. This option defaults to true. If you are on heroku and do not want this functionality set `heroku.use_dyno_names` to `false`. You can also control this configuration options with the environment variable of `NEW_RELIC_HEROKU_USE_DYNO_NAMES`. Thanks @benney-au-le for your contribution 🚀
4
+
5
+ ### v9.14.1 (2023-03-23)
6
+
7
+ * Restored assigning loaded version of agent to require.cache as `__NR_cache` instead of a symbol to properly detect attempts at loading agent twice.
8
+
1
9
  ### v9.14.0 (2023-03-23)
2
10
 
3
11
  * Added new API function called `setErrorGroupCallback`, which provides a way for you to customize the `error.group.name` attribute of errors that are captured by the agent. This attribute controls how the Errors Inbox functionality groups similar errors together. To learn more about this function, please refer to our [example app](https://github.com/newrelic/newrelic-node-examples).
package/index.js CHANGED
@@ -14,7 +14,6 @@ require('./lib/util/unwrapped-core')
14
14
 
15
15
  const featureFlags = require('./lib/feature_flags').prerelease
16
16
  const psemver = require('./lib/util/process-version')
17
- const symbols = require('./lib/symbols')
18
17
  let logger = require('./lib/logger') // Gets re-loaded after initialization.
19
18
 
20
19
  const pkgJSON = require('./package.json')
@@ -24,15 +23,15 @@ logger.info(
24
23
  process.version
25
24
  )
26
25
 
27
- if (require.cache[symbols.cache]) {
26
+ if (require.cache.__NR_cache) {
28
27
  logger.warn(
29
28
  'Attempting to load a second copy of newrelic from %s, using cache instead',
30
29
  __dirname
31
30
  )
32
- if (require.cache[symbols.cache].agent) {
33
- require.cache[symbols.cache].agent.recordSupportability('Agent/DoubleLoad')
31
+ if (require.cache.__NR_cache.agent) {
32
+ require.cache.__NR_cache.agent.recordSupportability('Agent/DoubleLoad')
34
33
  }
35
- module.exports = require.cache[symbols.cache]
34
+ module.exports = require.cache.__NR_cache
36
35
  } else {
37
36
  initialize()
38
37
  }
@@ -99,7 +98,7 @@ function initialize() {
99
98
  API = require('./stub_api')
100
99
  }
101
100
 
102
- require.cache[symbols.cache] = module.exports = new API(agent)
101
+ require.cache.__NR_cache = module.exports = new API(agent)
103
102
 
104
103
  // If we loaded an agent, record a startup time for the agent.
105
104
  // NOTE: Metrics are recorded in seconds, so divide the value by 1000.
@@ -1318,6 +1318,13 @@ defaultConfig.definition = () => ({
1318
1318
  }
1319
1319
  }
1320
1320
  }
1321
+ },
1322
+
1323
+ heroku: {
1324
+ use_dyno_names: {
1325
+ default: true,
1326
+ formatter: boolean
1327
+ }
1321
1328
  }
1322
1329
  })
1323
1330
 
@@ -914,11 +914,17 @@ Config.prototype.getIPAddresses = function getIPAddresses() {
914
914
 
915
915
  function getHostnameSafe() {
916
916
  let _hostname
917
+ const config = this
917
918
  this.getHostnameSafe = function getCachedHostname() {
918
919
  return _hostname
919
920
  }
920
921
  try {
921
- _hostname = os.hostname()
922
+ if (config.heroku.use_dyno_names) {
923
+ const dynoName = process.env.DYNO
924
+ _hostname = dynoName || os.hostname()
925
+ } else {
926
+ _hostname = os.hostname()
927
+ }
922
928
  return _hostname
923
929
  } catch (e) {
924
930
  const addresses = this.getIPAddresses()
@@ -5,8 +5,6 @@
5
5
 
6
6
  'use strict'
7
7
 
8
- /* eslint sonarjs/cognitive-complexity: ["error", 57] -- TODO: https://issues.newrelic.com/browse/NEWRELIC-5252 */
9
-
10
8
  const dbutil = require('../db/utils')
11
9
  const hasOwnProperty = require('../util/properties').hasOwn
12
10
  const logger = require('../logger').child({ component: 'DatastoreShim' })
@@ -357,19 +355,17 @@ function recordOperation(nodule, properties, opSpec) {
357
355
  return this.record(nodule, properties, function opRecorder(shim, fn, fnName, args) {
358
356
  shim.logger.trace('Recording datastore operation "%s"', fnName)
359
357
 
360
- // Derive the segment information.
358
+ // Derive the segment information, starting from some defaults
361
359
  let segDesc = null
362
360
  if (shim.isFunction(opSpec)) {
363
361
  segDesc = opSpec.call(this, shim, fn, fnName, args)
364
362
  } else {
365
363
  segDesc = {
366
- name: opSpec.name || fnName || 'other',
367
- parameters: opSpec.parameters,
368
- callback: opSpec.callback,
369
- after: 'after' in opSpec ? opSpec.after : null,
370
- promise: 'promise' in opSpec ? opSpec.promise : null,
371
- record: opSpec.record,
372
- opaque: opSpec.opaque || false
364
+ name: fnName || 'other',
365
+ opaque: false,
366
+ after: null,
367
+ promise: null,
368
+ ...opSpec
373
369
  }
374
370
  }
375
371
  if (hasOwnProperty(segDesc, 'parameters')) {
@@ -648,26 +644,31 @@ function _recordQuery(suffix, nodule, properties, querySpec) {
648
644
 
649
645
  let queryDesc = querySpec
650
646
  if (shim.isFunction(querySpec)) {
651
- queryDesc = querySpec.call(this, shim, fn, fnName, args)
647
+ queryDesc = querySpec.call(this, shim, fn, fnName, args) || Object.create(null)
648
+ }
649
+
650
+ // Set some default values, in case they're missing.
651
+ queryDesc = {
652
+ name: fnName,
653
+ callback: null,
654
+ rowCallback: null,
655
+ stream: null,
656
+ after: null,
657
+ promise: null,
658
+ opaque: false,
659
+ inContext: null,
660
+ ...queryDesc
652
661
  }
653
662
 
663
+ const parameters = _normalizeParameters.call(shim, queryDesc.parameters || Object.create(null))
664
+
654
665
  // If we're not actually recording this, then just return the segment
655
666
  // descriptor now.
656
- if (hasOwnProperty(queryDesc, 'record') && queryDesc.record === false) {
657
- const parameters = _normalizeParameters.call(
658
- shim,
659
- queryDesc.parameters || Object.create(null)
660
- )
667
+ if (queryDesc?.record === false) {
661
668
  return {
662
- name: queryDesc.name || fnName,
663
- parameters,
664
- callback: 'callback' in queryDesc ? queryDesc.callback : null,
665
- rowCallback: 'rowCallback' in queryDesc ? queryDesc.rowCallback : null,
666
- stream: 'stream' in queryDesc ? queryDesc.stream : null,
667
- after: 'after' in queryDesc ? queryDesc.after : null,
668
- promise: 'promise' in queryDesc ? queryDesc.promise : null,
669
- internal: 'internal' in queryDesc ? queryDesc.internal : false,
670
- opaque: 'opaque' in queryDesc ? queryDesc.opaque : false
669
+ internal: false,
670
+ ...queryDesc,
671
+ parameters
671
672
  }
672
673
  }
673
674
 
@@ -683,18 +684,13 @@ function _recordQuery(suffix, nodule, properties, querySpec) {
683
684
  shim.logger.trace('Found and parsed query %s -> %s', parsed.type, name)
684
685
 
685
686
  // Return the segment descriptor.
686
- const parameters = _normalizeParameters.call(shim, queryDesc.parameters || Object.create(null))
687
687
  return {
688
+ internal: true,
689
+ ...queryDesc,
690
+ // This name and parameters might override those in the original
691
+ // queryDesc.
688
692
  name: shim._metrics.STATEMENT + name,
689
693
  parameters,
690
- callback: 'callback' in queryDesc ? queryDesc.callback : null,
691
- rowCallback: 'rowCallback' in queryDesc ? queryDesc.rowCallback : null,
692
- stream: 'stream' in queryDesc ? queryDesc.stream : null,
693
- after: 'after' in queryDesc ? queryDesc.after : null,
694
- promise: 'promise' in queryDesc ? queryDesc.promise : null,
695
- internal: 'internal' in queryDesc ? queryDesc.internal : true,
696
- opaque: 'opaque' in queryDesc ? queryDesc.opaque : false,
697
- inContext: 'inContext' in queryDesc ? queryDesc.inContext : null,
698
694
  recorder: function queryRecorder(segment, scope) {
699
695
  if (segment) {
700
696
  parsed.recordMetrics(segment, scope)
@@ -806,6 +802,20 @@ function _normalizeParameters(parameters) {
806
802
 
807
803
  parameters.product = parameters.product || this._datastore
808
804
 
805
+ _normalizeDatabaseName(parameters, dsTracerConf)
806
+ _normalizeInstanceInformation(parameters, dsTracerConf, config)
807
+
808
+ return parameters
809
+ }
810
+
811
+ /**
812
+ * Normalizes the database name from segment parameter values.
813
+ *
814
+ * @private
815
+ * @param {object} parameters - The segment parameters to clean up.
816
+ * @param {object} dsTracerConf - The datastore tracer configuration
817
+ */
818
+ function _normalizeDatabaseName(parameters, dsTracerConf) {
809
819
  // Add database name if provided and enabled.
810
820
  if (!dsTracerConf.database_name_reporting.enabled) {
811
821
  delete parameters.database_name
@@ -815,7 +825,18 @@ function _normalizeParameters(parameters) {
815
825
  ? String(parameters.database_name)
816
826
  : parameters.database_name || INSTANCE_UNKNOWN
817
827
  }
828
+ }
818
829
 
830
+ /**
831
+ * Normalizes the database instance information from segment parameter
832
+ * values: host and the port/path/id.
833
+ *
834
+ * @private
835
+ * @param {object} parameters - The segment parameters to clean up.
836
+ * @param {object} dsTracerConf - The datastore tracer configuration
837
+ * @param {object} config - The agent configuration
838
+ */
839
+ function _normalizeInstanceInformation(parameters, dsTracerConf, config) {
819
840
  // Add instance information if enabled.
820
841
  if (!dsTracerConf.instance_reporting.enabled) {
821
842
  delete parameters.host
@@ -836,6 +857,4 @@ function _normalizeParameters(parameters) {
836
857
  }
837
858
  }
838
859
  }
839
-
840
- return parameters
841
860
  }
package/lib/symbols.js CHANGED
@@ -6,7 +6,6 @@
6
6
  'use strict'
7
7
 
8
8
  module.exports = {
9
- cache: Symbol('cache'),
10
9
  clm: Symbol('codeLevelMetrics'),
11
10
  context: Symbol('context'),
12
11
  databaseName: Symbol('databaseName'),
@@ -5,8 +5,6 @@
5
5
 
6
6
  'use strict'
7
7
 
8
- /* eslint sonarjs/cognitive-complexity: ["error", 29] -- TODO: https://issues.newrelic.com/browse/NEWRELIC-5252 */
9
-
10
8
  const errorHelper = require('../errors/helper')
11
9
  const hashes = require('../util/hashes')
12
10
  const logger = require('../logger').child({ component: 'transaction' })
@@ -187,7 +185,7 @@ Transaction.prototype.isWeb = function isWeb() {
187
185
  }
188
186
 
189
187
  /**
190
- * @returns {bool} Is this transaction still alive?
188
+ * @returns {boolean} Is this transaction still alive?
191
189
  */
192
190
  Transaction.prototype.isActive = function isActive() {
193
191
  return this.timer.isActive()
@@ -196,6 +194,8 @@ Transaction.prototype.isActive = function isActive() {
196
194
  /**
197
195
  * Close out the current transaction and its associated trace. Remove any
198
196
  * instances of this transaction annotated onto the call stack.
197
+ *
198
+ * @returns {(Transaction|undefined)} this transaction, or undefined
199
199
  */
200
200
  Transaction.prototype.end = function end() {
201
201
  if (!this.timer.isActive()) {
@@ -259,6 +259,8 @@ Transaction.prototype._cleanUneededReferences = function _cleanUneededReferences
259
259
  * For web transactions, this represents the time from when the request was received
260
260
  * to when response was sent. For background transactions, it is equal to duration
261
261
  * of the transaction trace (until last segment ended).
262
+ *
263
+ * @returns {number} timer or trace duration in milliseconds
262
264
  */
263
265
  Transaction.prototype.getResponseTimeInMillis = function getResponseTimeInMillis() {
264
266
  if (this.isWeb()) {
@@ -408,6 +410,7 @@ Transaction.prototype.isIgnored = function getIgnore() {
408
410
  * @param {number} statusCode - The response status code.
409
411
  */
410
412
  Transaction.prototype.finalizeNameFromUri = finalizeNameFromUri
413
+
411
414
  function finalizeNameFromUri(requestURL, statusCode) {
412
415
  if (logger.traceEnabled()) {
413
416
  logger.trace(
@@ -427,24 +430,7 @@ function finalizeNameFromUri(requestURL, statusCode) {
427
430
 
428
431
  // If a namestate stack exists, copy route parameters over to the trace.
429
432
  if (!this.nameState.isEmpty() && this.baseSegment) {
430
- this.nameState.forEachParams(function forEachRouteParams(params) {
431
- for (const key in params) {
432
- if (props.hasOwn(params, key)) {
433
- this.trace.attributes.addAttribute(DESTS.NONE, REQUEST_PARAMS_PATH + key, params[key])
434
-
435
- const segment = this.agent.tracer.getSegment()
436
-
437
- if (!segment) {
438
- logger.trace(
439
- 'Active segment not available, not adding request.parameters attribute for %s',
440
- key
441
- )
442
- } else {
443
- segment.attributes.addAttribute(DESTS.NONE, REQUEST_PARAMS_PATH + key, params[key])
444
- }
445
- }
446
- }
447
- }, this)
433
+ this.nameState.forEachParams(forEachRouteParams, this)
448
434
  }
449
435
 
450
436
  // Allow the API to explicitly set the ignored status.
@@ -468,6 +454,25 @@ function finalizeNameFromUri(requestURL, statusCode) {
468
454
  }
469
455
  }
470
456
 
457
+ function forEachRouteParams(params) {
458
+ for (const key in params) {
459
+ if (props.hasOwn(params, key)) {
460
+ this.trace.attributes.addAttribute(DESTS.NONE, REQUEST_PARAMS_PATH + key, params[key])
461
+
462
+ const segment = this.agent.tracer.getSegment()
463
+
464
+ if (!segment) {
465
+ logger.trace(
466
+ 'Active segment not available, not adding request.parameters attribute for %s',
467
+ key
468
+ )
469
+ } else {
470
+ segment.attributes.addAttribute(DESTS.NONE, REQUEST_PARAMS_PATH + key, params[key])
471
+ }
472
+ }
473
+ }
474
+ }
475
+
471
476
  Transaction.prototype._copyNameToActiveSpan = function _copyNameToActiveSpan(name) {
472
477
  const spanContext = this.agent.tracer.getSpanContext()
473
478
  if (!spanContext) {
@@ -512,6 +517,7 @@ Transaction.prototype._markAsWeb = function _markAsWeb(rawURL) {
512
517
  * @param {string} [name]
513
518
  * Optional. The partial name to use for the finalized transaction. If omitted
514
519
  * the current partial name is used.
520
+ * @returns {undefined} undefined, finalizing name as a side effect
515
521
  */
516
522
  Transaction.prototype.finalizeName = function finalizeName(name) {
517
523
  // If no name is given, and this is a web transaction with a url, then
@@ -564,6 +570,8 @@ Transaction.prototype.finalizeName = function finalizeName(name) {
564
570
  * cause extra performance overhead. Once this is refactored we can make the
565
571
  * caching better and eliminate this extra overhead. Be mindful of if/when this
566
572
  * is called.
573
+ *
574
+ * @returns {string} finalized name value or partial name
567
575
  */
568
576
  Transaction.prototype.getName = function getName() {
569
577
  if (this.isWeb() && this.url) {
@@ -620,7 +628,7 @@ Transaction.prototype.getFullName = function getFullName() {
620
628
  * otherwise it will parse .url, store it on .parsedUrl, then scrub the URL and
621
629
  * store it in the cache.
622
630
  *
623
- * Returns a string or undefined.
631
+ * @returns {(string|undefined)} Returns a string or undefined.
624
632
  */
625
633
  Transaction.prototype.getScrubbedUrl = function getScrubbedUrl() {
626
634
  if (!this.isWeb()) {
@@ -703,18 +711,17 @@ Transaction.prototype.measure = function measure(name, scope, duration, exclusiv
703
711
  *
704
712
  * @param {string} name Metric name.
705
713
  * @param {number} duration Duration of the transaction, in milliseconds.
706
- * @param {number} keyApdex A key transaction apdexT, in milliseconds
707
- * (optional).
708
- * @param keyApdexInMillis
714
+ * @param {number} keyApdexInMillis Duration sent to the metrics getOrCreateApdexMetric method, to
715
+ * derive apdex from timing in milliseconds
709
716
  */
710
717
  Transaction.prototype._setApdex = function _setApdex(name, duration, keyApdexInMillis) {
711
718
  const apdexStats = this.metrics.getOrCreateApdexMetric(name, null, keyApdexInMillis)
712
719
 
713
720
  // if we have an error-like status code, and all the errors are
714
721
  // expected, we know the status code was caused by an expected
715
- // error, so we will not report "frustrating". Otherwise, we
722
+ // error, so we will not report "frustrating." Otherwise, we
716
723
  // don't know which error triggered the error-like status code,
717
- // and will still incrementing frustrating. If this is an issue,
724
+ // and will still increment "frustrating." If this is an issue,
718
725
  // users can either set a status code as expected, or ignore the
719
726
  // specific error to avoid incrementing to frustrating
720
727
  if (
@@ -732,6 +739,7 @@ Transaction.prototype._setApdex = function _setApdex(name, duration, keyApdexInM
732
739
  * Store first 10 unique path hashes calculated for a transaction.
733
740
  *
734
741
  * @param {string} pathHash Path hash
742
+ * @returns {undefined}
735
743
  */
736
744
  Transaction.prototype.pushPathHash = function pushPathHash(pathHash) {
737
745
  if (this.pathHashes.length >= 10 || this.pathHashes.indexOf(pathHash) !== -1) {
@@ -742,6 +750,8 @@ Transaction.prototype.pushPathHash = function pushPathHash(pathHash) {
742
750
 
743
751
  /**
744
752
  * Return whether transaction spawned any outbound requests.
753
+ *
754
+ * @returns {boolean} if there are more than zero pathHashes
745
755
  */
746
756
  Transaction.prototype.includesOutboundRequests = function includesOutboundRequests() {
747
757
  return this.pathHashes.length > 0
@@ -750,6 +760,8 @@ Transaction.prototype.includesOutboundRequests = function includesOutboundReques
750
760
  /**
751
761
  * Get unique previous path hashes for a transaction. Does not include
752
762
  * current path hash.
763
+ *
764
+ * @returns {(string|null)} Returns sorted altHashes joined by commas, or null.
753
765
  */
754
766
  Transaction.prototype.alternatePathHashes = function alternatePathHashes() {
755
767
  const curHash = hashes.calculatePathHash(
@@ -771,7 +783,7 @@ Transaction.prototype.alternatePathHashes = function alternatePathHashes() {
771
783
  * Add the error information to the current segment and add the segment ID as
772
784
  * an attribute onto the exception.
773
785
  *
774
- * @param {Exception} exception The exception object to be collected.
786
+ * @param {Exception} exception The exception object to be collected.
775
787
  */
776
788
  Transaction.prototype._linkExceptionToSegment = _linkExceptionToSegment
777
789
 
@@ -817,7 +829,7 @@ function _addException(exception) {
817
829
  * When the transaction ends, the exception will be collected along with the transaction
818
830
  * details.
819
831
  *
820
- * @param {Exception} exception The exception object to be collected.
832
+ * @param {Exception} exception The exception object to be collected.
821
833
  */
822
834
  Transaction.prototype.addUserError = _addUserError
823
835
 
@@ -832,7 +844,7 @@ function _addUserError(exception) {
832
844
  }
833
845
 
834
846
  /**
835
- * Returns if the transaction's current status code is errored
847
+ * @returns {boolean} true if the transaction's current status code is errored
836
848
  * but considered ignored via the config.
837
849
  */
838
850
  Transaction.prototype.hasIgnoredErrorStatusCode = function _hasIgnoredErrorStatusCode() {
@@ -840,7 +852,7 @@ Transaction.prototype.hasIgnoredErrorStatusCode = function _hasIgnoredErrorStatu
840
852
  }
841
853
 
842
854
  /**
843
- * Returns true if an error happened during the transaction or if the transaction itself is
855
+ * @returns {boolean} true if an error happened during the transaction or if the transaction itself is
844
856
  * considered to be an error.
845
857
  */
846
858
  Transaction.prototype.hasErrors = function _hasErrors() {
@@ -850,7 +862,9 @@ Transaction.prototype.hasErrors = function _hasErrors() {
850
862
  return transactionHasExceptions || transactionHasuserErrors || isErroredTransaction
851
863
  }
852
864
 
853
- // Returns true if all the errors/exceptions collected so far are expected errors
865
+ /**
866
+ * @returns {boolean} true if all the errors/exceptions collected so far are expected errors
867
+ */
854
868
  Transaction.prototype.hasOnlyExpectedErrors = function hasOnlyExpectedErrors() {
855
869
  if (0 === this.exceptions.length) {
856
870
  return false
@@ -871,7 +885,7 @@ Transaction.prototype.hasOnlyExpectedErrors = function hasOnlyExpectedErrors() {
871
885
  }
872
886
 
873
887
  /**
874
- * Returns agent intrinsic attribute for this transaction.
888
+ * @returns {object} agent intrinsic attribute for this transaction.
875
889
  */
876
890
  Transaction.prototype.getIntrinsicAttributes = function getIntrinsicAttributes() {
877
891
  if (!this._intrinsicAttributes.totalTime) {
@@ -909,7 +923,7 @@ Transaction.prototype.getIntrinsicAttributes = function getIntrinsicAttributes()
909
923
  * W3C TraceContext format is preferred over the NewRelic DT format.
910
924
  * NewRelic DT format will be used if no `traceparent` header is found.
911
925
  *
912
- * @param @param {string} [transport='Unknown'] - The transport type that delivered the trace.
926
+ * @param {string} [transport='Unknown'] - The transport type that delivered the trace.
913
927
  * @param {object} headers - Headers to search for supported trace formats. Keys must be lowercase.
914
928
  */
915
929
  Transaction.prototype.acceptDistributedTraceHeaders = acceptDistributedTraceHeaders
@@ -1025,32 +1039,33 @@ function acceptTraceContextPayload(traceparent, tracestate, transport) {
1025
1039
  }
1026
1040
  }
1027
1041
 
1028
- /**
1029
- * Parses incoming distributed trace header payload.
1030
- *
1031
- * @param {object} payload - The distributed trace payload to accept.
1032
- * @param {string} [transport='Unknown'] - The transport type that delivered the payload.
1042
+ /*
1043
+ The following underscored functions are used exclusively by the
1044
+ _acceptDistributedTracePayload method. They're broken out to reduce
1045
+ its cognitive complexity.
1033
1046
  */
1034
- Transaction.prototype._acceptDistributedTracePayload = _acceptDistributedTracePayload
1035
- function _acceptDistributedTracePayload(payload, transport) {
1047
+ const _dtPayloadTest = function _dtPayloadTest(payload) {
1036
1048
  if (!payload) {
1037
1049
  this.agent.recordSupportability('DistributedTrace/AcceptPayload/Ignored/Null')
1038
- return
1039
1050
  }
1040
-
1051
+ return !!payload
1052
+ }
1053
+ const _isDtTest = function _isDtTest() {
1041
1054
  if (this.isDistributedTrace) {
1042
1055
  logger.warn(
1043
1056
  'Already accepted or created a distributed trace payload for transaction %s, ignoring call',
1044
1057
  this.id
1045
1058
  )
1059
+ let supportabilityMetric = 'DistributedTrace/AcceptPayload/Ignored/CreateBeforeAccept'
1046
1060
  if (this.parentId) {
1047
- this.agent.recordSupportability('DistributedTrace/AcceptPayload/Ignored/Multiple')
1048
- } else {
1049
- this.agent.recordSupportability('DistributedTrace/AcceptPayload/Ignored/CreateBeforeAccept')
1061
+ supportabilityMetric = 'DistributedTrace/AcceptPayload/Ignored/Multiple'
1050
1062
  }
1051
- return
1063
+ this.agent.recordSupportability(supportabilityMetric)
1064
+ return true
1052
1065
  }
1053
-
1066
+ return false
1067
+ }
1068
+ const _dtConfigTest = function _dtConfigTest() {
1054
1069
  const config = this.agent.config
1055
1070
  const distTraceEnabled = config.distributed_tracing.enabled
1056
1071
  const trustedAccount = config.trusted_account_key || config.account_id
@@ -1064,60 +1079,56 @@ function _acceptDistributedTracePayload(payload, transport) {
1064
1079
  )
1065
1080
 
1066
1081
  this.agent.recordSupportability(DT_ACCEPT_PAYLOAD_EXCEPTION_METRIC)
1067
- return
1082
+ return false
1068
1083
  }
1069
-
1084
+ return trustedAccount
1085
+ }
1086
+ const _dtParseTest = function _dtParseTest(payload) {
1070
1087
  const parsed = this._getParsedPayload(payload)
1071
1088
 
1072
1089
  if (!parsed) {
1073
- return
1090
+ return false
1074
1091
  }
1075
1092
 
1093
+ if (!parsed.v) {
1094
+ logger.warn('Received a distributed trace payload with no version field', this.id)
1095
+ }
1096
+ if (!parsed.d) {
1097
+ logger.warn('Received a distributed trace payload with no data field', this.id)
1098
+ }
1076
1099
  if (!parsed.v || !parsed.d) {
1077
- if (!parsed.v) {
1078
- logger.warn('Received a distributed trace payload with no version field', this.id)
1079
- }
1080
- if (!parsed.d) {
1081
- logger.warn('Received a distributed trace payload with no data field', this.id)
1082
- }
1083
1100
  this.agent.recordSupportability(DT_ACCEPT_PAYLOAD_PARSE_EXCEPTION_METRIC)
1084
- return
1101
+ return false
1085
1102
  }
1086
-
1103
+ return parsed
1104
+ }
1105
+ const _dtVersionTest = function _dtVersionTest(parsed) {
1087
1106
  const majorVersion = parsed.v && typeof parsed.v[0] === 'number' && parsed.v[0]
1088
- if (majorVersion == null) {
1107
+
1108
+ if (majorVersion === null) {
1089
1109
  logger.warn('Invalid distributed trace payload, not accepting')
1090
1110
  this.agent.recordSupportability(DT_ACCEPT_PAYLOAD_EXCEPTION_METRIC)
1091
1111
  }
1092
1112
  if (majorVersion > 0) {
1093
1113
  // TODO: Add DistributedTracePayload class?
1094
1114
  this.agent.recordSupportability('DistributedTrace/AcceptPayload/Ignored/MajorVersion')
1095
- return
1096
1115
  }
1097
-
1098
- const data = parsed.d
1099
-
1100
- if (!data) {
1101
- logger.warn('No distributed trace data received, not accepting payload')
1102
- this.agent.recordSupportability(DT_ACCEPT_PAYLOAD_EXCEPTION_METRIC)
1103
- return
1104
- }
1105
-
1106
- const requiredKeysExist = REQUIRED_DT_KEYS.every(function checkExists(key) {
1116
+ return majorVersion
1117
+ }
1118
+ const _dtRequiredKeyTest = function _dtRequiredKeyTest(data) {
1119
+ return REQUIRED_DT_KEYS.every(function checkExists(key) {
1107
1120
  return data[key] != null
1108
1121
  })
1122
+ }
1123
+ const _dtSpanParentTest = function _dtSpanParentTest(requiredKeysExist, data) {
1109
1124
  // Either parentSpanId or parentId are required.
1110
1125
  if (!requiredKeysExist || (data.tx == null && data.id == null)) {
1111
1126
  this.agent.recordSupportability(DT_ACCEPT_PAYLOAD_PARSE_EXCEPTION_METRIC)
1112
- return
1113
- }
1114
-
1115
- const trustedAccountKey = data.tk || data.ac
1116
- if (trustedAccountKey !== trustedAccount) {
1117
- this.agent.recordSupportability(`DistributedTrace/AcceptPayload/Ignored/UntrustedAccount`)
1118
- return
1127
+ return false
1119
1128
  }
1120
-
1129
+ return true
1130
+ }
1131
+ const _dtDefineAttrsFromTraceData = function _dtDefineAttrsFromTraceData(data, transport) {
1121
1132
  this.parentType = data.ty
1122
1133
  this.parentApp = data.ap
1123
1134
  this.parentAcct = data.ac
@@ -1147,11 +1158,75 @@ function _acceptDistributedTracePayload(payload, transport) {
1147
1158
  this.agent.recordSupportability('DistributedTrace/AcceptPayload/Success')
1148
1159
  }
1149
1160
 
1161
+ /**
1162
+ * Parses incoming distributed trace header payload.
1163
+ *
1164
+ * @param {object} payload - The distributed trace payload to accept.
1165
+ * @param {string} [transport='Unknown'] - The transport type that delivered the payload.
1166
+ */
1167
+ Transaction.prototype._acceptDistributedTracePayload = _acceptDistributedTracePayload
1168
+ function _acceptDistributedTracePayload(payload, transport) {
1169
+ const payloadTest = _dtPayloadTest.bind(this)
1170
+ if (!payloadTest(payload)) {
1171
+ return
1172
+ }
1173
+
1174
+ const isDtTest = _isDtTest.bind(this)
1175
+ if (isDtTest()) {
1176
+ return
1177
+ }
1178
+
1179
+ const configTest = _dtConfigTest.bind(this)
1180
+ const configTestResult = configTest()
1181
+ if (!configTestResult) {
1182
+ return
1183
+ }
1184
+
1185
+ const traceParseTest = _dtParseTest.bind(this)
1186
+ const parsed = traceParseTest(payload)
1187
+ if (!parsed) {
1188
+ return
1189
+ }
1190
+
1191
+ const traceVersionTest = _dtVersionTest.bind(this)
1192
+ if (traceVersionTest(parsed) > 0) {
1193
+ return
1194
+ }
1195
+
1196
+ const data = parsed.d
1197
+
1198
+ if (!data) {
1199
+ logger.warn('No distributed trace data received, not accepting payload')
1200
+ this.agent.recordSupportability(DT_ACCEPT_PAYLOAD_EXCEPTION_METRIC)
1201
+ return
1202
+ }
1203
+
1204
+ const requiredKeyTest = _dtRequiredKeyTest.bind(this)
1205
+ const requiredKeysExist = requiredKeyTest(data)
1206
+ const spanParentTest = _dtSpanParentTest.bind(this)
1207
+ const spanParentResult = spanParentTest(requiredKeysExist, data)
1208
+
1209
+ if (!spanParentResult) {
1210
+ return
1211
+ }
1212
+
1213
+ const trustedAccount = configTestResult
1214
+ const trustedAccountKey = data.tk || data.ac
1215
+ if (trustedAccountKey !== trustedAccount) {
1216
+ this.agent.recordSupportability(`DistributedTrace/AcceptPayload/Ignored/UntrustedAccount`)
1217
+ return
1218
+ }
1219
+
1220
+ const defineAttrsFromTraceData = _dtDefineAttrsFromTraceData.bind(this)
1221
+ defineAttrsFromTraceData(data, transport)
1222
+ }
1223
+
1150
1224
  /**
1151
1225
  * Returns parsed payload object after attempting to decode it from base64,
1152
1226
  * and parsing the JSON string.
1153
1227
  *
1154
- * @param payload
1228
+ * @param {string} payload Payload string to be JSON.parsed
1229
+ * @returns {(object|null)} parsed JSON payload or null
1155
1230
  */
1156
1231
  Transaction.prototype._getParsedPayload = function _getParsedPayload(payload) {
1157
1232
  let parsed = payload
@@ -1305,7 +1380,7 @@ Transaction.prototype.addRequestParameters = addRequestParameters
1305
1380
  * 'request.parameters.{key}'. These attributes will only be created
1306
1381
  * when 'request.parameters.*' is included in the attribute config.
1307
1382
  *
1308
- * @param {Object<string, string>} requestParameters
1383
+ * @param {Object<string, string>} requestParameters of the request object
1309
1384
  */
1310
1385
  function addRequestParameters(requestParameters) {
1311
1386
  for (const key in requestParameters) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "newrelic",
3
- "version": "9.14.0",
3
+ "version": "9.15.0",
4
4
  "author": "New Relic Node.js agent team <nodejs@newrelic.com>",
5
5
  "license": "Apache-2.0",
6
6
  "contributors": [