dd-trace 2.18.0 → 2.20.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 (96) hide show
  1. package/LICENSE-3rdparty.csv +2 -0
  2. package/index.d.ts +42 -4
  3. package/package.json +5 -3
  4. package/packages/datadog-instrumentations/src/cucumber.js +0 -2
  5. package/packages/datadog-instrumentations/src/elasticsearch.js +51 -47
  6. package/packages/datadog-instrumentations/src/google-cloud-pubsub.js +1 -1
  7. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  8. package/packages/datadog-instrumentations/src/mariadb.js +43 -69
  9. package/packages/datadog-instrumentations/src/mocha.js +14 -18
  10. package/packages/datadog-instrumentations/src/opensearch.js +10 -0
  11. package/packages/datadog-instrumentations/src/pg.js +2 -1
  12. package/packages/datadog-instrumentations/src/rhea.js +20 -17
  13. package/packages/datadog-plugin-amqp10/src/consumer.js +32 -0
  14. package/packages/datadog-plugin-amqp10/src/index.js +11 -101
  15. package/packages/datadog-plugin-amqp10/src/producer.js +34 -0
  16. package/packages/datadog-plugin-amqp10/src/util.js +15 -0
  17. package/packages/datadog-plugin-amqplib/src/client.js +38 -0
  18. package/packages/datadog-plugin-amqplib/src/consumer.js +40 -0
  19. package/packages/datadog-plugin-amqplib/src/index.js +14 -102
  20. package/packages/datadog-plugin-amqplib/src/producer.js +37 -0
  21. package/packages/datadog-plugin-amqplib/src/util.js +14 -0
  22. package/packages/datadog-plugin-dns/src/index.js +16 -91
  23. package/packages/datadog-plugin-dns/src/lookup.js +40 -0
  24. package/packages/datadog-plugin-dns/src/lookup_service.js +24 -0
  25. package/packages/datadog-plugin-dns/src/resolve.js +24 -0
  26. package/packages/datadog-plugin-dns/src/reverse.js +21 -0
  27. package/packages/datadog-plugin-elasticsearch/src/index.js +7 -7
  28. package/packages/datadog-plugin-google-cloud-pubsub/src/client.js +25 -0
  29. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +42 -0
  30. package/packages/datadog-plugin-google-cloud-pubsub/src/index.js +14 -99
  31. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +34 -0
  32. package/packages/datadog-plugin-graphql/src/execute.js +73 -0
  33. package/packages/datadog-plugin-graphql/src/index.js +14 -176
  34. package/packages/datadog-plugin-graphql/src/parse.js +32 -0
  35. package/packages/datadog-plugin-graphql/src/resolve.js +70 -76
  36. package/packages/datadog-plugin-graphql/src/validate.js +28 -0
  37. package/packages/datadog-plugin-grpc/src/client.js +46 -55
  38. package/packages/datadog-plugin-grpc/src/index.js +7 -24
  39. package/packages/datadog-plugin-grpc/src/server.js +50 -52
  40. package/packages/datadog-plugin-grpc/src/util.js +15 -14
  41. package/packages/datadog-plugin-http/src/index.js +7 -22
  42. package/packages/datadog-plugin-http2/src/index.js +8 -26
  43. package/packages/datadog-plugin-jest/src/index.js +3 -0
  44. package/packages/datadog-plugin-kafkajs/src/consumer.js +42 -0
  45. package/packages/datadog-plugin-kafkajs/src/index.js +11 -87
  46. package/packages/datadog-plugin-kafkajs/src/producer.js +31 -0
  47. package/packages/datadog-plugin-mocha/src/index.js +2 -2
  48. package/packages/datadog-plugin-moleculer/src/client.js +22 -36
  49. package/packages/datadog-plugin-moleculer/src/index.js +8 -26
  50. package/packages/datadog-plugin-moleculer/src/server.js +18 -30
  51. package/packages/datadog-plugin-mongodb-core/src/index.js +1 -1
  52. package/packages/datadog-plugin-net/src/ipc.js +21 -0
  53. package/packages/datadog-plugin-net/src/tcp.js +46 -0
  54. package/packages/datadog-plugin-opensearch/src/index.js +11 -0
  55. package/packages/datadog-plugin-pg/src/index.js +2 -1
  56. package/packages/datadog-plugin-rhea/src/consumer.js +55 -0
  57. package/packages/datadog-plugin-rhea/src/index.js +11 -99
  58. package/packages/datadog-plugin-rhea/src/producer.js +45 -0
  59. package/packages/datadog-plugin-sharedb/src/index.js +22 -39
  60. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +1 -1
  61. package/packages/dd-trace/src/appsec/iast/index.js +4 -6
  62. package/packages/dd-trace/src/appsec/iast/path-line.js +3 -0
  63. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +2 -5
  64. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +2 -5
  65. package/packages/dd-trace/src/config.js +31 -6
  66. package/packages/dd-trace/src/constants.js +3 -0
  67. package/packages/dd-trace/src/dogstatsd.js +42 -10
  68. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +10 -2
  69. package/packages/dd-trace/src/exporters/agent/index.js +6 -2
  70. package/packages/dd-trace/src/exporters/agent/writer.js +2 -9
  71. package/packages/dd-trace/src/exporters/common/request.js +12 -0
  72. package/packages/dd-trace/src/exporters/span-stats/index.js +6 -2
  73. package/packages/dd-trace/src/format.js +13 -0
  74. package/packages/dd-trace/src/metrics.js +15 -2
  75. package/packages/dd-trace/src/opentracing/span.js +2 -1
  76. package/packages/dd-trace/src/plugin_manager.js +6 -1
  77. package/packages/dd-trace/src/plugins/client.js +3 -1
  78. package/packages/dd-trace/src/plugins/composite.js +26 -0
  79. package/packages/dd-trace/src/plugins/consumer.js +9 -0
  80. package/packages/dd-trace/src/plugins/incoming.js +7 -0
  81. package/packages/dd-trace/src/plugins/index.js +1 -0
  82. package/packages/dd-trace/src/plugins/outgoing.js +1 -1
  83. package/packages/dd-trace/src/plugins/producer.js +9 -0
  84. package/packages/dd-trace/src/plugins/server.js +9 -0
  85. package/packages/dd-trace/src/plugins/storage.js +0 -4
  86. package/packages/dd-trace/src/plugins/tracing.js +9 -9
  87. package/packages/dd-trace/src/plugins/util/web.js +49 -3
  88. package/packages/dd-trace/src/profiling/config.js +8 -5
  89. package/packages/dd-trace/src/profiling/profiler.js +8 -1
  90. package/packages/dd-trace/src/span_processor.js +3 -0
  91. package/packages/dd-trace/src/span_sampler.js +80 -0
  92. package/packages/dd-trace/src/span_stats.js +2 -2
  93. package/packages/dd-trace/src/telemetry/dependencies.js +10 -6
  94. package/packages/dd-trace/src/telemetry/send-data.js +3 -1
  95. package/packages/dd-trace/src/tracer.js +6 -2
  96. package/packages/dd-trace/src/util.js +43 -1
@@ -24,6 +24,12 @@ class TracingPlugin extends Plugin {
24
24
  })
25
25
  }
26
26
 
27
+ get activeSpan () {
28
+ const store = storage.getStore()
29
+
30
+ return store && store.span
31
+ }
32
+
27
33
  configure (config) {
28
34
  return super.configure({
29
35
  ...config,
@@ -37,7 +43,7 @@ class TracingPlugin extends Plugin {
37
43
  start () {} // implemented by individual plugins
38
44
 
39
45
  finish () {
40
- this.activeSpan().finish()
46
+ this.activeSpan.finish()
41
47
  }
42
48
 
43
49
  error (error) {
@@ -49,7 +55,7 @@ class TracingPlugin extends Plugin {
49
55
  }
50
56
 
51
57
  addError (error) {
52
- const span = this.activeSpan()
58
+ const span = this.activeSpan
53
59
 
54
60
  if (!span._spanContext._tags['error']) {
55
61
  span.setTag('error', error || 1)
@@ -66,7 +72,7 @@ class TracingPlugin extends Plugin {
66
72
  const span = this.tracer.startSpan(name, {
67
73
  childOf,
68
74
  tags: {
69
- 'service.name': service,
75
+ 'service.name': service || this.tracer._service,
70
76
  'resource.name': resource,
71
77
  'span.kind': kind,
72
78
  'span.type': type,
@@ -81,12 +87,6 @@ class TracingPlugin extends Plugin {
81
87
 
82
88
  return span
83
89
  }
84
-
85
- activeSpan () {
86
- const store = storage.getStore()
87
-
88
- return store && store.span
89
- }
90
90
  }
91
91
 
92
92
  module.exports = TracingPlugin
@@ -58,13 +58,15 @@ const web = {
58
58
  const hooks = getHooks(config)
59
59
  const filter = urlFilter.getFilter(config)
60
60
  const middleware = getMiddlewareSetting(config)
61
+ const queryStringObfuscation = getQsObfuscator(config)
61
62
 
62
63
  return Object.assign({}, config, {
63
64
  headers,
64
65
  validateStatus,
65
66
  hooks,
66
67
  filter,
67
- middleware
68
+ middleware,
69
+ queryStringObfuscation
68
70
  })
69
71
  },
70
72
 
@@ -368,6 +370,24 @@ const web = {
368
370
  return req.socket && req.socket.remoteAddress
369
371
  },
370
372
 
373
+ obfuscateQs (config, url) {
374
+ const { queryStringObfuscation } = config
375
+
376
+ if (queryStringObfuscation === false) return url
377
+
378
+ const i = url.indexOf('?')
379
+ if (i === -1) return url
380
+
381
+ const path = url.slice(0, i)
382
+ if (queryStringObfuscation === true) return path
383
+
384
+ let qs = url.slice(i + 1)
385
+
386
+ qs = qs.replace(queryStringObfuscation, '<redacted>')
387
+
388
+ return `${path}?${qs}`
389
+ },
390
+
371
391
  wrapWriteHead (context) {
372
392
  const { req, res } = context
373
393
  const writeHead = res.writeHead
@@ -458,11 +478,11 @@ function reactivate (req, fn) {
458
478
  }
459
479
 
460
480
  function addRequestTags (context) {
461
- const { req, span } = context
481
+ const { req, span, config } = context
462
482
  const url = extractURL(req)
463
483
 
464
484
  span.addTags({
465
- [HTTP_URL]: url.split('?')[0],
485
+ [HTTP_URL]: web.obfuscateQs(config, url),
466
486
  [HTTP_METHOD]: req.method,
467
487
  [SPAN_KIND]: SERVER,
468
488
  [SPAN_TYPE]: WEB,
@@ -619,4 +639,30 @@ function getMiddlewareSetting (config) {
619
639
  return true
620
640
  }
621
641
 
642
+ function getQsObfuscator (config) {
643
+ const obfuscator = config.queryStringObfuscation
644
+
645
+ if (typeof obfuscator === 'boolean') {
646
+ return obfuscator
647
+ }
648
+
649
+ if (typeof obfuscator === 'string') {
650
+ if (obfuscator === '') return false // disable obfuscator
651
+
652
+ if (obfuscator === '.*') return true // optimize full redact
653
+
654
+ try {
655
+ return new RegExp(obfuscator, 'gi')
656
+ } catch (err) {
657
+ log.error(err)
658
+ }
659
+ }
660
+
661
+ if (config.hasOwnProperty('queryStringObfuscation')) {
662
+ log.error('Expected `queryStringObfuscation` to be a regex string or boolean.')
663
+ }
664
+
665
+ return true
666
+ }
667
+
622
668
  module.exports = web
@@ -2,7 +2,7 @@
2
2
 
3
3
  const coalesce = require('koalas')
4
4
  const os = require('os')
5
- const { URL } = require('url')
5
+ const { URL, format } = require('url')
6
6
  const { AgentExporter } = require('./exporters/agent')
7
7
  const { FileExporter } = require('./exporters/file')
8
8
  const { ConsoleLogger } = require('./loggers/console')
@@ -59,10 +59,13 @@ class Config {
59
59
  this.sourceMap = sourceMap
60
60
  this.endpointCollection = endpointCollection
61
61
 
62
- const hostname = coalesce(options.hostname, DD_AGENT_HOST, 'localhost')
63
- const port = coalesce(options.port, DD_TRACE_AGENT_PORT, 8126)
64
- this.url = new URL(coalesce(options.url, DD_TRACE_AGENT_URL,
65
- `http://${hostname || 'localhost'}:${port || 8126}`))
62
+ const hostname = coalesce(options.hostname, DD_AGENT_HOST) || 'localhost'
63
+ const port = coalesce(options.port, DD_TRACE_AGENT_PORT) || 8126
64
+ this.url = new URL(coalesce(options.url, DD_TRACE_AGENT_URL, format({
65
+ protocol: 'http:',
66
+ hostname,
67
+ port
68
+ })))
66
69
 
67
70
  this.exporters = ensureExporters(options.exporters || [
68
71
  new AgentExporter(this)
@@ -36,9 +36,16 @@ class Profiler extends EventEmitter {
36
36
  this._logger = config.logger
37
37
  this._enabled = true
38
38
 
39
+ // Log errors if the source map finder fails, but don't prevent the rest
40
+ // of the profiler from running without source maps.
41
+ let mapper
39
42
  try {
40
- const mapper = await maybeSourceMap(config.sourceMap)
43
+ mapper = await maybeSourceMap(config.sourceMap)
44
+ } catch (err) {
45
+ this._logger.error(err)
46
+ }
41
47
 
48
+ try {
42
49
  for (const profiler of config.profilers) {
43
50
  // TODO: move this out of Profiler when restoring sourcemap support
44
51
  profiler.start({ mapper })
@@ -2,6 +2,7 @@
2
2
 
3
3
  const log = require('./log')
4
4
  const format = require('./format')
5
+ const SpanSampler = require('./span_sampler')
5
6
 
6
7
  const { SpanStatsProcessor } = require('./span_stats')
7
8
 
@@ -15,6 +16,7 @@ class SpanProcessor {
15
16
  this._config = config
16
17
 
17
18
  this._stats = new SpanStatsProcessor(config)
19
+ this._spanSampler = new SpanSampler(config)
18
20
  }
19
21
 
20
22
  process (span) {
@@ -27,6 +29,7 @@ class SpanProcessor {
27
29
 
28
30
  if (started.length === finished.length || finished.length >= flushMinSpans) {
29
31
  this._prioritySampler.sample(spanContext)
32
+ this._spanSampler.sample(spanContext)
30
33
 
31
34
  for (const span of started) {
32
35
  if (span._duration !== undefined) {
@@ -0,0 +1,80 @@
1
+ 'use strict'
2
+ const { globMatch } = require('../src/util')
3
+ const {
4
+ USER_KEEP,
5
+ AUTO_KEEP
6
+ } = require('../../../ext').priority
7
+ const RateLimiter = require('./rate_limiter')
8
+
9
+ class SpanSampler {
10
+ constructor ({ spanSamplingRules = [] }) {
11
+ this._rules = spanSamplingRules
12
+ this._limiters = {}
13
+ }
14
+
15
+ sample (spanContext) {
16
+ const decision = spanContext._sampling.priority
17
+ if (decision === USER_KEEP || decision === AUTO_KEEP) return
18
+
19
+ const { started } = spanContext._trace
20
+ for (const span of started) {
21
+ const service = span.tracer()._service
22
+ const name = span._name
23
+ const rule = findRule(this._rules, service, name)
24
+ if (!rule) continue
25
+
26
+ const sampleRate = getSampleRate(rule.sampleRate)
27
+ const maxPerSecond = getMaxPerSecond(rule.maxPerSecond)
28
+ const sampled = sample(sampleRate)
29
+ if (!sampled) continue
30
+
31
+ const key = `${service}:${name}`
32
+ const limiter = getLimiter(this._limiters, key, maxPerSecond)
33
+ if (limiter.isAllowed()) {
34
+ span.context()._sampling.spanSampling = {
35
+ sampleRate,
36
+ maxPerSecond
37
+ }
38
+ }
39
+ }
40
+ }
41
+ }
42
+
43
+ function findRule (rules, service, name) {
44
+ for (const rule of rules) {
45
+ const servicePattern = getService(rule.service)
46
+ const namePattern = getName(rule.name)
47
+ if (globMatch(servicePattern, service) && globMatch(namePattern, name)) {
48
+ return rule
49
+ }
50
+ }
51
+ }
52
+
53
+ function getLimiter (list, key, maxPerSecond) {
54
+ if (typeof list[key] === 'undefined') {
55
+ list[key] = new RateLimiter(maxPerSecond)
56
+ }
57
+ return list[key]
58
+ }
59
+
60
+ function sample (sampleRate) {
61
+ return Math.random() < sampleRate
62
+ }
63
+
64
+ function getService (service) {
65
+ return service || '*'
66
+ }
67
+
68
+ function getName (name) {
69
+ return name || '*'
70
+ }
71
+
72
+ function getSampleRate (sampleRate) {
73
+ return sampleRate || 1.0
74
+ }
75
+
76
+ function getMaxPerSecond (maxPerSecond) {
77
+ return maxPerSecond || Infinity
78
+ }
79
+
80
+ module.exports = SpanSampler
@@ -65,8 +65,8 @@ class SpanAggStats {
65
65
  TopLevelHits: this.topLevelHits,
66
66
  Errors: this.errors,
67
67
  Duration: this.duration,
68
- OkSummary: this.okDistribution.toProto(),
69
- ErrorSummary: this.errorDistribution.toProto()
68
+ OkSummary: this.okDistribution.toProto(), // TODO: custom proto encoding
69
+ ErrorSummary: this.errorDistribution.toProto() // TODO: custom proto encoding
70
70
  }
71
71
  }
72
72
  }
@@ -7,7 +7,7 @@ const { sendData } = require('./send-data')
7
7
  const dc = require('diagnostics_channel')
8
8
  const { fileURLToPath } = require('url')
9
9
 
10
- const savedDependencies = []
10
+ const savedDependencies = new Set()
11
11
  const detectedDependencyNames = new Set()
12
12
  const FILE_URI_START = `file://`
13
13
  const moduleLoadStartChannel = dc.channel('dd-trace:moduleLoadStart')
@@ -18,10 +18,14 @@ function waitAndSend (config, application, host) {
18
18
  if (!immediate) {
19
19
  immediate = setImmediate(() => {
20
20
  immediate = null
21
- if (savedDependencies.length > 0) {
22
- const dependencies = savedDependencies.splice(0, 1000)
21
+ if (savedDependencies.size > 0) {
22
+ const dependencies = Array.from(savedDependencies.values()).splice(0, 1000).map(pair => {
23
+ savedDependencies.delete(pair)
24
+ const [name, version] = pair.split(' ')
25
+ return { name, version }
26
+ })
23
27
  sendData(config, application, host, 'app-dependencies-loaded', { dependencies })
24
- if (savedDependencies.length > 0) {
28
+ if (savedDependencies.size > 0) {
25
29
  waitAndSend(config, application, host)
26
30
  }
27
31
  }
@@ -49,7 +53,7 @@ function onModuleLoad (data) {
49
53
  if (basedir) {
50
54
  try {
51
55
  const { version } = requirePackageJson(basedir, module)
52
- savedDependencies.push({ name, version })
56
+ savedDependencies.add(`${name} ${version}`)
53
57
  waitAndSend(config, application, host)
54
58
  } catch (e) {
55
59
  // can not read the package.json, do nothing
@@ -75,7 +79,7 @@ function stop () {
75
79
  application = null
76
80
  host = null
77
81
  detectedDependencyNames.clear()
78
- savedDependencies.splice(0, savedDependencies.length)
82
+ savedDependencies.clear()
79
83
  if (moduleLoadStartChannel.hasSubscribers) {
80
84
  moduleLoadStartChannel.unsubscribe(onModuleLoad)
81
85
  }
@@ -3,9 +3,11 @@ let seqId = 0
3
3
  function sendData (config, application, host, reqType, payload = {}) {
4
4
  const {
5
5
  hostname,
6
- port
6
+ port,
7
+ url
7
8
  } = config
8
9
  const options = {
10
+ url,
9
11
  hostname,
10
12
  port,
11
13
  method: 'POST',
@@ -44,11 +44,15 @@ class DatadogTracer extends Tracer {
44
44
  const result = this.scope().activate(span, () => fn(span))
45
45
 
46
46
  if (result && typeof result.then === 'function') {
47
- result.then(
48
- () => span.finish(),
47
+ return result.then(
48
+ value => {
49
+ span.finish()
50
+ return value
51
+ },
49
52
  err => {
50
53
  addError(span, err)
51
54
  span.finish()
55
+ throw err
52
56
  }
53
57
  )
54
58
  } else {
@@ -20,8 +20,50 @@ function isError (value) {
20
20
  return false
21
21
  }
22
22
 
23
+ // Matches a glob pattern to a given subject string
24
+ function globMatch (pattern, subject) {
25
+ let px = 0 // [p]attern inde[x]
26
+ let sx = 0 // [s]ubject inde[x]
27
+ let nextPx = 0
28
+ let nextSx = 0
29
+ while (px < pattern.length || sx < subject.length) {
30
+ if (px < pattern.length) {
31
+ const c = pattern[px]
32
+ switch (c) {
33
+ default: // ordinary character
34
+ if (sx < subject.length && subject[sx] === c) {
35
+ px++
36
+ sx++
37
+ continue
38
+ }
39
+ break
40
+ case '?':
41
+ if (sx < subject.length) {
42
+ px++
43
+ sx++
44
+ continue
45
+ }
46
+ break
47
+ case '*':
48
+ nextPx = px
49
+ nextSx = sx + 1
50
+ px++
51
+ continue
52
+ }
53
+ }
54
+ if (nextSx > 0 && nextSx <= subject.length) {
55
+ px = nextPx
56
+ sx = nextSx
57
+ continue
58
+ }
59
+ return false
60
+ }
61
+ return true
62
+ }
63
+
23
64
  module.exports = {
24
65
  isTrue,
25
66
  isFalse,
26
- isError
67
+ isError,
68
+ globMatch
27
69
  }