dd-trace 5.103.0 → 5.105.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 (213) hide show
  1. package/LICENSE-3rdparty.csv +90 -102
  2. package/index.d.ts +107 -6
  3. package/package.json +18 -17
  4. package/packages/datadog-core/src/storage.js +1 -1
  5. package/packages/datadog-instrumentations/src/aerospike.js +1 -1
  6. package/packages/datadog-instrumentations/src/ai.js +8 -7
  7. package/packages/datadog-instrumentations/src/aws-sdk.js +15 -2
  8. package/packages/datadog-instrumentations/src/azure-cosmos.js +7 -0
  9. package/packages/datadog-instrumentations/src/azure-functions.js +3 -0
  10. package/packages/datadog-instrumentations/src/cassandra-driver.js +5 -2
  11. package/packages/datadog-instrumentations/src/cucumber.js +181 -35
  12. package/packages/datadog-instrumentations/src/dns.js +54 -18
  13. package/packages/datadog-instrumentations/src/elasticsearch.js +4 -4
  14. package/packages/datadog-instrumentations/src/fastify.js +142 -82
  15. package/packages/datadog-instrumentations/src/graphql.js +188 -67
  16. package/packages/datadog-instrumentations/src/grpc/client.js +48 -32
  17. package/packages/datadog-instrumentations/src/helpers/ai-messages.js +322 -14
  18. package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +1 -1
  19. package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
  20. package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -1
  21. package/packages/datadog-instrumentations/src/helpers/kafka.js +17 -0
  22. package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +269 -0
  23. package/packages/datadog-instrumentations/src/helpers/promise-instrumentor.js +42 -0
  24. package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +3 -2
  26. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +19 -6
  27. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/azure-cosmos.js +50 -0
  28. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
  29. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/langgraph.js +4 -2
  30. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +85 -0
  31. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +31 -229
  32. package/packages/datadog-instrumentations/src/hono.js +54 -3
  33. package/packages/datadog-instrumentations/src/http/client.js +2 -2
  34. package/packages/datadog-instrumentations/src/http/server.js +9 -4
  35. package/packages/datadog-instrumentations/src/ioredis.js +3 -3
  36. package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +163 -0
  37. package/packages/datadog-instrumentations/src/jest.js +390 -183
  38. package/packages/datadog-instrumentations/src/kafkajs.js +140 -17
  39. package/packages/datadog-instrumentations/src/mariadb.js +1 -1
  40. package/packages/datadog-instrumentations/src/memcached.js +2 -1
  41. package/packages/datadog-instrumentations/src/mocha/main.js +399 -107
  42. package/packages/datadog-instrumentations/src/mocha/utils.js +48 -8
  43. package/packages/datadog-instrumentations/src/mongodb-core.js +1 -1
  44. package/packages/datadog-instrumentations/src/mongoose.js +10 -12
  45. package/packages/datadog-instrumentations/src/mysql.js +2 -2
  46. package/packages/datadog-instrumentations/src/mysql2.js +1 -1
  47. package/packages/datadog-instrumentations/src/nats.js +182 -0
  48. package/packages/datadog-instrumentations/src/nyc.js +38 -1
  49. package/packages/datadog-instrumentations/src/openai.js +33 -18
  50. package/packages/datadog-instrumentations/src/oracledb.js +6 -1
  51. package/packages/datadog-instrumentations/src/pg.js +1 -1
  52. package/packages/datadog-instrumentations/src/pino.js +17 -5
  53. package/packages/datadog-instrumentations/src/playwright.js +537 -297
  54. package/packages/datadog-instrumentations/src/router.js +80 -34
  55. package/packages/datadog-instrumentations/src/stripe.js +1 -1
  56. package/packages/datadog-instrumentations/src/vitest.js +246 -149
  57. package/packages/datadog-plugin-avsc/src/schema_iterator.js +1 -1
  58. package/packages/datadog-plugin-azure-cosmos/src/index.js +144 -0
  59. package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -1
  60. package/packages/datadog-plugin-azure-functions/src/index.js +5 -2
  61. package/packages/datadog-plugin-azure-service-bus/src/producer.js +1 -1
  62. package/packages/datadog-plugin-bunyan/src/index.js +28 -0
  63. package/packages/datadog-plugin-cucumber/src/index.js +17 -3
  64. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +223 -45
  65. package/packages/datadog-plugin-cypress/src/support.js +69 -1
  66. package/packages/datadog-plugin-dns/src/lookup.js +8 -6
  67. package/packages/datadog-plugin-elasticsearch/src/index.js +28 -8
  68. package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +1 -1
  69. package/packages/datadog-plugin-graphql/src/execute.js +2 -0
  70. package/packages/datadog-plugin-graphql/src/resolve.js +64 -67
  71. package/packages/datadog-plugin-graphql/src/utils.js +4 -1
  72. package/packages/datadog-plugin-http/src/server.js +40 -15
  73. package/packages/datadog-plugin-jest/src/index.js +11 -3
  74. package/packages/datadog-plugin-jest/src/util.js +15 -8
  75. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +1 -1
  76. package/packages/datadog-plugin-kafkajs/src/producer.js +35 -0
  77. package/packages/datadog-plugin-langgraph/src/stream.js +1 -1
  78. package/packages/datadog-plugin-mocha/src/index.js +19 -4
  79. package/packages/datadog-plugin-mongodb-core/src/index.js +311 -35
  80. package/packages/datadog-plugin-nats/src/consumer.js +43 -0
  81. package/packages/datadog-plugin-nats/src/index.js +20 -0
  82. package/packages/datadog-plugin-nats/src/producer.js +62 -0
  83. package/packages/datadog-plugin-nats/src/util.js +33 -0
  84. package/packages/datadog-plugin-next/src/index.js +5 -3
  85. package/packages/datadog-plugin-openai/src/tracing.js +15 -2
  86. package/packages/datadog-plugin-oracledb/src/index.js +13 -2
  87. package/packages/datadog-plugin-pino/src/index.js +42 -0
  88. package/packages/datadog-plugin-playwright/src/index.js +4 -4
  89. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +1 -1
  90. package/packages/datadog-plugin-redis/src/index.js +37 -2
  91. package/packages/datadog-plugin-rhea/src/producer.js +1 -1
  92. package/packages/datadog-plugin-router/src/index.js +33 -44
  93. package/packages/datadog-plugin-selenium/src/index.js +1 -1
  94. package/packages/datadog-plugin-undici/src/index.js +19 -0
  95. package/packages/datadog-plugin-vitest/src/index.js +24 -20
  96. package/packages/datadog-plugin-winston/src/index.js +30 -0
  97. package/packages/datadog-shimmer/src/shimmer.js +49 -21
  98. package/packages/dd-trace/src/aiguard/index.js +1 -1
  99. package/packages/dd-trace/src/aiguard/sdk.js +1 -1
  100. package/packages/dd-trace/src/appsec/api_security_sampler.js +1 -1
  101. package/packages/dd-trace/src/appsec/blocking.js +2 -2
  102. package/packages/dd-trace/src/appsec/index.js +11 -4
  103. package/packages/dd-trace/src/appsec/reporter.js +24 -11
  104. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
  105. package/packages/dd-trace/src/appsec/sdk/utils.js +1 -1
  106. package/packages/dd-trace/src/appsec/user_tracking.js +5 -4
  107. package/packages/dd-trace/src/baggage.js +7 -1
  108. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +0 -1
  109. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +25 -13
  110. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -1
  111. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +5 -3
  112. package/packages/dd-trace/src/ci-visibility/test-optimization-cache.js +70 -6
  113. package/packages/dd-trace/src/config/generated-config-types.d.ts +7 -2
  114. package/packages/dd-trace/src/config/supported-configurations.json +36 -8
  115. package/packages/dd-trace/src/crashtracking/crashtracker.js +15 -3
  116. package/packages/dd-trace/src/datastreams/context.js +4 -2
  117. package/packages/dd-trace/src/datastreams/writer.js +2 -4
  118. package/packages/dd-trace/src/debugger/devtools_client/condition.js +5 -8
  119. package/packages/dd-trace/src/encode/0.4.js +124 -108
  120. package/packages/dd-trace/src/encode/0.5.js +114 -26
  121. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +57 -42
  122. package/packages/dd-trace/src/encode/agentless-json.js +4 -2
  123. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +32 -13
  124. package/packages/dd-trace/src/encode/span-stats.js +16 -16
  125. package/packages/dd-trace/src/encode/tags-processors.js +16 -0
  126. package/packages/dd-trace/src/exporters/common/agents.js +3 -1
  127. package/packages/dd-trace/src/exporters/common/request.js +3 -1
  128. package/packages/dd-trace/src/id.js +17 -4
  129. package/packages/dd-trace/src/lambda/handler.js +2 -4
  130. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -1
  131. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
  132. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
  133. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +9 -7
  134. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  135. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +1 -1
  136. package/packages/dd-trace/src/llmobs/sdk.js +10 -16
  137. package/packages/dd-trace/src/llmobs/span_processor.js +3 -3
  138. package/packages/dd-trace/src/llmobs/tagger.js +9 -1
  139. package/packages/dd-trace/src/llmobs/telemetry.js +1 -1
  140. package/packages/dd-trace/src/llmobs/util.js +66 -3
  141. package/packages/dd-trace/src/log/index.js +1 -1
  142. package/packages/dd-trace/src/log/writer.js +3 -1
  143. package/packages/dd-trace/src/msgpack/chunk.js +394 -10
  144. package/packages/dd-trace/src/msgpack/index.js +96 -2
  145. package/packages/dd-trace/src/noop/span.js +3 -1
  146. package/packages/dd-trace/src/openfeature/encoding.js +70 -0
  147. package/packages/dd-trace/src/openfeature/flagging_provider.js +20 -0
  148. package/packages/dd-trace/src/openfeature/span-enrichment-hook.js +143 -0
  149. package/packages/dd-trace/src/openfeature/span-enrichment.js +149 -0
  150. package/packages/dd-trace/src/openfeature/writers/exposures.js +51 -20
  151. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -1
  152. package/packages/dd-trace/src/opentelemetry/span-helpers.js +4 -3
  153. package/packages/dd-trace/src/opentelemetry/span.js +1 -1
  154. package/packages/dd-trace/src/opentracing/propagation/log.js +18 -7
  155. package/packages/dd-trace/src/opentracing/propagation/text_map.js +62 -67
  156. package/packages/dd-trace/src/opentracing/span.js +59 -19
  157. package/packages/dd-trace/src/opentracing/span_context.js +49 -0
  158. package/packages/dd-trace/src/plugins/apollo.js +3 -1
  159. package/packages/dd-trace/src/plugins/ci_plugin.js +23 -33
  160. package/packages/dd-trace/src/plugins/database.js +7 -6
  161. package/packages/dd-trace/src/plugins/index.js +4 -0
  162. package/packages/dd-trace/src/plugins/log_injection.js +56 -0
  163. package/packages/dd-trace/src/plugins/log_plugin.js +3 -46
  164. package/packages/dd-trace/src/plugins/outbound.js +1 -1
  165. package/packages/dd-trace/src/plugins/plugin.js +15 -17
  166. package/packages/dd-trace/src/plugins/tracing.js +48 -8
  167. package/packages/dd-trace/src/plugins/util/git.js +3 -1
  168. package/packages/dd-trace/src/plugins/util/test.js +318 -13
  169. package/packages/dd-trace/src/plugins/util/web.js +89 -64
  170. package/packages/dd-trace/src/priority_sampler.js +2 -2
  171. package/packages/dd-trace/src/profiling/profiler.js +2 -2
  172. package/packages/dd-trace/src/profiling/profilers/wall.js +10 -4
  173. package/packages/dd-trace/src/sampling_rule.js +7 -7
  174. package/packages/dd-trace/src/scope.js +7 -5
  175. package/packages/dd-trace/src/service-naming/extra-services.js +14 -0
  176. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +10 -0
  177. package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +8 -0
  178. package/packages/dd-trace/src/service-naming/source-resolver.js +46 -0
  179. package/packages/dd-trace/src/span_format.js +190 -58
  180. package/packages/dd-trace/src/spanleak.js +1 -1
  181. package/packages/dd-trace/src/standalone/index.js +3 -3
  182. package/packages/dd-trace/src/tagger.js +0 -2
  183. package/vendor/dist/@apm-js-collab/code-transformer/index.js +70 -39
  184. package/vendor/dist/@datadog/sketches-js/LICENSE +10 -36
  185. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  186. package/vendor/dist/protobufjs/index.js +1 -1
  187. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  188. package/packages/dd-trace/src/msgpack/encoder.js +0 -308
  189. package/packages/dd-trace/src/plugins/structured_log_plugin.js +0 -9
  190. package/vendor/dist/opentracing/LICENSE +0 -201
  191. package/vendor/dist/opentracing/binary_carrier.d.ts +0 -11
  192. package/vendor/dist/opentracing/constants.d.ts +0 -61
  193. package/vendor/dist/opentracing/examples/demo/demo.d.ts +0 -2
  194. package/vendor/dist/opentracing/ext/tags.d.ts +0 -90
  195. package/vendor/dist/opentracing/functions.d.ts +0 -20
  196. package/vendor/dist/opentracing/global_tracer.d.ts +0 -14
  197. package/vendor/dist/opentracing/index.d.ts +0 -12
  198. package/vendor/dist/opentracing/index.js +0 -1
  199. package/vendor/dist/opentracing/mock_tracer/index.d.ts +0 -5
  200. package/vendor/dist/opentracing/mock_tracer/mock_context.d.ts +0 -13
  201. package/vendor/dist/opentracing/mock_tracer/mock_report.d.ts +0 -16
  202. package/vendor/dist/opentracing/mock_tracer/mock_span.d.ts +0 -50
  203. package/vendor/dist/opentracing/mock_tracer/mock_tracer.d.ts +0 -26
  204. package/vendor/dist/opentracing/noop.d.ts +0 -8
  205. package/vendor/dist/opentracing/reference.d.ts +0 -33
  206. package/vendor/dist/opentracing/span.d.ts +0 -147
  207. package/vendor/dist/opentracing/span_context.d.ts +0 -26
  208. package/vendor/dist/opentracing/test/api_compatibility.d.ts +0 -16
  209. package/vendor/dist/opentracing/test/mocktracer_implemenation.d.ts +0 -3
  210. package/vendor/dist/opentracing/test/noop_implementation.d.ts +0 -4
  211. package/vendor/dist/opentracing/test/opentracing_api.d.ts +0 -3
  212. package/vendor/dist/opentracing/test/unittest.d.ts +0 -2
  213. package/vendor/dist/opentracing/tracer.d.ts +0 -127
@@ -0,0 +1,143 @@
1
+ 'use strict'
2
+
3
+ const { channel } = require('dc-polyfill')
4
+ const log = require('../log')
5
+ const { SpanEnrichmentState } = require('./span-enrichment')
6
+
7
+ const finishCh = channel('dd-trace:span:finish')
8
+
9
+ /**
10
+ * OpenFeature hook that enriches APM spans with feature flag evaluation data.
11
+ *
12
+ * Implements the OpenFeature `finally` hook interface to capture flag evaluations
13
+ * and add span tags for observability. Tags are accumulated during the span's
14
+ * lifetime and applied when the root span finishes.
15
+ *
16
+ * Span tags added:
17
+ * - `ffe_flags_enc`: Base64 delta-varint encoded serial IDs
18
+ * - `ffe_subjects_enc`: JSON dict of SHA256(targeting_key) → encoded serial IDs
19
+ * - `ffe_runtime_defaults`: JSON dict of flag_key → default value string
20
+ */
21
+ class SpanEnrichmentHook {
22
+ #tracer
23
+ /** @type {WeakMap<object, SpanEnrichmentState>} */
24
+ #spanStates = new WeakMap()
25
+
26
+ /**
27
+ * Handler for span finish channel. Applies accumulated tags to the span.
28
+ * Arrow function to preserve `this` binding for channel subscription.
29
+ *
30
+ * @param {object} span - The span that is finishing
31
+ */
32
+ #onSpanFinish = (span) => {
33
+ const state = this.#spanStates.get(span)
34
+ if (!state || !state.hasData()) return
35
+
36
+ try {
37
+ const tags = state.toSpanTags()
38
+
39
+ for (const [key, value] of Object.entries(tags)) {
40
+ if (value) {
41
+ span.setTag(key, value)
42
+ }
43
+ }
44
+ } catch (err) {
45
+ log.warn('SpanEnrichmentHook: error applying span tags: %s', err.message)
46
+ } finally {
47
+ this.#spanStates.delete(span)
48
+ }
49
+ }
50
+
51
+ /**
52
+ * @param {import('../tracer')} tracer - Datadog tracer instance
53
+ */
54
+ constructor (tracer) {
55
+ this.#tracer = tracer
56
+ finishCh.subscribe(this.#onSpanFinish)
57
+ }
58
+
59
+ /**
60
+ * Called by the OpenFeature SDK after every flag evaluation (success or error).
61
+ *
62
+ * @param {object} hookContext - Hook context containing the flag key and evaluation context
63
+ * @param {string} hookContext.flagKey - The flag key being evaluated
64
+ * @param {object} [hookContext.context] - Evaluation context
65
+ * @param {string} [hookContext.context.targetingKey] - Targeting key
66
+ * @param {object} evaluationDetails - Full evaluation details including flag metadata
67
+ * @param {object} [evaluationDetails.flagMetadata] - Metadata from the provider
68
+ * @param {number} [evaluationDetails.flagMetadata.__dd_split_serial_id] - Serial ID from UFC split
69
+ * @param {boolean} [evaluationDetails.flagMetadata.__dd_do_log] - Whether to log subject
70
+ * @param {string} [evaluationDetails.variant] - Variant key if flag was found in UFC
71
+ * @param {boolean|string|number|object} [evaluationDetails.value] - Evaluated value
72
+ * @returns {void}
73
+ */
74
+ finally (hookContext, evaluationDetails) {
75
+ try {
76
+ const rootSpan = this._getRootSpan()
77
+ if (!rootSpan) return
78
+
79
+ const state = this._getOrCreateState(rootSpan)
80
+ const { flagKey, context } = hookContext || {}
81
+ const { flagMetadata, variant, value } = evaluationDetails || {}
82
+
83
+ const serialId = flagMetadata?.__dd_split_serial_id
84
+ const doLog = flagMetadata?.__dd_do_log ?? false
85
+ const targetingKey = context?.targetingKey
86
+
87
+ if (serialId != null) {
88
+ state.addSerialId(serialId)
89
+
90
+ if (doLog && targetingKey) {
91
+ state.addSubject(targetingKey, serialId)
92
+ }
93
+ } else if (variant === undefined) {
94
+ state.addDefault(flagKey, value)
95
+ }
96
+ } catch (err) {
97
+ log.warn('SpanEnrichmentHook: error in finally hook: %s', err.message)
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Get the root span for the current trace context.
103
+ * The root span is always the first span in trace.started since spans
104
+ * are added in creation order and the root is created first.
105
+ *
106
+ * @returns {object|null} The root span, or null if no active span
107
+ * @private
108
+ */
109
+ _getRootSpan () {
110
+ const span = this.#tracer.scope().active()
111
+ if (!span) return null
112
+
113
+ const trace = span.context()._trace
114
+
115
+ return trace?.started?.[0] ?? span
116
+ }
117
+
118
+ /**
119
+ * Get or create enrichment state for a span.
120
+ *
121
+ * @param {object} span - The span to get state for
122
+ * @returns {SpanEnrichmentState} The enrichment state
123
+ * @private
124
+ */
125
+ _getOrCreateState (span) {
126
+ let state = this.#spanStates.get(span)
127
+ if (!state) {
128
+ state = new SpanEnrichmentState()
129
+ this.#spanStates.set(span, state)
130
+ }
131
+ return state
132
+ }
133
+
134
+ /**
135
+ * Cleanup method to unsubscribe from channels.
136
+ * Should be called when the provider is shut down.
137
+ */
138
+ destroy () {
139
+ finishCh.unsubscribe(this.#onSpanFinish)
140
+ }
141
+ }
142
+
143
+ module.exports = SpanEnrichmentHook
@@ -0,0 +1,149 @@
1
+ 'use strict'
2
+
3
+ const log = require('../log')
4
+
5
+ const { encodeDeltaVarint, hashTargetingKey } = require('./encoding')
6
+
7
+ const MAX_SERIAL_IDS = 200
8
+ const MAX_SUBJECTS = 10
9
+ const MAX_EXPERIMENTS_PER_SUBJECT = 20
10
+ const MAX_DEFAULTS = 5
11
+ const MAX_DEFAULT_VALUE_LENGTH = 64
12
+
13
+ /**
14
+ * Manages feature flag enrichment state for a single root span.
15
+ * Accumulates serial IDs, subjects, and defaults throughout the span's lifetime.
16
+ */
17
+ class SpanEnrichmentState {
18
+ constructor () {
19
+ /** @type {Set<number>} */
20
+ this._serialIds = new Set()
21
+
22
+ /** @type {Map<string, Set<number>>} hashed targeting key -> serial IDs */
23
+ this._subjects = new Map()
24
+
25
+ /** @type {Map<string, string>} flag key -> runtime default value */
26
+ this._defaults = new Map()
27
+ }
28
+
29
+ /**
30
+ * Add a serial ID from a flag evaluation.
31
+ *
32
+ * @param {number} serialId - The serial ID to add
33
+ * @returns {boolean} True if added, false if limit reached
34
+ */
35
+ addSerialId (serialId) {
36
+ if (this._serialIds.size >= MAX_SERIAL_IDS) {
37
+ log.debug('SpanEnrichment: MAX_SERIAL_IDS limit (%d) reached, dropping serialId %d', MAX_SERIAL_IDS, serialId)
38
+ return false
39
+ }
40
+ this._serialIds.add(serialId)
41
+ return true
42
+ }
43
+
44
+ /**
45
+ * Add a subject (targeting key) with its associated serial ID.
46
+ * Only called when doLog=true.
47
+ *
48
+ * @param {string} targetingKey - The targeting key (will be hashed)
49
+ * @param {number} serialId - The serial ID associated with this evaluation
50
+ * @returns {boolean} True if added, false if limit reached
51
+ */
52
+ addSubject (targetingKey, serialId) {
53
+ const hashedKey = hashTargetingKey(targetingKey)
54
+
55
+ if (this._subjects.has(hashedKey)) {
56
+ const subjectIds = this._subjects.get(hashedKey)
57
+ if (subjectIds.size >= MAX_EXPERIMENTS_PER_SUBJECT) {
58
+ log.debug('SpanEnrichment: MAX_EXPERIMENTS_PER_SUBJECT limit (%d) reached for subject',
59
+ MAX_EXPERIMENTS_PER_SUBJECT)
60
+ return false
61
+ }
62
+ subjectIds.add(serialId)
63
+ return true
64
+ }
65
+
66
+ if (this._subjects.size >= MAX_SUBJECTS) {
67
+ log.debug('SpanEnrichment: MAX_SUBJECTS limit (%d) reached, dropping subject', MAX_SUBJECTS)
68
+ return false
69
+ }
70
+
71
+ this._subjects.set(hashedKey, new Set([serialId]))
72
+ return true
73
+ }
74
+
75
+ /**
76
+ * Add a default fallback for a flag not found in UFC.
77
+ *
78
+ * @param {string} flagKey - The flag key
79
+ * @param {boolean|string|number|object} defaultValue - The default value used
80
+ * @returns {boolean} True if added, false if limit reached
81
+ */
82
+ addDefault (flagKey, defaultValue) {
83
+ if (this._defaults.has(flagKey)) {
84
+ return true
85
+ }
86
+
87
+ if (this._defaults.size >= MAX_DEFAULTS) {
88
+ log.debug('SpanEnrichment: MAX_DEFAULTS limit (%d) reached, dropping flag %s', MAX_DEFAULTS, flagKey)
89
+ return false
90
+ }
91
+
92
+ let valueStr = typeof defaultValue === 'object' && defaultValue !== null
93
+ ? JSON.stringify(defaultValue)
94
+ : String(defaultValue)
95
+
96
+ if (valueStr.length > MAX_DEFAULT_VALUE_LENGTH) {
97
+ valueStr = valueStr.slice(0, MAX_DEFAULT_VALUE_LENGTH)
98
+ }
99
+
100
+ this._defaults.set(flagKey, valueStr)
101
+ return true
102
+ }
103
+
104
+ /**
105
+ * Check if there is any enrichment data to add to the span.
106
+ * Note: _subjects is not checked because addSubject() is never called without first
107
+ * calling addSerialId(), so _subjects having data necessitates _serialIds having data.
108
+ *
109
+ * @returns {boolean} True if there is data to add
110
+ */
111
+ hasData () {
112
+ return this._serialIds.size > 0 || this._defaults.size > 0
113
+ }
114
+
115
+ /**
116
+ * Convert accumulated state to span tags.
117
+ *
118
+ * @returns {object} Object with ffe_flags_enc, ffe_subjects_enc, and ffe_runtime_defaults tags
119
+ */
120
+ toSpanTags () {
121
+ const tags = {}
122
+
123
+ if (this._serialIds.size > 0) {
124
+ tags.ffe_flags_enc = encodeDeltaVarint(this._serialIds)
125
+ }
126
+
127
+ if (this._subjects.size > 0) {
128
+ const subjectsObj = Object.fromEntries(
129
+ [...this._subjects].map(([key, ids]) => [key, encodeDeltaVarint(ids)])
130
+ )
131
+ tags.ffe_subjects_enc = JSON.stringify(subjectsObj)
132
+ }
133
+
134
+ if (this._defaults.size > 0) {
135
+ tags.ffe_runtime_defaults = JSON.stringify(Object.fromEntries(this._defaults))
136
+ }
137
+
138
+ return tags
139
+ }
140
+ }
141
+
142
+ module.exports = {
143
+ SpanEnrichmentState,
144
+ MAX_SERIAL_IDS,
145
+ MAX_SUBJECTS,
146
+ MAX_EXPERIMENTS_PER_SUBJECT,
147
+ MAX_DEFAULTS,
148
+ MAX_DEFAULT_VALUE_LENGTH,
149
+ }
@@ -8,8 +8,14 @@ const {
8
8
  EVP_PAYLOAD_SIZE_LIMIT,
9
9
  EVP_EVENT_SIZE_LIMIT,
10
10
  } = require('../constants/constants')
11
+ const log = require('../../log')
11
12
  const BaseFFEWriter = require('./base')
12
13
 
14
+ // Disabled-state cap. Drops invalidate experiment results because the provider's
15
+ // exposure dedupe cache keeps masking dropped events after recovery. The first
16
+ // drop emits a warning and `droppedEventCount` accumulates the cumulative loss.
17
+ const PENDING_MAX_EVENTS = 1000
18
+
13
19
  /**
14
20
  * @typedef {object} ExposureEvent
15
21
  * @property {number} timestamp - Unix timestamp in milliseconds
@@ -42,11 +48,21 @@ const BaseFFEWriter = require('./base')
42
48
  * ExposuresWriter is responsible for sending exposure events to the Datadog Agent.
43
49
  */
44
50
  class ExposuresWriter extends BaseFFEWriter {
51
+ // Disabled until the agent strategy probe resolves.
52
+ #enabled = false
53
+
54
+ /** @type {ExposureEvent[]} */
55
+ #pendingEvents = []
56
+
57
+ /** @type {ExposureContext} */
58
+ #context
59
+
60
+ #dropWarned = false
61
+
45
62
  /**
46
63
  * @param {import('../../config/config-base')} config - Tracer configuration object
47
64
  */
48
65
  constructor (config) {
49
- // Build full EVP endpoint path
50
66
  const basePath = EVP_PROXY_AGENT_BASE_PATH.replace(/\/+$/, '')
51
67
  const endpoint = EXPOSURES_ENDPOINT.replace(/^\/+/, '')
52
68
  const fullEndpoint = `${basePath}/${endpoint}`
@@ -60,33 +76,33 @@ class ExposuresWriter extends BaseFFEWriter {
60
76
  [EVP_SUBDOMAIN_HEADER_NAME]: EVP_SUBDOMAIN_VALUE,
61
77
  },
62
78
  })
63
- this._enabled = false // Start disabled until agent strategy is set
64
- this._pendingEvents = [] // Buffer events until enabled
65
79
 
80
+ /** @type {ExposureContext} */
66
81
  const context = {
67
82
  service: config.service,
68
83
  }
69
- // Only include version and env if they are defined
84
+
70
85
  if (config.version !== undefined) {
71
86
  context.version = config.version
72
87
  }
88
+
73
89
  if (config.env !== undefined) {
74
90
  context.env = config.env
75
91
  }
76
92
 
77
- this._context = context
93
+ this.#context = context
78
94
  }
79
95
 
80
96
  /**
81
97
  * @param {boolean} enabled - Whether to enable the writer
82
98
  */
83
99
  setEnabled (enabled) {
84
- this._enabled = enabled
100
+ this.#enabled = enabled
85
101
 
86
- if (enabled && this._pendingEvents.length > 0) {
102
+ if (enabled && this.#pendingEvents.length > 0) {
87
103
  // Flush all pending events as a batch
88
- super.append(this._pendingEvents)
89
- this._pendingEvents = []
104
+ super.append(this.#pendingEvents)
105
+ this.#pendingEvents = []
90
106
  }
91
107
  }
92
108
 
@@ -95,24 +111,38 @@ class ExposuresWriter extends BaseFFEWriter {
95
111
  * @param {ExposureEvent|ExposureEvent[]} events - Exposure event(s) to append
96
112
  */
97
113
  append (events) {
98
- if (!this._enabled) {
99
- // Buffer events until writer is ready
100
- if (Array.isArray(events)) {
101
- this._pendingEvents.push(...events)
102
- } else {
103
- this._pendingEvents.push(events)
104
- }
114
+ if (this.#enabled) {
115
+ super.append(events)
105
116
  return
106
117
  }
107
- super.append(events)
118
+
119
+ const eventArray = Array.isArray(events) ? events : [events]
120
+ this.#pendingEvents.push(...eventArray)
121
+ if (this.#pendingEvents.length > PENDING_MAX_EVENTS) {
122
+ const dropped = this.#pendingEvents.length - PENDING_MAX_EVENTS
123
+ this.#pendingEvents.splice(0, dropped)
124
+ this._droppedEvents += dropped
125
+ if (!this.#dropWarned) {
126
+ this.#dropWarned = true
127
+ log.warn(
128
+ '%s dropped exposure event(s) at cap %d. This may invalidate experiment results.',
129
+ this.constructor.name, PENDING_MAX_EVENTS)
130
+ }
131
+ }
132
+ }
133
+
134
+ /**
135
+ * @returns {number} Cumulative number of exposure events dropped due to buffer overflow.
136
+ */
137
+ get droppedEventCount () {
138
+ return this._droppedEvents
108
139
  }
109
140
 
110
141
  /**
111
142
  * Flushes buffered exposure events to the agent
112
143
  */
113
144
  flush () {
114
- if (!this._enabled) {
115
- // Don't flush when disabled
145
+ if (!this.#enabled) {
116
146
  return
117
147
  }
118
148
  super.flush()
@@ -125,6 +155,7 @@ class ExposuresWriter extends BaseFFEWriter {
125
155
  */
126
156
  makePayload (events) {
127
157
  const formattedEvents = events.map(event => {
158
+ /** @type {ExposureEvent} */
128
159
  return {
129
160
  timestamp: event.timestamp || Date.now(),
130
161
  allocation: {
@@ -145,7 +176,7 @@ class ExposuresWriter extends BaseFFEWriter {
145
176
  })
146
177
 
147
178
  return {
148
- context: this._context,
179
+ context: this.#context,
149
180
  exposures: formattedEvents,
150
181
  }
151
182
  }
@@ -336,7 +336,7 @@ class MetricAggregator {
336
336
  }
337
337
  }
338
338
 
339
- this.#applyDeltaTemporality(metricsMap, lastExportedState)
339
+ this.#applyDeltaTemporality(metricsMap.values(), lastExportedState)
340
340
  return metricsMap
341
341
  }
342
342
 
@@ -7,6 +7,7 @@ const { timeInputToHrTime } = require('../../../../vendor/dist/@opentelemetry/co
7
7
  const { ERROR_MESSAGE, ERROR_STACK, ERROR_TYPE, IGNORE_OTEL_ERROR } = require('../constants')
8
8
  const DatadogSpanContext = require('../opentracing/span_context')
9
9
  const TraceState = require('../opentracing/propagation/tracestate')
10
+ const { DD_MAJOR } = require('../../../../version')
10
11
 
11
12
  const id = require('../id')
12
13
 
@@ -176,8 +177,8 @@ function setOtelAttributes (ddSpan, attributes) {
176
177
  function addOtelLink (ddSpan, link, attrs) {
177
178
  if (!isWritable(ddSpan) || !link) return
178
179
 
179
- // TODO: Drop the (context, attrs) form in v6.0.0.
180
- const { context, attributes } = isOtelLink(link)
180
+ // v5 still accepts the legacy `addLink(context, attrs)` shape; v6 only takes `addLink(otel.Link)`.
181
+ const { context, attributes } = isOtelLink(link) || DD_MAJOR >= 6
181
182
  ? link
182
183
  : { context: link, attributes: attrs ?? {} }
183
184
 
@@ -230,7 +231,7 @@ function recordException (ddSpan, exception, timeInput) {
230
231
  [ERROR_TYPE]: exception.name,
231
232
  [ERROR_MESSAGE]: exception.message,
232
233
  [ERROR_STACK]: exception.stack,
233
- [IGNORE_OTEL_ERROR]: ddSpan.context()._tags[IGNORE_OTEL_ERROR] ?? true,
234
+ [IGNORE_OTEL_ERROR]: ddSpan.context().getTag(IGNORE_OTEL_ERROR) ?? true,
234
235
  })
235
236
 
236
237
  const attributes = {}
@@ -216,7 +216,7 @@ class Span extends BridgeSpanBase {
216
216
  'ptr.hash': ptrHash,
217
217
  'link.kind': 'span-pointer',
218
218
  }
219
- return this.addLink(zeroContext, attributes)
219
+ return this.addLink({ context: zeroContext, attributes })
220
220
  }
221
221
 
222
222
  /**
@@ -11,20 +11,31 @@ class LogPropagator {
11
11
  inject (spanContext, carrier) {
12
12
  if (!carrier) return
13
13
 
14
- carrier.dd = {}
14
+ const dd = {}
15
+ let hasField = false
15
16
 
16
17
  if (spanContext) {
17
- carrier.dd.trace_id = this._config.traceId128BitGenerationEnabled &&
18
+ dd.trace_id = this._config.traceId128BitGenerationEnabled &&
18
19
  this._config.traceId128BitLoggingEnabled && spanContext._trace.tags['_dd.p.tid']
19
20
  ? spanContext.toTraceId(true)
20
21
  : spanContext.toTraceId()
21
-
22
- carrier.dd.span_id = spanContext.toSpanId()
22
+ dd.span_id = spanContext.toSpanId()
23
+ hasField = true
24
+ }
25
+ if (this._config.service) {
26
+ dd.service = this._config.service
27
+ hasField = true
28
+ }
29
+ if (this._config.version) {
30
+ dd.version = this._config.version
31
+ hasField = true
32
+ }
33
+ if (this._config.env) {
34
+ dd.env = this._config.env
35
+ hasField = true
23
36
  }
24
37
 
25
- if (this._config.service) carrier.dd.service = this._config.service
26
- if (this._config.version) carrier.dd.version = this._config.version
27
- if (this._config.env) carrier.dd.env = this._config.env
38
+ if (hasField) carrier.dd = dd
28
39
  }
29
40
 
30
41
  extract (carrier) {