dd-trace 5.133.0 → 5.134.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/ci/vitest-no-worker-init-runner-setup.mjs +27 -0
- package/index.d.ts +16 -5
- package/package.json +17 -16
- package/packages/datadog-instrumentations/index.js +2 -1
- package/packages/datadog-instrumentations/src/cucumber.js +17 -8
- package/packages/datadog-instrumentations/src/helpers/bundler-register.js +27 -8
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +9 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentation-registry.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/jest.js +12 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/vitest.js +8 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +5 -0
- package/packages/datadog-instrumentations/src/jest.js +69 -57
- package/packages/datadog-instrumentations/src/mocha/utils.js +35 -2
- package/packages/datadog-instrumentations/src/nova-sonic/session.js +25 -16
- package/packages/datadog-instrumentations/src/playwright.js +2 -0
- package/packages/datadog-instrumentations/src/stripe.js +23 -12
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +1 -0
- package/packages/datadog-instrumentations/src/vitest-main.js +7 -3
- package/packages/datadog-instrumentations/src/vitest-util.js +5 -2
- package/packages/datadog-instrumentations/src/vitest.js +8 -0
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +3 -0
- package/packages/datadog-plugin-cypress/src/support.js +2 -0
- package/packages/dd-trace/src/aiguard/evaluation.js +143 -1
- package/packages/dd-trace/src/appsec/iast/path-line.js +2 -1
- package/packages/dd-trace/src/appsec/stack_trace.js +3 -2
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +3 -2
- package/packages/dd-trace/src/config/generated-config-types.d.ts +2 -0
- package/packages/dd-trace/src/config/index.js +45 -0
- package/packages/dd-trace/src/config/supported-configurations.json +9 -0
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +6 -5
- package/packages/dd-trace/src/debugger/devtools_client/condition.js +5 -5
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +4 -6
- package/packages/dd-trace/src/debugger/unsupported-runtime.js +16 -0
- package/packages/dd-trace/src/llmobs/agent-manifest.js +19 -2
- package/packages/dd-trace/src/llmobs/constants/tags.js +3 -0
- package/packages/dd-trace/src/llmobs/plugins/openai/constants.js +5 -0
- package/packages/dd-trace/src/llmobs/plugins/openai/index.js +9 -1
- package/packages/dd-trace/src/llmobs/sdk.js +15 -4
- package/packages/dd-trace/src/llmobs/span_processor.js +12 -0
- package/packages/dd-trace/src/llmobs/tagger.js +18 -8
- package/packages/dd-trace/src/llmobs/util.js +10 -4
- package/packages/dd-trace/src/openfeature/index.js +6 -0
- package/packages/dd-trace/src/openfeature/writers/util.js +4 -4
- package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +18 -1
- package/packages/dd-trace/src/plugin_manager.js +1 -2
- package/packages/dd-trace/src/plugins/tracing.js +0 -1
- package/packages/dd-trace/src/plugins/util/git.js +91 -35
- package/packages/dd-trace/src/plugins/util/stacktrace.js +2 -1
- package/packages/dd-trace/src/plugins/util/test.js +2 -0
- package/packages/dd-trace/src/plugins/util/web.js +90 -20
- package/packages/dd-trace/src/span_stats.js +4 -1
- package/packages/dd-trace/src/telemetry/logs/log-collector.js +1 -1
- package/packages/dd-trace/src/util.js +8 -4
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +8 -8
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const {
|
|
3
|
+
const {
|
|
4
|
+
ERROR,
|
|
5
|
+
HTTP_CLIENT_IP,
|
|
6
|
+
HTTP_USERAGENT,
|
|
7
|
+
MANUAL_DROP,
|
|
8
|
+
MANUAL_KEEP,
|
|
9
|
+
NETWORK_CLIENT_IP,
|
|
10
|
+
RESOURCE_NAME,
|
|
11
|
+
SAMPLING_PRIORITY,
|
|
12
|
+
SERVICE_NAME,
|
|
13
|
+
SPAN_KIND,
|
|
14
|
+
SPAN_TYPE,
|
|
15
|
+
} = require('../../../../ext/tags')
|
|
4
16
|
const createRfdc = require('../../../../vendor/dist/rfdc')
|
|
5
17
|
const clone = createRfdc({ proto: false, circles: false })
|
|
6
18
|
const { USER_ID, USER_SESSION_ID } = require('../appsec/addresses')
|
|
7
19
|
const { getActiveRequest } = require('../appsec/store')
|
|
20
|
+
const { ERROR_MESSAGE, ERROR_STACK, ERROR_TYPE, PROCESS_ID, SAMPLING_PRIORITY_KEY } = require('../constants')
|
|
21
|
+
const log = require('../log')
|
|
8
22
|
const { keepTrace } = require('../priority_sampler')
|
|
9
23
|
const { extractIp } = require('../plugins/util/ip_extractor')
|
|
10
24
|
const { AI_GUARD } = require('../standalone/product')
|
|
@@ -20,6 +34,12 @@ const ALLOW = 'ALLOW'
|
|
|
20
34
|
/** @typedef {import('../../../../index').aiguard.RedactionReplacement} RedactionReplacement */
|
|
21
35
|
/** @typedef {import('../opentracing/span')} Span */
|
|
22
36
|
|
|
37
|
+
/**
|
|
38
|
+
* @typedef {object} ExtraEntry
|
|
39
|
+
* @property {string} key
|
|
40
|
+
* @property {unknown} value
|
|
41
|
+
*/
|
|
42
|
+
|
|
23
43
|
/**
|
|
24
44
|
* @typedef {object} EvaluationResponse
|
|
25
45
|
* @property {string} action
|
|
@@ -30,6 +50,8 @@ const ALLOW = 'ALLOW'
|
|
|
30
50
|
* @property {boolean} hasTagProbabilities
|
|
31
51
|
* @property {boolean} blockingEnabled
|
|
32
52
|
* @property {unknown} redactionReplacements
|
|
53
|
+
* @property {ExtraEntry[]} extraTags
|
|
54
|
+
* @property {ExtraEntry[]} extraMetastruct
|
|
33
55
|
*/
|
|
34
56
|
|
|
35
57
|
/**
|
|
@@ -40,6 +62,8 @@ const ALLOW = 'ALLOW'
|
|
|
40
62
|
* @property {boolean} shouldBlock
|
|
41
63
|
* @property {boolean} hasTagProbabilities
|
|
42
64
|
* @property {{ enabled: boolean, applied: boolean, failures: number }} redaction
|
|
65
|
+
* @property {ExtraEntry[]} extraTags
|
|
66
|
+
* @property {ExtraEntry[]} extraMetastruct
|
|
43
67
|
*/
|
|
44
68
|
|
|
45
69
|
/**
|
|
@@ -68,6 +92,38 @@ function isRecord (value) {
|
|
|
68
92
|
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
69
93
|
}
|
|
70
94
|
|
|
95
|
+
// Shared by every evaluation without extra entries, so the common case allocates nothing.
|
|
96
|
+
const NO_EXTRA_ENTRIES = /** @type {ExtraEntry[]} */ (/** @type {unknown} */ (Object.freeze([])))
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Normalizes a backend extra field into well-formed key/value entries. The backend sends lists of
|
|
100
|
+
* entries rather than objects because the dotted keys are not valid JSON:API member names. Values
|
|
101
|
+
* are kept as-is: which of them reach the span is decided when they are applied.
|
|
102
|
+
*
|
|
103
|
+
* @param {string} field
|
|
104
|
+
* @param {unknown} entries
|
|
105
|
+
* @returns {ExtraEntry[]}
|
|
106
|
+
*/
|
|
107
|
+
function normalizeExtraEntries (field, entries) {
|
|
108
|
+
if (entries === undefined) return NO_EXTRA_ENTRIES
|
|
109
|
+
if (!Array.isArray(entries)) {
|
|
110
|
+
log.debug('AI Guard: ignoring %s of type %s', field, entries === null ? 'null' : typeof entries)
|
|
111
|
+
return NO_EXTRA_ENTRIES
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const normalized = []
|
|
115
|
+
for (const entry of entries) {
|
|
116
|
+
const key = isRecord(entry) ? entry.key : undefined
|
|
117
|
+
// A __proto__ key is never valid: it cannot become an own property on the span tags or the meta-struct.
|
|
118
|
+
if (typeof key === 'string' && key.length > 0 && key !== '__proto__') {
|
|
119
|
+
normalized.push({ key, value: entry.value })
|
|
120
|
+
} else {
|
|
121
|
+
log.debug('AI Guard: ignoring malformed %s entry', field)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return normalized.length === 0 ? NO_EXTRA_ENTRIES : normalized
|
|
125
|
+
}
|
|
126
|
+
|
|
71
127
|
/**
|
|
72
128
|
* Parses the backend evaluation response into the tracer's internal contract.
|
|
73
129
|
*
|
|
@@ -89,6 +145,9 @@ function parseEvaluationResponse (body) {
|
|
|
89
145
|
hasTagProbabilities: isRecord(attributes.tag_probs),
|
|
90
146
|
blockingEnabled: attributes.is_blocking_enabled === true,
|
|
91
147
|
redactionReplacements: attributes.redaction_replacements,
|
|
148
|
+
// Malformed extra entries are dropped here instead of turning the response into a bad_response.
|
|
149
|
+
extraTags: normalizeExtraEntries('extra_tags', attributes.extra_tags),
|
|
150
|
+
extraMetastruct: normalizeExtraEntries('extra_metastruct', attributes.extra_metastruct),
|
|
92
151
|
}
|
|
93
152
|
}
|
|
94
153
|
|
|
@@ -132,6 +191,8 @@ function createEvaluationOutcome (messages, evaluation, options) {
|
|
|
132
191
|
applied: redaction.redacted,
|
|
133
192
|
failures: redaction.failures,
|
|
134
193
|
},
|
|
194
|
+
extraTags: evaluation.extraTags,
|
|
195
|
+
extraMetastruct: evaluation.extraMetastruct,
|
|
135
196
|
}
|
|
136
197
|
}
|
|
137
198
|
|
|
@@ -147,6 +208,86 @@ const SERVICE_ENTRY_TAG_MAPPINGS = [
|
|
|
147
208
|
[USER_SESSION_ID, TAGS.USR_SESSION_ID_TAG_KEY],
|
|
148
209
|
]
|
|
149
210
|
|
|
211
|
+
// Backend extra tags never override what the tracer computes itself, nor keys that control the
|
|
212
|
+
// span or its trace (sampling, service, resource, error state). Internal _dd keys are covered by
|
|
213
|
+
// isReservedExtraTag instead of being listed here.
|
|
214
|
+
const RESERVED_EXTRA_TAGS = new Set([
|
|
215
|
+
TAGS.ACTION_TAG_KEY,
|
|
216
|
+
TAGS.REASON_TAG_KEY,
|
|
217
|
+
TAGS.TARGET_TAG_KEY,
|
|
218
|
+
TAGS.TOOL_NAME_TAG_KEY,
|
|
219
|
+
TAGS.BLOCKED_TAG_KEY,
|
|
220
|
+
TAGS.REDACTED_TAG_KEY,
|
|
221
|
+
TAGS.EVENT_TAG_KEY,
|
|
222
|
+
...SERVICE_ENTRY_TAG_MAPPINGS.map(([, destinationTag]) => destinationTag),
|
|
223
|
+
MANUAL_KEEP,
|
|
224
|
+
MANUAL_DROP,
|
|
225
|
+
SAMPLING_PRIORITY,
|
|
226
|
+
SERVICE_NAME,
|
|
227
|
+
RESOURCE_NAME,
|
|
228
|
+
SPAN_TYPE,
|
|
229
|
+
SPAN_KIND,
|
|
230
|
+
ERROR,
|
|
231
|
+
ERROR_TYPE,
|
|
232
|
+
ERROR_MESSAGE,
|
|
233
|
+
ERROR_STACK,
|
|
234
|
+
SAMPLING_PRIORITY_KEY,
|
|
235
|
+
PROCESS_ID,
|
|
236
|
+
'analytics.event',
|
|
237
|
+
'language',
|
|
238
|
+
'runtime-id',
|
|
239
|
+
'service.version',
|
|
240
|
+
'version',
|
|
241
|
+
'env',
|
|
242
|
+
])
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Returns whether a backend extra tag key must not be set on the span.
|
|
246
|
+
*
|
|
247
|
+
* @param {string} key
|
|
248
|
+
*/
|
|
249
|
+
function isReservedExtraTag (key) {
|
|
250
|
+
// _dd is the tracer's internal namespace (_dd.*, _dd1.*): the span formatter writes sampling,
|
|
251
|
+
// top-level and propagation keys there and a span tag with the same key would overwrite them.
|
|
252
|
+
// This is not an AI Guard namespace filter; the backend owns ai_guard.* keys.
|
|
253
|
+
return key.startsWith('_dd') || RESERVED_EXTRA_TAGS.has(key)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const RESERVED_EXTRA_META_STRUCT_KEYS = new Set(['messages', 'attack_categories', 'sds', 'tag_probs'])
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Copies backend-provided extra_tags to the span and extra_metastruct to the AI Guard meta-struct,
|
|
260
|
+
* skipping values and keys the span must not receive. Keys are used verbatim and entries are
|
|
261
|
+
* applied in order, so a repeated key keeps its last value.
|
|
262
|
+
*
|
|
263
|
+
* @param {Span} span
|
|
264
|
+
* @param {EvaluationMetaStruct} metaStruct
|
|
265
|
+
* @param {ExtraEntry[]} extraTags
|
|
266
|
+
* @param {ExtraEntry[]} extraMetastruct
|
|
267
|
+
*/
|
|
268
|
+
function applyExtraSpanData (span, metaStruct, extraTags, extraMetastruct) {
|
|
269
|
+
// The client decodes values with JSON.parse, so a string check and a null check cover every JSON type.
|
|
270
|
+
for (const { key, value } of extraTags) {
|
|
271
|
+
if (typeof value !== 'string') {
|
|
272
|
+
log.debug('AI Guard: ignoring extra_tags entry %s with non-string value', key)
|
|
273
|
+
} else if (isReservedExtraTag(key)) {
|
|
274
|
+
log.debug('AI Guard: ignoring reserved extra tag %s', key)
|
|
275
|
+
} else {
|
|
276
|
+
span.setTag(key, value)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
for (const { key, value } of extraMetastruct) {
|
|
281
|
+
if (value === undefined || value === null) {
|
|
282
|
+
log.debug('AI Guard: ignoring extra_metastruct entry %s with null value', key)
|
|
283
|
+
} else if (RESERVED_EXTRA_META_STRUCT_KEYS.has(key)) {
|
|
284
|
+
log.debug('AI Guard: ignoring reserved extra_metastruct key %s', key)
|
|
285
|
+
} else {
|
|
286
|
+
metaStruct[key] = value
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
150
291
|
class EvaluationReporter {
|
|
151
292
|
#config
|
|
152
293
|
#maxMessagesLength
|
|
@@ -230,6 +371,7 @@ class EvaluationReporter {
|
|
|
230
371
|
if (result.tags.length > 0) report.metaStruct.attack_categories = result.tags
|
|
231
372
|
if (result.sds.length > 0) report.metaStruct.sds = result.sds
|
|
232
373
|
if (outcome.hasTagProbabilities) report.metaStruct.tag_probs = result.tagProbabilities
|
|
374
|
+
applyExtraSpanData(report.span, report.metaStruct, outcome.extraTags, outcome.extraMetastruct)
|
|
233
375
|
|
|
234
376
|
if (redaction.enabled) {
|
|
235
377
|
report.span.setTag(TAGS.REDACTED_TAG_KEY, redaction.applied ? 'true' : 'false')
|
|
@@ -66,7 +66,8 @@ function getCallSiteFramesForLocation (callSiteFrames, externallyExcludedPaths)
|
|
|
66
66
|
|
|
67
67
|
if (
|
|
68
68
|
!isExcluded(callsite, externallyExcludedPaths) &&
|
|
69
|
-
(!filepath.includes(pathLine.ddBasePath) ||
|
|
69
|
+
(pathLine.ddBasePath === undefined || !filepath.includes(pathLine.ddBasePath) ||
|
|
70
|
+
globalThis.__DD_ESBUILD_IAST_WITH_NO_SM)
|
|
70
71
|
) {
|
|
71
72
|
clientFrames.push(callsite)
|
|
72
73
|
}
|
|
@@ -34,8 +34,9 @@ function getCallSiteList (maxDepth = 100, constructorOpt) {
|
|
|
34
34
|
|
|
35
35
|
function filterOutFramesFromLibrary (callSiteList) {
|
|
36
36
|
return callSiteList.filter(callSite => {
|
|
37
|
-
if (globalThis.__DD_ESBUILD_IAST_WITH_NO_SM) {
|
|
38
|
-
// bundled
|
|
37
|
+
if (globalThis.__DD_ESBUILD_IAST_WITH_NO_SM || ddBasePath === undefined) {
|
|
38
|
+
// bundled without a SourceMap, or dd-trace's location is unknown: not possible to discriminate if the frame
|
|
39
|
+
// comes from dd-trace code or not
|
|
39
40
|
return true
|
|
40
41
|
}
|
|
41
42
|
|
|
@@ -228,9 +228,10 @@ async function addBreakpoint (probe) {
|
|
|
228
228
|
}
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
-
function start () {
|
|
231
|
+
async function start () {
|
|
232
|
+
await session.post('Debugger.enable')
|
|
233
|
+
// Only flag the session as started once enabled, so the next probe retries a failed start and reports its cause
|
|
232
234
|
sessionStarted = true
|
|
233
|
-
return session.post('Debugger.enable') // return instead of await to reduce number of promises created
|
|
234
235
|
}
|
|
235
236
|
|
|
236
237
|
function drainBreakpointHitRequests () {
|
|
@@ -439,6 +439,7 @@ export interface GeneratedConfig {
|
|
|
439
439
|
DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_REQUEST_TIMEOUT_SECONDS: number;
|
|
440
440
|
DD_FEATURE_FLAGS_ENABLED: boolean;
|
|
441
441
|
DD_FEATURE_FLAGS_EVALUATION_TELEMETRY_PER_SUBJECT_OPT_OUT_ENABLED: boolean;
|
|
442
|
+
DD_FEATURE_FLAGS_EXPOSURES_DATADOG_LOGGING_ENABLED: boolean;
|
|
442
443
|
DD_FLAGGING_EVALUATION_COUNTS_ENABLED: boolean;
|
|
443
444
|
};
|
|
444
445
|
flushInterval: number;
|
|
@@ -726,6 +727,7 @@ export interface GeneratedEnvVarConfig {
|
|
|
726
727
|
DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_REQUEST_TIMEOUT_SECONDS: number;
|
|
727
728
|
DD_FEATURE_FLAGS_ENABLED: boolean;
|
|
728
729
|
DD_FEATURE_FLAGS_EVALUATION_TELEMETRY_PER_SUBJECT_OPT_OUT_ENABLED: boolean;
|
|
730
|
+
DD_FEATURE_FLAGS_EXPOSURES_DATADOG_LOGGING_ENABLED: boolean;
|
|
729
731
|
DD_FLAGGING_EVALUATION_COUNTS_ENABLED: boolean;
|
|
730
732
|
DD_GIT_BRANCH: string | undefined;
|
|
731
733
|
DD_GIT_COMMIT_AUTHOR_DATE: string | undefined;
|
|
@@ -12,6 +12,7 @@ const uuid = require('../../../../vendor/dist/crypto-randomuuid') // we need to
|
|
|
12
12
|
const set = require('../../../datadog-core/src/utils/src/set')
|
|
13
13
|
const { DD_MAJOR, NODE_MAJOR } = require('../../../../version')
|
|
14
14
|
const log = require('../log')
|
|
15
|
+
const { warn } = require('../log/writer')
|
|
15
16
|
const pkg = require('../pkg')
|
|
16
17
|
const { isTrue } = require('../util')
|
|
17
18
|
const telemetry = require('../telemetry')
|
|
@@ -25,6 +26,7 @@ const {
|
|
|
25
26
|
const { ORIGIN_KEY, DATADOG_MINI_AGENT_PATH } = require('../constants')
|
|
26
27
|
const { appendRules } = require('../payload-tagging/config')
|
|
27
28
|
const { createSiteUrl } = require('../exporters/common/url')
|
|
29
|
+
const { getUnsupportedRuntime } = require('../debugger/unsupported-runtime')
|
|
28
30
|
const ConfigBase = require('./config-base')
|
|
29
31
|
const {
|
|
30
32
|
getEnvironmentVariable,
|
|
@@ -200,6 +202,12 @@ class Config extends ConfigBase {
|
|
|
200
202
|
*/
|
|
201
203
|
#parsedDdTags
|
|
202
204
|
|
|
205
|
+
/**
|
|
206
|
+
* Options already explained as deactivated, so recalculating the config doesn't repeat the explanation
|
|
207
|
+
* @type {Set<ConfigPath>}
|
|
208
|
+
*/
|
|
209
|
+
#explainedDeactivations = new Set()
|
|
210
|
+
|
|
203
211
|
/**
|
|
204
212
|
* @type {Record<string, string>}
|
|
205
213
|
*/
|
|
@@ -639,6 +647,12 @@ class Config extends ConfigBase {
|
|
|
639
647
|
setAndTrack(this, 'remoteConfig.DD_REMOTE_CONFIGURATION_ENABLED', false)
|
|
640
648
|
}
|
|
641
649
|
|
|
650
|
+
if (this.isCiVisibility) {
|
|
651
|
+
// Test Optimization reports its metrics through instrumentation telemetry, not DogStatsD.
|
|
652
|
+
set(this, 'dogstatsd', undefined)
|
|
653
|
+
setAndTrack(this, 'runtimeMetrics.enabled', false)
|
|
654
|
+
}
|
|
655
|
+
|
|
642
656
|
const isTestOptimizationWorker = this.isCiVisibility &&
|
|
643
657
|
TEST_OPTIMIZATION_WORKER_EXPORTERS.has(this.tracing.DD_TRACE_EXPERIMENTAL_EXPORTER)
|
|
644
658
|
if (isTestOptimizationWorker) {
|
|
@@ -750,11 +764,42 @@ class Config extends ConfigBase {
|
|
|
750
764
|
deactivateIfEnabledAndWarnOnWindows(this, 'DD_PROFILING_ASYNC_CONTEXT_FRAME_ENABLED')
|
|
751
765
|
}
|
|
752
766
|
|
|
767
|
+
const unsupportedRuntime = getUnsupportedRuntime()
|
|
768
|
+
if (unsupportedRuntime !== undefined) {
|
|
769
|
+
this.#deactivateOnUnsupportedRuntime(
|
|
770
|
+
'dynamicInstrumentation.DD_DYNAMIC_INSTRUMENTATION_ENABLED',
|
|
771
|
+
`Live Debugger/Dynamic Instrumentation is disabled, because it is not supported on ${unsupportedRuntime}.`
|
|
772
|
+
)
|
|
773
|
+
this.#deactivateOnUnsupportedRuntime(
|
|
774
|
+
'testOptimization.DD_TEST_FAILED_TEST_REPLAY_ENABLED',
|
|
775
|
+
'Failed Test Replay is disabled, ' +
|
|
776
|
+
`because Live Debugger/Dynamic Instrumentation is not supported on ${unsupportedRuntime}.`
|
|
777
|
+
)
|
|
778
|
+
}
|
|
779
|
+
|
|
753
780
|
// Single tags update is tracked as a calculated value.
|
|
754
781
|
setAndTrack(this, 'tags', this.tags)
|
|
755
782
|
|
|
756
783
|
telemetry.updateConfig([...configWithOrigin.values()], this)
|
|
757
784
|
}
|
|
785
|
+
|
|
786
|
+
/**
|
|
787
|
+
* Only explains the deactivation of an option that was explicitly set, since Failed Test Replay is enabled by
|
|
788
|
+
* default.
|
|
789
|
+
*
|
|
790
|
+
* @param {ConfigPath} name
|
|
791
|
+
* @param {string} reason
|
|
792
|
+
*/
|
|
793
|
+
#deactivateOnUnsupportedRuntime (name, reason) {
|
|
794
|
+
if (!get(this, name)) return
|
|
795
|
+
|
|
796
|
+
if (this.startupLogs && trackedConfigOrigins.has(name) && !this.#explainedDeactivations.has(name)) {
|
|
797
|
+
this.#explainedDeactivations.add(name)
|
|
798
|
+
// `log.warn` only prints with debug logging enabled, so write the diagnostic like the startup logs do
|
|
799
|
+
warn(`DATADOG TRACER DIAGNOSTIC - ${reason}`)
|
|
800
|
+
}
|
|
801
|
+
setAndTrack(this, name, false)
|
|
802
|
+
}
|
|
758
803
|
}
|
|
759
804
|
|
|
760
805
|
/**
|
|
@@ -1004,6 +1004,15 @@
|
|
|
1004
1004
|
"namespace": "featureFlags"
|
|
1005
1005
|
}
|
|
1006
1006
|
],
|
|
1007
|
+
"DD_FEATURE_FLAGS_EXPOSURES_DATADOG_LOGGING_ENABLED": [
|
|
1008
|
+
{
|
|
1009
|
+
"implementation": "A",
|
|
1010
|
+
"type": "boolean",
|
|
1011
|
+
"default": "true",
|
|
1012
|
+
"description": "Enable Feature Flagging exposure delivery to Datadog independently of flag evaluation and evaluation telemetry.",
|
|
1013
|
+
"namespace": "featureFlags"
|
|
1014
|
+
}
|
|
1015
|
+
],
|
|
1007
1016
|
"DD_FLAGGING_EVALUATION_COUNTS_ENABLED": [
|
|
1008
1017
|
{
|
|
1009
1018
|
"implementation": "A",
|
|
@@ -103,14 +103,14 @@ async function addBreakpoint (probe) {
|
|
|
103
103
|
file, lineNumber, columnNumber, probe.id, probe.version
|
|
104
104
|
)
|
|
105
105
|
const position = await getGeneratedPosition(url, source, lineNumber, sourceMapURL)
|
|
106
|
-
if (position.line
|
|
107
|
-
lineNumber = position.line
|
|
108
|
-
columnNumber = position.column
|
|
109
|
-
} else {
|
|
106
|
+
if (position.line === null || position.column === null) {
|
|
110
107
|
throw new Error(
|
|
111
108
|
// eslint-disable-next-line @stylistic/max-len
|
|
112
109
|
`Could not find generated position for ${url}:${lineNumber}:${columnNumber} (probe: ${probe.id}, version: ${probe.version})`
|
|
113
110
|
)
|
|
111
|
+
} else {
|
|
112
|
+
lineNumber = position.line
|
|
113
|
+
columnNumber = position.column
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
|
|
@@ -357,9 +357,10 @@ async function reEvaluateProbe (probe) {
|
|
|
357
357
|
}
|
|
358
358
|
|
|
359
359
|
async function start () {
|
|
360
|
-
sessionStarted = true
|
|
361
360
|
log.debug('[debugger:devtools_client] Starting debugger')
|
|
362
361
|
await session.post('Debugger.enable')
|
|
362
|
+
// Only flag the session as started once enabled, so the next probe retries a failed start and reports its cause
|
|
363
|
+
sessionStarted = true
|
|
363
364
|
|
|
364
365
|
// Wait until there's a pause in script-loading to avoid accidentally adding probes to incorrect scripts. This is not
|
|
365
366
|
// a guarantee, but best effort.
|
|
@@ -312,9 +312,9 @@ function isInstanceOfCoreType (type, variable, fallback = `${variable} instanceo
|
|
|
312
312
|
function getSize (variable) {
|
|
313
313
|
return `((val) => {
|
|
314
314
|
if (${isString('val')} || ${isArrayOrTypedArray('val')}) {
|
|
315
|
-
return ${guardAgainstPropertyAccessSideEffects('
|
|
315
|
+
return ${guardAgainstPropertyAccessSideEffects('"length"')}
|
|
316
316
|
} else if (${isInstanceOfCoreType('Set', 'val')} || ${isInstanceOfCoreType('Map', 'val')}) {
|
|
317
|
-
return ${guardAgainstPropertyAccessSideEffects('
|
|
317
|
+
return ${guardAgainstPropertyAccessSideEffects('"size"')}
|
|
318
318
|
} else if (${isInstanceOfCoreType('WeakSet', 'val')} || ${isInstanceOfCoreType('WeakMap', 'val')}) {
|
|
319
319
|
throw new TypeError('Cannot get size of WeakSet or WeakMap')
|
|
320
320
|
} else if (typeof val === 'object' && val !== null) {
|
|
@@ -338,12 +338,12 @@ function accessProperty (variable, keyOrIndex, allowMapAccess) {
|
|
|
338
338
|
} else if (${isInstanceOfCoreType('Set', 'val')} || ${isInstanceOfCoreType('WeakSet', 'val')}) {
|
|
339
339
|
throw new Error('Accessing a Set or WeakSet is not allowed')
|
|
340
340
|
} else {
|
|
341
|
-
return ${guardAgainstPropertyAccessSideEffects('
|
|
341
|
+
return ${guardAgainstPropertyAccessSideEffects('key')}
|
|
342
342
|
}
|
|
343
343
|
})(${variable}, ${keyOrIndex})`
|
|
344
344
|
}
|
|
345
345
|
|
|
346
|
-
function guardAgainstPropertyAccessSideEffects (
|
|
346
|
+
function guardAgainstPropertyAccessSideEffects (propertyName) {
|
|
347
347
|
return `((val, key) => {
|
|
348
348
|
if (
|
|
349
349
|
${isInstanceOfCoreType('Proxy', 'val', 'true')} ||
|
|
@@ -353,7 +353,7 @@ function guardAgainstPropertyAccessSideEffects (variable, propertyName) {
|
|
|
353
353
|
} else {
|
|
354
354
|
return val[key]
|
|
355
355
|
}
|
|
356
|
-
})(
|
|
356
|
+
})(val, ${propertyName})`
|
|
357
357
|
}
|
|
358
358
|
|
|
359
359
|
function guardAgainstCoercionSideEffects (variable) {
|
|
@@ -126,8 +126,8 @@ async function traverseGetPropertiesResult (props, opts, depth) {
|
|
|
126
126
|
if (prop.value === undefined) continue
|
|
127
127
|
const { value: { type, objectId, subtype, description } } = prop
|
|
128
128
|
if (type === 'object') {
|
|
129
|
-
|
|
130
|
-
if (LEAF_SUBTYPES.has(subtype)) continue // don't waste time with these subtypes
|
|
129
|
+
// if `subtype` is "null"
|
|
130
|
+
if (objectId === undefined || LEAF_SUBTYPES.has(subtype)) continue // don't waste time with these subtypes
|
|
131
131
|
const size = parseLengthFromDescription(description, subtype)
|
|
132
132
|
if (size !== null && size >= LARGE_OBJECT_SKIP_THRESHOLD) {
|
|
133
133
|
const empty = []
|
|
@@ -310,8 +310,7 @@ function removeNonEnumerableProperties (props) {
|
|
|
310
310
|
}
|
|
311
311
|
|
|
312
312
|
function parseLengthFromDescription (description, subtype) {
|
|
313
|
-
if (typeof description !== 'string') return null
|
|
314
|
-
if (!SIZE_IN_DESCRIPTION_SUBTYPES.has(subtype)) return null
|
|
313
|
+
if (typeof description !== 'string' || !SIZE_IN_DESCRIPTION_SUBTYPES.has(subtype)) return null
|
|
315
314
|
|
|
316
315
|
const open = description.lastIndexOf('(')
|
|
317
316
|
if (open === -1) return null
|
|
@@ -323,8 +322,7 @@ function parseLengthFromDescription (description, subtype) {
|
|
|
323
322
|
if (s === '') return null
|
|
324
323
|
|
|
325
324
|
const n = Number(s)
|
|
326
|
-
if (!Number.isSafeInteger(n) || n < 0) return null
|
|
327
|
-
if (String(n) !== s) return null
|
|
325
|
+
if (!Number.isSafeInteger(n) || n < 0 || String(n) !== s) return null
|
|
328
326
|
|
|
329
327
|
return n
|
|
330
328
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Get the name of the runtime if it can't run Live Debugger/Dynamic Instrumentation, or `undefined` if it can.
|
|
5
|
+
*
|
|
6
|
+
* Live Debugger/Dynamic Instrumentation needs the Debugger domain of the `node:inspector` module. Bun only implements a
|
|
7
|
+
* few of its methods, never returns their results, and never emits its events. Deno doesn't let worker threads
|
|
8
|
+
* connect to the main thread inspector. Deno reports a Node.js `process.version`, so both runtimes are detected by
|
|
9
|
+
* their own `process.versions` entry instead.
|
|
10
|
+
*/
|
|
11
|
+
function getUnsupportedRuntime () {
|
|
12
|
+
if (process.versions.bun !== undefined) return 'Bun'
|
|
13
|
+
if (process.versions.deno !== undefined) return 'Deno'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
module.exports = { getUnsupportedRuntime }
|
|
@@ -35,7 +35,7 @@ const LABEL_FIELDS = ['name', 'instructions', 'model']
|
|
|
35
35
|
* model_settings?: Record<string, unknown>,
|
|
36
36
|
* tools?: AgentTool[],
|
|
37
37
|
* }} AgentManifestFields
|
|
38
|
-
* @typedef {{ manifest
|
|
38
|
+
* @typedef {{ version?: string, manifest?: AgentManifestFields }} AgentDeclaration
|
|
39
39
|
*/
|
|
40
40
|
|
|
41
41
|
/**
|
|
@@ -50,8 +50,9 @@ function buildAgentDeclaration (agent) {
|
|
|
50
50
|
let declaration
|
|
51
51
|
try {
|
|
52
52
|
if (isPlainObject(agent)) {
|
|
53
|
+
const version = toAgentVersion(agent.version)
|
|
53
54
|
const manifest = buildAgentManifest(agent)
|
|
54
|
-
if (manifest !== undefined) declaration = { manifest }
|
|
55
|
+
if (version !== undefined || manifest !== undefined) declaration = { version, manifest }
|
|
55
56
|
} else {
|
|
56
57
|
log.warn('Dropping agent annotation, the agent must be a plain object.')
|
|
57
58
|
}
|
|
@@ -105,6 +106,21 @@ function buildAgentManifest (agent) {
|
|
|
105
106
|
return manifest
|
|
106
107
|
}
|
|
107
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Unset values (`undefined`, `null`, `''`) are ignored without a warning, so they leave an earlier version in place.
|
|
111
|
+
* A comma is rejected because intake splits tags on it.
|
|
112
|
+
*
|
|
113
|
+
* @param {unknown} version
|
|
114
|
+
*/
|
|
115
|
+
function toAgentVersion (version) {
|
|
116
|
+
if (typeof version === 'string' && version !== '') {
|
|
117
|
+
if (!version.includes(',')) return version
|
|
118
|
+
log.warn('Dropping the agent version, it must not contain a comma.')
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
if (!isUnset(version)) log.warn('Dropping the agent version, it must be a string.')
|
|
122
|
+
}
|
|
123
|
+
|
|
108
124
|
/**
|
|
109
125
|
* @param {unknown} settings
|
|
110
126
|
* @param {string[]} dropped
|
|
@@ -293,4 +309,5 @@ function isScalar (value) {
|
|
|
293
309
|
module.exports = {
|
|
294
310
|
MANUAL_FRAMEWORK_NAME,
|
|
295
311
|
buildAgentDeclaration,
|
|
312
|
+
toAgentVersion,
|
|
296
313
|
}
|
|
@@ -23,6 +23,8 @@ module.exports = {
|
|
|
23
23
|
METADATA: '_ml_obs.meta.metadata',
|
|
24
24
|
COST_TAGS: '_ml_obs.meta.metadata._dd.cost_tags',
|
|
25
25
|
AGENT_MANIFEST: '_ml_obs.meta.metadata._dd.agent_manifest',
|
|
26
|
+
AGENT_VERSION: '_ml_obs.agent_version',
|
|
27
|
+
AGENT_VERSION_TAG_KEY: 'agent_version',
|
|
26
28
|
METRICS: '_ml_obs.metrics',
|
|
27
29
|
TOOL_DEFINITIONS: '_ml_obs.meta.tool_definitions',
|
|
28
30
|
ML_APP: '_ml_obs.meta.ml_app',
|
|
@@ -33,6 +35,7 @@ module.exports = {
|
|
|
33
35
|
|
|
34
36
|
PARENT_AGENT_NAME: '_ml_obs.parent_agent_name',
|
|
35
37
|
PARENT_AGENT_SPAN_ID: '_ml_obs.parent_agent_span_id',
|
|
38
|
+
PARENT_AGENT_VERSION: '_ml_obs.parent_agent_version',
|
|
36
39
|
PROPAGATED_PARENT_AGENT_ID_KEY: '_dd.p.llmobs_pagent_span_id',
|
|
37
40
|
PROPAGATED_PARENT_AGENT_NAME_KEY: '_dd.p.llmobs_pagent_name',
|
|
38
41
|
PROPAGATED_SAMPLE_RATE_KEY: '_dd.p.llmobs_sr',
|
|
@@ -12,6 +12,9 @@ const AUDIO_MIME_TYPES = {
|
|
|
12
12
|
mp3: 'audio/mpeg',
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
const DECISION_PROVIDER_TAG = 'decision_provider'
|
|
16
|
+
const DECISION_PROVIDER = 'openai'
|
|
17
|
+
|
|
15
18
|
module.exports = {
|
|
16
19
|
INPUT_TYPE_IMAGE,
|
|
17
20
|
INPUT_TYPE_FILE,
|
|
@@ -19,4 +22,6 @@ module.exports = {
|
|
|
19
22
|
IMAGE_FALLBACK,
|
|
20
23
|
FILE_FALLBACK,
|
|
21
24
|
AUDIO_MIME_TYPES,
|
|
25
|
+
DECISION_PROVIDER_TAG,
|
|
26
|
+
DECISION_PROVIDER,
|
|
22
27
|
}
|
|
@@ -8,7 +8,13 @@ const {
|
|
|
8
8
|
} = require('../../constants/tags')
|
|
9
9
|
const { audioMimeTypeFromFormat, formatAudioPart } = require('../../audio-utils')
|
|
10
10
|
const { safeJsonParse } = require('../../util')
|
|
11
|
-
const {
|
|
11
|
+
const {
|
|
12
|
+
AUDIO_MIME_TYPES,
|
|
13
|
+
DECISION_PROVIDER,
|
|
14
|
+
DECISION_PROVIDER_TAG,
|
|
15
|
+
IMAGE_FALLBACK,
|
|
16
|
+
INPUT_TYPE_IMAGE,
|
|
17
|
+
} = require('./constants')
|
|
12
18
|
const {
|
|
13
19
|
extractChatTemplateFromInstructions,
|
|
14
20
|
normalizePromptVariables,
|
|
@@ -479,6 +485,8 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin {
|
|
|
479
485
|
}
|
|
480
486
|
|
|
481
487
|
#tagDecision (span, inputs, response, error) {
|
|
488
|
+
this._tagger.tagSpanTags(span, { [DECISION_PROVIDER_TAG]: DECISION_PROVIDER })
|
|
489
|
+
|
|
482
490
|
const { input, questions } = inputs
|
|
483
491
|
|
|
484
492
|
const inputMessages = typeof input === 'string'
|
|
@@ -29,7 +29,7 @@ const {
|
|
|
29
29
|
getFunctionArguments,
|
|
30
30
|
validateKind,
|
|
31
31
|
} = require('./util')
|
|
32
|
-
const { buildAgentDeclaration } = require('./agent-manifest')
|
|
32
|
+
const { buildAgentDeclaration, toAgentVersion } = require('./agent-manifest')
|
|
33
33
|
/** @typedef {import('./agent-manifest').AgentDeclaration} AgentDeclaration */
|
|
34
34
|
const { storage } = require('./storage')
|
|
35
35
|
const telemetry = require('./telemetry')
|
|
@@ -163,7 +163,7 @@ class LLMObs extends NoopLLMObs {
|
|
|
163
163
|
const {
|
|
164
164
|
spanOptions,
|
|
165
165
|
...llmobsOptions
|
|
166
|
-
} = this.#extractOptions(options)
|
|
166
|
+
} = this.#extractOptions(options, kind)
|
|
167
167
|
|
|
168
168
|
if (fn.length > 1) {
|
|
169
169
|
return this._tracer.trace(name, spanOptions, (span, cb) =>
|
|
@@ -193,7 +193,7 @@ class LLMObs extends NoopLLMObs {
|
|
|
193
193
|
const {
|
|
194
194
|
spanOptions,
|
|
195
195
|
...llmobsOptions
|
|
196
|
-
} = this.#extractOptions(options)
|
|
196
|
+
} = this.#extractOptions(options, kind)
|
|
197
197
|
|
|
198
198
|
const llmobs = this
|
|
199
199
|
|
|
@@ -719,21 +719,32 @@ class LLMObs extends NoopLLMObs {
|
|
|
719
719
|
}
|
|
720
720
|
}
|
|
721
721
|
|
|
722
|
-
#extractOptions (options) {
|
|
722
|
+
#extractOptions (options, kind) {
|
|
723
723
|
const {
|
|
724
724
|
modelName,
|
|
725
725
|
modelProvider,
|
|
726
726
|
sessionId,
|
|
727
727
|
mlApp,
|
|
728
|
+
version,
|
|
728
729
|
_decorator,
|
|
729
730
|
...spanOptions
|
|
730
731
|
} = options
|
|
731
732
|
|
|
733
|
+
let agentVersion
|
|
734
|
+
if (version !== undefined) {
|
|
735
|
+
if (kind === 'agent') {
|
|
736
|
+
agentVersion = toAgentVersion(version)
|
|
737
|
+
} else {
|
|
738
|
+
logger.warn('Dropping the version option on a %s span, it is only supported for agent spans.', kind)
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
732
742
|
return {
|
|
733
743
|
mlApp,
|
|
734
744
|
modelName,
|
|
735
745
|
modelProvider,
|
|
736
746
|
sessionId,
|
|
747
|
+
agentVersion,
|
|
737
748
|
_decorator,
|
|
738
749
|
spanOptions,
|
|
739
750
|
}
|