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
@@ -12,6 +12,7 @@ const urltils = require('../../util/urltils')
12
12
  const properties = require('../../util/properties')
13
13
  const psemver = require('../../util/process-version')
14
14
  const headerAttributes = require('../../header-attributes')
15
+ const headerProcessing = require('../../header-processing')
15
16
 
16
17
  const NAMES = require('../../metrics/names')
17
18
  const DESTS = require('../../config/attribute-filter').DESTINATIONS
@@ -22,14 +23,12 @@ const DESTS = require('../../config/attribute-filter').DESTINATIONS
22
23
  *
23
24
  */
24
25
  const SHOULD_WRAP_HTTPS = psemver.satisfies('>=9.0.0 || 8.9.0')
26
+ const SHOULD_FORMAT_ARGS = psemver.satisfies('>=10.0.0')
25
27
  const NR_CONNECTION_PROP = '__NR__connection'
26
- const REQUEST_HEADER = 'x-request-start'
27
- const QUEUE_HEADER = 'x-queue-start'
28
28
  const NEWRELIC_ID_HEADER = 'x-newrelic-id'
29
29
  const NEWRELIC_APP_DATA_HEADER = 'x-newrelic-app-data'
30
30
  const NEWRELIC_TRANSACTION_HEADER = 'x-newrelic-transaction'
31
31
  const NEWRELIC_SYNTHETICS_HEADER = 'x-newrelic-synthetics'
32
- const CONTENT_LENGTH_REGEX = /^Content-Length$/i
33
32
  const TRANSACTION_INFO_KEY = '__NR_transactionInfo'
34
33
 
35
34
 
@@ -113,36 +112,11 @@ function wrapEmitWithTransaction(agent, emit, isHTTPS) {
113
112
  'Applying user naming rules for RUM.')
114
113
  transaction.applyUserNamingRules(request.url)
115
114
 
116
- /**
117
- * Calculate Queue Time
118
- *
119
- * Queue time is provided by certain providers by stamping the request
120
- * header with the time the request arrived at the router.
121
- *
122
- * Units for queue time are
123
- */
124
- var qtime = request.headers[REQUEST_HEADER] || request.headers[QUEUE_HEADER]
125
- if (qtime) {
126
- var split = qtime.split('=')
127
- if (split.length > 1) {
128
- qtime = split[1]
129
- }
130
-
131
- var start = parseFloat(qtime)
132
-
133
- if (isNaN(start)) {
134
- logger.warn('Queue time header parsed as NaN (%s)', qtime)
135
- } else {
136
- // nano seconds
137
- if (start > 1e18) start = start / 1e6
138
- // micro seconds
139
- else if (start > 1e15) start = start / 1e3
140
- // seconds
141
- else if (start < 1e12) start = start * 1e3
142
-
143
- transaction.queueTime = Date.now() - start
144
- }
115
+ const queueTimeStamp = headerProcessing.getQueueTime(logger, request.headers)
116
+ if (queueTimeStamp) {
117
+ transaction.queueTime = Date.now() - queueTimeStamp
145
118
  }
119
+
146
120
  if (agent.config.distributed_tracing.enabled) {
147
121
  // Node http headers are automatically lowercase
148
122
  transaction.acceptDistributedTraceHeaders(transport, request.headers)
@@ -179,6 +153,10 @@ function wrapEmitWithTransaction(agent, emit, isHTTPS) {
179
153
 
180
154
  if (response) {
181
155
  if (response.statusCode != null) {
156
+ /*
157
+ TODO: remove with next major release
158
+ httpResponseCode attribute is deprecated
159
+ */
182
160
  var responseCode = String(response.statusCode)
183
161
  transaction.trace.attributes.addAttribute(
184
162
  DESTS.TRANS_COMMON,
@@ -187,6 +165,16 @@ function wrapEmitWithTransaction(agent, emit, isHTTPS) {
187
165
  )
188
166
 
189
167
  if (/^\d+$/.test(responseCode)) {
168
+ transaction.trace.attributes.addAttribute(
169
+ DESTS.TRANS_COMMON,
170
+ 'http.statusCode',
171
+ responseCode
172
+ )
173
+
174
+ /*
175
+ TODO: remove with next major release
176
+ response.status attribute is deprecated
177
+ */
190
178
  transaction.trace.attributes.addAttribute(
191
179
  DESTS.TRANS_COMMON,
192
180
  'response.status',
@@ -194,7 +182,18 @@ function wrapEmitWithTransaction(agent, emit, isHTTPS) {
194
182
  )
195
183
  }
196
184
  }
185
+
197
186
  if (response.statusMessage !== undefined) {
187
+ transaction.trace.attributes.addAttribute(
188
+ DESTS.TRANS_COMMON,
189
+ 'http.statusText',
190
+ response.statusMessage
191
+ )
192
+
193
+ /*
194
+ TODO: remove with next major release
195
+ httpResponseMessage attribute is deprecated
196
+ */
198
197
  transaction.trace.attributes.addAttribute(
199
198
  DESTS.TRANS_COMMON,
200
199
  'httpResponseMessage',
@@ -202,8 +201,7 @@ function wrapEmitWithTransaction(agent, emit, isHTTPS) {
202
201
  )
203
202
  }
204
203
 
205
- // TODO: Just use `getHeaders()` after deprecating Node <7.7.
206
- var headers = (response.getHeaders && response.getHeaders()) || response._headers
204
+ var headers = response.getHeaders()
207
205
  if (headers) {
208
206
  headerAttributes.collectResponseHeaders(headers, transaction)
209
207
  }
@@ -356,25 +354,12 @@ function wrapWriteHead(agent, writeHead) {
356
354
  var new_headers = arguments[arguments.length - 1]
357
355
 
358
356
  if (typeof new_headers === 'object') {
359
- for (var header in new_headers) { // jshint ignore: line
360
- if (CONTENT_LENGTH_REGEX.test(header)) {
361
- contentLength = new_headers[header]
362
- break
363
- }
364
- }
357
+ contentLength = headerProcessing.getContentLengthFromHeaders(new_headers)
365
358
  }
366
359
 
367
- if (contentLength === -1 && this._headers) {
368
- // JSHint complains about ownProperty stuff, but since we are looking
369
- // for a specific name that doesn't matter so I'm disabling it.
370
- // Outbound headers can be capitalized in any way, use regex instead
371
- // of direct lookup.
372
- for (var userHeader in this._headers) { // jshint ignore: line
373
- if (CONTENT_LENGTH_REGEX.test(userHeader)) {
374
- contentLength = this._headers[userHeader]
375
- break
376
- }
377
- }
360
+ const currentHeaders = this.getHeaders()
361
+ if (contentLength === -1 && currentHeaders) {
362
+ contentLength = headerProcessing.getContentLengthFromHeaders(currentHeaders)
378
363
  }
379
364
  // Stored on the tx so we can push a metric with this time instead of
380
365
  // actual duration.
@@ -411,8 +396,60 @@ function wrapWriteHead(agent, writeHead) {
411
396
  }
412
397
  }
413
398
 
399
+ // Taken from the Node code base, internal/url.js
400
+ function urlToOptions(_url) {
401
+ const options = {
402
+ protocol: _url.protocol,
403
+ hostname: typeof _url.hostname === 'string' && _url.hostname.startsWith('[') ?
404
+ _url.hostname.slice(1, -1) :
405
+ _url.hostname,
406
+ hash: _url.hash,
407
+ search: _url.search,
408
+ pathname: _url.pathname,
409
+ path: `${_url.pathname || ''}${_url.search || ''}`,
410
+ href: _url.href
411
+ }
412
+ if (_url.port !== '') {
413
+ options.port = Number(_url.port)
414
+ }
415
+ if (_url.username || _url.password) {
416
+ options.auth = `${_url.username}:${_url.password}`
417
+ }
418
+ return options
419
+ }
420
+
414
421
  function wrapRequest(agent, request) {
415
- return function wrappedRequest(options) {
422
+ // URL as a global was not added until Node v10
423
+ const URLClass = global.URL ? global.URL : url.URL
424
+ return function wrappedRequest(input, options, cb) {
425
+ let reqArgs = arguments
426
+ if (SHOULD_FORMAT_ARGS) {
427
+ // Support new function signatures in Node v10+. If the first argument is a URL,
428
+ // merge it into the options object. This code is copied from Node internals.
429
+ if (typeof input === 'string') {
430
+ const urlStr = input
431
+ input = urlToOptions(new URLClass(urlStr))
432
+ } else if (input.constructor && input.constructor.name === 'URL') {
433
+ input = urlToOptions(input)
434
+ } else {
435
+ cb = options
436
+ options = input
437
+ input = null
438
+ }
439
+
440
+ if (typeof options === 'function') {
441
+ cb = options
442
+ options = input || {}
443
+ } else {
444
+ options = Object.assign(input || {}, options)
445
+ }
446
+
447
+ reqArgs = [options, cb]
448
+ } else {
449
+ // Node v8 function signature had options as the first argument
450
+ options = input
451
+ }
452
+
416
453
  // Don't pollute metrics and calls with NR connections
417
454
  const internalOnly = options && options[NR_CONNECTION_PROP]
418
455
  if (internalOnly) {
@@ -430,13 +467,12 @@ function wrapRequest(agent, request) {
430
467
  logOpts.port
431
468
  )
432
469
  }
433
- return request.apply(this, arguments)
470
+ return request.apply(this, reqArgs)
434
471
  }
435
472
 
436
- const args = agent.tracer.slice(arguments)
473
+ const args = agent.tracer.slice(reqArgs)
437
474
  const context = this
438
475
 
439
- // hostname & port logic pulled directly from node's 0.10 lib/http.js
440
476
  return instrumentOutbound(agent, options, function makeRequest(opts) {
441
477
  args[0] = opts
442
478
  return request.apply(context, args)
@@ -444,28 +480,6 @@ function wrapRequest(agent, request) {
444
480
  }
445
481
  }
446
482
 
447
- function wrapLegacyRequest(agent, request) {
448
- return function wrappedLegacyRequest(method, path, headers) {
449
- var makeRequest = request.bind(this, method, path, headers)
450
-
451
- if (agent.tracer.getTransaction()) {
452
- return instrumentOutbound(agent, this, makeRequest)
453
- }
454
-
455
- logger.trace('No transaction, not recording external to %s:%s', this.host, this.port)
456
- return makeRequest()
457
- }
458
- }
459
-
460
- function wrapLegacyClient(agent, proto) {
461
- shimmer.wrapMethod(
462
- proto,
463
- 'http.Client.prototype',
464
- 'request',
465
- wrapLegacyRequest.bind(null, agent)
466
- )
467
- }
468
-
469
483
  module.exports = function initialize(agent, http, moduleName) {
470
484
  if (!http) {
471
485
  logger.debug('Did not get http module, not instrumenting!')
@@ -522,7 +536,7 @@ module.exports = function initialize(agent, http, moduleName) {
522
536
  // change originally also appeared in 8.9.0 but was reverted in 8.9.1.
523
537
  //
524
538
  // TODO: Remove `SHOULD_WRAP_HTTPS` after deprecating Node <9.
525
- if (SHOULD_WRAP_HTTPS || !IS_HTTPS) {
539
+ if (SHOULD_WRAP_HTTPS || !IS_HTTPS) {
526
540
  shimmer.wrapMethod(
527
541
  http,
528
542
  'http',
@@ -530,21 +544,12 @@ module.exports = function initialize(agent, http, moduleName) {
530
544
  wrapRequest.bind(null, agent)
531
545
  )
532
546
 
533
- // Upon updating to https-proxy-agent v2, we are now using
534
- // agent-base v4, which patches https.get to use https.request.
535
- //
536
- // https://github.com/TooTallNate/node-agent-base/commit/7de92df780e147d4618
537
- //
538
- // This means we need to skip instrumenting to avoid double
539
- // instrumenting external requests.
540
- if (!IS_HTTPS && psemver.satisfies('>=8')) {
541
- shimmer.wrapMethod(
542
- http,
543
- 'http',
544
- 'get',
545
- wrapRequest.bind(null, agent)
546
- )
547
- }
547
+ shimmer.wrapMethod(
548
+ http,
549
+ 'http',
550
+ 'get',
551
+ wrapRequest.bind(null, agent)
552
+ )
548
553
  }
549
554
 
550
555
  shimmer.wrapMethod(
@@ -569,58 +574,6 @@ module.exports = function initialize(agent, http, moduleName) {
569
574
  }
570
575
  }
571
576
  )
572
-
573
- // http.Client is deprecated, but still in use
574
- // TODO: Remove this once Node <7 is deprecated.
575
- var DeprecatedClient, deprecatedCreateClient
576
- function clearGetters() {
577
- if (DeprecatedClient) {
578
- delete http.Client
579
- http.Client = DeprecatedClient
580
- }
581
- if (deprecatedCreateClient) {
582
- delete http.createClient
583
- http.createClient = deprecatedCreateClient
584
- }
585
- }
586
-
587
- // TODO: Remove this once Node <7 is deprecated.
588
- DeprecatedClient = shimmer.wrapDeprecated(
589
- http,
590
- 'http',
591
- 'Client',
592
- {
593
- get: function get() {
594
- var example = new DeprecatedClient(80, 'localhost')
595
- wrapLegacyClient(agent, example.constructor.prototype)
596
- clearGetters()
597
-
598
- return DeprecatedClient
599
- },
600
- set: function set(NewClient) {
601
- DeprecatedClient = NewClient
602
- }
603
- }
604
- )
605
-
606
- // TODO: Remove this once Node <7 is deprecated.
607
- deprecatedCreateClient = shimmer.wrapDeprecated(
608
- http,
609
- 'http',
610
- 'createClient',
611
- {
612
- get: function get() {
613
- var example = deprecatedCreateClient(80, 'localhost')
614
- wrapLegacyClient(agent, example.constructor.prototype)
615
- clearGetters()
616
-
617
- return deprecatedCreateClient
618
- },
619
- set: function set(newCreateClient) {
620
- deprecatedCreateClient = newCreateClient
621
- }
622
- }
623
- )
624
577
  }
625
578
 
626
579
  /**
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
- var shared = require('./shared')
3
+ const shared = require('./shared')
4
+ const record = require('../../metrics/recorders/generic')
4
5
 
5
6
  // TODO: abstract and consolidate mostly-shared hapi functionality
6
7
  module.exports = function initialize(agent, hapi, moduleName, shim) {
@@ -154,14 +155,20 @@ function wrapPreHandlers(shim, container, path) {
154
155
  }
155
156
  return container
156
157
  } else if (shim.isFunction(container)) {
157
- return wrapRouteHandler(shim, container, path)
158
+ return wrapPreHandler(shim, container, path)
158
159
  } else if (container.method && shim.isFunction(container.method)) {
159
160
  return shim.wrap(container, 'method', function wrapHandler(shim, handler) {
160
- return wrapRouteHandler(shim, handler, path)
161
+ return wrapPreHandler(shim, handler, path)
161
162
  })
162
163
  }
163
164
  }
164
165
 
166
+ function wrapPreHandler(shim, container, path) {
167
+ return shim.record(container, (shim) => {
168
+ return {name: [shim.HAPI, ' pre handler: ','(',path,')'].join(''), recorder: record}
169
+ })
170
+ }
171
+
165
172
  function wrapRouteHandler(shim, handler, path) {
166
173
  return shim.recordMiddleware(handler, {
167
174
  route: path,
@@ -16,7 +16,8 @@ const SUPPORTABILITY = {
16
16
  DEPENDENCIES: 'Supportability/InstalledDependencies',
17
17
  NODEJS: 'Supportability/Nodejs',
18
18
  REGISTRATION: 'Supportability/Registration',
19
- EVENT_HARVEST: 'Supportability/EventHarvest'
19
+ EVENT_HARVEST: 'Supportability/EventHarvest',
20
+ INFINITE_TRACING: 'Supportability/InfiniteTracing'
20
21
  }
21
22
 
22
23
  const ERRORS = {
@@ -231,6 +232,15 @@ const SPAN_EVENTS = {
231
232
  DROPPED: SUPPORTABILITY.PREFIX + 'SpanEvent/Discarded'
232
233
  }
233
234
 
235
+ const INFINITE_TRACING = {
236
+ SEEN: SUPPORTABILITY.INFINITE_TRACING + '/Span/Seen',
237
+ SENT: SUPPORTABILITY.INFINITE_TRACING + '/Span/Sent',
238
+ SPAN_RESPONSE_ERROR: SUPPORTABILITY.INFINITE_TRACING + '/Span/Response/Error',
239
+ SPAN_RESPONSE_GRPC_UNIMPLEMENTED: SUPPORTABILITY.INFINITE_TRACING + '/Span/gRPC/UNIMPLEMENTED',
240
+ SPAN_RESPONSE_GRPC_STATUS: SUPPORTABILITY.INFINITE_TRACING + '/Span/gRPC/%s',
241
+ DRAIN_DURATION: SUPPORTABILITY.INFINITE_TRACING + '/Drain/Duration'
242
+ }
243
+
234
244
  module.exports = {
235
245
  ACTION_DELIMITER: '/',
236
246
  ALL: ALL,
@@ -253,6 +263,7 @@ module.exports = {
253
263
  GC: GC,
254
264
  HAPI: HAPI,
255
265
  HTTP: 'HttpDispatcher',
266
+ INFINITE_TRACING: INFINITE_TRACING,
256
267
  LOOP: LOOP,
257
268
  MEMCACHE: MEMCACHE,
258
269
  MEMORY: MEMORY,
@@ -0,0 +1,15 @@
1
+ 'use strict'
2
+ class ProxyGrpc {
3
+ constructor(grpcLibrary = '@grpc/grpc-js') {
4
+ this.library = require(grpcLibrary)
5
+
6
+ // add methods or objets from base grpc class that we need as needed
7
+ this.credentials = this.library.credentials
8
+ this.Metadata = this.library.Metadata
9
+ this.loadPackageDefinition = this.library.loadPackageDefinition
10
+ this.status = this.library.status
11
+ this.Server = this.library.Server
12
+ this.ServerCredentials = this.library.ServerCredentials
13
+ }
14
+ }
15
+ module.exports = (new ProxyGrpc)
@@ -174,6 +174,8 @@ class AwsLambda {
174
174
 
175
175
  shim.agent.setLambdaArn(context.invokedFunctionArn)
176
176
 
177
+ shim.agent.setLambdaFunctionVersion(context.functionVersion)
178
+
177
179
  segment.start()
178
180
 
179
181
  let res
@@ -0,0 +1,112 @@
1
+ 'use strict'
2
+
3
+ const psemver = require('../util/process-version')
4
+ const logger = require('../logger')
5
+ const SpanEventAggregator = require('./span-event-aggregator')
6
+ const StreamingSpanEventAggregator = require('./streaming-span-event-aggregator')
7
+
8
+ function createSpanEventAggregator(config, collector, metrics) {
9
+ let shouldCreateStreaming = false
10
+ if (config.infinite_tracing.trace_observer.host) {
11
+ // TODO: ideally this validation and configuration clearing would happen
12
+ // in the config. Since we don't currently have a way to generate
13
+ // support metrics in the config, keeping this related logic together here.
14
+ // If logic happened prior, could merely check for existance of trace_observer.host.
15
+ shouldCreateStreaming = validateInfiniteTracing(config.infinite_tracing.trace_observer)
16
+
17
+ if (!shouldCreateStreaming) {
18
+ // Explicitly disable for any downstream consumers
19
+ config.infinite_tracing.trace_observer.host = ''
20
+ config.infinite_tracing.trace_observer.port = ''
21
+ }
22
+ }
23
+
24
+ if (shouldCreateStreaming) {
25
+ return createStreamingAggregator(config, collector, metrics)
26
+ }
27
+
28
+ return createStandardAggregator(config, collector, metrics)
29
+ }
30
+
31
+ function createStreamingAggregator(config, collector, metrics) {
32
+ logger.trace('Creating streaming span event aggregator for infinite tracing.')
33
+
34
+ // loading the class here to ensure its behind a feature flag
35
+ // and won't trigger a grpc load in node 8
36
+ const GrpcConnection = require('../grpc/connection')
37
+ const connection = new GrpcConnection(config.infinite_tracing.trace_observer, metrics)
38
+ const SpanStreamer = require('./span-streamer')
39
+ const spanStreamer = new SpanStreamer(
40
+ config.license_key,
41
+ connection,
42
+ metrics
43
+ )
44
+
45
+ const opts = {
46
+ periodMs: 1000,
47
+ limit: 50000,
48
+ span_streamer: spanStreamer
49
+ }
50
+
51
+ const aggregator = new StreamingSpanEventAggregator(opts, collector, metrics)
52
+
53
+ return aggregator
54
+ }
55
+
56
+ function createStandardAggregator(config, collector, metrics) {
57
+ logger.trace('Creating standard span event aggregator.')
58
+
59
+ const opts = {
60
+ periodMs: config.event_harvest_config.report_period_ms,
61
+ limit: config.event_harvest_config.harvest_limits.span_event_data
62
+ }
63
+
64
+ const aggregator = new SpanEventAggregator(opts, collector, metrics)
65
+ return aggregator
66
+ }
67
+
68
+ function validateInfiniteTracing(trace_observer) {
69
+ if (!psemver.satisfies('>=10.10.0')) {
70
+ logger.warn(
71
+ 'Infinite tracing disabled: this version of Node is not supported (must be >=10.10.0)'
72
+ )
73
+ return false
74
+ }
75
+
76
+ trace_observer.host = trace_observer.host.trim()
77
+
78
+ if (!validateHostName(trace_observer.host)) {
79
+ logger.warn('Infinite tracing disabled: invalid infinite_tracing.trace_observer.host value')
80
+
81
+ return false
82
+ }
83
+
84
+ if (typeof trace_observer.port !== 'string') {
85
+ trace_observer.port = String(trace_observer.port)
86
+ }
87
+
88
+ trace_observer.port = trace_observer.port.trim()
89
+
90
+ if (!validatePortValue(trace_observer.port)) {
91
+ logger.warn('Infinite tracing disabled: invalid infinite_tracing.trace_observer.port value')
92
+
93
+ return false
94
+ }
95
+
96
+ return true
97
+ }
98
+
99
+ function validateHostName(host) {
100
+ // Regular expression for validating a hostname
101
+ const hostReg = /(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{0,62}[a-zA-Z0-9]\.)+[a-zA-Z]{2,63}$)/
102
+
103
+ return hostReg.test(host)
104
+ }
105
+
106
+ function validatePortValue(port) {
107
+ if (port.length === 0) return false
108
+
109
+ return !isNaN(port)
110
+ }
111
+
112
+ module.exports = createSpanEventAggregator
@@ -0,0 +1,44 @@
1
+ 'use strict'
2
+
3
+ const STRING_TYPE = 'string_value'
4
+ const BOOL_TYPE = 'bool_value'
5
+ const INT_TYPE = 'int_value'
6
+ const DOUBLE_TYPE = 'double_value'
7
+
8
+ function mapToStreamingType(value) {
9
+ if (value === null || value === undefined) {
10
+ return
11
+ }
12
+
13
+ const valueType = typeof value
14
+
15
+ let protoTypeString = null
16
+ switch (valueType) {
17
+ case 'string': {
18
+ protoTypeString = STRING_TYPE
19
+ break
20
+ }
21
+ case 'boolean': {
22
+ protoTypeString = BOOL_TYPE
23
+ break
24
+ }
25
+ case 'number': {
26
+ const isInteger = Number.isInteger(value)
27
+ protoTypeString = isInteger ? INT_TYPE : DOUBLE_TYPE
28
+ break
29
+ }
30
+ default: {
31
+ protoTypeString = null
32
+ }
33
+ }
34
+
35
+ if (protoTypeString) {
36
+ return {
37
+ [protoTypeString]: value
38
+ }
39
+ }
40
+
41
+ return
42
+ }
43
+
44
+ module.exports = mapToStreamingType
@@ -35,6 +35,11 @@ class SpanEventAggregator extends EventAggregator {
35
35
  return [this.runId, metrics, eventData]
36
36
  }
37
37
 
38
+ start() {
39
+ logger.debug('starting SpanEventAggregator')
40
+ return super.start()
41
+ }
42
+
38
43
  send() {
39
44
  if (spanLogger.traceEnabled()) {
40
45
  spanLogger.trace({
@@ -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
  /**
@@ -48,7 +49,8 @@ class SpanIntrinsics {
48
49
  * @class
49
50
  */
50
51
  class SpanEvent {
51
- constructor(attributes) {
52
+ constructor(attributes, customAttributes) {
53
+ this.customAttributes = customAttributes
52
54
  this.attributes = attributes
53
55
  this.intrinsics = new SpanIntrinsics()
54
56
  }
@@ -78,14 +80,15 @@ class SpanEvent {
78
80
  */
79
81
  static fromSegment(segment, parentId = null, isRoot = false) {
80
82
  const attributes = segment.attributes.get(DESTINATIONS.SPAN_EVENT)
83
+ const customAttributes = segment.customAttributes.get(DESTINATIONS.SPAN_EVENT)
81
84
 
82
85
  let span = null
83
86
  if (HttpSpanEvent.testSegment(segment)) {
84
- span = new HttpSpanEvent(attributes)
87
+ span = new HttpSpanEvent(attributes, customAttributes)
85
88
  } else if (DatastoreSpanEvent.testSegment(segment)) {
86
- span = new DatastoreSpanEvent(attributes)
89
+ span = new DatastoreSpanEvent(attributes, customAttributes)
87
90
  } else {
88
- span = new SpanEvent(attributes)
91
+ span = new SpanEvent(attributes, customAttributes)
89
92
  }
90
93
 
91
94
  const tx = segment.transaction
@@ -120,11 +123,21 @@ class SpanEvent {
120
123
  toJSON() {
121
124
  return [
122
125
  _filterNulls(this.intrinsics),
123
- EMPTY_USER_ATTRS,
126
+ this.customAttributes ?
127
+ _filterNulls(this.customAttributes) :
128
+ EMPTY_USER_ATTRS,
124
129
  _filterNulls(this.attributes)
125
130
  ]
126
131
  }
127
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
+
128
141
  addAttribute(key, value, truncateExempt = false) {
129
142
  const {attributeFilter} = Config.getInstance()
130
143
  const dest = attributeFilter.filterSegment(DESTINATIONS.SPAN_EVENT, key)
@@ -141,8 +154,8 @@ class SpanEvent {
141
154
  * @class
142
155
  */
143
156
  class HttpSpanEvent extends SpanEvent {
144
- constructor(attributes) {
145
- super(attributes)
157
+ constructor(attributes, customAttributes) {
158
+ super(attributes, customAttributes)
146
159
 
147
160
  this.intrinsics.category = CATEGORIES.HTTP
148
161
  this.intrinsics.component = attributes.library || HTTP_LIBRARY
@@ -175,8 +188,8 @@ class HttpSpanEvent extends SpanEvent {
175
188
  * @class.
176
189
  */
177
190
  class DatastoreSpanEvent extends SpanEvent {
178
- constructor(attributes) {
179
- super(attributes)
191
+ constructor(attributes, customAttributes) {
192
+ super(attributes, customAttributes)
180
193
 
181
194
  this.intrinsics.category = CATEGORIES.DATASTORE
182
195
  this.intrinsics['span.kind'] = CLIENT_KIND