dd-trace 4.40.0 → 4.42.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 (74) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/ext/exporters.d.ts +1 -1
  3. package/index.d.ts +54 -1
  4. package/init.js +40 -1
  5. package/initialize.mjs +8 -5
  6. package/package.json +24 -20
  7. package/packages/datadog-core/src/storage/index.js +1 -10
  8. package/packages/datadog-esbuild/index.js +5 -1
  9. package/packages/datadog-instrumentations/src/aws-sdk.js +2 -1
  10. package/packages/datadog-instrumentations/src/cucumber.js +76 -34
  11. package/packages/datadog-instrumentations/src/helpers/hook.js +8 -3
  12. package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -0
  13. package/packages/datadog-instrumentations/src/helpers/instrument.js +4 -3
  14. package/packages/datadog-instrumentations/src/helpers/register.js +56 -5
  15. package/packages/datadog-instrumentations/src/http/server.js +98 -0
  16. package/packages/datadog-instrumentations/src/mocha/main.js +12 -1
  17. package/packages/datadog-instrumentations/src/mocha/utils.js +58 -14
  18. package/packages/datadog-instrumentations/src/mocha/worker.js +1 -0
  19. package/packages/datadog-instrumentations/src/playwright.js +1 -1
  20. package/packages/datadog-instrumentations/src/undici.js +18 -0
  21. package/packages/datadog-instrumentations/src/vitest.js +303 -0
  22. package/packages/datadog-plugin-aws-sdk/src/base.js +8 -1
  23. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +9 -3
  24. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +6 -1
  25. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +23 -5
  26. package/packages/datadog-plugin-child_process/src/index.js +1 -1
  27. package/packages/datadog-plugin-cucumber/src/index.js +24 -1
  28. package/packages/datadog-plugin-mocha/src/index.js +25 -4
  29. package/packages/datadog-plugin-openai/src/index.js +52 -30
  30. package/packages/datadog-plugin-openai/src/token-estimator.js +20 -0
  31. package/packages/datadog-plugin-undici/src/index.js +12 -0
  32. package/packages/datadog-plugin-vitest/src/index.js +156 -0
  33. package/packages/dd-trace/src/appsec/blocking.js +4 -0
  34. package/packages/dd-trace/src/appsec/channels.js +1 -0
  35. package/packages/dd-trace/src/appsec/iast/path-line.js +2 -19
  36. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +4 -0
  37. package/packages/dd-trace/src/appsec/index.js +45 -11
  38. package/packages/dd-trace/src/appsec/rasp.js +32 -5
  39. package/packages/dd-trace/src/appsec/recommended.json +208 -3
  40. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +1 -0
  41. package/packages/dd-trace/src/appsec/remote_config/index.js +2 -0
  42. package/packages/dd-trace/src/appsec/reporter.js +64 -20
  43. package/packages/dd-trace/src/appsec/sdk/track_event.js +3 -0
  44. package/packages/dd-trace/src/appsec/stack_trace.js +90 -0
  45. package/packages/dd-trace/src/appsec/standalone.js +130 -0
  46. package/packages/dd-trace/src/appsec/telemetry.js +33 -1
  47. package/packages/dd-trace/src/appsec/waf/index.js +2 -2
  48. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +2 -2
  49. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +4 -2
  50. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -2
  51. package/packages/dd-trace/src/config.js +110 -40
  52. package/packages/dd-trace/src/constants.js +3 -1
  53. package/packages/dd-trace/src/datastreams/processor.js +2 -1
  54. package/packages/dd-trace/src/exporters/agent/index.js +2 -2
  55. package/packages/dd-trace/src/format.js +22 -2
  56. package/packages/dd-trace/src/opentelemetry/span.js +33 -7
  57. package/packages/dd-trace/src/opentracing/propagation/text_map.js +12 -0
  58. package/packages/dd-trace/src/opentracing/span.js +42 -1
  59. package/packages/dd-trace/src/opentracing/tracer.js +2 -2
  60. package/packages/dd-trace/src/plugins/ci_plugin.js +7 -0
  61. package/packages/dd-trace/src/plugins/index.js +3 -0
  62. package/packages/dd-trace/src/plugins/util/test.js +5 -1
  63. package/packages/dd-trace/src/priority_sampler.js +2 -5
  64. package/packages/dd-trace/src/profiling/profiler.js +1 -1
  65. package/packages/dd-trace/src/proxy.js +3 -1
  66. package/packages/dd-trace/src/rate_limiter.js +2 -2
  67. package/packages/dd-trace/src/service-naming/schemas/v0/web.js +4 -0
  68. package/packages/dd-trace/src/service-naming/schemas/v1/web.js +4 -0
  69. package/packages/dd-trace/src/span_stats.js +4 -3
  70. package/packages/dd-trace/src/tagger.js +10 -1
  71. package/packages/dd-trace/src/telemetry/init-telemetry.js +75 -0
  72. package/packages/dd-trace/src/tracer.js +2 -2
  73. package/packages/dd-trace/src/util.js +6 -1
  74. package/packages/datadog-core/src/storage/async_hooks.js +0 -49
@@ -31,7 +31,10 @@ function newStore () {
31
31
  return {
32
32
  [DD_TELEMETRY_REQUEST_METRICS]: {
33
33
  duration: 0,
34
- durationExt: 0
34
+ durationExt: 0,
35
+ raspDuration: 0,
36
+ raspDurationExt: 0,
37
+ raspEvalCount: 0
35
38
  }
36
39
  }
37
40
  }
@@ -76,6 +79,28 @@ function getOrCreateMetricTags (store, versionsTags) {
76
79
  return metricTags
77
80
  }
78
81
 
82
+ function updateRaspRequestsMetricTags (metrics, req, raspRuleType) {
83
+ if (!req) return
84
+
85
+ const store = getStore(req)
86
+
87
+ // it does not depend on whether telemetry is enabled or not
88
+ addRaspRequestMetrics(store, metrics)
89
+
90
+ if (!enabled) return
91
+
92
+ const tags = { rule_type: raspRuleType, waf_version: metrics.wafVersion }
93
+ appsecMetrics.count('appsec.rasp.rule.eval', tags).inc(1)
94
+
95
+ if (metrics.wafTimeout) {
96
+ appsecMetrics.count('appsec.rasp.timeout', tags).inc(1)
97
+ }
98
+
99
+ if (metrics.ruleTriggered) {
100
+ appsecMetrics.count('appsec.rasp.rule.match', tags).inc(1)
101
+ }
102
+ }
103
+
79
104
  function updateWafRequestsMetricTags (metrics, req) {
80
105
  if (!req) return
81
106
 
@@ -141,6 +166,12 @@ function addRequestMetrics (store, { duration, durationExt }) {
141
166
  store[DD_TELEMETRY_REQUEST_METRICS].durationExt += durationExt || 0
142
167
  }
143
168
 
169
+ function addRaspRequestMetrics (store, { duration, durationExt }) {
170
+ store[DD_TELEMETRY_REQUEST_METRICS].raspDuration += duration || 0
171
+ store[DD_TELEMETRY_REQUEST_METRICS].raspDurationExt += durationExt || 0
172
+ store[DD_TELEMETRY_REQUEST_METRICS].raspEvalCount++
173
+ }
174
+
144
175
  function getRequestMetrics (req) {
145
176
  if (req) {
146
177
  const store = getStore(req)
@@ -153,6 +184,7 @@ module.exports = {
153
184
  disable,
154
185
 
155
186
  updateWafRequestsMetricTags,
187
+ updateRaspRequestsMetricTags,
156
188
  incrementWafInitMetric,
157
189
  incrementWafUpdatesMetric,
158
190
  incrementWafRequestsMetric,
@@ -46,7 +46,7 @@ function update (newRules) {
46
46
  }
47
47
  }
48
48
 
49
- function run (data, req) {
49
+ function run (data, req, raspRuleType) {
50
50
  if (!req) {
51
51
  const store = storage.getStore()
52
52
  if (!store || !store.req) {
@@ -59,7 +59,7 @@ function run (data, req) {
59
59
 
60
60
  const wafContext = waf.wafManager.getWAFContext(req)
61
61
 
62
- return wafContext.run(data)
62
+ return wafContext.run(data, raspRuleType)
63
63
  }
64
64
 
65
65
  function disposeContext (req) {
@@ -19,7 +19,7 @@ class WAFContextWrapper {
19
19
  this.addressesToSkip = new Set()
20
20
  }
21
21
 
22
- run ({ persistent, ephemeral }) {
22
+ run ({ persistent, ephemeral }, raspRuleType) {
23
23
  const payload = {}
24
24
  let payloadHasData = false
25
25
  const inputs = {}
@@ -72,7 +72,7 @@ class WAFContextWrapper {
72
72
  blockTriggered,
73
73
  wafVersion: this.wafVersion,
74
74
  wafTimeout: result.timeout
75
- })
75
+ }, raspRuleType)
76
76
 
77
77
  if (ruleTriggered) {
78
78
  Reporter.reportAttack(JSON.stringify(result.events))
@@ -190,7 +190,8 @@ class CiVisibilityExporter extends AgentInfoExporter {
190
190
  requireGit,
191
191
  isEarlyFlakeDetectionEnabled,
192
192
  earlyFlakeDetectionNumRetries,
193
- earlyFlakeDetectionFaultyThreshold
193
+ earlyFlakeDetectionFaultyThreshold,
194
+ isFlakyTestRetriesEnabled
194
195
  } = remoteConfiguration
195
196
  return {
196
197
  isCodeCoverageEnabled,
@@ -199,7 +200,8 @@ class CiVisibilityExporter extends AgentInfoExporter {
199
200
  requireGit,
200
201
  isEarlyFlakeDetectionEnabled: isEarlyFlakeDetectionEnabled && this._config.isEarlyFlakeDetectionEnabled,
201
202
  earlyFlakeDetectionNumRetries,
202
- earlyFlakeDetectionFaultyThreshold
203
+ earlyFlakeDetectionFaultyThreshold,
204
+ isFlakyTestRetriesEnabled
203
205
  }
204
206
  }
205
207
 
@@ -93,7 +93,8 @@ function getLibraryConfiguration ({
93
93
  tests_skipping: isSuitesSkippingEnabled,
94
94
  itr_enabled: isItrEnabled,
95
95
  require_git: requireGit,
96
- early_flake_detection: earlyFlakeDetectionConfig
96
+ early_flake_detection: earlyFlakeDetectionConfig,
97
+ flaky_test_retries_enabled: isFlakyTestRetriesEnabled
97
98
  }
98
99
  }
99
100
  } = JSON.parse(res)
@@ -107,7 +108,8 @@ function getLibraryConfiguration ({
107
108
  earlyFlakeDetectionNumRetries:
108
109
  earlyFlakeDetectionConfig?.slow_test_retries?.['5s'] || DEFAULT_EARLY_FLAKE_DETECTION_NUM_RETRIES,
109
110
  earlyFlakeDetectionFaultyThreshold:
110
- earlyFlakeDetectionConfig?.faulty_session_threshold ?? DEFAULT_EARLY_FLAKE_DETECTION_ERROR_THRESHOLD
111
+ earlyFlakeDetectionConfig?.faulty_session_threshold ?? DEFAULT_EARLY_FLAKE_DETECTION_ERROR_THRESHOLD,
112
+ isFlakyTestRetriesEnabled
111
113
  }
112
114
 
113
115
  log.debug(() => `Remote settings: ${JSON.stringify(settings)}`)
@@ -26,50 +26,104 @@ const telemetryCounters = {
26
26
  'otel.env.invalid': {}
27
27
  }
28
28
 
29
- function getCounter (event, ddVar, otelVar, otelTracesSamplerArg) {
29
+ function getCounter (event, ddVar, otelVar) {
30
30
  const counters = telemetryCounters[event]
31
31
  const tags = []
32
+ const ddVarPrefix = 'config.datadog:'
33
+ const otelVarPrefix = 'config.opentelemetry:'
34
+ if (ddVar) {
35
+ ddVar = ddVarPrefix + ddVar
36
+ tags.push(ddVar)
37
+ }
38
+ if (otelVar) {
39
+ otelVar = otelVarPrefix + otelVar
40
+ tags.push(otelVar)
41
+ }
32
42
 
33
- if (ddVar) tags.push(ddVar)
34
- if (otelVar) tags.push(otelVar)
35
- if (otelTracesSamplerArg) tags.push(otelTracesSamplerArg)
36
-
37
- if (!(ddVar in counters)) counters[ddVar] = {}
43
+ if (!(otelVar in counters)) counters[otelVar] = {}
38
44
 
39
45
  const counter = tracerMetrics.count(event, tags)
40
- counters[ddVar][otelVar] = counter
46
+ counters[otelVar][ddVar] = counter
41
47
  return counter
42
48
  }
43
49
 
44
50
  const otelDdEnvMapping = {
45
- DD_TRACE_LOG_LEVEL: 'OTEL_LOG_LEVEL',
46
- DD_TRACE_PROPAGATION_STYLE: 'OTEL_PROPAGATORS',
47
- DD_SERVICE: 'OTEL_SERVICE_NAME',
48
- DD_TRACE_SAMPLE_RATE: 'OTEL_TRACES_SAMPLER',
49
- DD_TRACE_ENABLED: 'OTEL_TRACES_EXPORTER',
50
- DD_RUNTIME_METRICS_ENABLED: 'OTEL_METRICS_EXPORTER',
51
- DD_TAGS: 'OTEL_RESOURCE_ATTRIBUTES',
52
- DD_TRACE_OTEL_ENABLED: 'OTEL_SDK_DISABLED'
51
+ OTEL_LOG_LEVEL: 'DD_TRACE_LOG_LEVEL',
52
+ OTEL_PROPAGATORS: 'DD_TRACE_PROPAGATION_STYLE',
53
+ OTEL_SERVICE_NAME: 'DD_SERVICE',
54
+ OTEL_TRACES_SAMPLER: 'DD_TRACE_SAMPLE_RATE',
55
+ OTEL_TRACES_SAMPLER_ARG: 'DD_TRACE_SAMPLE_RATE',
56
+ OTEL_TRACES_EXPORTER: 'DD_TRACE_ENABLED',
57
+ OTEL_METRICS_EXPORTER: 'DD_RUNTIME_METRICS_ENABLED',
58
+ OTEL_RESOURCE_ATTRIBUTES: 'DD_TAGS',
59
+ OTEL_SDK_DISABLED: 'DD_TRACE_OTEL_ENABLED',
60
+ OTEL_LOGS_EXPORTER: undefined
53
61
  }
54
62
 
55
- const otelInvalidEnv = ['OTEL_LOGS_EXPORTER']
63
+ const VALID_PROPAGATION_STYLES = new Set(['datadog', 'tracecontext', 'b3', 'b3 single header', 'none'])
56
64
 
57
- function checkIfBothOtelAndDdEnvVarSet () {
58
- for (const [ddVar, otelVar] of Object.entries(otelDdEnvMapping)) {
59
- if (process.env[ddVar] && process.env[otelVar]) {
60
- log.warn(`both ${ddVar} and ${otelVar} environment variables are set`)
61
- getCounter('otel.env.hiding', ddVar, otelVar,
62
- otelVar === 'OTEL_TRACES_SAMPLER' &&
63
- process.env.OTEL_TRACES_SAMPLER_ARG
64
- ? 'OTEL_TRACES_SAMPLER_ARG'
65
- : undefined).inc()
65
+ const VALID_LOG_LEVELS = new Set(['debug', 'info', 'warn', 'error'])
66
+
67
+ function getFromOtelSamplerMap (otelTracesSampler, otelTracesSamplerArg) {
68
+ const OTEL_TRACES_SAMPLER_MAPPING = {
69
+ always_on: '1.0',
70
+ always_off: '0.0',
71
+ traceidratio: otelTracesSamplerArg,
72
+ parentbased_always_on: '1.0',
73
+ parentbased_always_off: '0.0',
74
+ parentbased_traceidratio: otelTracesSamplerArg
75
+ }
76
+ return OTEL_TRACES_SAMPLER_MAPPING[otelTracesSampler]
77
+ }
78
+
79
+ function validateOtelPropagators (propagators) {
80
+ if (!process.env.PROPAGATION_STYLE_EXTRACT &&
81
+ !process.env.PROPAGATION_STYLE_INJECT &&
82
+ !process.env.DD_TRACE_PROPAGATION_STYLE &&
83
+ process.env.OTEL_PROPAGATORS) {
84
+ for (const style in propagators) {
85
+ if (!VALID_PROPAGATION_STYLES.has(style)) {
86
+ log.warn('unexpected value for OTEL_PROPAGATORS environment variable')
87
+ getCounter('otel.env.invalid', 'DD_TRACE_PROPAGATION_STYLE', 'OTEL_PROPAGATORS').inc()
88
+ }
66
89
  }
67
90
  }
91
+ }
68
92
 
69
- for (const otelVar of otelInvalidEnv) {
70
- if (process.env[otelVar]) {
71
- log.warn(`${otelVar} is not supported by the Datadog SDK`)
72
- getCounter('otel.env.invalid', otelVar).inc()
93
+ function validateEnvVarType (envVar) {
94
+ const value = process.env[envVar]
95
+ switch (envVar) {
96
+ case 'OTEL_LOG_LEVEL':
97
+ return VALID_LOG_LEVELS.has(value)
98
+ case 'OTEL_PROPAGATORS':
99
+ case 'OTEL_RESOURCE_ATTRIBUTES':
100
+ case 'OTEL_SERVICE_NAME':
101
+ return typeof value === 'string'
102
+ case 'OTEL_TRACES_SAMPLER':
103
+ return getFromOtelSamplerMap(value, process.env.OTEL_TRACES_SAMPLER_ARG) !== undefined
104
+ case 'OTEL_TRACES_SAMPLER_ARG':
105
+ return !isNaN(parseFloat(value))
106
+ case 'OTEL_SDK_DISABLED':
107
+ return value.toLowerCase() === 'true' || value.toLowerCase() === 'false'
108
+ case 'OTEL_TRACES_EXPORTER':
109
+ case 'OTEL_METRICS_EXPORTER':
110
+ case 'OTEL_LOGS_EXPORTER':
111
+ return value.toLowerCase() === 'none'
112
+ default:
113
+ return false
114
+ }
115
+ }
116
+
117
+ function checkIfBothOtelAndDdEnvVarSet () {
118
+ for (const [otelEnvVar, ddEnvVar] of Object.entries(otelDdEnvMapping)) {
119
+ if (ddEnvVar && process.env[ddEnvVar] && process.env[otelEnvVar]) {
120
+ log.warn(`both ${ddEnvVar} and ${otelEnvVar} environment variables are set`)
121
+ getCounter('otel.env.hiding', ddEnvVar, otelEnvVar).inc()
122
+ }
123
+
124
+ if (process.env[otelEnvVar] && !validateEnvVarType(otelEnvVar)) {
125
+ log.warn(`unexpected value for ${otelEnvVar} environment variable`)
126
+ getCounter('otel.env.invalid', ddEnvVar, otelEnvVar).inc()
73
127
  }
74
128
  }
75
129
  }
@@ -80,9 +134,9 @@ const fromEntries = Object.fromEntries || (entries =>
80
134
  // eslint-disable-next-line max-len
81
135
  const qsRegex = '(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?|access_?|secret_?)key(?:_?id)?|token|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)(?:(?:\\s|%20)*(?:=|%3D)[^&]+|(?:"|%22)(?:\\s|%20)*(?::|%3A)(?:\\s|%20)*(?:"|%22)(?:%2[^2]|%[^2]|[^"%])+(?:"|%22))|bearer(?:\\s|%20)+[a-z0-9\\._\\-]+|token(?::|%3A)[a-z0-9]{13}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L](?:[\\w=-]|%3D)+\\.ey[I-L](?:[\\w=-]|%3D)+(?:\\.(?:[\\w.+\\/=-]|%3D|%2F|%2B)+)?|[\\-]{5}BEGIN(?:[a-z\\s]|%20)+PRIVATE(?:\\s|%20)KEY[\\-]{5}[^\\-]+[\\-]{5}END(?:[a-z\\s]|%20)+PRIVATE(?:\\s|%20)KEY|ssh-rsa(?:\\s|%20)*(?:[a-z0-9\\/\\.+]|%2F|%5C|%2B){100,}'
82
136
  // eslint-disable-next-line max-len
83
- const defaultWafObfuscatorKeyRegex = '(?i)(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?)key)|token|consumer_?(?:id|key|secret)|sign(?:ed|ature)|bearer|authorization'
137
+ const defaultWafObfuscatorKeyRegex = '(?i)(?:p(?:ass)?w(?:or)?d|pass(?:[_-]?phrase)?|secret(?:[_-]?key)?|(?:(?:api|private|public|access)[_-]?)key)|(?:(?:auth|access|id|refresh)[_-]?)?token|consumer[_-]?(?:id|key|secret)|sign(?:ed|ature)|bearer|authorization|jsessionid|phpsessid|asp\\.net[_-]sessionid|sid|jwt'
84
138
  // eslint-disable-next-line max-len
85
- const defaultWafObfuscatorValueRegex = '(?i)(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?|access_?|secret_?)key(?:_?id)?|token|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)(?:\\s*=[^;]|"\\s*:\\s*"[^"]+")|bearer\\s+[a-z0-9\\._\\-]+|token:[a-z0-9]{13}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L][\\w=-]+\\.ey[I-L][\\w=-]+(?:\\.[\\w.+\\/=-]+)?|[\\-]{5}BEGIN[a-z\\s]+PRIVATE\\sKEY[\\-]{5}[^\\-]+[\\-]{5}END[a-z\\s]+PRIVATE\\sKEY|ssh-rsa\\s*[a-z0-9\\/\\.+]{100,}'
139
+ const defaultWafObfuscatorValueRegex = '(?i)(?:p(?:ass)?w(?:or)?d|pass(?:[_-]?phrase)?|secret(?:[_-]?key)?|(?:(?:api|private|public|access)[_-]?)key(?:[_-]?id)?|(?:(?:auth|access|id|refresh)[_-]?)?token|consumer[_-]?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?|jsessionid|phpsessid|asp\\.net(?:[_-]|-)sessionid|sid|jwt)(?:\\s*=[^;]|"\\s*:\\s*"[^"]+")|bearer\\s+[a-z0-9\\._\\-]+|token:[a-z0-9]{13}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L][\\w=-]+\\.ey[I-L][\\w=-]+(?:\\.[\\w.+\\/=-]+)?|[\\-]{5}BEGIN[a-z\\s]+PRIVATE\\sKEY[\\-]{5}[^\\-]+[\\-]{5}END[a-z\\s]+PRIVATE\\sKEY|ssh-rsa\\s*[a-z0-9\\/\\.+]{100,}'
86
140
  const runtimeId = uuid()
87
141
 
88
142
  function maybeFile (filepath) {
@@ -235,6 +289,9 @@ class Config {
235
289
  options.tracePropagationStyle,
236
290
  defaultPropagationStyle
237
291
  )
292
+
293
+ validateOtelPropagators(PROPAGATION_STYLE_INJECT)
294
+
238
295
  const DD_TRACE_PROPAGATION_EXTRACT_FIRST = coalesce(
239
296
  process.env.DD_TRACE_PROPAGATION_EXTRACT_FIRST,
240
297
  false
@@ -440,6 +497,10 @@ class Config {
440
497
  this._setValue(defaults, 'appsec.rateLimit', 100)
441
498
  this._setValue(defaults, 'appsec.rules', undefined)
442
499
  this._setValue(defaults, 'appsec.sca.enabled', null)
500
+ this._setValue(defaults, 'appsec.standalone.enabled', undefined)
501
+ this._setValue(defaults, 'appsec.stackTrace.enabled', true)
502
+ this._setValue(defaults, 'appsec.stackTrace.maxDepth', 32)
503
+ this._setValue(defaults, 'appsec.stackTrace.maxStackTraces', 2)
443
504
  this._setValue(defaults, 'appsec.wafTimeout', 5e3) // µs
444
505
  this._setValue(defaults, 'clientIpEnabled', false)
445
506
  this._setValue(defaults, 'clientIpHeader', null)
@@ -524,10 +585,13 @@ class Config {
524
585
  DD_APPSEC_ENABLED,
525
586
  DD_APPSEC_HTTP_BLOCKED_TEMPLATE_HTML,
526
587
  DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON,
588
+ DD_APPSEC_MAX_STACK_TRACES,
589
+ DD_APPSEC_MAX_STACK_TRACE_DEPTH,
527
590
  DD_APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP,
528
591
  DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP,
529
592
  DD_APPSEC_RULES,
530
593
  DD_APPSEC_SCA_ENABLED,
594
+ DD_APPSEC_STACK_TRACE_ENABLED,
531
595
  DD_APPSEC_RASP_ENABLED,
532
596
  DD_APPSEC_TRACE_RATE_LIMIT,
533
597
  DD_APPSEC_WAF_TIMEOUT,
@@ -536,6 +600,7 @@ class Config {
536
600
  DD_DOGSTATSD_HOSTNAME,
537
601
  DD_DOGSTATSD_PORT,
538
602
  DD_ENV,
603
+ DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED,
539
604
  DD_EXPERIMENTAL_PROFILING_ENABLED,
540
605
  JEST_WORKER_ID,
541
606
  DD_IAST_DEDUPLICATION_ENABLED,
@@ -627,6 +692,12 @@ class Config {
627
692
  this._setString(env, 'appsec.rules', DD_APPSEC_RULES)
628
693
  // DD_APPSEC_SCA_ENABLED is never used locally, but only sent to the backend
629
694
  this._setBoolean(env, 'appsec.sca.enabled', DD_APPSEC_SCA_ENABLED)
695
+ this._setBoolean(env, 'appsec.standalone.enabled', DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED)
696
+ this._setBoolean(env, 'appsec.stackTrace.enabled', DD_APPSEC_STACK_TRACE_ENABLED)
697
+ this._setValue(env, 'appsec.stackTrace.maxDepth', maybeInt(DD_APPSEC_MAX_STACK_TRACE_DEPTH))
698
+ this._envUnprocessed['appsec.stackTrace.maxDepth'] = DD_APPSEC_MAX_STACK_TRACE_DEPTH
699
+ this._setValue(env, 'appsec.stackTrace.maxStackTraces', maybeInt(DD_APPSEC_MAX_STACK_TRACES))
700
+ this._envUnprocessed['appsec.stackTrace.maxStackTraces'] = DD_APPSEC_MAX_STACK_TRACES
630
701
  this._setValue(env, 'appsec.wafTimeout', maybeInt(DD_APPSEC_WAF_TIMEOUT))
631
702
  this._envUnprocessed['appsec.wafTimeout'] = DD_APPSEC_WAF_TIMEOUT
632
703
  this._setBoolean(env, 'clientIpEnabled', DD_TRACE_CLIENT_IP_ENABLED)
@@ -701,15 +772,8 @@ class Config {
701
772
  : undefined
702
773
  this._setBoolean(env, 'runtimeMetrics', DD_RUNTIME_METRICS_ENABLED ||
703
774
  otelSetRuntimeMetrics)
704
- const OTEL_TRACES_SAMPLER_MAPPING = {
705
- always_on: '1.0',
706
- always_off: '0.0',
707
- traceidratio: OTEL_TRACES_SAMPLER_ARG,
708
- parentbased_always_on: '1.0',
709
- parentbased_always_off: '0.0',
710
- parentbased_traceidratio: OTEL_TRACES_SAMPLER_ARG
711
- }
712
- this._setUnit(env, 'sampleRate', DD_TRACE_SAMPLE_RATE || OTEL_TRACES_SAMPLER_MAPPING[OTEL_TRACES_SAMPLER])
775
+ this._setUnit(env, 'sampleRate', DD_TRACE_SAMPLE_RATE ||
776
+ getFromOtelSamplerMap(OTEL_TRACES_SAMPLER, OTEL_TRACES_SAMPLER_ARG))
713
777
  this._setValue(env, 'sampler.rateLimit', DD_TRACE_RATE_LIMIT)
714
778
  this._setSamplingRule(env, 'sampler.rules', safeJsonParse(DD_TRACE_SAMPLING_RULES))
715
779
  this._envUnprocessed['sampler.rules'] = DD_TRACE_SAMPLING_RULES
@@ -767,6 +831,12 @@ class Config {
767
831
  this._setValue(opts, 'appsec.rateLimit', maybeInt(options.appsec.rateLimit))
768
832
  this._optsUnprocessed['appsec.rateLimit'] = options.appsec.rateLimit
769
833
  this._setString(opts, 'appsec.rules', options.appsec.rules)
834
+ this._setBoolean(opts, 'appsec.standalone.enabled', options.experimental?.appsec?.standalone?.enabled)
835
+ this._setBoolean(opts, 'appsec.stackTrace.enabled', options.appsec.stackTrace?.enabled)
836
+ this._setValue(opts, 'appsec.stackTrace.maxDepth', maybeInt(options.appsec.stackTrace?.maxDepth))
837
+ this._optsUnprocessed['appsec.stackTrace.maxDepth'] = options.appsec.stackTrace?.maxDepth
838
+ this._setValue(opts, 'appsec.stackTrace.maxStackTraces', maybeInt(options.appsec.stackTrace?.maxStackTraces))
839
+ this._optsUnprocessed['appsec.stackTrace.maxStackTraces'] = options.appsec.stackTrace?.maxStackTraces
770
840
  this._setValue(opts, 'appsec.wafTimeout', maybeInt(options.appsec.wafTimeout))
771
841
  this._optsUnprocessed['appsec.wafTimeout'] = options.appsec.wafTimeout
772
842
  this._setBoolean(opts, 'clientIpEnabled', options.clientIpEnabled)
@@ -32,5 +32,7 @@ module.exports = {
32
32
  PEER_SERVICE_SOURCE_KEY: '_dd.peer.service.source',
33
33
  PEER_SERVICE_REMAP_KEY: '_dd.peer.service.remapped_from',
34
34
  SCI_REPOSITORY_URL: '_dd.git.repository_url',
35
- SCI_COMMIT_SHA: '_dd.git.commit.sha'
35
+ SCI_COMMIT_SHA: '_dd.git.commit.sha',
36
+ APM_TRACING_ENABLED_KEY: '_dd.apm.enabled',
37
+ APPSEC_PROPAGATION_KEY: '_dd.p.appsec'
36
38
  }
@@ -211,7 +211,8 @@ class DataStreamsProcessor {
211
211
  Stats,
212
212
  TracerVersion: pkg.version,
213
213
  Version: this.version,
214
- Lang: 'javascript'
214
+ Lang: 'javascript',
215
+ Tags: Object.entries(this.tags).map(([key, value]) => `${key}:${value}`)
215
216
  }
216
217
  this.writer.flush(payload)
217
218
  }
@@ -7,7 +7,7 @@ const Writer = require('./writer')
7
7
  class AgentExporter {
8
8
  constructor (config, prioritySampler) {
9
9
  this._config = config
10
- const { url, hostname, port, lookup, protocolVersion, stats = {} } = config
10
+ const { url, hostname, port, lookup, protocolVersion, stats = {}, appsec } = config
11
11
  this._url = url || new URL(format({
12
12
  protocol: 'http:',
13
13
  hostname: hostname || 'localhost',
@@ -15,7 +15,7 @@ class AgentExporter {
15
15
  }))
16
16
 
17
17
  const headers = {}
18
- if (stats.enabled) {
18
+ if (stats.enabled || appsec?.standalone?.enabled) {
19
19
  headers['Datadog-Client-Computed-Stats'] = 'yes'
20
20
  }
21
21
 
@@ -34,6 +34,7 @@ function format (span) {
34
34
  const formatted = formatSpan(span)
35
35
 
36
36
  extractSpanLinks(formatted, span)
37
+ extractSpanEvents(formatted, span)
37
38
  extractRootTags(formatted, span)
38
39
  extractChunkTags(formatted, span)
39
40
  extractTags(formatted, span)
@@ -52,6 +53,7 @@ function formatSpan (span) {
52
53
  resource: String(spanContext._name),
53
54
  error: 0,
54
55
  meta: {},
56
+ meta_struct: span.meta_struct,
55
57
  metrics: {},
56
58
  start: Math.round(span._startTime * 1e6),
57
59
  duration: Math.round(span._duration * 1e6),
@@ -88,6 +90,22 @@ function extractSpanLinks (trace, span) {
88
90
  if (links.length > 0) { trace.meta['_dd.span_links'] = JSON.stringify(links) }
89
91
  }
90
92
 
93
+ function extractSpanEvents (trace, span) {
94
+ const events = []
95
+ if (span._events) {
96
+ for (const event of span._events) {
97
+ const formattedEvent = {
98
+ name: event.name,
99
+ time_unix_nano: Math.round(event.startTime * 1e6),
100
+ attributes: event.attributes && Object.keys(event.attributes).length > 0 ? event.attributes : undefined
101
+ }
102
+
103
+ events.push(formattedEvent)
104
+ }
105
+ }
106
+ if (events.length > 0) { trace.meta.events = JSON.stringify(events) }
107
+ }
108
+
91
109
  function extractTags (trace, span) {
92
110
  const context = span.context()
93
111
  const origin = context._trace.origin
@@ -134,7 +152,10 @@ function extractTags (trace, span) {
134
152
  case ERROR_STACK:
135
153
  // HACK: remove when implemented in the backend
136
154
  if (context._name !== 'fs.operation') {
137
- trace.error = 1
155
+ // HACK: to ensure otel.recordException does not influence trace.error
156
+ if (tags.setTraceError) {
157
+ trace.error = 1
158
+ }
138
159
  } else {
139
160
  break
140
161
  }
@@ -142,7 +163,6 @@ function extractTags (trace, span) {
142
163
  addTag(trace.meta, trace.metrics, tag, tags[tag])
143
164
  }
144
165
  }
145
-
146
166
  setSingleSpanIngestionTags(trace, context._spanSampling)
147
167
 
148
168
  addTag(trace.meta, trace.metrics, 'language', 'javascript')
@@ -20,6 +20,20 @@ function hrTimeToMilliseconds (time) {
20
20
  return time[0] * 1e3 + time[1] / 1e6
21
21
  }
22
22
 
23
+ function isTimeInput (startTime) {
24
+ if (typeof startTime === 'number') {
25
+ return true
26
+ }
27
+ if (startTime instanceof Date) {
28
+ return true
29
+ }
30
+ if (Array.isArray(startTime) && startTime.length === 2 &&
31
+ typeof startTime[0] === 'number' && typeof startTime[1] === 'number') {
32
+ return true
33
+ }
34
+ return false
35
+ }
36
+
23
37
  const spanKindNames = {
24
38
  [api.SpanKind.INTERNAL]: kinds.INTERNAL,
25
39
  [api.SpanKind.SERVER]: kinds.SERVER,
@@ -196,11 +210,6 @@ class Span {
196
210
  return this
197
211
  }
198
212
 
199
- addEvent (name, attributesOrStartTime, startTime) {
200
- api.diag.warn('Events not supported')
201
- return this
202
- }
203
-
204
213
  addLink (context, attributes) {
205
214
  // extract dd context
206
215
  const ddSpanContext = context._ddContext
@@ -244,12 +253,29 @@ class Span {
244
253
  return this.ended === false
245
254
  }
246
255
 
247
- recordException (exception) {
256
+ addEvent (name, attributesOrStartTime, startTime) {
257
+ startTime = attributesOrStartTime && isTimeInput(attributesOrStartTime) ? attributesOrStartTime : startTime
258
+ const hrStartTime = timeInputToHrTime(startTime || (performance.now() + timeOrigin))
259
+ startTime = hrTimeToMilliseconds(hrStartTime)
260
+
261
+ this._ddSpan.addEvent(name, attributesOrStartTime, startTime)
262
+ return this
263
+ }
264
+
265
+ recordException (exception, timeInput) {
266
+ // HACK: identifier is added so that trace.error remains unchanged after a call to otel.recordException
248
267
  this._ddSpan.addTags({
249
268
  [ERROR_TYPE]: exception.name,
250
269
  [ERROR_MESSAGE]: exception.message,
251
- [ERROR_STACK]: exception.stack
270
+ [ERROR_STACK]: exception.stack,
271
+ doNotSetTraceError: true
252
272
  })
273
+ const attributes = {}
274
+ if (exception.message) attributes['exception.message'] = exception.message
275
+ if (exception.type) attributes['exception.type'] = exception.type
276
+ if (exception.escaped) attributes['exception.escaped'] = exception.escaped
277
+ if (exception.stack) attributes['exception.stacktrace'] = exception.stack
278
+ this.addEvent(exception.name, attributes, timeInput)
253
279
  }
254
280
 
255
281
  get duration () {
@@ -6,9 +6,13 @@ const DatadogSpanContext = require('../span_context')
6
6
  const log = require('../../log')
7
7
  const TraceState = require('./tracestate')
8
8
  const tags = require('../../../../../ext/tags')
9
+ const { channel } = require('dc-polyfill')
9
10
 
10
11
  const { AUTO_KEEP, AUTO_REJECT, USER_KEEP } = require('../../../../../ext/priority')
11
12
 
13
+ const injectCh = channel('dd-trace:span:inject')
14
+ const extractCh = channel('dd-trace:span:extract')
15
+
12
16
  const traceKey = 'x-datadog-trace-id'
13
17
  const spanKey = 'x-datadog-parent-id'
14
18
  const originKey = 'x-datadog-origin'
@@ -54,6 +58,10 @@ class TextMapPropagator {
54
58
  this._injectB3SingleHeader(spanContext, carrier)
55
59
  this._injectTraceparent(spanContext, carrier)
56
60
 
61
+ if (injectCh.hasSubscribers) {
62
+ injectCh.publish({ spanContext, carrier })
63
+ }
64
+
57
65
  log.debug(() => `Inject into carrier: ${JSON.stringify(pick(carrier, logKeys))}.`)
58
66
  }
59
67
 
@@ -62,6 +70,10 @@ class TextMapPropagator {
62
70
 
63
71
  if (!spanContext) return spanContext
64
72
 
73
+ if (extractCh.hasSubscribers) {
74
+ extractCh.publish({ spanContext, carrier })
75
+ }
76
+
65
77
  log.debug(() => `Extract from carrier: ${JSON.stringify(pick(carrier, logKeys))}.`)
66
78
 
67
79
  return spanContext
@@ -67,6 +67,8 @@ class DatadogSpan {
67
67
  this._store = storage.getStore()
68
68
  this._duration = undefined
69
69
 
70
+ this._events = []
71
+
70
72
  // For internal use only. You probably want `context()._name`.
71
73
  // This name property is not updated when the span name changes.
72
74
  // This is necessary for span count metrics.
@@ -97,7 +99,10 @@ class DatadogSpan {
97
99
  unfinishedRegistry.register(this, operationName, this)
98
100
  }
99
101
  spanleak.addSpan(this, operationName)
100
- startCh.publish(this)
102
+
103
+ if (startCh.hasSubscribers) {
104
+ startCh.publish({ span: this, fields })
105
+ }
101
106
  }
102
107
 
103
108
  toString () {
@@ -163,6 +168,19 @@ class DatadogSpan {
163
168
  })
164
169
  }
165
170
 
171
+ addEvent (name, attributesOrStartTime, startTime) {
172
+ const event = { name }
173
+ if (attributesOrStartTime) {
174
+ if (typeof attributesOrStartTime === 'object') {
175
+ event.attributes = this._sanitizeEventAttributes(attributesOrStartTime)
176
+ } else {
177
+ startTime = attributesOrStartTime
178
+ }
179
+ }
180
+ event.startTime = startTime || this._getTime()
181
+ this._events.push(event)
182
+ }
183
+
166
184
  finish (finishTime) {
167
185
  if (this._duration !== undefined) {
168
186
  return
@@ -221,7 +239,30 @@ class DatadogSpan {
221
239
  const [key, value] = entry
222
240
  addArrayOrScalarAttributes(key, value)
223
241
  })
242
+ return sanitizedAttributes
243
+ }
244
+
245
+ _sanitizeEventAttributes (attributes = {}) {
246
+ const sanitizedAttributes = {}
224
247
 
248
+ for (const key in attributes) {
249
+ const value = attributes[key]
250
+ if (Array.isArray(value)) {
251
+ const newArray = []
252
+ for (const subkey in value) {
253
+ if (ALLOWED.includes(typeof value[subkey])) {
254
+ newArray.push(value[subkey])
255
+ } else {
256
+ log.warn('Dropping span event attribute. It is not of an allowed type')
257
+ }
258
+ }
259
+ sanitizedAttributes[key] = newArray
260
+ } else if (ALLOWED.includes(typeof value)) {
261
+ sanitizedAttributes[key] = value
262
+ } else {
263
+ log.warn('Dropping span event attribute. It is not of an allowed type')
264
+ }
265
+ }
225
266
  return sanitizedAttributes
226
267
  }
227
268
 
@@ -19,7 +19,7 @@ const REFERENCE_CHILD_OF = 'child_of'
19
19
  const REFERENCE_FOLLOWS_FROM = 'follows_from'
20
20
 
21
21
  class DatadogTracer {
22
- constructor (config) {
22
+ constructor (config, prioritySampler) {
23
23
  const Exporter = getExporter(config.experimental.exporter)
24
24
 
25
25
  this._config = config
@@ -28,7 +28,7 @@ class DatadogTracer {
28
28
  this._env = config.env
29
29
  this._logInjection = config.logInjection
30
30
  this._debug = config.debug
31
- this._prioritySampler = new PrioritySampler(config.env, config.sampler)
31
+ this._prioritySampler = prioritySampler ?? new PrioritySampler(config.env, config.sampler)
32
32
  this._exporter = new Exporter(config, this._prioritySampler)
33
33
  this._processor = new SpanProcessor(this._exporter, this._prioritySampler, config)
34
34
  this._url = this._exporter._url
@@ -91,6 +91,13 @@ module.exports = class CiPlugin extends Plugin {
91
91
  ...testModuleSpanMetadata
92
92
  }
93
93
  })
94
+ // only for vitest
95
+ // These are added for the worker threads to use
96
+ if (this.constructor.id === 'vitest') {
97
+ process.env.DD_CIVISIBILITY_TEST_SESSION_ID = this.testSessionSpan.context().toTraceId()
98
+ process.env.DD_CIVISIBILITY_TEST_MODULE_ID = this.testModuleSpan.context().toSpanId()
99
+ }
100
+
94
101
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_CREATED, 'module')
95
102
  })
96
103