dd-trace 5.102.0 → 5.104.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 (201) hide show
  1. package/ext/exporters.js +1 -0
  2. package/index.d.ts +25 -3
  3. package/package.json +15 -13
  4. package/packages/datadog-esbuild/src/utils.js +2 -2
  5. package/packages/datadog-instrumentations/src/ai.js +1 -1
  6. package/packages/datadog-instrumentations/src/aws-sdk.js +2 -2
  7. package/packages/datadog-instrumentations/src/cassandra-driver.js +5 -2
  8. package/packages/datadog-instrumentations/src/confluentinc-kafka-javascript.js +32 -15
  9. package/packages/datadog-instrumentations/src/couchbase.js +69 -220
  10. package/packages/datadog-instrumentations/src/cucumber.js +104 -31
  11. package/packages/datadog-instrumentations/src/elasticsearch.js +4 -4
  12. package/packages/datadog-instrumentations/src/electron/preload.js +42 -0
  13. package/packages/datadog-instrumentations/src/electron.js +240 -0
  14. package/packages/datadog-instrumentations/src/fetch.js +5 -5
  15. package/packages/datadog-instrumentations/src/graphql.js +13 -17
  16. package/packages/datadog-instrumentations/src/grpc/client.js +48 -32
  17. package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +2 -2
  18. package/packages/datadog-instrumentations/src/helpers/hook.js +4 -1
  19. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  20. package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -2
  21. package/packages/datadog-instrumentations/src/helpers/kafka.js +58 -0
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +3 -2
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +19 -5
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +14 -13
  25. package/packages/datadog-instrumentations/src/http/client.js +2 -2
  26. package/packages/datadog-instrumentations/src/ioredis.js +18 -14
  27. package/packages/datadog-instrumentations/src/jest.js +382 -84
  28. package/packages/datadog-instrumentations/src/kafkajs.js +184 -174
  29. package/packages/datadog-instrumentations/src/mariadb.js +1 -1
  30. package/packages/datadog-instrumentations/src/memcached.js +2 -1
  31. package/packages/datadog-instrumentations/src/mocha/main.js +309 -56
  32. package/packages/datadog-instrumentations/src/mocha/utils.js +48 -8
  33. package/packages/datadog-instrumentations/src/mongodb-core.js +34 -9
  34. package/packages/datadog-instrumentations/src/mongoose.js +10 -12
  35. package/packages/datadog-instrumentations/src/mysql.js +2 -2
  36. package/packages/datadog-instrumentations/src/mysql2.js +1 -1
  37. package/packages/datadog-instrumentations/src/pg.js +25 -11
  38. package/packages/datadog-instrumentations/src/playwright.js +449 -60
  39. package/packages/datadog-instrumentations/src/redis.js +19 -10
  40. package/packages/datadog-instrumentations/src/router.js +4 -2
  41. package/packages/datadog-instrumentations/src/vitest.js +246 -149
  42. package/packages/datadog-plugin-apollo/src/gateway/request.js +1 -21
  43. package/packages/datadog-plugin-aws-sdk/src/base.js +18 -24
  44. package/packages/datadog-plugin-aws-sdk/src/services/cloudwatchlogs.js +1 -1
  45. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +1 -1
  46. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -1
  47. package/packages/datadog-plugin-aws-sdk/src/services/lambda.js +1 -1
  48. package/packages/datadog-plugin-aws-sdk/src/services/redshift.js +1 -1
  49. package/packages/datadog-plugin-aws-sdk/src/services/s3.js +1 -1
  50. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +2 -2
  51. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -1
  52. package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +1 -1
  53. package/packages/datadog-plugin-couchbase/src/index.js +58 -52
  54. package/packages/datadog-plugin-cucumber/src/index.js +1 -0
  55. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +239 -40
  56. package/packages/datadog-plugin-cypress/src/support.js +13 -1
  57. package/packages/datadog-plugin-elasticsearch/src/index.js +28 -8
  58. package/packages/datadog-plugin-electron/src/index.js +17 -0
  59. package/packages/datadog-plugin-electron/src/ipc.js +143 -0
  60. package/packages/datadog-plugin-electron/src/net.js +82 -0
  61. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +27 -18
  62. package/packages/datadog-plugin-graphql/src/execute.js +6 -28
  63. package/packages/datadog-plugin-graphql/src/resolve.js +30 -35
  64. package/packages/datadog-plugin-graphql/src/tools/signature.js +32 -7
  65. package/packages/datadog-plugin-graphql/src/tools/transforms.js +118 -100
  66. package/packages/datadog-plugin-graphql/src/utils.js +33 -1
  67. package/packages/datadog-plugin-grpc/src/client.js +6 -7
  68. package/packages/datadog-plugin-grpc/src/util.js +57 -22
  69. package/packages/datadog-plugin-http/src/client.js +2 -2
  70. package/packages/datadog-plugin-jest/src/index.js +92 -50
  71. package/packages/datadog-plugin-kafkajs/src/producer.js +32 -0
  72. package/packages/datadog-plugin-mocha/src/index.js +1 -0
  73. package/packages/datadog-plugin-mongodb-core/src/index.js +70 -69
  74. package/packages/datadog-plugin-mysql/src/index.js +1 -1
  75. package/packages/datadog-plugin-openai/src/services.js +2 -1
  76. package/packages/datadog-plugin-pg/src/index.js +3 -3
  77. package/packages/datadog-plugin-playwright/src/index.js +4 -0
  78. package/packages/datadog-plugin-redis/src/index.js +54 -24
  79. package/packages/datadog-plugin-undici/src/index.js +19 -0
  80. package/packages/datadog-plugin-vitest/src/index.js +19 -7
  81. package/packages/datadog-shimmer/src/shimmer.js +35 -0
  82. package/packages/dd-trace/src/aiguard/index.js +3 -1
  83. package/packages/dd-trace/src/aiguard/sdk.js +36 -30
  84. package/packages/dd-trace/src/aiguard/tags.js +20 -11
  85. package/packages/dd-trace/src/appsec/blocking.js +2 -2
  86. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +2 -2
  87. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +1 -1
  88. package/packages/dd-trace/src/appsec/index.js +10 -3
  89. package/packages/dd-trace/src/appsec/reporter.js +19 -5
  90. package/packages/dd-trace/src/azure_metadata.js +17 -6
  91. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +4 -4
  92. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +4 -2
  93. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +6 -4
  94. package/packages/dd-trace/src/ci-visibility/requests/fs-cache.js +1 -1
  95. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -1
  96. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +5 -3
  97. package/packages/dd-trace/src/config/defaults.js +3 -14
  98. package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -1
  99. package/packages/dd-trace/src/config/helper.js +4 -0
  100. package/packages/dd-trace/src/config/index.js +2 -2
  101. package/packages/dd-trace/src/config/major-overrides.js +98 -0
  102. package/packages/dd-trace/src/config/parsers.js +7 -1
  103. package/packages/dd-trace/src/config/supported-configurations.json +60 -38
  104. package/packages/dd-trace/src/crashtracking/crashtracker.js +15 -3
  105. package/packages/dd-trace/src/datastreams/checkpointer.js +2 -2
  106. package/packages/dd-trace/src/datastreams/context.js +4 -2
  107. package/packages/dd-trace/src/datastreams/manager.js +1 -1
  108. package/packages/dd-trace/src/datastreams/processor.js +2 -2
  109. package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +2 -2
  110. package/packages/dd-trace/src/debugger/devtools_client/source-maps.js +1 -1
  111. package/packages/dd-trace/src/debugger/devtools_client/state.js +2 -1
  112. package/packages/dd-trace/src/debugger/index.js +7 -7
  113. package/packages/dd-trace/src/dogstatsd.js +2 -2
  114. package/packages/dd-trace/src/encode/0.4.js +45 -54
  115. package/packages/dd-trace/src/encode/0.5.js +34 -3
  116. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +26 -19
  117. package/packages/dd-trace/src/encode/agentless-json.js +1 -1
  118. package/packages/dd-trace/src/exporter.js +2 -0
  119. package/packages/dd-trace/src/exporters/agent/index.js +2 -1
  120. package/packages/dd-trace/src/exporters/agentless/index.js +3 -2
  121. package/packages/dd-trace/src/exporters/agentless/writer.js +2 -2
  122. package/packages/dd-trace/src/exporters/common/agents.js +3 -1
  123. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +2 -1
  124. package/packages/dd-trace/src/exporters/common/request.js +4 -2
  125. package/packages/dd-trace/src/exporters/electron/index.js +49 -0
  126. package/packages/dd-trace/src/external-logger/src/index.js +2 -1
  127. package/packages/dd-trace/src/git_metadata.js +10 -8
  128. package/packages/dd-trace/src/id.js +17 -4
  129. package/packages/dd-trace/src/lambda/handler-paths.js +52 -0
  130. package/packages/dd-trace/src/lambda/handler.js +2 -4
  131. package/packages/dd-trace/src/lambda/index.js +62 -14
  132. package/packages/dd-trace/src/lambda/runtime/patch.js +21 -46
  133. package/packages/dd-trace/src/llmobs/index.js +13 -2
  134. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +45 -15
  135. package/packages/dd-trace/src/llmobs/sdk.js +10 -0
  136. package/packages/dd-trace/src/llmobs/writers/base.js +2 -1
  137. package/packages/dd-trace/src/log/writer.js +3 -1
  138. package/packages/dd-trace/src/noop/span.js +3 -1
  139. package/packages/dd-trace/src/openfeature/writers/base.js +2 -1
  140. package/packages/dd-trace/src/openfeature/writers/exposures.js +51 -20
  141. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +3 -2
  142. package/packages/dd-trace/src/opentracing/propagation/text_map.js +20 -9
  143. package/packages/dd-trace/src/payload-tagging/config/index.js +2 -2
  144. package/packages/dd-trace/src/plugins/apollo.js +3 -1
  145. package/packages/dd-trace/src/plugins/ci_plugin.js +52 -17
  146. package/packages/dd-trace/src/plugins/database.js +54 -12
  147. package/packages/dd-trace/src/plugins/index.js +1 -0
  148. package/packages/dd-trace/src/plugins/log_plugin.js +3 -1
  149. package/packages/dd-trace/src/plugins/plugin.js +2 -4
  150. package/packages/dd-trace/src/plugins/tracing.js +5 -3
  151. package/packages/dd-trace/src/plugins/util/ci.js +8 -8
  152. package/packages/dd-trace/src/plugins/util/git-cache.js +20 -18
  153. package/packages/dd-trace/src/plugins/util/git.js +3 -1
  154. package/packages/dd-trace/src/plugins/util/stacktrace.js +2 -2
  155. package/packages/dd-trace/src/plugins/util/test.js +119 -5
  156. package/packages/dd-trace/src/plugins/util/user-provided-git.js +17 -15
  157. package/packages/dd-trace/src/plugins/util/web.js +11 -0
  158. package/packages/dd-trace/src/priority_sampler.js +1 -1
  159. package/packages/dd-trace/src/profiling/profiler.js +1 -1
  160. package/packages/dd-trace/src/profiling/profilers/wall.js +1 -1
  161. package/packages/dd-trace/src/profiling/ssi-heuristics.js +1 -1
  162. package/packages/dd-trace/src/rate_limiter.js +1 -1
  163. package/packages/dd-trace/src/remote_config/scheduler.js +1 -1
  164. package/packages/dd-trace/src/ritm.js +2 -1
  165. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +5 -8
  166. package/packages/dd-trace/src/scope.js +7 -5
  167. package/packages/dd-trace/src/serverless.js +5 -2
  168. package/packages/dd-trace/src/service-naming/extra-services.js +14 -0
  169. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +20 -0
  170. package/packages/dd-trace/src/service-naming/schemas/v0/web.js +4 -0
  171. package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +20 -0
  172. package/packages/dd-trace/src/service-naming/schemas/v1/web.js +4 -0
  173. package/packages/dd-trace/src/span_stats.js +1 -1
  174. package/packages/dd-trace/src/telemetry/dependencies.js +1 -1
  175. package/packages/dd-trace/src/telemetry/endpoints.js +1 -1
  176. package/packages/dd-trace/src/telemetry/telemetry.js +2 -2
  177. package/packages/dd-trace/src/lambda/runtime/ritm.js +0 -133
  178. package/vendor/dist/opentracing/LICENSE +0 -201
  179. package/vendor/dist/opentracing/binary_carrier.d.ts +0 -11
  180. package/vendor/dist/opentracing/constants.d.ts +0 -61
  181. package/vendor/dist/opentracing/examples/demo/demo.d.ts +0 -2
  182. package/vendor/dist/opentracing/ext/tags.d.ts +0 -90
  183. package/vendor/dist/opentracing/functions.d.ts +0 -20
  184. package/vendor/dist/opentracing/global_tracer.d.ts +0 -14
  185. package/vendor/dist/opentracing/index.d.ts +0 -12
  186. package/vendor/dist/opentracing/index.js +0 -1
  187. package/vendor/dist/opentracing/mock_tracer/index.d.ts +0 -5
  188. package/vendor/dist/opentracing/mock_tracer/mock_context.d.ts +0 -13
  189. package/vendor/dist/opentracing/mock_tracer/mock_report.d.ts +0 -16
  190. package/vendor/dist/opentracing/mock_tracer/mock_span.d.ts +0 -50
  191. package/vendor/dist/opentracing/mock_tracer/mock_tracer.d.ts +0 -26
  192. package/vendor/dist/opentracing/noop.d.ts +0 -8
  193. package/vendor/dist/opentracing/reference.d.ts +0 -33
  194. package/vendor/dist/opentracing/span.d.ts +0 -147
  195. package/vendor/dist/opentracing/span_context.d.ts +0 -26
  196. package/vendor/dist/opentracing/test/api_compatibility.d.ts +0 -16
  197. package/vendor/dist/opentracing/test/mocktracer_implemenation.d.ts +0 -3
  198. package/vendor/dist/opentracing/test/noop_implementation.d.ts +0 -4
  199. package/vendor/dist/opentracing/test/opentracing_api.d.ts +0 -3
  200. package/vendor/dist/opentracing/test/unittest.d.ts +0 -2
  201. package/vendor/dist/opentracing/tracer.d.ts +0 -127
@@ -91,6 +91,38 @@ class KafkajsProducerPlugin extends ProducerPlugin {
91
91
  }
92
92
  }
93
93
 
94
+ finish (ctx) {
95
+ const span = ctx?.currentStore?.span
96
+ const result = ctx?.result
97
+ if (span && Array.isArray(result) && result.length > 0) {
98
+ // The broker response is one entry per (topic, partition). Each entry
99
+ // carries a `baseOffset` — the offset assigned to the first record sent
100
+ // to that partition. We don't know per-partition record counts from the
101
+ // response, only the starting offset.
102
+ const offsets = []
103
+ for (const entry of result) {
104
+ const offsetAsLong = entry.offset ?? entry.baseOffset
105
+ if (entry.partition === undefined || offsetAsLong === undefined) continue
106
+ // Kafka offsets are 64-bit; coercing to Number loses precision past
107
+ // 2^53. Keep them as strings so the tag matches the exact offset on
108
+ // long-lived/high-throughput topics.
109
+ offsets.push({ partition: entry.partition, start_offset: String(offsetAsLong) })
110
+ }
111
+ if (offsets.length > 0) {
112
+ offsets.sort((a, b) => a.partition - b.partition)
113
+ span.setTag('kafka.messages.offsets', JSON.stringify(offsets))
114
+ }
115
+ // Single-message send: the one entry's partition/offset describes the
116
+ // exact record. Also expose them as flat tags for easy filtering.
117
+ if (offsets.length === 1 && ctx.messages?.length === 1) {
118
+ span.setTag('kafka.partition', offsets[0].partition)
119
+ // Set as a string meta tag (not a metric) to preserve full 64-bit precision.
120
+ span.setTag('kafka.message.offset', offsets[0].start_offset)
121
+ }
122
+ }
123
+ super.finish(ctx)
124
+ }
125
+
94
126
  bindStart (ctx) {
95
127
  const { topic, messages, bootstrapServers, clusterId, disableHeaderInjection } = ctx
96
128
  const span = this.startSpan({
@@ -105,6 +105,7 @@ class MochaPlugin extends CiPlugin {
105
105
  'mocha'
106
106
  ),
107
107
  ...this.getSessionRequestErrorTags(),
108
+ ...this.getSessionItrSkippingEnabledTags(),
108
109
  }
109
110
  if (isUnskippable) {
110
111
  testSuiteMetadata[TEST_ITR_UNSKIPPABLE] = 'true'
@@ -20,6 +20,9 @@ class MongodbCorePlugin extends DatabasePlugin {
20
20
 
21
21
  this.config.heartbeatEnabled = config.heartbeatEnabled ??
22
22
  this._tracerConfig.DD_TRACE_MONGODB_HEARTBEAT_ENABLED
23
+ this.config.obfuscateQuery = normaliseObfuscateQuery(
24
+ config.obfuscateQuery ?? this._tracerConfig.DD_TRACE_MONGODB_OBFUSCATE_QUERY
25
+ )
23
26
  }
24
27
 
25
28
  bindStart (ctx) {
@@ -28,7 +31,7 @@ class MongodbCorePlugin extends DatabasePlugin {
28
31
  if (!this.config.heartbeatEnabled && isHeartbeat(ops, this.config)) {
29
32
  return
30
33
  }
31
- const query = getQuery(ops)
34
+ const query = getQuery(ops, this.config.obfuscateQuery)
32
35
  const resource = truncate(getResource(this, ns, query, name))
33
36
  const serviceResult = this.serviceName({ pluginConfig: this.config })
34
37
  const span = this.startSpan(this.operationName(), {
@@ -90,9 +93,8 @@ class MongodbCorePlugin extends DatabasePlugin {
90
93
  }
91
94
  }
92
95
 
93
- function sanitizeBigInt (data) {
94
- return JSON.stringify(data, (_key, value) => typeof value === 'bigint' ? value.toString() : value)
95
- }
96
+ const MAX_DEPTH = 10
97
+ const MAX_QUERY_LENGTH = 10_000
96
98
 
97
99
  function extractQuery (statements) {
98
100
  if (statements.length === 1 && statements[0].q) return statements[0].q
@@ -100,22 +102,26 @@ function extractQuery (statements) {
100
102
  const extractedQueries = []
101
103
  for (let i = 0; i < statements.length; i++) {
102
104
  if (statements[i].q) {
103
- extractedQueries.push(limitDepth(statements[i].q))
105
+ extractedQueries.push(statements[i].q)
104
106
  }
105
107
  }
106
108
 
107
109
  return extractedQueries
108
110
  }
109
111
 
110
- function getQuery (cmd) {
112
+ /**
113
+ * @param {Record<string, unknown> | unknown[] | undefined} cmd
114
+ * @param {'none' | 'types' | 'redact'} mode
115
+ */
116
+ function getQuery (cmd, mode) {
111
117
  if (!cmd || (typeof cmd !== 'object' && !Array.isArray(cmd))) return
112
118
 
113
- if (Array.isArray(cmd)) return sanitizeBigInt(extractQuery(cmd))
114
- if (cmd.query) return sanitizeBigInt(limitDepth(cmd.query))
115
- if (cmd.filter) return sanitizeBigInt(limitDepth(cmd.filter))
116
- if (cmd.pipeline) return sanitizeBigInt(limitDepth(cmd.pipeline))
117
- if (cmd.deletes) return sanitizeBigInt(extractQuery(cmd.deletes))
118
- if (cmd.updates) return sanitizeBigInt(extractQuery(cmd.updates))
119
+ if (Array.isArray(cmd)) return sanitiseAndStringify(extractQuery(cmd), mode)
120
+ if (cmd.query) return sanitiseAndStringify(cmd.query, mode)
121
+ if (cmd.filter) return sanitiseAndStringify(cmd.filter, mode)
122
+ if (cmd.pipeline) return sanitiseAndStringify(cmd.pipeline, mode)
123
+ if (cmd.deletes) return sanitiseAndStringify(extractQuery(cmd.deletes), mode)
124
+ if (cmd.updates) return sanitiseAndStringify(extractQuery(cmd.updates), mode)
119
125
  }
120
126
 
121
127
  function getResource (plugin, ns, query, operationName) {
@@ -129,73 +135,68 @@ function getResource (plugin, ns, query, operationName) {
129
135
  }
130
136
 
131
137
  function truncate (input) {
132
- return input.slice(0, Math.min(input.length, 10_000))
133
- }
134
-
135
- function shouldSimplify (input) {
136
- return !isObject(input) || typeof input.toJSON === 'function'
138
+ return input.length > MAX_QUERY_LENGTH ? input.slice(0, MAX_QUERY_LENGTH) : input
137
139
  }
138
140
 
139
- function shouldHide (input) {
140
- return Buffer.isBuffer(input) || typeof input === 'function' || isBinary(input)
141
- }
142
-
143
- function limitDepth (input) {
144
- if (isBSON(input)) {
145
- input = input.toJSON()
146
- }
147
-
148
- if (shouldHide(input)) return '?'
149
- if (shouldSimplify(input)) return input
150
-
151
- const output = {}
152
- const queue = [{
153
- input,
154
- output,
155
- depth: 0,
156
- }]
157
-
158
- while (queue.length) {
159
- const {
160
- input, output, depth,
161
- } = queue.pop()
162
- const nextDepth = depth + 1
163
- for (const key of Object.keys(input)) {
164
- let child = input[key]
165
- if (typeof child === 'function') continue
166
-
167
- if (isBSON(child)) {
168
- child = typeof child.toJSON === 'function' ? child.toJSON() : '?'
169
- }
141
+ // Single-pass sanitisation. The replacer:
142
+ // - skips functions and coerces bigint to its decimal string,
143
+ // - collapses Buffer / BSON Binary / BSON types without toJSON (MinKey, MaxKey) to a sentinel,
144
+ // - lets JSON.stringify call toJSON on other BSON types (ObjectId, Long, Decimal128, Date, Timestamp, ...)
145
+ // so the result lands here as a primitive or plain object,
146
+ // - tracks depth via an ancestor stack so cycles and depth >= MAX_DEPTH collapse to the sentinel,
147
+ // - in `redact` mode, replaces every primitive leaf (including null) with '?',
148
+ // - in `types` mode, replaces every primitive leaf with the typeof of the *original* value (so a
149
+ // BSON Date that flattens to a string still reports as 'object'), and 'null' for null.
150
+ // Keys, operator names, and array / pipeline shape are preserved in both modes so the resulting
151
+ // JSON is still a usable query signature.
152
+ /**
153
+ * @param {Record<string, unknown> | unknown[]} input
154
+ * @param {'none' | 'types' | 'redact'} mode
155
+ */
156
+ function sanitiseAndStringify (input, mode) {
157
+ const ancestors = []
158
+ return JSON.stringify(input, function (key, value) {
159
+ if (typeof value === 'function') return
160
+ if (typeof value === 'bigint') {
161
+ if (mode === 'redact') return '?'
162
+ if (mode === 'types') return 'bigint'
163
+ return value.toString()
164
+ }
170
165
 
171
- if (depth >= 10 || shouldHide(child)) {
172
- output[key] = '?'
173
- } else if (shouldSimplify(child)) {
174
- output[key] = child
175
- } else {
176
- output[key] = {}
177
- queue.push({
178
- input: child,
179
- output: output[key],
180
- depth: nextDepth,
181
- })
166
+ const original = key === '' ? value : this[key]
167
+ if (typeof original === 'object' && original !== null) {
168
+ const bsontype = original._bsontype
169
+ if (Buffer.isBuffer(original) || (bsontype !== undefined && (bsontype === 'Binary' || value === original))) {
170
+ return mode === 'types' ? 'object' : '?'
182
171
  }
183
172
  }
184
- }
185
173
 
186
- return output
187
- }
174
+ if (value === null || typeof value !== 'object') {
175
+ if (key === '' || mode === 'none') return value
176
+ if (mode === 'redact') return '?'
177
+ return original === null ? 'null' : typeof original
178
+ }
188
179
 
189
- function isObject (val) {
190
- return val !== null && typeof val === 'object' && !Array.isArray(val)
191
- }
180
+ while (ancestors.length > 0 && ancestors.at(-1) !== this) ancestors.pop()
181
+ if (ancestors.length >= MAX_DEPTH || ancestors.includes(value)) {
182
+ return mode === 'types' ? 'object' : '?'
183
+ }
184
+ ancestors.push(value)
192
185
 
193
- function isBSON (val) {
194
- return val && val._bsontype && !isBinary(val)
186
+ return value
187
+ })
195
188
  }
196
189
 
197
- function isBinary (val) {
198
- return val && val._bsontype === 'Binary'
190
+ /**
191
+ * Coerce the plugin-config and env values for `obfuscateQuery` to one of the three canonical modes.
192
+ * Anything outside the enum — including `undefined` — falls back to `'none'`.
193
+ *
194
+ * @param {unknown} value
195
+ * @returns {'none' | 'types' | 'redact'}
196
+ */
197
+ function normaliseObfuscateQuery (value) {
198
+ if (value === 'types' || value === 'redact') return value
199
+ return 'none'
199
200
  }
200
201
 
201
202
  function isHeartbeat (ops, config) {
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { storage } = require('../../datadog-core')
4
- const CLIENT_PORT_KEY = require('../../dd-trace/src/constants')
4
+ const { CLIENT_PORT_KEY } = require('../../dd-trace/src/constants')
5
5
  const DatabasePlugin = require('../../dd-trace/src/plugins/database')
6
6
 
7
7
  class MySQLPlugin extends DatabasePlugin {
@@ -36,7 +36,8 @@ module.exports.init = function (tracerConfig) {
36
36
 
37
37
  interval = setInterval(() => {
38
38
  metrics.flush()
39
- }, FLUSH_INTERVAL).unref()
39
+ }, FLUSH_INTERVAL)
40
+ interval.unref?.()
40
41
 
41
42
  return { metrics, logger }
42
43
  }
@@ -9,9 +9,9 @@ class PGPlugin extends DatabasePlugin {
9
9
  static system = 'postgres'
10
10
 
11
11
  bindStart (ctx) {
12
- const { params = {}, query, processId, stream } = ctx
12
+ const { params = {}, query, originalText, processId, stream } = ctx
13
13
  const service = this.serviceName({ pluginConfig: this.config, params })
14
- const originalStatement = this.maybeTruncate(query.text)
14
+ const originalStatement = this.maybeTruncate(originalText)
15
15
 
16
16
  const span = this.startSpan(this.operationName(), {
17
17
  service,
@@ -32,7 +32,7 @@ class PGPlugin extends DatabasePlugin {
32
32
  span.setTag('db.stream', 1)
33
33
  }
34
34
 
35
- query.__ddInjectableQuery = this.injectDbmQuery(span, query.text, service.name, !!query.name)
35
+ ctx.injected = this.injectDbmQuery(span, originalText, service.name, !!query.name)
36
36
 
37
37
  return ctx.currentStore
38
38
  }
@@ -321,6 +321,7 @@ class PlaywrightPlugin extends CiPlugin {
321
321
  isAtrRetry,
322
322
  isModified,
323
323
  finalStatus,
324
+ earlyFlakeAbortReason,
324
325
  onDone,
325
326
  }) => {
326
327
  if (!span) return
@@ -384,6 +385,9 @@ class PlaywrightPlugin extends CiPlugin {
384
385
  if (finalStatus) {
385
386
  span.setTag(TEST_FINAL_STATUS, finalStatus)
386
387
  }
388
+ if (earlyFlakeAbortReason) {
389
+ span.setTag(TEST_EARLY_FLAKE_ABORT_REASON, earlyFlakeAbortReason)
390
+ }
387
391
  for (const step of steps) {
388
392
  const stepStartTime = step.startTime.getTime()
389
393
  const stepSpan = this.tracer.startSpan('playwright.step', {
@@ -4,17 +4,31 @@ const { CLIENT_PORT_KEY } = require('../../dd-trace/src/constants')
4
4
  const CachePlugin = require('../../dd-trace/src/plugins/cache')
5
5
  const urlFilter = require('../../dd-trace/src/plugins/util/urlfilter')
6
6
 
7
+ const MAX_ARG_LENGTH = 100
8
+ const MAX_COMMAND_LENGTH = 1000
9
+
7
10
  class RedisPlugin extends CachePlugin {
8
11
  static id = 'redis'
9
12
  static system = 'redis'
10
13
 
14
+ /** @type {string} */
15
+ #rawCommandKey
16
+ /** @type {Map<string | undefined, { name: string, source: string | undefined }>} */
17
+ #serviceByConnection = new Map()
18
+ // `nomenclature.config` identity at last cache fill. `withNamingSchema` swaps it without
19
+ // running this plugin's `configure`, so identity drives invalidation in `bindStart`.
20
+ /** @type {object | undefined} */
21
+ #lastNomenclatureConfig
22
+ /** @type {string | undefined} */
23
+ #cachedOperationName
24
+
11
25
  constructor (...args) {
12
26
  super(...args)
13
27
  this._spanType = 'redis'
14
28
  }
15
29
 
16
30
  bindStart (ctx) {
17
- const { db, command, args, connectionOptions, connectionName } = ctx
31
+ const { db, command, args, argsStartIndex, connectionOptions, connectionName } = ctx
18
32
 
19
33
  const resource = command
20
34
  const normalizedCommand = command.toUpperCase()
@@ -22,14 +36,27 @@ class RedisPlugin extends CachePlugin {
22
36
  return { noop: true }
23
37
  }
24
38
 
39
+ const nomConfig = this._tracer._nomenclature.config
40
+ if (this.#lastNomenclatureConfig !== nomConfig) {
41
+ this.#lastNomenclatureConfig = nomConfig
42
+ this.#cachedOperationName = undefined
43
+ this.#serviceByConnection.clear()
44
+ }
45
+
46
+ let service = this.#serviceByConnection.get(connectionName)
47
+ if (service === undefined) {
48
+ service = this.serviceName({ pluginConfig: this.config, system: this.system, connectionName })
49
+ this.#serviceByConnection.set(connectionName, service)
50
+ }
51
+
25
52
  this.startSpan({
26
53
  resource,
27
- service: this.serviceName({ pluginConfig: this.config, system: this.system, connectionName }),
54
+ service,
28
55
  type: this._spanType,
29
56
  meta: {
30
57
  'db.type': this._spanType,
31
58
  'db.name': db || '0',
32
- [`${this._spanType}.raw_command`]: formatCommand(normalizedCommand, args),
59
+ [this.#rawCommandKey]: formatCommand(normalizedCommand, args, argsStartIndex),
33
60
  'out.host': connectionOptions.host,
34
61
  [CLIENT_PORT_KEY]: connectionOptions.port,
35
62
  },
@@ -38,41 +65,44 @@ class RedisPlugin extends CachePlugin {
38
65
  return ctx.currentStore
39
66
  }
40
67
 
68
+ operationName () {
69
+ this.#cachedOperationName ??= super.operationName()
70
+ return this.#cachedOperationName
71
+ }
72
+
41
73
  configure (config) {
42
74
  super.configure(normalizeConfig(config))
75
+ // Subclasses (iovalkey) overwrite `_spanType` in their constructor, before any `configure`,
76
+ // so reading it here is stable.
77
+ this.#rawCommandKey = `${this._spanType}.raw_command`
78
+ this.#lastNomenclatureConfig = undefined
79
+ this.#cachedOperationName = undefined
80
+ this.#serviceByConnection.clear()
43
81
  }
44
82
  }
45
83
 
46
- function formatCommand (command, args) {
84
+ function formatCommand (command, args, argsStartIndex = 0) {
47
85
  if (!args || command === 'AUTH') return command
48
86
 
49
- for (let i = 0, l = args.length; i < l; i++) {
50
- if (typeof args[i] === 'function') continue
51
-
52
- command = `${command} ${formatArg(args[i])}`
87
+ let result = command
88
+ for (let i = argsStartIndex, l = args.length; i < l; i++) {
89
+ const arg = args[i]
90
+ if (typeof arg === 'function') continue
53
91
 
54
- if (command.length > 1000) return trim(command, 1000)
92
+ result = `${result} ${formatArg(arg)}`
93
+ if (result.length > MAX_COMMAND_LENGTH) return result.slice(0, MAX_COMMAND_LENGTH - 3) + '...'
55
94
  }
56
95
 
57
- return command
96
+ return result
58
97
  }
59
98
 
60
99
  function formatArg (arg) {
61
- switch (typeof arg) {
62
- case 'string':
63
- case 'number':
64
- return trim(String(arg), 100)
65
- default:
66
- return '?'
67
- }
68
- }
69
-
70
- function trim (str, maxlen) {
71
- if (str.length > maxlen) {
72
- str = str.slice(0, maxlen - 3) + '...'
100
+ if (typeof arg === 'string') {
101
+ return arg.length > MAX_ARG_LENGTH ? arg.slice(0, MAX_ARG_LENGTH - 3) + '...' : arg
73
102
  }
74
-
75
- return str
103
+ // Number stringification is bounded (~23 chars max), so it never hits MAX_ARG_LENGTH.
104
+ if (typeof arg === 'number') return String(arg)
105
+ return '?'
76
106
  }
77
107
 
78
108
  function normalizeConfig (config) {
@@ -27,6 +27,7 @@ class UndiciPlugin extends HttpClientPlugin {
27
27
  // Subscribe to native undici diagnostic channels for undici >= 4.7.0
28
28
  // These channels fire for ALL undici requests (fetch, request, stream, etc.)
29
29
  this.addSub('undici:request:create', this.#onNativeRequestCreate.bind(this))
30
+ this.addSub('undici:request:bodySent', this.#onNativeRequestBodySent.bind(this))
30
31
  this.addSub('undici:request:headers', this.#onNativeRequestHeaders.bind(this))
31
32
  this.addSub('undici:request:trailers', this.#onNativeRequestTrailers.bind(this))
32
33
  this.addSub('undici:request:error', this.#onNativeRequestError.bind(this))
@@ -109,12 +110,30 @@ class UndiciPlugin extends HttpClientPlugin {
109
110
  span,
110
111
  store,
111
112
  uri,
113
+ method,
112
114
  })
113
115
 
114
116
  // Enter the span context
115
117
  storage('legacy').enterWith({ ...store, span })
116
118
  }
117
119
 
120
+ #onNativeRequestBodySent ({ request }) {
121
+ const ctx = requestContexts.get(request)
122
+ if (!ctx || ctx.method !== 'CONNECT') return
123
+
124
+ const { span, store } = ctx
125
+
126
+ this.config.hooks.request(span, null, null)
127
+
128
+ span.finish()
129
+
130
+ requestContexts.delete(request)
131
+
132
+ if (store) {
133
+ storage('legacy').enterWith(store)
134
+ }
135
+ }
136
+
118
137
  #onNativeRequestHeaders ({ request, response }) {
119
138
  const ctx = requestContexts.get(request)
120
139
  if (!ctx) return
@@ -56,6 +56,16 @@ class VitestPlugin extends CiPlugin {
56
56
 
57
57
  this.taskToFinishTime = new WeakMap()
58
58
 
59
+ this.addSub('ci:vitest:session:configuration', ({ onDone }) => {
60
+ const testSessionSpanContext = this.testSessionSpan?.context()
61
+ const testModuleSpanContext = this.testModuleSpan?.context()
62
+ onDone({
63
+ testSessionId: testSessionSpanContext?.toTraceId(),
64
+ testModuleId: testModuleSpanContext?.toSpanId(),
65
+ testCommand: this.command,
66
+ })
67
+ })
68
+
59
69
  this.addSub('ci:vitest:test:is-new', ({ knownTests, testSuiteAbsolutePath, testName, onDone }) => {
60
70
  // if for whatever reason the worker does not receive valid known tests, we don't consider it as new
61
71
  if (!knownTests.vitest) {
@@ -299,14 +309,16 @@ class VitestPlugin extends CiPlugin {
299
309
  this.addBind('ci:vitest:test-suite:start', (ctx) => {
300
310
  const { testSuiteAbsolutePath, frameworkVersion } = ctx
301
311
 
302
- // TODO: Handle case where the command is not set
303
- this.command = this._tracerConfig.DD_CIVISIBILITY_TEST_COMMAND
312
+ const testCommand = ctx.testCommand || 'vitest run'
313
+ const { testSessionId, testModuleId } = ctx
314
+ this.command = testCommand
304
315
  this.frameworkVersion = frameworkVersion
305
- const testSessionSpanContext = this.tracer.extract('text_map', {
306
- // TODO: Handle case where the session ID or module ID is not set
307
- 'x-datadog-trace-id': this._tracerConfig.DD_CIVISIBILITY_TEST_SESSION_ID,
308
- 'x-datadog-parent-id': this._tracerConfig.DD_CIVISIBILITY_TEST_MODULE_ID,
309
- })
316
+ const testSessionSpanContext = testSessionId && testModuleId
317
+ ? this.tracer.extract('text_map', {
318
+ 'x-datadog-trace-id': testSessionId,
319
+ 'x-datadog-parent-id': testModuleId,
320
+ })
321
+ : undefined
310
322
 
311
323
  const trimmedCommand = DD_MAJOR < 6 ? this.command : 'vitest run'
312
324
  // test suites run in a different process, so they also need to init the metadata dictionary
@@ -82,6 +82,40 @@ function wrapFunction (original, wrapper) {
82
82
  return wrapped
83
83
  }
84
84
 
85
+ /**
86
+ * Lean variant of `wrapFunction` for the case where the wrapped value is a
87
+ * user-supplied callback that the user cannot reasonably introspect beyond
88
+ * `name` and `length`, and the wrapper closure is fully controlled by us.
89
+ *
90
+ * Compared to `wrapFunction`, this skips the prototype copy, the
91
+ * `assertNotClass` guard, and the `Reflect.ownKeys` descriptor-copy loop.
92
+ * Only `name` and `length` are preserved, and only when the wrapper's
93
+ * autogenerated values differ -- a wrapper whose closure already has the
94
+ * right arity / name pays no overhead.
95
+ *
96
+ * Use `wrapFunction` instead when any of the following is true: the wrapped
97
+ * function needs to keep its prototype, has custom own properties the caller
98
+ * may read, or is `new`-ed.
99
+ *
100
+ * @param {Function} original - User-supplied callback being wrapped.
101
+ * @param {(original: Function) => Function} wrapper - Factory that receives
102
+ * `original` and returns the wrapper closure.
103
+ * @returns {Function} The wrapper closure with `name` and `length` preserved.
104
+ */
105
+ function wrapCallback (original, wrapper) {
106
+ if (typeof original !== 'function') {
107
+ return original
108
+ }
109
+ const wrapped = wrapper(original)
110
+ if (wrapped.name !== original.name) {
111
+ Object.defineProperty(wrapped, 'name', { value: original.name, configurable: true })
112
+ }
113
+ if (wrapped.length !== original.length) {
114
+ Object.defineProperty(wrapped, 'length', { value: original.length, configurable: true })
115
+ }
116
+ return wrapped
117
+ }
118
+
85
119
  /**
86
120
  * Wraps a method of an object with a wrapper function.
87
121
  *
@@ -280,6 +314,7 @@ function assertNotClass (target) {
280
314
 
281
315
  module.exports = {
282
316
  wrap,
317
+ wrapCallback,
283
318
  wrapFunction,
284
319
  massWrap,
285
320
  }
@@ -3,6 +3,7 @@
3
3
  const log = require('../log')
4
4
  const { incomingHttpRequestStart, aiguardChannel } = require('./channels')
5
5
  const AIGuard = require('./sdk')
6
+ const { SOURCE_AUTO, INTEGRATION_NONE } = require('./tags')
6
7
 
7
8
  let isEnabled = false
8
9
  let aiguard
@@ -51,7 +52,8 @@ function onEvaluate (ctx) {
51
52
  return
52
53
  }
53
54
 
54
- aiguard.evaluate(ctx.messages, { block })
55
+ const opts = { block, source: SOURCE_AUTO, integration: ctx.integration || INTEGRATION_NONE }
56
+ aiguard.evaluate(ctx.messages, opts)
55
57
  .then(() => {
56
58
  ctx.resolve()
57
59
  })