dd-trace 4.19.0 → 4.21.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 (62) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/index.d.ts +24 -0
  3. package/package.json +6 -5
  4. package/packages/datadog-instrumentations/src/aerospike.js +47 -0
  5. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  6. package/packages/datadog-instrumentations/src/http/client.js +22 -0
  7. package/packages/datadog-instrumentations/src/jest.js +11 -5
  8. package/packages/datadog-instrumentations/src/kafkajs.js +27 -0
  9. package/packages/datadog-instrumentations/src/next.js +3 -1
  10. package/packages/datadog-instrumentations/src/restify.js +1 -1
  11. package/packages/datadog-plugin-aerospike/src/index.js +113 -0
  12. package/packages/datadog-plugin-http/src/client.js +19 -2
  13. package/packages/datadog-plugin-kafkajs/src/consumer.js +51 -0
  14. package/packages/datadog-plugin-kafkajs/src/producer.js +55 -0
  15. package/packages/datadog-plugin-next/src/index.js +7 -7
  16. package/packages/dd-trace/src/appsec/addresses.js +2 -1
  17. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
  18. package/packages/dd-trace/src/appsec/iast/analyzers/header-injection-analyzer.js +105 -0
  19. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/constants.js +7 -0
  20. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +12 -19
  21. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/header-sensitive-analyzer.js +20 -0
  22. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/json-sensitive-analyzer.js +6 -10
  23. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/ldap-sensitive-analyzer.js +18 -25
  24. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +79 -85
  25. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +27 -36
  26. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +14 -11
  27. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
  28. package/packages/dd-trace/src/appsec/index.js +14 -2
  29. package/packages/dd-trace/src/appsec/recommended.json +1395 -2
  30. package/packages/dd-trace/src/appsec/reporter.js +19 -0
  31. package/packages/dd-trace/src/appsec/rule_manager.js +9 -6
  32. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +6 -3
  33. package/packages/dd-trace/src/appsec/waf/waf_manager.js +0 -1
  34. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +17 -1
  35. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +75 -56
  36. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +15 -9
  37. package/packages/dd-trace/src/config.js +37 -3
  38. package/packages/dd-trace/src/datastreams/processor.js +107 -12
  39. package/packages/dd-trace/src/id.js +12 -0
  40. package/packages/dd-trace/src/noop/proxy.js +4 -0
  41. package/packages/dd-trace/src/opentracing/propagation/text_map.js +14 -5
  42. package/packages/dd-trace/src/opentracing/span.js +2 -0
  43. package/packages/dd-trace/src/plugins/ci_plugin.js +2 -1
  44. package/packages/dd-trace/src/plugins/index.js +1 -0
  45. package/packages/dd-trace/src/plugins/util/git.js +2 -2
  46. package/packages/dd-trace/src/plugins/util/test.js +3 -2
  47. package/packages/dd-trace/src/profiler.js +5 -3
  48. package/packages/dd-trace/src/profiling/config.js +17 -10
  49. package/packages/dd-trace/src/profiling/profiler.js +10 -4
  50. package/packages/dd-trace/src/profiling/profilers/events.js +171 -73
  51. package/packages/dd-trace/src/profiling/profilers/wall.js +93 -67
  52. package/packages/dd-trace/src/proxy.js +21 -1
  53. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +5 -0
  54. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +4 -0
  55. package/packages/dd-trace/src/spanleak.js +98 -0
  56. package/packages/dd-trace/src/startup-log.js +7 -1
  57. package/packages/dd-trace/src/telemetry/dependencies.js +55 -9
  58. package/packages/dd-trace/src/telemetry/index.js +135 -43
  59. package/packages/dd-trace/src/telemetry/logs/index.js +1 -1
  60. package/packages/dd-trace/src/telemetry/send-data.js +47 -5
  61. package/packages/dd-trace/src/tracer.js +4 -0
  62. package/scripts/install_plugin_modules.js +11 -3
@@ -10,6 +10,7 @@ const {
10
10
  incrementWafUpdatesMetric,
11
11
  incrementWafRequestsMetric
12
12
  } = require('./telemetry')
13
+ const zlib = require('zlib')
13
14
 
14
15
  // default limiter, configurable with setRateLimit()
15
16
  let limiter = new Limiter(100)
@@ -140,6 +141,23 @@ function reportAttack (attackData) {
140
141
  rootSpan.addTags(newTags)
141
142
  }
142
143
 
144
+ function reportSchemas (derivatives) {
145
+ if (!derivatives) return
146
+
147
+ const req = storage.getStore()?.req
148
+ const rootSpan = web.root(req)
149
+
150
+ if (!rootSpan) return
151
+
152
+ const tags = {}
153
+ for (const [address, value] of Object.entries(derivatives)) {
154
+ const gzippedValue = zlib.gzipSync(JSON.stringify(value))
155
+ tags[address] = gzippedValue.toString('base64')
156
+ }
157
+
158
+ rootSpan.addTags(tags)
159
+ }
160
+
143
161
  function finishRequest (req, res) {
144
162
  const rootSpan = web.root(req)
145
163
  if (!rootSpan) return
@@ -175,6 +193,7 @@ module.exports = {
175
193
  reportMetrics,
176
194
  reportAttack,
177
195
  reportWafUpdate: incrementWafUpdatesMetric,
196
+ reportSchemas,
178
197
  finishRequest,
179
198
  setRateLimit,
180
199
  mapHeaderAndTags
@@ -1,5 +1,6 @@
1
1
  'use strict'
2
2
 
3
+ const fs = require('fs')
3
4
  const waf = require('./waf')
4
5
  const { ACKNOWLEDGED, ERROR } = require('./remote_config/apply_states')
5
6
  const blocking = require('./blocking')
@@ -13,13 +14,15 @@ let appliedExclusions = new Map()
13
14
  let appliedCustomRules = new Map()
14
15
  let appliedActions = new Map()
15
16
 
16
- function applyRules (rules, config) {
17
- defaultRules = rules
17
+ function loadRules (config) {
18
+ defaultRules = config.rules
19
+ ? JSON.parse(fs.readFileSync(config.rules))
20
+ : require('./recommended.json')
18
21
 
19
- waf.init(rules, config)
22
+ waf.init(defaultRules, config)
20
23
 
21
- if (rules.actions) {
22
- blocking.updateBlockingConfiguration(rules.actions.find(action => action.id === 'block'))
24
+ if (defaultRules.actions) {
25
+ blocking.updateBlockingConfiguration(defaultRules.actions.find(action => action.id === 'block'))
23
26
  }
24
27
  }
25
28
 
@@ -252,7 +255,7 @@ function clearAllRules () {
252
255
  }
253
256
 
254
257
  module.exports = {
255
- applyRules,
258
+ loadRules,
256
259
  updateWafFromRC,
257
260
  clearAllRules
258
261
  }
@@ -10,9 +10,8 @@ const preventDuplicateAddresses = new Set([
10
10
  ])
11
11
 
12
12
  class WAFContextWrapper {
13
- constructor (ddwafContext, requiredAddresses, wafTimeout, wafVersion, rulesVersion) {
13
+ constructor (ddwafContext, wafTimeout, wafVersion, rulesVersion) {
14
14
  this.ddwafContext = ddwafContext
15
- this.requiredAddresses = requiredAddresses
16
15
  this.wafTimeout = wafTimeout
17
16
  this.wafVersion = wafVersion
18
17
  this.rulesVersion = rulesVersion
@@ -26,7 +25,9 @@ class WAFContextWrapper {
26
25
 
27
26
  // TODO: possible optimizaion: only send params that haven't already been sent with same value to this wafContext
28
27
  for (const key of Object.keys(params)) {
29
- if (this.requiredAddresses.has(key) && !this.addressesToSkip.has(key)) {
28
+ // TODO: requiredAddresses is no longer used due to processor addresses are not included in the list. Check on
29
+ // future versions when the actual addresses are included in the 'loaded' section inside diagnostics.
30
+ if (!this.addressesToSkip.has(key)) {
30
31
  inputs[key] = params[key]
31
32
  if (preventDuplicateAddresses.has(key)) {
32
33
  newAddressesToSkip.add(key)
@@ -63,6 +64,8 @@ class WAFContextWrapper {
63
64
  Reporter.reportAttack(JSON.stringify(result.events))
64
65
  }
65
66
 
67
+ Reporter.reportSchemas(result.derivatives)
68
+
66
69
  return result.actions
67
70
  } catch (err) {
68
71
  log.error('Error while running the AppSec WAF')
@@ -37,7 +37,6 @@ class WAFManager {
37
37
  if (!wafContext) {
38
38
  wafContext = new WAFContextWrapper(
39
39
  this.ddwaf.createContext(),
40
- this.ddwaf.requiredAddresses,
41
40
  this.wafTimeout,
42
41
  this.ddwafVersion,
43
42
  this.rulesVersion
@@ -143,7 +143,23 @@ class CiVisibilityExporter extends AgentInfoExporter {
143
143
  * where the tests run in a subprocess, because `getItrConfiguration` is called only once.
144
144
  */
145
145
  this._itrConfig = itrConfig
146
- callback(err, itrConfig)
146
+
147
+ if (err) {
148
+ callback(err, {})
149
+ } else if (itrConfig?.requireGit) {
150
+ // If the backend requires git, we'll wait for the upload to finish and request settings again
151
+ this._gitUploadPromise.then(gitUploadError => {
152
+ if (gitUploadError) {
153
+ return callback(gitUploadError, {})
154
+ }
155
+ getItrConfigurationRequest(configuration, (err, finalItrConfig) => {
156
+ this._itrConfig = finalItrConfig
157
+ callback(err, finalItrConfig)
158
+ })
159
+ })
160
+ } else {
161
+ callback(null, itrConfig)
162
+ }
147
163
  })
148
164
  })
149
165
  }
@@ -10,7 +10,7 @@ const {
10
10
  getLatestCommits,
11
11
  getRepositoryUrl,
12
12
  generatePackFilesForCommits,
13
- getCommitsToUpload,
13
+ getCommitsRevList,
14
14
  isShallowRepository,
15
15
  unshallowRepository
16
16
  } = require('../../../plugins/util/git')
@@ -46,11 +46,7 @@ function getCommonRequestOptions (url) {
46
46
  * The response are the commits for which the backend already has information
47
47
  * This response is used to know which commits can be ignored from there on
48
48
  */
49
- function getCommitsToExclude ({ url, isEvpProxy, repositoryUrl }, callback) {
50
- const latestCommits = getLatestCommits()
51
-
52
- log.debug(`There were ${latestCommits.length} commits since last month.`)
53
-
49
+ function getCommitsToUpload ({ url, repositoryUrl, latestCommits, isEvpProxy }, callback) {
54
50
  const commonOptions = getCommonRequestOptions(url)
55
51
 
56
52
  const options = {
@@ -83,13 +79,23 @@ function getCommitsToExclude ({ url, isEvpProxy, repositoryUrl }, callback) {
83
79
  const error = new Error(`Error fetching commits to exclude: ${err.message}`)
84
80
  return callback(error)
85
81
  }
86
- let commitsToExclude
82
+ let alreadySeenCommits
87
83
  try {
88
- commitsToExclude = validateCommits(JSON.parse(response).data)
84
+ alreadySeenCommits = validateCommits(JSON.parse(response).data)
89
85
  } catch (e) {
90
86
  return callback(new Error(`Can't parse commits to exclude response: ${e.message}`))
91
87
  }
92
- callback(null, commitsToExclude, latestCommits)
88
+ log.debug(`There are ${alreadySeenCommits.length} commits to exclude.`)
89
+ const commitsToInclude = latestCommits.filter((commit) => !alreadySeenCommits.includes(commit))
90
+ log.debug(`There are ${commitsToInclude.length} commits to include.`)
91
+
92
+ if (!commitsToInclude.length) {
93
+ return callback(null, [])
94
+ }
95
+
96
+ const commitsToUpload = getCommitsRevList(alreadySeenCommits, commitsToInclude)
97
+
98
+ callback(null, commitsToUpload)
93
99
  })
94
100
  }
95
101
 
@@ -150,6 +156,53 @@ function uploadPackFile ({ url, isEvpProxy, packFileToUpload, repositoryUrl, hea
150
156
  })
151
157
  }
152
158
 
159
+ function generateAndUploadPackFiles ({
160
+ url,
161
+ isEvpProxy,
162
+ commitsToUpload,
163
+ repositoryUrl,
164
+ headCommit
165
+ }, callback) {
166
+ log.debug(`There are ${commitsToUpload.length} commits to upload`)
167
+
168
+ const packFilesToUpload = generatePackFilesForCommits(commitsToUpload)
169
+
170
+ log.debug(`Uploading ${packFilesToUpload.length} packfiles.`)
171
+
172
+ if (!packFilesToUpload.length) {
173
+ return callback(new Error('Failed to generate packfiles'))
174
+ }
175
+
176
+ let packFileIndex = 0
177
+ // This uploads packfiles sequentially
178
+ const uploadPackFileCallback = (err) => {
179
+ if (err || packFileIndex === packFilesToUpload.length) {
180
+ return callback(err)
181
+ }
182
+ return uploadPackFile(
183
+ {
184
+ packFileToUpload: packFilesToUpload[packFileIndex++],
185
+ url,
186
+ isEvpProxy,
187
+ repositoryUrl,
188
+ headCommit
189
+ },
190
+ uploadPackFileCallback
191
+ )
192
+ }
193
+
194
+ uploadPackFile(
195
+ {
196
+ packFileToUpload: packFilesToUpload[packFileIndex++],
197
+ url,
198
+ isEvpProxy,
199
+ repositoryUrl,
200
+ headCommit
201
+ },
202
+ uploadPackFileCallback
203
+ )
204
+ }
205
+
153
206
  /**
154
207
  * This function uploads git metadata to CI Visibility's backend.
155
208
  */
@@ -165,65 +218,31 @@ function sendGitMetadata (url, isEvpProxy, configRepositoryUrl, callback) {
165
218
  return callback(new Error('Repository URL is empty'))
166
219
  }
167
220
 
168
- if (isShallowRepository()) {
169
- log.debug('It is shallow clone, unshallowing...')
170
- unshallowRepository()
171
- }
221
+ const latestCommits = getLatestCommits()
222
+ log.debug(`There were ${latestCommits.length} commits since last month.`)
223
+ const [headCommit] = latestCommits
172
224
 
173
- getCommitsToExclude({ url, repositoryUrl, isEvpProxy }, (err, commitsToExclude, latestCommits) => {
225
+ const getOnFinishGetCommitsToUpload = (hasCheckedShallow) => (err, commitsToUpload) => {
174
226
  if (err) {
175
227
  return callback(err)
176
228
  }
177
- log.debug(`There are ${commitsToExclude.length} commits to exclude.`)
178
- const [headCommit] = latestCommits
179
- const commitsToInclude = latestCommits.filter((commit) => !commitsToExclude.includes(commit))
180
- log.debug(`There are ${commitsToInclude.length} commits to include.`)
181
-
182
- const commitsToUpload = getCommitsToUpload(commitsToExclude, commitsToInclude)
183
229
 
184
230
  if (!commitsToUpload.length) {
185
231
  log.debug('No commits to upload')
186
232
  return callback(null)
187
233
  }
188
- log.debug(`There are ${commitsToUpload.length} commits to upload`)
189
-
190
- const packFilesToUpload = generatePackFilesForCommits(commitsToUpload)
191
-
192
- log.debug(`Uploading ${packFilesToUpload.length} packfiles.`)
193
-
194
- if (!packFilesToUpload.length) {
195
- return callback(new Error('Failed to generate packfiles'))
196
- }
197
234
 
198
- let packFileIndex = 0
199
- // This uploads packfiles sequentially
200
- const uploadPackFileCallback = (err) => {
201
- if (err || packFileIndex === packFilesToUpload.length) {
202
- return callback(err)
203
- }
204
- return uploadPackFile(
205
- {
206
- packFileToUpload: packFilesToUpload[packFileIndex++],
207
- url,
208
- isEvpProxy,
209
- repositoryUrl,
210
- headCommit
211
- },
212
- uploadPackFileCallback
213
- )
235
+ // If it has already unshallowed or the clone is not shallow, we move on
236
+ if (hasCheckedShallow || !isShallowRepository()) {
237
+ return generateAndUploadPackFiles({ url, isEvpProxy, commitsToUpload, repositoryUrl, headCommit }, callback)
214
238
  }
239
+ // Otherwise we unshallow and get commits to upload again
240
+ log.debug('It is shallow clone, unshallowing...')
241
+ unshallowRepository()
242
+ getCommitsToUpload({ url, repositoryUrl, latestCommits, isEvpProxy }, getOnFinishGetCommitsToUpload(true))
243
+ }
215
244
 
216
- uploadPackFile(
217
- {
218
- packFileToUpload: packFilesToUpload[packFileIndex++],
219
- url,
220
- isEvpProxy,
221
- repositoryUrl,
222
- headCommit
223
- },
224
- uploadPackFileCallback
225
- )
226
- })
245
+ getCommitsToUpload({ url, repositoryUrl, latestCommits, isEvpProxy }, getOnFinishGetCommitsToUpload(false))
227
246
  }
228
247
 
229
248
  module.exports = {
@@ -15,6 +15,7 @@ function getItrConfiguration ({
15
15
  runtimeName,
16
16
  runtimeVersion,
17
17
  branch,
18
+ testLevel = 'suite',
18
19
  custom
19
20
  }, done) {
20
21
  const options = {
@@ -23,7 +24,8 @@ function getItrConfiguration ({
23
24
  headers: {
24
25
  'Content-Type': 'application/json'
25
26
  },
26
- url
27
+ url,
28
+ timeout: 20000
27
29
  }
28
30
 
29
31
  if (isEvpProxy) {
@@ -42,7 +44,7 @@ function getItrConfiguration ({
42
44
  id: id().toString(10),
43
45
  type: 'ci_app_test_service_libraries_settings',
44
46
  attributes: {
45
- test_level: 'suite',
47
+ test_level: testLevel,
46
48
  configurations: {
47
49
  'os.platform': osPlatform,
48
50
  'os.version': osVersion,
@@ -67,25 +69,29 @@ function getItrConfiguration ({
67
69
  try {
68
70
  const {
69
71
  data: {
70
- attributes
72
+ attributes: {
73
+ code_coverage: isCodeCoverageEnabled,
74
+ tests_skipping: isSuitesSkippingEnabled,
75
+ itr_enabled: isItrEnabled,
76
+ require_git: requireGit
77
+ }
71
78
  }
72
79
  } = JSON.parse(res)
73
80
 
74
- let isCodeCoverageEnabled = attributes.code_coverage
75
- let isSuitesSkippingEnabled = attributes.tests_skipping
81
+ const settings = { isCodeCoverageEnabled, isSuitesSkippingEnabled, isItrEnabled, requireGit }
76
82
 
77
- log.debug(() => `Remote settings: ${{ isCodeCoverageEnabled, isSuitesSkippingEnabled }}`)
83
+ log.debug(() => `Remote settings: ${JSON.stringify(settings)}`)
78
84
 
79
85
  if (process.env.DD_CIVISIBILITY_DANGEROUSLY_FORCE_COVERAGE) {
80
- isCodeCoverageEnabled = true
86
+ settings.isCodeCoverageEnabled = true
81
87
  log.debug(() => 'Dangerously set code coverage to true')
82
88
  }
83
89
  if (process.env.DD_CIVISIBILITY_DANGEROUSLY_FORCE_TEST_SKIPPING) {
84
- isSuitesSkippingEnabled = true
90
+ settings.isSuitesSkippingEnabled = true
85
91
  log.debug(() => 'Dangerously set test skipping to true')
86
92
  }
87
93
 
88
- done(null, { isCodeCoverageEnabled, isSuitesSkippingEnabled })
94
+ done(null, settings)
89
95
  } catch (err) {
90
96
  done(err)
91
97
  }
@@ -309,6 +309,10 @@ class Config {
309
309
  options.tracePropagationStyle,
310
310
  defaultPropagationStyle
311
311
  )
312
+ const DD_TRACE_PROPAGATION_EXTRACT_FIRST = coalesce(
313
+ process.env.DD_TRACE_PROPAGATION_EXTRACT_FIRST,
314
+ false
315
+ )
312
316
  const DD_TRACE_RUNTIME_ID_ENABLED = coalesce(
313
317
  options.experimental && options.experimental.runtimeId,
314
318
  process.env.DD_TRACE_EXPERIMENTAL_RUNTIME_ID_ENABLED,
@@ -395,7 +399,6 @@ class Config {
395
399
  appsec.enabled,
396
400
  process.env.DD_APPSEC_ENABLED && isTrue(process.env.DD_APPSEC_ENABLED)
397
401
  )
398
-
399
402
  const DD_APPSEC_RULES = coalesce(
400
403
  appsec.rules,
401
404
  process.env.DD_APPSEC_RULES
@@ -437,6 +440,16 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
437
440
  process.env.DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING,
438
441
  'safe'
439
442
  ).toLowerCase()
443
+ const DD_EXPERIMENTAL_API_SECURITY_ENABLED = coalesce(
444
+ appsec?.apiSecurity?.enabled,
445
+ isTrue(process.env.DD_EXPERIMENTAL_API_SECURITY_ENABLED),
446
+ false
447
+ )
448
+ const DD_API_SECURITY_REQUEST_SAMPLE_RATE = coalesce(
449
+ appsec?.apiSecurity?.requestSampling,
450
+ parseFloat(process.env.DD_API_SECURITY_REQUEST_SAMPLE_RATE),
451
+ 0.1
452
+ )
440
453
 
441
454
  const remoteConfigOptions = options.remoteConfig || {}
442
455
  const DD_REMOTE_CONFIGURATION_ENABLED = coalesce(
@@ -461,6 +474,11 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
461
474
  DD_IAST_ENABLED
462
475
  )
463
476
 
477
+ const DD_TELEMETRY_DEPENDENCY_COLLECTION_ENABLED = coalesce(
478
+ process.env.DD_TELEMETRY_DEPENDENCY_COLLECTION_ENABLED,
479
+ true
480
+ )
481
+
464
482
  const defaultIastRequestSampling = 30
465
483
  const iastRequestSampling = coalesce(
466
484
  parseInt(iastOptions?.requestSampling),
@@ -522,6 +540,12 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
522
540
  true
523
541
  )
524
542
 
543
+ // 0: disabled, 1: logging, 2: garbage collection + logging
544
+ const DD_TRACE_SPAN_LEAK_DEBUG = coalesce(
545
+ process.env.DD_TRACE_SPAN_LEAK_DEBUG,
546
+ 0
547
+ )
548
+
525
549
  const ingestion = options.ingestion || {}
526
550
  const dogstatsd = coalesce(options.dogstatsd, {})
527
551
  const sampler = {
@@ -579,6 +603,7 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
579
603
  inject: DD_TRACE_PROPAGATION_STYLE_INJECT,
580
604
  extract: DD_TRACE_PROPAGATION_STYLE_EXTRACT
581
605
  }
606
+ this.tracePropagationExtractFirst = isTrue(DD_TRACE_PROPAGATION_EXTRACT_FIRST)
582
607
  this.experimental = {
583
608
  runtimeId: isTrue(DD_TRACE_RUNTIME_ID_ENABLED),
584
609
  exporter: DD_TRACE_EXPORTER,
@@ -604,13 +629,14 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
604
629
  heartbeatInterval: DD_TELEMETRY_HEARTBEAT_INTERVAL,
605
630
  debug: isTrue(DD_TELEMETRY_DEBUG),
606
631
  logCollection: isTrue(DD_TELEMETRY_LOG_COLLECTION_ENABLED),
607
- metrics: isTrue(DD_TELEMETRY_METRICS_ENABLED)
632
+ metrics: isTrue(DD_TELEMETRY_METRICS_ENABLED),
633
+ dependencyCollection: DD_TELEMETRY_DEPENDENCY_COLLECTION_ENABLED
608
634
  }
609
635
  this.protocolVersion = DD_TRACE_AGENT_PROTOCOL_VERSION
610
636
  this.tagsHeaderMaxLength = parseInt(DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH)
611
637
  this.appsec = {
612
638
  enabled: DD_APPSEC_ENABLED,
613
- rules: DD_APPSEC_RULES ? safeJsonParse(maybeFile(DD_APPSEC_RULES)) : require('./appsec/recommended.json'),
639
+ rules: DD_APPSEC_RULES,
614
640
  customRulesProvided: !!DD_APPSEC_RULES,
615
641
  rateLimit: DD_APPSEC_TRACE_RATE_LIMIT,
616
642
  wafTimeout: DD_APPSEC_WAF_TIMEOUT,
@@ -621,8 +647,14 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
621
647
  eventTracking: {
622
648
  enabled: ['extended', 'safe'].includes(DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING),
623
649
  mode: DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING
650
+ },
651
+ apiSecurity: {
652
+ enabled: DD_EXPERIMENTAL_API_SECURITY_ENABLED,
653
+ // Coerce value between 0 and 1
654
+ requestSampling: Math.min(1, Math.max(0, DD_API_SECURITY_REQUEST_SAMPLE_RATE))
624
655
  }
625
656
  }
657
+
626
658
  this.remoteConfig = {
627
659
  enabled: DD_REMOTE_CONFIGURATION_ENABLED,
628
660
  pollInterval: DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS
@@ -696,6 +728,8 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
696
728
  this.isGCPFunction = isGCPFunction
697
729
  this.isAzureFunctionConsumptionPlan = isAzureFunctionConsumptionPlan
698
730
 
731
+ this.spanLeakDebug = Number(DD_TRACE_SPAN_LEAK_DEBUG)
732
+
699
733
  tagger.add(this.tags, {
700
734
  service: this.service,
701
735
  env: this.env,
@@ -45,14 +45,73 @@ class StatsPoint {
45
45
  }
46
46
  }
47
47
 
48
- class StatsBucket extends Map {
48
+ class Backlog {
49
+ constructor ({ offset, ...tags }) {
50
+ this._tags = Object.keys(tags).sort().map(key => `${key}:${tags[key]}`)
51
+ this._hash = this._tags.join(',')
52
+ this._offset = offset
53
+ }
54
+
55
+ get hash () { return this._hash }
56
+
57
+ get offset () { return this._offset }
58
+
59
+ get tags () { return this._tags }
60
+
61
+ encode () {
62
+ return {
63
+ Tags: this.tags,
64
+ Value: this.offset
65
+ }
66
+ }
67
+ }
68
+
69
+ class StatsBucket {
70
+ constructor () {
71
+ this._checkpoints = new Map()
72
+ this._backlogs = new Map()
73
+ }
74
+
75
+ get checkpoints () {
76
+ return this._checkpoints
77
+ }
78
+
79
+ get backlogs () {
80
+ return this._backlogs
81
+ }
82
+
49
83
  forCheckpoint (checkpoint) {
50
84
  const key = checkpoint.hash
51
- if (!this.has(key)) {
52
- this.set(key, new StatsPoint(checkpoint.hash, checkpoint.parentHash, checkpoint.edgeTags)) // StatsPoint
85
+ if (!this._checkpoints.has(key)) {
86
+ this._checkpoints.set(
87
+ key, new StatsPoint(checkpoint.hash, checkpoint.parentHash, checkpoint.edgeTags)
88
+ )
53
89
  }
54
90
 
55
- return this.get(key)
91
+ return this._checkpoints.get(key)
92
+ }
93
+
94
+ /**
95
+ * Conditionally add a backlog to the bucket. If there is currently an offset
96
+ * matching the backlog's tags, overwrite the offset IFF the backlog's offset
97
+ * is greater than the recorded offset.
98
+ *
99
+ * @typedef {{[key: string]: string}} BacklogData
100
+ * @property {number} offset
101
+ *
102
+ * @param {BacklogData} backlogData
103
+ * @returns {Backlog}
104
+ */
105
+ forBacklog (backlogData) {
106
+ const backlog = new Backlog(backlogData)
107
+ const existingBacklog = this._backlogs.get(backlog.hash)
108
+ if (existingBacklog !== undefined) {
109
+ if (existingBacklog.offset > backlog.offset) {
110
+ return existingBacklog
111
+ }
112
+ }
113
+ this._backlogs.set(backlog.hash, backlog)
114
+ return backlog
56
115
  }
57
116
  }
58
117
 
@@ -122,12 +181,12 @@ class DataStreamsProcessor {
122
181
  }
123
182
 
124
183
  onInterval () {
125
- const serialized = this._serializeBuckets()
126
- if (!serialized) return
184
+ const { Stats } = this._serializeBuckets()
185
+ if (Stats.length === 0) return
127
186
  const payload = {
128
187
  Env: this.env,
129
188
  Service: this.service,
130
- Stats: serialized,
189
+ Stats,
131
190
  TracerVersion: pkg.version,
132
191
  Version: this.version,
133
192
  Lang: 'javascript'
@@ -135,10 +194,19 @@ class DataStreamsProcessor {
135
194
  this.writer.flush(payload)
136
195
  }
137
196
 
197
+ /**
198
+ * Given a timestamp in nanoseconds, compute and return the closest TimeBucket
199
+ * @param {number} timestamp
200
+ * @returns {StatsBucket}
201
+ */
202
+ bucketFromTimestamp (timestamp) {
203
+ const bucketTime = Math.round(timestamp - (timestamp % this.bucketSizeNs))
204
+ return this.buckets.forTime(bucketTime)
205
+ }
206
+
138
207
  recordCheckpoint (checkpoint, span = null) {
139
208
  if (!this.enabled) return
140
- const bucketTime = Math.round(checkpoint.currentTimestamp - (checkpoint.currentTimestamp % this.bucketSizeNs))
141
- this.buckets.forTime(bucketTime)
209
+ this.bucketFromTimestamp(checkpoint.currentTimestamp)
142
210
  .forCheckpoint(checkpoint)
143
211
  .addLatencies(checkpoint)
144
212
  // set DSM pathway hash on span to enable related traces feature on DSM tab, convert from buffer to uint64
@@ -207,26 +275,52 @@ class DataStreamsProcessor {
207
275
  return dataStreamsContext
208
276
  }
209
277
 
278
+ recordOffset ({ timestamp, ...backlogData }) {
279
+ if (!this.enabled) return
280
+ return this.bucketFromTimestamp(timestamp)
281
+ .forBacklog(backlogData)
282
+ }
283
+
284
+ setOffset (offsetObj) {
285
+ if (!this.enabled) return
286
+ const nowNs = Date.now() * 1e6
287
+ const backlogData = {
288
+ ...offsetObj,
289
+ timestamp: nowNs
290
+ }
291
+ this.recordOffset(backlogData)
292
+ }
293
+
210
294
  _serializeBuckets () {
295
+ // TimeBuckets
211
296
  const serializedBuckets = []
212
297
 
213
298
  for (const [ timeNs, bucket ] of this.buckets.entries()) {
214
299
  const points = []
215
300
 
216
- for (const stats of bucket.values()) {
301
+ // bucket: StatsBucket
302
+ // stats: StatsPoint
303
+ for (const stats of bucket._checkpoints.values()) {
217
304
  points.push(stats.encode())
218
305
  }
219
306
 
307
+ const backlogs = []
308
+ for (const backlog of bucket._backlogs.values()) {
309
+ backlogs.push(backlog.encode())
310
+ }
220
311
  serializedBuckets.push({
221
312
  Start: new Uint64(timeNs),
222
313
  Duration: new Uint64(this.bucketSizeNs),
223
- Stats: points
314
+ Stats: points,
315
+ Backlogs: backlogs
224
316
  })
225
317
  }
226
318
 
227
319
  this.buckets.clear()
228
320
 
229
- return serializedBuckets
321
+ return {
322
+ Stats: serializedBuckets
323
+ }
230
324
  }
231
325
  }
232
326
 
@@ -234,6 +328,7 @@ module.exports = {
234
328
  DataStreamsProcessor: DataStreamsProcessor,
235
329
  StatsPoint: StatsPoint,
236
330
  StatsBucket: StatsBucket,
331
+ Backlog,
237
332
  TimeBuckets,
238
333
  getMessageSize,
239
334
  getHeadersSize,
@@ -42,6 +42,18 @@ class Identifier {
42
42
  toJSON () {
43
43
  return this.toString()
44
44
  }
45
+
46
+ equals (other) {
47
+ const length = this._buffer.length
48
+ const otherLength = other._buffer.length
49
+
50
+ // Only compare the bytes available in both IDs.
51
+ for (let i = length, j = otherLength; i >= 0 && j >= 0; i--, j--) {
52
+ if (this._buffer[i] !== other._buffer[j]) return false
53
+ }
54
+
55
+ return true
56
+ }
45
57
  }
46
58
 
47
59
  // Create a buffer, using an optional hexadecimal value if provided.