dd-trace 2.27.0 → 2.28.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 (49) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/README.md +5 -5
  3. package/index.d.ts +30 -1
  4. package/package.json +5 -4
  5. package/packages/datadog-instrumentations/src/ldapjs.js +12 -2
  6. package/packages/datadog-instrumentations/src/mongoose.js +1 -1
  7. package/packages/datadog-instrumentations/src/next.js +2 -1
  8. package/packages/datadog-instrumentations/src/playwright.js +40 -11
  9. package/packages/datadog-plugin-hapi/src/index.js +5 -1
  10. package/packages/datadog-plugin-http/src/server.js +1 -1
  11. package/packages/datadog-plugin-http2/src/server.js +1 -1
  12. package/packages/dd-trace/src/appsec/addresses.js +3 -1
  13. package/packages/dd-trace/src/appsec/blocking.js +35 -9
  14. package/packages/dd-trace/src/appsec/iast/iast-context.js +6 -2
  15. package/packages/dd-trace/src/appsec/iast/index.js +3 -2
  16. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +5 -2
  17. package/packages/dd-trace/src/appsec/index.js +4 -4
  18. package/packages/dd-trace/src/appsec/recommended.json +76 -75
  19. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
  20. package/packages/dd-trace/src/appsec/remote_config/index.js +3 -0
  21. package/packages/dd-trace/src/appsec/sdk/index.js +19 -1
  22. package/packages/dd-trace/src/appsec/sdk/noop.js +6 -0
  23. package/packages/dd-trace/src/appsec/sdk/set_user.js +30 -0
  24. package/packages/dd-trace/src/appsec/sdk/track_event.js +2 -2
  25. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +73 -0
  26. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +15 -0
  27. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +7 -1
  28. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +4 -2
  29. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +4 -2
  30. package/packages/dd-trace/src/config.js +1 -1
  31. package/packages/dd-trace/src/lambda/handler.js +5 -6
  32. package/packages/dd-trace/src/log/writer.js +32 -24
  33. package/packages/dd-trace/src/metrics.js +18 -0
  34. package/packages/dd-trace/src/noop/proxy.js +2 -2
  35. package/packages/dd-trace/src/opentracing/propagation/text_map.js +2 -0
  36. package/packages/dd-trace/src/opentracing/span_context.js +5 -3
  37. package/packages/dd-trace/src/plugins/ci_plugin.js +4 -1
  38. package/packages/dd-trace/src/plugins/util/exec.js +2 -2
  39. package/packages/dd-trace/src/plugins/util/git.js +16 -1
  40. package/packages/dd-trace/src/profiler.js +3 -0
  41. package/packages/dd-trace/src/profiling/config.js +8 -3
  42. package/packages/dd-trace/src/profiling/exporters/file.js +13 -2
  43. package/packages/dd-trace/src/profiling/profiler.js +23 -6
  44. package/packages/dd-trace/src/profiling/profilers/wall.js +1 -0
  45. package/packages/dd-trace/src/proxy.js +1 -1
  46. package/packages/dd-trace/src/span_processor.js +1 -1
  47. package/packages/dd-trace/src/span_sampler.js +68 -52
  48. package/packages/dd-trace/src/startup-log.js +3 -6
  49. package/packages/dd-trace/src/tracer.js +0 -16
@@ -8,6 +8,7 @@ const os = require('os')
8
8
  const Client = require('./dogstatsd')
9
9
  const log = require('./log')
10
10
  const Histogram = require('./histogram')
11
+ const { performance } = require('perf_hooks')
11
12
 
12
13
  const INTERVAL = 10 * 1000
13
14
 
@@ -20,6 +21,7 @@ let cpuUsage
20
21
  let gauges
21
22
  let counters
22
23
  let histograms
24
+ let elu
23
25
 
24
26
  reset()
25
27
 
@@ -259,6 +261,21 @@ function captureHistograms () {
259
261
  })
260
262
  }
261
263
 
264
+ /**
265
+ * Gathers and reports Event Loop Utilization (ELU) since last run
266
+ *
267
+ * ELU is a measure of how busy the event loop is, like running JavaScript or
268
+ * waiting on *Sync functions. The value is between 0 (idle) and 1 (exhausted).
269
+ *
270
+ * performance.eventLoopUtilization available in Node.js >= v14.10, >= v12.19, >= v16
271
+ */
272
+ const captureELU = ('eventLoopUtilization' in performance) ? () => {
273
+ // if elu is undefined (first run) the measurement is from start of process
274
+ elu = performance.eventLoopUtilization(elu)
275
+
276
+ client.gauge('runtime.node.event_loop.utilization', elu.utilization)
277
+ } : () => {}
278
+
262
279
  function captureCommonMetrics () {
263
280
  captureMemoryUsage()
264
281
  captureProcess()
@@ -266,6 +283,7 @@ function captureCommonMetrics () {
266
283
  captureGauges()
267
284
  captureCounters()
268
285
  captureHistograms()
286
+ captureELU()
269
287
  }
270
288
 
271
289
  function captureNativeMetrics () {
@@ -81,8 +81,8 @@ class Tracer {
81
81
  return this._tracer.getRumData.apply(this._tracer, arguments)
82
82
  }
83
83
 
84
- setUser () {
85
- this._tracer.setUser.apply(this._tracer, arguments)
84
+ setUser (user) {
85
+ this.appsec.setUser(user)
86
86
  return this
87
87
  }
88
88
  }
@@ -302,6 +302,7 @@ class TextMapPropagator {
302
302
  const matches = headerValue.trim().match(traceparentExpr)
303
303
  if (matches.length) {
304
304
  const [ version, traceId, spanId, flags, tail ] = matches.slice(1)
305
+ const traceparent = { version }
305
306
  const tracestate = TraceState.fromString(carrier.tracestate)
306
307
  if (invalidSegment.test(traceId)) return null
307
308
  if (invalidSegment.test(spanId)) return null
@@ -316,6 +317,7 @@ class TextMapPropagator {
316
317
  traceId: id(traceId, 16),
317
318
  spanId: id(spanId, 16),
318
319
  sampling: { priority: parseInt(flags, 10) & 1 ? 1 : 0 },
320
+ traceparent,
319
321
  tracestate
320
322
  })
321
323
 
@@ -12,8 +12,9 @@ class DatadogSpanContext {
12
12
  this._name = props.name
13
13
  this._isFinished = props.isFinished || false
14
14
  this._tags = props.tags || {}
15
- this._sampling = props.sampling || {}
15
+ this._sampling = Object.assign({}, props.sampling)
16
16
  this._baggageItems = props.baggageItems || {}
17
+ this._traceparent = props.traceparent
17
18
  this._tracestate = props.tracestate
18
19
  this._noop = props.noop || null
19
20
  this._trace = props.trace || {
@@ -32,10 +33,11 @@ class DatadogSpanContext {
32
33
  }
33
34
 
34
35
  toTraceparent () {
35
- const sampling = this._sampling.priority >= AUTO_KEEP ? '01' : '00'
36
+ const flags = this._sampling.priority >= AUTO_KEEP ? '01' : '00'
36
37
  const traceId = this._traceId.toString(16).padStart(32, '0')
37
38
  const spanId = this._spanId.toString(16).padStart(16, '0')
38
- return `01-${traceId}-${spanId}-${sampling}`
39
+ const version = (this._traceparent && this._traceparent.version) || '00'
40
+ return `${version}-${traceId}-${spanId}-${flags}`
39
41
  }
40
42
  }
41
43
 
@@ -128,10 +128,13 @@ module.exports = class CiPlugin extends Plugin {
128
128
  const suiteTags = {
129
129
  [TEST_SUITE_ID]: testSuiteSpan.context().toSpanId(),
130
130
  [TEST_SESSION_ID]: testSuiteSpan.context().toTraceId(),
131
- [TEST_MODULE_ID]: testSuiteSpan.context()._parentId.toString(10),
132
131
  [TEST_COMMAND]: testSuiteSpan.context()._tags[TEST_COMMAND],
133
132
  [TEST_BUNDLE]: testSuiteSpan.context()._tags[TEST_COMMAND]
134
133
  }
134
+ if (testSuiteSpan.context()._parentId) {
135
+ suiteTags[TEST_MODULE_ID] = testSuiteSpan.context()._parentId.toString(10)
136
+ }
137
+
135
138
  testTags = {
136
139
  ...testTags,
137
140
  ...suiteTags
@@ -1,8 +1,8 @@
1
- const { execSync } = require('child_process')
1
+ const cp = require('child_process')
2
2
 
3
3
  const sanitizedExec = (cmd, options = {}) => {
4
4
  try {
5
- return execSync(cmd, options).toString().replace(/(\r\n|\n|\r)/gm, '')
5
+ return cp.execSync(cmd, options).toString().replace(/(\r\n|\n|\r)/gm, '')
6
6
  } catch (e) {
7
7
  return ''
8
8
  }
@@ -21,6 +21,19 @@ const {
21
21
 
22
22
  const GIT_REV_LIST_MAX_BUFFER = 8 * 1024 * 1024 // 8MB
23
23
 
24
+ function isShallowRepository () {
25
+ return sanitizedExec('git rev-parse --is-shallow-repository', { stdio: 'pipe' }) === 'true'
26
+ }
27
+
28
+ function unshallowRepository () {
29
+ try {
30
+ execSync('git config remote.origin.partialclonefilter "blob:none"', { stdio: 'pipe' })
31
+ execSync('git fetch --shallow-since="1 month ago" --update-shallow --refetch', { stdio: 'pipe' })
32
+ } catch (err) {
33
+ log.error(err)
34
+ }
35
+ }
36
+
24
37
  function getRepositoryUrl () {
25
38
  return sanitizedExec('git config --get remote.origin.url', { stdio: 'pipe' })
26
39
  }
@@ -146,5 +159,7 @@ module.exports = {
146
159
  getRepositoryUrl,
147
160
  generatePackFilesForCommits,
148
161
  getCommitsToUpload,
149
- GIT_REV_LIST_MAX_BUFFER
162
+ GIT_REV_LIST_MAX_BUFFER,
163
+ isShallowRepository,
164
+ unshallowRepository
150
165
  }
@@ -3,6 +3,9 @@
3
3
  const log = require('./log')
4
4
  const { profiler } = require('./profiling')
5
5
 
6
+ // Stop profiler upon exit in order to collect and export the current profile
7
+ process.once('beforeExit', () => { profiler.stop() })
8
+
6
9
  module.exports = {
7
10
  start: config => {
8
11
  const { service, version, env, url, hostname, port, tags } = config
@@ -23,7 +23,9 @@ const {
23
23
  DD_AGENT_HOST,
24
24
  DD_TRACE_AGENT_PORT,
25
25
  DD_PROFILING_UPLOAD_TIMEOUT,
26
- DD_PROFILING_SOURCE_MAP
26
+ DD_PROFILING_SOURCE_MAP,
27
+ DD_PROFILING_UPLOAD_PERIOD,
28
+ DD_PROFILING_PPROF_PREFIX
27
29
  } = process.env
28
30
 
29
31
  class Config {
@@ -35,13 +37,15 @@ class Config {
35
37
  const version = coalesce(options.version, DD_VERSION)
36
38
  const functionname = process.env.AWS_LAMBDA_FUNCTION_NAME
37
39
  // Must be longer than one minute so pad with five seconds
38
- const flushInterval = coalesce(options.interval, 65 * 1000)
40
+ const flushInterval = coalesce(options.interval, Number(DD_PROFILING_UPLOAD_PERIOD) * 1000, 65 * 1000)
39
41
  const uploadTimeout = coalesce(options.uploadTimeout,
40
- DD_PROFILING_UPLOAD_TIMEOUT, 60 * 1000)
42
+ Number(DD_PROFILING_UPLOAD_TIMEOUT), 60 * 1000)
41
43
  const sourceMap = coalesce(options.sourceMap,
42
44
  DD_PROFILING_SOURCE_MAP, true)
43
45
  const endpointCollection = coalesce(options.endpointCollection,
44
46
  DD_PROFILING_ENDPOINT_COLLECTION_ENABLED, false)
47
+ const pprofPrefix = coalesce(options.pprofPrefix,
48
+ DD_PROFILING_PPROF_PREFIX)
45
49
 
46
50
  this.enabled = String(enabled) !== 'false'
47
51
  this.service = service
@@ -60,6 +64,7 @@ class Config {
60
64
  this.uploadTimeout = uploadTimeout
61
65
  this.sourceMap = sourceMap
62
66
  this.endpointCollection = endpointCollection
67
+ this.pprofPrefix = pprofPrefix
63
68
 
64
69
  const hostname = coalesce(options.hostname, DD_AGENT_HOST) || 'localhost'
65
70
  const port = coalesce(options.port, DD_TRACE_AGENT_PORT) || 8126
@@ -4,11 +4,22 @@ const fs = require('fs')
4
4
  const { promisify } = require('util')
5
5
  const writeFile = promisify(fs.writeFile)
6
6
 
7
+ function formatDateTime (t) {
8
+ const pad = (n) => String(n).padStart(2, '0')
9
+ return `${t.getUTCFullYear()}${pad(t.getUTCMonth() + 1)}${pad(t.getUTCDate())}` +
10
+ `T${pad(t.getUTCHours())}${pad(t.getUTCMinutes())}${pad(t.getUTCSeconds())}Z`
11
+ }
12
+
7
13
  class FileExporter {
8
- export ({ profiles }) {
14
+ constructor ({ pprofPrefix } = {}) {
15
+ this._pprofPrefix = pprofPrefix || ''
16
+ }
17
+
18
+ export ({ profiles, end }) {
9
19
  const types = Object.keys(profiles)
20
+ const dateStr = formatDateTime(end)
10
21
  const tasks = types.map(type => {
11
- return writeFile(`${type}.pb.gz`, profiles[type])
22
+ return writeFile(`${this._pprofPrefix}${type}_${dateStr}.pprof`, profiles[type])
12
23
  })
13
24
 
14
25
  return Promise.all(tasks)
@@ -56,7 +56,7 @@ class Profiler extends EventEmitter {
56
56
  this._capture(this._timeoutInterval)
57
57
  } catch (e) {
58
58
  this._logger.error(e)
59
- this.stop()
59
+ this._stop()
60
60
  }
61
61
  }
62
62
 
@@ -64,7 +64,16 @@ class Profiler extends EventEmitter {
64
64
  this._timeoutInterval = this._config.flushInterval
65
65
  }
66
66
 
67
- stop () {
67
+ async stop () {
68
+ if (!this._enabled) return
69
+
70
+ // collect and export current profiles
71
+ // once collect returns, profilers can be safely stopped
72
+ this._collect()
73
+ this._stop()
74
+ }
75
+
76
+ _stop () {
68
77
  if (!this._enabled) return
69
78
 
70
79
  this._enabled = false
@@ -92,16 +101,24 @@ class Profiler extends EventEmitter {
92
101
  }
93
102
 
94
103
  async _collect () {
104
+ if (!this._enabled) return
105
+
95
106
  const start = this._lastStart
96
107
  const end = new Date()
97
- const profiles = {}
108
+ const profiles = []
109
+ const encodedProfiles = {}
98
110
 
99
111
  try {
112
+ // collect profiles synchronously so that profilers can be safely stopped asynchronously
100
113
  for (const profiler of this._config.profilers) {
101
114
  const profile = profiler.profile()
102
115
  if (!profile) continue
116
+ profiles.push({ profiler, profile })
117
+ }
103
118
 
104
- profiles[profiler.type] = await profiler.encode(profile)
119
+ // encode and export asynchronously
120
+ for (const { profiler, profile } of profiles) {
121
+ encodedProfiles[profiler.type] = await profiler.encode(profile)
105
122
  this._logger.debug(() => {
106
123
  const profileJson = JSON.stringify(profile, (key, value) => {
107
124
  return typeof value === 'bigint' ? value.toString() : value
@@ -111,11 +128,11 @@ class Profiler extends EventEmitter {
111
128
  }
112
129
 
113
130
  this._capture(this._timeoutInterval)
114
- await this._submit(profiles, start, end)
131
+ await this._submit(encodedProfiles, start, end)
115
132
  this._logger.debug('Submitted profiles')
116
133
  } catch (err) {
117
134
  this._logger.error(err)
118
- this.stop()
135
+ this._stop()
119
136
  }
120
137
  }
121
138
 
@@ -35,6 +35,7 @@ class NativeWallProfiler {
35
35
  stop () {
36
36
  if (!this._stop) return
37
37
  this._stop()
38
+ this._stop = undefined
38
39
  }
39
40
 
40
41
  _record () {
@@ -51,7 +51,7 @@ class Tracer extends NoopProxy {
51
51
  }
52
52
 
53
53
  this._tracer = new DatadogTracer(config)
54
- this.appsec = new AppsecSdk(this._tracer)
54
+ this.appsec = new AppsecSdk(this._tracer, config)
55
55
 
56
56
  if (config.iast.enabled) {
57
57
  require('./appsec/iast').enable(config, this._tracer)
@@ -17,7 +17,7 @@ class SpanProcessor {
17
17
  this._killAll = false
18
18
 
19
19
  this._stats = new SpanStatsProcessor(config)
20
- this._spanSampler = new SpanSampler(config)
20
+ this._spanSampler = new SpanSampler(config.sampler)
21
21
  }
22
22
 
23
23
  process (span) {
@@ -2,76 +2,92 @@
2
2
  const { globMatch } = require('../src/util')
3
3
  const { USER_KEEP, AUTO_KEEP } = require('../../../ext').priority
4
4
  const RateLimiter = require('./rate_limiter')
5
+ const Sampler = require('./sampler')
5
6
 
6
- class SpanSampler {
7
- constructor ({ spanSamplingRules = [] }) {
8
- this._rules = spanSamplingRules
9
- this._limiters = {}
7
+ class SpanSamplingRule {
8
+ constructor ({ service, name, sampleRate = 1.0, maxPerSecond } = {}) {
9
+ this.service = service
10
+ this.name = name
11
+
12
+ this._sampler = new Sampler(sampleRate)
13
+ this._limiter = undefined
14
+
15
+ if (Number.isFinite(maxPerSecond)) {
16
+ this._limiter = new RateLimiter(maxPerSecond)
17
+ }
10
18
  }
11
19
 
12
- sample (spanContext) {
13
- const decision = spanContext._sampling.priority
14
- if (decision === USER_KEEP || decision === AUTO_KEEP) return
20
+ get sampleRate () {
21
+ return this._sampler.rate()
22
+ }
15
23
 
16
- const { started } = spanContext._trace
17
- for (const span of started) {
18
- const service = span.tracer()._service
19
- const name = span._name
20
- const rule = findRule(this._rules, service, name)
21
- if (!rule) continue
24
+ get maxPerSecond () {
25
+ return this._limiter && this._limiter._rateLimit
26
+ }
22
27
 
23
- const sampleRate = getSampleRate(rule.sampleRate)
24
- const maxPerSecond = getMaxPerSecond(rule.maxPerSecond)
25
- const sampled = sample(sampleRate)
26
- if (!sampled) continue
28
+ static from (config) {
29
+ return new SpanSamplingRule(config)
30
+ }
27
31
 
28
- const key = `${service}:${name}`
29
- const limiter = getLimiter(this._limiters, key, maxPerSecond)
30
- if (limiter.isAllowed()) {
31
- span.context()._sampling.spanSampling = {
32
- sampleRate,
33
- maxPerSecond
34
- }
35
- }
32
+ match (service, name) {
33
+ if (this.service && !globMatch(this.service, service)) {
34
+ return false
36
35
  }
37
- }
38
- }
39
36
 
40
- function findRule (rules, service, name) {
41
- for (const rule of rules) {
42
- const servicePattern = getService(rule.service)
43
- const namePattern = getName(rule.name)
44
- if (globMatch(servicePattern, service) && globMatch(namePattern, name)) {
45
- return rule
37
+ if (this.name && !globMatch(this.name, name)) {
38
+ return false
46
39
  }
40
+
41
+ return true
47
42
  }
48
- }
49
43
 
50
- function getLimiter (list, key, maxPerSecond) {
51
- if (typeof list[key] === 'undefined') {
52
- list[key] = new RateLimiter(maxPerSecond)
44
+ sample () {
45
+ if (!this._sampler.isSampled()) {
46
+ return false
47
+ }
48
+
49
+ if (this._limiter) {
50
+ return this._limiter.isAllowed()
51
+ }
52
+
53
+ return true
53
54
  }
54
- return list[key]
55
55
  }
56
56
 
57
- function sample (sampleRate) {
58
- return Math.random() < sampleRate
59
- }
57
+ class SpanSampler {
58
+ constructor ({ spanSamplingRules = [] } = {}) {
59
+ this._rules = spanSamplingRules.map(SpanSamplingRule.from)
60
+ }
60
61
 
61
- function getService (service) {
62
- return service || '*'
63
- }
62
+ findRule (service, name) {
63
+ for (const rule of this._rules) {
64
+ if (rule.match(service, name)) {
65
+ return rule
66
+ }
67
+ }
68
+ }
64
69
 
65
- function getName (name) {
66
- return name || '*'
67
- }
70
+ sample (spanContext) {
71
+ const decision = spanContext._sampling.priority
72
+ if (decision === USER_KEEP || decision === AUTO_KEEP) return
68
73
 
69
- function getSampleRate (sampleRate) {
70
- return sampleRate || 1.0
71
- }
74
+ const { started } = spanContext._trace
75
+ for (const span of started) {
76
+ const tags = span.context()._tags || {}
77
+ const name = span._name
78
+ const service = tags.service ||
79
+ tags['service.name'] ||
80
+ span.tracer()._service
72
81
 
73
- function getMaxPerSecond (maxPerSecond) {
74
- return maxPerSecond || Infinity
82
+ const rule = this.findRule(service, name)
83
+ if (rule && rule.sample()) {
84
+ span.context()._sampling.spanSampling = {
85
+ sampleRate: rule.sampleRate,
86
+ maxPerSecond: rule.maxPerSecond
87
+ }
88
+ }
89
+ }
90
+ }
75
91
  }
76
92
 
77
93
  module.exports = SpanSampler
@@ -1,14 +1,11 @@
1
1
  'use strict'
2
2
 
3
- const mainLogger = require('./log')
3
+ const { info, warn } = require('./log/writer')
4
4
 
5
5
  const os = require('os')
6
6
  const { inspect } = require('util')
7
7
  const tracerVersion = require('../../../package.json').version
8
8
 
9
- const logger = Object.create(mainLogger)
10
- logger.toggle(true)
11
-
12
9
  let config
13
10
  let pluginManager
14
11
  let samplingRules = []
@@ -89,9 +86,9 @@ function startupLog ({ agentError } = {}) {
89
86
  // out.service_mapping
90
87
  // out.service_mapping_error
91
88
 
92
- logger.info('DATADOG TRACER CONFIGURATION - ' + out)
89
+ info('DATADOG TRACER CONFIGURATION - ' + out)
93
90
  if (agentError) {
94
- logger.warn('DATADOG TRACER DIAGNOSTIC - Agent Error: ' + agentError.message)
91
+ warn('DATADOG TRACER DIAGNOSTIC - Agent Error: ' + agentError.message)
95
92
  }
96
93
 
97
94
  config = undefined
@@ -127,22 +127,6 @@ class DatadogTracer extends Tracer {
127
127
  <meta name="dd-trace-id" content="${traceId}" />\
128
128
  <meta name="dd-trace-time" content="${traceTime}" />`
129
129
  }
130
-
131
- setUser (user) {
132
- if (!user || !user.id) return this
133
-
134
- const span = this.scope().active()
135
- if (!span) return this
136
-
137
- const rootSpan = span._spanContext._trace.started[0]
138
- if (!rootSpan) return this
139
-
140
- for (const k of Object.keys(user)) {
141
- rootSpan.setTag(`usr.${k}`, '' + user[k])
142
- }
143
-
144
- return this
145
- }
146
130
  }
147
131
 
148
132
  function addError (span, error) {