dd-trace 4.38.1 → 4.40.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 (73) hide show
  1. package/LICENSE-3rdparty.csv +0 -3
  2. package/README.md +8 -18
  3. package/ci/init.js +7 -0
  4. package/ext/exporters.d.ts +1 -0
  5. package/ext/exporters.js +2 -1
  6. package/ext/tags.d.ts +1 -0
  7. package/ext/tags.js +1 -0
  8. package/index.d.ts +18 -3
  9. package/initialize.mjs +52 -0
  10. package/package.json +9 -12
  11. package/packages/datadog-instrumentations/src/amqplib.js +5 -2
  12. package/packages/datadog-instrumentations/src/apollo-server-core.js +0 -1
  13. package/packages/datadog-instrumentations/src/apollo-server.js +0 -1
  14. package/packages/datadog-instrumentations/src/body-parser.js +0 -1
  15. package/packages/datadog-instrumentations/src/check_require_cache.js +67 -5
  16. package/packages/datadog-instrumentations/src/cookie-parser.js +0 -1
  17. package/packages/datadog-instrumentations/src/express.js +0 -1
  18. package/packages/datadog-instrumentations/src/graphql.js +0 -2
  19. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  20. package/packages/datadog-instrumentations/src/helpers/register.js +5 -2
  21. package/packages/datadog-instrumentations/src/http/server.js +0 -1
  22. package/packages/datadog-instrumentations/src/jest.js +6 -3
  23. package/packages/datadog-instrumentations/src/mocha/common.js +48 -0
  24. package/packages/datadog-instrumentations/src/mocha/main.js +487 -0
  25. package/packages/datadog-instrumentations/src/mocha/utils.js +306 -0
  26. package/packages/datadog-instrumentations/src/mocha/worker.js +51 -0
  27. package/packages/datadog-instrumentations/src/mocha.js +4 -673
  28. package/packages/datadog-instrumentations/src/openai.js +188 -17
  29. package/packages/datadog-instrumentations/src/playwright.js +4 -3
  30. package/packages/datadog-instrumentations/src/router.js +1 -1
  31. package/packages/datadog-instrumentations/src/selenium.js +13 -6
  32. package/packages/datadog-plugin-graphql/src/resolve.js +4 -0
  33. package/packages/datadog-plugin-mocha/src/index.js +82 -8
  34. package/packages/datadog-plugin-next/src/index.js +1 -2
  35. package/packages/datadog-plugin-openai/src/index.js +219 -73
  36. package/packages/dd-trace/src/appsec/addresses.js +4 -2
  37. package/packages/dd-trace/src/appsec/blocking.js +19 -25
  38. package/packages/dd-trace/src/appsec/channels.js +2 -1
  39. package/packages/dd-trace/src/appsec/graphql.js +10 -3
  40. package/packages/dd-trace/src/appsec/index.js +11 -4
  41. package/packages/dd-trace/src/appsec/rasp.js +35 -0
  42. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
  43. package/packages/dd-trace/src/appsec/remote_config/index.js +1 -0
  44. package/packages/dd-trace/src/appsec/rule_manager.js +15 -25
  45. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +2 -5
  46. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +3 -1
  47. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +5 -1
  48. package/packages/dd-trace/src/config.js +97 -22
  49. package/packages/dd-trace/src/constants.js +2 -0
  50. package/packages/dd-trace/src/encode/0.4.js +47 -8
  51. package/packages/dd-trace/src/exporter.js +1 -0
  52. package/packages/dd-trace/src/flare/file.js +44 -0
  53. package/packages/dd-trace/src/flare/index.js +98 -0
  54. package/packages/dd-trace/src/log/channels.js +54 -29
  55. package/packages/dd-trace/src/log/writer.js +7 -49
  56. package/packages/dd-trace/src/opentelemetry/span.js +8 -0
  57. package/packages/dd-trace/src/opentracing/propagation/text_map.js +57 -12
  58. package/packages/dd-trace/src/plugins/index.js +1 -0
  59. package/packages/dd-trace/src/plugins/util/ip_extractor.js +1 -1
  60. package/packages/dd-trace/src/plugins/util/test.js +6 -0
  61. package/packages/dd-trace/src/priority_sampler.js +8 -4
  62. package/packages/dd-trace/src/profiler.js +2 -1
  63. package/packages/dd-trace/src/profiling/config.js +1 -0
  64. package/packages/dd-trace/src/profiling/profiler.js +1 -1
  65. package/packages/dd-trace/src/profiling/{ssi-telemetry.js → ssi-heuristics.js} +64 -36
  66. package/packages/dd-trace/src/profiling/ssi-telemetry-mock-profiler.js +4 -9
  67. package/packages/dd-trace/src/proxy.js +49 -15
  68. package/packages/dd-trace/src/ritm.js +13 -1
  69. package/packages/dd-trace/src/sampling_rule.js +2 -1
  70. package/packages/dd-trace/src/startup-log.js +19 -15
  71. package/packages/dd-trace/src/telemetry/index.js +6 -2
  72. package/packages/dd-trace/src/tracer.js +3 -0
  73. package/packages/dd-trace/src/plugins/util/ip_blocklist.js +0 -51
@@ -15,5 +15,6 @@ module.exports = {
15
15
  APM_TRACING_LOGS_INJECTION: 1n << 13n,
16
16
  APM_TRACING_HTTP_HEADER_TAGS: 1n << 14n,
17
17
  APM_TRACING_CUSTOM_TAGS: 1n << 15n,
18
- APM_TRACING_ENABLED: 1n << 19n
18
+ APM_TRACING_ENABLED: 1n << 19n,
19
+ APM_TRACING_SAMPLE_RULES: 1n << 29n
19
20
  }
@@ -15,6 +15,7 @@ function enable (config, appsec) {
15
15
  rc.updateCapabilities(RemoteConfigCapabilities.APM_TRACING_LOGS_INJECTION, true)
16
16
  rc.updateCapabilities(RemoteConfigCapabilities.APM_TRACING_SAMPLE_RATE, true)
17
17
  rc.updateCapabilities(RemoteConfigCapabilities.APM_TRACING_ENABLED, true)
18
+ rc.updateCapabilities(RemoteConfigCapabilities.APM_TRACING_SAMPLE_RULES, true)
18
19
 
19
20
  const activation = Activation.fromConfig(config)
20
21
 
@@ -3,7 +3,6 @@
3
3
  const fs = require('fs')
4
4
  const waf = require('./waf')
5
5
  const { ACKNOWLEDGED, ERROR } = require('./remote_config/apply_states')
6
- const blocking = require('./blocking')
7
6
 
8
7
  let defaultRules
9
8
 
@@ -20,10 +19,6 @@ function loadRules (config) {
20
19
  : require('./recommended.json')
21
20
 
22
21
  waf.init(defaultRules, config)
23
-
24
- if (defaultRules.actions) {
25
- blocking.updateBlockingConfiguration(defaultRules.actions.find(action => action.id === 'block'))
26
- }
27
22
  }
28
23
 
29
24
  function updateWafFromRC ({ toUnapply, toApply, toModify }) {
@@ -68,7 +63,7 @@ function updateWafFromRC ({ toUnapply, toApply, toModify }) {
68
63
  item.apply_state = ERROR
69
64
  item.apply_error = 'Multiple ruleset received in ASM_DD'
70
65
  } else {
71
- if (file && file.rules && file.rules.length) {
66
+ if (file?.rules?.length) {
72
67
  const { version, metadata, rules, processors, scanners } = file
73
68
 
74
69
  newRuleset = { version, metadata, rules, processors, scanners }
@@ -78,30 +73,23 @@ function updateWafFromRC ({ toUnapply, toApply, toModify }) {
78
73
  batch.add(item)
79
74
  }
80
75
  } else if (product === 'ASM') {
81
- let batchConfiguration = false
82
- if (file && file.rules_override && file.rules_override.length) {
83
- batchConfiguration = true
76
+ if (file?.rules_override?.length) {
84
77
  newRulesOverride.set(id, file.rules_override)
85
78
  }
86
79
 
87
- if (file && file.exclusions && file.exclusions.length) {
88
- batchConfiguration = true
80
+ if (file?.exclusions?.length) {
89
81
  newExclusions.set(id, file.exclusions)
90
82
  }
91
83
 
92
- if (file && file.custom_rules && file.custom_rules.length) {
93
- batchConfiguration = true
84
+ if (file?.custom_rules?.length) {
94
85
  newCustomRules.set(id, file.custom_rules)
95
86
  }
96
87
 
97
- if (file && file.actions && file.actions.length) {
88
+ if (file?.actions?.length) {
98
89
  newActions.set(id, file.actions)
99
90
  }
100
91
 
101
- // "actions" data is managed by tracer and not by waf
102
- if (batchConfiguration) {
103
- batch.add(item)
104
- }
92
+ batch.add(item)
105
93
  }
106
94
  }
107
95
 
@@ -112,7 +100,9 @@ function updateWafFromRC ({ toUnapply, toApply, toModify }) {
112
100
  newRuleset ||
113
101
  newRulesOverride.modified ||
114
102
  newExclusions.modified ||
115
- newCustomRules.modified) {
103
+ newCustomRules.modified ||
104
+ newActions.modified
105
+ ) {
116
106
  const payload = newRuleset || {}
117
107
 
118
108
  if (newRulesData.modified) {
@@ -127,6 +117,9 @@ function updateWafFromRC ({ toUnapply, toApply, toModify }) {
127
117
  if (newCustomRules.modified) {
128
118
  payload.custom_rules = concatArrays(newCustomRules)
129
119
  }
120
+ if (newActions.modified) {
121
+ payload.actions = concatArrays(newActions)
122
+ }
130
123
 
131
124
  try {
132
125
  waf.update(payload)
@@ -146,6 +139,9 @@ function updateWafFromRC ({ toUnapply, toApply, toModify }) {
146
139
  if (newCustomRules.modified) {
147
140
  appliedCustomRules = newCustomRules
148
141
  }
142
+ if (newActions.modified) {
143
+ appliedActions = newActions
144
+ }
149
145
  } catch (err) {
150
146
  newApplyState = ERROR
151
147
  newApplyError = err.toString()
@@ -156,11 +152,6 @@ function updateWafFromRC ({ toUnapply, toApply, toModify }) {
156
152
  config.apply_state = newApplyState
157
153
  if (newApplyError) config.apply_error = newApplyError
158
154
  }
159
-
160
- if (newActions.modified) {
161
- blocking.updateBlockingConfiguration(concatArrays(newActions).find(action => action.id === 'block'))
162
- appliedActions = newActions
163
- }
164
155
  }
165
156
 
166
157
  // A Map with a new prop `modified`, a bool that indicates if the Map was modified
@@ -242,7 +233,6 @@ function copyRulesData (rulesData) {
242
233
 
243
234
  function clearAllRules () {
244
235
  waf.destroy()
245
- blocking.updateBlockingConfiguration(undefined)
246
236
 
247
237
  defaultRules = undefined
248
238
 
@@ -3,17 +3,14 @@
3
3
  const { USER_ID } = require('../addresses')
4
4
  const waf = require('../waf')
5
5
  const { getRootSpan } = require('./utils')
6
- const { block } = require('../blocking')
6
+ const { block, getBlockingAction } = require('../blocking')
7
7
  const { storage } = require('../../../../datadog-core')
8
8
  const { setUserTags } = require('./set_user')
9
9
  const log = require('../../log')
10
10
 
11
11
  function isUserBlocked (user) {
12
12
  const actions = waf.run({ persistent: { [USER_ID]: user.id } })
13
-
14
- if (!actions) return false
15
-
16
- return actions.includes('block')
13
+ return !!getBlockingAction(actions)
17
14
  }
18
15
 
19
16
  function checkUserAndSetUser (tracer, user) {
@@ -3,6 +3,7 @@
3
3
  const log = require('../../log')
4
4
  const Reporter = require('../reporter')
5
5
  const addresses = require('../addresses')
6
+ const { getBlockingAction } = require('../blocking')
6
7
 
7
8
  // TODO: remove once ephemeral addresses are implemented
8
9
  const preventDuplicateAddresses = new Set([
@@ -60,7 +61,8 @@ class WAFContextWrapper {
60
61
  this.addressesToSkip = newAddressesToSkip
61
62
 
62
63
  const ruleTriggered = !!result.events?.length
63
- const blockTriggered = result.actions?.includes('block')
64
+
65
+ const blockTriggered = !!getBlockingAction(result.actions)
64
66
 
65
67
  Reporter.reportMetrics({
66
68
  duration: result.totalRuntime / 1e3,
@@ -4,7 +4,8 @@ const Writer = require('./writer')
4
4
  const {
5
5
  JEST_WORKER_COVERAGE_PAYLOAD_CODE,
6
6
  JEST_WORKER_TRACE_PAYLOAD_CODE,
7
- CUCUMBER_WORKER_TRACE_PAYLOAD_CODE
7
+ CUCUMBER_WORKER_TRACE_PAYLOAD_CODE,
8
+ MOCHA_WORKER_TRACE_PAYLOAD_CODE
8
9
  } = require('../../../plugins/util/test')
9
10
 
10
11
  function getInterprocessTraceCode () {
@@ -14,6 +15,9 @@ function getInterprocessTraceCode () {
14
15
  if (process.env.CUCUMBER_WORKER_ID) {
15
16
  return CUCUMBER_WORKER_TRACE_PAYLOAD_CODE
16
17
  }
18
+ if (process.env.MOCHA_WORKER_ID) {
19
+ return MOCHA_WORKER_TRACE_PAYLOAD_CODE
20
+ }
17
21
  return null
18
22
  }
19
23
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  const fs = require('fs')
4
4
  const os = require('os')
5
- const uuid = require('crypto-randomuuid')
5
+ const uuid = require('crypto-randomuuid') // we need to keep the old uuid dep because of cypress
6
6
  const URL = require('url').URL
7
7
  const log = require('./log')
8
8
  const pkg = require('./pkg')
@@ -170,6 +170,7 @@ class Config {
170
170
  // Configure the logger first so it can be used to warn about other configs
171
171
  this.debug = isTrue(coalesce(
172
172
  process.env.DD_TRACE_DEBUG,
173
+ process.env.OTEL_LOG_LEVEL && process.env.OTEL_LOG_LEVEL === 'debug',
173
174
  false
174
175
  ))
175
176
  this.logger = options.logger
@@ -288,15 +289,6 @@ class Config {
288
289
  )
289
290
 
290
291
  const sampler = {
291
- rules: coalesce(
292
- options.samplingRules,
293
- safeJsonParse(process.env.DD_TRACE_SAMPLING_RULES),
294
- []
295
- ).map(rule => {
296
- return remapify(rule, {
297
- sample_rate: 'sampleRate'
298
- })
299
- }),
300
292
  spanSamplingRules: coalesce(
301
293
  options.spanSamplingRules,
302
294
  safeJsonParse(maybeFile(process.env.DD_SPAN_SAMPLING_RULES_FILE)),
@@ -444,6 +436,7 @@ class Config {
444
436
  this._setValue(defaults, 'appsec.enabled', undefined)
445
437
  this._setValue(defaults, 'appsec.obfuscatorKeyRegex', defaultWafObfuscatorKeyRegex)
446
438
  this._setValue(defaults, 'appsec.obfuscatorValueRegex', defaultWafObfuscatorValueRegex)
439
+ this._setValue(defaults, 'appsec.rasp.enabled', false)
447
440
  this._setValue(defaults, 'appsec.rateLimit', 100)
448
441
  this._setValue(defaults, 'appsec.rules', undefined)
449
442
  this._setValue(defaults, 'appsec.sca.enabled', null)
@@ -485,9 +478,12 @@ class Config {
485
478
  this._setValue(defaults, 'peerServiceMapping', {})
486
479
  this._setValue(defaults, 'plugins', true)
487
480
  this._setValue(defaults, 'port', '8126')
488
- this._setValue(defaults, 'profiling.enabled', false)
481
+ this._setValue(defaults, 'profiling.enabled', undefined)
489
482
  this._setValue(defaults, 'profiling.exporters', 'agent')
490
483
  this._setValue(defaults, 'profiling.sourceMap', true)
484
+ this._setValue(defaults, 'profiling.ssi', false)
485
+ this._setValue(defaults, 'profiling.heuristicsEnabled', false)
486
+ this._setValue(defaults, 'profiling.longLivedThreshold', undefined)
491
487
  this._setValue(defaults, 'protocolVersion', '0.4')
492
488
  this._setValue(defaults, 'queryStringObfuscation', qsRegex)
493
489
  this._setValue(defaults, 'remoteConfig.enabled', true)
@@ -496,6 +492,7 @@ class Config {
496
492
  this._setValue(defaults, 'runtimeMetrics', false)
497
493
  this._setValue(defaults, 'sampleRate', undefined)
498
494
  this._setValue(defaults, 'sampler.rateLimit', undefined)
495
+ this._setValue(defaults, 'sampler.rules', [])
499
496
  this._setValue(defaults, 'scope', undefined)
500
497
  this._setValue(defaults, 'service', service)
501
498
  this._setValue(defaults, 'site', 'datadoghq.com')
@@ -531,6 +528,7 @@ class Config {
531
528
  DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP,
532
529
  DD_APPSEC_RULES,
533
530
  DD_APPSEC_SCA_ENABLED,
531
+ DD_APPSEC_RASP_ENABLED,
534
532
  DD_APPSEC_TRACE_RATE_LIMIT,
535
533
  DD_APPSEC_WAF_TIMEOUT,
536
534
  DD_DATA_STREAMS_ENABLED,
@@ -558,6 +556,7 @@ class Config {
558
556
  DD_PROFILING_ENABLED,
559
557
  DD_PROFILING_EXPORTERS,
560
558
  DD_PROFILING_SOURCE_MAP,
559
+ DD_INTERNAL_PROFILING_LONG_LIVED_THRESHOLD,
561
560
  DD_REMOTE_CONFIGURATION_ENABLED,
562
561
  DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS,
563
562
  DD_RUNTIME_METRICS_ENABLED,
@@ -590,6 +589,7 @@ class Config {
590
589
  DD_TRACE_REMOVE_INTEGRATION_SERVICE_NAMES_ENABLED,
591
590
  DD_TRACE_REPORT_HOSTNAME,
592
591
  DD_TRACE_SAMPLE_RATE,
592
+ DD_TRACE_SAMPLING_RULES,
593
593
  DD_TRACE_SCOPE,
594
594
  DD_TRACE_SPAN_ATTRIBUTE_SCHEMA,
595
595
  DD_TRACE_STARTUP_LOGS,
@@ -607,6 +607,7 @@ class Config {
607
607
 
608
608
  const tags = {}
609
609
  const env = this._env = {}
610
+ this._envUnprocessed = {}
610
611
 
611
612
  tagger.add(tags, OTEL_RESOURCE_ATTRIBUTES, true)
612
613
  tagger.add(tags, DD_TAGS)
@@ -614,15 +615,20 @@ class Config {
614
615
  tagger.add(tags, DD_TRACE_GLOBAL_TAGS)
615
616
 
616
617
  this._setValue(env, 'appsec.blockedTemplateHtml', maybeFile(DD_APPSEC_HTTP_BLOCKED_TEMPLATE_HTML))
618
+ this._envUnprocessed['appsec.blockedTemplateHtml'] = DD_APPSEC_HTTP_BLOCKED_TEMPLATE_HTML
617
619
  this._setValue(env, 'appsec.blockedTemplateJson', maybeFile(DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON))
620
+ this._envUnprocessed['appsec.blockedTemplateJson'] = DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON
618
621
  this._setBoolean(env, 'appsec.enabled', DD_APPSEC_ENABLED)
619
622
  this._setString(env, 'appsec.obfuscatorKeyRegex', DD_APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP)
620
623
  this._setString(env, 'appsec.obfuscatorValueRegex', DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP)
624
+ this._setBoolean(env, 'appsec.rasp.enabled', DD_APPSEC_RASP_ENABLED)
621
625
  this._setValue(env, 'appsec.rateLimit', maybeInt(DD_APPSEC_TRACE_RATE_LIMIT))
626
+ this._envUnprocessed['appsec.rateLimit'] = DD_APPSEC_TRACE_RATE_LIMIT
622
627
  this._setString(env, 'appsec.rules', DD_APPSEC_RULES)
623
628
  // DD_APPSEC_SCA_ENABLED is never used locally, but only sent to the backend
624
629
  this._setBoolean(env, 'appsec.sca.enabled', DD_APPSEC_SCA_ENABLED)
625
630
  this._setValue(env, 'appsec.wafTimeout', maybeInt(DD_APPSEC_WAF_TIMEOUT))
631
+ this._envUnprocessed['appsec.wafTimeout'] = DD_APPSEC_WAF_TIMEOUT
626
632
  this._setBoolean(env, 'clientIpEnabled', DD_TRACE_CLIENT_IP_ENABLED)
627
633
  this._setString(env, 'clientIpHeader', DD_TRACE_CLIENT_IP_HEADER)
628
634
  this._setString(env, 'dbmPropagationMode', DD_DBM_PROPAGATION_MODE)
@@ -635,13 +641,16 @@ class Config {
635
641
  this._setBoolean(env, 'experimental.runtimeId', DD_TRACE_EXPERIMENTAL_RUNTIME_ID_ENABLED)
636
642
  if (AWS_LAMBDA_FUNCTION_NAME) this._setValue(env, 'flushInterval', 0)
637
643
  this._setValue(env, 'flushMinSpans', maybeInt(DD_TRACE_PARTIAL_FLUSH_MIN_SPANS))
644
+ this._envUnprocessed.flushMinSpans = DD_TRACE_PARTIAL_FLUSH_MIN_SPANS
638
645
  this._setBoolean(env, 'gitMetadataEnabled', DD_TRACE_GIT_METADATA_ENABLED)
639
646
  this._setArray(env, 'headerTags', DD_TRACE_HEADER_TAGS)
640
647
  this._setString(env, 'hostname', coalesce(DD_AGENT_HOST, DD_TRACE_AGENT_HOSTNAME))
641
648
  this._setBoolean(env, 'iast.deduplicationEnabled', DD_IAST_DEDUPLICATION_ENABLED)
642
649
  this._setBoolean(env, 'iast.enabled', DD_IAST_ENABLED)
643
650
  this._setValue(env, 'iast.maxConcurrentRequests', maybeInt(DD_IAST_MAX_CONCURRENT_REQUESTS))
651
+ this._envUnprocessed['iast.maxConcurrentRequests'] = DD_IAST_MAX_CONCURRENT_REQUESTS
644
652
  this._setValue(env, 'iast.maxContextOperations', maybeInt(DD_IAST_MAX_CONTEXT_OPERATIONS))
653
+ this._envUnprocessed['iast.maxContextOperations'] = DD_IAST_MAX_CONTEXT_OPERATIONS
645
654
  this._setBoolean(env, 'iast.redactionEnabled', DD_IAST_REDACTION_ENABLED && !isFalse(DD_IAST_REDACTION_ENABLED))
646
655
  this._setString(env, 'iast.redactionNamePattern', DD_IAST_REDACTION_NAME_PATTERN)
647
656
  this._setString(env, 'iast.redactionValuePattern', DD_IAST_REDACTION_VALUE_PATTERN)
@@ -649,20 +658,34 @@ class Config {
649
658
  if (iastRequestSampling > -1 && iastRequestSampling < 101) {
650
659
  this._setValue(env, 'iast.requestSampling', iastRequestSampling)
651
660
  }
661
+ this._envUnprocessed['iast.requestSampling'] = DD_IAST_REQUEST_SAMPLING
652
662
  this._setString(env, 'iast.telemetryVerbosity', DD_IAST_TELEMETRY_VERBOSITY)
653
663
  this._setBoolean(env, 'isGCPFunction', getIsGCPFunction())
654
664
  this._setBoolean(env, 'logInjection', DD_LOGS_INJECTION)
655
665
  this._setBoolean(env, 'openAiLogsEnabled', DD_OPENAI_LOGS_ENABLED)
656
666
  this._setValue(env, 'openaiSpanCharLimit', maybeInt(DD_OPENAI_SPAN_CHAR_LIMIT))
667
+ this._envUnprocessed.openaiSpanCharLimit = DD_OPENAI_SPAN_CHAR_LIMIT
657
668
  if (DD_TRACE_PEER_SERVICE_MAPPING) {
658
669
  this._setValue(env, 'peerServiceMapping', fromEntries(
659
- process.env.DD_TRACE_PEER_SERVICE_MAPPING.split(',').map(x => x.trim().split(':'))
670
+ DD_TRACE_PEER_SERVICE_MAPPING.split(',').map(x => x.trim().split(':'))
660
671
  ))
672
+ this._envUnprocessed.peerServiceMapping = DD_TRACE_PEER_SERVICE_MAPPING
661
673
  }
662
674
  this._setString(env, 'port', DD_TRACE_AGENT_PORT)
663
675
  this._setBoolean(env, 'profiling.enabled', coalesce(DD_EXPERIMENTAL_PROFILING_ENABLED, DD_PROFILING_ENABLED))
664
676
  this._setString(env, 'profiling.exporters', DD_PROFILING_EXPORTERS)
665
677
  this._setBoolean(env, 'profiling.sourceMap', DD_PROFILING_SOURCE_MAP && !isFalse(DD_PROFILING_SOURCE_MAP))
678
+ if (DD_PROFILING_ENABLED === 'auto' || DD_INJECTION_ENABLED) {
679
+ this._setBoolean(env, 'profiling.ssi', true)
680
+ if (DD_PROFILING_ENABLED === 'auto' || DD_INJECTION_ENABLED.split(',').includes('profiler')) {
681
+ this._setBoolean(env, 'profiling.heuristicsEnabled', true)
682
+ }
683
+ if (DD_INTERNAL_PROFILING_LONG_LIVED_THRESHOLD) {
684
+ // This is only used in testing to not have to wait 30s
685
+ this._setValue(env, 'profiling.longLivedThreshold', Number(DD_INTERNAL_PROFILING_LONG_LIVED_THRESHOLD))
686
+ }
687
+ }
688
+
666
689
  this._setString(env, 'protocolVersion', DD_TRACE_AGENT_PROTOCOL_VERSION)
667
690
  this._setString(env, 'queryStringObfuscation', DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP)
668
691
  this._setBoolean(env, 'remoteConfig.enabled', coalesce(
@@ -670,6 +693,7 @@ class Config {
670
693
  !this._isInServerlessEnvironment()
671
694
  ))
672
695
  this._setValue(env, 'remoteConfig.pollInterval', maybeFloat(DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS))
696
+ this._envUnprocessed['remoteConfig.pollInterval'] = DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS
673
697
  this._setBoolean(env, 'reportHostname', DD_TRACE_REPORT_HOSTNAME)
674
698
  // only used to explicitly set runtimeMetrics to false
675
699
  const otelSetRuntimeMetrics = String(OTEL_METRICS_EXPORTER).toLowerCase() === 'none'
@@ -687,11 +711,14 @@ class Config {
687
711
  }
688
712
  this._setUnit(env, 'sampleRate', DD_TRACE_SAMPLE_RATE || OTEL_TRACES_SAMPLER_MAPPING[OTEL_TRACES_SAMPLER])
689
713
  this._setValue(env, 'sampler.rateLimit', DD_TRACE_RATE_LIMIT)
714
+ this._setSamplingRule(env, 'sampler.rules', safeJsonParse(DD_TRACE_SAMPLING_RULES))
715
+ this._envUnprocessed['sampler.rules'] = DD_TRACE_SAMPLING_RULES
690
716
  this._setString(env, 'scope', DD_TRACE_SCOPE)
691
717
  this._setString(env, 'service', DD_SERVICE || DD_SERVICE_NAME || tags.service || OTEL_SERVICE_NAME)
692
718
  this._setString(env, 'site', DD_SITE)
693
719
  if (DD_TRACE_SPAN_ATTRIBUTE_SCHEMA) {
694
720
  this._setString(env, 'spanAttributeSchema', validateNamingVersion(DD_TRACE_SPAN_ATTRIBUTE_SCHEMA))
721
+ this._envUnprocessed.spanAttributeSchema = DD_TRACE_SPAN_ATTRIBUTE_SCHEMA
695
722
  }
696
723
  this._setBoolean(env, 'spanRemoveIntegrationFromService', DD_TRACE_REMOVE_INTEGRATION_SERVICE_NAMES_ENABLED)
697
724
  this._setBoolean(env, 'startupLogs', DD_TRACE_STARTUP_LOGS)
@@ -706,10 +733,9 @@ class Config {
706
733
  this._setBoolean(env, 'telemetry.debug', DD_TELEMETRY_DEBUG)
707
734
  this._setBoolean(env, 'telemetry.dependencyCollection', DD_TELEMETRY_DEPENDENCY_COLLECTION_ENABLED)
708
735
  this._setValue(env, 'telemetry.heartbeatInterval', maybeInt(Math.floor(DD_TELEMETRY_HEARTBEAT_INTERVAL * 1000)))
736
+ this._envUnprocessed['telemetry.heartbeatInterval'] = DD_TELEMETRY_HEARTBEAT_INTERVAL * 1000
709
737
  const hasTelemetryLogsUsingFeatures =
710
- isTrue(DD_IAST_ENABLED) ||
711
- isTrue(DD_PROFILING_ENABLED) ||
712
- (typeof DD_INJECTION_ENABLED === 'string' && DD_INJECTION_ENABLED.split(',').includes('profiling'))
738
+ env['iast.enabled'] || env['profiling.enabled'] || env['profiling.heuristicsEnabled']
713
739
  ? true
714
740
  : undefined
715
741
  this._setBoolean(env, 'telemetry.logCollection', coalesce(DD_TELEMETRY_LOG_COLLECTION_ENABLED,
@@ -724,19 +750,25 @@ class Config {
724
750
  _applyOptions (options) {
725
751
  const opts = this._options = this._options || {}
726
752
  const tags = {}
753
+ this._optsUnprocessed = {}
727
754
 
728
755
  options = this.options = Object.assign({ ingestion: {} }, options, opts)
729
756
 
730
757
  tagger.add(tags, options.tags)
731
758
 
732
759
  this._setValue(opts, 'appsec.blockedTemplateHtml', maybeFile(options.appsec.blockedTemplateHtml))
760
+ this._optsUnprocessed['appsec.blockedTemplateHtml'] = options.appsec.blockedTemplateHtml
733
761
  this._setValue(opts, 'appsec.blockedTemplateJson', maybeFile(options.appsec.blockedTemplateJson))
762
+ this._optsUnprocessed['appsec.blockedTemplateJson'] = options.appsec.blockedTemplateJson
734
763
  this._setBoolean(opts, 'appsec.enabled', options.appsec.enabled)
735
764
  this._setString(opts, 'appsec.obfuscatorKeyRegex', options.appsec.obfuscatorKeyRegex)
736
765
  this._setString(opts, 'appsec.obfuscatorValueRegex', options.appsec.obfuscatorValueRegex)
766
+ this._setBoolean(opts, 'appsec.rasp.enabled', options.appsec.rasp?.enabled)
737
767
  this._setValue(opts, 'appsec.rateLimit', maybeInt(options.appsec.rateLimit))
768
+ this._optsUnprocessed['appsec.rateLimit'] = options.appsec.rateLimit
738
769
  this._setString(opts, 'appsec.rules', options.appsec.rules)
739
770
  this._setValue(opts, 'appsec.wafTimeout', maybeInt(options.appsec.wafTimeout))
771
+ this._optsUnprocessed['appsec.wafTimeout'] = options.appsec.wafTimeout
740
772
  this._setBoolean(opts, 'clientIpEnabled', options.clientIpEnabled)
741
773
  this._setString(opts, 'clientIpHeader', options.clientIpHeader)
742
774
  this._setString(opts, 'dbmPropagationMode', options.dbmPropagationMode)
@@ -751,22 +783,26 @@ class Config {
751
783
  this._setString(opts, 'experimental.exporter', options.experimental && options.experimental.exporter)
752
784
  this._setBoolean(opts, 'experimental.runtimeId', options.experimental && options.experimental.runtimeId)
753
785
  this._setValue(opts, 'flushInterval', maybeInt(options.flushInterval))
786
+ this._optsUnprocessed.flushInterval = options.flushInterval
754
787
  this._setValue(opts, 'flushMinSpans', maybeInt(options.flushMinSpans))
788
+ this._optsUnprocessed.flushMinSpans = options.flushMinSpans
755
789
  this._setArray(opts, 'headerTags', options.headerTags)
756
790
  this._setString(opts, 'hostname', options.hostname)
757
791
  this._setBoolean(opts, 'iast.deduplicationEnabled', options.iastOptions && options.iastOptions.deduplicationEnabled)
758
792
  this._setBoolean(opts, 'iast.enabled',
759
793
  options.iastOptions && (options.iastOptions === true || options.iastOptions.enabled === true))
760
- const iastRequestSampling = maybeInt(options.iastOptions?.requestSampling)
761
794
  this._setValue(opts, 'iast.maxConcurrentRequests',
762
795
  maybeInt(options.iastOptions?.maxConcurrentRequests))
763
- this._setValue(opts, 'iast.maxContextOperations',
764
- maybeInt(options.iastOptions && options.iastOptions.maxContextOperations))
765
- this._setBoolean(opts, 'iast.redactionEnabled', options.iastOptions && options.iastOptions.redactionEnabled)
796
+ this._optsUnprocessed['iast.maxConcurrentRequests'] = options.iastOptions?.maxConcurrentRequests
797
+ this._setValue(opts, 'iast.maxContextOperations', maybeInt(options.iastOptions?.maxContextOperations))
798
+ this._optsUnprocessed['iast.maxContextOperations'] = options.iastOptions?.maxContextOperations
799
+ this._setBoolean(opts, 'iast.redactionEnabled', options.iastOptions?.redactionEnabled)
766
800
  this._setString(opts, 'iast.redactionNamePattern', options.iastOptions?.redactionNamePattern)
767
801
  this._setString(opts, 'iast.redactionValuePattern', options.iastOptions?.redactionValuePattern)
802
+ const iastRequestSampling = maybeInt(options.iastOptions?.requestSampling)
768
803
  if (iastRequestSampling > -1 && iastRequestSampling < 101) {
769
804
  this._setValue(opts, 'iast.requestSampling', iastRequestSampling)
805
+ this._optsUnprocessed['iast.requestSampling'] = options.iastOptions?.requestSampling
770
806
  }
771
807
  this._setString(opts, 'iast.telemetryVerbosity', options.iastOptions && options.iastOptions.telemetryVerbosity)
772
808
  this._setBoolean(opts, 'isCiVisibility', options.isCiVisibility)
@@ -780,16 +816,19 @@ class Config {
780
816
  this._setString(opts, 'protocolVersion', options.protocolVersion)
781
817
  if (options.remoteConfig) {
782
818
  this._setValue(opts, 'remoteConfig.pollInterval', maybeFloat(options.remoteConfig.pollInterval))
819
+ this._optsUnprocessed['remoteConfig.pollInterval'] = options.remoteConfig.pollInterval
783
820
  }
784
821
  this._setBoolean(opts, 'reportHostname', options.reportHostname)
785
822
  this._setBoolean(opts, 'runtimeMetrics', options.runtimeMetrics)
786
823
  this._setUnit(opts, 'sampleRate', coalesce(options.sampleRate, options.ingestion.sampleRate))
787
824
  const ingestion = options.ingestion || {}
788
825
  this._setValue(opts, 'sampler.rateLimit', coalesce(options.rateLimit, ingestion.rateLimit))
826
+ this._setSamplingRule(opts, 'sampler.rules', options.samplingRules)
789
827
  this._setString(opts, 'service', options.service || tags.service)
790
828
  this._setString(opts, 'site', options.site)
791
829
  if (options.spanAttributeSchema) {
792
830
  this._setString(opts, 'spanAttributeSchema', validateNamingVersion(options.spanAttributeSchema))
831
+ this._optsUnprocessed.spanAttributeSchema = options.spanAttributeSchema
793
832
  }
794
833
  this._setBoolean(opts, 'spanRemoveIntegrationFromService', options.spanRemoveIntegrationFromService)
795
834
  this._setBoolean(opts, 'startupLogs', options.startupLogs)
@@ -918,6 +957,7 @@ class Config {
918
957
 
919
958
  _applyRemote (options) {
920
959
  const opts = this._remote = this._remote || {}
960
+ this._remoteUnprocessed = {}
921
961
  const tags = {}
922
962
  const headerTags = options.tracing_header_tags
923
963
  ? options.tracing_header_tags.map(tag => {
@@ -933,6 +973,18 @@ class Config {
933
973
  this._setArray(opts, 'headerTags', headerTags)
934
974
  this._setTags(opts, 'tags', tags)
935
975
  this._setBoolean(opts, 'tracing', options.tracing_enabled)
976
+ // ignore tags for now since rc sampling rule tags format is not supported
977
+ this._setSamplingRule(opts, 'sampler.rules', this._ignoreTags(options.tracing_sampling_rules))
978
+ this._remoteUnprocessed['sampler.rules'] = options.tracing_sampling_rules
979
+ }
980
+
981
+ _ignoreTags (samplingRules) {
982
+ if (samplingRules) {
983
+ for (const rule of samplingRules) {
984
+ delete rule.tags
985
+ }
986
+ }
987
+ return samplingRules
936
988
  }
937
989
 
938
990
  _setBoolean (obj, name, value) {
@@ -959,12 +1011,12 @@ class Config {
959
1011
  }
960
1012
 
961
1013
  _setArray (obj, name, value) {
962
- if (value === null || value === undefined) {
1014
+ if (value == null) {
963
1015
  return this._setValue(obj, name, null)
964
1016
  }
965
1017
 
966
1018
  if (typeof value === 'string') {
967
- value = value && value.split(',')
1019
+ value = value.split(',')
968
1020
  }
969
1021
 
970
1022
  if (Array.isArray(value)) {
@@ -972,6 +1024,25 @@ class Config {
972
1024
  }
973
1025
  }
974
1026
 
1027
+ _setSamplingRule (obj, name, value) {
1028
+ if (value == null) {
1029
+ return this._setValue(obj, name, null)
1030
+ }
1031
+
1032
+ if (typeof value === 'string') {
1033
+ value = value.split(',')
1034
+ }
1035
+
1036
+ if (Array.isArray(value)) {
1037
+ value = value.map(rule => {
1038
+ return remapify(rule, {
1039
+ sample_rate: 'sampleRate'
1040
+ })
1041
+ })
1042
+ this._setValue(obj, name, value)
1043
+ }
1044
+ }
1045
+
975
1046
  _setString (obj, name, value) {
976
1047
  obj[name] = value ? String(value) : undefined // unset for empty strings
977
1048
  }
@@ -997,18 +1068,21 @@ class Config {
997
1068
  _merge () {
998
1069
  const containers = [this._remote, this._options, this._env, this._calculated, this._defaults]
999
1070
  const origins = ['remote_config', 'code', 'env_var', 'calculated', 'default']
1071
+ const unprocessedValues = [this._remoteUnprocessed, this._optsUnprocessed, this._envUnprocessed, {}, {}]
1000
1072
  const changes = []
1001
1073
 
1002
1074
  for (const name in this._defaults) {
1003
1075
  for (let i = 0; i < containers.length; i++) {
1004
1076
  const container = containers[i]
1005
1077
  const origin = origins[i]
1078
+ const unprocessed = unprocessedValues[i]
1006
1079
 
1007
1080
  if ((container[name] !== null && container[name] !== undefined) || container === this._defaults) {
1008
1081
  if (get(this, name) === container[name] && has(this, name)) break
1009
1082
 
1010
- const value = container[name]
1083
+ let value = container[name]
1011
1084
  set(this, name, value)
1085
+ value = unprocessed[name] || value
1012
1086
 
1013
1087
  changes.push({ name, value, origin })
1014
1088
 
@@ -1026,6 +1100,7 @@ function maybeInt (number) {
1026
1100
  const parsed = parseInt(number)
1027
1101
  return isNaN(parsed) ? undefined : parsed
1028
1102
  }
1103
+
1029
1104
  function maybeFloat (number) {
1030
1105
  const parsed = parseFloat(number)
1031
1106
  return isNaN(parsed) ? undefined : parsed
@@ -15,6 +15,8 @@ module.exports = {
15
15
  SAMPLING_MECHANISM_MANUAL: 4,
16
16
  SAMPLING_MECHANISM_APPSEC: 5,
17
17
  SAMPLING_MECHANISM_SPAN: 8,
18
+ SAMPLING_MECHANISM_REMOTE_USER: 11,
19
+ SAMPLING_MECHANISM_REMOTE_DYNAMIC: 12,
18
20
  SPAN_SAMPLING_MECHANISM: '_dd.span_sampling.mechanism',
19
21
  SPAN_SAMPLING_RULE_RATE: '_dd.span_sampling.rule_rate',
20
22
  SPAN_SAMPLING_MAX_PER_SECOND: '_dd.span_sampling.max_per_second',
@@ -120,7 +120,7 @@ class AgentEncoder {
120
120
  this._encodeMap(bytes, span.metrics)
121
121
  if (span.meta_struct) {
122
122
  this._encodeString(bytes, 'meta_struct')
123
- this._encodeObject(bytes, span.meta_struct)
123
+ this._encodeMetaStruct(bytes, span.meta_struct)
124
124
  }
125
125
  }
126
126
  }
@@ -271,12 +271,48 @@ class AgentEncoder {
271
271
  }
272
272
  }
273
273
 
274
+ _encodeMetaStruct (bytes, value) {
275
+ const keys = Array.isArray(value) ? [] : Object.keys(value)
276
+ const validKeys = keys.filter(key => {
277
+ const v = value[key]
278
+ return typeof v === 'string' ||
279
+ typeof v === 'number' ||
280
+ (v !== null && typeof v === 'object')
281
+ })
282
+
283
+ this._encodeMapPrefix(bytes, validKeys.length)
284
+
285
+ for (const key of validKeys) {
286
+ const v = value[key]
287
+ this._encodeString(bytes, key)
288
+ this._encodeObjectAsByteArray(bytes, v)
289
+ }
290
+ }
291
+
292
+ _encodeObjectAsByteArray (bytes, value) {
293
+ const prefixLength = 5
294
+ const offset = bytes.length
295
+
296
+ bytes.reserve(prefixLength)
297
+ bytes.length += prefixLength
298
+
299
+ this._encodeObject(bytes, value)
300
+
301
+ // we should do it after encoding the object to know the real length
302
+ const length = bytes.length - offset - prefixLength
303
+ bytes.buffer[offset] = 0xc6
304
+ bytes.buffer[offset + 1] = length >> 24
305
+ bytes.buffer[offset + 2] = length >> 16
306
+ bytes.buffer[offset + 3] = length >> 8
307
+ bytes.buffer[offset + 4] = length
308
+ }
309
+
274
310
  _encodeObject (bytes, value, circularReferencesDetector = new Set()) {
275
311
  circularReferencesDetector.add(value)
276
312
  if (Array.isArray(value)) {
277
- return this._encodeObjectAsArray(bytes, value, circularReferencesDetector)
313
+ this._encodeObjectAsArray(bytes, value, circularReferencesDetector)
278
314
  } else if (value !== null && typeof value === 'object') {
279
- return this._encodeObjectAsMap(bytes, value, circularReferencesDetector)
315
+ this._encodeObjectAsMap(bytes, value, circularReferencesDetector)
280
316
  } else if (typeof value === 'string' || typeof value === 'number') {
281
317
  this._encodeValue(bytes, value)
282
318
  }
@@ -284,16 +320,19 @@ class AgentEncoder {
284
320
 
285
321
  _encodeObjectAsMap (bytes, value, circularReferencesDetector) {
286
322
  const keys = Object.keys(value)
287
- const validKeys = keys.filter(key =>
288
- typeof value[key] === 'string' ||
289
- typeof value[key] === 'number' ||
290
- (value[key] !== null && typeof value[key] === 'object' && !circularReferencesDetector.has(value[key])))
323
+ const validKeys = keys.filter(key => {
324
+ const v = value[key]
325
+ return typeof v === 'string' ||
326
+ typeof v === 'number' ||
327
+ (v !== null && typeof v === 'object' && !circularReferencesDetector.has(v))
328
+ })
291
329
 
292
330
  this._encodeMapPrefix(bytes, validKeys.length)
293
331
 
294
332
  for (const key of validKeys) {
333
+ const v = value[key]
295
334
  this._encodeString(bytes, key)
296
- this._encodeObject(bytes, value[key], circularReferencesDetector)
335
+ this._encodeObject(bytes, v, circularReferencesDetector)
297
336
  }
298
337
  }
299
338
 
@@ -19,6 +19,7 @@ module.exports = name => {
19
19
  return require('./ci-visibility/exporters/agent-proxy')
20
20
  case exporters.JEST_WORKER:
21
21
  case exporters.CUCUMBER_WORKER:
22
+ case exporters.MOCHA_WORKER:
22
23
  return require('./ci-visibility/exporters/test-worker')
23
24
  default:
24
25
  return inAWSLambda && !usingLambdaExtension ? require('./exporters/log') : require('./exporters/agent')