dd-trace 5.104.0 → 5.106.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 (159) hide show
  1. package/LICENSE-3rdparty.csv +90 -102
  2. package/index.d.ts +82 -3
  3. package/package.json +15 -15
  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 +16 -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/cucumber-worker-threads.js +19 -0
  11. package/packages/datadog-instrumentations/src/cucumber.js +390 -157
  12. package/packages/datadog-instrumentations/src/dns.js +54 -18
  13. package/packages/datadog-instrumentations/src/fastify.js +142 -82
  14. package/packages/datadog-instrumentations/src/graphql.js +188 -62
  15. package/packages/datadog-instrumentations/src/helpers/ai-messages.js +322 -14
  16. package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
  17. package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -1
  18. package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +269 -0
  19. package/packages/datadog-instrumentations/src/helpers/promise-instrumentor.js +42 -0
  20. package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
  21. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -3
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/azure-cosmos.js +50 -0
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/langgraph.js +4 -2
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +85 -0
  26. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +37 -236
  27. package/packages/datadog-instrumentations/src/hono.js +54 -3
  28. package/packages/datadog-instrumentations/src/http/server.js +9 -4
  29. package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +163 -0
  30. package/packages/datadog-instrumentations/src/jest.js +360 -150
  31. package/packages/datadog-instrumentations/src/kafkajs.js +120 -16
  32. package/packages/datadog-instrumentations/src/mocha/main.js +128 -17
  33. package/packages/datadog-instrumentations/src/nats.js +182 -0
  34. package/packages/datadog-instrumentations/src/nyc.js +38 -1
  35. package/packages/datadog-instrumentations/src/openai.js +33 -18
  36. package/packages/datadog-instrumentations/src/oracledb.js +6 -1
  37. package/packages/datadog-instrumentations/src/pino.js +17 -5
  38. package/packages/datadog-instrumentations/src/playwright.js +515 -292
  39. package/packages/datadog-instrumentations/src/router.js +76 -32
  40. package/packages/datadog-instrumentations/src/stripe.js +1 -1
  41. package/packages/datadog-plugin-avsc/src/schema_iterator.js +1 -1
  42. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/tracing.js +1 -1
  43. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +218 -4
  44. package/packages/datadog-plugin-azure-cosmos/src/index.js +144 -0
  45. package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -1
  46. package/packages/datadog-plugin-azure-functions/src/index.js +5 -2
  47. package/packages/datadog-plugin-azure-service-bus/src/producer.js +1 -1
  48. package/packages/datadog-plugin-bunyan/src/index.js +28 -0
  49. package/packages/datadog-plugin-cucumber/src/index.js +17 -3
  50. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +199 -28
  51. package/packages/datadog-plugin-cypress/src/support.js +69 -1
  52. package/packages/datadog-plugin-dns/src/lookup.js +8 -6
  53. package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +1 -1
  54. package/packages/datadog-plugin-graphql/src/execute.js +2 -0
  55. package/packages/datadog-plugin-graphql/src/resolve.js +64 -67
  56. package/packages/datadog-plugin-http/src/server.js +40 -15
  57. package/packages/datadog-plugin-jest/src/index.js +11 -3
  58. package/packages/datadog-plugin-jest/src/util.js +15 -8
  59. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +1 -1
  60. package/packages/datadog-plugin-kafkajs/src/producer.js +3 -0
  61. package/packages/datadog-plugin-langgraph/src/stream.js +1 -1
  62. package/packages/datadog-plugin-mocha/src/index.js +19 -4
  63. package/packages/datadog-plugin-mongodb-core/src/index.js +281 -40
  64. package/packages/datadog-plugin-nats/src/consumer.js +43 -0
  65. package/packages/datadog-plugin-nats/src/index.js +20 -0
  66. package/packages/datadog-plugin-nats/src/producer.js +62 -0
  67. package/packages/datadog-plugin-nats/src/util.js +33 -0
  68. package/packages/datadog-plugin-next/src/index.js +5 -3
  69. package/packages/datadog-plugin-openai/src/tracing.js +15 -2
  70. package/packages/datadog-plugin-oracledb/src/index.js +13 -2
  71. package/packages/datadog-plugin-pino/src/index.js +42 -0
  72. package/packages/datadog-plugin-playwright/src/index.js +4 -4
  73. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +1 -1
  74. package/packages/datadog-plugin-rhea/src/producer.js +1 -1
  75. package/packages/datadog-plugin-router/src/index.js +33 -44
  76. package/packages/datadog-plugin-selenium/src/index.js +1 -1
  77. package/packages/datadog-plugin-vitest/src/index.js +5 -13
  78. package/packages/datadog-plugin-winston/src/index.js +30 -0
  79. package/packages/datadog-shimmer/src/shimmer.js +33 -40
  80. package/packages/dd-trace/src/aiguard/index.js +1 -1
  81. package/packages/dd-trace/src/aiguard/sdk.js +1 -1
  82. package/packages/dd-trace/src/appsec/api_security_sampler.js +1 -1
  83. package/packages/dd-trace/src/appsec/index.js +1 -1
  84. package/packages/dd-trace/src/appsec/reporter.js +5 -6
  85. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
  86. package/packages/dd-trace/src/appsec/sdk/utils.js +1 -1
  87. package/packages/dd-trace/src/appsec/user_tracking.js +5 -4
  88. package/packages/dd-trace/src/baggage.js +7 -1
  89. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +0 -1
  90. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +25 -13
  91. package/packages/dd-trace/src/ci-visibility/test-optimization-cache.js +70 -6
  92. package/packages/dd-trace/src/config/generated-config-types.d.ts +6 -2
  93. package/packages/dd-trace/src/config/supported-configurations.json +27 -8
  94. package/packages/dd-trace/src/datastreams/writer.js +2 -4
  95. package/packages/dd-trace/src/debugger/devtools_client/condition.js +5 -8
  96. package/packages/dd-trace/src/encode/0.4.js +124 -108
  97. package/packages/dd-trace/src/encode/0.5.js +114 -26
  98. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +31 -23
  99. package/packages/dd-trace/src/encode/agentless-json.js +4 -2
  100. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +32 -13
  101. package/packages/dd-trace/src/encode/span-stats.js +16 -16
  102. package/packages/dd-trace/src/encode/tags-processors.js +16 -0
  103. package/packages/dd-trace/src/id.js +15 -0
  104. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +92 -6
  105. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +43 -21
  106. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
  107. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
  108. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +9 -7
  109. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  110. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +1 -1
  111. package/packages/dd-trace/src/llmobs/sdk.js +0 -16
  112. package/packages/dd-trace/src/llmobs/span_processor.js +3 -3
  113. package/packages/dd-trace/src/llmobs/tagger.js +9 -1
  114. package/packages/dd-trace/src/llmobs/telemetry.js +1 -1
  115. package/packages/dd-trace/src/llmobs/util.js +66 -3
  116. package/packages/dd-trace/src/log/index.js +1 -1
  117. package/packages/dd-trace/src/msgpack/chunk.js +394 -10
  118. package/packages/dd-trace/src/msgpack/index.js +96 -2
  119. package/packages/dd-trace/src/openfeature/encoding.js +70 -0
  120. package/packages/dd-trace/src/openfeature/flagging_provider.js +20 -0
  121. package/packages/dd-trace/src/openfeature/span-enrichment-hook.js +143 -0
  122. package/packages/dd-trace/src/openfeature/span-enrichment.js +149 -0
  123. package/packages/dd-trace/src/opentelemetry/span-helpers.js +4 -3
  124. package/packages/dd-trace/src/opentelemetry/span.js +1 -1
  125. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +22 -3
  126. package/packages/dd-trace/src/opentracing/propagation/log.js +18 -7
  127. package/packages/dd-trace/src/opentracing/propagation/text_map.js +64 -77
  128. package/packages/dd-trace/src/opentracing/span.js +59 -19
  129. package/packages/dd-trace/src/opentracing/span_context.js +50 -3
  130. package/packages/dd-trace/src/plugins/ci_plugin.js +20 -20
  131. package/packages/dd-trace/src/plugins/database.js +7 -6
  132. package/packages/dd-trace/src/plugins/index.js +4 -0
  133. package/packages/dd-trace/src/plugins/log_injection.js +56 -0
  134. package/packages/dd-trace/src/plugins/log_plugin.js +3 -48
  135. package/packages/dd-trace/src/plugins/outbound.js +1 -1
  136. package/packages/dd-trace/src/plugins/plugin.js +15 -17
  137. package/packages/dd-trace/src/plugins/tracing.js +43 -5
  138. package/packages/dd-trace/src/plugins/util/test.js +236 -13
  139. package/packages/dd-trace/src/plugins/util/web.js +79 -65
  140. package/packages/dd-trace/src/priority_sampler.js +2 -2
  141. package/packages/dd-trace/src/profiling/config.js +10 -23
  142. package/packages/dd-trace/src/profiling/exporters/agent.js +11 -10
  143. package/packages/dd-trace/src/profiling/profiler.js +21 -11
  144. package/packages/dd-trace/src/profiling/profilers/wall.js +12 -7
  145. package/packages/dd-trace/src/sampling_rule.js +7 -7
  146. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +10 -0
  147. package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +8 -0
  148. package/packages/dd-trace/src/service-naming/source-resolver.js +46 -0
  149. package/packages/dd-trace/src/span_format.js +190 -58
  150. package/packages/dd-trace/src/spanleak.js +1 -1
  151. package/packages/dd-trace/src/standalone/index.js +3 -3
  152. package/packages/dd-trace/src/tagger.js +0 -2
  153. package/vendor/dist/@apm-js-collab/code-transformer/index.js +70 -39
  154. package/vendor/dist/@datadog/sketches-js/LICENSE +10 -36
  155. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  156. package/vendor/dist/protobufjs/index.js +1 -1
  157. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  158. package/packages/dd-trace/src/msgpack/encoder.js +0 -308
  159. package/packages/dd-trace/src/plugins/structured_log_plugin.js +0 -9
@@ -1,5 +1,7 @@
1
1
  'use strict'
2
2
 
3
+ const { isMap, isRegExp } = require('node:util').types
4
+
3
5
  const DatabasePlugin = require('../../dd-trace/src/plugins/database')
4
6
 
5
7
  class MongodbCorePlugin extends DatabasePlugin {
@@ -138,62 +140,301 @@ function truncate (input) {
138
140
  return input.length > MAX_QUERY_LENGTH ? input.slice(0, MAX_QUERY_LENGTH) : input
139
141
  }
140
142
 
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.
143
+ // Depth doubles as the cycle bound: a cycle pushes past MAX_DEPTH and bails,
144
+ // after which the slow path catches it via its ancestor stack.
145
+ /** @param {unknown} input */
146
+ function canStringifyDirect (input) {
147
+ if (input === null ||
148
+ typeof input !== 'object' ||
149
+ ArrayBuffer.isView(input) ||
150
+ input._bsontype !== undefined ||
151
+ isRegExp(input) ||
152
+ isMap(input) ||
153
+ typeof input.toJSON === 'function') {
154
+ return false
155
+ }
156
+ return canStringifyDirectWalk(input, 1)
157
+ }
158
+
159
+ /**
160
+ * @param {Record<string, unknown> | unknown[]} value
161
+ * @param {number} depth
162
+ */
163
+ function canStringifyDirectWalk (value, depth) {
164
+ if (depth > MAX_DEPTH) return false
165
+ const children = Array.isArray(value) ? value : Object.values(value)
166
+ for (const child of children) {
167
+ if (child === null ||
168
+ typeof child === 'string' ||
169
+ typeof child === 'number' ||
170
+ typeof child === 'boolean') {
171
+ continue
172
+ }
173
+ if (typeof child !== 'object' ||
174
+ ArrayBuffer.isView(child) ||
175
+ child._bsontype !== undefined ||
176
+ isRegExp(child) ||
177
+ isMap(child) ||
178
+ typeof child.toJSON === 'function') {
179
+ return false
180
+ }
181
+ if (!canStringifyDirectWalk(child, depth + 1)) return false
182
+ }
183
+ return true
184
+ }
185
+
152
186
  /**
153
187
  * @param {Record<string, unknown> | unknown[]} input
154
188
  * @param {'none' | 'types' | 'redact'} mode
155
189
  */
156
190
  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
- }
191
+ if (mode === 'none') {
192
+ if (canStringifyDirect(input)) return JSON.stringify(input)
193
+ return buildNone(input, [])
194
+ }
195
+ if (mode === 'redact') return buildRedact(input, [])
196
+ return buildTypes(input, [])
197
+ }
165
198
 
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' : '?'
171
- }
199
+ const REDACT_LEAF = '"?"'
200
+
201
+ /**
202
+ * @param {RegExp} value
203
+ * @returns {string}
204
+ */
205
+ function stringifyRegExp (value) {
206
+ return `{"$regex":${JSON.stringify(value.source)},"$options":${JSON.stringify(value.flags)}}`
207
+ }
208
+
209
+ /**
210
+ * @param {Record<string, unknown> | unknown[]} value
211
+ * @param {object[]} ancestors
212
+ * @returns {string | undefined}
213
+ */
214
+ function buildNone (value, ancestors) {
215
+ // ArrayBuffer views (Buffer, every TypedArray, DataView) and Binary BSON
216
+ // wrappers redact at the leaf; the walker neither recurses into the bytes
217
+ // nor invokes any custom conversion.
218
+ const bsontype = value._bsontype
219
+ if (ArrayBuffer.isView(value) || bsontype === 'Binary' ||
220
+ ancestors.length >= MAX_DEPTH || ancestors.includes(value)) {
221
+ return REDACT_LEAF
222
+ }
223
+
224
+ if (isRegExp(value)) return stringifyRegExp(value)
225
+
226
+ // Mirror JSON.stringify's contract: when `toJSON` is present, walk its
227
+ // result (wrappers like Timestamp / Decimal128 expand to a small object,
228
+ // ObjectId / Date flatten to a primitive).
229
+ if (typeof value.toJSON === 'function') {
230
+ const json = value.toJSON()
231
+ if (json === value) return REDACT_LEAF
232
+ // JSON.stringify keeps a null result as null (an invalid Date's toJSON
233
+ // returns null); only function / symbol / undefined results drop the key.
234
+ if (json === null) return 'null'
235
+ if (typeof json !== 'object') return classifyLeafForNone(json)
236
+ // A wrapper that exposes binary state through toJSON (Buffer-backed
237
+ // class with WeakMap state, etc.) returns a TypedArray here. Re-screen
238
+ // before the per-key walk would expand it element by element.
239
+ if (ArrayBuffer.isView(json) || json._bsontype === 'Binary') return REDACT_LEAF
240
+ value = json
241
+ } else if (bsontype !== undefined) {
242
+ return REDACT_LEAF
243
+ }
244
+
245
+ // The driver serializes a Map via its entries; mirror that as a document so
246
+ // the tag matches the wire shape.
247
+ if (isMap(value)) value = Object.fromEntries(value)
248
+
249
+ ancestors.push(value)
250
+
251
+ let result
252
+ if (Array.isArray(value)) {
253
+ result = '['
254
+ let sep = ''
255
+ for (let i = 0; i < value.length; i++) {
256
+ // JSON.stringify renders unsupported leaves (function, symbol, undefined) as null in arrays.
257
+ result += sep + (classifyForNone(value[i], ancestors) ?? 'null')
258
+ sep = ','
172
259
  }
260
+ result += ']'
261
+ } else {
262
+ result = '{'
263
+ let sep = ''
264
+ for (const key of Object.keys(value)) {
265
+ const childResult = classifyForNone(value[key], ancestors)
266
+ if (childResult === undefined) continue
267
+ result += sep + JSON.stringify(key) + ':' + childResult
268
+ sep = ','
269
+ }
270
+ result += '}'
271
+ }
272
+ ancestors.pop()
273
+ return result
274
+ }
275
+
276
+ /**
277
+ * @param {unknown} child
278
+ * @param {object[]} ancestors
279
+ * @returns {string | undefined}
280
+ */
281
+ function classifyForNone (child, ancestors) {
282
+ if (typeof child !== 'object') return classifyLeafForNone(child)
283
+ if (child === null) return 'null'
284
+ return buildNone(child, ancestors)
285
+ }
286
+
287
+ /**
288
+ * @param {unknown} leaf
289
+ * @returns {string | undefined}
290
+ */
291
+ function classifyLeafForNone (leaf) {
292
+ // Implicit `undefined` for function / symbol / undefined matches the
293
+ // contract callers rely on: JSON.stringify drops those property values
294
+ // inside objects and writes `null` in arrays.
295
+ switch (typeof leaf) {
296
+ case 'string': return JSON.stringify(leaf)
297
+ case 'number': return Number.isFinite(leaf) ? String(leaf) : 'null'
298
+ case 'boolean': return leaf ? 'true' : 'false'
299
+ case 'bigint': return `"${String(leaf)}"`
300
+ }
301
+ }
302
+
303
+ /**
304
+ * @param {Record<string, unknown> | unknown[]} value
305
+ * @param {object[]} ancestors
306
+ */
307
+ function buildRedact (value, ancestors) {
308
+ const bsontype = value._bsontype
309
+ if (ArrayBuffer.isView(value) || bsontype === 'Binary' || isRegExp(value) ||
310
+ ancestors.length >= MAX_DEPTH || ancestors.includes(value)) {
311
+ return REDACT_LEAF
312
+ }
313
+
314
+ // Mirror JSON.stringify: when `toJSON` is present, walk its result (which
315
+ // wrappers like Timestamp / Decimal128 expand to `{$timestamp: "..."}` etc).
316
+ // A primitive, null, or self-reference collapses to the sentinel — master's
317
+ // `value === original` short-circuit.
318
+ if (typeof value.toJSON === 'function') {
319
+ const json = value.toJSON()
320
+ if (typeof json !== 'object' || json === null || json === value) return REDACT_LEAF
321
+ // Re-screen: toJSON can return a TypedArray or Binary BSON wrapper.
322
+ if (ArrayBuffer.isView(json) || json._bsontype === 'Binary') return REDACT_LEAF
323
+ value = json
324
+ } else if (bsontype !== undefined) {
325
+ return REDACT_LEAF
326
+ }
327
+
328
+ if (isMap(value)) value = Object.fromEntries(value)
329
+
330
+ ancestors.push(value)
173
331
 
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
332
+ let result
333
+ if (Array.isArray(value)) {
334
+ result = '['
335
+ let sep = ''
336
+ for (let i = 0; i < value.length; i++) {
337
+ result += sep + classifyForRedact(value[i], ancestors)
338
+ sep = ','
178
339
  }
340
+ result += ']'
341
+ } else {
342
+ result = '{'
343
+ let sep = ''
344
+ for (const key of Object.keys(value)) {
345
+ result += sep + JSON.stringify(key) + ':' + classifyForRedact(value[key], ancestors)
346
+ sep = ','
347
+ }
348
+ result += '}'
349
+ }
350
+ ancestors.pop()
351
+ return result
352
+ }
179
353
 
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' : '?'
354
+ /**
355
+ * @param {unknown} child
356
+ * @param {object[]} ancestors
357
+ */
358
+ function classifyForRedact (child, ancestors) {
359
+ if (typeof child !== 'object' || child === null) return REDACT_LEAF
360
+ return buildRedact(child, ancestors)
361
+ }
362
+
363
+ const TYPE_OBJECT = '"object"'
364
+ const TYPE_NULL = '"null"'
365
+ const TYPE_BY_TYPEOF = {
366
+ string: '"string"',
367
+ number: '"number"',
368
+ boolean: '"boolean"',
369
+ bigint: '"bigint"',
370
+ undefined: '"undefined"',
371
+ }
372
+
373
+ /**
374
+ * @param {Record<string, unknown> | unknown[]} value
375
+ * @param {object[]} ancestors
376
+ */
377
+ function buildTypes (value, ancestors) {
378
+ const bsontype = value._bsontype
379
+ if (ArrayBuffer.isView(value) || bsontype === 'Binary' || isRegExp(value) ||
380
+ ancestors.length >= MAX_DEPTH || ancestors.includes(value)) {
381
+ return TYPE_OBJECT
382
+ }
383
+
384
+ if (typeof value.toJSON === 'function') {
385
+ const json = value.toJSON()
386
+ if (typeof json !== 'object' ||
387
+ json === null ||
388
+ json === value ||
389
+ ArrayBuffer.isView(json) ||
390
+ json._bsontype === 'Binary') {
391
+ return TYPE_OBJECT
183
392
  }
184
- ancestors.push(value)
393
+ value = json
394
+ } else if (bsontype !== undefined) {
395
+ return TYPE_OBJECT
396
+ }
397
+
398
+ if (isMap(value)) value = Object.fromEntries(value)
185
399
 
186
- return value
187
- })
400
+ ancestors.push(value)
401
+
402
+ let result
403
+ if (Array.isArray(value)) {
404
+ result = '['
405
+ let sep = ''
406
+ for (let i = 0; i < value.length; i++) {
407
+ // JSON.stringify renders unsupported leaves (function, symbol) as null in arrays.
408
+ result += sep + (classifyForTypes(value[i], ancestors) ?? 'null')
409
+ sep = ','
410
+ }
411
+ result += ']'
412
+ } else {
413
+ result = '{'
414
+ let sep = ''
415
+ for (const key of Object.keys(value)) {
416
+ const childResult = classifyForTypes(value[key], ancestors)
417
+ if (childResult === undefined) continue
418
+ result += sep + JSON.stringify(key) + ':' + childResult
419
+ sep = ','
420
+ }
421
+ result += '}'
422
+ }
423
+ ancestors.pop()
424
+ return result
188
425
  }
189
426
 
190
427
  /**
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'}
428
+ * @param {unknown} child
429
+ * @param {object[]} ancestors
196
430
  */
431
+ function classifyForTypes (child, ancestors) {
432
+ if (typeof child !== 'object') return TYPE_BY_TYPEOF[typeof child]
433
+ if (child === null) return TYPE_NULL
434
+ return buildTypes(child, ancestors)
435
+ }
436
+
437
+ /** @param {unknown} value */
197
438
  function normaliseObfuscateQuery (value) {
198
439
  if (value === 'types' || value === 'redact') return value
199
440
  return 'none'
@@ -0,0 +1,43 @@
1
+ 'use strict'
2
+
3
+ const { TEXT_MAP } = require('../../../ext/formats')
4
+ const ConsumerPlugin = require('../../dd-trace/src/plugins/consumer')
5
+ const { headersToTextMap } = require('./util')
6
+
7
+ const MESSAGING_DESTINATION_KEY = 'messaging.destination.name'
8
+
9
+ class NatsConsumerPlugin extends ConsumerPlugin {
10
+ static id = 'nats'
11
+ static operation = 'consume'
12
+
13
+ bindStart (ctx) {
14
+ const { subject: filter, message } = ctx
15
+ // For wildcard subscriptions (e.g. `orders.*`), `filter` is the subscription
16
+ // pattern but `message.subject` is the actual delivered subject. Prefer the
17
+ // delivered one for resource/destination so spans aren't all collapsed under
18
+ // the wildcard pattern. Fall back to the filter if the message is missing it.
19
+ const subject = typeof message?.subject === 'string' ? message.subject : filter
20
+ const carrier = headersToTextMap(message?.headers)
21
+ const childOf = carrier ? this.tracer.extract(TEXT_MAP, carrier) : null
22
+
23
+ const meta = {
24
+ component: 'nats',
25
+ 'nats.subject': subject,
26
+ [MESSAGING_DESTINATION_KEY]: subject,
27
+ }
28
+ if (filter && filter !== subject) {
29
+ meta['nats.subscription.subject'] = filter
30
+ }
31
+
32
+ this.startSpan({
33
+ childOf,
34
+ resource: subject,
35
+ type: 'worker',
36
+ meta,
37
+ }, ctx)
38
+
39
+ return ctx.currentStore
40
+ }
41
+ }
42
+
43
+ module.exports = NatsConsumerPlugin
@@ -0,0 +1,20 @@
1
+ 'use strict'
2
+
3
+ const CompositePlugin = require('../../dd-trace/src/plugins/composite')
4
+ const ProducerPlugin = require('./producer')
5
+ const ConsumerPlugin = require('./consumer')
6
+
7
+ class NatsPlugin extends CompositePlugin {
8
+ static id = 'nats'
9
+ // Disabled by default — users must opt in via DD_TRACE_NATS_ENABLED=true
10
+ // or `tracer.use('nats')`. Matches the feature parity dashboard policy.
11
+ static experimental = true
12
+ static get plugins () {
13
+ return {
14
+ producer: ProducerPlugin,
15
+ consumer: ConsumerPlugin,
16
+ }
17
+ }
18
+ }
19
+
20
+ module.exports = NatsPlugin
@@ -0,0 +1,62 @@
1
+ 'use strict'
2
+
3
+ const { TEXT_MAP } = require('../../../ext/formats')
4
+ const { CLIENT_PORT_KEY } = require('../../dd-trace/src/constants')
5
+ const ProducerPlugin = require('../../dd-trace/src/plugins/producer')
6
+ const { getOperationName } = require('./util')
7
+
8
+ const MESSAGING_DESTINATION_KEY = 'messaging.destination.name'
9
+
10
+ class NatsProducerPlugin extends ProducerPlugin {
11
+ static id = 'nats'
12
+ static operation = 'publish'
13
+ static peerServicePrecursors = [MESSAGING_DESTINATION_KEY]
14
+
15
+ bindStart (ctx) {
16
+ const { subject, options, connection, type, createHeaders } = ctx
17
+ const server = connection?.protocol?.servers?.getCurrent?.() ??
18
+ connection?.protocol?.servers?.getCurrentServer?.()
19
+ const operation = getOperationName(type)
20
+
21
+ const span = this.startSpan({
22
+ resource: subject,
23
+ meta: {
24
+ component: 'nats',
25
+ 'nats.subject': subject,
26
+ 'nats.operation': operation,
27
+ [MESSAGING_DESTINATION_KEY]: subject,
28
+ 'out.host': server?.hostname,
29
+ },
30
+ }, ctx)
31
+
32
+ if (server?.port) {
33
+ span.setTag(CLIENT_PORT_KEY, server.port)
34
+ }
35
+
36
+ if (this.serverSupportsHeaders(connection)) {
37
+ let headers = options.headers
38
+ if (!headers && typeof createHeaders === 'function') {
39
+ headers = createHeaders()
40
+ options.headers = headers
41
+ }
42
+ if (headers && typeof headers.set === 'function') {
43
+ const carrier = {}
44
+ this.tracer.inject(span, TEXT_MAP, carrier)
45
+ for (const key of Object.keys(carrier)) {
46
+ headers.set(key, carrier[key])
47
+ }
48
+ }
49
+ }
50
+
51
+ return ctx.currentStore
52
+ }
53
+
54
+ serverSupportsHeaders (connection) {
55
+ const info = connection?.protocol?.info
56
+ // If info isn't available yet (e.g. publish before INFO), assume supported — modern NATS does.
57
+ if (!info) return true
58
+ return info.headers !== false
59
+ }
60
+ }
61
+
62
+ module.exports = NatsProducerPlugin
@@ -0,0 +1,33 @@
1
+ 'use strict'
2
+
3
+ function headersToTextMap (msgHdrs) {
4
+ if (!msgHdrs || typeof msgHdrs[Symbol.iterator] !== 'function') return null
5
+ const textMap = {}
6
+ for (const [key, values] of msgHdrs) {
7
+ if (!Array.isArray(values) || values.length === 0) continue
8
+ // Trace headers are single-valued (injected via `set`, not `append`), so
9
+ // the first element is always the authoritative value.
10
+ textMap[key] = values[0]
11
+ }
12
+ return textMap
13
+ }
14
+
15
+ function getOperationName (type) {
16
+ switch (type) {
17
+ case 'publish':
18
+ return 'publish'
19
+ case 'request':
20
+ case 'requestMany':
21
+ return 'request'
22
+ default:
23
+ // Surface unrecognized operations explicitly rather than silently
24
+ // collapsing them into 'publish' — if NATS adds a new outbound API,
25
+ // this lets us see it in traces and fix the mapping deliberately.
26
+ return 'unknown'
27
+ }
28
+ }
29
+
30
+ module.exports = {
31
+ headersToTextMap,
32
+ getOperationName,
33
+ }
@@ -33,11 +33,13 @@ class NextPlugin extends ServerPlugin {
33
33
  'span.type': 'web',
34
34
  'span.kind': 'server',
35
35
  'http.method': req.method,
36
- ...(serviceSource === undefined ? {} : { [SVC_SRC_KEY]: serviceSource }),
36
+ ...(serviceSource === undefined ? undefined : { [SVC_SRC_KEY]: serviceSource }),
37
37
  },
38
38
  integrationName: this.constructor.id,
39
39
  })
40
40
 
41
+ this.stampIntegrationService(span, serviceName)
42
+
41
43
  analyticsSampler.sample(span, this.config.measured, true)
42
44
 
43
45
  return { ...store, span, req }
@@ -60,7 +62,7 @@ class NextPlugin extends ServerPlugin {
60
62
  if (!store) return
61
63
 
62
64
  const span = store.span
63
- const error = span.context()._tags.error
65
+ const error = span.context().getTag('error')
64
66
  const requestError = req.error || nextRequest.error
65
67
 
66
68
  if (requestError) {
@@ -97,7 +99,7 @@ class NextPlugin extends ServerPlugin {
97
99
  if (!req) return
98
100
 
99
101
  // Only use error page names if there's not already a name
100
- const current = span.context()._tags['next.page']
102
+ const current = span.context().getTag('next.page')
101
103
  const isErrorPage = errorPages.has(page)
102
104
 
103
105
  if (current && isErrorPage) {
@@ -8,6 +8,7 @@ const Sampler = require('../../dd-trace/src/sampler')
8
8
  const { MEASURED } = require('../../../ext/tags')
9
9
 
10
10
  const { DD_MAJOR } = require('../../../version')
11
+ const { ERROR_TYPE } = require('../../dd-trace/src/constants')
11
12
  const {
12
13
  convertBuffersToObjects,
13
14
  constructCompletionResponseFromStreamedChunks,
@@ -157,7 +158,7 @@ class OpenAiTracingPlugin extends TracingPlugin {
157
158
  const span = store?.span
158
159
  if (!span) return
159
160
 
160
- const error = !!span.context()._tags.error
161
+ const error = !!span.context().getTag('error')
161
162
 
162
163
  let headers, body, method, path
163
164
  if (!error) {
@@ -171,7 +172,7 @@ class OpenAiTracingPlugin extends TracingPlugin {
171
172
  headers = Object.fromEntries(headers)
172
173
  }
173
174
 
174
- const resource = span._spanContext._tags['resource.name']
175
+ const resource = span.context().getTag('resource.name')
175
176
  const normalizedMethodName = store.normalizedMethodName
176
177
 
177
178
  body = coerceResponseBody(body, normalizedMethodName)
@@ -211,6 +212,18 @@ class OpenAiTracingPlugin extends TracingPlugin {
211
212
  this.sendMetrics(headers, body, endpoint, span._duration, error, tags)
212
213
  }
213
214
 
215
+ error (ctx) {
216
+ const span = ctx.currentStore?.span
217
+ if (!span) return
218
+
219
+ super.error(ctx) // add normal error tag
220
+
221
+ const errorType = ctx.error?.type
222
+ if (errorType) {
223
+ span.setTag(ERROR_TYPE, errorType)
224
+ }
225
+ }
226
+
214
227
  sendMetrics (headers, body, endpoint, duration, error, spanTags) {
215
228
  const tags = [`error:${Number(!!error)}`]
216
229
  if (error) {
@@ -24,19 +24,30 @@ class OracledbPlugin extends DatabasePlugin {
24
24
  dbInstance ??= dbInfo.dbInstance
25
25
  }
26
26
 
27
- this.startSpan(this.operationName(), {
27
+ // oracledb >= 6.4 accepts `execute({ statement, values })` (sql-template-tag form)
28
+ // in addition to a plain SQL string. Extract the SQL text either way so we can tag
29
+ // the resource and inject DBM into the statement, then re-wrap if needed to keep
30
+ // the caller's binds.
31
+ const sql = query?.statement ?? query
32
+
33
+ const span = this.startSpan(this.operationName(), {
28
34
  service,
29
- resource: query,
35
+ resource: sql,
30
36
  type: 'sql',
31
37
  kind: 'client',
32
38
  meta: {
33
39
  'db.user': this.config.user,
34
40
  'db.instance': dbInstance,
41
+ 'db.name': dbInstance,
35
42
  'db.hostname': hostname,
43
+ 'out.host': hostname,
36
44
  [CLIENT_PORT_KEY]: port,
37
45
  },
38
46
  }, ctx)
39
47
 
48
+ const injected = this.injectDbmQuery(span, sql, service.name)
49
+ ctx.injected = query?.statement ? { ...query, statement: injected } : injected
50
+
40
51
  return ctx.currentStore
41
52
  }
42
53
  }
@@ -1,9 +1,51 @@
1
1
  'use strict'
2
2
 
3
+ const { buildLogHolder, messageProxy } = require('../../dd-trace/src/plugins/log_injection')
3
4
  const LogPlugin = require('../../dd-trace/src/plugins/log_plugin')
4
5
 
5
6
  class PinoPlugin extends LogPlugin {
6
7
  static id = 'pino'
8
+
9
+ constructor (...args) {
10
+ super(...args)
11
+ this.addSub('apm:pino:log:json', (payload) => this.handleJsonLine(payload))
12
+ this.addSub('apm:pino:log', (arg) => this.handlePrettyMessage(arg))
13
+ }
14
+
15
+ /**
16
+ * Splice `,"dd":<json>` into the JSON line pino has already produced.
17
+ * The caller-owned message object is never observed -- user Proxies and
18
+ * custom serialisers see nothing because there is no mutation to see.
19
+ *
20
+ * @param {{ line: string }} payload
21
+ */
22
+ handleJsonLine (payload) {
23
+ const logHolder = buildLogHolder(this.tracer)
24
+ if (!logHolder) return
25
+
26
+ const line = payload.line
27
+ const lastClose = line.lastIndexOf('}')
28
+ if (lastClose < 1) return
29
+
30
+ const ddJson = JSON.stringify(logHolder.dd)
31
+ const sep = line.charCodeAt(lastClose - 1) === 0x7B ? '' : ','
32
+ payload.line = line.slice(0, lastClose) + sep + '"dd":' + ddJson + line.slice(lastClose)
33
+ }
34
+
35
+ /**
36
+ * `pino-pretty` (bundled with pino 5/7, separate package on >=8) reads
37
+ * the original message object rather than the JSON line, so the splice
38
+ * above is invisible to it. Wrap the message in a Proxy that exposes a
39
+ * virtual `dd` field for the prettifier to pick up.
40
+ *
41
+ * @param {{ message: object }} arg
42
+ */
43
+ handlePrettyMessage (arg) {
44
+ const logHolder = buildLogHolder(this.tracer)
45
+ if (!logHolder) return
46
+
47
+ arg.message = messageProxy(arg.message, logHolder)
48
+ }
7
49
  }
8
50
 
9
51
  module.exports = PinoPlugin