newrelic 6.7.0 → 6.10.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.
@@ -71,6 +71,13 @@ function wrapEmitWithTransaction(agent, emit, isHTTPS) {
71
71
  const segment = tracer.createSegment(request.url, recordWeb)
72
72
  segment.start()
73
73
 
74
+ if (request.method != null) {
75
+ segment.addSpanAttribute(
76
+ 'request.method',
77
+ request.method
78
+ )
79
+ }
80
+
74
81
  if (txInfo) {
75
82
  // Seed segment stack to enable parenting logic leveraged by
76
83
  // web framework instrumentations.
@@ -98,6 +105,11 @@ function wrapEmitWithTransaction(agent, emit, isHTTPS) {
98
105
  transaction.url
99
106
  )
100
107
 
108
+ segment.addSpanAttribute(
109
+ 'request.uri',
110
+ transaction.url
111
+ )
112
+
101
113
  // store the port on which this transaction runs
102
114
  if (this.address instanceof Function) {
103
115
  var address = this.address()
@@ -117,28 +129,30 @@ function wrapEmitWithTransaction(agent, emit, isHTTPS) {
117
129
  transaction.queueTime = Date.now() - queueTimeStamp
118
130
  }
119
131
 
132
+ const synthHeader = request.headers[NEWRELIC_SYNTHETICS_HEADER]
133
+
134
+ if (synthHeader && agent.config.trusted_account_ids && agent.config.encoding_key) {
135
+ handleSyntheticsHeader(
136
+ synthHeader,
137
+ agent.config.encoding_key,
138
+ agent.config.trusted_account_ids,
139
+ transaction
140
+ )
141
+ }
142
+
120
143
  if (agent.config.distributed_tracing.enabled) {
121
144
  // Node http headers are automatically lowercase
122
145
  transaction.acceptDistributedTraceHeaders(transport, request.headers)
123
146
  } else if (agent.config.cross_application_tracer.enabled) {
124
- var encKey = agent.config.encoding_key
125
- var incomingCatId = request.headers[NEWRELIC_ID_HEADER]
126
- var obfTransaction = request.headers[NEWRELIC_TRANSACTION_HEADER]
127
- var synthHeader = request.headers[NEWRELIC_SYNTHETICS_HEADER]
128
- if (encKey) {
129
- cat.handleCatHeaders(incomingCatId, obfTransaction, encKey, transaction)
147
+ const incomingCatId = request.headers[NEWRELIC_ID_HEADER]
148
+ const obfTransaction = request.headers[NEWRELIC_TRANSACTION_HEADER]
149
+
150
+ if (agent.config.encoding_key) {
151
+ cat.handleCatHeaders(incomingCatId, obfTransaction, agent.config.encoding_key, transaction)
130
152
  if (transaction.incomingCatId) {
131
153
  logger.trace('Got inbound request CAT headers in transaction %s',
132
154
  transaction.id)
133
155
  }
134
- if (synthHeader && agent.config.trusted_account_ids) {
135
- handleSyntheticsHeader(
136
- synthHeader,
137
- encKey,
138
- agent.config.trusted_account_ids,
139
- transaction
140
- )
141
- }
142
156
  }
143
157
  }
144
158
 
@@ -170,6 +184,8 @@ function wrapEmitWithTransaction(agent, emit, isHTTPS) {
170
184
  'http.statusCode',
171
185
  responseCode
172
186
  )
187
+
188
+ segment.addSpanAttribute('http.statusCode', responseCode)
173
189
 
174
190
  /*
175
191
  TODO: remove with next major release
@@ -190,6 +206,8 @@ function wrapEmitWithTransaction(agent, emit, isHTTPS) {
190
206
  response.statusMessage
191
207
  )
192
208
 
209
+ segment.addSpanAttribute('http.statusText', response.statusMessage)
210
+
193
211
  /*
194
212
  TODO: remove with next major release
195
213
  httpResponseMessage attribute is deprecated
@@ -4,7 +4,7 @@ var EventEmitter = require('events').EventEmitter
4
4
  var util = require('util')
5
5
  var logger = require('../logger').child({component: 'metric_normalizer'})
6
6
  var deepEqual = require('../util/deep-equal')
7
- var Rule = require('./normalizer/rule')
7
+ var NormalizerRule = require('./normalizer/rule')
8
8
  var NAMES = require('../metrics/names.js')
9
9
 
10
10
 
@@ -82,7 +82,7 @@ MetricNormalizer.prototype.load = function load(json) {
82
82
 
83
83
  json.forEach(function cb_forEach(ruleJSON) {
84
84
  // no need to add the same rule twice
85
- var rule = new Rule(ruleJSON)
85
+ var rule = new NormalizerRule(ruleJSON)
86
86
  if (!this.rules.find(deepEqual.bind(null, rule))) {
87
87
  this.rules.push(rule)
88
88
  logger.trace("Loaded %s normalization rule: %s", this.type, rule)
@@ -142,9 +142,9 @@ MetricNormalizer.prototype.loadFromConfig = function loadFromConfig() {
142
142
  : r.precedence > json.eval_order
143
143
  })
144
144
  if (insert === -1) {
145
- this.rules.push(new Rule(json))
145
+ this.rules.push(new NormalizerRule(json))
146
146
  } else {
147
- this.rules.splice(insert, 0, new Rule(json))
147
+ this.rules.splice(insert, 0, new NormalizerRule(json))
148
148
  }
149
149
  }, this)
150
150
  }
@@ -186,7 +186,7 @@ MetricNormalizer.prototype.addSimple = function addSimple(pattern, name) {
186
186
  json.ignore = true
187
187
  }
188
188
 
189
- this.rules.unshift(new Rule(json))
189
+ this.rules.unshift(new NormalizerRule(json))
190
190
  }
191
191
 
192
192
  /**
@@ -0,0 +1,196 @@
1
+ 'use strict'
2
+
3
+ const Config = require('./config')
4
+ const logger = require('./logger').child({component: 'attributes'})
5
+ const isValidType = require('./util/attribute-types')
6
+ const byteUtils = require('./util/byte-limit')
7
+ const properties = require('./util/properties')
8
+
9
+ const ATTRIBUTE_PRIORITY = {
10
+ HIGH: Infinity,
11
+ LOW: -Infinity
12
+ }
13
+
14
+ class PrioritizedAttributes {
15
+ constructor(scope, limit = Infinity) {
16
+ this.filter = makeFilter(scope)
17
+ this.limit = limit
18
+
19
+ this.attributes = new Map()
20
+ }
21
+
22
+
23
+ isValidLength(str) {
24
+ return typeof str === 'number' || byteUtils.isValidLength(str, 255)
25
+ }
26
+
27
+ _set(destinations, key, value, truncateExempt, priority) {
28
+ this.attributes.set(key, {value, destinations, truncateExempt, priority})
29
+ }
30
+
31
+ get(dest) {
32
+ const attrs = Object.create(null)
33
+
34
+ for (let [key, attr] of this.attributes) {
35
+ if (!(attr.destinations & dest)) {
36
+ continue
37
+ }
38
+
39
+ attrs[key] = typeof attr.value === 'string' && !attr.truncateExempt
40
+ ? byteUtils.truncate(attr.value, 255)
41
+ : attr.value
42
+ }
43
+
44
+ return attrs
45
+ }
46
+
47
+ has(key) {
48
+ this.attributes.has(key)
49
+ }
50
+
51
+ reset() {
52
+ this.attributes = new Map()
53
+ }
54
+
55
+ addAttribute(
56
+ destinations,
57
+ key,
58
+ value,
59
+ truncateExempt = false,
60
+ priority = ATTRIBUTE_PRIORITY.HIGH
61
+ ) {
62
+ const existingAttribute = this.attributes.get(key)
63
+
64
+ let droppableAttributeKey = null
65
+ if (!existingAttribute && this.attributes.size === this.limit) {
66
+ droppableAttributeKey = this._getDroppableAttributeKey(priority)
67
+
68
+ if (!droppableAttributeKey) {
69
+ logger.debug(
70
+ `Maximum number of custom attributes have been added.
71
+ Dropping attribute ${key} with ${value} type.`
72
+ )
73
+
74
+ return
75
+ }
76
+ }
77
+
78
+ if (existingAttribute && priority < existingAttribute.priority) {
79
+ logger.debug('incoming priority for \'%s\' is lower than existing, not updating.', key)
80
+ logger.trace(
81
+ '%s attribute retained value: %s, ignored value: %s',
82
+ key,
83
+ existingAttribute.value,
84
+ value
85
+ )
86
+ return
87
+ }
88
+
89
+ if (!isValidType(value)) {
90
+ logger.debug(
91
+ 'Not adding attribute %s with %s value type. This is expected for undefined' +
92
+ 'attributes and only an issue if an attribute is not expected to be undefined' +
93
+ 'or not of the type expected.',
94
+ key,
95
+ typeof value
96
+ )
97
+ return
98
+ }
99
+
100
+ if (!this.isValidLength(key)) {
101
+ logger.warn(
102
+ 'Length limit exceeded for attribute name, not adding: %s',
103
+ key
104
+ )
105
+ return
106
+ }
107
+
108
+
109
+ // Only set the attribute if at least one destination passed
110
+ const validDestinations = this.filter(destinations, key)
111
+ if (!validDestinations) {
112
+ return
113
+ }
114
+
115
+ if (droppableAttributeKey) {
116
+ logger.trace(
117
+ 'dropping existing lower priority attribute %s ' +
118
+ 'to add higher priority attribute %s',
119
+ droppableAttributeKey,
120
+ key
121
+ )
122
+
123
+ this.attributes.delete(droppableAttributeKey)
124
+ }
125
+
126
+ this._set(validDestinations, key, value, truncateExempt, priority)
127
+ }
128
+
129
+ addAttributes(destinations, attrs) {
130
+ for (let key in attrs) {
131
+ if (properties.hasOwn(attrs, key)) {
132
+ this.addAttribute(destinations, key, attrs[key])
133
+ }
134
+ }
135
+ }
136
+
137
+ _getDroppableAttributeKey(incomingPriority) {
138
+ // There will never be anything lower priority to drop
139
+ if (incomingPriority === ATTRIBUTE_PRIORITY.LOW) {
140
+ return null
141
+ }
142
+
143
+ this.lastFoundIndexCache = this.lastFoundIndexCache || Object.create(null)
144
+ const lastFoundIndex = this.lastFoundIndexCache[incomingPriority]
145
+
146
+ // We've already dropped all items lower than incomingPriority.
147
+ // We can honor the cache because at the point by which we've dropped
148
+ // all lower priority items, due to being at max capacity, there will never be another
149
+ // lower-priority item added. Lower priority items are unable to drop higher priority items.
150
+ if (lastFoundIndex === -1) {
151
+ return null
152
+ }
153
+
154
+ // We can't reverse iterate w/o creating an array that will iterate,
155
+ // so we just iterate forward stopping once we've checked the last cached index.
156
+ let lowerPriorityAttributeName = null
157
+ let foundIndex = -1
158
+
159
+ let index = 0
160
+ for (const [key, attribute] of this.attributes) {
161
+ // Don't search past last found lower priority item.
162
+ // At the point of dropping items for this priority,
163
+ // lower priority items will never be added.
164
+ if (lastFoundIndex && index > lastFoundIndex) {
165
+ break
166
+ }
167
+
168
+ if (attribute.priority < incomingPriority) {
169
+ lowerPriorityAttributeName = key
170
+ foundIndex = index
171
+ }
172
+
173
+ index++
174
+ }
175
+
176
+ // Item may not get dropped, so we simply store the index as
177
+ // an upper maximum and allow a future pass to clear out.
178
+ this.lastFoundIndexCache[incomingPriority] = foundIndex
179
+
180
+ return lowerPriorityAttributeName
181
+ }
182
+ }
183
+
184
+ function makeFilter(scope) {
185
+ const {attributeFilter} = Config.getInstance()
186
+ if (scope === 'transaction') {
187
+ return (d, k) => attributeFilter.filterTransaction(d, k)
188
+ } else if (scope === 'segment') {
189
+ return (d, k) => attributeFilter.filterSegment(d, k)
190
+ }
191
+ }
192
+
193
+ module.exports = {
194
+ PrioritizedAttributes: PrioritizedAttributes,
195
+ ATTRIBUTE_PRIORITY: ATTRIBUTE_PRIORITY
196
+ }
@@ -121,7 +121,10 @@ class AwsLambda {
121
121
  transaction.setPartialName(transactionName)
122
122
 
123
123
  const isApiGatewayLambdaProxy = apiGateway.isLambdaProxyEvent(event)
124
-
124
+ const segmentRecorder = isApiGatewayLambdaProxy ? recordWeb : recordBackground
125
+ const segment = shim.createSegment(functionName, segmentRecorder)
126
+ transaction.baseSegment = segment
127
+
125
128
  // resultProcessor is used to execute additional logic based on the
126
129
  // payload supplied to the callback.
127
130
  let resultProcessor
@@ -131,9 +134,6 @@ class AwsLambda {
131
134
  resultProcessor = getApiGatewayLambdaProxyResultProcessor(transaction)
132
135
  }
133
136
 
134
- const segmentRecorder = isApiGatewayLambdaProxy ? recordWeb : recordBackground
135
- const segment = shim.createSegment(functionName, segmentRecorder)
136
- transaction.baseSegment = segment
137
137
 
138
138
  const cbIndex = args.length - 1
139
139
 
@@ -175,6 +175,8 @@ class AwsLambda {
175
175
  shim.agent.setLambdaArn(context.invokedFunctionArn)
176
176
 
177
177
  shim.agent.setLambdaFunctionVersion(context.functionVersion)
178
+
179
+ segment.addSpanAttributes(awsAttributes)
178
180
 
179
181
  segment.start()
180
182
 
@@ -301,6 +303,8 @@ function getApiGatewayLambdaProxyResultProcessor(transaction) {
301
303
  function setWebRequest(shim, transaction, request) {
302
304
  transaction.type = shim.WEB
303
305
 
306
+ const segment = transaction.baseSegment
307
+
304
308
  transaction.url = urltils.scrub(request.url.path)
305
309
  transaction.verb = request.method
306
310
  transaction.trace.attributes.addAttribute(
@@ -308,6 +312,12 @@ function setWebRequest(shim, transaction, request) {
308
312
  'request.method',
309
313
  request.method
310
314
  )
315
+
316
+ segment.addSpanAttribute(
317
+ 'request.method',
318
+ request.method
319
+ )
320
+
311
321
  transaction.port = request.url.port
312
322
 
313
323
  transaction.addRequestParameters(request.url.requestParameters)
@@ -319,6 +329,11 @@ function setWebRequest(shim, transaction, request) {
319
329
  request.url.path
320
330
  )
321
331
 
332
+ segment.addSpanAttribute(
333
+ 'request.uri',
334
+ request.url.path
335
+ )
336
+
322
337
  headerAttributes.collectRequestHeaders(request.headers, transaction)
323
338
 
324
339
  if (shim.agent.config.distributed_tracing.enabled) {
@@ -369,6 +384,10 @@ function setWebResponse(transaction, response) {
369
384
  transaction.statusCode = response.statusCode
370
385
 
371
386
  const responseCode = String(response.statusCode)
387
+ /*
388
+ TODO: remove with next major release
389
+ httpResponseCode attribute is deprecated
390
+ */
372
391
  transaction.trace.attributes.addAttribute(
373
392
  ATTR_DEST.TRANS_COMMON,
374
393
  'httpResponseCode',
@@ -376,10 +395,24 @@ function setWebResponse(transaction, response) {
376
395
  )
377
396
 
378
397
  if (/^\d+$/.test(responseCode)) {
398
+ /*
399
+ TODO: remove with next major release
400
+ response.status attribute is deprecated
401
+ */
379
402
  transaction.trace.attributes.addAttribute(
380
403
  ATTR_DEST.TRANS_COMMON,
381
404
  'response.status',
382
405
  responseCode)
406
+
407
+ transaction.trace.attributes.addAttribute(
408
+ ATTR_DEST.TRANS_COMMON,
409
+ 'http.statusCode',
410
+ responseCode
411
+ )
412
+
413
+ const segment = transaction.agent.tracer.getSegment()
414
+
415
+ segment.addSpanAttribute('http.statusCode', responseCode)
383
416
  }
384
417
 
385
418
  headerAttributes.collectResponseHeaders(response.headers, transaction)
@@ -21,7 +21,7 @@
21
21
  "httpMethod",
22
22
  "path",
23
23
  "requestContext",
24
- "resource"
24
+ "requestContext.stage"
25
25
  ]
26
26
  },
27
27
  "cloudFront": {
@@ -750,6 +750,12 @@ function recordSubscribedConsume(nodule, properties, spec) {
750
750
  ATTR_DESTS.NONE,
751
751
  'message.parameters.' + key,
752
752
  msgDesc.parameters[key])
753
+
754
+ tx.baseSegment.attributes.addAttribute(
755
+ ATTR_DESTS.NONE,
756
+ 'message.parameters.' + key,
757
+ msgDesc.parameters[key]
758
+ )
753
759
  }
754
760
  }
755
761
 
@@ -762,6 +768,11 @@ function recordSubscribedConsume(nodule, properties, spec) {
762
768
  'message.routingKey',
763
769
  msgDesc.routingKey
764
770
  )
771
+
772
+ tx.baseSegment.addSpanAttribute(
773
+ 'message.routingKey',
774
+ msgDesc.routingKey
775
+ )
765
776
  }
766
777
  if (shim.isString(msgDesc.queue)) {
767
778
  tx.trace.attributes.addAttribute(
@@ -769,6 +780,11 @@ function recordSubscribedConsume(nodule, properties, spec) {
769
780
  'message.queueName',
770
781
  msgDesc.queue
771
782
  )
783
+
784
+ tx.baseSegment.addSpanAttribute(
785
+ 'message.queueName',
786
+ msgDesc.queue
787
+ )
772
788
  }
773
789
  }
774
790
  if (msgDesc.headers) {
package/lib/shim/shim.js CHANGED
@@ -989,9 +989,9 @@ function record(nodule, properties, recordNamer) {
989
989
  })
990
990
 
991
991
  // Apply the function, and (if it returned a stream) bind that too.
992
- // If you are looking here Geoff, the reason there is no check for
993
- // `segment` is because it should be guaranteed by the parent and active
994
- // transaction check at the beginning of this function. :)
992
+ // The reason there is no check for `segment` is because it should
993
+ // be guaranteed by the parent and active transaction check
994
+ // at the beginning of this function.
995
995
  var ret = _applyRecorderSegment(segment, this, args, segDesc)
996
996
  if (ret) {
997
997
  if (segDesc.stream) {
@@ -0,0 +1,36 @@
1
+ 'use strict'
2
+
3
+ const {MAXIMUM_CUSTOM_ATTRIBUTES} = require('../attributes')
4
+ const {PrioritizedAttributes, ATTRIBUTE_PRIORITY} = require('../prioritized-attributes')
5
+ const {DESTINATIONS} = require('../config/attribute-filter')
6
+
7
+ // Scoping impacts memoization. We could decide to add a scope instead of including
8
+ // spans in segment scope in the future.
9
+ const ATTRIBUTE_SCOPE = 'segment'
10
+
11
+ class SpanContext {
12
+ constructor(intrinsicAttributes, customAttributes) {
13
+ this.intrinsicAttributes = intrinsicAttributes || Object.create(null)
14
+
15
+ this.customAttributes =
16
+ customAttributes || new PrioritizedAttributes(ATTRIBUTE_SCOPE, MAXIMUM_CUSTOM_ATTRIBUTES)
17
+
18
+ this.ATTRIBUTE_PRIORITY = ATTRIBUTE_PRIORITY
19
+ }
20
+
21
+ addIntrinsicAttribute(key, value) {
22
+ this.intrinsicAttributes[key] = value
23
+ }
24
+
25
+ addCustomAttribute(key, value, priority) {
26
+ this.customAttributes.addAttribute(
27
+ DESTINATIONS.SPAN_EVENT,
28
+ key,
29
+ value,
30
+ false,
31
+ priority
32
+ )
33
+ }
34
+ }
35
+
36
+ module.exports = SpanContext
@@ -80,7 +80,9 @@ class SpanEvent {
80
80
  */
81
81
  static fromSegment(segment, parentId = null, isRoot = false) {
82
82
  const attributes = segment.attributes.get(DESTINATIONS.SPAN_EVENT)
83
- const customAttributes = segment.customAttributes.get(DESTINATIONS.SPAN_EVENT)
83
+
84
+ const spanContext = segment.getSpanContext()
85
+ const customAttributes = spanContext.customAttributes.get(DESTINATIONS.SPAN_EVENT)
84
86
 
85
87
  let span = null
86
88
  if (HttpSpanEvent.testSegment(segment)) {
@@ -91,6 +93,11 @@ class SpanEvent {
91
93
  span = new SpanEvent(attributes, customAttributes)
92
94
  }
93
95
 
96
+
97
+ for (const [key, value] of Object.entries(spanContext.intrinsicAttributes)) {
98
+ span.intrinsics[key] = value
99
+ }
100
+
94
101
  const tx = segment.transaction
95
102
 
96
103
  span.intrinsics.traceId = tx.traceId
@@ -126,10 +126,11 @@ class SpanStreamer {
126
126
  }
127
127
  }
128
128
 
129
- connect(agent_run_id) {
129
+ connect(agent_run_id, requestHeadersMap) {
130
130
  this.connection.setConnectionDetails(
131
131
  this.license_key,
132
- agent_run_id
132
+ agent_run_id,
133
+ requestHeadersMap
133
134
  )
134
135
 
135
136
  this.connection.connectSpans()
@@ -31,8 +31,10 @@ class StreamingSpanEventAggregator extends Aggregator {
31
31
  }
32
32
 
33
33
  logger.trace('StreamingSpanEventAggregator starting up')
34
- this.stream.connect(this.runId)
34
+ this.stream.connect(this.runId, this.requestHeadersMap)
35
35
  this.started = true
36
+
37
+ this.emit('started')
36
38
  }
37
39
 
38
40
  stop() {
@@ -43,6 +45,8 @@ class StreamingSpanEventAggregator extends Aggregator {
43
45
  logger.trace('StreamingSpanEventAggregator stopping')
44
46
  this.stream.disconnect()
45
47
  this.started = false
48
+
49
+ this.emit('stopped')
46
50
  }
47
51
 
48
52
  send() {
@@ -83,6 +87,12 @@ class StreamingSpanEventAggregator extends Aggregator {
83
87
  const span = StreamingSpanEvent.fromSegment(segment, parentId, isRoot)
84
88
  this.stream.write(span)
85
89
  }
90
+
91
+ reconfigure(config) {
92
+ super.reconfigure(config)
93
+
94
+ this.requestHeadersMap = config.request_headers_map
95
+ }
86
96
  }
87
97
 
88
98
  module.exports = StreamingSpanEventAggregator
@@ -98,7 +98,9 @@ class StreamingSpanEvent {
98
98
 
99
99
  static fromSegment(segment, parentId = null, isRoot = false) {
100
100
  const agentAttributes = segment.attributes.get(DESTINATIONS.SPAN_EVENT)
101
- const customAttributes = segment.customAttributes.get(DESTINATIONS.SPAN_EVENT)
101
+
102
+ const spanContext = segment.getSpanContext()
103
+ const customAttributes = spanContext.customAttributes.get(DESTINATIONS.SPAN_EVENT)
102
104
 
103
105
  const transaction = segment.transaction
104
106
  const traceId = transaction.traceId
@@ -112,6 +114,10 @@ class StreamingSpanEvent {
112
114
  span = new StreamingSpanEvent(traceId, agentAttributes, customAttributes)
113
115
  }
114
116
 
117
+ for (const [key, value] of Object.entries(spanContext.intrinsicAttributes)) {
118
+ span.addIntrinsicAttribute(key, value)
119
+ }
120
+
115
121
  span.addIntrinsicAttribute('guid', segment.id)
116
122
  span.addIntrinsicAttribute('parentId', parentId)
117
123
  span.addIntrinsicAttribute('transactionId', transaction.id)