dd-trace 6.2.0 → 6.4.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/README.md +1 -1
- package/index.electron.js +3 -0
- package/package.json +8 -5
- package/packages/datadog-instrumentations/src/ai.js +29 -24
- package/packages/datadog-instrumentations/src/cookie.js +7 -1
- package/packages/datadog-instrumentations/src/cucumber.js +4 -3
- package/packages/datadog-instrumentations/src/express.js +20 -2
- package/packages/datadog-instrumentations/src/grpc/server.js +18 -0
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/modelcontextprotocol-sdk.js +30 -54
- package/packages/datadog-instrumentations/src/helpers/router-helper.js +8 -8
- package/packages/datadog-instrumentations/src/http/server.js +27 -0
- package/packages/datadog-instrumentations/src/http2/server.js +143 -17
- package/packages/datadog-instrumentations/src/jest.js +10 -4
- package/packages/datadog-instrumentations/src/modelcontextprotocol-sdk.js +248 -1
- package/packages/datadog-instrumentations/src/playwright.js +40 -3
- package/packages/datadog-instrumentations/src/router.js +180 -105
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +41 -16
- package/packages/datadog-plugin-aws-sdk/src/services/sns.js +21 -21
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +14 -8
- package/packages/datadog-plugin-aws-sdk/src/util.js +0 -22
- package/packages/datadog-plugin-azure-functions/src/index.js +2 -1
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +5 -5
- package/packages/datadog-plugin-graphql/src/execute.js +90 -14
- package/packages/datadog-plugin-graphql/src/parse.js +22 -1
- package/packages/datadog-plugin-graphql/src/request.js +33 -2
- package/packages/datadog-plugin-graphql/src/utils.js +42 -0
- package/packages/datadog-plugin-graphql/src/validate.js +13 -1
- package/packages/datadog-plugin-http2/src/server.js +38 -6
- package/packages/datadog-plugin-jest/src/util.js +21 -6
- package/packages/datadog-plugin-modelcontextprotocol-sdk/src/tracing.js +144 -26
- package/packages/datadog-plugin-modelcontextprotocol-sdk/src/utils.js +30 -0
- package/packages/datadog-plugin-playwright/src/index.js +1 -1
- package/packages/datadog-plugin-vitest/src/index.js +4 -2
- package/packages/datadog-shimmer/src/shimmer.js +9 -2
- package/packages/dd-trace/index.electron.js +3 -0
- package/packages/dd-trace/index.js +2 -37
- package/packages/dd-trace/src/appsec/channels.js +1 -0
- package/packages/dd-trace/src/appsec/index.js +6 -3
- package/packages/dd-trace/src/appsec/waf/waf_manager.js +1 -1
- package/packages/dd-trace/src/bootstrap.js +39 -0
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +23 -16
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +14 -2
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +22 -6
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +5 -0
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +55 -5
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +8 -1
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +5 -1
- package/packages/dd-trace/src/config/config-types.d.ts +8 -0
- package/packages/dd-trace/src/config/defaults.js +20 -15
- package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -0
- package/packages/dd-trace/src/config/index.js +13 -3
- package/packages/dd-trace/src/config/parsers.js +79 -0
- package/packages/dd-trace/src/config/supported-configurations.json +17 -0
- package/packages/dd-trace/src/constants.js +7 -0
- package/packages/dd-trace/src/datastreams/pathway.js +6 -4
- package/packages/dd-trace/src/feature-registry.js +22 -0
- package/packages/dd-trace/src/llmobs/constants/tags.js +2 -0
- package/packages/dd-trace/src/llmobs/index.js +10 -2
- package/packages/dd-trace/src/llmobs/tagger.js +19 -1
- package/packages/dd-trace/src/noop/proxy.js +5 -4
- package/packages/dd-trace/src/openfeature/register.js +46 -0
- package/packages/dd-trace/src/opentelemetry/span_processor.js +7 -5
- package/packages/dd-trace/src/opentelemetry/tracer_provider.js +32 -16
- package/packages/dd-trace/src/opentracing/propagation/log.js +11 -2
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +80 -14
- package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +10 -1
- package/packages/dd-trace/src/opentracing/tracer.js +7 -1
- package/packages/dd-trace/src/plugins/util/test.js +9 -4
- package/packages/dd-trace/src/plugins/util/web.js +12 -0
- package/packages/dd-trace/src/proxy.js +21 -9
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +20 -11
- package/packages/dd-trace/src/standalone/index.js +0 -22
- package/vendor/dist/@datadog/sketches-js/index.js +1 -1
- package/vendor/dist/protobufjs/index.js +1 -1
- package/vendor/dist/protobufjs/minimal/index.js +1 -1
|
@@ -28,12 +28,16 @@ function parseSkippableSuitesResponse (
|
|
|
28
28
|
if (validateRequiredFields) {
|
|
29
29
|
validateSkippableTestsResponse(parsedResponse)
|
|
30
30
|
}
|
|
31
|
-
const coverage =
|
|
31
|
+
const coverage = {}
|
|
32
|
+
for (const [filename, bitmap] of Object.entries(parsedResponse.meta?.coverage || {})) {
|
|
33
|
+
coverage[filename.replaceAll('\\', '/')] = bitmap
|
|
34
|
+
}
|
|
32
35
|
|
|
33
36
|
const skippableItems = parsedResponse
|
|
34
37
|
.data
|
|
35
38
|
.filter(({ type }) => type === testLevel)
|
|
36
39
|
const skippableSuites = []
|
|
40
|
+
let numExcludedByMissingLineCoverage = 0
|
|
37
41
|
for (const {
|
|
38
42
|
attributes: {
|
|
39
43
|
suite,
|
|
@@ -42,13 +46,58 @@ function parseSkippableSuitesResponse (
|
|
|
42
46
|
},
|
|
43
47
|
} of skippableItems) {
|
|
44
48
|
// Only reject candidates without backend line coverage when we need that coverage to backfill reports.
|
|
45
|
-
if (isCoverageReportUploadEnabled && isMissingLineCodeCoverage)
|
|
49
|
+
if (isCoverageReportUploadEnabled && isMissingLineCodeCoverage) {
|
|
50
|
+
numExcludedByMissingLineCoverage++
|
|
51
|
+
continue
|
|
52
|
+
}
|
|
46
53
|
|
|
47
54
|
skippableSuites.push(testLevel === 'suite' ? suite : { suite, name })
|
|
48
55
|
}
|
|
49
56
|
const correlationId = parsedResponse.meta?.correlation_id
|
|
50
57
|
|
|
51
|
-
return {
|
|
58
|
+
return {
|
|
59
|
+
skippableSuites,
|
|
60
|
+
correlationId,
|
|
61
|
+
coverage,
|
|
62
|
+
numReceivedSkippableItems: skippableItems.length,
|
|
63
|
+
numExcludedByMissingLineCoverage,
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Logs how missing line coverage affected the skippable candidates without logging their contents.
|
|
69
|
+
*
|
|
70
|
+
* @param {{
|
|
71
|
+
* skippableSuites: Array<string|{suite: string, name: string}>,
|
|
72
|
+
* numReceivedSkippableItems?: number,
|
|
73
|
+
* numExcludedByMissingLineCoverage?: number
|
|
74
|
+
* }} result - Parsed skippable response.
|
|
75
|
+
* @param {'suite'|'test'} testLevel - Test optimization skipping level.
|
|
76
|
+
* @returns {void}
|
|
77
|
+
*/
|
|
78
|
+
function logSkippableSuitesResponse (result, testLevel) {
|
|
79
|
+
if (result.numReceivedSkippableItems === undefined) {
|
|
80
|
+
log.debug('Number of received skippable %ss: %d', testLevel, result.skippableSuites.length)
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
log.debug(
|
|
85
|
+
'Received %d skippable %s candidates; excluded %d because line coverage is missing; %d remain.',
|
|
86
|
+
result.numReceivedSkippableItems,
|
|
87
|
+
testLevel,
|
|
88
|
+
result.numExcludedByMissingLineCoverage,
|
|
89
|
+
result.skippableSuites.length
|
|
90
|
+
)
|
|
91
|
+
if (
|
|
92
|
+
result.numReceivedSkippableItems > 0 &&
|
|
93
|
+
result.numExcludedByMissingLineCoverage === result.numReceivedSkippableItems
|
|
94
|
+
) {
|
|
95
|
+
log.warn(
|
|
96
|
+
'All %d skippable %s candidates were excluded: coverage upload is enabled but line coverage is missing.',
|
|
97
|
+
result.numReceivedSkippableItems,
|
|
98
|
+
testLevel
|
|
99
|
+
)
|
|
100
|
+
}
|
|
52
101
|
}
|
|
53
102
|
|
|
54
103
|
function getSkippableSuites ({
|
|
@@ -96,6 +145,7 @@ function getSkippableSuites ({
|
|
|
96
145
|
}, cb)
|
|
97
146
|
}, (err, data) => {
|
|
98
147
|
if (err) return done(err)
|
|
148
|
+
logSkippableSuitesResponse(data, testLevel)
|
|
99
149
|
done(null, data.skippableSuites, data.correlationId, data.coverage)
|
|
100
150
|
})
|
|
101
151
|
}
|
|
@@ -203,6 +253,7 @@ function fetchFromApi ({
|
|
|
203
253
|
const result = parseSkippableSuitesResponse(parsedResponse, {
|
|
204
254
|
testLevel,
|
|
205
255
|
isCoverageReportUploadEnabled,
|
|
256
|
+
validateRequiredFields: true,
|
|
206
257
|
})
|
|
207
258
|
const skippableItems = parsedResponse.data.filter(({ type }) => type === testLevel)
|
|
208
259
|
incrementCountMetric(
|
|
@@ -213,7 +264,6 @@ function fetchFromApi ({
|
|
|
213
264
|
skippableItems.length
|
|
214
265
|
)
|
|
215
266
|
distributionMetric(TELEMETRY_ITR_SKIPPABLE_TESTS_RESPONSE_BYTES, {}, res.length)
|
|
216
|
-
log.debug('Number of received skippable %ss:', testLevel, result.skippableSuites.length)
|
|
217
267
|
|
|
218
268
|
writeToCache(cacheKey, result)
|
|
219
269
|
|
|
@@ -225,4 +275,4 @@ function fetchFromApi ({
|
|
|
225
275
|
})
|
|
226
276
|
}
|
|
227
277
|
|
|
228
|
-
module.exports = { getSkippableSuites, parseSkippableSuitesResponse }
|
|
278
|
+
module.exports = { getSkippableSuites, logSkippableSuitesResponse, parseSkippableSuitesResponse }
|
|
@@ -49,7 +49,7 @@ function parseLibraryConfigurationResponse (rawJson, config = getConfig(), optio
|
|
|
49
49
|
requireGit,
|
|
50
50
|
isEarlyFlakeDetectionEnabled: isKnownTestsEnabled && (earlyFlakeDetectionConfig?.enabled ?? false),
|
|
51
51
|
earlyFlakeDetectionNumRetries:
|
|
52
|
-
earlyFlakeDetectionConfig?.slow_test_retries?.['5s']
|
|
52
|
+
earlyFlakeDetectionConfig?.slow_test_retries?.['5s'] ?? DEFAULT_EARLY_FLAKE_DETECTION_NUM_RETRIES,
|
|
53
53
|
earlyFlakeDetectionSlowTestRetries:
|
|
54
54
|
earlyFlakeDetectionConfig?.slow_test_retries ?? DEFAULT_EARLY_FLAKE_DETECTION_SLOW_TEST_RETRIES,
|
|
55
55
|
earlyFlakeDetectionFaultyThreshold:
|
|
@@ -74,6 +74,13 @@ function parseLibraryConfigurationResponse (rawJson, config = getConfig(), optio
|
|
|
74
74
|
settings.isSuitesSkippingEnabled = true
|
|
75
75
|
log.debug('Dangerously set test skipping to true')
|
|
76
76
|
}
|
|
77
|
+
if (
|
|
78
|
+
settings.isCoverageReportUploadEnabled &&
|
|
79
|
+
!config.testOptimization.DD_CIVISIBILITY_CODE_COVERAGE_REPORT_UPLOAD_ENABLED
|
|
80
|
+
) {
|
|
81
|
+
settings.isCoverageReportUploadEnabled = false
|
|
82
|
+
log.debug('Code coverage report upload was disabled by the environment variable')
|
|
83
|
+
}
|
|
77
84
|
|
|
78
85
|
return settings
|
|
79
86
|
}
|
|
@@ -6,7 +6,10 @@ const path = require('node:path')
|
|
|
6
6
|
const log = require('../log')
|
|
7
7
|
const { getNumFromKnownTests } = require('../plugins/util/test')
|
|
8
8
|
const { parseKnownTestsResponse } = require('./early-flake-detection/get-known-tests')
|
|
9
|
-
const {
|
|
9
|
+
const {
|
|
10
|
+
logSkippableSuitesResponse,
|
|
11
|
+
parseSkippableSuitesResponse,
|
|
12
|
+
} = require('./intelligent-test-runner/get-skippable-suites')
|
|
10
13
|
const { parseLibraryConfigurationResponse } = require('./requests/get-library-configuration')
|
|
11
14
|
const {
|
|
12
15
|
getNumFromTestManagementTests,
|
|
@@ -112,6 +115,7 @@ class TestOptimizationHttpCache {
|
|
|
112
115
|
const parsedResponse = JSON.parse(payload)
|
|
113
116
|
const result = parseSkippableSuitesResponse(parsedResponse, { ...options, validateRequiredFields: true })
|
|
114
117
|
const testLevel = options.testLevel || 'suite'
|
|
118
|
+
logSkippableSuitesResponse(result, testLevel)
|
|
115
119
|
const skippableItems = parsedResponse.data.filter(({ type }) => type === testLevel)
|
|
116
120
|
incrementCountMetric(
|
|
117
121
|
testLevel === 'test'
|
|
@@ -70,3 +70,11 @@ export type ConfigKey = KnownStringKeys<ConfigProperties>
|
|
|
70
70
|
export type ConfigPath = ConfigPathFor<ConfigProperties>
|
|
71
71
|
export type ConfigPathValue<TPath extends ConfigPath> = ConfigPathValueFor<ConfigProperties, TPath>
|
|
72
72
|
export type ConfigDefaults = Partial<{ [TPath in ConfigPath]: ConfigPathValue<TPath> }>
|
|
73
|
+
export type ConfigurationOption = {
|
|
74
|
+
property?: string
|
|
75
|
+
parser?: (value: unknown, optionName: string, source: string) => unknown
|
|
76
|
+
type: string
|
|
77
|
+
canonicalName?: string
|
|
78
|
+
transformer?: (value: unknown, optionName: string, source: string) => unknown
|
|
79
|
+
telemetryTransformer?: (value: unknown) => unknown
|
|
80
|
+
}
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const assert = require('node:assert')
|
|
3
4
|
const util = require('util')
|
|
4
5
|
|
|
5
6
|
const { DD_MAJOR } = require('../../../../version')
|
|
6
|
-
const {
|
|
7
|
+
const {
|
|
8
|
+
parsers,
|
|
9
|
+
programmaticTypeCoercions,
|
|
10
|
+
transformers,
|
|
11
|
+
telemetryTransformers,
|
|
12
|
+
setWarnInvalidValue,
|
|
13
|
+
} = require('./parsers')
|
|
7
14
|
const applyMajorOverrides = require('./major-overrides')
|
|
8
15
|
const {
|
|
9
16
|
supportedConfigurations,
|
|
@@ -70,6 +77,7 @@ for (const [name, value] of Object.entries(defaults)) {
|
|
|
70
77
|
function generateTelemetry (value = null, origin, optionName) {
|
|
71
78
|
const tableEntry = configurationsTable[optionName]
|
|
72
79
|
const { type, canonicalName = optionName } = tableEntry ?? { type: typeof value }
|
|
80
|
+
const error = parseErrors.get(`${canonicalName}${origin}`)
|
|
73
81
|
// Sensitive configurations are excluded from configuration telemetry: their
|
|
74
82
|
// entry is never added to `configWithOrigin`, the single source for every
|
|
75
83
|
// telemetry path (app-started and app-client-configuration-change).
|
|
@@ -81,7 +89,9 @@ function generateTelemetry (value = null, origin, optionName) {
|
|
|
81
89
|
// TODO: Validate that space separated tags are parsed by the backend. Optimizations would be possible with that.
|
|
82
90
|
// TODO: How to handle telemetry reporting for aliases?
|
|
83
91
|
if (value !== null) {
|
|
84
|
-
if (
|
|
92
|
+
if (error && typeof value === 'object') {
|
|
93
|
+
value = util.inspect(value, { customInspect: false, getters: false })
|
|
94
|
+
} else if (telemetryTransformers[type]) {
|
|
85
95
|
value = telemetryTransformers[type](value)
|
|
86
96
|
} else if (typeof value === 'object' && value !== null) {
|
|
87
97
|
// Custom optionsTable entries (no `configurationsTable` row, e.g. `logger`)
|
|
@@ -100,7 +110,6 @@ function generateTelemetry (value = null, origin, optionName) {
|
|
|
100
110
|
origin,
|
|
101
111
|
seq_id: seqId++,
|
|
102
112
|
}
|
|
103
|
-
const error = parseErrors.get(`${canonicalName}${origin}`)
|
|
104
113
|
if (error) {
|
|
105
114
|
parseErrors.delete(`${canonicalName}${origin}`)
|
|
106
115
|
telemetryEntry.error = error
|
|
@@ -135,12 +144,8 @@ function generateTelemetry (value = null, origin, optionName) {
|
|
|
135
144
|
const optionsTable = {
|
|
136
145
|
// Additional properties that are not supported by the supported-configurations.json file.
|
|
137
146
|
lookup: {
|
|
138
|
-
transformer (value) {
|
|
139
|
-
if (typeof value === 'function') {
|
|
140
|
-
return value
|
|
141
|
-
}
|
|
142
|
-
},
|
|
143
147
|
property: 'lookup',
|
|
148
|
+
type: 'FUNCTION',
|
|
144
149
|
},
|
|
145
150
|
logger: {
|
|
146
151
|
transformer (object) {
|
|
@@ -165,12 +170,15 @@ const optionsTable = {
|
|
|
165
170
|
}
|
|
166
171
|
},
|
|
167
172
|
property: 'logger',
|
|
173
|
+
type: 'MAP',
|
|
168
174
|
},
|
|
169
175
|
isCiVisibility: {
|
|
170
176
|
property: 'isCiVisibility',
|
|
177
|
+
type: 'BOOLEAN',
|
|
171
178
|
},
|
|
172
179
|
plugins: {
|
|
173
180
|
property: 'plugins',
|
|
181
|
+
type: 'BOOLEAN',
|
|
174
182
|
},
|
|
175
183
|
}
|
|
176
184
|
|
|
@@ -185,13 +193,7 @@ const parser = (value, optionName, source) => {
|
|
|
185
193
|
|
|
186
194
|
/**
|
|
187
195
|
* @template {import('./config-types').ConfigPath} TPath
|
|
188
|
-
* @type {Partial<Record<TPath,
|
|
189
|
-
* property?: string,
|
|
190
|
-
* parser: (value: unknown, optionName: string, source: string) => unknown,
|
|
191
|
-
* canonicalName?: string,
|
|
192
|
-
* transformer?: (value: unknown, optionName: string, source: string) => unknown,
|
|
193
|
-
* telemetryTransformer?: (value: unknown) => unknown
|
|
194
|
-
* }>>} ConfigurationsTable
|
|
196
|
+
* @type {Partial<Record<TPath, import('./config-types').ConfigurationOption>>} ConfigurationsTable
|
|
195
197
|
*/
|
|
196
198
|
const configurationsTable = {}
|
|
197
199
|
|
|
@@ -231,6 +233,7 @@ for (const [canonicalName, entries] of Object.entries(supportedConfigurations))
|
|
|
231
233
|
? `${entry.namespace}.${canonicalName}`
|
|
232
234
|
: (entry.internalPropertyName ?? entry.configurationNames?.[0] ?? canonicalName)
|
|
233
235
|
const type = entry.type.toUpperCase()
|
|
236
|
+
assert.ok(programmaticTypeCoercions[type])
|
|
234
237
|
|
|
235
238
|
let transformer = transformers[entry.transform]
|
|
236
239
|
if (entry.allowed) {
|
|
@@ -353,6 +356,8 @@ module.exports = {
|
|
|
353
356
|
parseErrors,
|
|
354
357
|
|
|
355
358
|
generateTelemetry,
|
|
359
|
+
|
|
360
|
+
warnInvalidValue,
|
|
356
361
|
}
|
|
357
362
|
|
|
358
363
|
// `dns` is instrumented, so requiring it pulls in the dns plugin, which loads
|
|
@@ -546,6 +546,7 @@ export interface GeneratedConfig {
|
|
|
546
546
|
DD_CIVISIBILITY_AGENTLESS_ENABLED: boolean;
|
|
547
547
|
DD_CIVISIBILITY_AGENTLESS_URL: URL | undefined;
|
|
548
548
|
DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER: string | undefined;
|
|
549
|
+
DD_CIVISIBILITY_CODE_COVERAGE_REPORT_UPLOAD_ENABLED: boolean;
|
|
549
550
|
DD_CIVISIBILITY_DANGEROUSLY_FORCE_COVERAGE: boolean;
|
|
550
551
|
DD_CIVISIBILITY_DANGEROUSLY_FORCE_TEST_SKIPPING: boolean;
|
|
551
552
|
DD_CIVISIBILITY_EARLY_FLAKE_DETECTION_ENABLED: boolean;
|
|
@@ -561,6 +562,7 @@ export interface GeneratedConfig {
|
|
|
561
562
|
DD_CIVISIBILITY_TEST_COMMAND: string | undefined;
|
|
562
563
|
DD_CIVISIBILITY_TEST_MODULE_ID: string | undefined;
|
|
563
564
|
DD_CIVISIBILITY_TEST_SESSION_ID: string | undefined;
|
|
565
|
+
DD_TEST_EARLY_FLAKE_DETECTION_RETRY_COUNT: number | undefined;
|
|
564
566
|
DD_TEST_FAILED_TEST_REPLAY_ENABLED: boolean;
|
|
565
567
|
DD_TEST_FAILURE_SCREENSHOTS_ENABLED: boolean | undefined;
|
|
566
568
|
DD_TEST_FLEET_CONFIG_PATH: string | undefined;
|
|
@@ -636,6 +638,7 @@ export interface GeneratedEnvVarConfig {
|
|
|
636
638
|
DD_CIVISIBILITY_AGENTLESS_ENABLED: boolean;
|
|
637
639
|
DD_CIVISIBILITY_AGENTLESS_URL: URL | undefined;
|
|
638
640
|
DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER: string | undefined;
|
|
641
|
+
DD_CIVISIBILITY_CODE_COVERAGE_REPORT_UPLOAD_ENABLED: boolean;
|
|
639
642
|
DD_CIVISIBILITY_DANGEROUSLY_FORCE_COVERAGE: boolean;
|
|
640
643
|
DD_CIVISIBILITY_DANGEROUSLY_FORCE_TEST_SKIPPING: boolean;
|
|
641
644
|
DD_CIVISIBILITY_EARLY_FLAKE_DETECTION_ENABLED: boolean;
|
|
@@ -794,6 +797,7 @@ export interface GeneratedEnvVarConfig {
|
|
|
794
797
|
DD_TELEMETRY_HEARTBEAT_INTERVAL: number;
|
|
795
798
|
DD_TELEMETRY_LOG_COLLECTION_ENABLED: boolean;
|
|
796
799
|
DD_TELEMETRY_METRICS_ENABLED: boolean;
|
|
800
|
+
DD_TEST_EARLY_FLAKE_DETECTION_RETRY_COUNT: number | undefined;
|
|
797
801
|
DD_TEST_FAILED_TEST_REPLAY_ENABLED: boolean;
|
|
798
802
|
DD_TEST_FAILURE_SCREENSHOTS_ENABLED: boolean | undefined;
|
|
799
803
|
DD_TEST_FLEET_CONFIG_PATH: string | undefined;
|
|
@@ -35,9 +35,10 @@ const {
|
|
|
35
35
|
configWithOrigin,
|
|
36
36
|
parseErrors,
|
|
37
37
|
generateTelemetry,
|
|
38
|
+
warnInvalidValue,
|
|
38
39
|
} = require('./defaults')
|
|
39
40
|
const { normalizeService } = require('./normalize-service')
|
|
40
|
-
const { transformers } = require('./parsers')
|
|
41
|
+
const { programmaticTypeCoercions, transformers } = require('./parsers')
|
|
41
42
|
|
|
42
43
|
const RUNTIME_ID = uuid()
|
|
43
44
|
|
|
@@ -55,6 +56,7 @@ const tracerMetrics = telemetryMetrics.manager.namespace('tracers')
|
|
|
55
56
|
* @typedef {import('../../../../index').TracerOptions} TracerOptions
|
|
56
57
|
* @typedef {import('./config-types').ConfigKey} ConfigKey
|
|
57
58
|
* @typedef {import('./config-types').ConfigPath} ConfigPath
|
|
59
|
+
* @typedef {import('./config-types').ConfigurationOption} ConfigurationOption
|
|
58
60
|
* @typedef {{
|
|
59
61
|
* value: import('./config-types').ConfigPathValue<ConfigPath>,
|
|
60
62
|
* source: TelemetrySource
|
|
@@ -121,6 +123,9 @@ function setAndTrack (config, name, value, rawValue = value, source = 'calculate
|
|
|
121
123
|
if (value == null) {
|
|
122
124
|
// TODO: This works as before while ignoring undefined programmatic options is not ideal.
|
|
123
125
|
if (source !== 'default') {
|
|
126
|
+
if (rawValue !== value) {
|
|
127
|
+
generateTelemetry(rawValue, source, name)
|
|
128
|
+
}
|
|
124
129
|
return
|
|
125
130
|
}
|
|
126
131
|
} else if (source === 'calculated' || source === 'remote_config') {
|
|
@@ -285,8 +290,13 @@ class Config extends ConfigBase {
|
|
|
285
290
|
continue
|
|
286
291
|
}
|
|
287
292
|
}
|
|
288
|
-
|
|
289
|
-
|
|
293
|
+
const coerced = programmaticTypeCoercions[entry.type](value)
|
|
294
|
+
if (coerced === undefined && value !== undefined) {
|
|
295
|
+
warnInvalidValue(value, fullName, source, `Invalid ${entry.type} input`)
|
|
296
|
+
}
|
|
297
|
+
const transformed = coerced !== undefined && entry.transformer
|
|
298
|
+
? entry.transformer(coerced, fullName, source)
|
|
299
|
+
: coerced
|
|
290
300
|
setAndTrack(this, entry.property ?? name, transformed, value, source)
|
|
291
301
|
}
|
|
292
302
|
}
|
|
@@ -297,8 +297,87 @@ const parsers = {
|
|
|
297
297
|
},
|
|
298
298
|
}
|
|
299
299
|
|
|
300
|
+
const programmaticTypeCoercions = {
|
|
301
|
+
/**
|
|
302
|
+
* @param {unknown} value
|
|
303
|
+
*/
|
|
304
|
+
BOOLEAN (value) {
|
|
305
|
+
if (typeof value === 'boolean') {
|
|
306
|
+
return value
|
|
307
|
+
}
|
|
308
|
+
if (typeof value === 'string') {
|
|
309
|
+
return parsers.BOOLEAN(value)
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
/**
|
|
313
|
+
* @param {unknown} value
|
|
314
|
+
*/
|
|
315
|
+
INT (value) {
|
|
316
|
+
if (typeof value === 'number' || typeof value === 'string' && value.trim() !== '') {
|
|
317
|
+
return parsers.INT(value)
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
/**
|
|
321
|
+
* @param {unknown} value
|
|
322
|
+
*/
|
|
323
|
+
DECIMAL (value) {
|
|
324
|
+
if (typeof value === 'number' || typeof value === 'string' && value.trim() !== '') {
|
|
325
|
+
return parsers.DECIMAL(value)
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
/**
|
|
329
|
+
* @param {unknown} value
|
|
330
|
+
*/
|
|
331
|
+
STRING (value) {
|
|
332
|
+
if (typeof value === 'string') {
|
|
333
|
+
return value
|
|
334
|
+
}
|
|
335
|
+
if (typeof value === 'boolean' || typeof value === 'number' && Number.isFinite(value)) {
|
|
336
|
+
return String(value)
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
/**
|
|
340
|
+
* @param {unknown} value
|
|
341
|
+
*/
|
|
342
|
+
FUNCTION (value) {
|
|
343
|
+
if (typeof value === 'function') {
|
|
344
|
+
return value
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
/**
|
|
348
|
+
* @param {unknown} value
|
|
349
|
+
*/
|
|
350
|
+
ARRAY (value) {
|
|
351
|
+
if (Array.isArray(value)) {
|
|
352
|
+
for (const item of value) {
|
|
353
|
+
if (typeof item !== 'string' && typeof item !== 'number' && typeof item !== 'boolean') {
|
|
354
|
+
return
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return value
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
/**
|
|
361
|
+
* @param {unknown} value
|
|
362
|
+
*/
|
|
363
|
+
MAP (value) {
|
|
364
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
365
|
+
return value
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
/**
|
|
369
|
+
* @param {unknown} value
|
|
370
|
+
*/
|
|
371
|
+
JSON (value) {
|
|
372
|
+
if (typeof value === 'object' && value !== null) {
|
|
373
|
+
return value
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
}
|
|
377
|
+
|
|
300
378
|
module.exports = {
|
|
301
379
|
parsers,
|
|
380
|
+
programmaticTypeCoercions,
|
|
302
381
|
transformers,
|
|
303
382
|
telemetryTransformers,
|
|
304
383
|
setWarnInvalidValue,
|
|
@@ -470,6 +470,14 @@
|
|
|
470
470
|
"namespace": "testOptimization"
|
|
471
471
|
}
|
|
472
472
|
],
|
|
473
|
+
"DD_CIVISIBILITY_CODE_COVERAGE_REPORT_UPLOAD_ENABLED": [
|
|
474
|
+
{
|
|
475
|
+
"implementation": "A",
|
|
476
|
+
"type": "boolean",
|
|
477
|
+
"default": "true",
|
|
478
|
+
"namespace": "testOptimization"
|
|
479
|
+
}
|
|
480
|
+
],
|
|
473
481
|
"DD_CIVISIBILITY_DANGEROUSLY_FORCE_COVERAGE": [
|
|
474
482
|
{
|
|
475
483
|
"implementation": "A",
|
|
@@ -494,6 +502,15 @@
|
|
|
494
502
|
"namespace": "testOptimization"
|
|
495
503
|
}
|
|
496
504
|
],
|
|
505
|
+
"DD_TEST_EARLY_FLAKE_DETECTION_RETRY_COUNT": [
|
|
506
|
+
{
|
|
507
|
+
"implementation": "A",
|
|
508
|
+
"type": "int",
|
|
509
|
+
"allowed": "0|[1-9][0-9]*",
|
|
510
|
+
"default": null,
|
|
511
|
+
"namespace": "testOptimization"
|
|
512
|
+
}
|
|
513
|
+
],
|
|
497
514
|
"DD_CIVISIBILITY_ENABLED": [
|
|
498
515
|
{
|
|
499
516
|
"implementation": "A",
|
|
@@ -31,6 +31,13 @@ module.exports = {
|
|
|
31
31
|
ERROR_MESSAGE: 'error.message',
|
|
32
32
|
ERROR_STACK: 'error.stack',
|
|
33
33
|
IGNORE_OTEL_ERROR: Symbol('ignore.otel.error'),
|
|
34
|
+
// Marks an `Http2Server` that another instrumentation (currently @grpc/grpc-js)
|
|
35
|
+
// owns and traces through its own span lifecycle over the raw HTTP/2 stream
|
|
36
|
+
// API. The http2 server instrumentation reads this to leave such servers
|
|
37
|
+
// untraced, so a gRPC call keeps a single span with gRPC as the top frame
|
|
38
|
+
// instead of gaining an extra web.request span. A module-local Symbol keeps
|
|
39
|
+
// the mark private to dd-trace and unforgeable from user code.
|
|
40
|
+
FOREIGN_HTTP2_SERVER: Symbol('foreign.http2.server'),
|
|
34
41
|
COMPONENT: 'component',
|
|
35
42
|
CLIENT_PORT_KEY: 'network.destination.port',
|
|
36
43
|
PEER_SERVICE_KEY: 'peer.service',
|
|
@@ -136,16 +136,18 @@ const DsmPathwayCodec = {
|
|
|
136
136
|
* @param {Buffer} dataStreamsContext.hash
|
|
137
137
|
* @param {number} dataStreamsContext.pathwayStartNs
|
|
138
138
|
* @param {number} dataStreamsContext.edgeStartNs
|
|
139
|
-
* @param {object} carrier
|
|
139
|
+
* @param {object} [carrier]
|
|
140
|
+
* @returns {object | undefined}
|
|
140
141
|
*/
|
|
141
142
|
encode (dataStreamsContext, carrier) {
|
|
142
|
-
if (!dataStreamsContext || !dataStreamsContext.hash)
|
|
143
|
-
|
|
144
|
-
}
|
|
143
|
+
if (!dataStreamsContext || !dataStreamsContext.hash) return
|
|
144
|
+
carrier ??= {}
|
|
145
145
|
carrier[CONTEXT_PROPAGATION_KEY_BASE64] = encodePathwayContextBase64(dataStreamsContext)
|
|
146
146
|
|
|
147
147
|
// eslint-disable-next-line eslint-rules/eslint-log-printf-style
|
|
148
148
|
log.debug(() => `Injected into DSM carrier: ${JSON.stringify(pick(carrier, logKeys))}.`)
|
|
149
|
+
|
|
150
|
+
return carrier
|
|
149
151
|
},
|
|
150
152
|
|
|
151
153
|
/**
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {object} Feature
|
|
5
|
+
* @property {string} name
|
|
6
|
+
* @property {object} noop
|
|
7
|
+
* @property {() => object} factory
|
|
8
|
+
* @property {Function} [remoteConfig]
|
|
9
|
+
* @property {Function} [enable]
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** @type {{ [name: string]: Feature }} */
|
|
13
|
+
const features = {}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {Feature} feature
|
|
17
|
+
*/
|
|
18
|
+
function registerFeature (feature) {
|
|
19
|
+
features[feature.name] = feature
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = { features, registerFeature }
|
|
@@ -4,6 +4,7 @@ module.exports = {
|
|
|
4
4
|
SPAN_KINDS: ['llm', 'agent', 'workflow', 'task', 'tool', 'embedding', 'retrieval'],
|
|
5
5
|
SPAN_KIND: '_ml_obs.meta.span.kind',
|
|
6
6
|
SESSION_ID: '_ml_obs.session_id',
|
|
7
|
+
SESSION_ID_TRACE_DEFAULT_KEY: '_ml_obs.trace_session_id',
|
|
7
8
|
DECORATOR: '_ml_obs.decorator',
|
|
8
9
|
INTEGRATION: '_ml_obs.integration',
|
|
9
10
|
METADATA: '_ml_obs.meta.metadata',
|
|
@@ -13,6 +14,7 @@ module.exports = {
|
|
|
13
14
|
ML_APP: '_ml_obs.meta.ml_app',
|
|
14
15
|
PROPAGATED_PARENT_ID_KEY: '_dd.p.llmobs_parent_id',
|
|
15
16
|
PROPAGATED_ML_APP_KEY: '_dd.p.llmobs_ml_app',
|
|
17
|
+
PROPAGATED_SESSION_ID_KEY: '_dd.p.llmobs_sid',
|
|
16
18
|
PARENT_ID_KEY: '_ml_obs.llmobs_parent_id',
|
|
17
19
|
PROPAGATED_SAMPLE_RATE_KEY: '_dd.p.llmobs_sr',
|
|
18
20
|
PROPAGATED_SAMPLING_DECISION_KEY: '_dd.p.llmobs_sd',
|
|
@@ -7,8 +7,11 @@ const { DD_MAJOR } = require('../../../../version')
|
|
|
7
7
|
const startupLogs = require('../startup-log')
|
|
8
8
|
const {
|
|
9
9
|
ML_APP,
|
|
10
|
+
SESSION_ID,
|
|
11
|
+
SESSION_ID_TRACE_DEFAULT_KEY,
|
|
10
12
|
PROPAGATED_ML_APP_KEY,
|
|
11
13
|
PROPAGATED_PARENT_ID_KEY,
|
|
14
|
+
PROPAGATED_SESSION_ID_KEY,
|
|
12
15
|
SAMPLE_RATE,
|
|
13
16
|
SAMPLING_DECISION,
|
|
14
17
|
PROPAGATED_SAMPLE_RATE_KEY,
|
|
@@ -110,7 +113,7 @@ function disable () {
|
|
|
110
113
|
}
|
|
111
114
|
|
|
112
115
|
// since LLMObs traces can extend between services and be the same trace,
|
|
113
|
-
// we need to propagate the parent id, mlApp, and sampling rate/decision.
|
|
116
|
+
// we need to propagate the parent id, mlApp, session id, and sampling rate/decision.
|
|
114
117
|
function handleLLMObsInjection ({ carrier }) {
|
|
115
118
|
// Respect the standard propagator's gate: when trace tag propagation is
|
|
116
119
|
// disabled, don't write `x-datadog-tags` for LLMObs either.
|
|
@@ -130,8 +133,12 @@ function handleLLMObsInjection ({ carrier }) {
|
|
|
130
133
|
mlObsSpanTags?.[SAMPLE_RATE] ?? parentContext?._trace?.tags?.[PROPAGATED_SAMPLE_RATE_KEY]
|
|
131
134
|
const samplingDecision =
|
|
132
135
|
mlObsSpanTags?.[SAMPLING_DECISION] ?? parentContext?._trace?.tags?.[PROPAGATED_SAMPLING_DECISION_KEY]
|
|
136
|
+
const sessionId =
|
|
137
|
+
mlObsSpanTags?.[SESSION_ID] ??
|
|
138
|
+
parentContext?._trace?.tags?.[SESSION_ID_TRACE_DEFAULT_KEY] ??
|
|
139
|
+
parentContext?._trace?.tags?.[PROPAGATED_SESSION_ID_KEY]
|
|
133
140
|
|
|
134
|
-
if (!parentId && !mlApp && samplingDecision == null) return
|
|
141
|
+
if (!parentId && !mlApp && samplingDecision == null && !sessionId) return
|
|
135
142
|
|
|
136
143
|
// `_injectTags` only writes `x-datadog-tags` when the trace has `_dd.p.*`
|
|
137
144
|
// tags, so it may be undefined here — coalesce before appending.
|
|
@@ -139,6 +146,7 @@ function handleLLMObsInjection ({ carrier }) {
|
|
|
139
146
|
let tags = existing || ''
|
|
140
147
|
if (parentId) tags += `${tags ? ',' : ''}${PROPAGATED_PARENT_ID_KEY}=${parentId}`
|
|
141
148
|
if (mlApp) tags += `${tags ? ',' : ''}${PROPAGATED_ML_APP_KEY}=${mlApp}`
|
|
149
|
+
if (sessionId) tags += `${tags ? ',' : ''}${PROPAGATED_SESSION_ID_KEY}=${sessionId}`
|
|
142
150
|
if (sampleRate != null) tags += `${tags ? ',' : ''}${PROPAGATED_SAMPLE_RATE_KEY}=${sampleRate}`
|
|
143
151
|
if (samplingDecision != null) tags += `${tags ? ',' : ''}${PROPAGATED_SAMPLING_DECISION_KEY}=${samplingDecision}`
|
|
144
152
|
if (tags !== existing) carrier['x-datadog-tags'] = tags
|
|
@@ -7,6 +7,7 @@ const {
|
|
|
7
7
|
MODEL_NAME,
|
|
8
8
|
MODEL_PROVIDER,
|
|
9
9
|
SESSION_ID,
|
|
10
|
+
SESSION_ID_TRACE_DEFAULT_KEY,
|
|
10
11
|
ML_APP,
|
|
11
12
|
SPAN_KIND,
|
|
12
13
|
INPUT_VALUE,
|
|
@@ -35,6 +36,7 @@ const {
|
|
|
35
36
|
INTEGRATION,
|
|
36
37
|
DECORATOR,
|
|
37
38
|
PROPAGATED_ML_APP_KEY,
|
|
39
|
+
PROPAGATED_SESSION_ID_KEY,
|
|
38
40
|
DEFAULT_PROMPT_NAME,
|
|
39
41
|
INTERNAL_CONTEXT_VARIABLE_KEYS,
|
|
40
42
|
INTERNAL_QUERY_VARIABLE_KEYS,
|
|
@@ -139,7 +141,11 @@ class LLMObsTagger {
|
|
|
139
141
|
if (modelName) this.tagModelName(span, modelName)
|
|
140
142
|
if (modelProvider) this._setTag(span, MODEL_PROVIDER, modelProvider)
|
|
141
143
|
|
|
142
|
-
|
|
144
|
+
const traceTags = span.context()._trace.tags
|
|
145
|
+
sessionId = sessionId ||
|
|
146
|
+
registry.get(parent)?.[SESSION_ID] ||
|
|
147
|
+
traceTags[SESSION_ID_TRACE_DEFAULT_KEY] ||
|
|
148
|
+
traceTags[PROPAGATED_SESSION_ID_KEY]
|
|
143
149
|
if (sessionId) this._setTag(span, SESSION_ID, sessionId)
|
|
144
150
|
if (integration) this._setTag(span, INTEGRATION, integration)
|
|
145
151
|
if (_decorator) this._setTag(span, DECORATOR, _decorator)
|
|
@@ -752,6 +758,18 @@ class LLMObsTagger {
|
|
|
752
758
|
|
|
753
759
|
const tagsCarrier = registry.get(span)
|
|
754
760
|
tagsCarrier[key] = value
|
|
761
|
+
|
|
762
|
+
// The first session set in a trace becomes the trace-level default, stored on the trace-shared
|
|
763
|
+
// tags so later spans (incl. those under a session-less parent) inherit it in-process.
|
|
764
|
+
// Established here, the single choke point for session writes, so sessions post-populated by
|
|
765
|
+
// integrations after span start also seed it. First-writer wins, so an explicit session still
|
|
766
|
+
// overrides locally. Cross-service injection is handled centrally in `handleLLMObsInjection`.
|
|
767
|
+
if (key === SESSION_ID && value) {
|
|
768
|
+
const traceTags = span.context()._trace.tags
|
|
769
|
+
if (traceTags[SESSION_ID_TRACE_DEFAULT_KEY] === undefined) {
|
|
770
|
+
traceTags[SESSION_ID_TRACE_DEFAULT_KEY] = value
|
|
771
|
+
}
|
|
772
|
+
}
|
|
755
773
|
}
|
|
756
774
|
}
|
|
757
775
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { features } = require('../feature-registry')
|
|
3
4
|
const NoopAppsecSdk = require('../appsec/sdk/noop')
|
|
4
5
|
const NoopLLMObsSDK = require('../llmobs/noop')
|
|
5
|
-
const NoopFlaggingProvider = require('../openfeature/noop')
|
|
6
6
|
const NoopAIGuardSDK = require('../aiguard/noop')
|
|
7
7
|
const NoopDogStatsDClient = require('./dogstatsd')
|
|
8
8
|
const NoopTracer = require('./tracer')
|
|
@@ -11,7 +11,6 @@ const noop = new NoopTracer()
|
|
|
11
11
|
const noopAppsec = new NoopAppsecSdk()
|
|
12
12
|
const noopDogStatsDClient = new NoopDogStatsDClient()
|
|
13
13
|
const noopLLMObs = new NoopLLMObsSDK(noop)
|
|
14
|
-
const noopOpenFeatureProvider = new NoopFlaggingProvider()
|
|
15
14
|
const noopAIGuard = new NoopAIGuardSDK()
|
|
16
15
|
const noopProfiling = {
|
|
17
16
|
setCustomLabelKeys () {},
|
|
@@ -25,8 +24,10 @@ class NoopProxy {
|
|
|
25
24
|
this.appsec = noopAppsec
|
|
26
25
|
this.dogstatsd = noopDogStatsDClient
|
|
27
26
|
this.llmobs = noopLLMObs
|
|
28
|
-
this.openfeature = noopOpenFeatureProvider
|
|
29
27
|
this.aiguard = noopAIGuard
|
|
28
|
+
for (const { name, noop } of Object.values(features)) {
|
|
29
|
+
this[name] = noop
|
|
30
|
+
}
|
|
30
31
|
this.setBaggageItem = (key, value) => {}
|
|
31
32
|
this.getBaggageItem = (key) => {}
|
|
32
33
|
this.getAllBaggageItems = () => {}
|
|
@@ -82,7 +83,7 @@ class NoopProxy {
|
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
inject () {
|
|
85
|
-
|
|
86
|
+
this._tracer.inject.apply(this._tracer, arguments)
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
extract () {
|