newrelic 6.3.0 → 6.5.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.
Files changed (39) hide show
  1. package/{.eslintrc → .eslintrc.js} +4 -2
  2. package/.travis.yml +0 -1
  3. package/NEWS.md +136 -0
  4. package/README.md +1 -0
  5. package/api.js +75 -4
  6. package/bin/publish-docs.sh +1 -1
  7. package/bin/travis-node.sh +4 -1
  8. package/index.js +3 -3
  9. package/lib/aggregators/base-aggregator.js +1 -1
  10. package/lib/attributes.js +15 -6
  11. package/lib/config/default.js +11 -1
  12. package/lib/config/env.js +3 -1
  13. package/lib/config/hsm.js +6 -1
  14. package/lib/config/index.js +29 -8
  15. package/lib/config/lasp.js +16 -1
  16. package/lib/errors/error-collector.js +51 -90
  17. package/lib/errors/helper.js +13 -13
  18. package/lib/errors/index.js +36 -18
  19. package/lib/feature_flags.js +3 -3
  20. package/lib/header-processing.js +75 -0
  21. package/lib/instrumentation/core/child_process.js +12 -0
  22. package/lib/instrumentation/core/http-outbound.js +6 -28
  23. package/lib/instrumentation/core/http.js +100 -170
  24. package/lib/instrumentation/hapi/hapi-17.js +10 -3
  25. package/lib/metrics/names.js +1 -0
  26. package/lib/serverless/aws-lambda.js +79 -33
  27. package/lib/serverless/event-sources.json +128 -0
  28. package/lib/shim/transaction-shim.js +10 -33
  29. package/lib/spans/span-event-aggregator.js +2 -2
  30. package/lib/spans/span-event.js +33 -11
  31. package/lib/transaction/handle.js +73 -4
  32. package/lib/transaction/index.js +182 -34
  33. package/lib/transaction/trace/index.js +6 -5
  34. package/lib/transaction/trace/segment.js +12 -1
  35. package/lib/transaction/tracecontext.js +409 -163
  36. package/lib/util/get.js +16 -0
  37. package/lib/util/hashes.js +55 -6
  38. package/package.json +8 -10
  39. package/stub_api.js +5 -0
@@ -13,7 +13,6 @@ var url = require('url')
13
13
  var urltils = require('../util/urltils')
14
14
  const TraceContext = require('./tracecontext').TraceContext
15
15
 
16
-
17
16
  /*
18
17
  *
19
18
  * CONSTANTS
@@ -47,8 +46,14 @@ const TRANSPORT_TYPES_SET = _makeValueSet(TRANSPORT_TYPES)
47
46
  const REQUIRED_DT_KEYS = ['ty', 'ac', 'ap', 'tr', 'ti']
48
47
  const DTPayload = require('./dt-payload')
49
48
  const DTPayloadStub = DTPayload.Stub
49
+
50
50
  const TRACE_CONTEXT_PARENT_HEADER = 'traceparent'
51
+ const TRACE_CONTEXT_STATE_HEADER = 'tracestate'
52
+ const NEWRELIC_TRACE_HEADER = 'newrelic'
51
53
 
54
+ const MULTIPLE_INSERT_MESSAGE =
55
+ 'insertDistributedTraceHeaders called on headers object that already contains ' +
56
+ 'distributed trace data. These may be overwritten. traceparent? \'%s\', newrelic? \'%s\'.'
52
57
 
53
58
  /**
54
59
  * Bundle together the metrics and the trace segment for a single agent
@@ -76,7 +81,7 @@ function Transaction(agent) {
76
81
  ++agent.activeTransactions
77
82
 
78
83
  this.numSegments = 0
79
- this.id = hashes.makeId()
84
+ this.id = hashes.makeId(16)
80
85
 
81
86
  this.trace = new Trace(this)
82
87
  this.exceptions = []
@@ -121,7 +126,18 @@ function Transaction(agent) {
121
126
  this.parentAcct = null
122
127
  this.parentTransportType = null
123
128
  this.parentTransportDuration = null
124
- this.traceId = null
129
+ this._traceId = null
130
+ Object.defineProperty(this, "traceId", {
131
+ get() {
132
+ if (this._traceId === null) {
133
+ this._traceId = hashes.makeId(32)
134
+ }
135
+ return this._traceId
136
+ },
137
+ set(traceId) {
138
+ this._traceId = traceId
139
+ }
140
+ })
125
141
  this.parentSpanId = null
126
142
  this.isDistributedTrace = null
127
143
  this.acceptedDistributedTrace = null
@@ -662,19 +678,40 @@ Transaction.prototype.alternatePathHashes = function alternatePathHashes() {
662
678
  return altHashes.length === 0 ? null : altHashes.sort().join(',')
663
679
  }
664
680
 
681
+ /**
682
+ * Add the error information to the current segment and add the segment ID as
683
+ * an attribute onto the exception.
684
+ *
685
+ * @param {Exception} exception The exception object to be collected.
686
+ */
687
+ Transaction.prototype._linkExceptionToSegment = _linkExceptionToSegment
688
+
689
+ function _linkExceptionToSegment(exception) {
690
+ const segment = this.agent.tracer.getSegment()
691
+ if (!segment) {
692
+ return
693
+ }
694
+
695
+ // Add error attributes to the span
696
+ const details = exception.getErrorDetails(this.agent.config)
697
+ segment.addAttribute('error.message', details.message)
698
+ segment.addAttribute('error.class', details.type)
699
+
700
+ // Add the span/segment ID to the exception as agent attributes
701
+ exception.agentAttributes.spanId = segment.id
702
+ }
703
+
665
704
  /**
666
705
  * Associate an exception with the transaction. When the transaction ends,
667
706
  * the exception will be collected along with the transaction details.
668
707
  *
669
- * @param {Error} exception The exception to be collected.
670
- * @param {object} customAttributes Any custom attributes associated with
671
- * the request (optional).
672
- * @param {number} timestamp The timestamp for when the exception occurred.
708
+ * @param {Exception} exception The exception object to be collected.
673
709
  */
674
710
  Transaction.prototype.addException = _addException
675
711
 
676
- function _addException(exception, customAttributes, timestamp) {
677
- this.exceptions.push([exception, customAttributes, timestamp])
712
+ function _addException(exception) {
713
+ this._linkExceptionToSegment(exception)
714
+ this.exceptions.push(exception)
678
715
  }
679
716
 
680
717
  /**
@@ -682,20 +719,18 @@ function _addException(exception, customAttributes, timestamp) {
682
719
  * When the transaction ends, the exception will be collected along with the transaction
683
720
  * details.
684
721
  *
685
- * @param {Error} exception The exception to be collected.
686
- * @param {object} customAttributes Any custom attributes associated with
687
- * the request (optional).
688
- * @param {number} timestamp The timestamp for when the exception occurred.
722
+ * @param {Exception} exception The exception object to be collected.
689
723
  */
690
724
  Transaction.prototype.addUserError = _addUserError
691
725
 
692
- function _addUserError(exception, customAttributes, timestamp) {
693
- this.userErrors.push([exception, customAttributes, timestamp])
726
+ function _addUserError(exception) {
727
+ this._linkExceptionToSegment(exception)
728
+ this.userErrors.push(exception)
694
729
  }
695
730
 
696
731
  /**
697
- * Returns true if an error happened during the transaction or if the transaction itself
698
- * is considered to be an error.
732
+ * Returns true if an error happened during the transaction or if the transaction itself is
733
+ * considered to be an error.
699
734
  */
700
735
  Transaction.prototype.hasErrors = function _hasErrors() {
701
736
  var isErroredTransaction = urltils.isError(this.agent.config, this.statusCode)
@@ -704,10 +739,7 @@ Transaction.prototype.hasErrors = function _hasErrors() {
704
739
  return (transactionHasExceptions || transactionHasuserErrors || isErroredTransaction)
705
740
  }
706
741
 
707
- /**
708
- * Returns true if all the errors/exceptions collected so far
709
- * are expected errors.
710
- */
742
+ // Returns true if all the errors/exceptions collected so far are expected errors
711
743
  Transaction.prototype.hasOnlyExpectedErrors = function hasOnlyExpectedErrors() {
712
744
  if (0 === this.exceptions.length) {
713
745
  return false
@@ -719,13 +751,13 @@ Transaction.prototype.hasOnlyExpectedErrors = function hasOnlyExpectedErrors() {
719
751
  const isUnexpected = !(
720
752
  errorHelper.isExpectedException(
721
753
  this,
722
- exception[0],
754
+ exception.error,
723
755
  this.agent.config,
724
756
  urltils
725
757
  ) ||
726
758
  errorHelper.shouldIgnoreError(
727
759
  this,
728
- exception[0],
760
+ exception.error,
729
761
  this.agent.config
730
762
  )
731
763
  )
@@ -772,6 +804,123 @@ Transaction.prototype.getIntrinsicAttributes = function getIntrinsicAttributes()
772
804
  return Object.assign(Object.create(null), this._intrinsicAttributes)
773
805
  }
774
806
 
807
+ /**
808
+ * Parsing incoming headers for use in a distributed trace.
809
+ * W3C TraceContext format is preferred over the NewRelic DT format.
810
+ * NewRelic DT format will be used if no `traceparent` header is found.
811
+ * @param @param {string} [transport='Unknown'] - The transport type that delivered the trace.
812
+ * @param {object} headers - Headers to search for supported trace formats. Keys must be lowercase.
813
+ */
814
+ Transaction.prototype.acceptDistributedTraceHeaders = acceptDistributedTraceHeaders
815
+ function acceptDistributedTraceHeaders(transportType, headers) {
816
+ const transport = TRANSPORT_TYPES_SET[transportType] ? transportType : TRANSPORT_TYPES.UNKNOWN
817
+
818
+ // assumes header keys already lowercase
819
+ const traceparent = headers[TRACE_CONTEXT_PARENT_HEADER]
820
+
821
+ if (traceparent) {
822
+ logger.trace('Accepting trace context DT payload for transaction %s', this.id)
823
+
824
+ // assumes header keys already lowercase
825
+ const tracestate = headers[TRACE_CONTEXT_STATE_HEADER]
826
+
827
+ this.acceptTraceContextPayload(traceparent, tracestate, transport)
828
+ } else {
829
+ // assumes header keys already lowercase
830
+ const payload = headers[NEWRELIC_TRACE_HEADER]
831
+ if (payload) {
832
+ logger.trace('Accepting newrelic DT payload for transaction %s', this.id)
833
+
834
+ this.acceptDistributedTracePayload(payload, transport)
835
+ }
836
+ }
837
+ }
838
+
839
+ /**
840
+ * Inserts distributed trace headers into the provided headers map.
841
+ * @param {Object} headers
842
+ */
843
+ Transaction.prototype.insertDistributedTraceHeaders = insertDistributedTraceHeaders
844
+ function insertDistributedTraceHeaders(headers) {
845
+ if (!headers) {
846
+ logger.trace('insertDistributedTraceHeaders called without headers.')
847
+ return
848
+ }
849
+
850
+ checkForExistingNrTraceHeaders(headers)
851
+
852
+ // Ensure we have priority before generating trace headers.
853
+ this._calculatePriority()
854
+
855
+ this.traceContext.addTraceContextHeaders(headers)
856
+ this.isDistributedTrace = true
857
+
858
+ logger.trace('Added outbound request w3c trace context headers in transaction %s', this.id)
859
+
860
+ if (this.agent.config.distributed_tracing.exclude_newrelic_header) {
861
+ logger.trace('Excluding newrelic header due to exclude_newrelic_header: true')
862
+ return
863
+ }
864
+
865
+ try {
866
+ const newrelicFormatData = this.createDistributedTracePayload().httpSafe()
867
+ headers[NEWRELIC_TRACE_HEADER] = newrelicFormatData
868
+ logger.trace('Added outbound request distributed tracing headers in transaction %s', this.id)
869
+ } catch (error) {
870
+ logger.trace(error, 'Failed to create distributed trace payload')
871
+ }
872
+ }
873
+
874
+ function checkForExistingNrTraceHeaders(headers) {
875
+ const traceparentHeader = headers[TRACE_CONTEXT_PARENT_HEADER]
876
+ const newrelicHeader = headers[NEWRELIC_TRACE_HEADER]
877
+
878
+ const hasExisting = traceparentHeader || newrelicHeader
879
+ if (hasExisting) {
880
+ logger.trace(MULTIPLE_INSERT_MESSAGE, traceparentHeader, newrelicHeader)
881
+ }
882
+ }
883
+
884
+ Transaction.prototype.acceptTraceContextPayload = acceptTraceContextPayload
885
+ function acceptTraceContextPayload(traceparent, tracestate, transport) {
886
+ if (this.isDistributedTrace) {
887
+ logger.warn(
888
+ 'Already accepted or created a distributed trace payload for transaction %s, ignoring call',
889
+ this.id
890
+ )
891
+
892
+ if (this.acceptedDistributedTrace) {
893
+ this.agent.recordSupportability('TraceContext/Accept/Ignored/Multiple')
894
+ } else {
895
+ this.agent.recordSupportability('TraceContext/Accept/Ignored/CreateBeforeAccept')
896
+ }
897
+
898
+ return
899
+ }
900
+
901
+ const traceContext =
902
+ this.traceContext.acceptTraceContextPayload(traceparent, tracestate)
903
+
904
+ if (traceContext.acceptedTraceparent) {
905
+ this.acceptedDistributedTrace = true
906
+ this.isDistributedTrace = true
907
+
908
+ this.traceId = traceContext.traceId
909
+ this.parentSpanId = traceContext.parentSpanId
910
+ this.parentTransportDuration = traceContext.transportDuration
911
+ this.parentTransportType = transport
912
+
913
+ if (traceContext.acceptedTracestate) {
914
+ this.parentType = traceContext.parentType
915
+ this.parentAcct = traceContext.accountId
916
+ this.parentApp = traceContext.appId
917
+ this.parentId = traceContext.transactionId
918
+ this.sampled = traceContext.sampled
919
+ this.priority = traceContext.priority
920
+ }
921
+ }
922
+ }
923
+
775
924
  /**
776
925
  * Parses incoming distributed trace header payload.
777
926
  *
@@ -787,7 +936,7 @@ function acceptDistributedTracePayload(payload, transport) {
787
936
 
788
937
  if (this.isDistributedTrace) {
789
938
  logger.warn(
790
- 'Already accepted distributed trace payload for transaction %s, ignoring call',
939
+ 'Already accepted or created a distributed trace payload for transaction %s, ignoring call',
791
940
  this.id
792
941
  )
793
942
  if (this.parentId) {
@@ -874,7 +1023,10 @@ function acceptDistributedTracePayload(payload, transport) {
874
1023
  return
875
1024
  }
876
1025
 
877
- transport = TRANSPORT_TYPES_SET[transport] ? transport : 'Unknown'
1026
+ // TODO: This should be removable / covered by acceptDistributedTraceHeaders
1027
+ // once the Transaction#acceptDistributedTracePayload API that directly invokes
1028
+ // this acceptDistributedTracePayload is fully removed in a future Major version.
1029
+ transport = TRANSPORT_TYPES_SET[transport] ? transport : TRANSPORT_TYPES.UNKNOWN
878
1030
 
879
1031
  this.parentType = data.ty
880
1032
  this.parentApp = data.ap
@@ -967,6 +1119,9 @@ function createDistributedTracePayload() {
967
1119
  return new DTPayloadStub()
968
1120
  }
969
1121
 
1122
+ // TODO: This should be removable / covered by insertDistributedTraceHeaders
1123
+ // once the Transaction#createDistributedTracePayload API that directly invokes
1124
+ // this createDistributedTracePayload is fully removed in a future Major version.
970
1125
  this._calculatePriority()
971
1126
 
972
1127
  const currSegment = this.agent.tracer.getSegment()
@@ -975,7 +1130,7 @@ function createDistributedTracePayload() {
975
1130
  ac: accountId,
976
1131
  ap: appId,
977
1132
  tx: this.id,
978
- tr: this.getTraceId(),
1133
+ tr: this.traceId,
979
1134
  pr: this.priority,
980
1135
  sa: this.sampled,
981
1136
  ti: Date.now()
@@ -1003,7 +1158,7 @@ function addDistributedTraceIntrinsics(attrs) {
1003
1158
  this._calculatePriority()
1004
1159
 
1005
1160
  // *always* add these if DT flag is enabled.
1006
- attrs.traceId = this.getTraceId()
1161
+ attrs.traceId = this.traceId
1007
1162
  attrs.guid = this.id
1008
1163
  attrs.priority = this.priority
1009
1164
 
@@ -1082,11 +1237,4 @@ function addRequestParameters(requestParameters) {
1082
1237
  }
1083
1238
  }
1084
1239
 
1085
- /**
1086
- * Gets the current distributed trace traceId
1087
- */
1088
- Transaction.prototype.getTraceId = function getTraceId() {
1089
- return this.traceId || this.id
1090
- }
1091
-
1092
1240
  module.exports = Transaction
@@ -2,7 +2,7 @@
2
2
 
3
3
  var codec = require('../../util/codec')
4
4
  var Segment = require('./segment')
5
- var Attributes = require('../../attributes')
5
+ var {Attributes, MAXIMUM_CUSTOM_ATTRIBUTES} = require('../../attributes')
6
6
  var logger = require('../../logger').child({component: 'trace'})
7
7
 
8
8
 
@@ -29,7 +29,7 @@ function Trace(transaction) {
29
29
  this.segmentsSeen = 0
30
30
  this.totalTimeCache = null
31
31
 
32
- this.custom = new Attributes(ATTRIBUTE_SCOPE, 64)
32
+ this.custom = new Attributes(ATTRIBUTE_SCOPE, MAXIMUM_CUSTOM_ATTRIBUTES)
33
33
  this.attributes = new Attributes(ATTRIBUTE_SCOPE)
34
34
 
35
35
  // sending displayName if set by user
@@ -79,7 +79,7 @@ Trace.prototype.generateSpanEvents = function generateSpanEvents() {
79
79
  const spanAggregator = this.transaction.agent.spanEventAggregator
80
80
  const children = this.root.getChildren()
81
81
  for (let i = 0; i < children.length; ++i) {
82
- toProcess.push(new DTTraceNode(children[i], this.transaction.parentSpanId))
82
+ toProcess.push(new DTTraceNode(children[i], this.transaction.parentSpanId, true))
83
83
  }
84
84
 
85
85
  while (toProcess.length) {
@@ -89,7 +89,7 @@ Trace.prototype.generateSpanEvents = function generateSpanEvents() {
89
89
  // Even though at some point we might want to stop adding events
90
90
  // because all the priorities should be the same, we need to count
91
91
  // the spans as seen.
92
- spanAggregator.addSegment(segment, segmentInfo.parentId)
92
+ spanAggregator.addSegment(segment, segmentInfo.parentId, segmentInfo.isRoot)
93
93
 
94
94
  const nodes = segment.getChildren()
95
95
  for (let i = 0; i < nodes.length; ++i) {
@@ -100,9 +100,10 @@ Trace.prototype.generateSpanEvents = function generateSpanEvents() {
100
100
  }
101
101
  }
102
102
 
103
- function DTTraceNode(segment, parentId) {
103
+ function DTTraceNode(segment, parentId, isRoot = false) {
104
104
  this.segment = segment
105
105
  this.parentId = parentId
106
+ this.isRoot = isRoot
106
107
  }
107
108
 
108
109
  /**
@@ -6,7 +6,7 @@ const Timer = require('../../timer')
6
6
  const urltils = require('../../util/urltils')
7
7
  const hashes = require('../../util/hashes')
8
8
 
9
- const Attributes = require('../../attributes')
9
+ const {Attributes, MAXIMUM_CUSTOM_ATTRIBUTES} = require('../../attributes')
10
10
  const ExclusiveCalculator = require('./exclusive-time-calculator')
11
11
 
12
12
  const NAMES = require('../../metrics/names')
@@ -52,6 +52,7 @@ function TraceSegment(transaction, name, recorder) {
52
52
  }
53
53
 
54
54
  this.attributes = new Attributes(ATTRIBUTE_SCOPE)
55
+ this.customAttributes = new Attributes(ATTRIBUTE_SCOPE, MAXIMUM_CUSTOM_ATTRIBUTES)
55
56
 
56
57
  this.children = []
57
58
 
@@ -90,6 +91,16 @@ TraceSegment.prototype.getAttributes = function getAttributes() {
90
91
  return this.attributes.get(DESTINATIONS.TRANS_SEGMENT)
91
92
  }
92
93
 
94
+ TraceSegment.prototype.addCustomSpanAttribute =
95
+ function addCustomSpanAttribute(key, value, truncateExempt = false) {
96
+ this.customAttributes.addAttribute(
97
+ DESTINATIONS.SPAN_EVENT,
98
+ key,
99
+ value,
100
+ truncateExempt
101
+ )
102
+ }
103
+
93
104
  TraceSegment.prototype.getSpanId = function getSpanId() {
94
105
  const conf = this.transaction.agent.config
95
106
  const enabled = conf.span_events.enabled && conf.distributed_tracing.enabled