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.
- package/.eslintignore +1 -1
- package/{.eslintrc → .eslintrc.js} +2 -1
- package/.travis.yml +5 -6
- package/NEWS.md +105 -1
- package/THIRD_PARTY_NOTICES.md +420 -0
- package/api.js +72 -1
- package/index.js +3 -3
- package/lib/agent.js +22 -8
- package/lib/aggregators/base-aggregator.js +1 -1
- package/lib/attributes.js +15 -6
- package/lib/collector/serverless.js +8 -0
- package/lib/config/default.js +23 -1
- package/lib/config/env.js +7 -1
- package/lib/config/hsm.js +6 -1
- package/lib/config/index.js +36 -8
- package/lib/config/lasp.js +16 -1
- package/lib/errors/error-collector.js +51 -90
- package/lib/errors/helper.js +13 -13
- package/lib/errors/index.js +36 -18
- package/lib/grpc/connection/states.js +9 -0
- package/lib/grpc/connection.js +344 -0
- package/lib/grpc/endpoints/infinite-tracing/v1.proto +29 -0
- package/lib/header-processing.js +75 -0
- package/lib/instrumentation/core/child_process.js +12 -0
- package/lib/instrumentation/core/http-outbound.js +4 -0
- package/lib/instrumentation/core/http.js +98 -145
- package/lib/instrumentation/hapi/hapi-17.js +10 -3
- package/lib/metrics/names.js +12 -1
- package/lib/proxy/grpc.js +15 -0
- package/lib/serverless/aws-lambda.js +2 -0
- package/lib/spans/create-span-event-aggregator.js +112 -0
- package/lib/spans/map-to-streaming-type.js +44 -0
- package/lib/spans/span-event-aggregator.js +5 -0
- package/lib/spans/span-event.js +22 -9
- package/lib/spans/span-streamer.js +109 -0
- package/lib/spans/streaming-span-attributes.js +59 -0
- package/lib/spans/streaming-span-event-aggregator.js +98 -0
- package/lib/spans/streaming-span-event.js +261 -0
- package/lib/transaction/index.js +36 -20
- package/lib/transaction/trace/index.js +13 -10
- package/lib/transaction/trace/segment.js +12 -1
- package/lib/transaction/tracecontext.js +4 -0
- package/package.json +10 -10
- 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
|
package/lib/transaction/index.js
CHANGED
|
@@ -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 {
|
|
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
|
|
693
|
-
this.
|
|
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 {
|
|
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
|
|
709
|
-
this.
|
|
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
|
-
*
|
|
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
|
|
754
|
+
exception.error,
|
|
739
755
|
this.agent.config,
|
|
740
756
|
urltils
|
|
741
757
|
) ||
|
|
742
758
|
errorHelper.shouldIgnoreError(
|
|
743
759
|
this,
|
|
744
|
-
exception
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
87
|
-
|
|
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
|
-
//
|
|
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()
|