dd-trace 6.3.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/index.electron.js +3 -0
- package/package.json +6 -2
- package/packages/datadog-instrumentations/src/ai.js +29 -24
- 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/router-helper.js +8 -8
- package/packages/datadog-instrumentations/src/http2/server.js +143 -17
- package/packages/datadog-instrumentations/src/jest.js +10 -4
- 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-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 +32 -1
- 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-vitest/src/index.js +4 -2
- package/packages/dd-trace/index.electron.js +3 -0
- package/packages/dd-trace/index.js +2 -37
- package/packages/dd-trace/src/bootstrap.js +39 -0
- 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/generated-config-types.d.ts +4 -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 +1 -0
- package/packages/dd-trace/src/llmobs/index.js +10 -2
- package/packages/dd-trace/src/llmobs/tagger.js +11 -7
- 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/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/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
|
@@ -15,6 +15,9 @@ class Writer {
|
|
|
15
15
|
this._interprocessCode = interprocessCode
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* @param {() => void} [onDone]
|
|
20
|
+
*/
|
|
18
21
|
flush (onDone) {
|
|
19
22
|
const count = this._encoder.count()
|
|
20
23
|
|
|
@@ -22,6 +25,8 @@ class Writer {
|
|
|
22
25
|
const payload = this._encoder.makePayload()
|
|
23
26
|
|
|
24
27
|
this._sendPayload(payload, onDone)
|
|
28
|
+
} else {
|
|
29
|
+
onDone?.()
|
|
25
30
|
}
|
|
26
31
|
}
|
|
27
32
|
|
|
@@ -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'
|
|
@@ -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;
|
|
@@ -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',
|
|
@@ -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,
|
|
@@ -141,7 +142,10 @@ class LLMObsTagger {
|
|
|
141
142
|
if (modelProvider) this._setTag(span, MODEL_PROVIDER, modelProvider)
|
|
142
143
|
|
|
143
144
|
const traceTags = span.context()._trace.tags
|
|
144
|
-
sessionId = sessionId ||
|
|
145
|
+
sessionId = sessionId ||
|
|
146
|
+
registry.get(parent)?.[SESSION_ID] ||
|
|
147
|
+
traceTags[SESSION_ID_TRACE_DEFAULT_KEY] ||
|
|
148
|
+
traceTags[PROPAGATED_SESSION_ID_KEY]
|
|
145
149
|
if (sessionId) this._setTag(span, SESSION_ID, sessionId)
|
|
146
150
|
if (integration) this._setTag(span, INTEGRATION, integration)
|
|
147
151
|
if (_decorator) this._setTag(span, DECORATOR, _decorator)
|
|
@@ -756,14 +760,14 @@ class LLMObsTagger {
|
|
|
756
760
|
tagsCarrier[key] = value
|
|
757
761
|
|
|
758
762
|
// The first session set in a trace becomes the trace-level default, stored on the trace-shared
|
|
759
|
-
//
|
|
760
|
-
//
|
|
761
|
-
//
|
|
762
|
-
//
|
|
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`.
|
|
763
767
|
if (key === SESSION_ID && value) {
|
|
764
768
|
const traceTags = span.context()._trace.tags
|
|
765
|
-
if (traceTags[
|
|
766
|
-
traceTags[
|
|
769
|
+
if (traceTags[SESSION_ID_TRACE_DEFAULT_KEY] === undefined) {
|
|
770
|
+
traceTags[SESSION_ID_TRACE_DEFAULT_KEY] = value
|
|
767
771
|
}
|
|
768
772
|
}
|
|
769
773
|
}
|
|
@@ -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 () {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { registerFeature } = require('../feature-registry')
|
|
4
|
+
|
|
5
|
+
const noop = new (require('./noop'))()
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @param {import('../proxy')} proxy
|
|
9
|
+
* @returns {boolean}
|
|
10
|
+
*/
|
|
11
|
+
function hasFlaggingProvider (proxy) {
|
|
12
|
+
const descriptor = Reflect.getOwnPropertyDescriptor(proxy, 'openfeature')
|
|
13
|
+
|
|
14
|
+
return descriptor?.value !== undefined && descriptor.value !== noop
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
registerFeature({
|
|
18
|
+
name: 'openfeature',
|
|
19
|
+
noop,
|
|
20
|
+
factory: () => require('./index'),
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {object} rc - RemoteConfig instance
|
|
24
|
+
* @param {import('../config/config-base')} config
|
|
25
|
+
* @param {import('../proxy')} proxy
|
|
26
|
+
*/
|
|
27
|
+
remoteConfig (rc, config, proxy) {
|
|
28
|
+
const openfeatureRemoteConfig = require('./remote_config')
|
|
29
|
+
openfeatureRemoteConfig.enable(rc, config, () => proxy.openfeature)
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {import('../config/config-base')} config
|
|
34
|
+
* @param {import('../tracer')} tracer
|
|
35
|
+
* @param {import('../proxy')} proxy
|
|
36
|
+
* @param {Function} lazyProxy
|
|
37
|
+
*/
|
|
38
|
+
enable (config, tracer, proxy, lazyProxy) {
|
|
39
|
+
if (config.experimental.flaggingProvider.enabled) {
|
|
40
|
+
proxy._modules.openfeature.enable(config)
|
|
41
|
+
if (!hasFlaggingProvider(proxy)) {
|
|
42
|
+
lazyProxy(proxy, 'openfeature', () => require('./flagging_provider'), tracer, config)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
})
|
|
@@ -8,8 +8,13 @@ class LogPropagator {
|
|
|
8
8
|
this._config = config
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* @param {DatadogSpanContext | null | undefined} spanContext
|
|
13
|
+
* @param {Record<string, unknown>} [carrier]
|
|
14
|
+
* @returns {Record<string, unknown> | undefined}
|
|
15
|
+
*/
|
|
11
16
|
inject (spanContext, carrier) {
|
|
12
|
-
if (
|
|
17
|
+
if (carrier === null) return
|
|
13
18
|
|
|
14
19
|
const dd = {}
|
|
15
20
|
let hasField = false
|
|
@@ -35,7 +40,11 @@ class LogPropagator {
|
|
|
35
40
|
hasField = true
|
|
36
41
|
}
|
|
37
42
|
|
|
38
|
-
if (hasField)
|
|
43
|
+
if (!hasField) return
|
|
44
|
+
|
|
45
|
+
carrier ??= {}
|
|
46
|
+
carrier.dd = dd
|
|
47
|
+
return carrier
|
|
39
48
|
}
|
|
40
49
|
|
|
41
50
|
extract (carrier) {
|