newrelic 6.4.0 → 6.6.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 (44) hide show
  1. package/.eslintignore +1 -1
  2. package/{.eslintrc → .eslintrc.js} +2 -1
  3. package/.travis.yml +5 -6
  4. package/NEWS.md +105 -1
  5. package/THIRD_PARTY_NOTICES.md +420 -0
  6. package/api.js +72 -1
  7. package/index.js +3 -3
  8. package/lib/agent.js +22 -8
  9. package/lib/aggregators/base-aggregator.js +1 -1
  10. package/lib/attributes.js +15 -6
  11. package/lib/collector/serverless.js +8 -0
  12. package/lib/config/default.js +23 -1
  13. package/lib/config/env.js +7 -1
  14. package/lib/config/hsm.js +6 -1
  15. package/lib/config/index.js +36 -8
  16. package/lib/config/lasp.js +16 -1
  17. package/lib/errors/error-collector.js +51 -90
  18. package/lib/errors/helper.js +13 -13
  19. package/lib/errors/index.js +36 -18
  20. package/lib/grpc/connection/states.js +9 -0
  21. package/lib/grpc/connection.js +344 -0
  22. package/lib/grpc/endpoints/infinite-tracing/v1.proto +29 -0
  23. package/lib/header-processing.js +75 -0
  24. package/lib/instrumentation/core/child_process.js +12 -0
  25. package/lib/instrumentation/core/http-outbound.js +4 -0
  26. package/lib/instrumentation/core/http.js +98 -145
  27. package/lib/instrumentation/hapi/hapi-17.js +10 -3
  28. package/lib/metrics/names.js +12 -1
  29. package/lib/proxy/grpc.js +15 -0
  30. package/lib/serverless/aws-lambda.js +2 -0
  31. package/lib/spans/create-span-event-aggregator.js +112 -0
  32. package/lib/spans/map-to-streaming-type.js +44 -0
  33. package/lib/spans/span-event-aggregator.js +5 -0
  34. package/lib/spans/span-event.js +22 -9
  35. package/lib/spans/span-streamer.js +109 -0
  36. package/lib/spans/streaming-span-attributes.js +59 -0
  37. package/lib/spans/streaming-span-event-aggregator.js +98 -0
  38. package/lib/spans/streaming-span-event.js +261 -0
  39. package/lib/transaction/index.js +36 -20
  40. package/lib/transaction/trace/index.js +13 -10
  41. package/lib/transaction/trace/segment.js +12 -1
  42. package/lib/transaction/tracecontext.js +4 -0
  43. package/package.json +10 -10
  44. package/stub_api.js +5 -0
@@ -0,0 +1,109 @@
1
+ 'use strict'
2
+
3
+ const logger = require('../logger').child({component: 'span-streamer'})
4
+
5
+ const NAMES = require('../metrics/names')
6
+
7
+ const NO_STREAM_WARNING =
8
+ 'Attempting to stream spans before connection created. ' +
9
+ 'This warning will not appear again this agent run.'
10
+
11
+ const BACK_PRESSURE_WARNING =
12
+ 'Back pressure detected in SpanStreamer! Spans will be dropped until the current batch ' +
13
+ 'has fully sent. Will not warn again for %s seconds.'
14
+ const BACK_PRESSURE_WARNING_INTERVAL = 60 // in seconds
15
+
16
+ const BACK_PRESSURE_STOP = 'Back pressure has ended, continuing to stream'
17
+
18
+ class SpanStreamer {
19
+ constructor(license_key, connection, metrics) {
20
+ this.stream = null
21
+ this.license_key = license_key
22
+ this.connection = connection
23
+ this._metrics = metrics
24
+ this._writable = false
25
+
26
+ // 'connected' indicates a safely writeable stream.
27
+ // May still be mid-connect to gRPC server.
28
+ this.connection.on('connected', (stream) =>{
29
+ this.stream = stream
30
+ this._writable = true
31
+ })
32
+
33
+ this.connection.on('disconnected', () =>{
34
+ this.stream = null
35
+ this._writable = false
36
+ })
37
+ }
38
+
39
+ write(span) {
40
+ if (!this.stream) {
41
+ logger.warnOnce(NO_STREAM_WARNING)
42
+ return false
43
+ }
44
+
45
+ if (!this._writable) {
46
+ return false
47
+ }
48
+
49
+ const formattedSpan = span.toStreamingFormat()
50
+
51
+ try {
52
+ // false indicates the stream has reached the highWaterMark
53
+ // and future writes should be avoided until drained. written items,
54
+ // including the one that returned false, will still be buffered.
55
+ this._writable = this.stream.write(formattedSpan)
56
+
57
+ if (!this._writable) {
58
+ logger.infoOncePer(
59
+ 'BACK_PRESSURE_START',
60
+ BACK_PRESSURE_WARNING_INTERVAL * 1000,
61
+ BACK_PRESSURE_WARNING,
62
+ BACK_PRESSURE_WARNING_INTERVAL
63
+ )
64
+
65
+ const waitDrainStart = Date.now()
66
+
67
+ this.stream.once('drain', () => {
68
+ const drainCompleted = Date.now()
69
+ const drainDurationMs = drainCompleted - waitDrainStart
70
+
71
+ // Metric can be used to see how frequently completing drains
72
+ // as well as average time to drain from when we first notice.
73
+ this._metrics.getOrCreateMetric(NAMES.INFINITE_TRACING.DRAIN_DURATION)
74
+ .recordValue(drainDurationMs / 1000)
75
+
76
+ logger.trace(BACK_PRESSURE_STOP)
77
+ this._writable = true
78
+ })
79
+ }
80
+
81
+ // span was added to internal node stream buffer to be sent while draining
82
+ // so we return true even when we should not write anymore afterwards
83
+ return true
84
+ } catch (err) {
85
+ logger.trace('Could not stream span.', err)
86
+ // TODO: something has gone horribly wrong.
87
+ // We may want to log and turn off this aggregator
88
+ // to prevent sending further spans. Maybe even "disable" their creation?
89
+ // or is there a situation where we can recover?
90
+
91
+ return false
92
+ }
93
+ }
94
+
95
+ connect(agent_run_id) {
96
+ this.connection.setConnectionDetails(
97
+ this.license_key,
98
+ agent_run_id
99
+ )
100
+
101
+ this.connection.connectSpans()
102
+ }
103
+
104
+ disconnect() {
105
+ this.connection.disconnect()
106
+ }
107
+ }
108
+
109
+ module.exports = SpanStreamer
@@ -0,0 +1,59 @@
1
+ 'use strict'
2
+
3
+ const mapToStreamingType = require('./map-to-streaming-type')
4
+
5
+ /**
6
+ * Specialized attribute collection class for use with infinite streaming.
7
+ * Currently designed to be sent over grpc via the v1.proto definition.
8
+ *
9
+ * @private
10
+ * @class
11
+ */
12
+ class StreamingSpanAttributes {
13
+ constructor(attributes) {
14
+ if (attributes) {
15
+ this.addAttributes(attributes)
16
+ }
17
+ }
18
+
19
+ /**
20
+ * Add a key/value pair to the attribute collection.
21
+ * null/undefined values will be dropped.
22
+ *
23
+ * Does not apply filtering/truncation.
24
+ *
25
+ * @param {string} key Name of the attribute to be stored.
26
+ * @param {string|boolean|number} value Value of the attribute to be stored.
27
+ */
28
+ addAttribute(key, value) {
29
+ const streamingValue = mapToStreamingType(value)
30
+ if (streamingValue) {
31
+ this[key] = streamingValue
32
+ return true
33
+ }
34
+
35
+ return false
36
+ }
37
+
38
+ /**
39
+ * Adds all attributes in an object to the attribute collection.
40
+ * null/undefined values will be dropped.
41
+ *
42
+ * Does not apply filtering/truncation.
43
+ *
44
+ * @param {object} [attributes]
45
+ * @param {string} [attributes.key] Name of the attribute to be stored.
46
+ * @param {string|boolean|number} [attributes.value] Value of the attribute to be stored.
47
+ */
48
+ addAttributes(attributes) {
49
+ if (!attributes) {
50
+ return
51
+ }
52
+
53
+ for (let [key, value] of Object.entries(attributes)) {
54
+ this.addAttribute(key, value)
55
+ }
56
+ }
57
+ }
58
+
59
+ module.exports = StreamingSpanAttributes
@@ -0,0 +1,98 @@
1
+ 'use strict'
2
+
3
+ const Aggregator = require('../aggregators/base-aggregator')
4
+ const StreamingSpanEvent = require('./streaming-span-event')
5
+ const NAMES = require('../metrics/names')
6
+ const logger = require('../logger').child({component: 'streaming-span-event-aggregator'})
7
+
8
+ const SEND_WARNING =
9
+ 'send() is not currently supported on streaming span event aggregator. ' +
10
+ 'This warning will not appear again this agent run.'
11
+
12
+ // TODO: this doesn't "aggregate". Perhaps we need a different terminology
13
+ // for the base-class and then this implementation can avoid the misleading language.
14
+ class StreamingSpanEventAggregator extends Aggregator {
15
+ constructor(opts, collector, metrics) {
16
+ opts = opts || {}
17
+ opts.periodMs = opts.periodMs ? opts.periodMs : 1000
18
+ opts.limit = opts.limit ? opts.limit : 10000
19
+ opts.method = opts.method || 'span_event_data'
20
+
21
+ super(opts, collector)
22
+
23
+ this.metricNames = opts.metricNames || NAMES.INFINITE_TRACING
24
+ this.stream = opts.span_streamer
25
+ this.metrics = metrics
26
+ this.started = false
27
+ this.isStream = true
28
+ }
29
+
30
+ start() {
31
+ if (this.started) {
32
+ return
33
+ }
34
+
35
+ logger.trace('StreamingSpanEventAggregator starting up')
36
+ this.stream.connect(this.runId)
37
+ this.started = true
38
+ }
39
+
40
+ stop() {
41
+ if (!this.started) {
42
+ return
43
+ }
44
+
45
+ logger.trace('StreamingSpanEventAggregator stopping')
46
+ this.stream.disconnect()
47
+ this.started = false
48
+ }
49
+
50
+ send() {
51
+ // Only log once started. This will get invoked on initial harvest
52
+ // prior to start which we'll just ignore.
53
+ if (this.started) {
54
+ logger.warnOnce(SEND_WARNING)
55
+ }
56
+
57
+ this.emit(`finished ${this.method} data send.`)
58
+
59
+ return
60
+ }
61
+
62
+ /**
63
+ * Not a payload based aggregator
64
+ *
65
+ * This is here to implement the implicit interface
66
+ */
67
+ _toPayloadSync() {
68
+ return
69
+ }
70
+
71
+ /**
72
+ * Attempts to add the given segment to the collection.
73
+ *
74
+ * @param {TraceSegment} segment - The segment to add.
75
+ * @param {string} [parentId=null] - The GUID of the parent span.
76
+ *
77
+ * @return {bool} True if the segment was added, or false if it was discarded.
78
+ */
79
+ addSegment(segment, parentId, isRoot) {
80
+ if (!this.started) {
81
+ logger.trace('Aggregator has not yet started, dropping span (%s).', segment.name)
82
+ return
83
+ }
84
+
85
+ // SEEN/SENT are to understand where we've had to drop spans due back-pressure, errors,
86
+ // reconnects, etc. so moving after start check to avoid logging for a
87
+ // currently unsupported case.
88
+ this.metrics.getOrCreateMetric(this.metricNames.SEEN).incrementCallCount()
89
+
90
+ const span = StreamingSpanEvent.fromSegment(segment, parentId, isRoot)
91
+
92
+ if (this.stream.write(span)) {
93
+ this.metrics.getOrCreateMetric(this.metricNames.SENT).incrementCallCount()
94
+ }
95
+ }
96
+ }
97
+
98
+ module.exports = StreamingSpanEventAggregator
@@ -0,0 +1,261 @@
1
+ 'use strict'
2
+
3
+ const StreamingSpanAttributes = require('./streaming-span-attributes')
4
+ const {truncate} = require('../util/byte-limit')
5
+ const Config = require('../config')
6
+
7
+ const {DESTINATIONS} = require('../config/attribute-filter')
8
+ const NAMES = require('../metrics/names')
9
+ const HTTP_LIBRARY = 'http'
10
+ const CLIENT_KIND = 'client'
11
+ const CATEGORIES = {
12
+ HTTP: 'http',
13
+ DATASTORE: 'datastore',
14
+ GENERIC: 'generic'
15
+ }
16
+
17
+ /**
18
+ * Specialized span event class for use with infinite streaming.
19
+ * Currently designed to be sent over grpc via the v1.proto definition.
20
+ *
21
+ * @private
22
+ * @class
23
+ */
24
+ class StreamingSpanEvent {
25
+ /**
26
+ * @param {*} traceId TraceId for the Span.
27
+ * @param {object} agentAttributes Initial set of agent attributes.
28
+ * Must be pre-filtered and truncated.
29
+ * @param {object} customAttributes Initial set of custom attributes.
30
+ * Must be pre-filtered and truncated.
31
+ */
32
+ constructor(traceId, agentAttributes, customAttributes) {
33
+ this._traceId = traceId
34
+
35
+ this._intrinsicAttributes = new StreamingSpanAttributes()
36
+ this._intrinsicAttributes.addAttribute('traceId', traceId)
37
+ this._intrinsicAttributes.addAttribute('type', 'Span')
38
+ this._intrinsicAttributes.addAttribute('category', CATEGORIES.GENERIC)
39
+
40
+ this._customAttributes = new StreamingSpanAttributes(customAttributes)
41
+ this._agentAttributes = new StreamingSpanAttributes(agentAttributes)
42
+ }
43
+
44
+ /**
45
+ * Add a key/value pair to the Span's instrinisics collection.
46
+ *
47
+ * @param {string} key Name of the attribute to be stored.
48
+ * @param {string|boolean|number} value Value of the attribute to be stored.
49
+ */
50
+ addIntrinsicAttribute(key, value) {
51
+ this._intrinsicAttributes.addAttribute(key, value)
52
+ }
53
+
54
+ /**
55
+ * Add a key/value pair to the Span's custom/user attributes collection.
56
+ * @param {string} key Name of the attribute to be stored.
57
+ * @param {string|boolean|number} value Value of the attribute to be stored.
58
+ * @param {boolean} [truncateExempt=false] Set to true if attribute should not be truncated.
59
+ */
60
+ addCustomAttribute(key, value, truncateExempt = false) {
61
+ const shouldKeep = this._checkFilter(key)
62
+ if (shouldKeep) {
63
+ const processedValue = truncateExempt ? value : _truncate(value)
64
+ this._customAttributes.addAttribute(key, processedValue)
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Add a key/value pair to the Span's agent attributes collection.
70
+ * @param {string} key Name of the attribute to be stored.
71
+ * @param {string|boolean|number} value Value of the attribute to be stored.
72
+ * @param {boolean} [truncateExempt=false] Set to true if attribute should not be truncated.
73
+ */
74
+ addAgentAttribute(key, value, truncateExempt = false) {
75
+ const shouldKeep = this._checkFilter(key)
76
+ if (shouldKeep) {
77
+ const processedValue = truncateExempt ? value : _truncate(value)
78
+ this._agentAttributes.addAttribute(key, processedValue)
79
+ }
80
+ }
81
+
82
+ _checkFilter(key) {
83
+ const {attributeFilter} = Config.getInstance()
84
+ const dest = attributeFilter.filterSegment(DESTINATIONS.SPAN_EVENT, key)
85
+ return dest & DESTINATIONS.SPAN_EVENT
86
+ }
87
+
88
+ toStreamingFormat() {
89
+ // Attributes are pre-formatted.
90
+ const formatted = {
91
+ trace_id: this._traceId,
92
+ intrinsics: this._intrinsicAttributes,
93
+ user_attributes: this._customAttributes,
94
+ agent_attributes: this._agentAttributes
95
+ }
96
+ return formatted
97
+ }
98
+
99
+ static fromSegment(segment, parentId = null, isRoot = false) {
100
+ const agentAttributes = segment.attributes.get(DESTINATIONS.SPAN_EVENT)
101
+ const customAttributes = segment.customAttributes.get(DESTINATIONS.SPAN_EVENT)
102
+
103
+ const transaction = segment.transaction
104
+ const traceId = transaction.traceId
105
+
106
+ let span = null
107
+ if (StreamingHttpSpanEvent.isHttpSegment(segment)) {
108
+ span = new StreamingHttpSpanEvent(traceId, agentAttributes, customAttributes)
109
+ } else if (StreamingDatastoreSpanEvent.isDatastoreSegment(segment)) {
110
+ span = new StreamingDatastoreSpanEvent(traceId, agentAttributes, customAttributes)
111
+ } else {
112
+ span = new StreamingSpanEvent(traceId, agentAttributes, customAttributes)
113
+ }
114
+
115
+ span.addIntrinsicAttribute('guid', segment.id)
116
+ span.addIntrinsicAttribute('parentId', parentId)
117
+ span.addIntrinsicAttribute('transactionId', transaction.id)
118
+ span.addIntrinsicAttribute('sampled', transaction.sampled)
119
+ span.addIntrinsicAttribute('priority', transaction.priority)
120
+ span.addIntrinsicAttribute('name', segment.name)
121
+
122
+ if (isRoot) {
123
+ span.addIntrinsicAttribute('trustedParentId', transaction.traceContext.trustedParentId)
124
+ if (transaction.traceContext.tracingVendors) {
125
+ span.addIntrinsicAttribute('tracingVendors', transaction.traceContext.tracingVendors)
126
+ }
127
+ }
128
+
129
+ // Only set this if it will be `true`. Must be `null` otherwise.
130
+ if (transaction.baseSegment === segment) {
131
+ span.addIntrinsicAttribute('nr.entryPoint', true)
132
+ }
133
+
134
+ // Timestamp in milliseconds, duration in seconds. Yay consistency!
135
+ span.addIntrinsicAttribute('timestamp', segment.timer.start)
136
+ span.addIntrinsicAttribute('duration', segment.timer.getDurationInMillis() / 1000)
137
+
138
+ return span
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Specialized span event class for external requests for use with infinite streaming.
144
+ * Currently designed to be sent over grpc via the v1.proto definition.
145
+ *
146
+ * @private
147
+ * @class
148
+ */
149
+ class StreamingHttpSpanEvent extends StreamingSpanEvent {
150
+ /**
151
+ * @param {*} traceId TraceId for the Span.
152
+ * @param {object} agentAttributes Initial set of agent attributes.
153
+ * Must be pre-filtered and truncated.
154
+ * @param {object} customAttributes Initial set of custom attributes.
155
+ * Must be pre-filtered and truncated.
156
+ */
157
+ constructor(traceId, agentAttributes, customAttributes) {
158
+ super(traceId, agentAttributes, customAttributes)
159
+
160
+ this.addIntrinsicAttribute('category', CATEGORIES.HTTP)
161
+ this.addIntrinsicAttribute('component', agentAttributes.library || HTTP_LIBRARY)
162
+ this.addIntrinsicAttribute('span.kind', CLIENT_KIND)
163
+
164
+ if (agentAttributes.library) {
165
+ agentAttributes.library = null
166
+ }
167
+
168
+ if (agentAttributes.url) {
169
+ this.addAgentAttribute('http.url', agentAttributes.url)
170
+ agentAttributes.url = null
171
+ }
172
+
173
+ if (agentAttributes.procedure) {
174
+ this.addAgentAttribute('http.method', agentAttributes.procedure)
175
+ agentAttributes.procedure = null
176
+ }
177
+ }
178
+
179
+ static isHttpSegment(segment) {
180
+ return segment.name.startsWith(NAMES.EXTERNAL.PREFIX)
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Specialized span event class for datastore operations and queries for use with
186
+ * infinite streaming.
187
+ * Currently designed to be sent over grpc via the v1.proto definition.
188
+ *
189
+ * @private
190
+ * @class.
191
+ */
192
+ class StreamingDatastoreSpanEvent extends StreamingSpanEvent {
193
+ /**
194
+ * @param {*} traceId TraceId for the Span.
195
+ * @param {object} agentAttributes Initial set of agent attributes.
196
+ * Must be pre-filtered and truncated.
197
+ * @param {object} customAttributes Initial set of custom attributes.
198
+ * Must be pre-filtered and truncated.
199
+ */
200
+ constructor(traceId, agentAttributes, customAttributes) {
201
+ super(traceId, agentAttributes, customAttributes)
202
+
203
+ this.addIntrinsicAttribute('category', CATEGORIES.DATASTORE)
204
+ this.addIntrinsicAttribute('span.kind', CLIENT_KIND)
205
+
206
+ if (agentAttributes.product) {
207
+ this.addIntrinsicAttribute('component', agentAttributes.product)
208
+ agentAttributes.product = null
209
+ }
210
+
211
+ if (agentAttributes.collection) {
212
+ this.addAgentAttribute('db.collection', agentAttributes.collection)
213
+ agentAttributes.collection = null
214
+ }
215
+
216
+ if (agentAttributes.sql || agentAttributes.sql_obfuscated) {
217
+ let sql = null
218
+ if (agentAttributes.sql_obfuscated) {
219
+ sql = _truncate(agentAttributes.sql_obfuscated)
220
+ agentAttributes.sql_obfuscated = null
221
+ } else if (agentAttributes.sql) {
222
+ sql = _truncate(agentAttributes.sql)
223
+ agentAttributes.sql = null
224
+ }
225
+
226
+ // Flag as exempt from normal attribute truncation
227
+ this.addAgentAttribute('db.statement', sql, true)
228
+ }
229
+
230
+ if (agentAttributes.database_name) {
231
+ this.addAgentAttribute('db.instance', agentAttributes.database_name)
232
+ agentAttributes.database_name = null
233
+ }
234
+
235
+ if (agentAttributes.host) {
236
+ this.addAgentAttribute('peer.hostname', agentAttributes.host)
237
+
238
+ if (agentAttributes.port_path_or_id) {
239
+ const address = `${agentAttributes.host}:${agentAttributes.port_path_or_id}`
240
+ this.addAgentAttribute('peer.address', address)
241
+ agentAttributes.port_path_or_id = null
242
+ }
243
+
244
+ agentAttributes.host = null
245
+ }
246
+ }
247
+
248
+ static isDatastoreSegment(segment) {
249
+ return segment.name.startsWith(NAMES.DB.PREFIX)
250
+ }
251
+ }
252
+
253
+ function _truncate(val) {
254
+ let truncated = truncate(val, 1997)
255
+ if (truncated !== val) {
256
+ truncated += '...'
257
+ }
258
+ return truncated
259
+ }
260
+
261
+ module.exports = StreamingSpanEvent
@@ -678,19 +678,40 @@ Transaction.prototype.alternatePathHashes = function alternatePathHashes() {
678
678
  return altHashes.length === 0 ? null : altHashes.sort().join(',')
679
679
  }
680
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
+
681
704
  /**
682
705
  * Associate an exception with the transaction. When the transaction ends,
683
706
  * the exception will be collected along with the transaction details.
684
707
  *
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.
708
+ * @param {Exception} exception The exception object to be collected.
689
709
  */
690
710
  Transaction.prototype.addException = _addException
691
711
 
692
- function _addException(exception, customAttributes, timestamp) {
693
- this.exceptions.push([exception, customAttributes, timestamp])
712
+ function _addException(exception) {
713
+ this._linkExceptionToSegment(exception)
714
+ this.exceptions.push(exception)
694
715
  }
695
716
 
696
717
  /**
@@ -698,20 +719,18 @@ function _addException(exception, customAttributes, timestamp) {
698
719
  * When the transaction ends, the exception will be collected along with the transaction
699
720
  * details.
700
721
  *
701
- * @param {Error} exception The exception to be collected.
702
- * @param {object} customAttributes Any custom attributes associated with
703
- * the request (optional).
704
- * @param {number} timestamp The timestamp for when the exception occurred.
722
+ * @param {Exception} exception The exception object to be collected.
705
723
  */
706
724
  Transaction.prototype.addUserError = _addUserError
707
725
 
708
- function _addUserError(exception, customAttributes, timestamp) {
709
- this.userErrors.push([exception, customAttributes, timestamp])
726
+ function _addUserError(exception) {
727
+ this._linkExceptionToSegment(exception)
728
+ this.userErrors.push(exception)
710
729
  }
711
730
 
712
731
  /**
713
- * Returns true if an error happened during the transaction or if the transaction itself
714
- * 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.
715
734
  */
716
735
  Transaction.prototype.hasErrors = function _hasErrors() {
717
736
  var isErroredTransaction = urltils.isError(this.agent.config, this.statusCode)
@@ -720,10 +739,7 @@ Transaction.prototype.hasErrors = function _hasErrors() {
720
739
  return (transactionHasExceptions || transactionHasuserErrors || isErroredTransaction)
721
740
  }
722
741
 
723
- /**
724
- * Returns true if all the errors/exceptions collected so far
725
- * are expected errors.
726
- */
742
+ // Returns true if all the errors/exceptions collected so far are expected errors
727
743
  Transaction.prototype.hasOnlyExpectedErrors = function hasOnlyExpectedErrors() {
728
744
  if (0 === this.exceptions.length) {
729
745
  return false
@@ -735,13 +751,13 @@ Transaction.prototype.hasOnlyExpectedErrors = function hasOnlyExpectedErrors() {
735
751
  const isUnexpected = !(
736
752
  errorHelper.isExpectedException(
737
753
  this,
738
- exception[0],
754
+ exception.error,
739
755
  this.agent.config,
740
756
  urltils
741
757
  ) ||
742
758
  errorHelper.shouldIgnoreError(
743
759
  this,
744
- exception[0],
760
+ exception.error,
745
761
  this.agent.config
746
762
  )
747
763
  )
@@ -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
@@ -67,28 +67,31 @@ Trace.prototype.end = function end() {
67
67
  * Iterates over the trace tree and generates a span event for each segment.
68
68
  */
69
69
  Trace.prototype.generateSpanEvents = function generateSpanEvents() {
70
- var config = this.transaction.agent.config
70
+ const config = this.transaction.agent.config
71
+
72
+ const infiniteTracingConfigured = Boolean(config.infinite_tracing.trace_observer.host)
71
73
 
72
74
  if (
73
- this.transaction.sampled &&
75
+ (infiniteTracingConfigured || this.transaction.sampled) &&
74
76
  config.span_events.enabled &&
75
77
  config.distributed_tracing.enabled
76
78
  ) {
77
- var toProcess = []
79
+ let toProcess = []
80
+
78
81
  // Root segment does not become a span, so we need to process it separately.
79
82
  const spanAggregator = this.transaction.agent.spanEventAggregator
83
+
80
84
  const children = this.root.getChildren()
81
85
  for (let i = 0; i < children.length; ++i) {
82
86
  toProcess.push(new DTTraceNode(children[i], this.transaction.parentSpanId, true))
83
87
  }
84
88
 
85
89
  while (toProcess.length) {
86
- var segmentInfo = toProcess.pop()
87
- var segment = segmentInfo.segment
90
+ let segmentInfo = toProcess.pop()
91
+ let segment = segmentInfo.segment
88
92
 
89
- // Even though at some point we might want to stop adding events
90
- // because all the priorities should be the same, we need to count
91
- // the spans as seen.
93
+ // Even though at some point we might want to stop adding events because all the priorities
94
+ // should be the same, we need to count the spans as seen.
92
95
  spanAggregator.addSegment(segment, segmentInfo.parentId, segmentInfo.isRoot)
93
96
 
94
97
  const nodes = segment.getChildren()