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
@@ -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 parentTypes = ['App', 'Browser', 'Mobile']
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 accpeting, producing, and validating w3c tracecontext headers.
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.version = '00'
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
- get parentId() {
40
- // If spans enabled, use the segment ID
41
- if (!this._parentId && this.transaction.agent.config.span_events.enabled) {
42
- this._parentId = this.transaction.agent.tracer.getSegment().id
43
- }
44
-
45
- // Generate a new ID if spans are not enabled
46
- if (!this._parentId) {
47
- this._parentId = hashes.makeId()
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
- this._parentId = this._parentId.padStart(16, '0')
51
- return this._parentId
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
- get traceId() {
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
- get traceparent() {
62
- return `${this.version}-${this.traceId}-${this.parentId}-${this.createFlagsHex()}`
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 = '0'
69
- const parentType = '0' // '0' is App, which node agent will always be
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
- const spanId = this.transaction.agent.tracer.getSegment().id
73
- const transactionId = this.transaction.id
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 TraceContext headers
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
- // This gets the transaction object to calculate priority and set the sampled property
102
- const traceContextHeaders = this.createTraceContextPayload()
103
- Object.assign(headers, traceContextHeaders)
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
- if (!traceparent)
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
- const parsedParent = this._validateTraceParentHeader(traceparent)
122
-
123
- if (parsedParent) {
196
+ if (parsedParent.entryValid) {
124
197
  logger.trace('Accepted traceparent for transaction %s', this.transaction.id)
125
- this.version = parsedParent.version
126
- this._traceId = parsedParent.traceId
127
- this._parentId = parsedParent.parentId
128
- // Ignore the sampled flag for now
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.error('Invalid traceparent for transaction %s: %s',
132
- this.transaction.id, traceparent)
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
- return
222
+
223
+ return traceContextData
141
224
  }
142
225
 
143
- const parsedState = this._validateTraceStateHeader(tracestate)
226
+ const parsedState = this._validateAndParseTraceStateHeader(tracestate)
144
227
 
145
- // Keep the raw, non-NewRelic trace state string stored so that we can propogate it
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
- if (parsedState && parsedState.entryValid) {
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
- this._traceStateIntrinsics = parsedState.intrinsics
151
- } else {
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 {(boolean|Object)} returns false if invalid or an Object with the
163
- * traceparent data
289
+ * @returns {Object} returns an Object with the traceparent data and validation info
164
290
  */
165
- _validateTraceParentHeader(traceparent) {
166
- // eslint-disable-next-line max-len
167
- const namedRgx = /^([a-f0-9]{2})-((?![0]{32})[a-f0-9]{32})-((?![0]{16})[a-f0-9]{16})-([a-f0-9]{2})$/
168
- const match = traceparent.match(namedRgx)
169
- if (match) {
170
- const matchNames = {
171
- version: match[1],
172
- traceId: match[2],
173
- parentId: match[3],
174
- flags: match[4]
175
- }
176
- return matchNames
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
- return false
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 TraceStateValidation
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 {TraceStateValidation} returns an object with validation information and
366
+ * @returns {TraceStateData} returns an object with validation information and
215
367
  * instrinsics on any relevant New Relic tracestate strings found
216
368
  */
217
- _validateTraceStateHeader(tracestate) {
218
- const keyVals = tracestate.split(',')
219
-
220
- let entryFound = false
221
- let entryValid = false
222
- let intrinsics = undefined
223
- let newTraceState = undefined
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 nrVendorIndex = keyVals.findIndex((vendor) => {
228
- return vendor.startsWith(`${trustedKey}@nr`)
229
- })
230
- if (nrVendorIndex >= 0) {
231
- entryFound = true
382
+ const hasTrustKey = Boolean(trustedKey)
383
+ const expectedNrKey = `${trustedKey}@nr`
232
384
 
233
- // Remove the new relic entry that we found from the trace state key vals array
234
- const nrTraceStateString = keyVals.splice(nrVendorIndex, 1)[0]
235
- const nrTraceStateValue = nrTraceStateString.split('=')[1]
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
- intrinsics = this._validateAndParseIntrinsics(nrTraceStateValue)
238
- if (intrinsics) {
239
- entryValid = true
240
- } else {
241
- entryValid = false
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
- // Rebuild the new strace state string without the new relic entry
246
- newTraceState = keyVals.join(',')
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
- return {
249
- entryFound,
250
- entryValid,
251
- intrinsics,
252
- newTraceState
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
- _validateAndParseIntrinsics(nrIntrinsics) {
270
- const intrinsics = this._extractTraceStateIntrinsics(nrIntrinsics)
271
- // version
272
- const version = parseInt(intrinsics.parentType, 10)
273
- if (intrinsics.version !== '0' || version === NaN) return false
274
- intrinsics.version = version
275
-
276
- // parentType
277
- const parentTypeId = parseInt(intrinsics.parentType, 10)
278
- if (isNaN(parentTypeId)) return false
279
- intrinsics.parentType = parentTypeId
280
-
281
- if (parentTypes[parentTypeId] === undefined) return false
282
-
283
- // account ID
284
- if (intrinsics.accountId === '') return false
285
-
286
- // app ID
287
- if (intrinsics.appId === '') return false
288
-
289
- // sampled
290
- if (intrinsics.sampled !== undefined) {
291
- const sampled = parseInt(intrinsics.sampled, 10)
292
- if (isNaN(sampled)) return false
293
- if (sampled < 0 || sampled > 1) return false
294
- intrinsics.sampled = sampled
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
- // priority
298
- if (intrinsics.priority !== undefined) {
299
- const priority = parseFloat(intrinsics.priority)
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
- // timestamp
305
- const timestamp = parseInt(intrinsics.timestamp, 10)
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
- _extractTraceStateIntrinsics(value) {
313
- const intrinsics = value.split('-')
314
-
315
- const intrinsicsObject = {
316
- version: intrinsics[0],
317
- parentType: intrinsics[1],
318
- accountId: intrinsics[2],
319
- appId: intrinsics[3],
320
- spanId: intrinsics[4],
321
- transactionId: intrinsics[5],
322
- sampled: intrinsics[6],
323
- priority: intrinsics[7],
324
- timestamp: intrinsics[8]
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 intrinsicsObject
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