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,5 +1,11 @@
1
1
  'use strict'
2
2
 
3
+ // Capture real timers at module load time, before any test can install fake timers.
4
+ const realSetTimeout = setTimeout
5
+ const realClearTimeout = clearTimeout
6
+
7
+ const { threadId } = require('node:worker_threads')
8
+
3
9
  const { storage } = require('../../../datadog-core')
4
10
  const { COMPONENT } = require('../constants')
5
11
  const log = require('../log')
@@ -12,6 +18,7 @@ const {
12
18
  } = require('../ci-visibility/telemetry')
13
19
  const getDiClient = require('../ci-visibility/dynamic-instrumentation')
14
20
  const { DD_MAJOR } = require('../../../../version')
21
+ const { version: tracerVersion } = require('../../../../package.json')
15
22
  const id = require('../id')
16
23
  const { OS_VERSION, OS_PLATFORM, OS_ARCHITECTURE, RUNTIME_NAME, RUNTIME_VERSION } = require('./util/env')
17
24
  const {
@@ -84,6 +91,9 @@ const {
84
91
  } = require('./util/test')
85
92
 
86
93
  const legacyStorage = storage('legacy')
94
+ const DI_OPERATION_TIMEOUT_MS = 2000
95
+ const DI_LOGGER_THREAD_ID = threadId === 0 ? `pid:${process.pid}` : `pid:${process.pid};tid:${threadId}`
96
+ const DI_LOGGER_THREAD_NAME = threadId === 0 ? 'MainThread' : `WorkerThread:${threadId}`
87
97
 
88
98
  const FRAMEWORK_TO_TRIMMED_COMMAND = {
89
99
  vitest: 'vitest run',
@@ -108,6 +118,18 @@ const TEST_FRAMEWORKS_TO_SKIP_GIT_METADATA_EXTRACTION = new Set([
108
118
  'cucumber',
109
119
  ])
110
120
 
121
+ function withTimeout (promise, timeoutMs) {
122
+ return new Promise(resolve => {
123
+ const timeoutId = realSetTimeout(resolve, timeoutMs)
124
+
125
+ const done = () => {
126
+ realClearTimeout(timeoutId)
127
+ resolve()
128
+ }
129
+ promise.then(done, done)
130
+ })
131
+ }
132
+
111
133
  function setItrSkippingEnabledTagFromLibraryConfig (plugin, frameworkVersion) {
112
134
  const libraryCapabilitiesTags = getDefaultLibraryCapabilitiesTags(plugin.constructor.id, frameworkVersion)
113
135
 
@@ -143,6 +165,8 @@ module.exports = class CiPlugin extends Plugin {
143
165
  super(...args)
144
166
 
145
167
  this.fileLineToProbeId = new Map()
168
+ this.diBreakpointHitPromise = undefined
169
+ this.diBreakpointHitResolvers = []
146
170
  this.rootDir = process.cwd() // fallback in case :session:start events are not emitted
147
171
  this._testSuiteSpansByTestSuite = new Map()
148
172
  this._pendingWorkerTracesByTestSuite = new Map()
@@ -164,10 +188,6 @@ module.exports = class CiPlugin extends Plugin {
164
188
  setItrSkippingEnabledTagFromLibraryConfig(this, frameworkVersion)
165
189
  }
166
190
 
167
- const requestErrorTags = this.testSessionSpan
168
- ? getSessionRequestErrorTags(this.testSessionSpan)
169
- : Object.fromEntries(this._pendingRequestErrorTags.map(({ tag, value }) => [tag, value]))
170
-
171
191
  const libraryCapabilitiesTags = this.getLibraryCapabilitiesTags(frameworkVersion, ctx)
172
192
  const metadataTags = {
173
193
  test: {
@@ -175,7 +195,13 @@ module.exports = class CiPlugin extends Plugin {
175
195
  },
176
196
  }
177
197
  this.tracer._exporter.addMetadataTags(metadataTags)
178
- onDone({ err, libraryConfig, repositoryRoot: this.repositoryRoot, requestErrorTags })
198
+ onDone({
199
+ err,
200
+ isTestDynamicInstrumentationEnabled: this.config.isTestDynamicInstrumentationEnabled,
201
+ libraryConfig,
202
+ repositoryRoot: this.repositoryRoot,
203
+ requestErrorTags: this._getCurrentRequestErrorTags(),
204
+ })
179
205
  })
180
206
  })
181
207
 
@@ -307,7 +333,7 @@ module.exports = class CiPlugin extends Plugin {
307
333
  this.libraryConfig.isKnownTestsEnabled = false
308
334
  }
309
335
  }
310
- onDone({ err, knownTests })
336
+ onDone({ err, knownTests, requestErrorTags: this._getCurrentRequestErrorTags() })
311
337
  })
312
338
  })
313
339
 
@@ -327,7 +353,7 @@ module.exports = class CiPlugin extends Plugin {
327
353
  this.libraryConfig.isTestManagementEnabled = false
328
354
  }
329
355
  }
330
- onDone({ err, testManagementTests })
356
+ onDone({ err, testManagementTests, requestErrorTags: this._getCurrentRequestErrorTags() })
331
357
  })
332
358
  })
333
359
 
@@ -459,6 +485,18 @@ module.exports = class CiPlugin extends Plugin {
459
485
  }
460
486
  }
461
487
 
488
+ /**
489
+ * Returns the current request error tags, including tags queued before session creation.
490
+ *
491
+ * @returns {Record<string, string>}
492
+ */
493
+ _getCurrentRequestErrorTags () {
494
+ if (this.testSessionSpan) {
495
+ return getSessionRequestErrorTags(this.testSessionSpan)
496
+ }
497
+ return Object.fromEntries(this._pendingRequestErrorTags.map(({ tag, value }) => [tag, value]))
498
+ }
499
+
462
500
  /**
463
501
  * Updates repository-root-dependent state when a worker receives the root from
464
502
  * the coordinator process after plugin configuration.
@@ -776,6 +814,11 @@ module.exports = class CiPlugin extends Plugin {
776
814
  }
777
815
 
778
816
  onDiBreakpointHit ({ snapshot }) {
817
+ for (const resolve of this.diBreakpointHitResolvers) {
818
+ resolve()
819
+ }
820
+ this.diBreakpointHitResolvers.length = 0
821
+
779
822
  if (!this.activeTestSpan || this.activeTestSpan.context()._isFinished) {
780
823
  // This is unexpected and is caused by a race condition.
781
824
  log.warn('Breakpoint snapshot could not be attached to the active test span')
@@ -799,8 +842,17 @@ module.exports = class CiPlugin extends Plugin {
799
842
  )
800
843
 
801
844
  const activeTestSpanContext = this.activeTestSpan.context()
845
+ const topStackFrame = snapshot.stack?.[0]
802
846
 
803
847
  this.tracer._exporter.exportDiLogs(this.testEnvironmentMetadata, {
848
+ message: '',
849
+ logger: {
850
+ name: snapshot.probe.location.file,
851
+ method: topStackFrame?.function || '',
852
+ version: tracerVersion,
853
+ thread_id: DI_LOGGER_THREAD_ID,
854
+ thread_name: DI_LOGGER_THREAD_NAME,
855
+ },
804
856
  debugger: { snapshot },
805
857
  dd: {
806
858
  trace_id: activeTestSpanContext.toTraceId(),
@@ -809,6 +861,94 @@ module.exports = class CiPlugin extends Plugin {
809
861
  })
810
862
  }
811
863
 
864
+ /**
865
+ * Wait for a Dynamic Instrumentation operation without blocking test framework progress forever.
866
+ *
867
+ * @param {Promise<void>} promise - Dynamic Instrumentation operation promise.
868
+ * @returns {Promise<void>}
869
+ */
870
+ waitForDiOperation (promise) {
871
+ return withTimeout(promise, DI_OPERATION_TIMEOUT_MS)
872
+ }
873
+
874
+ /**
875
+ * Resolve any prepared breakpoint-hit wait when no caller still needs it.
876
+ */
877
+ cancelDiBreakpointHitWait () {
878
+ for (const resolve of this.diBreakpointHitResolvers) {
879
+ resolve()
880
+ }
881
+ this.diBreakpointHitResolvers.length = 0
882
+ }
883
+
884
+ /**
885
+ * Wait for a prepared breakpoint hit before resolving any unused waiters.
886
+ *
887
+ * @returns {Promise<void>}
888
+ */
889
+ waitForPreparedDiBreakpointHit () {
890
+ if (!this.diBreakpointHitPromise) {
891
+ this.cancelDiBreakpointHitWait()
892
+ return Promise.resolve()
893
+ }
894
+
895
+ return this.waitForDiOperation(this.diBreakpointHitPromise).then(
896
+ () => this.cancelDiBreakpointHitWait(),
897
+ () => this.cancelDiBreakpointHitWait()
898
+ )
899
+ }
900
+
901
+ /**
902
+ * Prepare a wait for the next breakpoint hit before the retried test starts.
903
+ *
904
+ * @returns {Promise<void>}
905
+ */
906
+ prepareDiBreakpointHitWait () {
907
+ if (!this.di) return Promise.resolve()
908
+
909
+ let resolveHit
910
+ const hitPromise = new Promise(resolve => {
911
+ resolveHit = resolve
912
+ this.diBreakpointHitResolvers.push(resolve)
913
+ })
914
+
915
+ const preparedPromise = hitPromise.finally(() => {
916
+ const resolverIndex = this.diBreakpointHitResolvers.indexOf(resolveHit)
917
+ if (resolverIndex !== -1) {
918
+ this.diBreakpointHitResolvers.splice(resolverIndex, 1)
919
+ }
920
+ if (this.diBreakpointHitPromise === preparedPromise) {
921
+ this.diBreakpointHitPromise = undefined
922
+ }
923
+ })
924
+
925
+ this.diBreakpointHitPromise = preparedPromise
926
+ return this.diBreakpointHitPromise
927
+ }
928
+
929
+ /**
930
+ * Wait until the DI worker has posted any breakpoint hits it was already processing.
931
+ *
932
+ * @returns {Promise<void>}
933
+ */
934
+ waitForDiBreakpointHits () {
935
+ if (!this.di) return Promise.resolve()
936
+ if (this.diBreakpointHitPromise) return this.waitForDiOperation(this.diBreakpointHitPromise)
937
+
938
+ return this.waitForInFlightDiBreakpointHits()
939
+ }
940
+
941
+ /**
942
+ * Wait until the DI worker has posted breakpoint hits it was already processing.
943
+ *
944
+ * @returns {Promise<void>}
945
+ */
946
+ waitForInFlightDiBreakpointHits () {
947
+ if (!this.di) return Promise.resolve()
948
+
949
+ return this.waitForDiOperation(this.di.waitForInFlightBreakpointHits())
950
+ }
951
+
812
952
  removeAllDiProbes () {
813
953
  if (this.fileLineToProbeId.size === 0) {
814
954
  return Promise.resolve()
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const plugins = {
4
+ get '@anthropic-ai/claude-agent-sdk' () { return require('../../../datadog-plugin-claude-agent-sdk/src') },
4
5
  get '@anthropic-ai/sdk' () { return require('../../../datadog-plugin-anthropic/src') },
5
6
  get '@apollo/gateway' () { return require('../../../datadog-plugin-apollo/src') },
6
7
  get '@aws-sdk/smithy-client' () { return require('../../../datadog-plugin-aws-sdk/src') },
@@ -46,6 +47,7 @@ const plugins = {
46
47
  get bunyan () { return require('../../../datadog-plugin-bunyan/src') },
47
48
  get 'cassandra-driver' () { return require('../../../datadog-plugin-cassandra-driver/src') },
48
49
  get child_process () { return require('../../../datadog-plugin-child_process/src') },
50
+ get 'claude-agent-sdk' () { return require('../../../datadog-plugin-claude-agent-sdk/src') },
49
51
  get connect () { return require('../../../datadog-plugin-connect/src') },
50
52
  get couchbase () { return require('../../../datadog-plugin-couchbase/src') },
51
53
  get cypress () { return require('../../../datadog-plugin-cypress/src') },
@@ -95,6 +95,8 @@ const TEST_IS_RUM_ACTIVE = 'test.is_rum_active'
95
95
  const TEST_CODE_OWNERS = 'test.codeowners'
96
96
  const TEST_SOURCE_FILE = 'test.source.file'
97
97
  const TEST_SOURCE_START = 'test.source.start'
98
+ const TEST_FAILURE_SCREENSHOT_UPLOADED = 'test.failure_screenshot.uploaded'
99
+ const TEST_FAILURE_SCREENSHOT_UPLOAD_ERROR = 'test.failure_screenshot.upload_error'
98
100
  const LIBRARY_VERSION = 'library_version'
99
101
  const TEST_COMMAND = 'test.command'
100
102
  const TEST_MODULE = 'test.module'
@@ -433,6 +435,8 @@ module.exports = {
433
435
  TEST_SKIP_REASON,
434
436
  TEST_IS_RUM_ACTIVE,
435
437
  TEST_SOURCE_FILE,
438
+ TEST_FAILURE_SCREENSHOT_UPLOADED,
439
+ TEST_FAILURE_SCREENSHOT_UPLOAD_ERROR,
436
440
  CI_APP_ORIGIN,
437
441
  LIBRARY_VERSION,
438
442
  JEST_WORKER_TRACE_PAYLOAD_CODE,
@@ -42,6 +42,19 @@ function sendRequest (options, form, callback) {
42
42
  storage('legacy').run({ noop: true }, () => {
43
43
  requestCounter.inc()
44
44
  const start = perf.now()
45
+
46
+ // Ensure the callback runs at most once per request. The 'timeout' handler
47
+ // below can fire after a response was already received (e.g. on a lingering
48
+ // keep-alive socket, once it goes idle); without this guard that would
49
+ // invoke the callback a second time and corrupt the retry accounting in
50
+ // AgentExporter.export.
51
+ let settled = false
52
+ const done = (err, res) => {
53
+ if (settled) return
54
+ settled = true
55
+ callback(err, res)
56
+ }
57
+
45
58
  const req = request(options, res => {
46
59
  durationDistribution.track(perf.now() - start)
47
60
  countStatusCode(res.statusCode)
@@ -49,16 +62,28 @@ function sendRequest (options, form, callback) {
49
62
  statusCodeErrorCounter.inc()
50
63
  const error = new Error(`HTTP Error ${res.statusCode}`)
51
64
  error.status = res.statusCode
52
- callback(error)
65
+ done(error)
53
66
  } else {
54
- callback(null, res)
67
+ done(null, res)
55
68
  }
56
69
  })
57
70
 
58
71
  req.on('error', (err) => {
59
72
  networkErrorCounter.inc()
60
- callback(err)
73
+ done(err)
61
74
  })
75
+
76
+ // `options.timeout` sets the socket's idle timeout, which only emits a
77
+ // 'timeout' event — per the Node docs it does NOT abort the request. Without
78
+ // destroying the socket here, a stalled upload hangs indefinitely: no
79
+ // 'error' fires, so the retry/backoff in AgentExporter.export never runs and
80
+ // the export promise never settles. Destroying the request makes the
81
+ // 'error' handler above run, which drives the retry (see the trace exporter
82
+ // in ../common/request.js, which likewise aborts the request on timeout).
83
+ req.on('timeout', () => {
84
+ req.destroy(new Error(`Profiling agent export timed out after ${options.timeout}ms`))
85
+ })
86
+
62
87
  if (form) {
63
88
  sizeDistribution.track(form.size())
64
89
  form.pipe(req)