dd-trace 6.0.0 → 6.1.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 (77) hide show
  1. package/LICENSE-3rdparty.csv +1 -2
  2. package/index.d.ts +21 -1
  3. package/package.json +32 -29
  4. package/packages/datadog-instrumentations/src/ai.js +45 -0
  5. package/packages/datadog-instrumentations/src/apollo-server.js +5 -13
  6. package/packages/datadog-instrumentations/src/child_process.js +3 -3
  7. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +587 -0
  8. package/packages/datadog-instrumentations/src/cucumber.js +102 -43
  9. package/packages/datadog-instrumentations/src/cypress-config.js +11 -3
  10. package/packages/datadog-instrumentations/src/fastify.js +27 -8
  11. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  12. package/packages/datadog-instrumentations/src/helpers/instrument.js +7 -0
  13. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +6 -6
  14. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/claude-agent-sdk.js +17 -0
  15. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  16. package/packages/datadog-instrumentations/src/jest.js +713 -63
  17. package/packages/datadog-instrumentations/src/mocha/main.js +24 -3
  18. package/packages/datadog-instrumentations/src/mocha/utils.js +128 -22
  19. package/packages/datadog-instrumentations/src/mocha/worker.js +13 -0
  20. package/packages/datadog-instrumentations/src/mongodb.js +74 -0
  21. package/packages/datadog-instrumentations/src/mongoose.js +4 -5
  22. package/packages/datadog-instrumentations/src/playwright.js +13 -9
  23. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +4 -0
  24. package/packages/datadog-instrumentations/src/vitest-main.js +28 -1
  25. package/packages/datadog-instrumentations/src/vitest-util.js +5 -0
  26. package/packages/datadog-instrumentations/src/vitest-worker.js +104 -34
  27. package/packages/datadog-plugin-ai/src/index.js +1 -1
  28. package/packages/datadog-plugin-ai/src/tracing.js +66 -3
  29. package/packages/datadog-plugin-ai/src/utils.js +17 -4
  30. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +1 -0
  31. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +1 -0
  32. package/packages/datadog-plugin-child_process/src/index.js +13 -2
  33. package/packages/datadog-plugin-claude-agent-sdk/src/index.js +31 -0
  34. package/packages/datadog-plugin-claude-agent-sdk/src/tracing.js +107 -0
  35. package/packages/datadog-plugin-claude-agent-sdk/src/util.js +15 -0
  36. package/packages/datadog-plugin-cucumber/src/index.js +14 -23
  37. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +351 -11
  38. package/packages/datadog-plugin-cypress/src/index.js +47 -2
  39. package/packages/datadog-plugin-cypress/src/plugin.js +1 -0
  40. package/packages/datadog-plugin-graphql/src/execute.js +21 -10
  41. package/packages/datadog-plugin-graphql/src/index.js +17 -1
  42. package/packages/datadog-plugin-graphql/src/utils.js +9 -2
  43. package/packages/datadog-plugin-graphql/src/validate.js +1 -1
  44. package/packages/datadog-plugin-jest/src/index.js +30 -14
  45. package/packages/datadog-plugin-mocha/src/index.js +39 -14
  46. package/packages/datadog-plugin-mongodb-core/src/bulk-write.js +43 -0
  47. package/packages/datadog-plugin-mongodb-core/src/index.js +8 -443
  48. package/packages/datadog-plugin-mongodb-core/src/query.js +452 -0
  49. package/packages/datadog-plugin-playwright/src/index.js +3 -2
  50. package/packages/datadog-plugin-vitest/src/index.js +51 -9
  51. package/packages/dd-trace/src/aiguard/index.js +9 -14
  52. package/packages/dd-trace/src/aiguard/integrations/index.js +34 -0
  53. package/packages/dd-trace/src/aiguard/integrations/openai.js +47 -33
  54. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +42 -28
  55. package/packages/dd-trace/src/appsec/graphql.js +9 -6
  56. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +3 -10
  57. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +26 -1
  58. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +115 -24
  59. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +3 -0
  60. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +3 -0
  61. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +75 -7
  62. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +156 -0
  63. package/packages/dd-trace/src/config/generated-config-types.d.ts +10 -0
  64. package/packages/dd-trace/src/config/supported-configurations.json +37 -0
  65. package/packages/dd-trace/src/crashtracking/crashtracker.js +2 -2
  66. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +403 -0
  67. package/packages/dd-trace/src/llmobs/plugins/ai/index.js +8 -395
  68. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +27 -0
  69. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +363 -0
  70. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +270 -0
  71. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/utils.js +10 -0
  72. package/packages/dd-trace/src/opentelemetry/span.js +5 -0
  73. package/packages/dd-trace/src/plugin_manager.js +16 -0
  74. package/packages/dd-trace/src/plugins/ci_plugin.js +147 -7
  75. package/packages/dd-trace/src/plugins/index.js +2 -0
  76. package/packages/dd-trace/src/plugins/util/test.js +4 -0
  77. package/packages/dd-trace/src/profiling/exporters/agent.js +28 -3
@@ -1,452 +1,17 @@
1
1
  'use strict'
2
2
 
3
- const { isMap, isRegExp } = require('node:util').types
3
+ const CompositePlugin = require('../../dd-trace/src/plugins/composite')
4
+ const MongodbCoreBulkWritePlugin = require('./bulk-write')
5
+ const MongodbCoreQueryPlugin = require('./query')
4
6
 
5
- const DatabasePlugin = require('../../dd-trace/src/plugins/database')
6
-
7
- class MongodbCorePlugin extends DatabasePlugin {
7
+ class MongodbCorePlugin extends CompositePlugin {
8
8
  static id = 'mongodb-core'
9
- static component = 'mongodb'
10
- // avoid using db.name for peer.service since it includes the collection name
11
- // should be removed if one day this will be fixed
12
- /**
13
- * @override
14
- */
15
- static peerServicePrecursors = []
16
-
17
- /**
18
- * @override
19
- */
20
- configure (config) {
21
- super.configure(config)
22
-
23
- this.config.heartbeatEnabled = config.heartbeatEnabled ??
24
- this._tracerConfig.DD_TRACE_MONGODB_HEARTBEAT_ENABLED
25
- this.config.obfuscateQuery = normaliseObfuscateQuery(
26
- config.obfuscateQuery ?? this._tracerConfig.DD_TRACE_MONGODB_OBFUSCATE_QUERY
27
- )
28
- }
29
-
30
- bindStart (ctx) {
31
- const { ns, ops, options = {}, name } = ctx
32
- // heartbeat commands can be disabled if this.config.heartbeatEnabled is false
33
- if (!this.config.heartbeatEnabled && isHeartbeat(ops, this.config)) {
34
- return
35
- }
36
- const query = getQuery(ops, this.config.obfuscateQuery)
37
- const resource = truncate(getResource(this, ns, query, name))
38
- const serviceResult = this.serviceName({ pluginConfig: this.config })
39
- const span = this.startSpan(this.operationName(), {
40
- service: serviceResult,
41
- resource,
42
- type: 'mongodb',
43
- kind: 'client',
44
- meta: {
45
- // this is not technically correct since it includes the collection but we changing will break customer stuff
46
- 'db.name': ns,
47
- 'mongodb.query': query,
48
- 'out.host': options.host,
49
- 'out.port': options.port,
50
- },
51
- }, ctx)
52
- const comment = this.injectDbmComment(span, ops.comment, serviceResult.name)
53
- if (comment) {
54
- ops.comment = comment
55
- }
56
-
57
- return ctx.currentStore
58
- }
59
-
60
- /**
61
- * @override
62
- */
63
- getPeerService (tags) {
64
- let ns = tags['db.name']
65
- if (ns && tags['peer.service'] === undefined) {
66
- const dotIndex = ns.indexOf('.')
67
- if (dotIndex !== -1) {
68
- ns = ns.slice(0, dotIndex)
69
- }
70
- // the mongo ns is either dbName either dbName.collection. So we keep the first part
71
- tags['peer.service'] = ns
72
- }
73
- return super.getPeerService(tags)
74
- }
75
-
76
- injectDbmComment (span, comment, serviceName) {
77
- const dbmTraceComment = this.createDbmComment(span, serviceName)
78
-
79
- if (!dbmTraceComment) {
80
- return comment
81
- }
82
-
83
- if (comment) {
84
- // if the command already has a comment, append the dbm trace comment
85
- if (typeof comment === 'string') {
86
- comment += `,${dbmTraceComment}`
87
- } else if (Array.isArray(comment)) {
88
- comment.push(dbmTraceComment)
89
- } // do nothing if the comment is not a string or an array
90
- } else {
91
- comment = dbmTraceComment
92
- }
93
-
94
- return comment
95
- }
96
- }
97
-
98
- const MAX_DEPTH = 10
99
- const MAX_QUERY_LENGTH = 10_000
100
-
101
- function extractQuery (statements) {
102
- if (statements.length === 1 && statements[0].q) return statements[0].q
103
-
104
- const extractedQueries = []
105
- for (let i = 0; i < statements.length; i++) {
106
- if (statements[i].q) {
107
- extractedQueries.push(statements[i].q)
108
- }
109
- }
110
-
111
- return extractedQueries
112
- }
113
-
114
- /**
115
- * @param {Record<string, unknown> | unknown[] | undefined} cmd
116
- * @param {'none' | 'types' | 'redact'} mode
117
- */
118
- function getQuery (cmd, mode) {
119
- if (!cmd || (typeof cmd !== 'object' && !Array.isArray(cmd))) return
120
-
121
- if (Array.isArray(cmd)) return sanitiseAndStringify(extractQuery(cmd), mode)
122
- if (cmd.query) return sanitiseAndStringify(cmd.query, mode)
123
- if (cmd.filter) return sanitiseAndStringify(cmd.filter, mode)
124
- if (cmd.pipeline) return sanitiseAndStringify(cmd.pipeline, mode)
125
- if (cmd.deletes) return sanitiseAndStringify(extractQuery(cmd.deletes), mode)
126
- if (cmd.updates) return sanitiseAndStringify(extractQuery(cmd.updates), mode)
127
- }
128
-
129
- function getResource (plugin, ns, query, operationName) {
130
- let resource = `${operationName} ${ns}`
131
-
132
- if (plugin.config.queryInResourceName && query) {
133
- resource += ` ${query}`
134
- }
135
-
136
- return resource
137
- }
138
-
139
- function truncate (input) {
140
- return input.length > MAX_QUERY_LENGTH ? input.slice(0, MAX_QUERY_LENGTH) : input
141
- }
142
-
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
-
186
- /**
187
- * @param {Record<string, unknown> | unknown[]} input
188
- * @param {'none' | 'types' | 'redact'} mode
189
- */
190
- function sanitiseAndStringify (input, mode) {
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
- }
198
-
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 = ','
9
+ static get plugins () {
10
+ return {
11
+ query: MongodbCoreQueryPlugin,
12
+ bulkWrite: MongodbCoreBulkWritePlugin,
259
13
  }
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
14
  }
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)
331
-
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 = ','
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
- }
353
-
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
392
- }
393
- value = json
394
- } else if (bsontype !== undefined) {
395
- return TYPE_OBJECT
396
- }
397
-
398
- if (isMap(value)) value = Object.fromEntries(value)
399
-
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
425
- }
426
-
427
- /**
428
- * @param {unknown} child
429
- * @param {object[]} ancestors
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 */
438
- function normaliseObfuscateQuery (value) {
439
- if (value === 'types' || value === 'redact') return value
440
- return 'none'
441
- }
442
-
443
- function isHeartbeat (ops, config) {
444
- // Check if it's a heartbeat command https://github.com/mongodb/specifications/blob/master/source/mongodb-handshake/handshake.md
445
- return (
446
- ops &&
447
- typeof ops === 'object' &&
448
- (ops.hello === 1 || ops.helloOk === true || ops.ismaster === 1 || ops.isMaster === 1)
449
- )
450
15
  }
451
16
 
452
17
  module.exports = MongodbCorePlugin