newrelic 6.13.2 → 7.0.2

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 (48) hide show
  1. package/NEWS.md +80 -7
  2. package/README.md +14 -5
  3. package/ROADMAP_Node.md +1 -1
  4. package/THIRD_PARTY_NOTICES.md +10 -12
  5. package/api.js +0 -32
  6. package/bin/run-versioned-tests.sh +3 -8
  7. package/index.js +5 -3
  8. package/lib/agent.js +4 -0
  9. package/lib/collector/api.js +2 -2
  10. package/lib/collector/http-agents.js +1 -1
  11. package/lib/collector/remote-method.js +18 -12
  12. package/lib/config/attribute-filter.js +1 -1
  13. package/lib/config/default.js +3 -11
  14. package/lib/config/env.js +0 -1
  15. package/lib/config/index.js +2 -23
  16. package/lib/db/parse-sql.js +1 -1
  17. package/lib/db/query-parsers/sql.js +1 -1
  18. package/lib/errors/error-collector.js +6 -1
  19. package/lib/errors/index.js +14 -1
  20. package/lib/grpc/connection.js +28 -4
  21. package/lib/instrumentation/core/http.js +34 -82
  22. package/lib/instrumentation/fastify/spec-builders.js +1 -1
  23. package/lib/metrics/names.js +4 -1
  24. package/lib/parse-proc-cpuinfo.js +1 -1
  25. package/lib/proxy/grpc.js +1 -1
  26. package/lib/serverless/aws-lambda.js +9 -23
  27. package/lib/shim/promise-shim.js +1 -1
  28. package/lib/shim/shim.js +3 -3
  29. package/lib/shim/transaction-shim.js +1 -1
  30. package/lib/shim/webframework-shim.js +1 -1
  31. package/lib/shimmer.js +1 -1
  32. package/lib/spans/create-span-event-aggregator.js +1 -1
  33. package/lib/spans/span-context.js +18 -0
  34. package/lib/spans/span-event.js +13 -2
  35. package/lib/spans/span-streamer.js +24 -13
  36. package/lib/spans/streaming-span-event-aggregator.js +4 -0
  37. package/lib/spans/streaming-span-event.js +13 -1
  38. package/lib/timer.js +1 -1
  39. package/lib/transaction/dt-payload.js +2 -2
  40. package/lib/transaction/handle.js +0 -54
  41. package/lib/transaction/index.js +25 -39
  42. package/lib/transaction/tracecontext.js +19 -4
  43. package/lib/util/byte-limit.js +1 -1
  44. package/lib/utilization/common.js +1 -1
  45. package/newrelic.js +1 -1
  46. package/package.json +11 -11
  47. package/stub_api.js +1 -1
  48. package/third_party_manifest.json +36 -36
@@ -38,7 +38,7 @@ class GrpcConnection extends EventEmitter {
38
38
  /**
39
39
  * GrpcConnection constructor
40
40
  *
41
- * Standard property setting/initilization, and sets an initial
41
+ * Standard property setting/initialization, and sets an initial
42
42
  * connection state of disconnected
43
43
  *
44
44
  * @param {Object} traceObserverConfig config item config.infinite_tracing.trace_observer
@@ -121,7 +121,7 @@ class GrpcConnection extends EventEmitter {
121
121
  // immediately.
122
122
  this._setState(connectionStates.connected, this.stream)
123
123
  } catch (err) {
124
- logger.trace(
124
+ logger.warn(
125
125
  err,
126
126
  'Unexpected error establishing gRPC stream, will not attempt reconnect.'
127
127
  )
@@ -198,9 +198,26 @@ class GrpcConnection extends EventEmitter {
198
198
  if (this.stream) {
199
199
  this.stream.removeAllListeners()
200
200
 
201
+ const oldStream = this.stream
202
+ this.stream.on('status', function endStreamStatusHandler(grpcStatus) {
203
+ logger.trace('End stream status received [%s]: %s', grpcStatus.code, grpcStatus.details)
204
+
205
+ // Cleanup the final end stream listeners.
206
+ oldStream.removeAllListeners()
207
+ })
208
+
209
+ // Listen to any final errors to prevent throwing.
210
+ // This is unlikely but if the server closes post
211
+ // removing listeners and prior to response it could
212
+ // happen. We noticed this via tests on Node 14.
213
+ this.stream.on('error', function endStreamErrorHandler(err) {
214
+ logger.trace('End stream error received. Code: [%s]: %s', err.code, err.details)
215
+ })
216
+
201
217
  // Indicates to server we are done.
202
218
  // Server officially closes the stream.
203
219
  this.stream.end()
220
+
204
221
  this.stream = null
205
222
  }
206
223
 
@@ -238,6 +255,9 @@ class GrpcConnection extends EventEmitter {
238
255
  // per the spec, An UNIMPLEMENTED status code from gRPC indicates
239
256
  // that the versioned Trace Observer is no longer available. Agents
240
257
  // MUST NOT attempt to reconnect in this case
258
+ logger.info(
259
+ '[UNIMPLEMENTED]: Trace Obserserver is no longer available. Shutting down connection.'
260
+ )
241
261
  this._disconnect()
242
262
  } else if (grpc.status[grpc.status.OK] === grpcStatusName) {
243
263
  this._reconnect()
@@ -256,7 +276,11 @@ class GrpcConnection extends EventEmitter {
256
276
  this._metrics.getOrCreateMetric(NAMES.INFINITE_TRACING.SPAN_RESPONSE_ERROR)
257
277
  .incrementCallCount()
258
278
 
259
- logger.trace('span stream error. Code: [%s]: %s',err.code, err.details)
279
+ // For errors, the status will either result in a disconnect or a reconnect
280
+ // delay that should prevent too frequent spamming. Unless the app is idle
281
+ // and regularly getting Status 13 reconnects from the server, in which case
282
+ // this will be almost the only logging.
283
+ logger.warn('Span stream error. Code: [%s]: %s', err.code, err.details)
260
284
  })
261
285
  }
262
286
 
@@ -266,7 +290,7 @@ class GrpcConnection extends EventEmitter {
266
290
  _generateCredentials(grpcApi) {
267
291
  let certBuffer = null
268
292
 
269
- // Current settable value for testing. If allowed to be overriden via
293
+ // Current settable value for testing. If allowed to be overridden via
270
294
  // configuration, this should be removed in place of setting
271
295
  // this._rootCerts from config via normal configuration precedence.
272
296
  const envTestCerts = process.env.NEWRELIC_GRPCCONNECTION_CA
@@ -15,7 +15,6 @@ const util = require('util')
15
15
  const url = require('url')
16
16
  const urltils = require('../../util/urltils')
17
17
  const properties = require('../../util/properties')
18
- const psemver = require('../../util/process-version')
19
18
  const headerAttributes = require('../../header-attributes')
20
19
  const headerProcessing = require('../../header-processing')
21
20
 
@@ -27,8 +26,6 @@ const DESTS = require('../../config/attribute-filter').DESTINATIONS
27
26
  * CONSTANTS
28
27
  *
29
28
  */
30
- const SHOULD_WRAP_HTTPS = psemver.satisfies('>=9.0.0 || 8.9.0')
31
- const SHOULD_FORMAT_ARGS = psemver.satisfies('>=10.0.0')
32
29
  const NR_CONNECTION_PROP = '__NR__connection'
33
30
  const NEWRELIC_ID_HEADER = 'x-newrelic-id'
34
31
  const NEWRELIC_APP_DATA_HEADER = 'x-newrelic-app-data'
@@ -172,16 +169,7 @@ function wrapEmitWithTransaction(agent, emit, isHTTPS) {
172
169
 
173
170
  if (response) {
174
171
  if (response.statusCode != null) {
175
- /*
176
- TODO: remove with next major release
177
- httpResponseCode attribute is deprecated
178
- */
179
172
  var responseCode = String(response.statusCode)
180
- transaction.trace.attributes.addAttribute(
181
- DESTS.TRANS_COMMON,
182
- 'httpResponseCode',
183
- responseCode
184
- )
185
173
 
186
174
  if (/^\d+$/.test(responseCode)) {
187
175
  transaction.trace.attributes.addAttribute(
@@ -189,18 +177,8 @@ function wrapEmitWithTransaction(agent, emit, isHTTPS) {
189
177
  'http.statusCode',
190
178
  responseCode
191
179
  )
192
-
193
- segment.addSpanAttribute('http.statusCode', responseCode)
194
180
 
195
- /*
196
- TODO: remove with next major release
197
- response.status attribute is deprecated
198
- */
199
- transaction.trace.attributes.addAttribute(
200
- DESTS.TRANS_COMMON,
201
- 'response.status',
202
- responseCode
203
- )
181
+ segment.addSpanAttribute('http.statusCode', responseCode)
204
182
  }
205
183
  }
206
184
 
@@ -212,16 +190,6 @@ function wrapEmitWithTransaction(agent, emit, isHTTPS) {
212
190
  )
213
191
 
214
192
  segment.addSpanAttribute('http.statusText', response.statusMessage)
215
-
216
- /*
217
- TODO: remove with next major release
218
- httpResponseMessage attribute is deprecated
219
- */
220
- transaction.trace.attributes.addAttribute(
221
- DESTS.TRANS_COMMON,
222
- 'httpResponseMessage',
223
- response.statusMessage
224
- )
225
193
  }
226
194
 
227
195
  var headers = response.getHeaders()
@@ -299,7 +267,7 @@ function wrapResponseEnd(agent, proto) {
299
267
  return end.apply(this, arguments)
300
268
  }
301
269
 
302
- // If an error happend, add it to the aggregator.
270
+ // If an error happened, add it to the aggregator.
303
271
  if (txInfo.error) {
304
272
  if (!txInfo.errorHandled || urltils.isError(agent.config, this.statusCode)) {
305
273
  agent.errors.add(txInfo.transaction, txInfo.error)
@@ -323,7 +291,7 @@ function wrapResponseEnd(agent, proto) {
323
291
  })
324
292
  }
325
293
 
326
- // CAT this wont be used unless CAT is enabled, see below where we actually do
294
+ // CAT this won't be used unless CAT is enabled, see below where we actually do
327
295
  // the shimmer stuff if you'd like to verify.
328
296
  function wrapWriteHead(agent, writeHead) {
329
297
  return function wrappedWriteHead() {
@@ -442,37 +410,30 @@ function urlToOptions(_url) {
442
410
  }
443
411
 
444
412
  function wrapRequest(agent, request) {
445
- // URL as a global was not added until Node v10
446
- const URLClass = global.URL ? global.URL : url.URL
447
413
  return function wrappedRequest(input, options, cb) {
448
- let reqArgs = arguments
449
- if (SHOULD_FORMAT_ARGS) {
450
- // Support new function signatures in Node v10+. If the first argument is a URL,
451
- // merge it into the options object. This code is copied from Node internals.
452
- if (typeof input === 'string') {
453
- const urlStr = input
454
- input = urlToOptions(new URLClass(urlStr))
455
- } else if (input.constructor && input.constructor.name === 'URL') {
456
- input = urlToOptions(input)
457
- } else {
458
- cb = options
459
- options = input
460
- input = null
461
- }
462
-
463
- if (typeof options === 'function') {
464
- cb = options
465
- options = input || {}
466
- } else {
467
- options = Object.assign(input || {}, options)
468
- }
469
-
470
- reqArgs = [options, cb]
414
+ // If the first argument is a URL, merge it into the options object.
415
+ // This code is copied from Node internals.
416
+ if (typeof input === 'string') {
417
+ const urlStr = input
418
+ input = urlToOptions(new URL(urlStr))
419
+ } else if (input.constructor && input.constructor.name === 'URL') {
420
+ input = urlToOptions(input)
471
421
  } else {
472
- // Node v8 function signature had options as the first argument
422
+ cb = options
473
423
  options = input
424
+ input = null
425
+ }
426
+
427
+ if (typeof options === 'function') {
428
+ cb = options
429
+ options = input || {}
430
+ } else {
431
+ options = Object.assign(input || {}, options)
474
432
  }
475
433
 
434
+ let reqArgs = [options, cb]
435
+
436
+
476
437
  // Don't pollute metrics and calls with NR connections
477
438
  const internalOnly = options && options[NR_CONNECTION_PROP]
478
439
  if (internalOnly) {
@@ -552,28 +513,19 @@ module.exports = function initialize(agent, http, moduleName) {
552
513
 
553
514
  var agentProto = http && http.Agent && http.Agent.prototype
554
515
 
555
- // As of node 0.8, http.request() is the right way to originate outbound
556
- // requests. From 0.11 until 9, the `https` module simply called through to
557
- // the `http` methods, so to prevent double-instrumenting we need to check
558
- // what module we're instrumenting and what version of Node we're on. This
559
- // change originally also appeared in 8.9.0 but was reverted in 8.9.1.
560
- //
561
- // TODO: Remove `SHOULD_WRAP_HTTPS` after deprecating Node <9.
562
- if (SHOULD_WRAP_HTTPS || !IS_HTTPS) {
563
- shimmer.wrapMethod(
564
- http,
565
- 'http',
566
- 'request',
567
- wrapRequest.bind(null, agent)
568
- )
516
+ shimmer.wrapMethod(
517
+ http,
518
+ 'http',
519
+ 'request',
520
+ wrapRequest.bind(null, agent)
521
+ )
569
522
 
570
- shimmer.wrapMethod(
571
- http,
572
- 'http',
573
- 'get',
574
- wrapRequest.bind(null, agent)
575
- )
576
- }
523
+ shimmer.wrapMethod(
524
+ http,
525
+ 'http',
526
+ 'get',
527
+ wrapRequest.bind(null, agent)
528
+ )
577
529
 
578
530
  shimmer.wrapMethod(
579
531
  agentProto,
@@ -59,7 +59,7 @@ function buildMiddlewareSpecForRouteHandler(shim, path) {
59
59
  * then this method is required/useful again.
60
60
  *
61
61
  * The isFinal param determines whether or not a path is appended for
62
- * this particuar piece of middleware. (i.e. if this is the final handler
62
+ * this particular piece of middleware. (i.e. if this is the final handler
63
63
  * that is actually handling the request, the path is actually left on)
64
64
  */
65
65
  next: function wrapNext(shim, fn, fnName, args, bindSegment) {
@@ -240,10 +240,13 @@ const SPAN_EVENTS = {
240
240
  const INFINITE_TRACING = {
241
241
  SEEN: SUPPORTABILITY.INFINITE_TRACING + '/Span/Seen',
242
242
  SENT: SUPPORTABILITY.INFINITE_TRACING + '/Span/Sent',
243
+ DROPPED: SUPPORTABILITY.INFINITE_TRACING + '/Span/Dropped',
243
244
  SPAN_RESPONSE_ERROR: SUPPORTABILITY.INFINITE_TRACING + '/Span/Response/Error',
244
245
  SPAN_RESPONSE_GRPC_UNIMPLEMENTED: SUPPORTABILITY.INFINITE_TRACING + '/Span/gRPC/UNIMPLEMENTED',
245
246
  SPAN_RESPONSE_GRPC_STATUS: SUPPORTABILITY.INFINITE_TRACING + '/Span/gRPC/%s',
246
- DRAIN_DURATION: SUPPORTABILITY.INFINITE_TRACING + '/Drain/Duration'
247
+ QUEUE_CAPACITY: SUPPORTABILITY.INFINITE_TRACING + '/Span/QueueCapacity',
248
+ QUEUE_SIZE: SUPPORTABILITY.INFINITE_TRACING + '/Span/QueueSize',
249
+ DRAIN_DURATION: SUPPORTABILITY.INFINITE_TRACING + '/Drain/Duration',
247
250
  }
248
251
 
249
252
  module.exports = {
@@ -22,7 +22,7 @@ function parseProcCPUInfo(data) {
22
22
  packages: null
23
23
  }
24
24
 
25
- // seperate the processors
25
+ // separate the processors
26
26
  var splitData = data.split('\n')
27
27
  .map(function formatAttribute(attr) {
28
28
  return attr.split(':')
package/lib/proxy/grpc.js CHANGED
@@ -8,7 +8,7 @@ class ProxyGrpc {
8
8
  constructor(grpcLibrary = '@grpc/grpc-js') {
9
9
  this.library = require(grpcLibrary)
10
10
 
11
- // add methods or objets from base grpc class that we need as needed
11
+ // add methods or objects from base grpc class that we need as needed
12
12
  this.credentials = this.library.credentials
13
13
  this.Metadata = this.library.Metadata
14
14
  this.loadPackageDefinition = this.library.loadPackageDefinition
@@ -129,7 +129,6 @@ class AwsLambda {
129
129
  const segmentRecorder = isApiGatewayLambdaProxy ? recordWeb : recordBackground
130
130
  const segment = shim.createSegment(functionName, segmentRecorder)
131
131
  transaction.baseSegment = segment
132
-
133
132
  // resultProcessor is used to execute additional logic based on the
134
133
  // payload supplied to the callback.
135
134
  let resultProcessor
@@ -180,7 +179,6 @@ class AwsLambda {
180
179
  shim.agent.setLambdaArn(context.invokedFunctionArn)
181
180
 
182
181
  shim.agent.setLambdaFunctionVersion(context.functionVersion)
183
-
184
182
  segment.addSpanAttributes(awsAttributes)
185
183
 
186
184
  segment.start()
@@ -194,7 +192,7 @@ class AwsLambda {
194
192
  throw err
195
193
  }
196
194
  if (shim.isPromise(res)) {
197
- res = lambdaInterceptPromise(res, txnEnder)
195
+ res = lambdaInterceptPromise(res, resultProcessor, txnEnder)
198
196
  }
199
197
  return res
200
198
  }
@@ -202,8 +200,11 @@ class AwsLambda {
202
200
  // In order to capture error events
203
201
  // we need to store the error in uncaughtException
204
202
  // otherwise the transaction will end before they are captured
205
- function lambdaInterceptPromise(prom, cb) {
203
+ function lambdaInterceptPromise(prom, resultProcessor, cb) {
206
204
  return prom.then(function onThen(arg) {
205
+ if (resultProcessor) {
206
+ resultProcessor(arg)
207
+ }
207
208
  cb()
208
209
  return arg
209
210
  }, function onCatch(err) {
@@ -389,33 +390,18 @@ function setWebResponse(transaction, response) {
389
390
  transaction.statusCode = response.statusCode
390
391
 
391
392
  const responseCode = String(response.statusCode)
392
- /*
393
- TODO: remove with next major release
394
- httpResponseCode attribute is deprecated
395
- */
396
- transaction.trace.attributes.addAttribute(
397
- ATTR_DEST.TRANS_COMMON,
398
- 'httpResponseCode',
399
- responseCode
400
- )
401
393
 
402
394
  if (/^\d+$/.test(responseCode)) {
403
- /*
404
- TODO: remove with next major release
405
- response.status attribute is deprecated
406
- */
407
- transaction.trace.attributes.addAttribute(
408
- ATTR_DEST.TRANS_COMMON,
409
- 'response.status',
410
- responseCode)
411
-
412
395
  transaction.trace.attributes.addAttribute(
413
396
  ATTR_DEST.TRANS_COMMON,
414
397
  'http.statusCode',
415
398
  responseCode
416
399
  )
417
400
 
418
- const segment = transaction.agent.tracer.getSegment()
401
+ // We are adding http.statusCode to base segment as
402
+ // we found in testing async invoked lambdas, the
403
+ // active segement is not available at this point.
404
+ const segment = transaction.baseSegment
419
405
 
420
406
  segment.addSpanAttribute('http.statusCode', responseCode)
421
407
  }
@@ -46,7 +46,7 @@ class PromiseShim extends Shim {
46
46
  }
47
47
 
48
48
  /**
49
- * Sets the class used to indentify promises from the wrapped promise library.
49
+ * Sets the class used to identify promises from the wrapped promise library.
50
50
  *
51
51
  * @param {function} clss - The promise library's class.
52
52
  */
package/lib/shim/shim.js CHANGED
@@ -308,7 +308,7 @@ Shim.prototype.__NR_unwrap = unwrapAll
308
308
  *
309
309
  * @description
310
310
  * The syntax for declarative instrumentation. It can be used interlaced with
311
- * custom, hand-written instrumentation for one-off or hard to simplifiy
311
+ * custom, hand-written instrumentation for one-off or hard to simplify
312
312
  * instrumentation logic.
313
313
  *
314
314
  * @property {Spec|WrapFunction} $return
@@ -1949,10 +1949,10 @@ function bindPromise(promise, segment) {
1949
1949
  * Copies the given parameters onto the segment, respecting the current agent
1950
1950
  * configuration.
1951
1951
  *
1952
- * @memberof Shim.protoype
1952
+ * @memberof Shim.prototype
1953
1953
  *
1954
1954
  * @param {TraceSegment} segment - The segment to copy the parameters onto.
1955
- * @param {object} parameters - The paramters to copy.
1955
+ * @param {object} parameters - The parameters to copy.
1956
1956
  */
1957
1957
  function copySegmentParameters(segment, parameters) {
1958
1958
  for (var key in parameters) {
@@ -184,7 +184,7 @@ function bindCreateTransaction(nodule, property, spec) {
184
184
  *
185
185
  * - `pushTransactionName(pathSegment)`
186
186
  *
187
- * Transactions are named for the middlware that sends the reponse. Some web
187
+ * Transactions are named for the middlware that sends the response. Some web
188
188
  * frameworks are capable of mounting middlware in complex routing stacks. In
189
189
  * order to maintain the correct name, transactions keep a stack of mount points
190
190
  * for each middlware/router/app/whatever. The instrumentation should push on
@@ -331,7 +331,7 @@ function setFramework(framework) {
331
331
  }
332
332
 
333
333
  /**
334
- * Sets the URI path to be used for naming the transaction currenty in scope.
334
+ * Sets the URI path to be used for naming the transaction currently in scope.
335
335
  *
336
336
  * @memberof WebFrameworkShim.prototype
337
337
  *
package/lib/shimmer.js CHANGED
@@ -381,7 +381,7 @@ var shimmer = module.exports = {
381
381
  * Forces file name resolve for modules not in our cache when
382
382
  * their parent has already been loaded/cached by Node.
383
383
  * Provides a fall-back for unexpected cases that may occur.
384
- * Also provides flexibilty for testing now that node 11+ caches these.
384
+ * Also provides flexibility for testing now that node 11+ caches these.
385
385
  * @param {*} request
386
386
  * @param {*} parent
387
387
  * @param {*} isMain
@@ -16,7 +16,7 @@ function createSpanEventAggregator(config, collector, metrics) {
16
16
  // TODO: ideally this validation and configuration clearing would happen
17
17
  // in the config. Since we don't currently have a way to generate
18
18
  // support metrics in the config, keeping this related logic together here.
19
- // If logic happened prior, could merely check for existance of trace_observer.host.
19
+ // If logic happened prior, could merely check for existence of trace_observer.host.
20
20
  shouldCreateStreaming = validateInfiniteTracing(config.infinite_tracing.trace_observer)
21
21
 
22
22
  if (!shouldCreateStreaming) {
@@ -21,6 +21,9 @@ class SpanContext {
21
21
  customAttributes || new PrioritizedAttributes(ATTRIBUTE_SCOPE, MAXIMUM_CUSTOM_ATTRIBUTES)
22
22
 
23
23
  this.ATTRIBUTE_PRIORITY = ATTRIBUTE_PRIORITY
24
+
25
+ this.hasError = false
26
+ this.errorDetails = null
24
27
  }
25
28
 
26
29
  addIntrinsicAttribute(key, value) {
@@ -36,6 +39,21 @@ class SpanContext {
36
39
  priority
37
40
  )
38
41
  }
42
+
43
+ /**
44
+ * Set error details to be potentially be used to create span
45
+ * attributes. Attributes will be created unless the transaction
46
+ * ends with an ignored error status code.
47
+ *
48
+ * Last error wins.
49
+ */
50
+ setError(details) {
51
+ this.hasError = true
52
+
53
+ // Error details will be used to create attributes unless the transaction ends
54
+ // with an ignored status code.
55
+ this.errorDetails = details
56
+ }
39
57
  }
40
58
 
41
59
  module.exports = SpanContext
@@ -84,9 +84,21 @@ class SpanEvent {
84
84
  * @return {SpanEvent} The constructed event.
85
85
  */
86
86
  static fromSegment(segment, parentId = null, isRoot = false) {
87
+ const spanContext = segment.getSpanContext()
88
+
89
+ // Since segments already hold span agent attributes and we want to leverage
90
+ // filtering, we add to the segment attributes prior to processing.
91
+ if (spanContext.hasError && !segment.transaction.hasIgnoredErrorStatusCode()) {
92
+ const details = spanContext.errorDetails
93
+ segment.addSpanAttribute('error.message', details.message)
94
+ segment.addSpanAttribute('error.class', details.type)
95
+ if (details.expected) {
96
+ segment.addSpanAttribute('error.expected', details.expected)
97
+ }
98
+ }
99
+
87
100
  const attributes = segment.attributes.get(DESTINATIONS.SPAN_EVENT)
88
101
 
89
- const spanContext = segment.getSpanContext()
90
102
  const customAttributes = spanContext.customAttributes.get(DESTINATIONS.SPAN_EVENT)
91
103
 
92
104
  let span = null
@@ -98,7 +110,6 @@ class SpanEvent {
98
110
  span = new SpanEvent(attributes, customAttributes)
99
111
  }
100
112
 
101
-
102
113
  for (const [key, value] of Object.entries(spanContext.intrinsicAttributes)) {
103
114
  span.intrinsics[key] = value
104
115
  }
@@ -8,10 +8,10 @@
8
8
  const logger = require('../logger').child({component: 'span-streamer'})
9
9
  const NAMES = require('../metrics/names').INFINITE_TRACING
10
10
 
11
+ const SPAN_DROP_MSG_INTERVAL_MS = 30000
11
12
  const SPAN_DROP_MSG =
12
- 'Queue full, dropping spans.'
13
- const SPAN_DROP_INTERVAL = 5
14
-
13
+ 'Queue full, dropping spans. ' +
14
+ `Will not warn again for ${SPAN_DROP_MSG_INTERVAL_MS / 1000} seconds.`
15
15
 
16
16
  class SpanStreamer {
17
17
  constructor(license_key, connection, metrics, queue_size) {
@@ -47,21 +47,20 @@ class SpanStreamer {
47
47
 
48
48
  // If not writeable (because of backpressure) queue the span
49
49
  if (!this._writable) {
50
- if (this.spans.length <= this.queue_size) {
50
+ if (this.spans.length < this.queue_size) {
51
51
  this.spans.push(span)
52
52
  return
53
53
  }
54
54
 
55
+ // While this can be directionally calculated between seen/sent the
56
+ // queue makes that a bit more disconnected. This will be a bit more specific.
57
+ this._metrics.getOrCreateMetric(NAMES.DROPPED).incrementCallCount()
58
+
55
59
  // If the queue is full drop the span
56
60
  logger.infoOncePer(
57
- // key for the OncePer
58
- 'SPAN_DROP_MSG',
59
- // interval in ms
60
- SPAN_DROP_INTERVAL * 1000,
61
- // message
62
- SPAN_DROP_MSG + ' Will not warn again for %s seconds.',
63
- // variables to put in log message
64
- SPAN_DROP_INTERVAL
61
+ 'SPAN_DROP_MSG', // key for the OncePer
62
+ SPAN_DROP_MSG_INTERVAL_MS,
63
+ SPAN_DROP_MSG
65
64
  )
66
65
 
67
66
  return
@@ -121,14 +120,21 @@ class SpanStreamer {
121
120
  }
122
121
 
123
122
  sendQueue() {
123
+ logger.trace('Sending spans from queue.')
124
+
124
125
  // Continue sending the spans that were in the queue. _writable is checked
125
126
  // so that if a send fails while clearing the queue, this drain handler can
126
127
  // finish, and the drain handler setup on the failed send will then attempt
127
128
  // to clear the queue
128
129
  while (this.spans.length > 0 && this._writable) {
129
130
  const nextObject = this.spans.shift()
130
- this.send(nextObject)
131
+ this.send(nextObject.toStreamingFormat())
131
132
  }
133
+
134
+ logger.trace(
135
+ 'Finished sending spans from queue. Items left in queue: %s',
136
+ this.spans.length
137
+ )
132
138
  }
133
139
 
134
140
  connect(agent_run_id, requestHeadersMap) {
@@ -144,6 +150,11 @@ class SpanStreamer {
144
150
  disconnect() {
145
151
  this.connection.disconnect()
146
152
  }
153
+
154
+ createMetrics() {
155
+ this._metrics.getOrCreateMetric(NAMES.QUEUE_CAPACITY).recordValue(this.queue_size)
156
+ this._metrics.getOrCreateMetric(NAMES.QUEUE_SIZE).recordValue(this.spans.length)
157
+ }
147
158
  }
148
159
 
149
160
  module.exports = SpanStreamer
@@ -98,6 +98,10 @@ class StreamingSpanEventAggregator extends Aggregator {
98
98
 
99
99
  this.requestHeadersMap = config.request_headers_map
100
100
  }
101
+
102
+ createMetrics() {
103
+ this.stream.createMetrics()
104
+ }
101
105
  }
102
106
 
103
107
  module.exports = StreamingSpanEventAggregator
@@ -102,9 +102,21 @@ class StreamingSpanEvent {
102
102
  }
103
103
 
104
104
  static fromSegment(segment, parentId = null, isRoot = false) {
105
+ const spanContext = segment.getSpanContext()
106
+
107
+ // Since segments already hold span agent attributes and we want to leverage
108
+ // filtering, we add to the segment attributes prior to processing.
109
+ if (spanContext.hasError && !segment.transaction.hasIgnoredErrorStatusCode()) {
110
+ const details = spanContext.errorDetails
111
+ segment.addSpanAttribute('error.message', details.message)
112
+ segment.addSpanAttribute('error.class', details.type)
113
+ if (details.expected) {
114
+ segment.addSpanAttribute('error.expected', details.expected)
115
+ }
116
+ }
117
+
105
118
  const agentAttributes = segment.attributes.get(DESTINATIONS.SPAN_EVENT)
106
119
 
107
- const spanContext = segment.getSpanContext()
108
120
  const customAttributes = spanContext.customAttributes.get(DESTINATIONS.SPAN_EVENT)
109
121
 
110
122
  const transaction = segment.transaction
package/lib/timer.js CHANGED
@@ -112,7 +112,7 @@ Timer.prototype.hasEnd = function hasEnd() {
112
112
  }
113
113
 
114
114
  /*
115
- * Sets duration and stops the timer, since the passed-in duration will take precendence
115
+ * Sets duration and stops the timer, since the passed-in duration will take precedence
116
116
  * over the measured duration.
117
117
  * @param {number} duration The duration the timer should report.
118
118
  */
@@ -12,8 +12,8 @@ const DT_VERSION_MINOR = 1
12
12
 
13
13
  module.exports = class DistributedTracePayload {
14
14
  /**
15
- * The class reponsible for producing distributed trace payloads.
16
- * Created by calling {@link TransactionHandle#createDistributedTracePayload}.
15
+ * The class responsible for producing distributed trace payloads.
16
+ * Created by calling {@link TransactionHandle#_createDistributedTracePayload}.
17
17
  *
18
18
  * @constructor
19
19
  */