newrelic 4.4.0 → 4.7.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.
@@ -6,11 +6,19 @@ var urltils = require('../../util/urltils')
6
6
  var hashes = require('../../util/hashes')
7
7
  var logger = require('../../logger').child({component: 'outbound'})
8
8
  var shimmer = require('../../shimmer')
9
+ var util = require('util')
10
+ var url = require('url')
11
+ var copy = require('../../util/copy')
9
12
 
10
13
  const DEFAULT_HOST = 'localhost'
11
14
  const DEFAULT_PORT = 80
12
15
  const DEFAULT_SSL_PORT = 443
13
16
 
17
+ const NEWRELIC_ID_HEADER = 'x-newrelic-id'
18
+ const NEWRELIC_TRACE_HEADER = 'newrelic'
19
+ const NEWRELIC_TRANSACTION_HEADER = 'x-newrelic-transaction'
20
+ const NEWRELIC_SYNTHETICS_HEADER = 'x-newrelic-synthetics'
21
+
14
22
  /**
15
23
  * Instruments an outbound HTTP request.
16
24
  *
@@ -21,6 +29,12 @@ const DEFAULT_SSL_PORT = 443
21
29
  * @return {http.ClientRequest} The instrumented outbound request.
22
30
  */
23
31
  module.exports = function instrumentOutbound(agent, opts, makeRequest) {
32
+ if (typeof opts === 'string') {
33
+ opts = url.parse(opts)
34
+ } else {
35
+ opts = copy.shallow(opts)
36
+ }
37
+
24
38
  let hostname = opts.hostname || opts.host || DEFAULT_HOST
25
39
  let port = opts.port || opts.defaultPort
26
40
  if (!port) {
@@ -32,7 +46,7 @@ module.exports = function instrumentOutbound(agent, opts, makeRequest) {
32
46
  'Invalid host name (%s) or port (%s) for outbound request.',
33
47
  hostname, port
34
48
  )
35
- return makeRequest()
49
+ return makeRequest(opts)
36
50
  }
37
51
 
38
52
  // Technically we shouldn't append the port if this is an https request on 443
@@ -54,8 +68,48 @@ module.exports = function instrumentOutbound(agent, opts, makeRequest) {
54
68
  )
55
69
 
56
70
  function instrumentRequest(segment) {
71
+ const transaction = segment.transaction
72
+ const outboundHeaders = Object.create(null)
73
+
74
+ // FLAG: synthetics
75
+ if (
76
+ agent.config.feature_flag.synthetics &&
77
+ agent.config.encoding_key &&
78
+ transaction.syntheticsHeader
79
+ ) {
80
+ outboundHeaders[NEWRELIC_SYNTHETICS_HEADER] = transaction.syntheticsHeader
81
+ }
82
+
83
+ // If CAT is enabled, inject the transaction header.
84
+ // TODO: abstract header logic shared with TransactionShim#insertCATRequestHeaders
85
+ if (agent.config.cross_application_tracer.enabled) {
86
+ if (agent.config.distributed_tracing.enabled) {
87
+ _addDistributedHeaders(transaction, outboundHeaders)
88
+ } else if (agent.config.encoding_key) {
89
+ _addCATHeaders(agent, transaction, outboundHeaders)
90
+ } else {
91
+ logger.trace('No encoding key found, not adding CAT headers')
92
+ }
93
+ }
94
+
95
+ if (util.isArray(opts.headers)) {
96
+ opts.headers = opts.headers.slice()
97
+ Array.prototype.push.apply(
98
+ opts.headers,
99
+ Object.keys(outboundHeaders).map(function getHeaderTuples(key) {
100
+ return [key, outboundHeaders[key]]
101
+ })
102
+ )
103
+ } else {
104
+ opts.headers = Object.assign(
105
+ Object.create(null),
106
+ opts.headers,
107
+ outboundHeaders
108
+ )
109
+ }
110
+
57
111
  segment.start()
58
- const request = makeRequest()
112
+ const request = makeRequest(opts)
59
113
  const parsed = urltils.scrubAndParseParameters(request.path)
60
114
  const proto = parsed.protocol || opts.protocol || 'http:'
61
115
  segment.name += parsed.path
@@ -129,7 +183,7 @@ function handleResponse(segment, hostname, req, res) {
129
183
  const agent = segment.transaction.agent
130
184
  if (
131
185
  agent.config.cross_application_tracer.enabled &&
132
- !agent.config.feature_flag.distributed_tracing
186
+ !agent.config.distributed_tracing.enabled
133
187
  ) {
134
188
  pullCatHeaders(agent.config, segment, hostname, res.headers['x-newrelic-app-data'])
135
189
  }
@@ -202,3 +256,48 @@ function _makeNonEnumerable(obj, prop) {
202
256
  logger.debug(e, 'Failed to make %s non enumerable.', prop)
203
257
  }
204
258
  }
259
+
260
+ function _addDistributedHeaders(tx, outboundHeaders) {
261
+ try {
262
+ const txData = tx.createDistributedTracePayload().httpSafe()
263
+ outboundHeaders[NEWRELIC_TRACE_HEADER] = txData
264
+
265
+ logger.trace(
266
+ 'Added outbound request distributed tracing headers in transaction %s',
267
+ tx.id
268
+ )
269
+ } catch (err) {
270
+ logger.trace(err, 'Failed to create distributed trace payload')
271
+ }
272
+ }
273
+
274
+ function _addCATHeaders(agent, tx, outboundHeaders) {
275
+ if (agent.config.obfuscatedId) {
276
+ outboundHeaders[NEWRELIC_ID_HEADER] = agent.config.obfuscatedId
277
+ }
278
+
279
+ var pathHash = hashes.calculatePathHash(
280
+ agent.config.applications()[0],
281
+ tx.getFullName() || '',
282
+ tx.referringPathHash
283
+ )
284
+ tx.pushPathHash(pathHash)
285
+
286
+ try {
287
+ let txData = JSON.stringify([
288
+ tx.id,
289
+ false,
290
+ tx.tripId || tx.id,
291
+ pathHash
292
+ ])
293
+ txData = hashes.obfuscateNameUsingKey(txData, agent.config.encoding_key)
294
+ outboundHeaders[NEWRELIC_TRANSACTION_HEADER] = txData
295
+
296
+ logger.trace(
297
+ 'Added outbound request CAT headers in transaction %s',
298
+ tx.id
299
+ )
300
+ } catch (err) {
301
+ logger.trace(err, 'Failed to create CAT payload')
302
+ }
303
+ }
@@ -11,7 +11,6 @@ var url = require('url')
11
11
  var urltils = require('../../util/urltils')
12
12
  var properties = require('../../util/properties')
13
13
  var psemver = require('../../util/process-version')
14
- var copy = require('../../util/copy')
15
14
 
16
15
  const NAMES = require('../../metrics/names')
17
16
  const DESTS = require('../../config/attribute-filter').DESTINATIONS
@@ -206,7 +205,7 @@ function wrapEmitWithTransaction(agent, emit, isHTTPS) {
206
205
  }
207
206
  }
208
207
  if (agent.config.cross_application_tracer.enabled) {
209
- if (agent.config.feature_flag.distributed_tracing) {
208
+ if (agent.config.distributed_tracing.enabled) {
210
209
  const payload = request.headers[NEWRELIC_TRACE_HEADER]
211
210
  if (payload) {
212
211
  logger.trace(
@@ -499,54 +498,10 @@ function wrapRequest(agent, request) {
499
498
 
500
499
  const args = agent.tracer.slice(arguments)
501
500
  const context = this
502
- const outboundHeaders = Object.create(null)
503
-
504
- // FLAG: synthetics
505
- if (
506
- agent.config.feature_flag.synthetics &&
507
- agent.config.encoding_key &&
508
- transaction.syntheticsHeader
509
- ) {
510
- outboundHeaders[NEWRELIC_SYNTHETICS_HEADER] = transaction.syntheticsHeader
511
- }
512
-
513
- // If CAT is enabled, inject the transaction header.
514
- // TODO: abstract header logic shared with TransactionShim#insertCATRequestHeaders
515
- if (agent.config.cross_application_tracer.enabled) {
516
- if (agent.config.feature_flag.distributed_tracing) {
517
- _addDistributedHeaders(transaction, outboundHeaders)
518
- } else if (agent.config.encoding_key) {
519
- _addCATHeaders(agent, transaction, outboundHeaders)
520
- } else {
521
- logger.trace('No encoding key found, not adding CAT headers')
522
- }
523
- }
524
-
525
- if (typeof options === 'string') {
526
- options = url.parse(options)
527
- } else {
528
- options = copy.shallow(options)
529
- }
530
-
531
- if (util.isArray(options.headers)) {
532
- options.headers = options.headers.slice()
533
- Array.prototype.push.apply(
534
- options.headers,
535
- Object.keys(outboundHeaders).map(function getHeaderTuples(key) {
536
- return [key, outboundHeaders[key]]
537
- })
538
- )
539
- } else {
540
- options.headers = Object.assign(
541
- Object.create(null),
542
- options.headers,
543
- outboundHeaders
544
- )
545
- }
546
- args[0] = options
547
501
 
548
502
  // hostname & port logic pulled directly from node's 0.10 lib/http.js
549
- return instrumentOutbound(agent, options, function makeRequest() {
503
+ return instrumentOutbound(agent, options, function makeRequest(opts) {
504
+ args[0] = opts
550
505
  return request.apply(context, args)
551
506
  })
552
507
  }
@@ -738,7 +693,7 @@ module.exports = function initialize(agent, http, moduleName) {
738
693
  *
739
694
  * @param {string} header - The raw X-NewRelic-Synthetics header
740
695
  * @param {string} encKey - Encoding key handed down from the server
741
- * @param {̄Array} trustedIds - Array of accounts to trust the header from.
696
+ * @param {Array.<number>} trustedIds - Array of accounts to trust the header from.
742
697
  * @param {Transaction} transaction - Where the synthetics data is attached to.
743
698
  */
744
699
  function handleSyntheticsHeader(header, encKey, trustedIds, transaction) {
@@ -756,8 +711,8 @@ function handleSyntheticsHeader(header, encKey, trustedIds, transaction) {
756
711
  *
757
712
  * @param {string} header - The raw X-NewRelic-Synthetics header
758
713
  * @param {string} encKey - Encoding key handed down from the server
759
- * @param {̄Array} trustedIds - Array of accounts to trust the header from.
760
- * @return {Object or null} - On successful parse and verification an object of
714
+ * @param {Array.<number>} trustedIds - Array of accounts to trust the header from.
715
+ * @return {Object|null} - On successful parse and verification an object of
761
716
  * synthetics data is returned, otherwise null is
762
717
  * returned.
763
718
  */
@@ -877,48 +832,3 @@ function _collectHeaders(headers, nameMap, prefix, tx) {
877
832
  }
878
833
  }
879
834
  }
880
-
881
- function _addDistributedHeaders(tx, outboundHeaders) {
882
- try {
883
- const txData = tx.createDistributedTracePayload().httpSafe()
884
- outboundHeaders[NEWRELIC_TRACE_HEADER] = txData
885
-
886
- logger.trace(
887
- 'Added outbound request distributed tracing headers in transaction %s',
888
- tx.id
889
- )
890
- } catch (err) {
891
- logger.trace(err, 'Failed to create distributed trace payload')
892
- }
893
- }
894
-
895
- function _addCATHeaders(agent, tx, outboundHeaders) {
896
- if (agent.config.obfuscatedId) {
897
- outboundHeaders[NEWRELIC_ID_HEADER] = agent.config.obfuscatedId
898
- }
899
-
900
- var pathHash = hashes.calculatePathHash(
901
- agent.config.applications()[0],
902
- tx.getFullName() || '',
903
- tx.referringPathHash
904
- )
905
- tx.pushPathHash(pathHash)
906
-
907
- try {
908
- let txData = JSON.stringify([
909
- tx.id,
910
- false,
911
- tx.tripId || tx.id,
912
- pathHash
913
- ])
914
- txData = hashes.obfuscateNameUsingKey(txData, agent.config.encoding_key)
915
- outboundHeaders[NEWRELIC_TRANSACTION_HEADER] = txData
916
-
917
- logger.trace(
918
- 'Added outbound request CAT headers in transaction %s',
919
- tx.id
920
- )
921
- } catch (err) {
922
- logger.trace(err, 'Failed to create CAT payload')
923
- }
924
- }
@@ -10,6 +10,10 @@ module.exports = function initialize(agent, hapi, moduleName, shim) {
10
10
  return !(result instanceof Error) && (result !== args[1].continue)
11
11
  })
12
12
 
13
+ // 'Server' and 'server' both point to the same export,
14
+ // but we can't make any assumption about which will be used.
15
+ // Since we wrap the prototype, the second wrap should exit early.
16
+ shim.wrapReturn(hapi, 'server', serverFactoryWrapper)
13
17
  shim.wrapReturn(hapi, 'Server', serverFactoryWrapper)
14
18
  }
15
19
 
@@ -3,7 +3,7 @@
3
3
  const NAMES = require('../names')
4
4
 
5
5
  function recordDistributedTrace(tx, suffix, duration, exclusive) {
6
- const distTraceReceived = !!tx.parentId
6
+ const distTraceReceived = !!tx.acceptedDistributedTrace
7
7
  const tag = [
8
8
  tx.parentType || 'Unknown',
9
9
  tx.parentAcct || 'Unknown',
@@ -33,7 +33,7 @@ function recordWeb(segment, scope) {
33
33
  tx.measure(NAMES.QUEUETIME, null, tx.queueTime)
34
34
  }
35
35
 
36
- if (config.feature_flag.distributed_tracing) {
36
+ if (config.distributed_tracing.enabled) {
37
37
  recordDistributedTrace(tx, 'Web', duration, exclusive)
38
38
  } else if (tx.incomingCatId) {
39
39
  tx.measure(
@@ -34,7 +34,7 @@ function recordBackground(segment, scope) {
34
34
  )
35
35
  tx.measure(NAMES.OTHER_TRANSACTION.TOTAL_TIME, null, totalTime, exclusive)
36
36
 
37
- if (tx.agent.config.feature_flag.distributed_tracing) {
37
+ if (tx.agent.config.distributed_tracing.enabled) {
38
38
  recordDistributedTrace(tx, 'Other', duration, exclusive)
39
39
  }
40
40
  }
@@ -3,7 +3,7 @@
3
3
  var Heap = require('@tyriar/fibonacci-heap').FibonacciHeap
4
4
 
5
5
  function PriorityQueue(limit) {
6
- this.limit = limit || 10
6
+ this.limit = limit == null ? 10 : limit
7
7
  this.seen = 0
8
8
  this._data = new Heap()
9
9
 
@@ -24,8 +24,11 @@ PriorityQueue.prototype.getMinimumPriority = function getMinimumPriority() {
24
24
  }
25
25
 
26
26
  PriorityQueue.prototype.add = function add(value, priority) {
27
- priority = priority || Math.random()
28
27
  this.seen++
28
+ if (this.limit <= 0) {
29
+ return false
30
+ }
31
+ priority = priority || Math.random()
29
32
  if (this.length === this.limit) {
30
33
  return this._replace(value, priority)
31
34
  }
@@ -333,10 +333,6 @@ function recordProduce(nodule, properties, recordNamer) {
333
333
  }
334
334
 
335
335
  var name = _nameMessageSegment(shim, msgDesc, shim._metrics.PRODUCE)
336
- if (msgDesc.headers) {
337
- shim.insertCATRequestHeaders(msgDesc.headers, true)
338
- }
339
-
340
336
  if (!shim.agent.config.message_tracer.segment_parameters.enabled) {
341
337
  delete msgDesc.parameters
342
338
  } else if (msgDesc.routingKey) {
@@ -350,6 +346,11 @@ function recordProduce(nodule, properties, recordNamer) {
350
346
  promise: msgDesc.promise || false,
351
347
  callback: msgDesc.callback || null,
352
348
  recorder: genericRecorder,
349
+ inContext: function generateCATHeaders() {
350
+ if (msgDesc.headers) {
351
+ shim.insertCATRequestHeaders(msgDesc.headers, true)
352
+ }
353
+ },
353
354
  parameters: msgDesc.parameters || null
354
355
  }
355
356
  })
package/lib/shim/shim.js CHANGED
@@ -946,7 +946,7 @@ function record(nodule, properties, recordNamer) {
946
946
  var promised = false
947
947
  var ret
948
948
  try {
949
- ret = shim.applySegment(fn, segment, true, ctx, args)
949
+ ret = shim.applySegment(fn, segment, true, ctx, args, segDesc.inContext)
950
950
  if (segDesc.after && segDesc.promise && shim.isPromise(ret)) {
951
951
  promised = true
952
952
  return ret.then(function onThen(val) {
@@ -1286,7 +1286,7 @@ function storeSegment(obj, segment) {
1286
1286
  * Sets the given segment as the active one for the duration of the function's
1287
1287
  * execution.
1288
1288
  *
1289
- * - `applySegment(func, segment, full, context, args)`
1289
+ * - `applySegment(func, segment, full, context, args[, inContextCB])`
1290
1290
  *
1291
1291
  * @memberof Shim.prototype
1292
1292
  *
@@ -1305,9 +1305,15 @@ function storeSegment(obj, segment) {
1305
1305
  * @param {Array.<*>} args
1306
1306
  * The arguments to be passed into the function.
1307
1307
  *
1308
+ * @param {Function} [inContextCB]
1309
+ * The function used to do more instrumentation work. This function is
1310
+ * guaranteed to be executed with the segment associated with.
1311
+ *
1312
+ *
1308
1313
  * @return {*} Whatever value `func` returned.
1309
1314
  */
1310
- function applySegment(func, segment, full, context, args) {
1315
+ /* eslint-disable max-params */
1316
+ function applySegment(func, segment, full, context, args, inContextCB) {
1311
1317
  // Exist fast for bad arguments.
1312
1318
  if (!this.isFunction(func)) {
1313
1319
  return
@@ -1327,6 +1333,10 @@ function applySegment(func, segment, full, context, args) {
1327
1333
  segment.start()
1328
1334
  }
1329
1335
 
1336
+ if (typeof inContextCB === 'function') {
1337
+ inContextCB()
1338
+ }
1339
+
1330
1340
  // Execute the function and then return the tracer segment to the old one.
1331
1341
  try {
1332
1342
  return func.apply(context, args)
@@ -1337,6 +1347,7 @@ function applySegment(func, segment, full, context, args) {
1337
1347
  tracer.segment = prevSegment
1338
1348
  }
1339
1349
  }
1350
+ /* eslint-enable max-params */
1340
1351
 
1341
1352
  /**
1342
1353
  * Creates a new segment.
@@ -45,6 +45,7 @@ function WrapSpec(spec) {
45
45
  function SegmentSpec(spec) {
46
46
  this.name = hasOwnProperty(spec, 'name') ? spec.name : null
47
47
  this.recorder = hasOwnProperty(spec, 'recorder') ? spec.recorder : null
48
+ this.inContext = hasOwnProperty(spec, 'inContext') ? spec.inContext : null
48
49
  this.parent = hasOwnProperty(spec, 'parent') ? spec.parent : null
49
50
  this.parameters = hasOwnProperty(spec, 'parameters') ? spec.parameters : null
50
51
  this.internal = hasOwnProperty(spec, 'internal') ? spec.internal : false
@@ -287,7 +287,7 @@ function handleCATHeaders(headers, segment, transportType) {
287
287
  transportType = Transaction.TRANSPORT_TYPES.UNKNOWN
288
288
  }
289
289
 
290
- if (this.agent.config.feature_flag.distributed_tracing) {
290
+ if (this.agent.config.distributed_tracing.enabled) {
291
291
  const payload = headers[DISTRIBUTED_TRACE_HEADER]
292
292
  if (payload) {
293
293
  tx.acceptDistributedTracePayload(payload, transportType)
@@ -351,7 +351,7 @@ function insertCATRequestHeaders(headers, useAlternateHeaderNames) {
351
351
  this.logger.trace('CAT disabled, not adding headers.')
352
352
  return
353
353
  }
354
- var usingDistributedTracing = this.agent.config.feature_flag.distributed_tracing
354
+ var usingDistributedTracing = this.agent.config.distributed_tracing.enabled
355
355
  if (!this.agent.config.encoding_key && !usingDistributedTracing) {
356
356
  this.logger.warn('Missing encoding key, not adding CAT headers!')
357
357
  return
@@ -433,7 +433,7 @@ function insertCATReplyHeader(headers, useAlternateHeaderNames) {
433
433
  if (!config.cross_application_tracer.enabled) {
434
434
  this.logger.trace('CAT disabled, not adding reply header.')
435
435
  return
436
- } else if (config.feature_flag.distributed_tracing) {
436
+ } else if (config.distributed_tracing.enabled) {
437
437
  this.logger.warn('CAT disabled, distributed tracing is enabled')
438
438
  return
439
439
  } else if (!config.encoding_key) {
package/lib/shimmer.js CHANGED
@@ -205,6 +205,9 @@ var shimmer = module.exports = {
205
205
  if (original.__NR_unwrap) return logger.debug("%s already wrapped by agent.", fqmn)
206
206
 
207
207
  var wrapped = wrapper(original, method)
208
+ Object.keys(original).forEach((key) => {
209
+ wrapped[key] = original[key]
210
+ })
208
211
  wrapped.__NR_original = original
209
212
  wrapped.__NR_unwrap = function __NR_unwrap() {
210
213
  nodule[method] = original
@@ -20,19 +20,18 @@ class SpanAggregator extends EventAggregator {
20
20
  * Attempts to add the given segment to the collection.
21
21
  *
22
22
  * @param {TraceSegment} segment - The segment to add.
23
- * @param {string} [parentId] - The GUID of the parent span.
24
- * @param {string} [grandparentId] - The GUID of the grandparent span.
23
+ * @param {string} [parentId=null] - The GUID of the parent span.
25
24
  *
26
25
  * @return {bool} True if the segment was added, or false if it was discarded.
27
26
  */
28
- addSegment(segment, parentId, grandparentId) {
27
+ addSegment(segment, parentId) {
29
28
  // Check if the priority would be accepted before creating the event object.
30
29
  const tx = segment.transaction
31
30
  if (tx.priority < this._events.getMinimumPriority()) {
32
31
  ++this._events.seen
33
32
  return false
34
33
  }
35
- const span = SpanEvent.fromSegment(segment, parentId || null, grandparentId || null)
34
+ const span = SpanEvent.fromSegment(segment, parentId || null)
36
35
  return this.addEvent(span, tx.priority)
37
36
  }
38
37
  }
@@ -1,9 +1,12 @@
1
1
  'use strict'
2
2
 
3
+ const truncate = require('../util/byte-limit').truncate
4
+
3
5
  const NAMES = require('../metrics/names')
4
- const EXTERNAL_LIBRARY = 'http'
6
+ const HTTP_LIBRARY = 'http'
7
+ const SPAN_KIND_ATTRIBUTE = 'client'
5
8
  const CATEGORIES = {
6
- EXTERNAL: 'external',
9
+ HTTP: 'http',
7
10
  DATASTORE: 'datastore',
8
11
  GENERIC: 'generic'
9
12
  }
@@ -25,8 +28,7 @@ class SpanEvent {
25
28
  this.traceId = null
26
29
  this.guid = null
27
30
  this.parentId = null
28
- this.grandparentId = null
29
- this.appLocalRootId = null
31
+ this.transactionId = null
30
32
  this.sampled = null
31
33
  this.priority = null
32
34
  this.name = null
@@ -45,8 +47,8 @@ class SpanEvent {
45
47
  return DatastoreSpanEvent
46
48
  }
47
49
 
48
- static get ExternalSpanEvent() {
49
- return ExternalSpanEvent
50
+ static get HttpSpanEvent() {
51
+ return HttpSpanEvent
50
52
  }
51
53
 
52
54
  /**
@@ -55,14 +57,15 @@ class SpanEvent {
55
57
  * The constructed span event will contain extra data depending on the
56
58
  * category of the segment.
57
59
  *
58
- * @param {TraceSegment} segment - The segment to turn into a span event.
60
+ * @param {TraceSegment} segment - The segment to turn into a span event.
61
+ * @param {?string} [parentId=null] - The ID of the segment's parent.
59
62
  *
60
63
  * @return {SpanEvent} The constructed event.
61
64
  */
62
- static fromSegment(segment, parentId, grandparentId) {
65
+ static fromSegment(segment, parentId) {
63
66
  let span = null
64
- if (ExternalSpanEvent.testSegment(segment)) {
65
- span = new ExternalSpanEvent(segment.parameters)
67
+ if (HttpSpanEvent.testSegment(segment)) {
68
+ span = new HttpSpanEvent(segment.parameters)
66
69
  } else if (DatastoreSpanEvent.testSegment(segment)) {
67
70
  span = new DatastoreSpanEvent(segment.parameters)
68
71
  } else {
@@ -74,12 +77,16 @@ class SpanEvent {
74
77
  span.traceId = tx.traceId || tx.id
75
78
  span.guid = segment.id
76
79
  span.parentId = parentId || null
77
- span.grandparentId = grandparentId || null
78
- span.appLocalRootId = tx.id
80
+
81
+ span.transactionId = tx.id
79
82
  span.sampled = tx.sampled
80
83
  span.priority = tx.priority
81
84
  span.name = segment.name
82
85
 
86
+ if (tx.baseSegment === segment) {
87
+ span['nr.entryPoint'] = true
88
+ }
89
+
83
90
  // Timestamp in milliseconds, duration in seconds. Yay consistency!
84
91
  span.timestamp = segment.timer.start
85
92
  span.duration = segment.timer.getDurationInMillis() / 1000
@@ -88,8 +95,15 @@ class SpanEvent {
88
95
  }
89
96
 
90
97
  toJSON() {
98
+ const attrs = Object.create(null)
99
+ for (let key in this) {
100
+ if (this[key] != null) {
101
+ attrs[key] = this[key]
102
+ }
103
+ }
104
+
91
105
  return [
92
- Object.assign(Object.create(null), this),
106
+ attrs,
93
107
  EMPTY_USER_ATTRS,
94
108
  EMPTY_AGENT_ATTRS
95
109
  ]
@@ -102,17 +116,18 @@ class SpanEvent {
102
116
  * @private
103
117
  * @class
104
118
  */
105
- class ExternalSpanEvent extends SpanEvent {
119
+ class HttpSpanEvent extends SpanEvent {
106
120
  constructor(parameters) {
107
121
  super()
108
122
 
109
- this.category = CATEGORIES.EXTERNAL
110
- this.externalLibrary = parameters.library || EXTERNAL_LIBRARY
123
+ this.category = CATEGORIES.HTTP
124
+ this.component = parameters.library || HTTP_LIBRARY
125
+ this['span.kind'] = SPAN_KIND_ATTRIBUTE
111
126
  if (parameters.url) {
112
- this.externalUri = parameters.url // Some day URI and URL will be consistent...
127
+ this['http.url'] = parameters.url
113
128
  }
114
129
  if (parameters.procedure) {
115
- this.externalProcedure = parameters.procedure
130
+ this['http.method'] = parameters.procedure
116
131
  }
117
132
  }
118
133
 
@@ -132,23 +147,24 @@ class DatastoreSpanEvent extends SpanEvent {
132
147
  super()
133
148
 
134
149
  this.category = CATEGORIES.DATASTORE
150
+ this['span.kind'] = SPAN_KIND_ATTRIBUTE
135
151
  if (parameters.product) {
136
- this.datastoreProduct = parameters.product
152
+ this.component = parameters.product
137
153
  }
138
- if (parameters.collection) {
139
- this.datastoreCollection = parameters.collection
154
+ if (parameters.sql_obfuscated) {
155
+ this['db.statement'] = _truncate(parameters.sql_obfuscated)
156
+ } else if (parameters.sql) {
157
+ this['db.statement'] = _truncate(parameters.sql)
140
158
  }
141
- if (parameters.operation) {
142
- this.datastoreOperation = parameters.operation
159
+ if (parameters.database_name) {
160
+ this['db.instance'] = parameters.database_name
143
161
  }
144
162
  if (parameters.host) {
145
- this.datastoreHost = parameters.host
146
- }
147
- if (parameters.port_path_or_id) {
148
- this.datastorePortPathOrId = parameters.port_path_or_id
149
- }
150
- if (parameters.database_name) {
151
- this.datastoreName = parameters.database_name
163
+ this['peer.hostname'] = parameters.host
164
+
165
+ if (parameters.port_path_or_id) {
166
+ this['peer.address'] = `${parameters.host}:${parameters.port_path_or_id}`
167
+ }
152
168
  }
153
169
  }
154
170
 
@@ -157,4 +173,12 @@ class DatastoreSpanEvent extends SpanEvent {
157
173
  }
158
174
  }
159
175
 
176
+ function _truncate(val) {
177
+ let truncated = truncate(val, 1997)
178
+ if (truncated !== val) {
179
+ truncated += '...'
180
+ }
181
+ return truncated
182
+ }
183
+
160
184
  module.exports = SpanEvent