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.
- package/{.eslintrc → .eslintrc.js} +4 -2
- package/.travis.yml +0 -1
- package/NEWS.md +136 -0
- package/README.md +1 -0
- package/api.js +75 -4
- package/bin/publish-docs.sh +1 -1
- package/bin/travis-node.sh +4 -1
- package/index.js +3 -3
- package/lib/aggregators/base-aggregator.js +1 -1
- package/lib/attributes.js +15 -6
- package/lib/config/default.js +11 -1
- package/lib/config/env.js +3 -1
- package/lib/config/hsm.js +6 -1
- package/lib/config/index.js +29 -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/feature_flags.js +3 -3
- package/lib/header-processing.js +75 -0
- package/lib/instrumentation/core/child_process.js +12 -0
- package/lib/instrumentation/core/http-outbound.js +6 -28
- package/lib/instrumentation/core/http.js +100 -170
- package/lib/instrumentation/hapi/hapi-17.js +10 -3
- package/lib/metrics/names.js +1 -0
- package/lib/serverless/aws-lambda.js +79 -33
- package/lib/serverless/event-sources.json +128 -0
- package/lib/shim/transaction-shim.js +10 -33
- package/lib/spans/span-event-aggregator.js +2 -2
- package/lib/spans/span-event.js +33 -11
- package/lib/transaction/handle.js +73 -4
- package/lib/transaction/index.js +182 -34
- package/lib/transaction/trace/index.js +6 -5
- package/lib/transaction/trace/segment.js +12 -1
- package/lib/transaction/tracecontext.js +409 -163
- package/lib/util/get.js +16 -0
- package/lib/util/hashes.js +55 -6
- package/package.json +8 -10
- package/stub_api.js +5 -0
|
@@ -2,16 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
const logger = require('../logger').child({component: 'TraceContext'})
|
|
4
4
|
const hashes = require('../util/hashes')
|
|
5
|
+
|
|
5
6
|
const TRACE_CONTEXT_PARENT_HEADER = 'traceparent'
|
|
6
7
|
const TRACE_CONTEXT_STATE_HEADER = 'tracestate'
|
|
7
|
-
const
|
|
8
|
+
const PARENT_TYPES = ['App', 'Browser', 'Mobile']
|
|
9
|
+
const APP_PARENT_TYPE = PARENT_TYPES.indexOf('App')
|
|
10
|
+
|
|
11
|
+
const W3C_TRACEPARENT_VERSION = '00'
|
|
12
|
+
const NR_TRACESTATE_VERSION = 0
|
|
13
|
+
|
|
14
|
+
// 255 (ff) explicitly not allowed for version
|
|
15
|
+
const VERSION_VALID_RGX = /^((?![f]{2})[a-f0-9]{2})$/
|
|
16
|
+
const TRACEID_VALID_RGX = /^((?![0]{32})[a-f0-9]{32})$/
|
|
17
|
+
const PARENTID_VALID_RGX = /^((?![0]{16})[a-f0-9]{16})$/
|
|
18
|
+
const FLAGS_VALID_RGX = /^([a-f0-9]{2})$/
|
|
8
19
|
|
|
9
20
|
const FLAGS = {
|
|
10
21
|
sampled: 0x00000001
|
|
11
22
|
}
|
|
12
23
|
|
|
13
24
|
/**
|
|
14
|
-
* The class reponsible for
|
|
25
|
+
* The class reponsible for accepting, validating, and producing w3c tracecontext headers.
|
|
15
26
|
*/
|
|
16
27
|
class TraceContext {
|
|
17
28
|
/**
|
|
@@ -20,57 +31,77 @@ class TraceContext {
|
|
|
20
31
|
*/
|
|
21
32
|
constructor(transaction) {
|
|
22
33
|
this.transaction = transaction
|
|
23
|
-
this.
|
|
34
|
+
this.tracingVendors = null
|
|
35
|
+
this.trustedParentId = null
|
|
36
|
+
this._traceStateRaw = null
|
|
24
37
|
this.flags = {
|
|
25
38
|
get sampled() {
|
|
26
39
|
return transaction.sampled
|
|
27
40
|
}
|
|
28
41
|
}
|
|
29
|
-
this.nrTraceState = {
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
this.parentType = 0
|
|
33
|
-
this._parentId
|
|
34
|
-
this._traceId
|
|
35
|
-
this._traceStateRaw
|
|
36
|
-
this._traceStateIntrinsics
|
|
37
42
|
}
|
|
38
43
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
/**
|
|
45
|
+
* Creates a W3C TraceContext traceparent header payload.
|
|
46
|
+
*/
|
|
47
|
+
createTraceparent() {
|
|
48
|
+
// In case we receive a trace ID that isn't the proper length, zero pad
|
|
49
|
+
let traceId = this.transaction.traceId
|
|
50
|
+
traceId = traceId.padStart(32, '0')
|
|
51
|
+
|
|
52
|
+
// If we had to pad, there's a chance this is an invalid upper-case header
|
|
53
|
+
// originating from a newrelic format DT payload being accepted.
|
|
54
|
+
if (traceId !== this.transaction.traceId && !TRACEID_VALID_RGX.test(traceId)) {
|
|
55
|
+
traceId = traceId.toLowerCase()
|
|
48
56
|
}
|
|
49
57
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
58
|
+
// If no segment/span is in context, generate one so we can have a valid traceparent
|
|
59
|
+
const segment = this.transaction.agent.tracer.getSegment()
|
|
60
|
+
let parentId = segment && segment.id
|
|
61
|
+
parentId = parentId || hashes.makeId(16)
|
|
53
62
|
|
|
54
|
-
|
|
55
|
-
if (!this._traceId) {
|
|
56
|
-
this._traceId = (hashes.makeId() + hashes.makeId()).padStart(32, '0')
|
|
57
|
-
}
|
|
58
|
-
return this._traceId
|
|
63
|
+
return `${W3C_TRACEPARENT_VERSION}-${traceId}-${parentId}-${this.createFlagsHex()}`
|
|
59
64
|
}
|
|
60
65
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
get tracestate() {
|
|
66
|
+
/**
|
|
67
|
+
* Creates a W3C TraceContext tracestate header payload.
|
|
68
|
+
*/
|
|
69
|
+
createTracestate() {
|
|
66
70
|
const config = this.transaction.agent.config
|
|
67
71
|
const trustedAccountKey = config.trusted_account_key
|
|
68
|
-
const version =
|
|
69
|
-
const parentType =
|
|
72
|
+
const version = NR_TRACESTATE_VERSION
|
|
73
|
+
const parentType = APP_PARENT_TYPE
|
|
70
74
|
const appId = config.primary_application_id
|
|
71
75
|
const accountId = config.account_id
|
|
72
|
-
|
|
73
|
-
|
|
76
|
+
|
|
77
|
+
if (!accountId || !appId || !trustedAccountKey) {
|
|
78
|
+
logger.debug(
|
|
79
|
+
'Unable to create tracestate header due to missing required fields ' +
|
|
80
|
+
'(account_id: %s, primary_application_id: %s, trusted_account_key: %s) in transaction %s' +
|
|
81
|
+
'This may occur if a trace is created prior to the agent fully starting.',
|
|
82
|
+
accountId,
|
|
83
|
+
appId,
|
|
84
|
+
trustedAccountKey,
|
|
85
|
+
this.transaction.id
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
this.transaction.agent.recordSupportability('TraceContext/TraceState/Create/Exception')
|
|
89
|
+
|
|
90
|
+
return this._traceStateRaw || ''
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// If no segment/span is in context, we do not send one as
|
|
94
|
+
// we technically do not have a "span" on the agent side and
|
|
95
|
+
// this trace data is newrelic specific.
|
|
96
|
+
let spanId = ''
|
|
97
|
+
if (config.span_events.enabled) {
|
|
98
|
+
const segment = this.transaction.agent.tracer.getSegment()
|
|
99
|
+
if (segment) {
|
|
100
|
+
spanId = segment.id
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const transactionId = config.transaction_events.enabled ? this.transaction.id : ''
|
|
74
105
|
const sampled = this.transaction.sampled ? '1' : '0'
|
|
75
106
|
const priority = this.transaction.priority ? this.transaction.priority.toFixed(6) : ''
|
|
76
107
|
const timestamp = Date.now()
|
|
@@ -85,73 +116,169 @@ class TraceContext {
|
|
|
85
116
|
return nrTraceState
|
|
86
117
|
}
|
|
87
118
|
|
|
88
|
-
createTraceContextPayload() {
|
|
89
|
-
this.transaction._calculatePriority()
|
|
90
|
-
return {
|
|
91
|
-
[TRACE_CONTEXT_PARENT_HEADER]: this.traceparent,
|
|
92
|
-
[TRACE_CONTEXT_STATE_HEADER]: this.tracestate
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
119
|
/**
|
|
97
|
-
* Takes a headers object and modifies it in place by adding
|
|
120
|
+
* Takes a headers object and modifies it in place by adding Trace Context headers
|
|
98
121
|
* @param {object} headers - Headers for an HTTP request
|
|
99
122
|
*/
|
|
100
123
|
addTraceContextHeaders(headers) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
124
|
+
if (!headers) {
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
headers[TRACE_CONTEXT_PARENT_HEADER] = this.createTraceparent()
|
|
129
|
+
|
|
130
|
+
const tracestate = this.createTracestate()
|
|
131
|
+
if (tracestate) {
|
|
132
|
+
headers[TRACE_CONTEXT_STATE_HEADER] = tracestate
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
this.transaction.agent.recordSupportability('TraceContext/Create/Success')
|
|
104
136
|
}
|
|
105
137
|
|
|
138
|
+
/**
|
|
139
|
+
* @typedef TraceContextData
|
|
140
|
+
* @property {boolean} acceptedTraceparent - Whether a W3C traceparent headers was
|
|
141
|
+
* parsed, validated, and accepted
|
|
142
|
+
* @property {boolean} acceptedTracestate - Whether a New Relic tracestate headers was
|
|
143
|
+
* parsed, validated, and accepted
|
|
144
|
+
* @property {boolean} entryValid - Whether the matching NR tracestate string is valid
|
|
145
|
+
* @property {Intrinsics} intrinsics - All the parts of the New Relic tracestate string
|
|
146
|
+
* parsed and split out into an object
|
|
147
|
+
* @property {string} newTraceState - The raw tracestate without the New Relic entry
|
|
148
|
+
*/
|
|
149
|
+
|
|
106
150
|
/**
|
|
107
151
|
* Takes a TraceContext headers from an HTTP request, parses them, validates them, and
|
|
108
|
-
* applies the values to the internal state
|
|
152
|
+
* applies the values to the internal state, returning an object with the
|
|
153
|
+
* relevant Trace Context data and validation information.
|
|
109
154
|
*
|
|
110
155
|
* @param {string} traceparent - W3C traceparent header from an HTTP request
|
|
111
156
|
* @param {string} tracestate - W3C tracestate header from an HTTP request
|
|
157
|
+
* @returns {Object} returns an Object with the traceparent data and validation info
|
|
112
158
|
*/
|
|
113
159
|
acceptTraceContextPayload(traceparent, tracestate) {
|
|
114
|
-
|
|
160
|
+
const traceContextData = {
|
|
161
|
+
acceptedTraceparent: false,
|
|
162
|
+
acceptedTracestate: false,
|
|
163
|
+
acceptedNRTracestate: false,
|
|
164
|
+
traceId: null,
|
|
165
|
+
parentSpanId: null,
|
|
166
|
+
parentType: null,
|
|
167
|
+
accountId: null,
|
|
168
|
+
appId: null,
|
|
169
|
+
transactionId: null,
|
|
170
|
+
sampled: null,
|
|
171
|
+
priority: null,
|
|
172
|
+
transportDuration: null
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
//
|
|
176
|
+
// Parsing traceparent
|
|
177
|
+
//
|
|
178
|
+
if (!traceparent) {
|
|
115
179
|
// From the W3C spec: If the vendor failed to parse traceparent, it MUST NOT
|
|
116
180
|
// attempt to parse tracestate
|
|
117
|
-
return
|
|
181
|
+
return traceContextData
|
|
182
|
+
}
|
|
118
183
|
|
|
119
184
|
logger.trace('Accepting TraceContext for transaction %s', this.transaction.id)
|
|
185
|
+
const parsedParent = this._validateAndParseTraceParentHeader(traceparent)
|
|
186
|
+
|
|
187
|
+
// Log if there is a version mismatch in traceparent
|
|
188
|
+
if (parsedParent.version !== W3C_TRACEPARENT_VERSION) {
|
|
189
|
+
logger.trace(
|
|
190
|
+
'Incoming traceparent version: %s, agent traceparent version: %s',
|
|
191
|
+
parsedParent.version,
|
|
192
|
+
W3C_TRACEPARENT_VERSION
|
|
193
|
+
)
|
|
194
|
+
}
|
|
120
195
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (parsedParent) {
|
|
196
|
+
if (parsedParent.entryValid) {
|
|
124
197
|
logger.trace('Accepted traceparent for transaction %s', this.transaction.id)
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
// this.flags = this.parseFlagsHex(parsed.flags)
|
|
198
|
+
traceContextData.acceptedTraceparent = true
|
|
199
|
+
|
|
200
|
+
traceContextData.traceId = parsedParent.traceId
|
|
201
|
+
traceContextData.parentSpanId = parsedParent.parentId
|
|
130
202
|
} else {
|
|
131
|
-
logger.
|
|
132
|
-
|
|
203
|
+
logger.trace(
|
|
204
|
+
'Invalid traceparent for transaction %s: %s',
|
|
205
|
+
this.transaction.id,
|
|
206
|
+
traceparent
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
this.transaction.agent.recordSupportability(
|
|
210
|
+
'TraceContext/TraceParent/Parse/Exception'
|
|
211
|
+
)
|
|
133
212
|
// From the W3C spec: If the vendor failed to parse traceparent, it MUST NOT
|
|
134
213
|
// attempt to parse tracestate
|
|
135
|
-
return
|
|
214
|
+
return traceContextData
|
|
136
215
|
}
|
|
137
216
|
|
|
217
|
+
//
|
|
218
|
+
// Parsing tracestate
|
|
219
|
+
//
|
|
138
220
|
if (!tracestate) {
|
|
139
221
|
logger.trace('No tracestate for transaction %s', this.transaction.id)
|
|
140
|
-
|
|
222
|
+
|
|
223
|
+
return traceContextData
|
|
141
224
|
}
|
|
142
225
|
|
|
143
|
-
const parsedState = this.
|
|
226
|
+
const parsedState = this._validateAndParseTraceStateHeader(tracestate)
|
|
144
227
|
|
|
145
|
-
|
|
228
|
+
if (!parsedState.traceStateValid) {
|
|
229
|
+
logger.trace('Invalid tracestate for transaction %s: %s',
|
|
230
|
+
this.transaction.id,
|
|
231
|
+
tracestate
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
this.transaction.agent.recordSupportability('TraceContext/TraceState/Parse/Exception')
|
|
235
|
+
return traceContextData
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Keep the raw, non-NewRelic tracestate string stored so that we can propogate it
|
|
146
239
|
this._traceStateRaw = parsedState.newTraceState
|
|
147
240
|
|
|
148
|
-
|
|
241
|
+
// These need to be kept to be added to root span events as an attribute
|
|
242
|
+
this.tracingVendors = parsedState.vendors
|
|
243
|
+
|
|
244
|
+
if (
|
|
245
|
+
parsedState.intrinsics &&
|
|
246
|
+
parsedState.intrinsics.version !== NR_TRACESTATE_VERSION
|
|
247
|
+
) {
|
|
248
|
+
logger.trace(
|
|
249
|
+
'Incoming tracestate version: %s, agent tracestate version: %s',
|
|
250
|
+
parsedState.intrinsics.version,
|
|
251
|
+
NR_TRACESTATE_VERSION
|
|
252
|
+
)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (parsedState.entryValid) {
|
|
149
256
|
logger.trace('Accepted tracestate for transaction %s', this.transaction.id)
|
|
150
|
-
|
|
151
|
-
|
|
257
|
+
traceContextData.acceptedTracestate = true
|
|
258
|
+
|
|
259
|
+
traceContextData.parentType = parsedState.intrinsics.parentType
|
|
260
|
+
traceContextData.accountId = parsedState.intrinsics.accountId
|
|
261
|
+
traceContextData.appId = parsedState.intrinsics.appId
|
|
262
|
+
traceContextData.transactionId = parsedState.intrinsics.transactionId
|
|
263
|
+
traceContextData.sampled = parsedState.intrinsics.sampled
|
|
264
|
+
traceContextData.priority = parsedState.intrinsics.priority
|
|
265
|
+
traceContextData.transportDuration =
|
|
266
|
+
Math.max(0, (Date.now() - parsedState.intrinsics.timestamp) / 1000)
|
|
267
|
+
|
|
268
|
+
this.trustedParentId = parsedState.intrinsics.spanId
|
|
269
|
+
this._traceStateRaw = parsedState.newTraceState
|
|
270
|
+
|
|
271
|
+
this.transaction.agent.recordSupportability('TraceContext/Accept/Success')
|
|
272
|
+
} else if (parsedState.entryFound) {
|
|
152
273
|
logger.error('Invalid tracestate for transaction %s: %s',
|
|
153
274
|
this.transaction.id, tracestate)
|
|
275
|
+
|
|
276
|
+
this.transaction.agent.recordSupportability(
|
|
277
|
+
'TraceContext/TraceState/InvalidNrEntry'
|
|
278
|
+
)
|
|
154
279
|
}
|
|
280
|
+
|
|
281
|
+
return traceContextData
|
|
155
282
|
}
|
|
156
283
|
|
|
157
284
|
/**
|
|
@@ -159,25 +286,48 @@ class TraceContext {
|
|
|
159
286
|
* parsed out if valid.
|
|
160
287
|
*
|
|
161
288
|
* @param {string} traceparent - a W3C traceparent header string
|
|
162
|
-
* @returns {
|
|
163
|
-
* traceparent data
|
|
289
|
+
* @returns {Object} returns an Object with the traceparent data and validation info
|
|
164
290
|
*/
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
291
|
+
_validateAndParseTraceParentHeader(traceparent) {
|
|
292
|
+
const traceParentInfo = {
|
|
293
|
+
entryValid: false,
|
|
294
|
+
version: null,
|
|
295
|
+
traceId: null,
|
|
296
|
+
parentId: null,
|
|
297
|
+
flags: null
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (!traceparent) {
|
|
301
|
+
return traceParentInfo
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const trimmed = traceparent.trim()
|
|
305
|
+
const parts = trimmed.split('-')
|
|
306
|
+
|
|
307
|
+
// No extra data allowed this version.
|
|
308
|
+
if (parts[0] === W3C_TRACEPARENT_VERSION && parts.length !== 4) {
|
|
309
|
+
return traceParentInfo
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const [version, traceId, parentId, flags] = parts
|
|
313
|
+
const isValid =
|
|
314
|
+
VERSION_VALID_RGX.test(version) &&
|
|
315
|
+
TRACEID_VALID_RGX.test(traceId) &&
|
|
316
|
+
PARENTID_VALID_RGX.test(parentId) &&
|
|
317
|
+
FLAGS_VALID_RGX.test(flags)
|
|
318
|
+
|
|
319
|
+
if (isValid) {
|
|
320
|
+
traceParentInfo.entryValid = true
|
|
321
|
+
traceParentInfo.version = version
|
|
322
|
+
traceParentInfo.traceId = traceId
|
|
323
|
+
traceParentInfo.parentId = parentId
|
|
324
|
+
traceParentInfo.flags = flags
|
|
177
325
|
}
|
|
178
|
-
|
|
326
|
+
|
|
327
|
+
return traceParentInfo
|
|
179
328
|
}
|
|
180
329
|
|
|
330
|
+
// Not used now, but will be useful when traceparent has more flags
|
|
181
331
|
parseFlagsHex(flags) {
|
|
182
332
|
const flagsInt = parseInt(flags, 16)
|
|
183
333
|
return Object.keys(FLAGS).reduce((o, key) => {
|
|
@@ -197,60 +347,120 @@ class TraceContext {
|
|
|
197
347
|
}
|
|
198
348
|
|
|
199
349
|
/**
|
|
200
|
-
* @typedef
|
|
350
|
+
* @typedef TraceStateData
|
|
201
351
|
* @property {boolean} entryFound - Whether a New Relic tracestate string with a match
|
|
202
352
|
* trusted account key field is found
|
|
203
353
|
* @property {boolean} entryValid - Whether the matching NR tracestate string is valid
|
|
354
|
+
* @property {string} entryInvalidReason - Why the tracestate did not validate
|
|
204
355
|
* @property {Intrinsics} intrinsics - All the parts of the New Relic tracestate string
|
|
205
356
|
* parsed and split out into an object
|
|
206
357
|
* @property {string} newTraceState - The raw tracestate without the New Relic entry
|
|
358
|
+
* @property {array} vendors - All the vendor strings found in the tracestate
|
|
207
359
|
*/
|
|
208
360
|
|
|
209
361
|
/**
|
|
210
362
|
* Accepts a W3C tracestate header string and returns an object with information about
|
|
211
|
-
* the validity and intrinsics of the tracestate
|
|
363
|
+
* the validity and intrinsics of the parsed tracestate string
|
|
212
364
|
*
|
|
213
365
|
* @param {string} tracestate - A raw W3C tracestate header string
|
|
214
|
-
* @returns {
|
|
366
|
+
* @returns {TraceStateData} returns an object with validation information and
|
|
215
367
|
* instrinsics on any relevant New Relic tracestate strings found
|
|
216
368
|
*/
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
369
|
+
_validateAndParseTraceStateHeader(tracestate) {
|
|
370
|
+
let tsd = {
|
|
371
|
+
entryFound: false,
|
|
372
|
+
entryValid: undefined,
|
|
373
|
+
entryInvalidReason: undefined,
|
|
374
|
+
traceStateValid: undefined,
|
|
375
|
+
intrinsics: undefined,
|
|
376
|
+
newTraceState: undefined,
|
|
377
|
+
vendors: undefined
|
|
378
|
+
}
|
|
224
379
|
|
|
225
380
|
// See if there's a New Relic Trace State
|
|
226
381
|
const trustedKey = this.transaction.agent.config.trusted_account_key
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
})
|
|
230
|
-
if (nrVendorIndex >= 0) {
|
|
231
|
-
entryFound = true
|
|
382
|
+
const hasTrustKey = Boolean(trustedKey)
|
|
383
|
+
const expectedNrKey = `${trustedKey}@nr`
|
|
232
384
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
385
|
+
if (!hasTrustKey) {
|
|
386
|
+
logger.debug(
|
|
387
|
+
'Unable to accept any New Relic tracestate list members. ' +
|
|
388
|
+
'Missing trusted_account_key. ' +
|
|
389
|
+
'This may occur if a trace is received prior to the agent fully starting.'
|
|
390
|
+
)
|
|
236
391
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
392
|
+
this.transaction.agent.recordSupportability('TraceContext/TraceState/Accept/Exception')
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
let nrTraceStateValue = null
|
|
396
|
+
|
|
397
|
+
const finalListMembers = []
|
|
398
|
+
const vendors = []
|
|
399
|
+
|
|
400
|
+
const incomingListMembers = tracestate.split(',')
|
|
401
|
+
for (let i = 0; i < incomingListMembers.length; i++) {
|
|
402
|
+
const listMember = incomingListMembers[i].trim()
|
|
403
|
+
|
|
404
|
+
// Multiple tracestate headers may get combined. Empty headers
|
|
405
|
+
// can result in a header such as tracestate: 'foo=1, ' which
|
|
406
|
+
// should still be considered valid with the empty item discarded.
|
|
407
|
+
if (listMember !== '') {
|
|
408
|
+
const listMemberParts = listMember.split('=')
|
|
409
|
+
if (listMemberParts.length !== 2) {
|
|
410
|
+
tsd.traceStateValid = false
|
|
411
|
+
logger.debug('Unable to parse tracestate list members.')
|
|
412
|
+
this.transaction.agent
|
|
413
|
+
.recordSupportability('TraceContext/TraceState/Parse/Exception/ListMember')
|
|
414
|
+
|
|
415
|
+
return tsd
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const [vendorKey, vendorValue] = listMemberParts
|
|
419
|
+
if (hasTrustKey && (vendorKey === expectedNrKey)) {
|
|
420
|
+
// Matching members do not get added to vendors.
|
|
421
|
+
// We'll replace the first valid entry and drop the rest
|
|
422
|
+
// (which would be invalid members if they exist).
|
|
423
|
+
|
|
424
|
+
// We only want the first one.
|
|
425
|
+
nrTraceStateValue = nrTraceStateValue || vendorValue
|
|
426
|
+
} else {
|
|
427
|
+
vendors.push(vendorKey)
|
|
428
|
+
|
|
429
|
+
finalListMembers.push(listMember)
|
|
430
|
+
}
|
|
242
431
|
}
|
|
243
432
|
}
|
|
244
433
|
|
|
245
|
-
|
|
246
|
-
|
|
434
|
+
tsd.traceStateValid = true
|
|
435
|
+
|
|
436
|
+
// Rebuild potentially cleaned-up listmembers
|
|
437
|
+
tsd.newTraceState = finalListMembers.join(',')
|
|
438
|
+
|
|
439
|
+
if (vendors.length > 0) {
|
|
440
|
+
tsd.vendors = vendors.join(',')
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (!hasTrustKey) {
|
|
444
|
+
return tsd
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (nrTraceStateValue) {
|
|
448
|
+
tsd.entryFound = true
|
|
247
449
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
450
|
+
const intrinsicsValidation = this._validateAndParseIntrinsics(nrTraceStateValue)
|
|
451
|
+
if (intrinsicsValidation.entryValid) {
|
|
452
|
+
tsd.entryValid = true
|
|
453
|
+
tsd.intrinsics = intrinsicsValidation
|
|
454
|
+
} else {
|
|
455
|
+
tsd.entryInvalidReason = intrinsicsValidation.invalidReason
|
|
456
|
+
tsd.entryValid = false
|
|
457
|
+
}
|
|
458
|
+
} else {
|
|
459
|
+
// TraceParent has been accepted, but no trustedKey on tracestate
|
|
460
|
+
this.transaction.agent.recordSupportability('TraceContext/TraceState/NoNrEntry')
|
|
253
461
|
}
|
|
462
|
+
|
|
463
|
+
return tsd
|
|
254
464
|
}
|
|
255
465
|
|
|
256
466
|
/**
|
|
@@ -265,66 +475,102 @@ class TraceContext {
|
|
|
265
475
|
* @property {number} priority - floating point of the priority the agent should use,
|
|
266
476
|
* rounded to 6 decimal places
|
|
267
477
|
* @property {number} timestamp - when the payload was created, milliseconds since epoch
|
|
478
|
+
* @property {boolean} entryValid - if all entries in the Intrinsics object is valid
|
|
268
479
|
*/
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
intrinsics
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Accepts a New Relic intrinsics string and returls a validation object w/
|
|
483
|
+
* the validity and intrinsics of the tracestate
|
|
484
|
+
*
|
|
485
|
+
* @param {string} nrTracestateValue - The value part of a New Relic tracestate entry
|
|
486
|
+
* @returns {Intrinsics} returns an Intrinsics object with validation information and
|
|
487
|
+
* instrinsics on any relevant New Relic tracestate strings found
|
|
488
|
+
*/
|
|
489
|
+
_validateAndParseIntrinsics(nrTracestateValue) {
|
|
490
|
+
const intrinsics = this._parseIntrinsics(nrTracestateValue)
|
|
491
|
+
|
|
492
|
+
// Functions that return true when the field is invalid
|
|
493
|
+
const isNull = v => v == null
|
|
494
|
+
const intrinsicInvalidations = {
|
|
495
|
+
version: isNaN, // required, int
|
|
496
|
+
parentType: isNull, // required, str
|
|
497
|
+
accountId: isNull, // required, str
|
|
498
|
+
appId: isNull, // required, str
|
|
499
|
+
sampled: v => v == null ? false : isNaN(v), // not required, int
|
|
500
|
+
priority: v => v == null ? false : isNaN(v), // not required, float
|
|
501
|
+
timestamp: isNaN // required, int
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
// If a field is found invalid, flag the entry as not valid
|
|
506
|
+
intrinsics.entryValid = true
|
|
507
|
+
for (const key of Object.keys(intrinsicInvalidations)) {
|
|
508
|
+
const invalidation = intrinsicInvalidations[key]
|
|
509
|
+
if (invalidation && invalidation(intrinsics[key])) {
|
|
510
|
+
intrinsics.entryValid = false
|
|
511
|
+
intrinsics.entryInvalidReason = `${key} failed invalidation test`
|
|
512
|
+
}
|
|
295
513
|
}
|
|
296
514
|
|
|
297
|
-
//
|
|
298
|
-
if (intrinsics.
|
|
299
|
-
|
|
300
|
-
if (isNaN(priority)) return false
|
|
301
|
-
intrinsics.priority = priority
|
|
515
|
+
// Convert to types expected by Transaction
|
|
516
|
+
if (intrinsics.sampled != null) {
|
|
517
|
+
intrinsics.sampled = Boolean(intrinsics.sampled)
|
|
302
518
|
}
|
|
303
519
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
if (isNaN(timestamp)) return false
|
|
307
|
-
intrinsics.timestamp = timestamp
|
|
520
|
+
intrinsics.parentType = PARENT_TYPES[intrinsics.parentType]
|
|
521
|
+
if (!intrinsics.parentType) intrinsics.entryValid = false
|
|
308
522
|
|
|
309
523
|
return intrinsics
|
|
310
524
|
}
|
|
311
525
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
526
|
+
/**
|
|
527
|
+
* Parses intrinsics of a New Relic tracestate entry's value
|
|
528
|
+
*/
|
|
529
|
+
_parseIntrinsics(nrTracestateValue) {
|
|
530
|
+
const intrinsics = this._extractTraceStateIntrinsics(nrTracestateValue)
|
|
531
|
+
|
|
532
|
+
const intrinsicConversions = {
|
|
533
|
+
version: parseInt,
|
|
534
|
+
parentType: parseInt,
|
|
535
|
+
|
|
536
|
+
// these two can be null, don't try to parse a null
|
|
537
|
+
sampled: v => v == null ? v : parseInt(v, 10),
|
|
538
|
+
priority: v => v == null ? v : parseFloat(v),
|
|
539
|
+
|
|
540
|
+
timestamp: parseInt,
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
for (const key of Object.keys(intrinsicConversions)) {
|
|
544
|
+
const conversion = intrinsicConversions[key]
|
|
545
|
+
if (conversion) {
|
|
546
|
+
intrinsics[key] = conversion(intrinsics[key])
|
|
547
|
+
}
|
|
325
548
|
}
|
|
326
549
|
|
|
327
|
-
return
|
|
550
|
+
return intrinsics
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
_extractTraceStateIntrinsics(nrTracestate) {
|
|
554
|
+
const splitValues = nrTracestate.split('-')
|
|
555
|
+
|
|
556
|
+
// convert empty strings to null
|
|
557
|
+
splitValues.forEach((value, i) => {
|
|
558
|
+
if (value === '') splitValues[i] = null
|
|
559
|
+
})
|
|
560
|
+
|
|
561
|
+
const intrinsics = {
|
|
562
|
+
version: splitValues[0],
|
|
563
|
+
parentType: splitValues[1],
|
|
564
|
+
accountId: splitValues[2],
|
|
565
|
+
appId: splitValues[3],
|
|
566
|
+
spanId: splitValues[4],
|
|
567
|
+
transactionId: splitValues[5],
|
|
568
|
+
sampled: splitValues[6],
|
|
569
|
+
priority: splitValues[7],
|
|
570
|
+
timestamp: splitValues[8]
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
return intrinsics
|
|
328
574
|
}
|
|
329
575
|
}
|
|
330
576
|
|