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.
- package/LICENSE-3rdparty.csv +1 -0
- package/ci/vitest-no-worker-init-setup.mjs +22 -12
- package/index.d.ts +166 -24
- package/package.json +7 -7
- package/packages/datadog-esbuild/index.js +9 -6
- package/packages/datadog-instrumentations/src/ai.js +26 -0
- package/packages/datadog-instrumentations/src/anthropic.js +111 -9
- package/packages/datadog-instrumentations/src/aws-sdk.js +2 -2
- package/packages/datadog-instrumentations/src/claude-agent-sdk.js +5 -1
- package/packages/datadog-instrumentations/src/cucumber.js +40 -4
- package/packages/datadog-instrumentations/src/helpers/hooks.js +0 -7
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentation-registry.js +2 -2
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +25 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +8 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +2 -0
- package/packages/datadog-instrumentations/src/jest/session-error.js +102 -0
- package/packages/datadog-instrumentations/src/jest.js +3 -10
- package/packages/datadog-instrumentations/src/playwright.js +26 -0
- package/packages/datadog-instrumentations/src/vitest-main.js +4 -1
- package/packages/datadog-instrumentations/src/vitest-worker.js +34 -3
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +83 -29
- package/packages/datadog-plugin-cucumber/src/index.js +19 -4
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +7 -0
- package/packages/datadog-plugin-fetch/src/index.js +3 -0
- package/packages/datadog-plugin-openai/src/stream-helpers.js +50 -1
- package/packages/datadog-plugin-oracledb/src/connection-parser.js +3 -1
- package/packages/datadog-plugin-undici/src/index.js +5 -0
- package/packages/dd-trace/src/aiguard/client.js +38 -19
- package/packages/dd-trace/src/aiguard/integrations/anthropic.js +29 -4
- package/packages/dd-trace/src/aiguard/integrations/index.js +1 -1
- package/packages/dd-trace/src/aiguard/integrations/openai.js +20 -56
- package/packages/dd-trace/src/aiguard/integrations/stream.js +60 -0
- package/packages/dd-trace/src/aiguard/messages/anthropic.js +64 -0
- package/packages/dd-trace/src/config/generated-config-types.d.ts +8 -4
- package/packages/dd-trace/src/config/remote_config.js +12 -0
- package/packages/dd-trace/src/config/supported-configurations.json +26 -5
- package/packages/dd-trace/src/constants.js +2 -0
- package/packages/dd-trace/src/debugger/constants.js +13 -4
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +14 -1
- package/packages/dd-trace/src/debugger/devtools_client/condition.js +109 -6
- package/packages/dd-trace/src/debugger/devtools_client/config.js +2 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +65 -6
- package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +22 -10
- package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +12 -8
- package/packages/dd-trace/src/debugger/devtools_client/send.js +14 -0
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +43 -5
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +3 -8
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +2 -122
- package/packages/dd-trace/src/debugger/guardrail-metrics.js +0 -2
- package/packages/dd-trace/src/debugger/index.js +60 -28
- package/packages/dd-trace/src/debugger/inspect-segment.js +92 -23
- package/packages/dd-trace/src/debugger/pause-duration-histogram.js +74 -0
- package/packages/dd-trace/src/debugger/probe_sampler.js +192 -52
- package/packages/dd-trace/src/debugger/redaction.js +139 -0
- package/packages/dd-trace/src/knuth-hash.js +17 -0
- package/packages/dd-trace/src/llmobs/constants/tags.js +33 -8
- package/packages/dd-trace/src/llmobs/experiments/client.js +66 -2
- package/packages/dd-trace/src/llmobs/experiments/evaluator.js +138 -0
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +213 -90
- package/packages/dd-trace/src/llmobs/experiments/index.js +40 -1
- package/packages/dd-trace/src/llmobs/experiments/noop.js +53 -2
- package/packages/dd-trace/src/llmobs/experiments/remote-evaluator.js +110 -0
- package/packages/dd-trace/src/llmobs/experiments/util.js +34 -6
- package/packages/dd-trace/src/llmobs/gen-ai-tags.js +127 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +18 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +33 -8
- package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +77 -41
- package/packages/dd-trace/src/llmobs/plugins/base.js +154 -15
- package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +162 -10
- package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +65 -16
- package/packages/dd-trace/src/llmobs/plugins/genai/index.js +14 -0
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +34 -35
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +10 -0
- package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +25 -2
- package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +8 -7
- package/packages/dd-trace/src/llmobs/plugins/openai/index.js +15 -2
- package/packages/dd-trace/src/llmobs/plugins/openai/realtime.js +5 -0
- package/packages/dd-trace/src/llmobs/plugins/vertexai.js +7 -0
- package/packages/dd-trace/src/llmobs/prompts/prompt.js +2 -1
- package/packages/dd-trace/src/llmobs/sdk.js +19 -3
- package/packages/dd-trace/src/llmobs/span_processor.js +52 -91
- package/packages/dd-trace/src/llmobs/tagger.js +37 -36
- package/packages/dd-trace/src/llmobs/writers/base.js +1 -6
- package/packages/dd-trace/src/openfeature/constants/constants.js +10 -0
- package/packages/dd-trace/src/openfeature/flagging_provider.js +14 -1
- package/packages/dd-trace/src/openfeature/writers/base.js +39 -18
- package/packages/dd-trace/src/openfeature/writers/flag-eval-evp-hook.js +105 -0
- package/packages/dd-trace/src/openfeature/writers/flag-evaluation-aggregation.js +190 -0
- package/packages/dd-trace/src/openfeature/writers/flag-evaluation-consumer.js +233 -0
- package/packages/dd-trace/src/openfeature/writers/flag-evaluation-context.js +262 -0
- package/packages/dd-trace/src/openfeature/writers/flag-evaluation-payload.js +181 -0
- package/packages/dd-trace/src/openfeature/writers/flag-evaluation-pii.js +108 -0
- package/packages/dd-trace/src/openfeature/writers/flag-evaluation-telemetry.js +154 -0
- package/packages/dd-trace/src/openfeature/writers/flag-evaluation-worker.js +77 -0
- package/packages/dd-trace/src/openfeature/writers/flag-evaluations.js +398 -0
- package/packages/dd-trace/src/openfeature/writers/util.js +5 -4
- package/packages/dd-trace/src/opentelemetry/context_manager.js +3 -0
- package/packages/dd-trace/src/opentelemetry/span_context.js +6 -2
- package/packages/dd-trace/src/opentelemetry/trace/index.js +5 -0
- package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +6 -0
- package/packages/dd-trace/src/opentelemetry/tracer.js +16 -18
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +116 -25
- package/packages/dd-trace/src/opentracing/propagation/tracestate.js +80 -16
- package/packages/dd-trace/src/opentracing/span.js +14 -9
- package/packages/dd-trace/src/opentracing/tracer.js +10 -3
- package/packages/dd-trace/src/otel-sampling.js +171 -0
- package/packages/dd-trace/src/plugins/index.js +0 -1
- package/packages/dd-trace/src/plugins/util/test.js +4 -0
- package/packages/dd-trace/src/priority_sampler.js +155 -33
- package/packages/dd-trace/src/profiler.js +51 -5
- package/packages/dd-trace/src/profiling/profiler.js +24 -20
- package/packages/dd-trace/src/remote_config/capabilities.js +9 -0
- package/packages/dd-trace/src/ritm.js +12 -9
- package/packages/dd-trace/src/sampler.js +2 -5
- package/packages/dd-trace/src/sampling_rule.js +4 -8
- package/packages/dd-trace/src/span_format.js +6 -0
- package/packages/dd-trace/src/span_processor.js +32 -3
- package/packages/dd-trace/src/standalone/index.js +5 -4
- package/packages/dd-trace/src/standalone/tracesource_priority_sampler.js +25 -5
- package/packages/dd-trace/src/telemetry/metrics.js +4 -3
- package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
- package/packages/datadog-instrumentations/src/postgres.js +0 -7
- 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
|
-
|
|
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(
|
|
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
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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:
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
238
|
-
clearInterval(
|
|
239
|
-
|
|
263
|
+
if (metricsFlushTimer !== null) {
|
|
264
|
+
clearInterval(metricsFlushTimer)
|
|
265
|
+
metricsFlushTimer = null
|
|
240
266
|
}
|
|
241
267
|
if (guardrailMetrics !== null) {
|
|
242
|
-
dc.unsubscribe(TELEMETRY_APP_CLOSING_CHANNEL,
|
|
243
|
-
// Report what the worker
|
|
244
|
-
// worker asynchronously, so anything it
|
|
245
|
-
// concerns events still sitting in the worker's upload buffer, which die with the worker anyway,
|
|
246
|
-
// deferring the drain until the worker has exited.
|
|
247
|
-
|
|
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
|
|
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
|
|
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
|
/**
|