dd-trace 5.51.0 → 5.53.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 (98) hide show
  1. package/LICENSE-3rdparty.csv +0 -6
  2. package/README.md +5 -0
  3. package/index.d.ts +88 -6
  4. package/package.json +3 -9
  5. package/packages/datadog-instrumentations/src/amqplib.js +8 -5
  6. package/packages/datadog-instrumentations/src/child_process.js +2 -1
  7. package/packages/datadog-instrumentations/src/confluentinc-kafka-javascript.js +406 -0
  8. package/packages/datadog-instrumentations/src/couchbase.js +2 -1
  9. package/packages/datadog-instrumentations/src/cucumber.js +43 -45
  10. package/packages/datadog-instrumentations/src/dns.js +16 -14
  11. package/packages/datadog-instrumentations/src/express.js +2 -6
  12. package/packages/datadog-instrumentations/src/fs.js +43 -51
  13. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  14. package/packages/datadog-instrumentations/src/helpers/register.js +17 -12
  15. package/packages/datadog-instrumentations/src/http/client.js +2 -1
  16. package/packages/datadog-instrumentations/src/iovalkey.js +51 -0
  17. package/packages/datadog-instrumentations/src/jest.js +53 -40
  18. package/packages/datadog-instrumentations/src/kafkajs.js +21 -8
  19. package/packages/datadog-instrumentations/src/mocha/main.js +33 -46
  20. package/packages/datadog-instrumentations/src/mocha/utils.js +76 -74
  21. package/packages/datadog-instrumentations/src/mysql2.js +3 -1
  22. package/packages/datadog-instrumentations/src/net.js +27 -29
  23. package/packages/datadog-instrumentations/src/next.js +6 -14
  24. package/packages/datadog-instrumentations/src/pg.js +15 -7
  25. package/packages/datadog-instrumentations/src/playwright.js +64 -67
  26. package/packages/datadog-instrumentations/src/url.js +9 -17
  27. package/packages/datadog-instrumentations/src/vitest.js +66 -72
  28. package/packages/datadog-plugin-confluentinc-kafka-javascript/src/batch-consumer.js +11 -0
  29. package/packages/datadog-plugin-confluentinc-kafka-javascript/src/consumer.js +11 -0
  30. package/packages/datadog-plugin-confluentinc-kafka-javascript/src/index.js +19 -0
  31. package/packages/datadog-plugin-confluentinc-kafka-javascript/src/producer.js +11 -0
  32. package/packages/datadog-plugin-cucumber/src/index.js +32 -18
  33. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +3 -0
  34. package/packages/datadog-plugin-dns/src/lookup.js +10 -5
  35. package/packages/datadog-plugin-dns/src/lookup_service.js +6 -2
  36. package/packages/datadog-plugin-dns/src/resolve.js +5 -2
  37. package/packages/datadog-plugin-dns/src/reverse.js +6 -2
  38. package/packages/datadog-plugin-fs/src/index.js +9 -2
  39. package/packages/datadog-plugin-iovalkey/src/index.js +18 -0
  40. package/packages/datadog-plugin-jest/src/index.js +17 -8
  41. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +2 -1
  42. package/packages/datadog-plugin-kafkajs/src/consumer.js +12 -21
  43. package/packages/datadog-plugin-kafkajs/src/producer.js +12 -5
  44. package/packages/datadog-plugin-kafkajs/src/utils.js +27 -0
  45. package/packages/datadog-plugin-langchain/src/index.js +0 -1
  46. package/packages/datadog-plugin-mocha/src/index.js +58 -35
  47. package/packages/datadog-plugin-net/src/ipc.js +6 -4
  48. package/packages/datadog-plugin-net/src/tcp.js +15 -9
  49. package/packages/datadog-plugin-pg/src/index.js +5 -1
  50. package/packages/datadog-plugin-playwright/src/index.js +29 -20
  51. package/packages/datadog-plugin-redis/src/index.js +8 -3
  52. package/packages/datadog-plugin-vitest/src/index.js +67 -44
  53. package/packages/datadog-shimmer/src/shimmer.js +164 -33
  54. package/packages/dd-trace/src/appsec/api_security_sampler.js +20 -12
  55. package/packages/dd-trace/src/appsec/graphql.js +2 -2
  56. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +14 -9
  57. package/packages/dd-trace/src/appsec/index.js +15 -12
  58. package/packages/dd-trace/src/appsec/rasp/index.js +4 -2
  59. package/packages/dd-trace/src/appsec/rasp/utils.js +11 -6
  60. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +2 -2
  61. package/packages/dd-trace/src/appsec/telemetry/index.js +1 -2
  62. package/packages/dd-trace/src/appsec/telemetry/rasp.js +0 -9
  63. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +6 -6
  64. package/packages/dd-trace/src/baggage.js +36 -0
  65. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +4 -2
  66. package/packages/dd-trace/src/config.js +14 -2
  67. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +61 -7
  68. package/packages/dd-trace/src/debugger/devtools_client/index.js +10 -26
  69. package/packages/dd-trace/src/debugger/devtools_client/send.js +8 -7
  70. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +15 -7
  71. package/packages/dd-trace/src/debugger/devtools_client/state.js +22 -2
  72. package/packages/dd-trace/src/dogstatsd.js +2 -0
  73. package/packages/dd-trace/src/exporters/common/docker.js +13 -31
  74. package/packages/dd-trace/src/guardrails/telemetry.js +2 -5
  75. package/packages/dd-trace/src/llmobs/tagger.js +3 -3
  76. package/packages/dd-trace/src/llmobs/writers/base.js +33 -12
  77. package/packages/dd-trace/src/noop/proxy.js +5 -0
  78. package/packages/dd-trace/src/opentelemetry/context_manager.js +2 -0
  79. package/packages/dd-trace/src/opentracing/propagation/text_map.js +17 -9
  80. package/packages/dd-trace/src/plugin_manager.js +2 -0
  81. package/packages/dd-trace/src/plugins/index.js +4 -0
  82. package/packages/dd-trace/src/plugins/log_plugin.js +9 -20
  83. package/packages/dd-trace/src/plugins/outbound.js +11 -3
  84. package/packages/dd-trace/src/plugins/tracing.js +8 -4
  85. package/packages/dd-trace/src/plugins/util/test.js +1 -1
  86. package/packages/dd-trace/src/profiling/exporter_cli.js +1 -1
  87. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_lookup.js +1 -1
  88. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_lookupservice.js +1 -1
  89. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_resolve.js +2 -2
  90. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_reverse.js +1 -1
  91. package/packages/dd-trace/src/profiling/profilers/event_plugins/event.js +15 -14
  92. package/packages/dd-trace/src/proxy.js +12 -4
  93. package/packages/dd-trace/src/serverless.js +0 -48
  94. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +8 -0
  95. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +8 -0
  96. package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +8 -0
  97. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +4 -0
  98. package/packages/dd-trace/src/standalone/product.js +3 -5
@@ -0,0 +1,36 @@
1
+ 'use strict'
2
+
3
+ const { storage } = require('../../datadog-core')
4
+ const baggageStorage = storage('baggage')
5
+
6
+ function setBaggageItem (key, value) {
7
+ storage('baggage').enterWith({ ...baggageStorage.getStore(), [key]: value })
8
+ return storage('baggage').getStore()
9
+ }
10
+
11
+ function getBaggageItem (key) {
12
+ return storage('baggage').getStore()?.[key]
13
+ }
14
+
15
+ function getAllBaggageItems () {
16
+ return storage('baggage').getStore()
17
+ }
18
+
19
+ function removeBaggageItem (keyToRemove) {
20
+ const { [keyToRemove]: _, ...newBaggage } = storage('baggage').getStore()
21
+ storage('baggage').enterWith(newBaggage)
22
+ return newBaggage
23
+ }
24
+
25
+ function removeAllBaggageItems () {
26
+ storage('baggage').enterWith({})
27
+ return storage('baggage').getStore()
28
+ }
29
+
30
+ module.exports = {
31
+ setBaggageItem,
32
+ getBaggageItem,
33
+ getAllBaggageItems,
34
+ removeBaggageItem,
35
+ removeAllBaggageItems
36
+ }
@@ -7,7 +7,8 @@ function getTestManagementTests ({
7
7
  evpProxyPrefix,
8
8
  isGzipCompatible,
9
9
  repositoryUrl,
10
- commitMessage
10
+ commitMessage,
11
+ sha
11
12
  }, done) {
12
13
  const options = {
13
14
  path: '/api/v2/test/libraries/test-management/tests',
@@ -41,7 +42,8 @@ function getTestManagementTests ({
41
42
  type: 'ci_app_libraries_tests_request',
42
43
  attributes: {
43
44
  repository_url: repositoryUrl,
44
- commit_message: commitMessage
45
+ commit_message: commitMessage,
46
+ sha
45
47
  }
46
48
  }
47
49
  })
@@ -139,7 +139,7 @@ const qsRegex = '(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private
139
139
  // eslint-disable-next-line @stylistic/js/max-len
140
140
  const defaultWafObfuscatorKeyRegex = '(?i)pass|pw(?:or)?d|secret|(?:api|private|public|access)[_-]?key|token|consumer[_-]?(?:id|key|secret)|sign(?:ed|ature)|bearer|authorization|jsessionid|phpsessid|asp\\.net[_-]sessionid|sid|jwt'
141
141
  // eslint-disable-next-line @stylistic/js/max-len
142
- 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,}'
142
+ 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*("[^"]+"|\\d+))|bearer\\s+([a-z0-9\\._\\-]+)|token\\s*:\\s*([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,})'
143
143
  const runtimeId = uuid()
144
144
 
145
145
  function maybeFile (filepath) {
@@ -481,7 +481,8 @@ class Config {
481
481
  this._setValue(defaults, 'clientIpEnabled', false)
482
482
  this._setValue(defaults, 'clientIpHeader', null)
483
483
  this._setValue(defaults, 'crashtracking.enabled', true)
484
- this._setValue(defaults, 'codeOriginForSpans.enabled', false)
484
+ this._setValue(defaults, 'codeOriginForSpans.enabled', true)
485
+ this._setValue(defaults, 'codeOriginForSpans.experimental.exit_spans.enabled', false)
485
486
  this._setValue(defaults, 'dbmPropagationMode', 'disabled')
486
487
  this._setValue(defaults, 'dogstatsd.hostname', '127.0.0.1')
487
488
  this._setValue(defaults, 'dogstatsd.port', '8125')
@@ -660,6 +661,7 @@ class Config {
660
661
  DD_APPSEC_WAF_TIMEOUT,
661
662
  DD_CRASHTRACKING_ENABLED,
662
663
  DD_CODE_ORIGIN_FOR_SPANS_ENABLED,
664
+ DD_CODE_ORIGIN_FOR_SPANS_EXPERIMENTAL_EXIT_SPANS_ENABLED,
663
665
  DD_DATA_STREAMS_ENABLED,
664
666
  DD_DBM_PROPAGATION_MODE,
665
667
  DD_DOGSTATSD_HOSTNAME,
@@ -825,6 +827,11 @@ class Config {
825
827
  !this._isInServerlessEnvironment()
826
828
  ))
827
829
  this._setBoolean(env, 'codeOriginForSpans.enabled', DD_CODE_ORIGIN_FOR_SPANS_ENABLED)
830
+ this._setBoolean(
831
+ env,
832
+ 'codeOriginForSpans.experimental.exit_spans.enabled',
833
+ DD_CODE_ORIGIN_FOR_SPANS_EXPERIMENTAL_EXIT_SPANS_ENABLED
834
+ )
828
835
  this._setString(env, 'dbmPropagationMode', DD_DBM_PROPAGATION_MODE)
829
836
  this._setString(env, 'dogstatsd.hostname', DD_DOGSTATSD_HOST || DD_DOGSTATSD_HOSTNAME)
830
837
  this._setString(env, 'dogstatsd.port', DD_DOGSTATSD_PORT)
@@ -1029,6 +1036,11 @@ class Config {
1029
1036
  this._setValue(opts, 'baggageMaxBytes', options.baggageMaxBytes)
1030
1037
  this._setValue(opts, 'baggageMaxItems', options.baggageMaxItems)
1031
1038
  this._setBoolean(opts, 'codeOriginForSpans.enabled', options.codeOriginForSpans?.enabled)
1039
+ this._setBoolean(
1040
+ opts,
1041
+ 'codeOriginForSpans.experimental.exit_spans.enabled',
1042
+ options.codeOriginForSpans?.experimental?.exit_spans?.enabled
1043
+ )
1032
1044
  this._setString(opts, 'dbmPropagationMode', options.dbmPropagationMode)
1033
1045
  if (options.dogstatsd) {
1034
1046
  this._setString(opts, 'dogstatsd.hostname', options.dogstatsd.hostname)
@@ -5,10 +5,35 @@ const { getGeneratedPosition } = require('./source-maps')
5
5
  const session = require('./session')
6
6
  const { compile: compileCondition, compileSegments, templateRequiresEvaluation } = require('./condition')
7
7
  const { MAX_SNAPSHOTS_PER_SECOND_PER_PROBE, MAX_NON_SNAPSHOTS_PER_SECOND_PER_PROBE } = require('./defaults')
8
- const { findScriptFromPartialPath, locationToBreakpoint, breakpointToProbes, probeToLocation } = require('./state')
8
+ const {
9
+ findScriptFromPartialPath,
10
+ clearState,
11
+ locationToBreakpoint,
12
+ breakpointToProbes,
13
+ probeToLocation
14
+ } = require('./state')
9
15
  const log = require('../../log')
10
16
 
11
17
  let sessionStarted = false
18
+ const probes = new Map()
19
+ let scriptLoadingStabilizedResolve
20
+ const scriptLoadingStabilized = new Promise((resolve) => { scriptLoadingStabilizedResolve = resolve })
21
+
22
+ // There's a race condition when a probe is first added, where the actual script that the probe is supposed to match
23
+ // hasn't been loaded yet. This will result in either the probe not being added at all, or an incorrect script being
24
+ // matched as the probe target.
25
+ //
26
+ // Therefore, once new scripts has been loaded, all probes are re-evaluated. If the matched `scriptId` has changed, we
27
+ // simply remove the old probe (if it was added to the wrong script) and apply it again.
28
+ session.on('scriptLoadingStabilized', () => {
29
+ log.debug('[debugger:devtools_client] Re-evaluating probes')
30
+ scriptLoadingStabilizedResolve()
31
+ for (const probe of probes.values()) {
32
+ reEvaluateProbe(probe).catch(err => {
33
+ log.error('[debugger:devtools_client] Error re-evaluating probe %s', probe.id, err)
34
+ })
35
+ }
36
+ })
12
37
 
13
38
  module.exports = {
14
39
  addBreakpoint,
@@ -18,13 +43,14 @@ module.exports = {
18
43
  async function addBreakpoint (probe) {
19
44
  if (!sessionStarted) await start()
20
45
 
46
+ probes.set(probe.id, probe)
47
+
21
48
  const file = probe.where.sourceFile
22
49
  let lineNumber = Number(probe.where.lines[0]) // Tracer doesn't support multiple-line breakpoints
23
50
  let columnNumber = 0 // Probes do not contain/support column information
24
51
 
25
52
  // Optimize for sending data to /debugger/v1/input endpoint
26
53
  probe.location = { file, lines: [String(lineNumber)] }
27
- delete probe.where
28
54
 
29
55
  // Optimize for fast calculations when probe is hit
30
56
  probe.templateRequiresEvaluation = templateRequiresEvaluation(probe.segments)
@@ -47,6 +73,8 @@ async function addBreakpoint (probe) {
47
73
  if (!script) throw new Error(`No loaded script found for ${file} (probe: ${probe.id}, version: ${probe.version})`)
48
74
  const { url, scriptId, sourceMapURL, source } = script
49
75
 
76
+ probe.scriptId = scriptId // Needed for detecting script changes during re-evaluation
77
+
50
78
  if (sourceMapURL) {
51
79
  log.debug(
52
80
  '[debugger:devtools_client] Translating location using source map for %s:%d:%d (probe: %s, version: %d)',
@@ -109,6 +137,8 @@ async function removeBreakpoint ({ id }) {
109
137
  throw Error(`Unknown probe id: ${id}`)
110
138
  }
111
139
 
140
+ probes.delete(id)
141
+
112
142
  const release = await lock()
113
143
 
114
144
  try {
@@ -173,23 +203,47 @@ async function updateBreakpoint (breakpoint, probe) {
173
203
  }
174
204
  }
175
205
 
176
- function start () {
206
+ async function reEvaluateProbe (probe) {
207
+ const script = findScriptFromPartialPath(probe.where.sourceFile)
208
+ log.debug('[debugger:devtools_client] re-evaluating probe %s: %s => %s', probe.id, probe.scriptId, script?.scriptId)
209
+
210
+ if (probe.scriptId !== script?.scriptId) {
211
+ log.debug('[debugger:devtools_client] Better match found for probe %s, re-evaluating', probe.id)
212
+ if (probeToLocation.has(probe.id)) {
213
+ await removeBreakpoint(probe)
214
+ }
215
+ await addBreakpoint(probe)
216
+ }
217
+ }
218
+
219
+ async function start () {
177
220
  sessionStarted = true
178
- return session.post('Debugger.enable')
221
+ log.debug('[debugger:devtools_client] Starting debugger')
222
+ await session.post('Debugger.enable')
223
+
224
+ // Wait until there's a pause in script-loading to avoid accidentally adding probes to incorrect scripts. This is not
225
+ // a guarantee, but best effort.
226
+ log.debug('[debugger:devtools_client] Waiting for script-loading to stabilize')
227
+ await scriptLoadingStabilized
228
+ log.debug('[debugger:devtools_client] Script loading stabilized')
179
229
  }
180
230
 
181
231
  function stop () {
182
232
  sessionStarted = false
233
+ clearState()
234
+ log.debug('[debugger:devtools_client] Stopping debugger')
183
235
  return session.post('Debugger.disable')
184
236
  }
185
237
 
186
238
  // Only if all probes have a condition can we use a compound condition.
187
239
  // Otherwise, we need to evaluate each probe individually once the breakpoint is hit.
188
- // TODO: Handle errors - if there's 2 conditons, and one fails but the other returns true, we should still pause the
189
- // breakpoint
190
240
  function compileCompoundCondition (probes) {
241
+ if (probes.length === 1) return probes[0].condition
242
+
191
243
  return probes.every(p => p.condition)
192
- ? probes.map(p => p.condition).filter(Boolean).join(' || ')
244
+ ? probes
245
+ .map((p) => `(() => { try { return ${p.condition} } catch { return false } })()`)
246
+ .join(' || ')
193
247
  : undefined
194
248
  }
195
249
 
@@ -6,7 +6,7 @@ const session = require('./session')
6
6
  const { getLocalStateForCallFrame } = require('./snapshot')
7
7
  const send = require('./send')
8
8
  const { getStackFromCallFrames } = require('./state')
9
- const { ackEmitting, ackError } = require('./status')
9
+ const { ackEmitting } = require('./status')
10
10
  const { parentThreadId } = require('./config')
11
11
  const { MAX_SNAPSHOTS_PER_SECOND_GLOBALLY } = require('./defaults')
12
12
  const log = require('../../log')
@@ -36,7 +36,6 @@ const getDDTagsExpression = `(() => {
36
36
  const threadId = parentThreadId === 0 ? `pid:${process.pid}` : `pid:${process.pid};tid:${parentThreadId}`
37
37
  const threadName = parentThreadId === 0 ? 'MainThread' : `WorkerThread:${parentThreadId}`
38
38
 
39
- const SUPPORT_ITERATOR_METHODS = NODE_MAJOR >= 22
40
39
  const SUPPORT_ARRAY_BUFFER_RESIZE = NODE_MAJOR >= 20
41
40
  const oneSecondNs = 1_000_000_000n
42
41
  let globalSnapshotSamplingRateWindowStart = 0n
@@ -77,14 +76,6 @@ session.on('Debugger.paused', async ({ params }) => {
77
76
  }
78
77
  }
79
78
 
80
- // If all the probes have a condition, we know that it triggered. If at least one probe doesn't have a condition, we
81
- // need to verify which conditions are met.
82
- const shouldVerifyConditions = (
83
- SUPPORT_ITERATOR_METHODS
84
- ? probesAtLocation.values()
85
- : Array.from(probesAtLocation.values())
86
- ).some((probe) => probe.condition === undefined)
87
-
88
79
  for (const probe of probesAtLocation.values()) {
89
80
  if (start - probe.lastCaptureNs < probe.nsBetweenSampling) {
90
81
  continue
@@ -109,7 +100,7 @@ session.on('Debugger.paused', async ({ params }) => {
109
100
  maxLength = highestOrUndefined(probe.capture.maxLength, maxLength)
110
101
  }
111
102
 
112
- if (shouldVerifyConditions && probe.condition !== undefined) {
103
+ if (probe.condition !== undefined) {
113
104
  // TODO: Bundle all conditions and evaluate them in a single call
114
105
  // TODO: Handle errors
115
106
  const { result } = await session.post('Debugger.evaluateOnCallFrame', {
@@ -153,20 +144,11 @@ session.on('Debugger.paused', async ({ params }) => {
153
144
  evalResults = result?.value ?? []
154
145
  }
155
146
 
156
- let processLocalState
157
- if (numberOfProbesWithSnapshots !== 0) {
158
- try {
159
- // TODO: Create unique states for each affected probe based on that probes unique `capture` settings (DEBUG-2863)
160
- processLocalState = await getLocalStateForCallFrame(
161
- params.callFrames[0],
162
- { maxReferenceDepth, maxCollectionSize, maxFieldCount, maxLength }
163
- )
164
- } catch (err) {
165
- for (let i = 0; i < numberOfProbesWithSnapshots; i++) {
166
- ackError(err, probes[snapshotProbeIndex[i]]) // TODO: Ok to continue after sending ackError?
167
- }
168
- }
169
- }
147
+ // TODO: Create unique states for each affected probe based on that probes unique `capture` settings (DEBUG-2863)
148
+ const processLocalState = numberOfProbesWithSnapshots !== 0 && await getLocalStateForCallFrame(
149
+ params.callFrames[0],
150
+ { maxReferenceDepth, maxCollectionSize, maxFieldCount, maxLength }
151
+ )
170
152
 
171
153
  await session.post('Debugger.resume')
172
154
  const diff = process.hrtime.bigint() - start // TODO: Recored as telemetry (DEBUG-2858)
@@ -206,7 +188,9 @@ session.on('Debugger.paused', async ({ params }) => {
206
188
 
207
189
  if (probe.captureSnapshot) {
208
190
  const state = processLocalState()
209
- if (state) {
191
+ if (state instanceof Error) {
192
+ snapshot.captureError = state.message
193
+ } else if (state) {
210
194
  snapshot.captures = {
211
195
  lines: { [probe.location.lines[0]]: { locals: state } }
212
196
  }
@@ -13,7 +13,8 @@ const { version } = require('../../../../../package.json')
13
13
  module.exports = send
14
14
 
15
15
  const MAX_MESSAGE_LENGTH = 8 * 1024 // 8KB
16
- const MAX_LOG_PAYLOAD_SIZE = 1024 * 1024 // 1MB
16
+ const MAX_LOG_PAYLOAD_SIZE_MB = 1
17
+ const MAX_LOG_PAYLOAD_SIZE_BYTES = MAX_LOG_PAYLOAD_SIZE_MB * 1024 * 1024
17
18
 
18
19
  const ddsource = 'dd_debugger'
19
20
  const hostname = getHostname()
@@ -48,13 +49,13 @@ function send (message, logger, dd, snapshot) {
48
49
  let json = JSON.stringify(payload)
49
50
  let size = Buffer.byteLength(json)
50
51
 
51
- if (size > MAX_LOG_PAYLOAD_SIZE) {
52
+ if (size > MAX_LOG_PAYLOAD_SIZE_BYTES) {
52
53
  // TODO: This is a very crude way to handle large payloads. Proper pruning will be implemented later (DEBUG-2624)
53
- const line = Object.values(payload.debugger.snapshot.captures.lines)[0]
54
- line.locals = {
55
- notCapturedReason: 'Snapshot was too large',
56
- size: Object.keys(line.locals).length
57
- }
54
+ delete payload.debugger.snapshot.captures
55
+ payload.debugger.snapshot.captureError =
56
+ `Snapshot was too large (max allowed size is ${MAX_LOG_PAYLOAD_SIZE_MB} MiB). ` +
57
+ 'Consider reducing the capture depth or turn off "Capture Variables" completely, ' +
58
+ 'and instead include the variables of interest directly in the message template.'
58
59
  json = JSON.stringify(payload)
59
60
  size = Buffer.byteLength(json)
60
61
  }
@@ -2,6 +2,7 @@
2
2
 
3
3
  const { getRuntimeObject } = require('./collector')
4
4
  const { processRawState } = require('./processor')
5
+ const log = require('../../../log')
5
6
 
6
7
  const DEFAULT_MAX_REFERENCE_DEPTH = 3
7
8
  const DEFAULT_MAX_COLLECTION_SIZE = 100
@@ -24,13 +25,20 @@ async function getLocalStateForCallFrame (
24
25
  const rawState = []
25
26
  let processedState = null
26
27
 
27
- await Promise.all(callFrame.scopeChain.map(async (scope) => {
28
- if (scope.type === 'global') return // The global scope is too noisy
29
- rawState.push(...await getRuntimeObject(
30
- scope.object.objectId,
31
- { maxReferenceDepth, maxCollectionSize, maxFieldCount }
32
- ))
33
- }))
28
+ try {
29
+ await Promise.all(callFrame.scopeChain.map(async (scope) => {
30
+ if (scope.type === 'global') return // The global scope is too noisy
31
+ rawState.push(...await getRuntimeObject(
32
+ scope.object.objectId,
33
+ { maxReferenceDepth, maxCollectionSize, maxFieldCount }
34
+ ))
35
+ }))
36
+ } catch (err) {
37
+ // TODO: We might be able to get part of the scope chain.
38
+ // Consider if we could set errors just for the part of the scope chain that throws during collection.
39
+ log.error('[debugger:devtools_client] Error getting local state for call frame', err)
40
+ return () => new Error('Error getting local state')
41
+ }
34
42
 
35
43
  // Deplay calling `processRawState` so the caller gets a chance to resume the main thread before processing `rawState`
36
44
  return () => {
@@ -9,12 +9,16 @@ const WINDOWS_DRIVE_LETTER_REGEX = /[a-zA-Z]/
9
9
 
10
10
  const loadedScripts = []
11
11
  const scriptUrls = new Map()
12
+ let reEvaluateProbesTimer = null
12
13
 
13
14
  module.exports = {
14
15
  locationToBreakpoint: new Map(),
15
16
  breakpointToProbes: new Map(),
16
17
  probeToLocation: new Map(),
17
18
 
19
+ _loadedScripts: loadedScripts, // Only exposed for testing
20
+ _scriptUrls: scriptUrls, // Only exposed for testing
21
+
18
22
  /**
19
23
  * Find the script to inspect based on a partial or absolute path. Handles both Windows and POSIX paths.
20
24
  *
@@ -88,11 +92,15 @@ module.exports = {
88
92
  }
89
93
  }
90
94
 
91
- return maxMatchLength > -1 ? bestMatch : null
95
+ return maxMatchLength !== -1 ? bestMatch : null
92
96
  },
93
97
 
94
98
  getStackFromCallFrames (callFrames) {
95
99
  return callFrames.map((frame) => {
100
+ // TODO: Possible race condition: If the breakpoint is in the process of being removed, and this is the last
101
+ // breakpoint, it will also stop the debugging session, which in turn will clear the state, which means clearing
102
+ // the `scriptUrls` map. That might result in this the `scriptUrls.get` call above returning `undefined`, which
103
+ // will throw when `startsWith` is called on it.
96
104
  let fileName = scriptUrls.get(frame.location.scriptId)
97
105
  if (fileName.startsWith('file://')) fileName = fileName.substr(7) // TODO: This might not be required
98
106
  return {
@@ -102,6 +110,14 @@ module.exports = {
102
110
  columnNumber: frame.location.columnNumber + 1 // Beware! columnNumber is zero-indexed
103
111
  }
104
112
  })
113
+ },
114
+
115
+ // The maps locationToBreakpoint, breakpointToProbes, and probeToLocation are always updated when breakpoints are
116
+ // removed. Therefore they do not need to get manually cleared. Only the state internal to this file needs to be
117
+ // cleared.
118
+ clearState () {
119
+ loadedScripts.length = 0
120
+ scriptUrls.clear()
105
121
  }
106
122
  }
107
123
 
@@ -112,7 +128,6 @@ module.exports = {
112
128
  // Unknown params.url values:
113
129
  // - `structured-stack` - Not sure what this is, but should just be ignored
114
130
  // - `` - Not sure what this is, but should just be ignored
115
- // TODO: Event fired for all files, every time debugger is enabled. So when we disable it, we need to reset the state
116
131
  session.on('Debugger.scriptParsed', ({ params }) => {
117
132
  scriptUrls.set(params.scriptId, params.url)
118
133
  if (params.url.startsWith('file:')) {
@@ -142,5 +157,10 @@ session.on('Debugger.scriptParsed', ({ params }) => {
142
157
  } else {
143
158
  loadedScripts.push(params)
144
159
  }
160
+
161
+ clearTimeout(reEvaluateProbesTimer)
162
+ reEvaluateProbesTimer = setTimeout(() => {
163
+ session.emit('scriptLoadingStabilized')
164
+ }, 500)
145
165
  }
146
166
  })
@@ -392,6 +392,8 @@ class CustomMetrics {
392
392
  * These are translated into [ 'tagName:tagValue' ] for internal use
393
393
  */
394
394
  static tagTranslator (objTags) {
395
+ if (Array.isArray(objTags)) return objTags
396
+
395
397
  const arrTags = []
396
398
 
397
399
  if (!objTags) return arrTags
@@ -10,43 +10,25 @@ const uuidSource =
10
10
  '[0-9a-f]{8}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{12}|[0-9a-f]{8}(?:-[0-9a-f]{4}){4}$'
11
11
  const containerSource = '[0-9a-f]{64}'
12
12
  const taskSource = '[0-9a-f]{32}-\\d+'
13
- const lineReg = /^(\d+):([^:]*):(.+)$/
13
+ const lineReg = /^(\d+):([^:]*):(.+)$/m
14
14
  const entityReg = new RegExp(`.*(${uuidSource}|${containerSource}|${taskSource})(?:\\.scope)?$`, 'm')
15
15
 
16
- const cgroup = readControlGroup()
17
- const entityId = getEntityId()
18
- const inode = getInode()
16
+ let inode = 0
17
+ let cgroup = ''
18
+ let entityId
19
19
 
20
- function getEntityId () {
21
- const match = cgroup.match(entityReg) || []
20
+ try {
21
+ cgroup = fs.readFileSync('/proc/self/cgroup', 'utf8').trim()
22
+ entityId = cgroup.match(entityReg)?.[1]
23
+ } catch { /* Ignore error */ }
22
24
 
23
- return match[1]
24
- }
25
-
26
- function getInode () {
27
- const match = cgroup.match(lineReg) || []
28
-
29
- return readInode(match[3])
30
- }
25
+ const inodePath = cgroup.match(lineReg)?.[3]
26
+ if (inodePath) {
27
+ const strippedPath = inodePath.replace(/^\/|\/$/g, '')
31
28
 
32
- function readControlGroup () {
33
29
  try {
34
- return fs.readFileSync('/proc/self/cgroup').toString().trim()
35
- } catch (err) {
36
- return ''
37
- }
38
- }
39
-
40
- function readInode (path) {
41
- if (!path) return 0
42
-
43
- const strippedPath = path.replace(/^\//, '').replace(/\/$/, '')
44
-
45
- try {
46
- return fs.statSync(`/sys/fs/cgroup/${strippedPath}`).ino
47
- } catch (err) {
48
- return 0
49
- }
30
+ inode = fs.statSync(`/sys/fs/cgroup/${strippedPath}`).ino
31
+ } catch { /* Ignore error */ }
50
32
  }
51
33
 
52
34
  module.exports = {
@@ -14,11 +14,8 @@ if (!process.env.DD_INJECTION_ENABLED) {
14
14
  module.exports = function () {}
15
15
  }
16
16
 
17
- if (!process.env.DD_TELEMETRY_FORWARDER_PATH) {
18
- module.exports = function () {}
19
- }
20
-
21
- if (!fs.existsSync(process.env.DD_TELEMETRY_FORWARDER_PATH)) {
17
+ var telemetryForwarderPath = process.env.DD_TELEMETRY_FORWARDER_PATH
18
+ if (typeof telemetryForwarderPath !== 'string' || !fs.existsSync(telemetryForwarderPath)) {
22
19
  module.exports = function () {}
23
20
  }
24
21
 
@@ -148,12 +148,12 @@ class LLMObsTagger {
148
148
  }
149
149
 
150
150
  tagSpanTags (span, tags) {
151
- // new tags will be merged with existing tags
152
151
  const currentTags = registry.get(span)?.[TAGS]
153
152
  if (currentTags) {
154
- Object.assign(tags, currentTags)
153
+ Object.assign(currentTags, tags)
154
+ } else {
155
+ this._setTag(span, TAGS, tags)
155
156
  }
156
- this._setTag(span, TAGS, tags)
157
157
  }
158
158
 
159
159
  changeKind (span, newKind) {
@@ -41,6 +41,18 @@ class BaseLLMObsWriter {
41
41
  this._destroyed = false
42
42
  }
43
43
 
44
+ get url () {
45
+ if (this._agentless == null) return null
46
+
47
+ const baseUrl = this._baseUrl.href
48
+ const endpoint = this._endpoint
49
+
50
+ // Split on protocol separator to preserve it
51
+ // path.join will remove some slashes unnecessarily
52
+ const [protocol, rest] = baseUrl.split('://')
53
+ return protocol + '://' + path.join(rest, endpoint)
54
+ }
55
+
44
56
  append (event, byteLength) {
45
57
  if (this._buffer.length >= this._bufferLimit) {
46
58
  logger.warn(`${this.constructor.name} event buffer full (limit is ${this._bufferLimit}), dropping event`)
@@ -69,7 +81,7 @@ class BaseLLMObsWriter {
69
81
  const options = this._getOptions()
70
82
 
71
83
  request(payload, options, (err, resp, code) => {
72
- parseResponseAndLog(err, code, events.length, options.url.href, this._eventType)
84
+ parseResponseAndLog(err, code, events.length, this.url, this._eventType)
73
85
  })
74
86
  }
75
87
 
@@ -87,17 +99,23 @@ class BaseLLMObsWriter {
87
99
 
88
100
  setAgentless (agentless) {
89
101
  this._agentless = agentless
90
- this._url = this._getUrl()
91
- logger.debug(`Configuring ${this.constructor.name} to ${this._url.href}`)
102
+ const { url, endpoint } = this._getUrlAndPath()
103
+
104
+ this._baseUrl = url
105
+ this._endpoint = endpoint
106
+
107
+ logger.debug(`Configuring ${this.constructor.name} to ${this.url}`)
92
108
  }
93
109
 
94
- _getUrl () {
110
+ _getUrlAndPath () {
95
111
  if (this._agentless) {
96
- return new URL(format({
97
- protocol: 'https:',
98
- hostname: `${this._intake}.${this._config.site}`,
99
- pathname: this._endpoint
100
- }))
112
+ return {
113
+ url: new URL(format({
114
+ protocol: 'https:',
115
+ hostname: `${this._intake}.${this._config.site}`
116
+ })),
117
+ endpoint: this._endpoint
118
+ }
101
119
  }
102
120
 
103
121
  const { hostname, port } = this._config
@@ -107,8 +125,10 @@ class BaseLLMObsWriter {
107
125
  port
108
126
  }))
109
127
 
110
- const proxyPath = path.join(EVP_PROXY_AGENT_BASE_PATH, this._endpoint)
111
- return new URL(proxyPath, base)
128
+ return {
129
+ url: base,
130
+ endpoint: path.join(EVP_PROXY_AGENT_BASE_PATH, this._endpoint)
131
+ }
112
132
  }
113
133
 
114
134
  _getOptions () {
@@ -118,7 +138,8 @@ class BaseLLMObsWriter {
118
138
  },
119
139
  method: 'POST',
120
140
  timeout: this._timeout,
121
- url: this._url
141
+ url: this._baseUrl,
142
+ path: this._endpoint
122
143
  }
123
144
 
124
145
  if (this._agentless) {
@@ -16,6 +16,11 @@ class NoopProxy {
16
16
  this.appsec = noopAppsec
17
17
  this.dogstatsd = noopDogStatsDClient
18
18
  this.llmobs = noopLLMObs
19
+ this.setBaggageItem = () => {}
20
+ this.getBaggageItem = () => {}
21
+ this.getAllBaggageItems = () => {}
22
+ this.removeBaggageItem = () => {}
23
+ this.removeAllBaggageItems = () => {}
19
24
  }
20
25
 
21
26
  init () {
@@ -12,6 +12,7 @@ class ContextManager {
12
12
  this._store = storage('opentelemetry')
13
13
  }
14
14
 
15
+ // converts dd to otel
15
16
  active () {
16
17
  const activeSpan = tracer.scope().active()
17
18
  const store = this._store.getStore()
@@ -54,6 +55,7 @@ class ContextManager {
54
55
  : wrappedContext
55
56
  }
56
57
 
58
+ // converts otel to dd
57
59
  with (context, fn, thisArg, ...args) {
58
60
  const span = trace.getSpan(context)
59
61
  const ddScope = tracer.scope()