dd-trace 6.18.0 → 6.20.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 (123) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/ci/vitest-no-worker-init-setup.mjs +22 -12
  3. package/index.d.ts +166 -24
  4. package/package.json +7 -7
  5. package/packages/datadog-esbuild/index.js +9 -6
  6. package/packages/datadog-instrumentations/src/ai.js +26 -0
  7. package/packages/datadog-instrumentations/src/anthropic.js +111 -9
  8. package/packages/datadog-instrumentations/src/aws-sdk.js +2 -2
  9. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +5 -1
  10. package/packages/datadog-instrumentations/src/cucumber.js +40 -4
  11. package/packages/datadog-instrumentations/src/helpers/hooks.js +0 -7
  12. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentation-registry.js +2 -2
  13. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +25 -0
  14. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +8 -0
  15. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +2 -0
  16. package/packages/datadog-instrumentations/src/jest/session-error.js +102 -0
  17. package/packages/datadog-instrumentations/src/jest.js +3 -10
  18. package/packages/datadog-instrumentations/src/playwright.js +26 -0
  19. package/packages/datadog-instrumentations/src/vitest-main.js +4 -1
  20. package/packages/datadog-instrumentations/src/vitest-worker.js +34 -3
  21. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +83 -29
  22. package/packages/datadog-plugin-cucumber/src/index.js +19 -4
  23. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +7 -0
  24. package/packages/datadog-plugin-fetch/src/index.js +3 -0
  25. package/packages/datadog-plugin-openai/src/stream-helpers.js +50 -1
  26. package/packages/datadog-plugin-oracledb/src/connection-parser.js +3 -1
  27. package/packages/datadog-plugin-undici/src/index.js +5 -0
  28. package/packages/dd-trace/src/aiguard/client.js +38 -19
  29. package/packages/dd-trace/src/aiguard/integrations/anthropic.js +29 -4
  30. package/packages/dd-trace/src/aiguard/integrations/index.js +1 -1
  31. package/packages/dd-trace/src/aiguard/integrations/openai.js +20 -56
  32. package/packages/dd-trace/src/aiguard/integrations/stream.js +60 -0
  33. package/packages/dd-trace/src/aiguard/messages/anthropic.js +64 -0
  34. package/packages/dd-trace/src/config/generated-config-types.d.ts +8 -4
  35. package/packages/dd-trace/src/config/remote_config.js +12 -0
  36. package/packages/dd-trace/src/config/supported-configurations.json +26 -5
  37. package/packages/dd-trace/src/constants.js +2 -0
  38. package/packages/dd-trace/src/debugger/constants.js +13 -4
  39. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +14 -1
  40. package/packages/dd-trace/src/debugger/devtools_client/condition.js +109 -6
  41. package/packages/dd-trace/src/debugger/devtools_client/config.js +2 -0
  42. package/packages/dd-trace/src/debugger/devtools_client/index.js +65 -6
  43. package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +22 -10
  44. package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +12 -8
  45. package/packages/dd-trace/src/debugger/devtools_client/send.js +14 -0
  46. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +43 -5
  47. package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +3 -8
  48. package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +2 -122
  49. package/packages/dd-trace/src/debugger/guardrail-metrics.js +0 -2
  50. package/packages/dd-trace/src/debugger/index.js +60 -28
  51. package/packages/dd-trace/src/debugger/inspect-segment.js +92 -23
  52. package/packages/dd-trace/src/debugger/pause-duration-histogram.js +74 -0
  53. package/packages/dd-trace/src/debugger/probe_sampler.js +192 -52
  54. package/packages/dd-trace/src/debugger/redaction.js +139 -0
  55. package/packages/dd-trace/src/knuth-hash.js +17 -0
  56. package/packages/dd-trace/src/llmobs/constants/tags.js +33 -8
  57. package/packages/dd-trace/src/llmobs/experiments/client.js +66 -2
  58. package/packages/dd-trace/src/llmobs/experiments/evaluator.js +138 -0
  59. package/packages/dd-trace/src/llmobs/experiments/experiment.js +213 -90
  60. package/packages/dd-trace/src/llmobs/experiments/index.js +40 -1
  61. package/packages/dd-trace/src/llmobs/experiments/noop.js +53 -2
  62. package/packages/dd-trace/src/llmobs/experiments/remote-evaluator.js +110 -0
  63. package/packages/dd-trace/src/llmobs/experiments/util.js +34 -6
  64. package/packages/dd-trace/src/llmobs/gen-ai-tags.js +127 -0
  65. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +18 -0
  66. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +33 -8
  67. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +77 -41
  68. package/packages/dd-trace/src/llmobs/plugins/base.js +154 -15
  69. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +162 -10
  70. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +65 -16
  71. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +14 -0
  72. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +34 -35
  73. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +10 -0
  74. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +25 -2
  75. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +8 -7
  76. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +15 -2
  77. package/packages/dd-trace/src/llmobs/plugins/openai/realtime.js +5 -0
  78. package/packages/dd-trace/src/llmobs/plugins/vertexai.js +7 -0
  79. package/packages/dd-trace/src/llmobs/prompts/prompt.js +2 -1
  80. package/packages/dd-trace/src/llmobs/sdk.js +19 -3
  81. package/packages/dd-trace/src/llmobs/span_processor.js +52 -91
  82. package/packages/dd-trace/src/llmobs/tagger.js +37 -36
  83. package/packages/dd-trace/src/llmobs/writers/base.js +1 -6
  84. package/packages/dd-trace/src/openfeature/constants/constants.js +10 -0
  85. package/packages/dd-trace/src/openfeature/flagging_provider.js +14 -1
  86. package/packages/dd-trace/src/openfeature/writers/base.js +39 -18
  87. package/packages/dd-trace/src/openfeature/writers/flag-eval-evp-hook.js +105 -0
  88. package/packages/dd-trace/src/openfeature/writers/flag-evaluation-aggregation.js +190 -0
  89. package/packages/dd-trace/src/openfeature/writers/flag-evaluation-consumer.js +233 -0
  90. package/packages/dd-trace/src/openfeature/writers/flag-evaluation-context.js +262 -0
  91. package/packages/dd-trace/src/openfeature/writers/flag-evaluation-payload.js +181 -0
  92. package/packages/dd-trace/src/openfeature/writers/flag-evaluation-pii.js +108 -0
  93. package/packages/dd-trace/src/openfeature/writers/flag-evaluation-telemetry.js +154 -0
  94. package/packages/dd-trace/src/openfeature/writers/flag-evaluation-worker.js +77 -0
  95. package/packages/dd-trace/src/openfeature/writers/flag-evaluations.js +398 -0
  96. package/packages/dd-trace/src/openfeature/writers/util.js +5 -4
  97. package/packages/dd-trace/src/opentelemetry/context_manager.js +3 -0
  98. package/packages/dd-trace/src/opentelemetry/span_context.js +6 -2
  99. package/packages/dd-trace/src/opentelemetry/trace/index.js +5 -0
  100. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +6 -0
  101. package/packages/dd-trace/src/opentelemetry/tracer.js +16 -18
  102. package/packages/dd-trace/src/opentracing/propagation/text_map.js +116 -25
  103. package/packages/dd-trace/src/opentracing/propagation/tracestate.js +80 -16
  104. package/packages/dd-trace/src/opentracing/span.js +14 -9
  105. package/packages/dd-trace/src/opentracing/tracer.js +10 -3
  106. package/packages/dd-trace/src/otel-sampling.js +171 -0
  107. package/packages/dd-trace/src/plugins/index.js +0 -1
  108. package/packages/dd-trace/src/plugins/util/test.js +4 -0
  109. package/packages/dd-trace/src/priority_sampler.js +155 -33
  110. package/packages/dd-trace/src/profiler.js +51 -5
  111. package/packages/dd-trace/src/profiling/profiler.js +24 -20
  112. package/packages/dd-trace/src/remote_config/capabilities.js +9 -0
  113. package/packages/dd-trace/src/ritm.js +12 -9
  114. package/packages/dd-trace/src/sampler.js +2 -5
  115. package/packages/dd-trace/src/sampling_rule.js +4 -8
  116. package/packages/dd-trace/src/span_format.js +6 -0
  117. package/packages/dd-trace/src/span_processor.js +32 -3
  118. package/packages/dd-trace/src/standalone/index.js +5 -4
  119. package/packages/dd-trace/src/standalone/tracesource_priority_sampler.js +25 -5
  120. package/packages/dd-trace/src/telemetry/metrics.js +4 -3
  121. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
  122. package/packages/datadog-instrumentations/src/postgres.js +0 -7
  123. package/packages/datadog-instrumentations/src/supabase.js +0 -15
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { workerData: { probePort } } = require('node:worker_threads')
4
+ const { WORKER_ERROR_REASON } = require('../constants')
4
5
  const { addBreakpoint, removeBreakpoint, modifyBreakpoint } = require('./breakpoints')
5
6
  const { ackReceived, ackInstalled, ackError } = require('./status')
6
7
  const log = require('./log')
@@ -54,7 +55,8 @@ probePort.on('message', async ({ action, probe, ackId }) => {
54
55
  await processMsg(action, probe)
55
56
  probePort.postMessage({ ackId })
56
57
  } catch (err) {
57
- probePort.postMessage({ ackId, error: err })
58
+ // Structured cloning an Error drops its custom properties, so send the reason separately.
59
+ probePort.postMessage({ ackId, error: err, reason: err.reason })
58
60
  ackError(err, probe)
59
61
  }
60
62
  })
@@ -72,18 +74,20 @@ async function processMsg (action, probe) {
72
74
  if (action !== 'unapply') ackReceived(probe)
73
75
 
74
76
  if (probe.type !== 'LOG_PROBE') {
75
- throw new Error(`Unsupported probe type: ${probe.type} (id: ${probe.id}, version: ${probe.version})`)
77
+ throw Object.assign(new Error(
78
+ `Unsupported probe type: ${probe.type} (id: ${probe.id}, version: ${probe.version})`
79
+ ), { reason: WORKER_ERROR_REASON.UNSUPPORTED_PROBE_TYPE })
76
80
  }
77
81
  if (!probe.where.sourceFile && !probe.where.lines) {
78
- throw new Error(
82
+ throw Object.assign(new Error(
79
83
  // eslint-disable-next-line @stylistic/max-len
80
84
  `Unsupported probe insertion point! Only line-based probes are supported (id: ${probe.id}, version: ${probe.version})`
81
- )
85
+ ), { reason: WORKER_ERROR_REASON.UNSUPPORTED_INSERTION_POINT })
82
86
  }
83
87
  if (probe.captureSnapshot && probe.captureExpressions?.length > 0) {
84
- throw new Error(
88
+ throw Object.assign(new Error(
85
89
  `Cannot set both captureSnapshot and captureExpressions (probe: ${probe.id}, version: ${probe.version})`
86
- )
90
+ ), { reason: WORKER_ERROR_REASON.CONFLICTING_CAPTURE_OPTIONS })
87
91
  }
88
92
 
89
93
  switch (action) {
@@ -99,8 +103,8 @@ async function processMsg (action, probe) {
99
103
  ackInstalled(probe)
100
104
  break
101
105
  default:
102
- throw new Error(
106
+ throw Object.assign(new Error(
103
107
  `Cannot process probe ${probe.id} (version: ${probe.version}) - unknown remote configuration action: ${action}`
104
- )
108
+ ), { reason: WORKER_ERROR_REASON.UNKNOWN_REMOTE_CONFIG_ACTION })
105
109
  }
106
110
  }
@@ -85,6 +85,20 @@ function send (message, logger, dd, snapshot, processTags, eventType, incomplete
85
85
  json = JSON.stringify(payload)
86
86
  }
87
87
  size = Buffer.byteLength(json)
88
+
89
+ if (size > MAX_LOG_PAYLOAD_SIZE_BYTES) {
90
+ // Trimming is best-effort: the pruner can only replace leaf nodes deep inside the snapshot and the fallback only
91
+ // drops captures, so a payload whose bytes sit elsewhere - a very deep stack, say - can stay over the cap. There
92
+ // is nothing to gain from sending it: the intake truncates the log message, then throws the snapshot away, and
93
+ // drops the event anyway if that still doesn't fit.
94
+ log.debug(
95
+ '[debugger:devtools_client] Dropping probe result for probe %s: payload is too large (%d bytes)',
96
+ snapshot.probe.id,
97
+ size
98
+ )
99
+ guardrailMetrics.eventDropped(DROPPED_REASON.PAYLOAD_TOO_LARGE, eventType)
100
+ return
101
+ }
88
102
  }
89
103
 
90
104
  if (jsonBuffer.write(json, size)) {
@@ -106,6 +106,13 @@ function isCollectable (scope) {
106
106
  * @property {CaptureLimits} limits - Fully resolved capture limits (precomputed at probe setup)
107
107
  */
108
108
 
109
+ /**
110
+ * @typedef {object} RedactedCaptureExpression
111
+ * @property {string} name - The name of the expression
112
+ * @property {{ expr: string, message: string }} redactionError - Reported instead of evaluating the expression, as it
113
+ * reads a redacted identifier
114
+ */
115
+
109
116
  /**
110
117
  * @typedef {object} CaptureExpressionResult
111
118
  * @property {() => Record<string, ReturnType<typeof processRemoteObject>>} processCaptureExpressions - Callback to
@@ -116,6 +123,7 @@ function isCollectable (scope) {
116
123
  * @property {import('./processor').IncompleteCapture} incomplete - The capture limits enforced on the expressions,
117
124
  * including a runtime error for every expression that threw or could not be evaluated. Only fully populated once
118
125
  * `processCaptureExpressions` has run.
126
+ * @property {boolean} timedOut - Whether evaluating an expression exceeded the evaluation time budget
119
127
  */
120
128
 
121
129
  /**
@@ -130,12 +138,20 @@ function isCollectable (scope) {
130
138
  * Collects raw data while paused, returns a callback to process after resume.
131
139
  *
132
140
  * @param {import('inspector').Debugger.CallFrame} callFrame - The call frame to evaluate expressions on
133
- * @param {CompiledCaptureExpression[]} expressions - The compiled expressions with precomputed capture limits
141
+ * @param {(CompiledCaptureExpression|RedactedCaptureExpression)[]} expressions - The compiled expressions with
142
+ * precomputed capture limits
134
143
  * @param {bigint} [deadlineNs] - The deadline in nanoseconds. Defaults to {@link BIGINT_MAX}. If the deadline is
135
144
  * reached, the snapshot will be truncated.
145
+ * @param {bigint} [evaluationTimeoutNs] - The time budget in nanoseconds for evaluating a single expression. Defaults
146
+ * to {@link BIGINT_MAX}. Evaluation can't be interrupted, so an exceeded budget is reported as an evaluation error.
136
147
  * @returns {Promise<CaptureExpressionResult>} Raw results with deferred processing callback
137
148
  */
138
- async function evaluateCaptureExpressions (callFrame, expressions, deadlineNs = BIGINT_MAX) {
149
+ async function evaluateCaptureExpressions (
150
+ callFrame,
151
+ expressions,
152
+ deadlineNs = BIGINT_MAX,
153
+ evaluationTimeoutNs = BIGINT_MAX
154
+ ) {
139
155
  /** @type {{ name: string, remoteObject: object, maxLength: number }[]} */
140
156
  const rawResults = []
141
157
  /** @type {{ expr: string, message: string }[]} */
@@ -146,12 +162,20 @@ async function evaluateCaptureExpressions (callFrame, expressions, deadlineNs =
146
162
  const incomplete = { reasons: 0 }
147
163
  /** @type {Record<string, ReturnType<typeof processRemoteObject>> | null} */
148
164
  let processedResult = null
165
+ let timedOut = false
149
166
 
150
167
  for (let i = 0; i < expressions.length; i++) {
151
- const { name, expression, limits } = expressions[i]
168
+ const captureExpression = expressions[i]
169
+ if ('redactionError' in captureExpression) {
170
+ evaluationErrors.push(captureExpression.redactionError)
171
+ continue
172
+ }
173
+
174
+ const { name, expression, limits } = captureExpression
152
175
  const { maxReferenceDepth, maxCollectionSize, maxFieldCount, maxLength } = limits
153
176
 
154
177
  try {
178
+ const evaluationStart = process.hrtime.bigint()
155
179
  const { result, exceptionDetails } = /** @type {EvaluateOnCallFrameResult} */ (
156
180
  // eslint-disable-next-line no-await-in-loop
157
181
  await session.post('Debugger.evaluateOnCallFrame', {
@@ -159,6 +183,13 @@ async function evaluateCaptureExpressions (callFrame, expressions, deadlineNs =
159
183
  expression,
160
184
  })
161
185
  )
186
+ if (process.hrtime.bigint() - evaluationStart > evaluationTimeoutNs) {
187
+ timedOut = true
188
+ evaluationErrors.push({
189
+ expr: name,
190
+ message: `Expression evaluation exceeded its time budget of ${evaluationTimeoutNs / 1_000_000n}ms`,
191
+ })
192
+ }
162
193
 
163
194
  // Handle evaluation exceptions (maybe transient - bad expression, undefined var, etc.)
164
195
  if (exceptionDetails) {
@@ -197,10 +228,16 @@ async function evaluateCaptureExpressions (callFrame, expressions, deadlineNs =
197
228
  // and are recorded when processed, unless they end up redacted.
198
229
  rawResults.push({ name, remoteObject: result, maxLength })
199
230
  // Add stub entries for remaining uncaptured expressions. The stubs are used as-is, so record them here.
200
- if (i + 1 < expressions.length) incomplete.reasons |= INCOMPLETE_REASON.TIMEOUT
231
+ // Redacted expressions are never evaluated, so they are reported the same way as without a timeout.
201
232
  for (let j = i + 1; j < expressions.length; j++) {
233
+ const remaining = expressions[j]
234
+ if ('redactionError' in remaining) {
235
+ evaluationErrors.push(remaining.redactionError)
236
+ continue
237
+ }
238
+ incomplete.reasons |= INCOMPLETE_REASON.TIMEOUT
202
239
  rawResults.push({
203
- name: expressions[j].name,
240
+ name: remaining.name,
204
241
  remoteObject: { notCapturedReason: 'timeout' },
205
242
  maxLength: 0,
206
243
  })
@@ -238,6 +275,7 @@ async function evaluateCaptureExpressions (callFrame, expressions, deadlineNs =
238
275
  evaluationErrors,
239
276
  fatalErrors,
240
277
  incomplete,
278
+ timedOut,
241
279
  }
242
280
  }
243
281
 
@@ -3,7 +3,7 @@
3
3
  const { INCOMPLETE_REASON } = require('../../guardrail-metrics')
4
4
  const { LARGE_OBJECT_SKIP_THRESHOLD } = require('./constants')
5
5
  const { collectionSizeSym, largeCollectionSkipThresholdSym, fieldCountSym, timeBudgetSym } = require('./symbols')
6
- const { normalizeName, REDACTED_IDENTIFIERS } = require('./redaction')
6
+ const { isRedactedIdentifier } = require('./redaction')
7
7
 
8
8
  module.exports = {
9
9
  processRawState: processProperties,
@@ -347,21 +347,16 @@ function arrayBufferToString (bytes, size) {
347
347
  }
348
348
 
349
349
  function shouldRedactProperty (prop) {
350
- return REDACTED_IDENTIFIERS.has(getNormalizedNameFromProp(prop))
350
+ return isRedactedIdentifier(prop.name, prop.symbol !== undefined)
351
351
  }
352
352
 
353
353
  function shouldRedactMapValue (key) {
354
354
  const isSymbol = key.value.type === 'symbol'
355
355
  if (!isSymbol && key.value.type !== 'string') return false // WeakMaps uses objects as keys
356
- const name = normalizeName(
356
+ return isRedactedIdentifier(
357
357
  isSymbol ? key.value.description : key.value.value,
358
358
  isSymbol
359
359
  )
360
- return REDACTED_IDENTIFIERS.has(name)
361
- }
362
-
363
- function getNormalizedNameFromProp (prop) {
364
- return normalizeName(prop.name, prop.symbol !== undefined)
365
360
  }
366
361
 
367
362
  function setNotCaptureReasonOnCollection (result, collection, incomplete) {
@@ -1,128 +1,8 @@
1
1
  'use strict'
2
2
 
3
3
  const config = require('../config')
4
-
5
- const excludedIdentifiers = new Set(
6
- config.dynamicInstrumentation.DD_DYNAMIC_INSTRUMENTATION_REDACTION_EXCLUDED_IDENTIFIERS
7
- .map((name) => normalizeName(name))
8
- )
9
-
10
- const REDACTED_IDENTIFIERS = new Set(
11
- [
12
- '2fa',
13
- '_csrf',
14
- '_csrf_token',
15
- '_session',
16
- '_xsrf',
17
- 'access_token',
18
- 'aiohttp_session',
19
- 'api_key',
20
- 'apisecret',
21
- 'apisignature',
22
- 'applicationkey',
23
- 'appkey',
24
- 'auth',
25
- 'authtoken',
26
- 'authorization',
27
- 'cc_number',
28
- 'certificatepin',
29
- 'cipher',
30
- 'client_secret',
31
- 'clientid',
32
- 'connect.sid',
33
- 'connectionstring',
34
- 'cookie',
35
- 'credentials',
36
- 'creditcard',
37
- 'csrf',
38
- 'csrf_token',
39
- 'cvv',
40
- 'databaseurl',
41
- 'db_url',
42
- 'encryption_key',
43
- 'encryptionkeyid',
44
- 'geo_location',
45
- 'gpg_key',
46
- 'ip_address',
47
- 'jti',
48
- 'jwt',
49
- 'license_key',
50
- 'masterkey',
51
- 'mysql_pwd',
52
- 'nonce',
53
- 'oauth',
54
- 'oauthtoken',
55
- 'otp',
56
- 'passhash',
57
- 'passwd',
58
- 'password',
59
- 'passwordb',
60
- 'pem_file',
61
- 'pgp_key',
62
- 'PHPSESSID',
63
- 'pin',
64
- 'pincode',
65
- 'pkcs8',
66
- 'private_key',
67
- 'publickey',
68
- 'pwd',
69
- 'recaptcha_key',
70
- 'refresh_token',
71
- 'routingnumber',
72
- 'salt',
73
- 'secret',
74
- 'secretKey',
75
- 'secrettoken',
76
- 'securitycode',
77
- 'security_answer',
78
- 'security_question',
79
- 'serviceaccountcredentials',
80
- 'session',
81
- 'sessionid',
82
- 'sessionkey',
83
- 'set_cookie',
84
- 'signature',
85
- 'signaturekey',
86
- 'ssh_key',
87
- 'ssn',
88
- 'symfony',
89
- 'token',
90
- 'transactionid',
91
- 'twilio_token',
92
- 'user_session',
93
- 'voterid',
94
- 'x-auth-token',
95
- 'x_api_key',
96
- 'x_csrftoken',
97
- 'x_forwarded_for',
98
- 'x_real_ip',
99
- 'XSRF-TOKEN',
100
- ...config.dynamicInstrumentation.DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS,
101
- ]
102
- .map(normalizeIdentifier)
103
- .filter(isIncludedIdentifier)
104
- )
105
-
106
- /**
107
- * @param {string} name
108
- */
109
- function normalizeIdentifier (name) {
110
- return normalizeName(name)
111
- }
112
-
113
- /**
114
- * @param {string} name
115
- */
116
- function isIncludedIdentifier (name) {
117
- return !excludedIdentifiers.has(name)
118
- }
119
-
120
- function normalizeName (name, isSymbol) {
121
- if (isSymbol) name = name.slice(7, -1) // Remove `Symbol(` and `)`
122
- return name.toLowerCase().replaceAll(/[-_@$.]/g, '')
123
- }
4
+ const { createIsRedactedIdentifier } = require('../../redaction')
124
5
 
125
6
  module.exports = {
126
- REDACTED_IDENTIFIERS,
127
- normalizeName,
7
+ isRedactedIdentifier: createIsRedactedIdentifier(config.dynamicInstrumentation),
128
8
  }
@@ -32,8 +32,6 @@ const SKIPPED_REASON_NAMES = ['rateLimitGlobal', 'rateLimitProbe', 'evaluationTi
32
32
  /** Complete events dropped before transport (`events.dropped`) */
33
33
  const DROPPED_REASON = Object.freeze({
34
34
  QUEUE_FULL: 0,
35
- // The spec allows either trimming oversized payloads or dropping them whole. This tracer trims, and reports
36
- // `capture.incomplete{payloadTooLarge}` instead, so this reason exists for spec parity but is never emitted.
37
35
  PAYLOAD_TOO_LARGE: 1,
38
36
  })
39
37
  const DROPPED_REASON_NAMES = ['queueFull', 'payloadTooLarge']
@@ -13,11 +13,12 @@ const {
13
13
  DEBUGGER_DIAGNOSTICS_V1,
14
14
  DEBUGGER_INPUT_DIRECT,
15
15
  DEBUGGER_INPUT_V2,
16
- GUARDRAIL_METRICS_FLUSH_INTERVAL_MS,
17
16
  INSPECT_SEGMENT_GLOBAL_PROPERTY,
17
+ METRICS_FLUSH_INTERVAL_MS,
18
18
  } = require('./constants')
19
19
  const { GuardrailMetrics, TELEMETRY_NAMESPACE } = require('./guardrail-metrics')
20
- const { installProbeSampler, uninstallProbeSampler } = require('./probe_sampler')
20
+ const { PauseDurationHistogram } = require('./pause-duration-histogram')
21
+ const { configureProbeSampler, installProbeSampler, uninstallProbeSampler } = require('./probe_sampler')
21
22
 
22
23
  /**
23
24
  * @typedef {ReturnType<import('../config')>} Config
@@ -40,7 +41,9 @@ let rc = null
40
41
  let inputPath = null
41
42
  /** @type {GuardrailMetrics | null} */
42
43
  let guardrailMetrics = null
43
- let guardrailMetricsTimer = null
44
+ /** @type {PauseDurationHistogram | null} */
45
+ let pauseDurations = null
46
+ let metricsFlushTimer = null
44
47
 
45
48
  // eslint-disable-next-line eslint-rules/eslint-process-env
46
49
  const { NODE_OPTIONS, ...env } = process.env
@@ -86,15 +89,20 @@ function start (config, rcInstance) {
86
89
 
87
90
  const debuggerGlobals = globalThis[Symbol.for('dd-trace')]
88
91
  debuggerGlobals.utilTypes = types
89
- debuggerGlobals[INSPECT_SEGMENT_GLOBAL_PROPERTY] = require('./inspect-segment')
92
+ const createInspectSegment = require('./inspect-segment')
93
+ const { createIsRedactedIdentifier } = require('./redaction')
94
+ debuggerGlobals[INSPECT_SEGMENT_GLOBAL_PROPERTY] =
95
+ createInspectSegment(createIsRedactedIdentifier(config.dynamicInstrumentation))
90
96
 
91
97
  const guardrailMetricsBuffer = GuardrailMetrics.createBuffer()
92
98
  guardrailMetrics = new GuardrailMetrics(guardrailMetricsBuffer)
93
- guardrailMetricsTimer = setInterval(flushGuardrailMetrics, GUARDRAIL_METRICS_FLUSH_INTERVAL_MS)
94
- guardrailMetricsTimer.unref?.()
95
- dc.subscribe(TELEMETRY_APP_CLOSING_CHANNEL, flushGuardrailMetrics)
99
+ const pauseDurationBuffer = PauseDurationHistogram.createBuffer()
100
+ pauseDurations = new PauseDurationHistogram(pauseDurationBuffer)
101
+ metricsFlushTimer = setInterval(flushMetrics, METRICS_FLUSH_INTERVAL_MS)
102
+ metricsFlushTimer.unref?.()
103
+ dc.subscribe(TELEMETRY_APP_CLOSING_CHANNEL, flushMetrics)
96
104
 
97
- const probeSamplerBuffer = installProbeSampler(guardrailMetrics)
105
+ const probeSamplerBuffer = installProbeSampler(guardrailMetrics, config)
98
106
 
99
107
  readProbeFile(config.dynamicInstrumentation.DD_DYNAMIC_INSTRUMENTATION_PROBE_FILE, (probes) => {
100
108
  const action = 'apply'
@@ -108,7 +116,8 @@ function start (config, rcInstance) {
108
116
  probeChannel.port2.postMessage({ action, probe, ackId })
109
117
  })
110
118
 
111
- probeChannel.port2.on('message', ({ ackId, error }) => {
119
+ probeChannel.port2.on('message', ({ ackId, error, reason }) => {
120
+ if (error && reason !== undefined) logWorkerError(error, reason)
112
121
  const ack = rcAckCallbacks.get(ackId)
113
122
  if (ack === undefined) {
114
123
  // This should never happen, but just in case something changes in the future, we should guard against it
@@ -143,6 +152,7 @@ function start (config, rcInstance) {
143
152
  configPort: configChannel.port1,
144
153
  probeSamplerBuffer,
145
154
  guardrailMetricsBuffer,
155
+ pauseDurationBuffer,
146
156
  },
147
157
  transferList: [probeChannel.port1, logChannel.port1, configChannel.port1],
148
158
  }
@@ -155,17 +165,14 @@ function start (config, rcInstance) {
155
165
  )
156
166
  })
157
167
 
158
- const threadPausedMetric = telemetryMetrics.manager.namespace(TELEMETRY_NAMESPACE)
159
- .distribution('execution.pause.duration')
160
- worker.on('message', (/** @type {{ type: string, durationMs: number }} */ { type, durationMs }) => {
161
- if (type === 'thread-paused') threadPausedMetric.track(durationMs)
162
- })
163
- worker.on('error', (err) => log.error('[debugger] worker thread error', err))
168
+ worker.on('error', (err) => logWorkerError(err))
164
169
  worker.on('messageerror', (err) => log.error('[debugger] received "messageerror" from worker', err))
165
170
 
166
171
  worker.once('exit', (code) => {
167
172
  const error = new Error(`Dynamic Instrumentation worker thread exited unexpectedly with code ${code}`)
168
- log.error('[debugger] worker thread exited unexpectedly', error)
173
+ // Telemetry omits printf arguments, so the numeric exit code must be part of the message.
174
+ // eslint-disable-next-line eslint-rules/eslint-log-printf-style
175
+ log.error(() => `[debugger] worker thread exited unexpectedly exit_code=${code}`, error)
169
176
  cleanup(error) // Be nice, clean up now that the worker thread encountered an issue and we can't continue
170
177
  })
171
178
 
@@ -179,6 +186,24 @@ function start (config, rcInstance) {
179
186
  })
180
187
  }
181
188
 
189
+ /**
190
+ * Failure metadata belongs in the telemetry message; exception messages remain in the redacted cause.
191
+ *
192
+ * @param {Error & { code?: unknown, reason?: unknown }} error - The worker failure
193
+ * @param {unknown} [reason] - Explicit reason preserved across a probe acknowledgement's structured clone
194
+ */
195
+ function logWorkerError (error, reason = error.reason) {
196
+ // Telemetry omits printf arguments, so the failure metadata must be part of the message.
197
+ // eslint-disable-next-line eslint-rules/eslint-log-printf-style
198
+ log.error(() => `[debugger] worker thread error name=${
199
+ typeof error.name === 'string' ? error.name : 'unknown'
200
+ } code=${
201
+ typeof error.code === 'string' ? error.code : 'unknown'
202
+ } reason=${
203
+ typeof reason === 'string' ? reason : 'unknown'
204
+ }`, error)
205
+ }
206
+
182
207
  /**
183
208
  * Reconfigure the Debugger worker with updated settings.
184
209
  * Sends the new configuration to the worker thread via the config channel.
@@ -193,6 +218,7 @@ function configure (config) {
193
218
  log.error('[debugger] Invalid DD_SITE for agentless Dynamic Instrumentation: %s', config.site)
194
219
  return
195
220
  }
221
+ configureProbeSampler(config)
196
222
  configChannel.port2.postMessage(debuggerConfig)
197
223
  }
198
224
 
@@ -234,18 +260,19 @@ function cleanup (error) {
234
260
  configChannel = null
235
261
  inputPath = null
236
262
 
237
- if (guardrailMetricsTimer !== null) {
238
- clearInterval(guardrailMetricsTimer)
239
- guardrailMetricsTimer = null
263
+ if (metricsFlushTimer !== null) {
264
+ clearInterval(metricsFlushTimer)
265
+ metricsFlushTimer = null
240
266
  }
241
267
  if (guardrailMetrics !== null) {
242
- dc.unsubscribe(TELEMETRY_APP_CLOSING_CHANNEL, flushGuardrailMetrics)
243
- // Report what the worker counted up until it was stopped. Known limitation: `Worker#terminate()` interrupts the
244
- // worker asynchronously, so anything it counts between this drain and its actual termination is lost. That only
245
- // concerns events still sitting in the worker's upload buffer, which die with the worker anyway, so it isn't worth
246
- // deferring the drain until the worker has exited.
247
- flushGuardrailMetrics()
268
+ dc.unsubscribe(TELEMETRY_APP_CLOSING_CHANNEL, flushMetrics)
269
+ // Report what the worker recorded up until it was stopped. Known limitation: `Worker#terminate()` interrupts the
270
+ // worker asynchronously, so anything it records between this drain and its actual termination is lost. That only
271
+ // concerns events still sitting in the worker's upload buffer, which die with the worker anyway, and a pause that
272
+ // might still be in progress, so it isn't worth deferring the drain until the worker has exited.
273
+ flushMetrics()
248
274
  guardrailMetrics = null
275
+ pauseDurations = null
249
276
  }
250
277
 
251
278
  // Call any pending ack callbacks
@@ -261,14 +288,19 @@ function cleanup (error) {
261
288
  }
262
289
 
263
290
  /**
264
- * Convert the guardrail counters accumulated by the probe sampler and the worker into telemetry metrics.
291
+ * Convert the guardrail counters accumulated by the probe sampler and the worker, and the pause durations recorded by
292
+ * the worker, into telemetry metrics.
265
293
  */
266
- function flushGuardrailMetrics () {
267
- if (guardrailMetrics === null) return
294
+ function flushMetrics () {
295
+ if (guardrailMetrics === null || pauseDurations === null) return
268
296
  const namespace = telemetryMetrics.manager.namespace(TELEMETRY_NAMESPACE)
269
297
  guardrailMetrics.drain((metric, tags, count) => {
270
298
  namespace.count(metric, tags).inc(count)
271
299
  })
300
+ const pauseDurationMetric = namespace.distribution('execution.pause.duration')
301
+ pauseDurations.drain((durationMs, count) => {
302
+ pauseDurationMetric.track(durationMs, count)
303
+ })
272
304
  }
273
305
 
274
306
  /**