dd-trace 2.14.0 → 2.16.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 (61) hide show
  1. package/LICENSE-3rdparty.csv +2 -0
  2. package/README.md +4 -0
  3. package/ext/tags.d.ts +2 -1
  4. package/ext/tags.js +2 -1
  5. package/index.d.ts +43 -20
  6. package/package.json +6 -4
  7. package/packages/datadog-instrumentations/src/crypto.js +32 -0
  8. package/packages/datadog-instrumentations/src/grpc/server.js +15 -7
  9. package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
  10. package/packages/datadog-instrumentations/src/http/server.js +1 -1
  11. package/packages/datadog-instrumentations/src/jest.js +136 -14
  12. package/packages/datadog-instrumentations/src/mocha.js +77 -31
  13. package/packages/datadog-instrumentations/src/net.js +13 -0
  14. package/packages/datadog-instrumentations/src/next.js +7 -3
  15. package/packages/datadog-plugin-jest/src/index.js +106 -6
  16. package/packages/datadog-plugin-mocha/src/index.js +15 -7
  17. package/packages/datadog-plugin-mongodb-core/src/index.js +19 -10
  18. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +4 -0
  19. package/packages/dd-trace/src/appsec/iast/analyzers/index.js +20 -0
  20. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +48 -0
  21. package/packages/dd-trace/src/appsec/iast/analyzers/weak-cipher-analyzer.js +27 -0
  22. package/packages/dd-trace/src/appsec/iast/analyzers/weak-hash-analyzer.js +24 -0
  23. package/packages/dd-trace/src/appsec/iast/iast-context.js +50 -0
  24. package/packages/dd-trace/src/appsec/iast/index.js +59 -0
  25. package/packages/dd-trace/src/appsec/iast/overhead-controller.js +94 -0
  26. package/packages/dd-trace/src/appsec/iast/path-line.js +70 -0
  27. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +121 -0
  28. package/packages/dd-trace/src/appsec/recommended.json +1144 -275
  29. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +1 -1
  30. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +3 -3
  31. package/packages/dd-trace/src/config.js +90 -10
  32. package/packages/dd-trace/src/constants.js +9 -1
  33. package/packages/dd-trace/src/encode/0.4.js +7 -1
  34. package/packages/dd-trace/src/encode/0.5.js +7 -1
  35. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +2 -2
  36. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +32 -20
  37. package/packages/dd-trace/src/encode/span-stats.js +155 -0
  38. package/packages/dd-trace/src/exporters/agent/index.js +14 -2
  39. package/packages/dd-trace/src/exporters/agent/writer.js +6 -3
  40. package/packages/dd-trace/src/exporters/common/request.js +9 -5
  41. package/packages/dd-trace/src/exporters/span-stats/index.js +20 -0
  42. package/packages/dd-trace/src/exporters/span-stats/writer.js +54 -0
  43. package/packages/dd-trace/src/format.js +2 -0
  44. package/packages/dd-trace/src/iitm.js +11 -0
  45. package/packages/dd-trace/src/opentracing/propagation/text_map.js +71 -0
  46. package/packages/dd-trace/src/opentracing/tracer.js +1 -1
  47. package/packages/dd-trace/src/plugin_manager.js +12 -2
  48. package/packages/dd-trace/src/plugins/index.js +3 -0
  49. package/packages/dd-trace/src/plugins/log_plugin.js +16 -9
  50. package/packages/dd-trace/src/plugins/util/ip_blocklist.js +51 -0
  51. package/packages/dd-trace/src/plugins/util/web.js +99 -2
  52. package/packages/dd-trace/src/priority_sampler.js +36 -1
  53. package/packages/dd-trace/src/proxy.js +3 -0
  54. package/packages/dd-trace/src/ritm.js +10 -1
  55. package/packages/dd-trace/src/span_processor.js +7 -1
  56. package/packages/dd-trace/src/span_stats.js +210 -0
  57. package/packages/dd-trace/src/telemetry/dependencies.js +83 -0
  58. package/packages/dd-trace/src/{telemetry.js → telemetry/index.js} +10 -65
  59. package/packages/dd-trace/src/telemetry/send-data.js +35 -0
  60. package/packages/dd-trace/src/plugins/util/redis.js +0 -74
  61. package/packages/dd-trace/src/plugins/util/tx.js +0 -75
@@ -0,0 +1,210 @@
1
+ const os = require('os')
2
+ const { version } = require('./pkg')
3
+ const pkg = require('../../../package.json')
4
+
5
+ const { LogCollapsingLowestDenseDDSketch } = require('@datadog/sketches-js')
6
+ const { ORIGIN_KEY, TOP_LEVEL_KEY } = require('./constants')
7
+ const {
8
+ MEASURED,
9
+ HTTP_STATUS_CODE
10
+ } = require('../../../ext/tags')
11
+
12
+ const { SpanStatsExporter } = require('./exporters/span-stats')
13
+
14
+ const {
15
+ DEFAULT_SPAN_NAME,
16
+ DEFAULT_SERVICE_NAME
17
+ } = require('./encode/tags-processors')
18
+
19
+ class SpanAggStats {
20
+ constructor (aggKey) {
21
+ this.aggKey = aggKey
22
+ this.hits = 0
23
+ this.topLevelHits = 0
24
+ this.errors = 0
25
+ this.duration = 0
26
+ this.okDistribution = new LogCollapsingLowestDenseDDSketch(0.00775)
27
+ this.errorDistribution = new LogCollapsingLowestDenseDDSketch(0.00775)
28
+ }
29
+
30
+ record (span) {
31
+ const durationNs = span._duration * 1e6
32
+ this.hits++
33
+ this.duration += durationNs
34
+
35
+ if (span.metrics[TOP_LEVEL_KEY]) {
36
+ this.topLevelHits++
37
+ }
38
+
39
+ if (span.error) {
40
+ this.errors++
41
+ this.errorDistribution.accept(durationNs)
42
+ } else {
43
+ this.okDistribution.accept(durationNs)
44
+ }
45
+ }
46
+
47
+ toJSON () {
48
+ const {
49
+ name,
50
+ service,
51
+ resource,
52
+ type,
53
+ statusCode,
54
+ synthetics
55
+ } = this.aggKey
56
+
57
+ return {
58
+ Name: name,
59
+ Service: service,
60
+ Resource: resource,
61
+ Type: type,
62
+ HTTPStatusCode: statusCode,
63
+ Synthetics: synthetics,
64
+ Hits: this.hits,
65
+ TopLevelHits: this.topLevelHits,
66
+ Errors: this.errors,
67
+ Duration: this.duration,
68
+ OkSummary: this.okDistribution.toProto(),
69
+ ErrorSummary: this.errorDistribution.toProto()
70
+ }
71
+ }
72
+ }
73
+
74
+ class SpanAggKey {
75
+ constructor (span) {
76
+ this.name = span.name || DEFAULT_SPAN_NAME
77
+ this.service = span.service || DEFAULT_SERVICE_NAME
78
+ this.resource = span.resource || ''
79
+ this.type = span.type || ''
80
+ this.statusCode = span.meta[HTTP_STATUS_CODE] || 0
81
+ this.synthetics = span.meta[ORIGIN_KEY] === 'synthetics'
82
+ }
83
+
84
+ toString () {
85
+ return [
86
+ this.name,
87
+ this.service,
88
+ this.resource,
89
+ this.type,
90
+ this.statusCode,
91
+ this.synthetics
92
+ ].join(',')
93
+ }
94
+ }
95
+
96
+ class SpanBuckets extends Map {
97
+ forSpan (span) {
98
+ const aggKey = new SpanAggKey(span)
99
+ const key = aggKey.toString()
100
+
101
+ if (!this.has(key)) {
102
+ this.set(key, new SpanAggStats(aggKey))
103
+ }
104
+
105
+ return this.get(key)
106
+ }
107
+ }
108
+
109
+ class TimeBuckets extends Map {
110
+ forTime (time) {
111
+ if (!this.has(time)) {
112
+ this.set(time, new SpanBuckets())
113
+ }
114
+
115
+ return this.get(time)
116
+ }
117
+ }
118
+
119
+ class SpanStatsProcessor {
120
+ constructor ({
121
+ stats: {
122
+ enabled = false,
123
+ interval = 10
124
+ },
125
+ hostname,
126
+ port,
127
+ url,
128
+ env,
129
+ tags
130
+ } = {}) {
131
+ this.exporter = new SpanStatsExporter({
132
+ hostname,
133
+ port,
134
+ tags,
135
+ url
136
+ })
137
+ this.interval = interval
138
+ this.bucketSizeNs = interval * 1e9
139
+ this.buckets = new TimeBuckets()
140
+ this.hostname = os.hostname()
141
+ this.enabled = enabled
142
+ this.env = env
143
+ this.tags = tags || {}
144
+ this.sequence = 0
145
+
146
+ if (enabled) {
147
+ this.timer = setInterval(this.onInterval.bind(this), interval * 1e3)
148
+ this.timer.unref()
149
+ }
150
+ }
151
+
152
+ onInterval () {
153
+ const serialized = this._serializeBuckets()
154
+ if (!serialized) return
155
+
156
+ this.exporter.export({
157
+ Hostname: this.hostname,
158
+ Env: this.env,
159
+ Version: version,
160
+ Stats: serialized,
161
+ Lang: 'javascript',
162
+ TracerVersion: pkg.version,
163
+ RuntimeID: this.tags['runtime-id'],
164
+ Sequence: ++this.sequence
165
+ })
166
+ }
167
+
168
+ onSpanFinished (span) {
169
+ if (!this.enabled) return
170
+ if (!span.metrics[TOP_LEVEL_KEY] && !span.metrics[MEASURED]) return
171
+
172
+ const spanEndNs = span.startTime + span.duration
173
+ const bucketTime = spanEndNs - (spanEndNs % this.bucketSizeNs)
174
+
175
+ this.buckets.forTime(bucketTime)
176
+ .forSpan(span)
177
+ .record(span)
178
+ }
179
+
180
+ _serializeBuckets () {
181
+ const { bucketSizeNs } = this
182
+ const serializedBuckets = []
183
+
184
+ for (const [ timeNs, bucket ] of this.buckets.entries()) {
185
+ const bucketAggStats = []
186
+
187
+ for (const stats of bucket.values()) {
188
+ bucketAggStats.push(stats.toJSON())
189
+ }
190
+
191
+ serializedBuckets.push({
192
+ Start: timeNs,
193
+ Duration: bucketSizeNs,
194
+ Stats: bucketAggStats
195
+ })
196
+ }
197
+
198
+ this.buckets.clear()
199
+
200
+ return serializedBuckets
201
+ }
202
+ }
203
+
204
+ module.exports = {
205
+ SpanAggStats,
206
+ SpanAggKey,
207
+ SpanBuckets,
208
+ TimeBuckets,
209
+ SpanStatsProcessor
210
+ }
@@ -0,0 +1,83 @@
1
+ 'use strict'
2
+
3
+ const path = require('path')
4
+ const parse = require('module-details-from-path')
5
+ const requirePackageJson = require('../require-package-json')
6
+ const { sendData } = require('./send-data')
7
+ const dc = require('diagnostics_channel')
8
+ const { fileURLToPath } = require('url')
9
+
10
+ const savedDependencies = []
11
+ const detectedDependencyNames = new Set()
12
+ const FILE_URI_START = `file://`
13
+ const moduleLoadStartChannel = dc.channel('dd-trace:moduleLoadStart')
14
+
15
+ let immediate, config, application, host
16
+
17
+ function waitAndSend (config, application, host) {
18
+ if (!immediate) {
19
+ immediate = setImmediate(() => {
20
+ immediate = null
21
+ if (savedDependencies.length > 0) {
22
+ const dependencies = savedDependencies.splice(0, 1000)
23
+ sendData(config, application, host, 'app-dependencies-loaded', { dependencies })
24
+ if (savedDependencies.length > 0) {
25
+ waitAndSend(config, application, host)
26
+ }
27
+ }
28
+ })
29
+ immediate.unref()
30
+ }
31
+ }
32
+
33
+ function onModuleLoad (data) {
34
+ if (data) {
35
+ let filename = data.filename
36
+ if (filename && filename.startsWith(FILE_URI_START)) {
37
+ try {
38
+ filename = fileURLToPath(filename)
39
+ } catch (e) {
40
+ // cannot transform url to path
41
+ }
42
+ }
43
+ const parseResult = filename && parse(filename)
44
+ const request = data.request || (parseResult && parseResult.name)
45
+ if (filename && request && isDependency(filename, request) && !detectedDependencyNames.has(request)) {
46
+ detectedDependencyNames.add(request)
47
+ if (parseResult) {
48
+ const { name, basedir } = parseResult
49
+ if (basedir) {
50
+ try {
51
+ const { version } = requirePackageJson(basedir, module)
52
+ savedDependencies.push({ name, version })
53
+ waitAndSend(config, application, host)
54
+ } catch (e) {
55
+ // can not read the package.json, do nothing
56
+ }
57
+ }
58
+ }
59
+ }
60
+ }
61
+ }
62
+ function start (_config, _application, _host) {
63
+ config = _config
64
+ application = _application
65
+ host = _host
66
+ moduleLoadStartChannel.subscribe(onModuleLoad)
67
+ }
68
+
69
+ function isDependency (filename, request) {
70
+ return request.indexOf(`.${path.sep}`) !== 0 && request.indexOf(path.sep) !== 0
71
+ }
72
+
73
+ function stop () {
74
+ config = null
75
+ application = null
76
+ host = null
77
+ detectedDependencyNames.clear()
78
+ savedDependencies.splice(0, savedDependencies.length)
79
+ if (moduleLoadStartChannel.hasSubscribers) {
80
+ moduleLoadStartChannel.unsubscribe(onModuleLoad)
81
+ }
82
+ }
83
+ module.exports = { start, stop }
@@ -1,17 +1,14 @@
1
1
  'use strict'
2
2
 
3
- const tracerVersion = require('../../../package.json').version
4
- const pkg = require('./pkg')
5
- const containerId = require('./exporters/common/docker').id()
6
- const requirePackageJson = require('./require-package-json')
7
- const path = require('path')
3
+ const tracerVersion = require('../../../../package.json').version
4
+ const containerId = require('../exporters/common/docker').id()
8
5
  const os = require('os')
9
- const request = require('./exporters/common/request')
6
+ const dependencies = require('./dependencies')
7
+ const { sendData } = require('./send-data')
10
8
 
11
9
  let config
12
10
  let pluginManager
13
11
 
14
- let seqId = 0
15
12
  let application
16
13
  let host
17
14
  let interval
@@ -33,27 +30,6 @@ function getIntegrations () {
33
30
  return newIntegrations
34
31
  }
35
32
 
36
- function getDependencies () {
37
- const deps = []
38
- const { dependencies } = pkg
39
- if (!dependencies) {
40
- return deps
41
- }
42
- const rootDir = pkg.findRoot()
43
- for (const [name, version] of Object.entries(dependencies)) {
44
- const dep = { name }
45
- try {
46
- dep.version = requirePackageJson(
47
- path.join(rootDir, 'node_modules', name.replace('/', path.sep))
48
- ).version
49
- } catch (e) {
50
- dep.version = version
51
- }
52
- deps.push(dep)
53
- }
54
- return deps
55
- }
56
-
57
33
  function flatten (input, result = [], prefix = [], traversedObjects = null) {
58
34
  traversedObjects = traversedObjects || new WeakSet()
59
35
  if (traversedObjects.has(input)) {
@@ -73,7 +49,7 @@ function flatten (input, result = [], prefix = [], traversedObjects = null) {
73
49
  function appStarted () {
74
50
  return {
75
51
  integrations: getIntegrations(),
76
- dependencies: getDependencies(),
52
+ dependencies: [],
77
53
  configuration: flatten(config),
78
54
  additional_payload: []
79
55
  }
@@ -81,7 +57,7 @@ function appStarted () {
81
57
 
82
58
  function onBeforeExit () {
83
59
  process.removeListener('beforeExit', onBeforeExit)
84
- sendData('app-closing')
60
+ sendData(config, application, host, 'app-closing')
85
61
  }
86
62
 
87
63
  function createAppObject () {
@@ -102,38 +78,6 @@ function createHostObject () {
102
78
  }
103
79
  }
104
80
 
105
- function sendData (reqType, payload = {}) {
106
- const {
107
- hostname,
108
- port
109
- } = config
110
- const options = {
111
- hostname,
112
- port,
113
- method: 'POST',
114
- path: '/telemetry/proxy/api/v2/apmtelemetry',
115
- headers: {
116
- 'content-type': 'application/json',
117
- 'dd-telemetry-api-version': 'v1',
118
- 'dd-telemetry-request-type': reqType
119
- }
120
- }
121
- const data = JSON.stringify({
122
- api_version: 'v1',
123
- request_type: reqType,
124
- tracer_time: Math.floor(Date.now() / 1000),
125
- runtime_id: config.tags['runtime-id'],
126
- seq_id: ++seqId,
127
- payload,
128
- application,
129
- host
130
- })
131
-
132
- request(data, options, () => {
133
- // ignore errors
134
- })
135
- }
136
-
137
81
  function start (aConfig, thePluginManager) {
138
82
  if (!aConfig.telemetryEnabled) {
139
83
  return
@@ -142,8 +86,9 @@ function start (aConfig, thePluginManager) {
142
86
  pluginManager = thePluginManager
143
87
  application = createAppObject()
144
88
  host = createHostObject()
145
- sendData('app-started', appStarted())
146
- interval = setInterval(() => sendData('app-heartbeat'), 60000)
89
+ dependencies.start(config, application, host)
90
+ sendData(config, application, host, 'app-started', appStarted())
91
+ interval = setInterval(() => sendData(config, application, host, 'app-heartbeat'), 60000)
147
92
  interval.unref()
148
93
  process.on('beforeExit', onBeforeExit)
149
94
  }
@@ -164,7 +109,7 @@ function updateIntegrations () {
164
109
  if (integrations.length === 0) {
165
110
  return
166
111
  }
167
- sendData('app-integrations-change', { integrations })
112
+ sendData(config, application, host, 'app-integrations-change', { integrations })
168
113
  }
169
114
 
170
115
  module.exports = {
@@ -0,0 +1,35 @@
1
+ const request = require('../exporters/common/request')
2
+ let seqId = 0
3
+ function sendData (config, application, host, reqType, payload = {}) {
4
+ const {
5
+ hostname,
6
+ port
7
+ } = config
8
+ const options = {
9
+ hostname,
10
+ port,
11
+ method: 'POST',
12
+ path: '/telemetry/proxy/api/v2/apmtelemetry',
13
+ headers: {
14
+ 'content-type': 'application/json',
15
+ 'dd-telemetry-api-version': 'v1',
16
+ 'dd-telemetry-request-type': reqType
17
+ }
18
+ }
19
+ const data = JSON.stringify({
20
+ api_version: 'v1',
21
+ request_type: reqType,
22
+ tracer_time: Math.floor(Date.now() / 1000),
23
+ runtime_id: config.tags['runtime-id'],
24
+ seq_id: ++seqId,
25
+ payload,
26
+ application,
27
+ host
28
+ })
29
+
30
+ request(data, options, () => {
31
+ // ignore errors
32
+ })
33
+ }
34
+
35
+ module.exports = { sendData }
@@ -1,74 +0,0 @@
1
- 'use strict'
2
-
3
- const analyticsSampler = require('../../analytics_sampler')
4
- const urlFilter = require('../util/urlfilter')
5
- const tx = require('./tx')
6
-
7
- const redis = {
8
- // Ensure the configuration has the correct structure and defaults.
9
- normalizeConfig (config) {
10
- const filter = urlFilter.getFilter(config)
11
-
12
- return Object.assign({}, config, {
13
- filter
14
- })
15
- },
16
-
17
- // Start a span for a Redis command.
18
- instrument (tracer, config, db, command, args) {
19
- const childOf = tracer.scope().active()
20
- const span = tracer.startSpan('redis.command', {
21
- childOf,
22
- tags: {
23
- 'span.kind': 'client',
24
- 'resource.name': command,
25
- 'span.type': 'redis',
26
- 'db.type': 'redis',
27
- 'db.name': db || '0',
28
- 'redis.raw_command': formatCommand(command, args)
29
- }
30
- })
31
-
32
- span.setTag('service.name', config.service || `${span.context()._tags['service.name']}-redis`)
33
-
34
- analyticsSampler.sample(span, config.measured)
35
-
36
- return span
37
- }
38
- }
39
-
40
- function formatCommand (command, args) {
41
- command = command.toUpperCase()
42
-
43
- if (!args || command === 'AUTH') return command
44
-
45
- for (let i = 0, l = args.length; i < l; i++) {
46
- if (typeof args[i] === 'function') continue
47
-
48
- command = `${command} ${formatArg(args[i])}`
49
-
50
- if (command.length > 1000) return trim(command, 1000)
51
- }
52
-
53
- return command
54
- }
55
-
56
- function formatArg (arg) {
57
- switch (typeof arg) {
58
- case 'string':
59
- case 'number':
60
- return trim(String(arg), 100)
61
- default:
62
- return '?'
63
- }
64
- }
65
-
66
- function trim (str, maxlen) {
67
- if (str.length > maxlen) {
68
- str = str.substr(0, maxlen - 3) + '...'
69
- }
70
-
71
- return str
72
- }
73
-
74
- module.exports = Object.assign({}, tx, redis)
@@ -1,75 +0,0 @@
1
- 'use strict'
2
-
3
- const tx = {
4
- // Set the outgoing host.
5
- setHost (span, hostname, port) {
6
- hostname && span.setTag('out.host', hostname)
7
- port && span.setTag('out.port', port)
8
- },
9
-
10
- // Wrap a promise or a callback to also finish the span.
11
- wrap (span, done) {
12
- if (typeof done === 'function' || !done) {
13
- return wrapCallback(span, done)
14
- } else if (isPromise(done)) {
15
- return wrapPromise(span, done)
16
- } else if (done && done.length) {
17
- return wrapArguments(span, done)
18
- }
19
- }
20
- }
21
-
22
- function wrapCallback (span, callback) {
23
- const scope = span.tracer().scope()
24
- const previous = scope.active()
25
-
26
- return function (err) {
27
- finish(span, err)
28
-
29
- if (callback) {
30
- return scope.activate(previous, () => callback.apply(this, arguments))
31
- }
32
- }
33
- }
34
-
35
- function wrapPromise (span, promise) {
36
- promise.then(
37
- () => finish(span),
38
- err => finish(span, err)
39
- )
40
-
41
- return promise
42
- }
43
-
44
- function wrapArguments (span, args) {
45
- const lastIndex = args.length - 1
46
- const callback = args[lastIndex]
47
-
48
- if (typeof callback === 'function') {
49
- args[lastIndex] = wrapCallback(span, args[lastIndex])
50
- }
51
-
52
- return args
53
- }
54
-
55
- function finish (span, error) {
56
- if (error) {
57
- span.addTags({
58
- 'error.type': error.name,
59
- 'error.msg': error.message,
60
- 'error.stack': error.stack
61
- })
62
- }
63
-
64
- span.finish()
65
- }
66
-
67
- function isPromise (obj) {
68
- return isObject(obj) && typeof obj.then === 'function'
69
- }
70
-
71
- function isObject (obj) {
72
- return typeof obj === 'object' && obj !== null
73
- }
74
-
75
- module.exports = tx