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.
Files changed (53) hide show
  1. package/index.electron.js +3 -0
  2. package/package.json +6 -2
  3. package/packages/datadog-instrumentations/src/ai.js +29 -24
  4. package/packages/datadog-instrumentations/src/cucumber.js +4 -3
  5. package/packages/datadog-instrumentations/src/express.js +20 -2
  6. package/packages/datadog-instrumentations/src/grpc/server.js +18 -0
  7. package/packages/datadog-instrumentations/src/helpers/router-helper.js +8 -8
  8. package/packages/datadog-instrumentations/src/http2/server.js +143 -17
  9. package/packages/datadog-instrumentations/src/jest.js +10 -4
  10. package/packages/datadog-instrumentations/src/router.js +180 -105
  11. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +41 -16
  12. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +21 -21
  13. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +14 -8
  14. package/packages/datadog-plugin-aws-sdk/src/util.js +0 -22
  15. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +5 -5
  16. package/packages/datadog-plugin-graphql/src/execute.js +90 -14
  17. package/packages/datadog-plugin-graphql/src/parse.js +22 -1
  18. package/packages/datadog-plugin-graphql/src/request.js +32 -1
  19. package/packages/datadog-plugin-graphql/src/utils.js +42 -0
  20. package/packages/datadog-plugin-graphql/src/validate.js +13 -1
  21. package/packages/datadog-plugin-http2/src/server.js +38 -6
  22. package/packages/datadog-plugin-jest/src/util.js +21 -6
  23. package/packages/datadog-plugin-vitest/src/index.js +4 -2
  24. package/packages/dd-trace/index.electron.js +3 -0
  25. package/packages/dd-trace/index.js +2 -37
  26. package/packages/dd-trace/src/bootstrap.js +39 -0
  27. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +14 -2
  28. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +22 -6
  29. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +5 -0
  30. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +55 -5
  31. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +8 -1
  32. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +5 -1
  33. package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -0
  34. package/packages/dd-trace/src/config/supported-configurations.json +17 -0
  35. package/packages/dd-trace/src/constants.js +7 -0
  36. package/packages/dd-trace/src/datastreams/pathway.js +6 -4
  37. package/packages/dd-trace/src/feature-registry.js +22 -0
  38. package/packages/dd-trace/src/llmobs/constants/tags.js +1 -0
  39. package/packages/dd-trace/src/llmobs/index.js +10 -2
  40. package/packages/dd-trace/src/llmobs/tagger.js +11 -7
  41. package/packages/dd-trace/src/noop/proxy.js +5 -4
  42. package/packages/dd-trace/src/openfeature/register.js +46 -0
  43. package/packages/dd-trace/src/opentracing/propagation/log.js +11 -2
  44. package/packages/dd-trace/src/opentracing/propagation/text_map.js +80 -14
  45. package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +10 -1
  46. package/packages/dd-trace/src/opentracing/tracer.js +7 -1
  47. package/packages/dd-trace/src/plugins/util/test.js +9 -4
  48. package/packages/dd-trace/src/plugins/util/web.js +12 -0
  49. package/packages/dd-trace/src/proxy.js +21 -9
  50. package/packages/dd-trace/src/standalone/index.js +0 -22
  51. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  52. package/vendor/dist/protobufjs/index.js +1 -1
  53. 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 = parsedResponse.meta?.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) continue
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 { skippableSuites, correlationId, coverage }
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'] || DEFAULT_EARLY_FLAKE_DETECTION_NUM_RETRIES,
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 { parseSkippableSuitesResponse } = require('./intelligent-test-runner/get-skippable-suites')
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
- return
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 || registry.get(parent)?.[SESSION_ID] || traceTags[PROPAGATED_SESSION_ID_KEY]
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
- // propagating tags so later spans (incl. those under a session-less parent) inherit it and it
760
- // rides `x-datadog-tags` across service boundaries. Established here, the single choke point for
761
- // session writes, so sessions post-populated by integrations after span start also seed it.
762
- // First-writer wins, so an explicit session still overrides locally.
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[PROPAGATED_SESSION_ID_KEY] === undefined) {
766
- traceTags[PROPAGATED_SESSION_ID_KEY] = value
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
- return this._tracer.inject.apply(this._tracer, arguments)
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 (!carrier) return
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) carrier.dd = dd
43
+ if (!hasField) return
44
+
45
+ carrier ??= {}
46
+ carrier.dd = dd
47
+ return carrier
39
48
  }
40
49
 
41
50
  extract (carrier) {