newrelic 6.4.0 → 6.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/.eslintignore +1 -1
  2. package/{.eslintrc → .eslintrc.js} +2 -1
  3. package/.travis.yml +5 -6
  4. package/NEWS.md +105 -1
  5. package/THIRD_PARTY_NOTICES.md +420 -0
  6. package/api.js +72 -1
  7. package/index.js +3 -3
  8. package/lib/agent.js +22 -8
  9. package/lib/aggregators/base-aggregator.js +1 -1
  10. package/lib/attributes.js +15 -6
  11. package/lib/collector/serverless.js +8 -0
  12. package/lib/config/default.js +23 -1
  13. package/lib/config/env.js +7 -1
  14. package/lib/config/hsm.js +6 -1
  15. package/lib/config/index.js +36 -8
  16. package/lib/config/lasp.js +16 -1
  17. package/lib/errors/error-collector.js +51 -90
  18. package/lib/errors/helper.js +13 -13
  19. package/lib/errors/index.js +36 -18
  20. package/lib/grpc/connection/states.js +9 -0
  21. package/lib/grpc/connection.js +344 -0
  22. package/lib/grpc/endpoints/infinite-tracing/v1.proto +29 -0
  23. package/lib/header-processing.js +75 -0
  24. package/lib/instrumentation/core/child_process.js +12 -0
  25. package/lib/instrumentation/core/http-outbound.js +4 -0
  26. package/lib/instrumentation/core/http.js +98 -145
  27. package/lib/instrumentation/hapi/hapi-17.js +10 -3
  28. package/lib/metrics/names.js +12 -1
  29. package/lib/proxy/grpc.js +15 -0
  30. package/lib/serverless/aws-lambda.js +2 -0
  31. package/lib/spans/create-span-event-aggregator.js +112 -0
  32. package/lib/spans/map-to-streaming-type.js +44 -0
  33. package/lib/spans/span-event-aggregator.js +5 -0
  34. package/lib/spans/span-event.js +22 -9
  35. package/lib/spans/span-streamer.js +109 -0
  36. package/lib/spans/streaming-span-attributes.js +59 -0
  37. package/lib/spans/streaming-span-event-aggregator.js +98 -0
  38. package/lib/spans/streaming-span-event.js +261 -0
  39. package/lib/transaction/index.js +36 -20
  40. package/lib/transaction/trace/index.js +13 -10
  41. package/lib/transaction/trace/segment.js +12 -1
  42. package/lib/transaction/tracecontext.js +4 -0
  43. package/package.json +10 -10
  44. package/stub_api.js +5 -0
@@ -6,8 +6,18 @@ var props = require('../util/properties')
6
6
  var urltils = require('../util/urltils')
7
7
  const errorHelper = require('../errors/helper')
8
8
 
9
- module.exports.createError = createError
10
- module.exports.createEvent = createEvent
9
+ class Exception {
10
+ constructor({error, timestamp, customAttributes, agentAttributes}) {
11
+ this.error = error
12
+ this.timestamp = timestamp || 0
13
+ this.customAttributes = customAttributes || {}
14
+ this.agentAttributes = agentAttributes || {}
15
+ }
16
+
17
+ getErrorDetails(config) {
18
+ return errorHelper.extractErrorInformation(null, this.error, config)
19
+ }
20
+ }
11
21
 
12
22
  /**
13
23
  * Given either or both of a transaction and an exception, generate an error
@@ -17,21 +27,20 @@ module.exports.createEvent = createEvent
17
27
  * handler, which traps actual instances of Error, try to set sensible
18
28
  * defaults for everything.
19
29
  *
20
- * @param {Transaction} transaction The agent transaction, presumably
21
- * coming out of the instrumentation.
22
- * @param {Error} exception Something trapped by an error listener.
23
- * @param {object} customAttributes Any custom attributes associated with
24
- * the request (optional).
30
+ * @param {Transaction} transaction The agent transaction, coming from the instrumentatation
31
+ * @param {Exception} exception An custom Exception object with the error and other information
32
+ * @param {object} config The configuration to use when creating the object
25
33
  */
26
- function createError(transaction, exception, customAttributes, config) {
34
+ function createError(transaction, exception, config) {
35
+ const error = exception.error
27
36
  let {name, message, type} = errorHelper.extractErrorInformation(
28
37
  transaction,
29
- exception,
38
+ error,
30
39
  config,
31
40
  urltils
32
41
  )
33
42
 
34
- var params = {
43
+ let params = {
35
44
  userAttributes: Object.create(null),
36
45
  agentAttributes: Object.create(null),
37
46
  intrinsics: Object.create(null)
@@ -39,20 +48,26 @@ function createError(transaction, exception, customAttributes, config) {
39
48
 
40
49
  if (transaction) {
41
50
  // Copy all of the parameters off of the transaction.
42
- params.agentAttributes = transaction.trace.attributes.get(DESTINATIONS.ERROR_EVENT)
43
51
  params.intrinsics = transaction.getIntrinsicAttributes()
52
+ let transactionAgentAttributes = transaction.trace.attributes.get(DESTINATIONS.ERROR_EVENT)
53
+ transactionAgentAttributes = transactionAgentAttributes || {}
54
+
55
+ // Merge the agent attributes specific to this error event with the transaction attributes
56
+ const agentAttributes = Object.assign(exception.agentAttributes, transactionAgentAttributes)
57
+ params.agentAttributes = agentAttributes
44
58
 
45
59
  // There should be no attributes to copy in HSM, but check status anyway
46
60
  if (!config.high_security) {
47
- var custom = transaction.trace.custom.get(DESTINATIONS.ERROR_EVENT)
61
+ const custom = transaction.trace.custom.get(DESTINATIONS.ERROR_EVENT)
48
62
  urltils.overwriteParameters(custom, params.userAttributes)
49
63
  }
50
64
  }
51
65
 
66
+ const customAttributes = exception.customAttributes
52
67
  if (!config.high_security && config.api.custom_attributes_enabled && customAttributes) {
53
- for (var key in customAttributes) {
68
+ for (let key in customAttributes) {
54
69
  if (props.hasOwn(customAttributes, key)) {
55
- var dest = config.attributeFilter.filterTransaction(
70
+ const dest = config.attributeFilter.filterTransaction(
56
71
  DESTINATIONS.ERROR_EVENT,
57
72
  key
58
73
  )
@@ -63,12 +78,11 @@ function createError(transaction, exception, customAttributes, config) {
63
78
  }
64
79
  }
65
80
 
66
-
67
- var stack = exception && exception.stack
81
+ const stack = exception.error && exception.error.stack
68
82
  if (stack) {
69
83
  params.stack_trace = ('' + stack).split(/[\n\r]/g)
70
84
  if (config.high_security || config.strip_exception_messages.enabled) {
71
- params.stack_trace[0] = exception.name + ': <redacted>'
85
+ params.stack_trace[0] = exception.error.name + ': <redacted>'
72
86
  }
73
87
  }
74
88
 
@@ -77,7 +91,7 @@ function createError(transaction, exception, customAttributes, config) {
77
91
  params.intrinsics['error.expected'] = true
78
92
  }
79
93
 
80
- var res = [0, name, message, type, params]
94
+ let res = [0, name, message, type, params]
81
95
  if (transaction) {
82
96
  res.transaction = transaction.id
83
97
  }
@@ -180,3 +194,7 @@ function _getErrorEventIntrinsicAttrs(
180
194
 
181
195
  return attributes
182
196
  }
197
+
198
+ module.exports.createError = createError
199
+ module.exports.createEvent = createEvent
200
+ module.exports.Exception = Exception
@@ -0,0 +1,9 @@
1
+ 'use strict'
2
+ module.exports = {
3
+ disconnected:0,
4
+ connecting:1,
5
+ connected:2,
6
+ 0:'disconnected',
7
+ 1:'connecting',
8
+ 2:'connected'
9
+ }
@@ -0,0 +1,344 @@
1
+ 'use strict'
2
+
3
+ const protoLoader = require('@grpc/proto-loader')
4
+ const grpc = require('../proxy/grpc')
5
+ const logger = require('../logger').child({component: 'grpc_connection'})
6
+ const EventEmitter = require('events')
7
+ const NAMES = require('../metrics/names')
8
+ const util = require('util')
9
+
10
+ const connectionStates = require('./connection/states')
11
+
12
+ const protoOptions = {keepCase: true,
13
+ longs: String,
14
+ enums: String,
15
+ defaults: true,
16
+ oneofs: true
17
+ }
18
+
19
+ const pathProtoDefinition = __dirname +
20
+ '../../../lib/grpc/endpoints/infinite-tracing/v1.proto'
21
+
22
+ const defaultBackoffOpts = {
23
+ initialSeconds: 0,
24
+ seconds:15
25
+ }
26
+
27
+ /**
28
+ * Class for managing the GRPC connection
29
+ *
30
+ * Both @grpc/grpc-js and grpc will manage the http2 connections
31
+ * for us -- this class manages the _stream_ connection logic.
32
+ *
33
+ * Will emit events based on the connectionStates (see above
34
+ */
35
+ class GrpcConnection extends EventEmitter {
36
+ /**
37
+ * GrpcConnection constructor
38
+ *
39
+ * Standard property setting/initilization, and sets an initial
40
+ * connection state of disconnected
41
+ *
42
+ * @param {Object} traceObserverConfig config item config.infinite_tracing.trace_observer
43
+ * @param {MetricAggregator} metrics metric aggregator, for supportability metrics
44
+ * @param {Object} backoffOpts allows injecting specific backoff times
45
+ */
46
+ constructor(traceObserverConfig, metrics, backoffOpts = defaultBackoffOpts) {
47
+ super()
48
+
49
+ // only set opts if the object is valid
50
+ if ( (backoffOpts.initialSeconds || backoffOpts.initialSeconds === 0) &&
51
+ (backoffOpts.seconds || backoffOpts.seconds === 0)) {
52
+ this._backoffOpts = backoffOpts
53
+ } else {
54
+ this._backoffOpts = defaultBackoffOpts
55
+ logger.trace(
56
+ 'invalid backoff information passed to constructor, using default values'
57
+ )
58
+ }
59
+
60
+ // initial "stream connection" has a 0 second backoff, unless
61
+ // different values are injected via _backoffOpts
62
+ this._streamBackoffSeconds = this._backoffOpts.initialSeconds
63
+ this._metrics = metrics
64
+ this._setState(connectionStates.disconnected)
65
+
66
+ this._endpoint = this.getTraceObserverEndpoint(traceObserverConfig)
67
+ this._licensekey = null
68
+ this._runId = null
69
+
70
+ this.stream = null
71
+ }
72
+
73
+ /**
74
+ * Sets connection details
75
+ *
76
+ * Allows setting of connection details _after_ object constructions
77
+ * but before the actual connection.
78
+ *
79
+ * @param {string} endpoint the GRPC server's endpoint
80
+ * @param {string} license_key the agent license key
81
+ * @param {string} run_id the current agent run id (also called agent run token)
82
+ * @param {string} [rootCerts] string of root (ca) certificates to attach to the connection.
83
+ */
84
+ setConnectionDetails(license_key, run_id, rootCerts) {
85
+ this._licenseKey = license_key
86
+ this._runId = run_id
87
+ this._rootCerts = rootCerts
88
+
89
+ return this
90
+ }
91
+
92
+ getTraceObserverEndpoint(traceObserverConfig) {
93
+ return `${traceObserverConfig.host}:${traceObserverConfig.port}`
94
+ }
95
+
96
+ /**
97
+ * Sets the connection state
98
+ *
99
+ * Used to indicate a transition from one connection state to
100
+ * the next. Also responsible for emitting the connect state event
101
+ *
102
+ * @param {int} state The connection state (See connectionStates above)
103
+ * @param {ClientDuplexStreamImpl} state The GRPC stream, when defined
104
+ */
105
+ _setState(state, stream = null) {
106
+ this._state = state
107
+ this.emit(connectionStates[state], stream)
108
+ }
109
+
110
+ /**
111
+ * Start the Connection
112
+ *
113
+ * Public Entry point -- initiates a connection
114
+ */
115
+ connectSpans() {
116
+ if (this._state !== connectionStates.disconnected) {
117
+ return
118
+ }
119
+
120
+ this._setState(connectionStates.connecting)
121
+ logger.trace('connecting to grpc endpoint in [%s] seconds', this._getBackoffSeconds())
122
+
123
+ setTimeout(() => {
124
+ try {
125
+ this.stream = this._connectSpans(this._endpoint, this._licenseKey, this._runId)
126
+
127
+ // May not actually be "connected" at this point but we can write to the stream
128
+ // immediately.
129
+ this._setState(connectionStates.connected, this.stream)
130
+
131
+ // any future stream connect/disconnect after initial connection
132
+ // should have a 15 second backoff
133
+ this._setStreamBackoffAfterInitialStreamSetup()
134
+ } catch (err) {
135
+ logger.trace(
136
+ err,
137
+ 'Unexpected error establishing GRPC stream, will not attempt reconnect.'
138
+ )
139
+ this._disconnectWithoutReconnect()
140
+ }
141
+ }, this._getBackoffSeconds() * 1000)
142
+ }
143
+
144
+ _setStreamBackoffAfterInitialStreamSetup() {
145
+ this._streamBackoffSeconds = this._backoffOpts.seconds
146
+ }
147
+
148
+ _setStreamBackoffToInitialValue() {
149
+ this._streamBackoffSeconds = this._backoffOpts.initialSeconds
150
+ }
151
+
152
+ /**
153
+ * End the current stream and set state to disconnected.
154
+ *
155
+ * No more data can be sent until connected again.
156
+ */
157
+ disconnect() {
158
+ if (this._state === connectionStates.disconnected) {
159
+ return
160
+ }
161
+
162
+ this._disconnectWithoutReconnect()
163
+ }
164
+
165
+ /**
166
+ * Calculates backoff seconds
167
+ */
168
+ _getBackoffSeconds() {
169
+ return this._streamBackoffSeconds
170
+ }
171
+
172
+ /**
173
+ * Method returns GRPC metadata for initial connection
174
+ *
175
+ * @param {string} license_key
176
+ * @param {string} run_id
177
+ */
178
+ _getMetadata(license_key, run_id, env) {
179
+ const metadata = new grpc.Metadata()
180
+ metadata.add('license_key', license_key)
181
+ metadata.add('agent_run_token', run_id)
182
+
183
+ // check environment variables for testing parameters and
184
+ // pass to server via meta-data.
185
+ const flaky = parseInt(env.NEWRELIC_GRPCCONNECTION_METADATA_FLAKY, 10)
186
+ const delay = parseInt(env.NEWRELIC_GRPCCONNECTION_METADATA_DELAY, 10)
187
+ if (flaky) {
188
+ logger.trace('Adding flaky metadata: %s', flaky)
189
+ metadata.add('flaky', flaky)
190
+ }
191
+ if (delay) {
192
+ logger.trace('Adding delay metadata: %s', delay)
193
+ metadata.add('delay',delay)
194
+ }
195
+
196
+ return metadata
197
+ }
198
+
199
+ /**
200
+ * Sets internal object state to disconnected initiates a new connection
201
+ *
202
+ * Called when we receive a stream status that indicates a potential
203
+ * problem with the stream. We set the state (which will emit an event),
204
+ * increment tries to honor the backoff, and then reconnect.
205
+ */
206
+ _reconnect() {
207
+ this._disconnect()
208
+ this.connectSpans()
209
+ }
210
+
211
+ _disconnect() {
212
+ if (this.stream) {
213
+ this.stream.removeAllListeners()
214
+
215
+ // Indicates to server we are done.
216
+ // Server officially closes the stream.
217
+ this.stream.end()
218
+ this.stream = null
219
+ }
220
+
221
+ this._setState(connectionStates.disconnected)
222
+ }
223
+
224
+ /**
225
+ * Disconnects Without Reconnect
226
+ *
227
+ * Certain GRPC statuses require us to disconnect and _not_
228
+ * attempt a stream reconnect.
229
+ */
230
+ _disconnectWithoutReconnect() {
231
+ this._disconnect()
232
+ this._setStreamBackoffToInitialValue()
233
+ }
234
+
235
+ /**
236
+ * Central location to setup stream observers
237
+ *
238
+ * Events from the GRPC stream (a ClientDuplexStreamImpl) are the main way
239
+ * we communicate with the GRPC server.
240
+ *
241
+ * @param {ClientDuplexStreamImpl} stream
242
+ */
243
+ _setupSpanStreamObservers(stream) {
244
+ // listen for responses from server and log
245
+ if (logger.traceEnabled()) {
246
+ stream.on('data', function data(response) {
247
+ logger.trace("grpc span response stream: %s", JSON.stringify(response))
248
+ })
249
+ }
250
+
251
+ // listen for status that indicate stream has ended,
252
+ // and we need to disconnect
253
+ stream.on('status', (grpcStatus) => {
254
+ logger.trace('GRPC Status Received [%s]: %s', grpcStatus.code, grpcStatus.details)
255
+ const grpcStatusName =
256
+ grpc.status[grpcStatus.code] ? grpc.status[grpcStatus.code] : 'UNKNOWN'
257
+
258
+ if (grpc.status[grpc.status.UNIMPLEMENTED] === grpcStatusName) {
259
+ this._metrics.getOrCreateMetric(
260
+ NAMES.INFINITE_TRACING.SPAN_RESPONSE_GRPC_UNIMPLEMENTED
261
+ ).incrementCallCount()
262
+
263
+ // per the spec, An UNIMPLEMENTED status code from gRPC indicates
264
+ // that the versioned Trace Observer is no longer available. Agents
265
+ // MUST NOT attempt to reconnect in this case
266
+ this._disconnectWithoutReconnect()
267
+ } else {
268
+ // all statuses are treated as "bad stuff" happened, and
269
+ // as a signal we need to reconnect (except UNIMPLEMENTED).
270
+ // Even an "OK status" indicates the call completed successfully,
271
+ // which indicates the stream is over.
272
+ if (grpc.status[grpc.status.OK] !== grpcStatusName) {
273
+ this._metrics.getOrCreateMetric(
274
+ util.format(NAMES.INFINITE_TRACING.SPAN_RESPONSE_GRPC_STATUS, grpcStatusName)
275
+ ).incrementCallCount()
276
+ }
277
+
278
+ this._reconnect()
279
+ }
280
+ })
281
+
282
+ // if we don't listen for the errors they'll bubble
283
+ // up and crash the application
284
+ stream.on('error', (err) => {
285
+ this._metrics.getOrCreateMetric(NAMES.INFINITE_TRACING.SPAN_RESPONSE_ERROR)
286
+ .incrementCallCount()
287
+
288
+ logger.trace('span stream error. Code: [%s]: %s',err.code, err.details)
289
+ })
290
+ }
291
+
292
+ /**
293
+ * Creates the GRPC credentials needed
294
+ */
295
+ _generateCredentials(grpcApi) {
296
+ let certBuffer = null
297
+
298
+ // Current settable value for testing. If allowed to be overriden via
299
+ // configuration, this should be removed in place of setting
300
+ // this._rootCerts from config via normal configuration precedence.
301
+ const envTestCerts = process.env.NEWRELIC_GRPCCONNECTION_CA
302
+ const rootCerts = this._rootCerts || envTestCerts
303
+ if (rootCerts) {
304
+ logger.debug('Infinite tracing root certificates found to attach to requests.')
305
+ try {
306
+ certBuffer = Buffer.from(rootCerts, 'utf-8')
307
+ } catch (err) {
308
+ logger.warn('Failed to create buffer from rootCerts, proceeding without.', err)
309
+ }
310
+ }
311
+
312
+ // null/undefined ca treated same as calling createSsl()
313
+ return grpcApi.credentials.createSsl(certBuffer)
314
+ }
315
+
316
+ /**
317
+ * Internal/private method for connection
318
+ *
319
+ * Contains the actual logic that connects to the GRPC service.
320
+ * "Connection" can be a somewhat misleading term here. This method
321
+ * invokes the "recordSpan" remote proceduce call. Behind the scenes
322
+ * this makes an http2 request with the metadata, and then returns
323
+ * a stream for further writing.
324
+ */
325
+ _connectSpans(endpoint, license_key, run_id) {
326
+ const packageDefinition = protoLoader.loadSync(pathProtoDefinition, protoOptions)
327
+
328
+ const serviceDefinition = grpc.loadPackageDefinition(
329
+ packageDefinition
330
+ ).com.newrelic.trace.v1
331
+
332
+ const credentials = this._generateCredentials(grpc)
333
+ const client = new serviceDefinition.IngestService(
334
+ endpoint,
335
+ credentials
336
+ )
337
+ const metadata = this._getMetadata(license_key, run_id, process.env)
338
+ const stream = client.recordSpan(metadata)
339
+ this._setupSpanStreamObservers(stream)
340
+
341
+ return stream
342
+ }
343
+ }
344
+ module.exports = GrpcConnection
@@ -0,0 +1,29 @@
1
+ syntax = "proto3";
2
+
3
+ package com.newrelic.trace.v1;
4
+
5
+ service IngestService {
6
+ // Accepts a stream of Span messages, and returns an irregular stream of
7
+ // RecordStatus messages.
8
+ rpc RecordSpan(stream Span) returns (stream RecordStatus) {}
9
+ }
10
+
11
+ message Span {
12
+ string trace_id = 1;
13
+ map<string, AttributeValue> intrinsics = 2;
14
+ map<string, AttributeValue> user_attributes = 3;
15
+ map<string, AttributeValue> agent_attributes = 4;
16
+ }
17
+
18
+ message AttributeValue {
19
+ oneof value {
20
+ string string_value = 1;
21
+ bool bool_value = 2;
22
+ int64 int_value = 3;
23
+ double double_value = 4;
24
+ }
25
+ }
26
+
27
+ message RecordStatus {
28
+ uint64 messages_seen = 1;
29
+ }
@@ -0,0 +1,75 @@
1
+ 'use strict'
2
+
3
+ const REQUEST_START_HEADER = 'x-request-start'
4
+ const QUEUE_HEADER = 'x-queue-start'
5
+ const CONTENT_LENGTH_REGEX = /^Content-Length$/i
6
+
7
+ /**
8
+ * Extracts queue time from the incoming request headers.
9
+ *
10
+ * Queue time is provided by certain providers by stamping the request
11
+ * header with the time the request arrived at the router.
12
+ * @param {*} logger
13
+ * @param {*} requestHeaders
14
+ */
15
+ function getQueueTime(logger, requestHeaders) {
16
+ const headerValue = requestHeaders[REQUEST_START_HEADER] || requestHeaders[QUEUE_HEADER]
17
+ if (!headerValue) {
18
+ return null
19
+ }
20
+
21
+ const split = headerValue.split('=')
22
+ const rawQueueTime = split.length > 1 ? split[1] : headerValue
23
+
24
+ const parsedQueueTime = parseFloat(rawQueueTime)
25
+ if (isNaN(parsedQueueTime)) {
26
+ logger.warn('Queue time header parsed as NaN. See trace level log for value.')
27
+
28
+ // This header can hold up to 4096 bytes which could quickly fill up logs.
29
+ // Do not log a level higher than debug.
30
+ logger.trace('Queue time: %s', rawQueueTime)
31
+
32
+ return null
33
+ }
34
+
35
+ const convertedQueueTime = convertUnit(parsedQueueTime)
36
+ return convertedQueueTime
37
+ }
38
+
39
+ function convertUnit(time) {
40
+ let convertedTime = time
41
+ if (convertedTime > 1e18) {
42
+ // nano seconds
43
+ convertedTime = convertedTime / 1e6
44
+ } else if (convertedTime > 1e15) {
45
+ // micro seconds
46
+ convertedTime = convertedTime / 1e3
47
+ } else if (convertedTime < 1e12) {
48
+ // seconds
49
+ convertedTime = convertedTime * 1e3
50
+ }
51
+
52
+ return convertedTime
53
+ }
54
+
55
+ /**
56
+ * Returns the value of the Content-Length header
57
+ *
58
+ * If no header is found, returns -1
59
+ *
60
+ * @param {*} headers
61
+ */
62
+ function getContentLengthFromHeaders(headers) {
63
+ let contentLength = -1
64
+ for (const [headerName, headerValue] of Object.entries(headers)) {
65
+ if (CONTENT_LENGTH_REGEX.test(headerName)) {
66
+ return headerValue
67
+ }
68
+ }
69
+ return contentLength
70
+ }
71
+
72
+ module.exports = {
73
+ getQueueTime,
74
+ getContentLengthFromHeaders
75
+ }
@@ -33,8 +33,20 @@ function initialize(agent, childProcess, moduleName, shim) {
33
33
  const args = shim.argsToArray.apply(shim, arguments)
34
34
  const cbIndex = args.length - 1
35
35
 
36
+ const originalListener = args[cbIndex]
37
+ if (!shim.isFunction(originalListener)) {
38
+ return fn.apply(this, arguments)
39
+ }
40
+
36
41
  shim.bindSegment(args, cbIndex)
37
42
 
43
+ // Leverage events.removeListener() mechanism that checks listener
44
+ // property to allow our wrapped listeners to match and remove appropriately.
45
+ // Avoids having to instrument removeListener() and potentially doubling
46
+ // lookup. Since our wrapping will only be referenced by the events
47
+ // collection, we should not need to unwrap.
48
+ args[cbIndex].listener = originalListener
49
+
38
50
  return fn.apply(this, args)
39
51
  }
40
52
  }
@@ -195,6 +195,10 @@ function handleError(segment, req, error) {
195
195
  * @param {http.IncomingMessage} res
196
196
  */
197
197
  function handleResponse(segment, hostname, req, res) {
198
+ // Add response attributes for spans
199
+ segment.addAttribute('http.statusCode', res.statusCode)
200
+ segment.addAttribute('http.statusText', res.statusMessage)
201
+
198
202
  // If CAT is enabled, grab those headers!
199
203
  const agent = segment.transaction.agent
200
204
  if (