dd-trace 5.30.0 → 5.32.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/README.md +9 -7
  3. package/package.json +7 -6
  4. package/packages/datadog-core/src/storage.js +11 -2
  5. package/packages/datadog-instrumentations/src/aerospike.js +1 -1
  6. package/packages/datadog-instrumentations/src/aws-sdk.js +2 -1
  7. package/packages/datadog-instrumentations/src/cucumber.js +14 -5
  8. package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -0
  9. package/packages/datadog-instrumentations/src/jest.js +70 -36
  10. package/packages/datadog-instrumentations/src/mocha/utils.js +23 -7
  11. package/packages/datadog-instrumentations/src/node-serialize.js +22 -0
  12. package/packages/datadog-instrumentations/src/openai.js +2 -0
  13. package/packages/datadog-instrumentations/src/vitest.js +107 -59
  14. package/packages/datadog-instrumentations/src/vm.js +49 -0
  15. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime.js +295 -0
  16. package/packages/datadog-plugin-aws-sdk/src/services/index.js +1 -0
  17. package/packages/datadog-plugin-cucumber/src/index.js +30 -32
  18. package/packages/datadog-plugin-jest/src/index.js +34 -37
  19. package/packages/datadog-plugin-langchain/src/index.js +12 -80
  20. package/packages/datadog-plugin-langchain/src/tracing.js +89 -0
  21. package/packages/datadog-plugin-mocha/src/index.js +18 -36
  22. package/packages/datadog-plugin-vitest/src/index.js +20 -34
  23. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
  24. package/packages/dd-trace/src/appsec/iast/analyzers/code-injection-analyzer.js +2 -0
  25. package/packages/dd-trace/src/appsec/iast/analyzers/untrusted-deserialization-analyzer.js +16 -0
  26. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +9 -8
  27. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
  28. package/packages/dd-trace/src/appsec/remote_config/manager.js +11 -1
  29. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +37 -0
  30. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +65 -28
  31. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +57 -17
  32. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +18 -3
  33. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +20 -3
  34. package/packages/dd-trace/src/config.js +39 -3
  35. package/packages/dd-trace/src/crashtracking/crashtracker.js +9 -0
  36. package/packages/dd-trace/src/crashtracking/noop.js +3 -0
  37. package/packages/dd-trace/src/datastreams/fnv.js +1 -1
  38. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +2 -2
  39. package/packages/dd-trace/src/debugger/devtools_client/config.js +3 -1
  40. package/packages/dd-trace/src/debugger/devtools_client/defaults.js +1 -0
  41. package/packages/dd-trace/src/debugger/devtools_client/index.js +32 -14
  42. package/packages/dd-trace/src/debugger/devtools_client/json-buffer.js +36 -0
  43. package/packages/dd-trace/src/debugger/devtools_client/send.js +29 -10
  44. package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +35 -1
  45. package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +112 -0
  46. package/packages/dd-trace/src/debugger/devtools_client/status.js +20 -11
  47. package/packages/dd-trace/src/debugger/index.js +2 -13
  48. package/packages/dd-trace/src/llmobs/plugins/base.js +40 -11
  49. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chain.js +24 -0
  50. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +111 -0
  51. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/embedding.js +42 -0
  52. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +102 -0
  53. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/llm.js +32 -0
  54. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +131 -0
  55. package/packages/dd-trace/src/llmobs/plugins/openai.js +1 -1
  56. package/packages/dd-trace/src/llmobs/sdk.js +90 -26
  57. package/packages/dd-trace/src/llmobs/tagger.js +11 -3
  58. package/packages/dd-trace/src/llmobs/util.js +7 -1
  59. package/packages/dd-trace/src/llmobs/writers/spans/agentProxy.js +3 -3
  60. package/packages/dd-trace/src/log/index.js +8 -9
  61. package/packages/dd-trace/src/noop/proxy.js +2 -2
  62. package/packages/dd-trace/src/noop/span.js +1 -1
  63. package/packages/dd-trace/src/opentelemetry/context_manager.js +43 -3
  64. package/packages/dd-trace/src/opentracing/span.js +11 -1
  65. package/packages/dd-trace/src/opentracing/span_context.js +12 -0
  66. package/packages/dd-trace/src/plugins/ci_plugin.js +57 -27
  67. package/packages/dd-trace/src/plugins/util/test.js +42 -12
  68. package/packages/dd-trace/src/priority_sampler.js +7 -2
  69. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +21 -0
  70. package/packages/dd-trace/src/profiling/profiler.js +11 -8
  71. package/packages/dd-trace/src/profiling/profilers/events.js +17 -1
  72. package/packages/dd-trace/src/proxy.js +6 -3
  73. package/packages/dd-trace/src/scope.js +1 -1
  74. package/packages/dd-trace/src/telemetry/index.js +2 -0
@@ -25,19 +25,20 @@ class SensitiveHandler {
25
25
 
26
26
  this._sensitiveAnalyzers = new Map()
27
27
  this._sensitiveAnalyzers.set(vulnerabilities.CODE_INJECTION, taintedRangeBasedSensitiveAnalyzer)
28
- this._sensitiveAnalyzers.set(vulnerabilities.TEMPLATE_INJECTION, taintedRangeBasedSensitiveAnalyzer)
29
28
  this._sensitiveAnalyzers.set(vulnerabilities.COMMAND_INJECTION, commandSensitiveAnalyzer)
30
- this._sensitiveAnalyzers.set(vulnerabilities.NOSQL_MONGODB_INJECTION, jsonSensitiveAnalyzer)
29
+ this._sensitiveAnalyzers.set(vulnerabilities.HARDCODED_PASSWORD, (evidence) => {
30
+ return hardcodedPasswordAnalyzer(evidence, this._valuePattern)
31
+ })
32
+ this._sensitiveAnalyzers.set(vulnerabilities.HEADER_INJECTION, (evidence) => {
33
+ return headerSensitiveAnalyzer(evidence, this._namePattern, this._valuePattern)
34
+ })
31
35
  this._sensitiveAnalyzers.set(vulnerabilities.LDAP_INJECTION, ldapSensitiveAnalyzer)
36
+ this._sensitiveAnalyzers.set(vulnerabilities.NOSQL_MONGODB_INJECTION, jsonSensitiveAnalyzer)
32
37
  this._sensitiveAnalyzers.set(vulnerabilities.SQL_INJECTION, sqlSensitiveAnalyzer)
33
38
  this._sensitiveAnalyzers.set(vulnerabilities.SSRF, urlSensitiveAnalyzer)
39
+ this._sensitiveAnalyzers.set(vulnerabilities.TEMPLATE_INJECTION, taintedRangeBasedSensitiveAnalyzer)
40
+ this._sensitiveAnalyzers.set(vulnerabilities.UNTRUSTED_DESERIALIZATION, taintedRangeBasedSensitiveAnalyzer)
34
41
  this._sensitiveAnalyzers.set(vulnerabilities.UNVALIDATED_REDIRECT, urlSensitiveAnalyzer)
35
- this._sensitiveAnalyzers.set(vulnerabilities.HEADER_INJECTION, (evidence) => {
36
- return headerSensitiveAnalyzer(evidence, this._namePattern, this._valuePattern)
37
- })
38
- this._sensitiveAnalyzers.set(vulnerabilities.HARDCODED_PASSWORD, (evidence) => {
39
- return hardcodedPasswordAnalyzer(evidence, this._valuePattern)
40
- })
41
42
  }
42
43
 
43
44
  isSensibleName (name) {
@@ -15,6 +15,7 @@ module.exports = {
15
15
  SSRF: 'SSRF',
16
16
  TEMPLATE_INJECTION: 'TEMPLATE_INJECTION',
17
17
  UNVALIDATED_REDIRECT: 'UNVALIDATED_REDIRECT',
18
+ UNTRUSTED_DESERIALIZATION: 'UNTRUSTED_DESERIALIZATION',
18
19
  WEAK_CIPHER: 'WEAK_CIPHER',
19
20
  WEAK_HASH: 'WEAK_HASH',
20
21
  WEAK_RANDOMNESS: 'WEAK_RANDOMNESS',
@@ -9,6 +9,7 @@ const log = require('../../log')
9
9
  const { getExtraServices } = require('../../service-naming/extra-services')
10
10
  const { UNACKNOWLEDGED, ACKNOWLEDGED, ERROR } = require('./apply_states')
11
11
  const Scheduler = require('./scheduler')
12
+ const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('../../plugins/util/tags')
12
13
 
13
14
  const clientId = uuid()
14
15
 
@@ -33,6 +34,14 @@ class RemoteConfigManager extends EventEmitter {
33
34
  port: config.port
34
35
  }))
35
36
 
37
+ const tags = config.repositoryUrl
38
+ ? {
39
+ ...config.tags,
40
+ [GIT_REPOSITORY_URL]: config.repositoryUrl,
41
+ [GIT_COMMIT_SHA]: config.commitSHA
42
+ }
43
+ : config.tags
44
+
36
45
  this._handlers = new Map()
37
46
  const appliedConfigs = this.appliedConfigs = new Map()
38
47
 
@@ -67,7 +76,8 @@ class RemoteConfigManager extends EventEmitter {
67
76
  service: config.service,
68
77
  env: config.env,
69
78
  app_version: config.version,
70
- extra_services: []
79
+ extra_services: [],
80
+ tags: Object.entries(tags).map((pair) => pair.join(':'))
71
81
  },
72
82
  capabilities: DEFAULT_CAPABILITY // updated by `updateCapabilities()`
73
83
  },
@@ -19,6 +19,7 @@ class WAFContextWrapper {
19
19
  this.rulesVersion = rulesVersion
20
20
  this.addressesToSkip = new Set()
21
21
  this.knownAddresses = knownAddresses
22
+ this.cachedUserIdActions = new Map()
22
23
  }
23
24
 
24
25
  run ({ persistent, ephemeral }, raspRule) {
@@ -27,6 +28,16 @@ class WAFContextWrapper {
27
28
  return
28
29
  }
29
30
 
31
+ // SPECIAL CASE FOR USER_ID
32
+ // TODO: make this universal
33
+ const userId = persistent?.[addresses.USER_ID] || ephemeral?.[addresses.USER_ID]
34
+ if (userId) {
35
+ const cachedAction = this.cachedUserIdActions.get(userId)
36
+ if (cachedAction) {
37
+ return cachedAction
38
+ }
39
+ }
40
+
30
41
  const payload = {}
31
42
  let payloadHasData = false
32
43
  const newAddressesToSkip = new Set(this.addressesToSkip)
@@ -79,6 +90,12 @@ class WAFContextWrapper {
79
90
 
80
91
  const blockTriggered = !!getBlockingAction(result.actions)
81
92
 
93
+ // SPECIAL CASE FOR USER_ID
94
+ // TODO: make this universal
95
+ if (userId && ruleTriggered && blockTriggered) {
96
+ this.setUserIdCache(userId, result)
97
+ }
98
+
82
99
  Reporter.reportMetrics({
83
100
  duration: result.totalRuntime / 1e3,
84
101
  durationExt: parseInt(end - start) / 1e3,
@@ -105,6 +122,26 @@ class WAFContextWrapper {
105
122
  }
106
123
  }
107
124
 
125
+ setUserIdCache (userId, result) {
126
+ // using old loops for speed
127
+ for (let i = 0; i < result.events.length; i++) {
128
+ const event = result.events[i]
129
+
130
+ for (let j = 0; j < event?.rule_matches?.length; j++) {
131
+ const match = event.rule_matches[j]
132
+
133
+ for (let k = 0; k < match?.parameters?.length; k++) {
134
+ const parameter = match.parameters[k]
135
+
136
+ if (parameter?.address === addresses.USER_ID) {
137
+ this.cachedUserIdActions.set(userId, result.actions)
138
+ return
139
+ }
140
+ }
141
+ }
142
+ }
143
+ }
144
+
108
145
  dispose () {
109
146
  this.ddwafContext.dispose()
110
147
  }
@@ -1,12 +1,12 @@
1
1
  'use strict'
2
2
 
3
3
  const { join } = require('path')
4
- const { Worker } = require('worker_threads')
4
+ const { Worker, threadId: parentThreadId } = require('worker_threads')
5
5
  const { randomUUID } = require('crypto')
6
6
  const log = require('../../log')
7
7
 
8
8
  const probeIdToResolveBreakpointSet = new Map()
9
- const probeIdToResolveBreakpointHit = new Map()
9
+ const probeIdToResolveBreakpointRemove = new Map()
10
10
 
11
11
  class TestVisDynamicInstrumentation {
12
12
  constructor () {
@@ -16,28 +16,34 @@ class TestVisDynamicInstrumentation {
16
16
  })
17
17
  this.breakpointSetChannel = new MessageChannel()
18
18
  this.breakpointHitChannel = new MessageChannel()
19
+ this.breakpointRemoveChannel = new MessageChannel()
20
+ this.onHitBreakpointByProbeId = new Map()
19
21
  }
20
22
 
21
- // Return 3 elements:
22
- // 1. Snapshot ID
23
+ removeProbe (probeId) {
24
+ return new Promise(resolve => {
25
+ this.breakpointRemoveChannel.port2.postMessage(probeId)
26
+
27
+ probeIdToResolveBreakpointRemove.set(probeId, resolve)
28
+ })
29
+ }
30
+
31
+ // Return 2 elements:
32
+ // 1. Probe ID
23
33
  // 2. Promise that's resolved when the breakpoint is set
24
- // 3. Promise that's resolved when the breakpoint is hit
25
- addLineProbe ({ file, line }) {
26
- const snapshotId = randomUUID()
34
+ addLineProbe ({ file, line }, onHitBreakpoint) {
27
35
  const probeId = randomUUID()
28
36
 
29
- this.breakpointSetChannel.port2.postMessage({
30
- snapshotId,
31
- probe: { id: probeId, file, line }
32
- })
37
+ this.breakpointSetChannel.port2.postMessage(
38
+ { id: probeId, file, line }
39
+ )
40
+
41
+ this.onHitBreakpointByProbeId.set(probeId, onHitBreakpoint)
33
42
 
34
43
  return [
35
- snapshotId,
44
+ probeId,
36
45
  new Promise(resolve => {
37
46
  probeIdToResolveBreakpointSet.set(probeId, resolve)
38
- }),
39
- new Promise(resolve => {
40
- probeIdToResolveBreakpointHit.set(probeId, resolve)
41
47
  })
42
48
  ]
43
49
  }
@@ -46,23 +52,42 @@ class TestVisDynamicInstrumentation {
46
52
  return this._readyPromise
47
53
  }
48
54
 
49
- start () {
55
+ start (config) {
50
56
  if (this.worker) return
51
57
 
52
- const { NODE_OPTIONS, ...envWithoutNodeOptions } = process.env
53
-
54
58
  log.debug('Starting Test Visibility - Dynamic Instrumentation client...')
55
59
 
60
+ const rcChannel = new MessageChannel() // mock channel
61
+ const configChannel = new MessageChannel() // mock channel
62
+
56
63
  this.worker = new Worker(
57
64
  join(__dirname, 'worker', 'index.js'),
58
65
  {
59
66
  execArgv: [],
60
- env: envWithoutNodeOptions,
67
+ // Not passing `NODE_OPTIONS` results in issues with yarn, which relies on NODE_OPTIONS
68
+ // for PnP support, hence why we deviate from the DI pattern here.
69
+ // To avoid infinite initialization loops, we're disabling DI and tracing in the worker.
70
+ env: {
71
+ ...process.env,
72
+ DD_TRACE_ENABLED: 0,
73
+ DD_TEST_DYNAMIC_INSTRUMENTATION_ENABLED: 0
74
+ },
61
75
  workerData: {
76
+ config: config.serialize(),
77
+ parentThreadId,
78
+ rcPort: rcChannel.port1,
79
+ configPort: configChannel.port1,
62
80
  breakpointSetChannel: this.breakpointSetChannel.port1,
63
- breakpointHitChannel: this.breakpointHitChannel.port1
81
+ breakpointHitChannel: this.breakpointHitChannel.port1,
82
+ breakpointRemoveChannel: this.breakpointRemoveChannel.port1
64
83
  },
65
- transferList: [this.breakpointSetChannel.port1, this.breakpointHitChannel.port1]
84
+ transferList: [
85
+ rcChannel.port1,
86
+ configChannel.port1,
87
+ this.breakpointSetChannel.port1,
88
+ this.breakpointHitChannel.port1,
89
+ this.breakpointRemoveChannel.port1
90
+ ]
66
91
  }
67
92
  )
68
93
  this.worker.on('online', () => {
@@ -70,10 +95,18 @@ class TestVisDynamicInstrumentation {
70
95
  this._onReady()
71
96
  })
72
97
 
98
+ this.worker.on('error', (err) => {
99
+ log.error('Test Visibility - Dynamic Instrumentation worker error', err)
100
+ })
101
+
102
+ this.worker.on('messageerror', (err) => {
103
+ log.error('Test Visibility - Dynamic Instrumentation worker messageerror', err)
104
+ })
105
+
73
106
  // Allow the parent to exit even if the worker is still running
74
107
  this.worker.unref()
75
108
 
76
- this.breakpointSetChannel.port2.on('message', ({ probeId }) => {
109
+ this.breakpointSetChannel.port2.on('message', (probeId) => {
77
110
  const resolve = probeIdToResolveBreakpointSet.get(probeId)
78
111
  if (resolve) {
79
112
  resolve()
@@ -83,15 +116,19 @@ class TestVisDynamicInstrumentation {
83
116
 
84
117
  this.breakpointHitChannel.port2.on('message', ({ snapshot }) => {
85
118
  const { probe: { id: probeId } } = snapshot
86
- const resolve = probeIdToResolveBreakpointHit.get(probeId)
87
- if (resolve) {
88
- resolve({ snapshot })
89
- probeIdToResolveBreakpointHit.delete(probeId)
119
+ const onHit = this.onHitBreakpointByProbeId.get(probeId)
120
+ if (onHit) {
121
+ onHit({ snapshot })
90
122
  }
91
123
  }).unref()
92
124
 
93
- this.worker.on('error', (err) => log.error('ci-visibility DI worker error', err))
94
- this.worker.on('messageerror', (err) => log.error('ci-visibility DI worker messageerror', err))
125
+ this.breakpointRemoveChannel.port2.on('message', (probeId) => {
126
+ const resolve = probeIdToResolveBreakpointRemove.get(probeId)
127
+ if (resolve) {
128
+ resolve()
129
+ probeIdToResolveBreakpointRemove.delete(probeId)
130
+ }
131
+ }).unref()
95
132
  }
96
133
  }
97
134
 
@@ -1,7 +1,14 @@
1
1
  'use strict'
2
- const sourceMap = require('source-map')
3
2
  const path = require('path')
4
- const { workerData: { breakpointSetChannel, breakpointHitChannel } } = require('worker_threads')
3
+ const {
4
+ workerData: {
5
+ breakpointSetChannel,
6
+ breakpointHitChannel,
7
+ breakpointRemoveChannel
8
+ }
9
+ } = require('worker_threads')
10
+ const { randomUUID } = require('crypto')
11
+ const sourceMap = require('source-map')
5
12
 
6
13
  // TODO: move debugger/devtools_client/session to common place
7
14
  const session = require('../../../debugger/devtools_client/session')
@@ -16,8 +23,8 @@ const log = require('../../../log')
16
23
 
17
24
  let sessionStarted = false
18
25
 
19
- const breakpointIdToSnapshotId = new Map()
20
26
  const breakpointIdToProbe = new Map()
27
+ const probeIdToBreakpointId = new Map()
21
28
 
22
29
  session.on('Debugger.paused', async ({ params: { hitBreakpoints: [hitBreakpoint], callFrames } }) => {
23
30
  const probe = breakpointIdToProbe.get(hitBreakpoint)
@@ -32,13 +39,11 @@ session.on('Debugger.paused', async ({ params: { hitBreakpoints: [hitBreakpoint]
32
39
 
33
40
  await session.post('Debugger.resume')
34
41
 
35
- const snapshotId = breakpointIdToSnapshotId.get(hitBreakpoint)
36
-
37
42
  const snapshot = {
38
- id: snapshotId,
43
+ id: randomUUID(),
39
44
  timestamp: Date.now(),
40
45
  probe: {
41
- id: probe.probeId,
46
+ id: probe.id,
42
47
  version: '0',
43
48
  location: probe.location
44
49
  },
@@ -56,13 +61,32 @@ session.on('Debugger.paused', async ({ params: { hitBreakpoints: [hitBreakpoint]
56
61
  breakpointHitChannel.postMessage({ snapshot })
57
62
  })
58
63
 
59
- // TODO: add option to remove breakpoint
60
- breakpointSetChannel.on('message', async ({ snapshotId, probe: { id: probeId, file, line } }) => {
61
- await addBreakpoint(snapshotId, { probeId, file, line })
62
- breakpointSetChannel.postMessage({ probeId })
64
+ breakpointRemoveChannel.on('message', async (probeId) => {
65
+ await removeBreakpoint(probeId)
66
+ breakpointRemoveChannel.postMessage(probeId)
67
+ })
68
+
69
+ breakpointSetChannel.on('message', async (probe) => {
70
+ await addBreakpoint(probe)
71
+ breakpointSetChannel.postMessage(probe.id)
63
72
  })
64
73
 
65
- async function addBreakpoint (snapshotId, probe) {
74
+ async function removeBreakpoint (probeId) {
75
+ if (!sessionStarted) {
76
+ // We should not get in this state, but abort if we do, so the code doesn't fail unexpected
77
+ throw Error(`Cannot remove probe ${probeId}: Debugger not started`)
78
+ }
79
+
80
+ const breakpointId = probeIdToBreakpointId.get(probeId)
81
+ if (!breakpointId) {
82
+ throw Error(`Unknown probe id: ${probeId}`)
83
+ }
84
+ await session.post('Debugger.removeBreakpoint', { breakpointId })
85
+ probeIdToBreakpointId.delete(probeId)
86
+ breakpointIdToProbe.delete(breakpointId)
87
+ }
88
+
89
+ async function addBreakpoint (probe) {
66
90
  if (!sessionStarted) await start()
67
91
  const { file, line } = probe
68
92
 
@@ -81,7 +105,7 @@ async function addBreakpoint (snapshotId, probe) {
81
105
  try {
82
106
  lineNumber = await processScriptWithInlineSourceMap({ file, line, sourceMapURL })
83
107
  } catch (err) {
84
- log.error(err)
108
+ log.error('Error processing script with inline source map', err)
85
109
  }
86
110
  }
87
111
 
@@ -93,7 +117,7 @@ async function addBreakpoint (snapshotId, probe) {
93
117
  })
94
118
 
95
119
  breakpointIdToProbe.set(breakpointId, probe)
96
- breakpointIdToSnapshotId.set(breakpointId, snapshotId)
120
+ probeIdToBreakpointId.set(probe.id, breakpointId)
97
121
  }
98
122
 
99
123
  function start () {
@@ -113,14 +137,30 @@ async function processScriptWithInlineSourceMap (params) {
113
137
  // Parse the source map
114
138
  const consumer = await new sourceMap.SourceMapConsumer(decodedSourceMap)
115
139
 
116
- // Map to the generated position
117
- const generatedPosition = consumer.generatedPositionFor({
118
- source: path.basename(file), // this needs to be the file, not the filepath
140
+ let generatedPosition
141
+
142
+ // Map to the generated position. We'll attempt with the full file path first, then with the basename.
143
+ // TODO: figure out why sometimes the full path doesn't work
144
+ generatedPosition = consumer.generatedPositionFor({
145
+ source: file,
119
146
  line,
120
147
  column: 0
121
148
  })
149
+ if (generatedPosition.line === null) {
150
+ generatedPosition = consumer.generatedPositionFor({
151
+ source: path.basename(file),
152
+ line,
153
+ column: 0
154
+ })
155
+ }
122
156
 
123
157
  consumer.destroy()
124
158
 
159
+ // If we can't find the line, just return the original line
160
+ if (generatedPosition.line === null) {
161
+ log.error(`Could not find generated position for ${file}:${line}`)
162
+ return line
163
+ }
164
+
125
165
  return generatedPosition.line
126
166
  }
@@ -5,7 +5,8 @@ const {
5
5
  JEST_WORKER_COVERAGE_PAYLOAD_CODE,
6
6
  JEST_WORKER_TRACE_PAYLOAD_CODE,
7
7
  CUCUMBER_WORKER_TRACE_PAYLOAD_CODE,
8
- MOCHA_WORKER_TRACE_PAYLOAD_CODE
8
+ MOCHA_WORKER_TRACE_PAYLOAD_CODE,
9
+ JEST_WORKER_LOGS_PAYLOAD_CODE
9
10
  } = require('../../../plugins/util/test')
10
11
 
11
12
  function getInterprocessTraceCode () {
@@ -29,18 +30,27 @@ function getInterprocessCoverageCode () {
29
30
  return null
30
31
  }
31
32
 
33
+ function getInterprocessLogsCode () {
34
+ if (process.env.JEST_WORKER_ID) {
35
+ return JEST_WORKER_LOGS_PAYLOAD_CODE
36
+ }
37
+ return null
38
+ }
39
+
32
40
  /**
33
41
  * Lightweight exporter whose writers only do simple JSON serialization
34
- * of trace and coverage payloads, which they send to the test framework's main process.
35
- * Currently used by Jest and Cucumber workers.
42
+ * of trace, coverage and logs payloads, which they send to the test framework's main process.
43
+ * Currently used by Jest, Cucumber and Mocha workers.
36
44
  */
37
45
  class TestWorkerCiVisibilityExporter {
38
46
  constructor () {
39
47
  const interprocessTraceCode = getInterprocessTraceCode()
40
48
  const interprocessCoverageCode = getInterprocessCoverageCode()
49
+ const interprocessLogsCode = getInterprocessLogsCode()
41
50
 
42
51
  this._writer = new Writer(interprocessTraceCode)
43
52
  this._coverageWriter = new Writer(interprocessCoverageCode)
53
+ this._logsWriter = new Writer(interprocessLogsCode)
44
54
  }
45
55
 
46
56
  export (payload) {
@@ -51,9 +61,14 @@ class TestWorkerCiVisibilityExporter {
51
61
  this._coverageWriter.append(formattedCoverage)
52
62
  }
53
63
 
64
+ exportDiLogs (testConfiguration, logMessage) {
65
+ this._logsWriter.append({ testConfiguration, logMessage })
66
+ }
67
+
54
68
  flush () {
55
69
  this._writer.flush()
56
70
  this._coverageWriter.flush()
71
+ this._logsWriter.flush()
57
72
  }
58
73
  }
59
74
 
@@ -13,15 +13,16 @@ class TestApiManualPlugin extends CiPlugin {
13
13
 
14
14
  constructor (...args) {
15
15
  super(...args)
16
+ this._isEnvDataCalcualted = false
16
17
  this.sourceRoot = process.cwd()
17
18
 
18
- this.addSub('dd-trace:ci:manual:test:start', ({ testName, testSuite }) => {
19
+ this.unconfiguredAddSub('dd-trace:ci:manual:test:start', ({ testName, testSuite }) => {
19
20
  const store = storage.getStore()
20
21
  const testSuiteRelative = getTestSuitePath(testSuite, this.sourceRoot)
21
22
  const testSpan = this.startTestSpan(testName, testSuiteRelative)
22
23
  this.enter(testSpan, store)
23
24
  })
24
- this.addSub('dd-trace:ci:manual:test:finish', ({ status, error }) => {
25
+ this.unconfiguredAddSub('dd-trace:ci:manual:test:finish', ({ status, error }) => {
25
26
  const store = storage.getStore()
26
27
  const testSpan = store && store.span
27
28
  if (testSpan) {
@@ -33,7 +34,7 @@ class TestApiManualPlugin extends CiPlugin {
33
34
  finishAllTraceSpans(testSpan)
34
35
  }
35
36
  })
36
- this.addSub('dd-trace:ci:manual:test:addTags', (tags) => {
37
+ this.unconfiguredAddSub('dd-trace:ci:manual:test:addTags', (tags) => {
37
38
  const store = storage.getStore()
38
39
  const testSpan = store && store.span
39
40
  if (testSpan) {
@@ -41,6 +42,22 @@ class TestApiManualPlugin extends CiPlugin {
41
42
  }
42
43
  })
43
44
  }
45
+
46
+ // To lazily calculate env data.
47
+ unconfiguredAddSub (channelName, handler) {
48
+ this.addSub(channelName, (...args) => {
49
+ if (!this._isEnvDataCalcualted) {
50
+ this._isEnvDataCalcualted = true
51
+ this.configure(this._config, true)
52
+ }
53
+ return handler(...args)
54
+ })
55
+ }
56
+
57
+ configure (config, shouldGetEnvironmentData) {
58
+ this._config = config
59
+ super.configure(config, shouldGetEnvironmentData)
60
+ }
44
61
  }
45
62
 
46
63
  module.exports = TestApiManualPlugin
@@ -472,7 +472,9 @@ class Config {
472
472
  this._setValue(defaults, 'dogstatsd.hostname', '127.0.0.1')
473
473
  this._setValue(defaults, 'dogstatsd.port', '8125')
474
474
  this._setValue(defaults, 'dsmEnabled', false)
475
- this._setValue(defaults, 'dynamicInstrumentationEnabled', false)
475
+ this._setValue(defaults, 'dynamicInstrumentation.enabled', false)
476
+ this._setValue(defaults, 'dynamicInstrumentation.redactedIdentifiers', [])
477
+ this._setValue(defaults, 'dynamicInstrumentation.redactionExcludedIdentifiers', [])
476
478
  this._setValue(defaults, 'env', undefined)
477
479
  this._setValue(defaults, 'experimental.enableGetRumData', false)
478
480
  this._setValue(defaults, 'experimental.exporter', undefined)
@@ -600,6 +602,8 @@ class Config {
600
602
  DD_DOGSTATSD_HOST,
601
603
  DD_DOGSTATSD_PORT,
602
604
  DD_DYNAMIC_INSTRUMENTATION_ENABLED,
605
+ DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS,
606
+ DD_DYNAMIC_INSTRUMENTATION_REDACTION_EXCLUDED_IDENTIFIERS,
603
607
  DD_ENV,
604
608
  DD_EXPERIMENTAL_API_SECURITY_ENABLED,
605
609
  DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED,
@@ -746,7 +750,13 @@ class Config {
746
750
  this._setString(env, 'dogstatsd.hostname', DD_DOGSTATSD_HOST || DD_DOGSTATSD_HOSTNAME)
747
751
  this._setString(env, 'dogstatsd.port', DD_DOGSTATSD_PORT)
748
752
  this._setBoolean(env, 'dsmEnabled', DD_DATA_STREAMS_ENABLED)
749
- this._setBoolean(env, 'dynamicInstrumentationEnabled', DD_DYNAMIC_INSTRUMENTATION_ENABLED)
753
+ this._setBoolean(env, 'dynamicInstrumentation.enabled', DD_DYNAMIC_INSTRUMENTATION_ENABLED)
754
+ this._setArray(env, 'dynamicInstrumentation.redactedIdentifiers', DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS)
755
+ this._setArray(
756
+ env,
757
+ 'dynamicInstrumentation.redactionExcludedIdentifiers',
758
+ DD_DYNAMIC_INSTRUMENTATION_REDACTION_EXCLUDED_IDENTIFIERS
759
+ )
750
760
  this._setString(env, 'env', DD_ENV || tags.env)
751
761
  this._setBoolean(env, 'traceEnabled', DD_TRACE_ENABLED)
752
762
  this._setBoolean(env, 'experimental.enableGetRumData', DD_TRACE_EXPERIMENTAL_GET_RUM_DATA_ENABLED)
@@ -926,7 +936,17 @@ class Config {
926
936
  this._setString(opts, 'dogstatsd.port', options.dogstatsd.port)
927
937
  }
928
938
  this._setBoolean(opts, 'dsmEnabled', options.dsmEnabled)
929
- this._setBoolean(opts, 'dynamicInstrumentationEnabled', options.experimental?.dynamicInstrumentationEnabled)
939
+ this._setBoolean(opts, 'dynamicInstrumentation.enabled', options.dynamicInstrumentation?.enabled)
940
+ this._setArray(
941
+ opts,
942
+ 'dynamicInstrumentation.redactedIdentifiers',
943
+ options.dynamicInstrumentation?.redactedIdentifiers
944
+ )
945
+ this._setArray(
946
+ opts,
947
+ 'dynamicInstrumentation.redactionExcludedIdentifiers',
948
+ options.dynamicInstrumentation?.redactionExcludedIdentifiers
949
+ )
930
950
  this._setString(opts, 'env', options.env || tags.env)
931
951
  this._setBoolean(opts, 'experimental.enableGetRumData', options.experimental?.enableGetRumData)
932
952
  this._setString(opts, 'experimental.exporter', options.experimental?.exporter)
@@ -1312,6 +1332,22 @@ class Config {
1312
1332
  this.sampler.sampleRate = this.sampleRate
1313
1333
  updateConfig(changes, this)
1314
1334
  }
1335
+
1336
+ // TODO: Refactor the Config class so it never produces any config objects that are incompatible with MessageChannel
1337
+ /**
1338
+ * Serializes the config object so it can be passed over a Worker Thread MessageChannel.
1339
+ * @returns {Object} The serialized config object.
1340
+ */
1341
+ serialize () {
1342
+ // URL objects cannot be serialized over the MessageChannel, so we need to convert them to strings first
1343
+ if (this.url instanceof URL) {
1344
+ const config = { ...this }
1345
+ config.url = this.url.toString()
1346
+ return config
1347
+ }
1348
+
1349
+ return this
1350
+ }
1315
1351
  }
1316
1352
 
1317
1353
  function maybeInt (number) {
@@ -40,6 +40,15 @@ class Crashtracker {
40
40
  }
41
41
  }
42
42
 
43
+ withProfilerSerializing (f) {
44
+ binding.beginProfilerSerializing()
45
+ try {
46
+ return f()
47
+ } finally {
48
+ binding.endProfilerSerializing()
49
+ }
50
+ }
51
+
43
52
  // TODO: Send only configured values when defaults are fixed.
44
53
  _getConfig (config) {
45
54
  const { hostname = '127.0.0.1', port = 8126 } = config
@@ -3,6 +3,9 @@
3
3
  class NoopCrashtracker {
4
4
  configure () {}
5
5
  start () {}
6
+ withProfilerSerializing (f) {
7
+ return f()
8
+ }
6
9
  }
7
10
 
8
11
  module.exports = new NoopCrashtracker()
@@ -15,7 +15,7 @@ function fnv64 (data) {
15
15
  data = Buffer.from(data, 'utf-8')
16
16
  }
17
17
  const byteArray = new Uint8Array(data)
18
- return fnv(byteArray, FNV1_64_INIT, FNV_64_PRIME, BigInt(2) ** BigInt(64))
18
+ return fnv(byteArray, FNV1_64_INIT, FNV_64_PRIME, 2n ** 64n)
19
19
  }
20
20
 
21
21
  module.exports = {
@@ -23,10 +23,10 @@ async function addBreakpoint (probe) {
23
23
  delete probe.where
24
24
 
25
25
  // Optimize for fast calculations when probe is hit
26
- const snapshotsPerSecond = probe.sampling.snapshotsPerSecond ?? (probe.captureSnapshot
26
+ const snapshotsPerSecond = probe.sampling?.snapshotsPerSecond ?? (probe.captureSnapshot
27
27
  ? MAX_SNAPSHOTS_PER_SECOND_PER_PROBE
28
28
  : MAX_NON_SNAPSHOTS_PER_SECOND_PER_PROBE)
29
- probe.sampling.nsBetweenSampling = BigInt(1 / snapshotsPerSecond * 1e9)
29
+ probe.nsBetweenSampling = BigInt(1 / snapshotsPerSecond * 1e9)
30
30
  probe.lastCaptureNs = 0n
31
31
 
32
32
  // TODO: Inbetween `await session.post('Debugger.enable')` and here, the scripts are parsed and cached.
@@ -5,11 +5,13 @@ const { format } = require('node:url')
5
5
  const log = require('../../log')
6
6
 
7
7
  const config = module.exports = {
8
+ dynamicInstrumentation: parentConfig.dynamicInstrumentation,
8
9
  runtimeId: parentConfig.tags['runtime-id'],
9
10
  service: parentConfig.service,
10
11
  commitSHA: parentConfig.commitSHA,
11
12
  repositoryUrl: parentConfig.repositoryUrl,
12
- parentThreadId
13
+ parentThreadId,
14
+ maxTotalPayloadSize: 5 * 1024 * 1024 // 5MB
13
15
  }
14
16
 
15
17
  updateUrl(parentConfig)