dd-trace 6.0.0 → 6.2.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 (98) hide show
  1. package/LICENSE-3rdparty.csv +1 -2
  2. package/index.d.ts +28 -1
  3. package/initialize.mjs +4 -2
  4. package/package.json +34 -30
  5. package/packages/datadog-instrumentations/src/ai.js +45 -0
  6. package/packages/datadog-instrumentations/src/apollo-server.js +5 -13
  7. package/packages/datadog-instrumentations/src/child_process.js +3 -3
  8. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +587 -0
  9. package/packages/datadog-instrumentations/src/cucumber.js +102 -43
  10. package/packages/datadog-instrumentations/src/cypress-config.js +11 -3
  11. package/packages/datadog-instrumentations/src/fastify.js +27 -8
  12. package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -0
  13. package/packages/datadog-instrumentations/src/helpers/instrument.js +7 -0
  14. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +5 -1
  15. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +6 -6
  16. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/claude-agent-sdk.js +17 -0
  17. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
  18. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/mercurius.js +31 -0
  19. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +19 -5
  20. package/packages/datadog-instrumentations/src/jest.js +713 -63
  21. package/packages/datadog-instrumentations/src/mercurius.js +11 -0
  22. package/packages/datadog-instrumentations/src/mocha/main.js +24 -3
  23. package/packages/datadog-instrumentations/src/mocha/utils.js +128 -22
  24. package/packages/datadog-instrumentations/src/mocha/worker.js +13 -0
  25. package/packages/datadog-instrumentations/src/mongodb.js +74 -0
  26. package/packages/datadog-instrumentations/src/mongoose.js +4 -5
  27. package/packages/datadog-instrumentations/src/otel-sdk-trace.js +15 -0
  28. package/packages/datadog-instrumentations/src/playwright.js +13 -9
  29. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +4 -0
  30. package/packages/datadog-instrumentations/src/vitest-main.js +26 -1
  31. package/packages/datadog-instrumentations/src/vitest-util.js +2 -0
  32. package/packages/datadog-instrumentations/src/vitest-worker.js +103 -34
  33. package/packages/datadog-plugin-ai/src/index.js +1 -1
  34. package/packages/datadog-plugin-ai/src/tracing.js +66 -3
  35. package/packages/datadog-plugin-ai/src/utils.js +17 -4
  36. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +1 -0
  37. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +1 -0
  38. package/packages/datadog-plugin-child_process/src/index.js +13 -2
  39. package/packages/datadog-plugin-claude-agent-sdk/src/index.js +31 -0
  40. package/packages/datadog-plugin-claude-agent-sdk/src/tracing.js +107 -0
  41. package/packages/datadog-plugin-claude-agent-sdk/src/util.js +15 -0
  42. package/packages/datadog-plugin-cucumber/src/index.js +14 -23
  43. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +351 -11
  44. package/packages/datadog-plugin-cypress/src/index.js +47 -2
  45. package/packages/datadog-plugin-cypress/src/plugin.js +1 -0
  46. package/packages/datadog-plugin-graphql/src/execute.js +25 -25
  47. package/packages/datadog-plugin-graphql/src/index.js +23 -1
  48. package/packages/datadog-plugin-graphql/src/request.js +104 -0
  49. package/packages/datadog-plugin-graphql/src/utils.js +186 -2
  50. package/packages/datadog-plugin-graphql/src/validate.js +25 -2
  51. package/packages/datadog-plugin-jest/src/index.js +30 -14
  52. package/packages/datadog-plugin-mocha/src/index.js +39 -14
  53. package/packages/datadog-plugin-mongodb-core/src/bulk-write.js +43 -0
  54. package/packages/datadog-plugin-mongodb-core/src/index.js +8 -443
  55. package/packages/datadog-plugin-mongodb-core/src/query.js +452 -0
  56. package/packages/datadog-plugin-playwright/src/index.js +3 -2
  57. package/packages/datadog-plugin-vitest/src/index.js +43 -9
  58. package/packages/dd-trace/src/aiguard/index.js +9 -14
  59. package/packages/dd-trace/src/aiguard/integrations/index.js +34 -0
  60. package/packages/dd-trace/src/aiguard/integrations/openai.js +47 -33
  61. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +42 -28
  62. package/packages/dd-trace/src/appsec/graphql.js +9 -6
  63. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +3 -10
  64. package/packages/dd-trace/src/appsec/lambda.js +8 -8
  65. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +26 -1
  66. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +115 -24
  67. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +3 -0
  68. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +3 -0
  69. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +75 -7
  70. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +156 -0
  71. package/packages/dd-trace/src/config/generated-config-types.d.ts +10 -0
  72. package/packages/dd-trace/src/config/supported-configurations.json +37 -0
  73. package/packages/dd-trace/src/crashtracking/crashtracker.js +2 -2
  74. package/packages/dd-trace/src/datastreams/writer.js +14 -2
  75. package/packages/dd-trace/src/encode/0.4.js +23 -2
  76. package/packages/dd-trace/src/encode/0.5.js +12 -1
  77. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +11 -1
  78. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +20 -2
  79. package/packages/dd-trace/src/exporters/common/writer.js +17 -1
  80. package/packages/dd-trace/src/guardrails/index.js +3 -1
  81. package/packages/dd-trace/src/guardrails/telemetry.js +40 -3
  82. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +403 -0
  83. package/packages/dd-trace/src/llmobs/plugins/ai/index.js +8 -395
  84. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +27 -0
  85. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +363 -0
  86. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +270 -0
  87. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/utils.js +10 -0
  88. package/packages/dd-trace/src/msgpack/chunk.js +33 -1
  89. package/packages/dd-trace/src/msgpack/index.js +6 -1
  90. package/packages/dd-trace/src/opentelemetry/span.js +5 -0
  91. package/packages/dd-trace/src/plugin_manager.js +16 -0
  92. package/packages/dd-trace/src/plugins/ci_plugin.js +147 -7
  93. package/packages/dd-trace/src/plugins/index.js +5 -0
  94. package/packages/dd-trace/src/plugins/util/test.js +4 -0
  95. package/packages/dd-trace/src/profiling/exporters/agent.js +28 -3
  96. package/packages/dd-trace/src/service-naming/schemas/v0/graphql.js +6 -0
  97. package/packages/dd-trace/src/service-naming/schemas/v1/graphql.js +8 -0
  98. package/vendor/dist/@apm-js-collab/code-transformer/index.js +5 -6
@@ -152,6 +152,11 @@ class Span extends BridgeSpanBase {
152
152
  hostname: _tracer._hostname,
153
153
  integrationName: parentTracer?._isOtelLibrary ? 'otel.library' : 'otel',
154
154
  tags: {
155
+ // Apply global/resource tags (DD_TAGS, OTEL_RESOURCE_ATTRIBUTES) as
156
+ // defaults, mirroring the native path in opentracing/tracer.js. The
157
+ // explicit service/resource/span.kind below take precedence, and any
158
+ // user-set OTel attributes applied via setAttributes() still win.
159
+ ..._tracer._config.tags,
155
160
  [SERVICE_NAME]: _tracer._service,
156
161
  [RESOURCE_NAME]: spanName,
157
162
  [SPAN_KIND]: spanKindNames[kind],
@@ -169,6 +169,10 @@ module.exports = class PluginManager {
169
169
  clientIpHeader,
170
170
  DD_TRACE_MEMCACHED_COMMAND_ENABLED,
171
171
  DD_TRACE_OTEL_SEMANTICS_ENABLED,
172
+ DD_TRACE_GRAPHQL_COLLAPSE,
173
+ DD_TRACE_GRAPHQL_DEPTH,
174
+ DD_TRACE_GRAPHQL_VARIABLES,
175
+ DD_TRACE_GRAPHQL_ERROR_EXTENSIONS,
172
176
  DD_TEST_SESSION_NAME,
173
177
  DD_AGENTLESS_LOG_SUBMISSION_ENABLED,
174
178
  testOptimization,
@@ -225,6 +229,18 @@ module.exports = class PluginManager {
225
229
  sharedConfig.middleware = middlewareTracingEnabled
226
230
  }
227
231
 
232
+ // The graphql `DD_TRACE_GRAPHQL_*` options are global on purpose: they feed
233
+ // the plugin config as a base that a programmatic `tracer.use('graphql', …)`
234
+ // overrides, and stay on the Config singleton so remote config and config
235
+ // telemetry observe them. Forwarded only for graphql so other plugins do not
236
+ // carry keys they ignore. The plugin-facing names drop the prefix.
237
+ if (name === 'graphql') {
238
+ sharedConfig.collapse = DD_TRACE_GRAPHQL_COLLAPSE
239
+ sharedConfig.depth = DD_TRACE_GRAPHQL_DEPTH
240
+ sharedConfig.variables = DD_TRACE_GRAPHQL_VARIABLES
241
+ sharedConfig.errorExtensions = DD_TRACE_GRAPHQL_ERROR_EXTENSIONS
242
+ }
243
+
228
244
  return sharedConfig
229
245
  }
230
246
  }
@@ -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') },
@@ -82,6 +84,9 @@ const plugins = {
82
84
  get langchain () { return require('../../../datadog-plugin-langchain/src') },
83
85
  get mariadb () { return require('../../../datadog-plugin-mariadb/src') },
84
86
  get memcached () { return require('../../../datadog-plugin-memcached/src') },
87
+ // mercurius is traced under the graphql plugin: its instrumentation opens the
88
+ // top-level graphql.request span handled by the graphql CompositePlugin.
89
+ get mercurius () { return require('../../../datadog-plugin-graphql/src') },
85
90
  get 'microgateway-core' () { return require('../../../datadog-plugin-microgateway-core/src') },
86
91
  get mocha () { return require('../../../datadog-plugin-mocha/src') },
87
92
  get 'mocha-each' () { return require('../../../datadog-plugin-mocha/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)
@@ -8,6 +8,12 @@ const graphql = {
8
8
  opName: () => 'graphql.execute',
9
9
  serviceName: identityService,
10
10
  },
11
+ // Top-level request span for drivers that funnel through a single entry
12
+ // point (mercurius). Matches the cross-tracer `graphql.request` v0 name.
13
+ request: {
14
+ opName: () => 'graphql.request',
15
+ serviceName: identityService,
16
+ },
11
17
  },
12
18
  }
13
19
 
@@ -8,6 +8,14 @@ const graphql = {
8
8
  opName: () => 'graphql.server.request',
9
9
  serviceName: identityService,
10
10
  },
11
+ // Top-level request span for drivers that funnel through a single entry
12
+ // point (mercurius). Matches the cross-tracer `graphql.server.request` v1
13
+ // name. The v1 overlap with the execute span's name above is a known wart
14
+ // tracked for a separate cross-tracer unification (breaking) change.
15
+ request: {
16
+ opName: () => 'graphql.server.request',
17
+ serviceName: identityService,
18
+ },
11
19
  },
12
20
  }
13
21
 
@@ -8,7 +8,7 @@
8
8
  const __apm$${T} = this["${T}"]
9
9
  this["${T}"] = function () {}
10
10
  if (typeof __apm$${T} === 'function') {
11
- Object.defineProperty(this["${T}"], 'length', {
11
+ Object.defineProperty(this["${T}"], 'length', {
12
12
  value: __apm$${T}.length,
13
13
  configurable: true
14
14
  })
@@ -94,7 +94,7 @@
94
94
  if (typeof promise?.then !== 'function') {
95
95
  __apm$ctx.result = promise;
96
96
  ${k}
97
- return promise;
97
+ return __apm$ctx.result;
98
98
  }
99
99
  // Mirror Node.js core diagnostics_channel behaviour: for native Promise
100
100
  // instances, chain normally (safe since there is no subclass API to
@@ -109,7 +109,7 @@
109
109
  ${k}
110
110
  ${w}.asyncStart.publish(__apm$ctx);
111
111
  ${w}.asyncEnd.publish(__apm$ctx);
112
- return result;
112
+ return __apm$ctx.result;
113
113
  },
114
114
  err => {
115
115
  __apm$ctx.error = err;
@@ -151,10 +151,8 @@
151
151
 
152
152
  return ${w}.start.runStores(__apm$ctx, () => {
153
153
  try {
154
- const result = __apm$traced();
155
- __apm$ctx.result = result;
154
+ __apm$ctx.result = __apm$traced();
156
155
  ${k}
157
- return result;
158
156
  } catch (err) {
159
157
  __apm$ctx.error = err;
160
158
  ${w}.error.publish(__apm$ctx);
@@ -163,6 +161,7 @@
163
161
  __apm$ctx.self ??= this;
164
162
  ${w}.end.publish(__apm$ctx);
165
163
  }
164
+ return __apm$ctx.result;
166
165
  });
167
166
  }
168
167
  `)}function declareIteratorChannel(i,s){let{channelName:k,module:{name:S}}=i,w=formatChannelVariable(k+":next");if(s.body.some(i=>i.declarations?.[0]?.id?.name===w))return;let C=formatChannelVariable(k),T=s.body.findIndex(i=>i.declarations?.[0]?.id?.name===C),_=`const ${w} = tr_ch_apm_tracingChannel("orchestrion:${S}:${k}:next")`;s.body.splice(T+1,0,E(_).body[0])}function generateIterPatch(i,s,k){let{channelName:S}=i,E=formatChannelVariable(S+":next");return declareIteratorChannel(i,k),`