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
@@ -0,0 +1,128 @@
1
+ {
2
+ "alb": {
3
+ "attributes": {},
4
+ "name": "alb",
5
+ "required_keys": [
6
+ "httpMethod",
7
+ "requestContext.elb"
8
+ ]
9
+ },
10
+ "apiGateway": {
11
+ "attributes": {
12
+ "aws.lambda.eventSource.accountId": "requestContext.accountId",
13
+ "aws.lambda.eventSource.apiId": "requestContext.apiId",
14
+ "aws.lambda.eventSource.resourceId": "requestContext.resourceId",
15
+ "aws.lambda.eventSource.resourcePath": "requestContext.resourcePath",
16
+ "aws.lambda.eventSource.stage": "requestContext.stage"
17
+ },
18
+ "name": "apiGateway",
19
+ "required_keys": [
20
+ "headers",
21
+ "httpMethod",
22
+ "path",
23
+ "requestContext",
24
+ "resource"
25
+ ]
26
+ },
27
+ "cloudFront": {
28
+ "attributes": {},
29
+ "name": "cloudFront",
30
+ "required_keys": [
31
+ "Records[0].cf"
32
+ ]
33
+ },
34
+ "cloudWatchScheduled": {
35
+ "attributes": {
36
+ "aws.lambda.eventSource.account": "account",
37
+ "aws.lambda.eventSource.id": "id",
38
+ "aws.lambda.eventSource.region": "region",
39
+ "aws.lambda.eventSource.resource": "resources[0]",
40
+ "aws.lambda.eventSource.time": "time"
41
+ },
42
+ "name": "cloudWatch_scheduled",
43
+ "required_keys": [
44
+ "detail-type",
45
+ "source"
46
+ ]
47
+ },
48
+ "dynamoStreams": {
49
+ "attributes": {
50
+ "aws.lambda.eventSource.length": "Records.length"
51
+ },
52
+ "name": "dynamo_streams",
53
+ "required_keys": [
54
+ "Records[0].dynamodb"
55
+ ]
56
+ },
57
+ "firehose": {
58
+ "attributes": {
59
+ "aws.lambda.eventSource.length": "records.length",
60
+ "aws.lambda.eventSource.region": "region"
61
+ },
62
+ "name": "firehose",
63
+ "required_keys": [
64
+ "deliveryStreamArn",
65
+ "records[0].kinesisRecordMetadata"
66
+ ]
67
+ },
68
+ "kinesis": {
69
+ "attributes": {
70
+ "aws.lambda.eventSource.length": "Records.length",
71
+ "aws.lambda.eventSource.region": "Records[0].awsRegion"
72
+ },
73
+ "name": "kinesis",
74
+ "required_keys": [
75
+ "Records[0].kinesis"
76
+ ]
77
+ },
78
+ "s3": {
79
+ "attributes": {
80
+ "aws.lambda.eventSource.bucketName": "Records[0].s3.bucket.name",
81
+ "aws.lambda.eventSource.eventName": "Records[0].eventName",
82
+ "aws.lambda.eventSource.eventTime": "Records[0].eventTime",
83
+ "aws.lambda.eventSource.length": "Records.length",
84
+ "aws.lambda.eventSource.objectKey": "Records[0].s3.object.key",
85
+ "aws.lambda.eventSource.objectSequencer": "Records[0].s3.object.sequencer",
86
+ "aws.lambda.eventSource.objectSize": "Records[0].s3.object.size",
87
+ "aws.lambda.eventSource.region": "Records[0].awsRegion"
88
+ },
89
+ "name": "s3",
90
+ "required_keys": [
91
+ "Records[0].s3"
92
+ ]
93
+ },
94
+ "ses": {
95
+ "attributes": {
96
+ "aws.lambda.eventSource.date": "Records[0].ses.mail.commonHeaders.date",
97
+ "aws.lambda.eventSource.length": "Records.length",
98
+ "aws.lambda.eventSource.messageId": "Records[0].ses.mail.commonHeaders.messageId",
99
+ "aws.lambda.eventSource.returnPath": "Records[0].ses.mail.commonHeaders.returnPath"
100
+ },
101
+ "name": "ses",
102
+ "required_keys": [
103
+ "Records[0].ses"
104
+ ]
105
+ },
106
+ "sns": {
107
+ "attributes": {
108
+ "aws.lambda.eventSource.length": "Records.length",
109
+ "aws.lambda.eventSource.messageId": "Records[0].Sns.MessageId",
110
+ "aws.lambda.eventSource.timestamp": "Records[0].Sns.Timestamp",
111
+ "aws.lambda.eventSource.topicArn": "Records[0].Sns.TopicArn",
112
+ "aws.lambda.eventSource.type": "Records[0].Sns.Type"
113
+ },
114
+ "name": "sns",
115
+ "required_keys": [
116
+ "Records[0].Sns"
117
+ ]
118
+ },
119
+ "sqs": {
120
+ "attributes": {
121
+ "aws.lambda.eventSource.length": "Records.length"
122
+ },
123
+ "name": "sqs",
124
+ "required_keys": [
125
+ "Records[0].receiptHandle"
126
+ ]
127
+ }
128
+ }
@@ -6,12 +6,7 @@ const logger = require('../logger').child({component: 'TransactionShim'})
6
6
  const Shim = require('./shim')
7
7
  const Transaction = require('../transaction')
8
8
  const util = require('util')
9
- const TRACE_CONTEXT_PARENT_HEADER
10
- = require('../transaction/tracecontext').TRACE_CONTEXT_PARENT_HEADER
11
- const TRACE_CONTEXT_STATE_HEADER
12
- = require('../transaction/tracecontext').TRACE_CONTEXT_STATE_HEADER
13
9
 
14
- const DISTRIBUTED_TRACE_HEADER = 'newrelic'
15
10
  const HTTP_CAT_ID_HEADER = 'X-NewRelic-Id'
16
11
  const MQ_CAT_ID_HEADER = 'NewRelicID'
17
12
  const MATCH_CAT_ID_HEADER = new RegExp(
@@ -32,7 +27,6 @@ const MATCH_CAT_APP_DATA_HEADER = new RegExp(
32
27
  )
33
28
 
34
29
  const TRANSACTION_TYPES_SET = Transaction.TYPES_SET
35
- const TRANSPORT_TYPES_SET = Transaction.TRANSPORT_TYPES_SET
36
30
 
37
31
  /**
38
32
  * Constructs a transaction managing shim.
@@ -282,26 +276,11 @@ function handleCATHeaders(headers, segment, transportType) {
282
276
  this.logger.trace('Not adding CAT reply header, not in an active transaction.')
283
277
  return
284
278
  }
285
- var tx = segment.transaction
286
279
 
287
- // Ensure this is a valid transport type.
288
- transportType = transportType || Transaction.TRANSPORT_TYPES.UNKNOWN
289
- if (!TRANSPORT_TYPES_SET[transportType]) {
290
- this.logger.debug('Unknown transport type: %j', transportType)
291
- transportType = Transaction.TRANSPORT_TYPES.UNKNOWN
292
- }
293
- if (this.agent.config.feature_flag.dt_format_w3c) {
294
- const traceparent = headers[TRACE_CONTEXT_PARENT_HEADER]
295
- const tracestate = headers[TRACE_CONTEXT_STATE_HEADER]
280
+ const transaction = segment.transaction
296
281
 
297
- if (traceparent && tracestate) {
298
- tx.traceContext.acceptTraceContextPayload(traceparent, tracestate)
299
- }
300
- } else if (this.agent.config.distributed_tracing.enabled) {
301
- const payload = headers[DISTRIBUTED_TRACE_HEADER]
302
- if (payload) {
303
- tx.acceptDistributedTracePayload(payload, transportType)
304
- }
282
+ if (this.agent.config.distributed_tracing.enabled) {
283
+ transaction.acceptDistributedTraceHeaders(transportType, headers)
305
284
  return
306
285
  }
307
286
 
@@ -323,12 +302,12 @@ function handleCATHeaders(headers, segment, transportType) {
323
302
  }
324
303
 
325
304
  if (catId && transactionData) {
326
- cat.handleCatHeaders(catId, transactionData, this.agent.config.encoding_key, tx)
327
- if (tx.incomingCatId) {
305
+ cat.handleCatHeaders(catId, transactionData, this.agent.config.encoding_key, transaction)
306
+ if (transaction.incomingCatId) {
328
307
  this.logger.trace(
329
308
  'Got inbound CAT headers in transaction %s from %s',
330
- tx.id,
331
- tx.incomingCatId
309
+ transaction.id,
310
+ transaction.incomingCatId
332
311
  )
333
312
  }
334
313
  }
@@ -378,11 +357,9 @@ function insertCATRequestHeaders(headers, useAlternateHeaderNames) {
378
357
  }
379
358
 
380
359
  if (distributedTracingEnabled) {
381
- const dtPayload = tx.createDistributedTracePayload().httpSafe()
382
- if (dtPayload) {
383
- headers[DISTRIBUTED_TRACE_HEADER] = dtPayload
384
- this.logger.trace('Added Distributed Tracing headers for transaction %s', tx.id)
385
- }
360
+ // TODO: Should probably honor SHIM_SYMBOLS.DISABLE_DT.
361
+ // TODO: Official testing and support.
362
+ tx.insertDistributedTraceHeaders(headers)
386
363
  } else {
387
364
  if (!this.agent.config.encoding_key) {
388
365
  this.logger.warn('Missing encoding key, not adding CAT headers!')
@@ -53,7 +53,7 @@ class SpanEventAggregator extends EventAggregator {
53
53
  *
54
54
  * @return {bool} True if the segment was added, or false if it was discarded.
55
55
  */
56
- addSegment(segment, parentId) {
56
+ addSegment(segment, parentId, isRoot) {
57
57
  // Check if the priority would be accepted before creating the event object.
58
58
  const tx = segment.transaction
59
59
 
@@ -63,7 +63,7 @@ class SpanEventAggregator extends EventAggregator {
63
63
 
64
64
  return false
65
65
  }
66
- const span = SpanEvent.fromSegment(segment, parentId || null)
66
+ const span = SpanEvent.fromSegment(segment, parentId || null, isRoot)
67
67
  return this.add(span, tx.priority)
68
68
  }
69
69
  }
@@ -12,6 +12,7 @@ const CATEGORIES = {
12
12
  DATASTORE: 'datastore',
13
13
  GENERIC: 'generic'
14
14
  }
15
+
15
16
  const EMPTY_USER_ATTRS = Object.freeze(Object.create(null))
16
17
 
17
18
  /**
@@ -33,6 +34,8 @@ class SpanIntrinsics {
33
34
  this.duration = null
34
35
  this['nr.entryPoint'] = null
35
36
  this['span.kind'] = null
37
+ this.trustedParentId = null
38
+ this.tracingVendors = null
36
39
  }
37
40
  }
38
41
 
@@ -46,7 +49,8 @@ class SpanIntrinsics {
46
49
  * @class
47
50
  */
48
51
  class SpanEvent {
49
- constructor(attributes) {
52
+ constructor(attributes, customAttributes) {
53
+ this.customAttributes = customAttributes
50
54
  this.attributes = attributes
51
55
  this.intrinsics = new SpanIntrinsics()
52
56
  }
@@ -74,21 +78,22 @@ class SpanEvent {
74
78
  *
75
79
  * @return {SpanEvent} The constructed event.
76
80
  */
77
- static fromSegment(segment, parentId = null) {
81
+ static fromSegment(segment, parentId = null, isRoot = false) {
78
82
  const attributes = segment.attributes.get(DESTINATIONS.SPAN_EVENT)
83
+ const customAttributes = segment.customAttributes.get(DESTINATIONS.SPAN_EVENT)
79
84
 
80
85
  let span = null
81
86
  if (HttpSpanEvent.testSegment(segment)) {
82
- span = new HttpSpanEvent(attributes)
87
+ span = new HttpSpanEvent(attributes, customAttributes)
83
88
  } else if (DatastoreSpanEvent.testSegment(segment)) {
84
- span = new DatastoreSpanEvent(attributes)
89
+ span = new DatastoreSpanEvent(attributes, customAttributes)
85
90
  } else {
86
- span = new SpanEvent(attributes)
91
+ span = new SpanEvent(attributes, customAttributes)
87
92
  }
88
93
 
89
94
  const tx = segment.transaction
90
95
 
91
- span.intrinsics.traceId = tx.getTraceId()
96
+ span.intrinsics.traceId = tx.traceId
92
97
  span.intrinsics.guid = segment.id
93
98
  span.intrinsics.parentId = parentId
94
99
  span.intrinsics.transactionId = tx.id
@@ -96,6 +101,13 @@ class SpanEvent {
96
101
  span.intrinsics.priority = tx.priority
97
102
  span.intrinsics.name = segment.name
98
103
 
104
+ if (isRoot) {
105
+ span.intrinsics.trustedParentId = tx.traceContext.trustedParentId
106
+ if (tx.traceContext.tracingVendors) {
107
+ span.intrinsics.tracingVendors = tx.traceContext.tracingVendors
108
+ }
109
+ }
110
+
99
111
  // Only set this if it will be `true`. Must be `null` otherwise.
100
112
  if (tx.baseSegment === segment) {
101
113
  span.intrinsics['nr.entryPoint'] = true
@@ -111,11 +123,21 @@ class SpanEvent {
111
123
  toJSON() {
112
124
  return [
113
125
  _filterNulls(this.intrinsics),
114
- EMPTY_USER_ATTRS,
126
+ this.customAttributes ?
127
+ _filterNulls(this.customAttributes) :
128
+ EMPTY_USER_ATTRS,
115
129
  _filterNulls(this.attributes)
116
130
  ]
117
131
  }
118
132
 
133
+ addCustomAttribute(key, value, truncateExempt = false) {
134
+ const {attributeFilter} = Config.getInstance()
135
+ const dest = attributeFilter.filterSegment(DESTINATIONS.SPAN_EVENT, key)
136
+ if (dest & DESTINATIONS.SPAN_EVENT) {
137
+ this.customAttributes[key] = truncateExempt ? value : _truncate(value)
138
+ }
139
+ }
140
+
119
141
  addAttribute(key, value, truncateExempt = false) {
120
142
  const {attributeFilter} = Config.getInstance()
121
143
  const dest = attributeFilter.filterSegment(DESTINATIONS.SPAN_EVENT, key)
@@ -132,8 +154,8 @@ class SpanEvent {
132
154
  * @class
133
155
  */
134
156
  class HttpSpanEvent extends SpanEvent {
135
- constructor(attributes) {
136
- super(attributes)
157
+ constructor(attributes, customAttributes) {
158
+ super(attributes, customAttributes)
137
159
 
138
160
  this.intrinsics.category = CATEGORIES.HTTP
139
161
  this.intrinsics.component = attributes.library || HTTP_LIBRARY
@@ -166,8 +188,8 @@ class HttpSpanEvent extends SpanEvent {
166
188
  * @class.
167
189
  */
168
190
  class DatastoreSpanEvent extends SpanEvent {
169
- constructor(attributes) {
170
- super(attributes)
191
+ constructor(attributes, customAttributes) {
192
+ super(attributes, customAttributes)
171
193
 
172
194
  this.intrinsics.category = CATEGORIES.DATASTORE
173
195
  this.intrinsics['span.kind'] = CLIENT_KIND
@@ -1,16 +1,21 @@
1
1
  'use strict'
2
- var logger = require('../logger').child({component: 'transactionHandle'})
3
- var DistributedTracePayloadStub = require('./dt-payload').Stub
4
2
 
5
- module.exports = class TransactionHandle {
3
+ const util = require('util')
4
+ const logger = require('../logger').child({component: 'transactionHandle'})
5
+ const DistributedTracePayloadStub = require('./dt-payload').Stub
6
+
7
+ const NAMES = require('../../lib/metrics/names')
8
+
9
+ class TransactionHandle {
6
10
  /**
7
11
  * A light representation of a transaction instance, returned by calling
8
12
  * {@link API#getTransaction}.
9
13
  *
10
14
  * @constructor
11
15
  */
12
- constructor(transaction) {
16
+ constructor(transaction, metrics) {
13
17
  this._transaction = transaction
18
+ this._metrics = metrics
14
19
  }
15
20
 
16
21
  /**
@@ -42,6 +47,29 @@ module.exports = class TransactionHandle {
42
47
  return this._transaction.isSampled()
43
48
  }
44
49
 
50
+ /**
51
+ * Parsing incoming headers for use in a distributed trace.
52
+ * W3C TraceContext format is preferred over the NewRelic DT format.
53
+ * NewRelic DT format will be used if no `traceparent` header is found.
54
+ * @param @param {string} [transportType='Unknown'] - The transport type that delivered the trace.
55
+ * @param {object} headers - Headers to search for supported formats. Keys must be lowercase.
56
+ */
57
+ acceptDistributedTraceHeaders(transportType, headers) {
58
+ incrementApiSupportMetric(this._metrics, 'acceptDistributedTraceHeaders')
59
+
60
+ return this._transaction.acceptDistributedTraceHeaders(transportType, headers)
61
+ }
62
+
63
+ /**
64
+ * Inserts distributed trace headers into the provided headers map.
65
+ * @param {Object} headers
66
+ */
67
+ insertDistributedTraceHeaders(headers) {
68
+ incrementApiSupportMetric(this._metrics, 'insertDistributedTraceHeaders')
69
+
70
+ return this._transaction.insertDistributedTraceHeaders(headers)
71
+ }
72
+
45
73
  /**
46
74
  *
47
75
  * Proxy method for Transaction#createDistrubtedTracePayload.
@@ -65,6 +93,39 @@ module.exports = class TransactionHandle {
65
93
  }
66
94
  }
67
95
 
96
+ // TODO: Fully remove functions in future Major release. v7.0.0?
97
+ TransactionHandle.prototype.acceptDistributedTracePayload = util.deprecate(
98
+ TransactionHandle.prototype.acceptDistributedTracePayload,
99
+ 'TransactionHandle#acceptDistributedTracePayload has been deprecated! ' +
100
+ 'Please use TransactionHandle#acceptDistributedTraceHeaders ' +
101
+ 'which supports multiple distributed trace formats.'
102
+ )
103
+
104
+ TransactionHandle.prototype.createDistributedTracePayload = util.deprecate(
105
+ TransactionHandle.prototype.createDistributedTracePayload,
106
+ 'TransactionHandle#createDistributedTracePayload has been deprecated! ' +
107
+ 'Please use TransactionHandle#insertDistributedTraceHeaders ' +
108
+ 'which supports multiple distributed trace formats.'
109
+ )
110
+
111
+ module.exports = TransactionHandle
112
+
113
+ function incrementApiSupportMetric(metrics, functionName) {
114
+ if (!metrics) {
115
+ logger.warnOnce(
116
+ 'Cannot add TransactionHandle API support metric. The metrics collection is missing.'
117
+ )
118
+ return
119
+ }
120
+
121
+ const metric = metrics.getOrCreateMetric(
122
+ NAMES.SUPPORTABILITY.TRANSACTION_API + '/' + functionName
123
+ )
124
+
125
+ metric.incrementCallCount()
126
+ return metric
127
+ }
128
+
68
129
  module.exports.Stub = class TransactionHandleStub {
69
130
  end(callback) {
70
131
  if (callback instanceof Function) {
@@ -93,4 +154,12 @@ module.exports.Stub = class TransactionHandleStub {
93
154
  "No transaction found when calling Transaction.acceptDistributedTracePayload"
94
155
  )
95
156
  }
157
+
158
+ acceptDistributedTraceHeaders() {
159
+ logger.debug("No transaction found when calling Transaction.acceptDistributedTraceHeaders")
160
+ }
161
+
162
+ insertDistributedTraceHeaders() {
163
+ logger.debug("No transaction found when calling Transaction.insertDistributedTraceHeaders")
164
+ }
96
165
  }