dd-trace 5.108.0 → 5.110.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 (174) hide show
  1. package/ci/init.js +4 -3
  2. package/index.d.ts +88 -25
  3. package/loader-hook.mjs +88 -2
  4. package/package.json +19 -14
  5. package/packages/datadog-esbuild/index.js +26 -0
  6. package/packages/datadog-esbuild/src/utils.js +46 -3
  7. package/packages/datadog-instrumentations/src/ai.js +43 -48
  8. package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js-context-methods.js +18 -0
  9. package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js.js +111 -0
  10. package/packages/datadog-instrumentations/src/aws-sdk.js +3 -1
  11. package/packages/datadog-instrumentations/src/connect.js +4 -3
  12. package/packages/datadog-instrumentations/src/electron.js +1 -1
  13. package/packages/datadog-instrumentations/src/fastify.js +4 -12
  14. package/packages/datadog-instrumentations/src/graphql.js +17 -6
  15. package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +81 -8
  16. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  17. package/packages/datadog-instrumentations/src/helpers/instrument.js +27 -2
  18. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +65 -0
  19. package/packages/datadog-instrumentations/src/helpers/register.js +6 -4
  20. package/packages/datadog-instrumentations/src/helpers/require-optional-peer.js +17 -0
  21. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/aws-durable-execution-sdk-js.js +31 -0
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +18 -2
  24. package/packages/datadog-instrumentations/src/http/client.js +12 -2
  25. package/packages/datadog-instrumentations/src/ioredis.js +0 -1
  26. package/packages/datadog-instrumentations/src/iovalkey.js +1 -2
  27. package/packages/datadog-instrumentations/src/koa.js +3 -2
  28. package/packages/datadog-instrumentations/src/mariadb.js +4 -2
  29. package/packages/datadog-instrumentations/src/mocha/main.js +3 -3
  30. package/packages/datadog-instrumentations/src/next.js +44 -10
  31. package/packages/datadog-instrumentations/src/openai.js +77 -18
  32. package/packages/datadog-instrumentations/src/otel-sdk-trace.js +7 -6
  33. package/packages/datadog-instrumentations/src/playwright.js +1 -1
  34. package/packages/datadog-instrumentations/src/redis.js +0 -1
  35. package/packages/datadog-instrumentations/src/restify.js +4 -3
  36. package/packages/datadog-instrumentations/src/router.js +18 -5
  37. package/packages/datadog-instrumentations/src/selenium.js +1 -2
  38. package/packages/datadog-instrumentations/src/tedious.js +28 -0
  39. package/packages/datadog-instrumentations/src/vitest.js +323 -7
  40. package/packages/datadog-plugin-avsc/src/schema_iterator.js +2 -2
  41. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/checkpoint.js +31 -0
  42. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/client.js +55 -0
  43. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +114 -0
  44. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +128 -0
  45. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/index.js +19 -0
  46. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/trace-checkpoint.js +224 -0
  47. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +43 -0
  48. package/packages/datadog-plugin-aws-sdk/src/base.js +14 -16
  49. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +43 -6
  50. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +100 -37
  51. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +108 -99
  52. package/packages/datadog-plugin-bullmq/src/filter.js +35 -0
  53. package/packages/datadog-plugin-bullmq/src/producer.js +84 -4
  54. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +9 -1
  55. package/packages/datadog-plugin-fs/src/index.js +1 -0
  56. package/packages/datadog-plugin-http/src/client.js +8 -3
  57. package/packages/datadog-plugin-http2/src/client.js +5 -2
  58. package/packages/datadog-plugin-mysql/src/index.js +7 -2
  59. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +2 -2
  60. package/packages/datadog-plugin-redis/src/index.js +1 -2
  61. package/packages/datadog-plugin-undici/src/index.js +5 -2
  62. package/packages/datadog-plugin-vitest/src/index.js +11 -2
  63. package/packages/datadog-webpack/index.js +17 -1
  64. package/packages/datadog-webpack/src/optional-peer-loader.js +17 -0
  65. package/packages/dd-trace/src/aiguard/channels.js +0 -1
  66. package/packages/dd-trace/src/aiguard/index.js +11 -49
  67. package/packages/dd-trace/src/aiguard/integrations/evaluate.js +46 -0
  68. package/packages/dd-trace/src/aiguard/integrations/openai.js +66 -0
  69. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +78 -0
  70. package/packages/{datadog-instrumentations/src/helpers/ai-messages.js → dd-trace/src/aiguard/messages/openai.js} +85 -193
  71. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +185 -0
  72. package/packages/dd-trace/src/aiguard/sdk.js +13 -1
  73. package/packages/dd-trace/src/appsec/api_security/index.js +55 -0
  74. package/packages/dd-trace/src/appsec/api_security/sampler.js +147 -0
  75. package/packages/dd-trace/src/appsec/channels.js +1 -0
  76. package/packages/dd-trace/src/appsec/downstream_requests.js +111 -58
  77. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/ldap-sensitive-analyzer.js +54 -12
  78. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +5 -1
  79. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +29 -4
  80. package/packages/dd-trace/src/appsec/index.js +10 -6
  81. package/packages/dd-trace/src/appsec/rasp/ssrf.js +19 -11
  82. package/packages/dd-trace/src/appsec/reporter.js +1 -0
  83. package/packages/dd-trace/src/appsec/telemetry/api_security.js +34 -0
  84. package/packages/dd-trace/src/appsec/telemetry/index.js +26 -0
  85. package/packages/dd-trace/src/ci-visibility/requests/request.js +1 -17
  86. package/packages/dd-trace/src/config/defaults.js +22 -3
  87. package/packages/dd-trace/src/config/generated-config-types.d.ts +538 -1
  88. package/packages/dd-trace/src/config/helper.js +133 -33
  89. package/packages/dd-trace/src/config/index.js +4 -1
  90. package/packages/dd-trace/src/config/parsers.js +19 -1
  91. package/packages/dd-trace/src/config/supported-configurations.json +45 -5
  92. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +49 -35
  93. package/packages/dd-trace/src/debugger/devtools_client/index.js +31 -66
  94. package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +83 -0
  95. package/packages/dd-trace/src/debugger/devtools_client/state.js +1 -0
  96. package/packages/dd-trace/src/debugger/index.js +5 -0
  97. package/packages/dd-trace/src/debugger/probe_sampler.js +112 -0
  98. package/packages/dd-trace/src/debugger/probe_sampler_constants.js +19 -0
  99. package/packages/dd-trace/src/dogstatsd.js +15 -8
  100. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +77 -26
  101. package/packages/dd-trace/src/exporters/agentless/index.js +7 -5
  102. package/packages/dd-trace/src/exporters/agentless/intake.js +43 -0
  103. package/packages/dd-trace/src/exporters/agentless/writer.js +5 -4
  104. package/packages/dd-trace/src/exporters/common/request.js +1 -20
  105. package/packages/dd-trace/src/exporters/common/url.js +34 -0
  106. package/packages/dd-trace/src/exporters/span-stats/writer.js +1 -3
  107. package/packages/dd-trace/src/guardrails/index.js +8 -5
  108. package/packages/dd-trace/src/index.js +6 -4
  109. package/packages/dd-trace/src/llmobs/constants/tags.js +6 -0
  110. package/packages/dd-trace/src/llmobs/index.js +16 -5
  111. package/packages/dd-trace/src/llmobs/plugins/genai/util.js +3 -3
  112. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +5 -1
  113. package/packages/dd-trace/src/llmobs/sdk.js +5 -3
  114. package/packages/dd-trace/src/llmobs/span_processor.js +4 -0
  115. package/packages/dd-trace/src/llmobs/tagger.js +55 -0
  116. package/packages/dd-trace/src/llmobs/telemetry.js +1 -0
  117. package/packages/dd-trace/src/log/index.js +18 -20
  118. package/packages/dd-trace/src/openfeature/flagging_provider.js +3 -1
  119. package/packages/dd-trace/src/opentelemetry/bridge-span-base.js +5 -4
  120. package/packages/dd-trace/src/opentelemetry/metrics/instruments.js +2 -1
  121. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +4 -3
  122. package/packages/dd-trace/src/opentelemetry/metrics/time.js +19 -0
  123. package/packages/dd-trace/src/opentelemetry/otlp/common.proto +2 -2
  124. package/packages/dd-trace/src/opentelemetry/otlp/logs.proto +1 -1
  125. package/packages/dd-trace/src/opentelemetry/otlp/logs_service.proto +1 -1
  126. package/packages/dd-trace/src/opentelemetry/otlp/metrics.proto +1 -1
  127. package/packages/dd-trace/src/opentelemetry/otlp/metrics_service.proto +1 -1
  128. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +11 -4
  129. package/packages/dd-trace/src/opentelemetry/span-helpers.js +29 -13
  130. package/packages/dd-trace/src/opentelemetry/span.js +14 -3
  131. package/packages/dd-trace/src/opentelemetry/span_context.js +1 -0
  132. package/packages/dd-trace/src/opentelemetry/trace/index.js +2 -1
  133. package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +3 -2
  134. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +26 -13
  135. package/packages/dd-trace/src/opentracing/propagation/text_map.js +3 -3
  136. package/packages/dd-trace/src/opentracing/span_context.js +17 -0
  137. package/packages/dd-trace/src/plugin_manager.js +7 -1
  138. package/packages/dd-trace/src/plugins/ci_plugin.js +36 -3
  139. package/packages/dd-trace/src/plugins/index.js +3 -0
  140. package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +276 -0
  141. package/packages/dd-trace/src/plugins/util/test.js +80 -0
  142. package/packages/dd-trace/src/plugins/util/url.js +68 -1
  143. package/packages/dd-trace/src/plugins/util/web.js +19 -36
  144. package/packages/dd-trace/src/priority_sampler.js +1 -13
  145. package/packages/dd-trace/src/profiling/config.js +129 -198
  146. package/packages/dd-trace/src/profiling/constants.js +4 -7
  147. package/packages/dd-trace/src/profiling/exporter_cli.js +10 -10
  148. package/packages/dd-trace/src/profiling/exporters/agent.js +23 -16
  149. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +45 -14
  150. package/packages/dd-trace/src/profiling/exporters/file.js +16 -5
  151. package/packages/dd-trace/src/profiling/oom.js +71 -0
  152. package/packages/dd-trace/src/profiling/profiler.js +35 -23
  153. package/packages/dd-trace/src/profiling/profilers/events.js +14 -7
  154. package/packages/dd-trace/src/profiling/profilers/space.js +27 -18
  155. package/packages/dd-trace/src/profiling/profilers/wall.js +35 -25
  156. package/packages/dd-trace/src/ritm.js +4 -2
  157. package/packages/dd-trace/src/serverless.js +1 -2
  158. package/packages/dd-trace/src/service-naming/schemas/util.js +36 -5
  159. package/packages/dd-trace/src/service-naming/schemas/v0/serverless.js +12 -0
  160. package/packages/dd-trace/src/service-naming/schemas/v1/serverless.js +12 -0
  161. package/packages/dd-trace/src/service-naming/schemas/v1/web.js +5 -3
  162. package/packages/dd-trace/src/span_format.js +2 -1
  163. package/packages/dd-trace/src/span_processor.js +6 -0
  164. package/packages/dd-trace/src/telemetry/send-data.js +12 -10
  165. package/packages/dd-trace/src/tracer.js +15 -1
  166. package/packages/dd-trace/src/util.js +14 -0
  167. package/register.js +58 -1
  168. package/vendor/dist/@apm-js-collab/code-transformer/index.js +1 -1
  169. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  170. package/vendor/dist/pprof-format/index.js +1 -1
  171. package/vendor/dist/protobufjs/index.js +1 -1
  172. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  173. package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +0 -284
  174. package/packages/dd-trace/src/appsec/api_security_sampler.js +0 -117
@@ -2,29 +2,33 @@
2
2
 
3
3
  const { channel, tracingChannel } = require('dc-polyfill')
4
4
  const shimmer = require('../../datadog-shimmer')
5
- const { convertVercelPromptToMessages, buildOutputMessages } = require('./helpers/ai-messages')
6
5
  const { addHook, getHooks } = require('./helpers/instrument')
7
6
 
8
7
  const vercelAiTracingChannel = tracingChannel('dd-trace:vercel-ai')
9
8
  const vercelAiSpanSetAttributesChannel = channel('dd-trace:vercel-ai:span:setAttributes')
10
- const aiguardChannel = channel('dd-trace:ai:aiguard')
9
+ const doGenerateBeforeChannel = channel('dd-trace:vercel-ai:doGenerate:before')
10
+ const doGenerateAfterChannel = channel('dd-trace:vercel-ai:doGenerate:after')
11
+ const doStreamBeforeChannel = channel('dd-trace:vercel-ai:doStream:before')
12
+ const doStreamAfterChannel = channel('dd-trace:vercel-ai:doStream:after')
11
13
 
12
14
  const tracers = new WeakSet()
13
15
  const wrappedModels = new WeakSet()
14
16
 
15
17
  /**
16
- * Publishes already-converted AI-style messages to the AI Guard evaluation channel.
18
+ * Publishes a provider-native lifecycle payload to a cancelable lifecycle channel.
17
19
  *
18
- * Subscribers push async work into `pending` and abort `abortController` to block.
20
+ * Subscribers push async work into `pending` synchronously during publication and
21
+ * abort `abortController` with an error before the pushed promise resolves to block.
19
22
  *
20
- * @param {Array<object>} messages - AI-style messages to evaluate.
23
+ * @param {object} lifecycleChannel
24
+ * @param {object} payload
21
25
  * @returns {Promise<void>}
22
26
  */
23
- function publishEvaluation (messages) {
27
+ function publishLifecycle (lifecycleChannel, payload) {
24
28
  const abortController = new AbortController()
25
- const ctx = { messages, integration: 'ai', abortController, pending: [] }
29
+ const ctx = { ...payload, abortController, pending: [] }
26
30
 
27
- aiguardChannel.publish(ctx)
31
+ lifecycleChannel.publish(ctx)
28
32
 
29
33
  return Promise.all(ctx.pending).then(() => {
30
34
  if (abortController.signal.aborted) {
@@ -34,12 +38,11 @@ function publishEvaluation (messages) {
34
38
  }
35
39
 
36
40
  /**
37
- * Wraps a Vercel AI language model's doGenerate and doStream methods to evaluate
38
- * messages with AIGuard.
41
+ * Wraps a Vercel AI language model's doGenerate and doStream lifecycle methods.
39
42
  *
40
43
  * @param {object} model - A Vercel AI language model instance
41
44
  */
42
- function wrapModelWithAIGuard (model) {
45
+ function wrapModelWithLifecycle (model) {
43
46
  if (!model || wrappedModels.has(model)) return
44
47
  wrappedModels.add(model)
45
48
 
@@ -48,20 +51,18 @@ function wrapModelWithAIGuard (model) {
48
51
  return function (options) {
49
52
  const originalResult = original.call(this, options)
50
53
 
51
- if (!aiguardChannel.hasSubscribers) return originalResult
54
+ if (!doGenerateBeforeChannel.hasSubscribers && !doGenerateAfterChannel.hasSubscribers) return originalResult
52
55
  if (!options.prompt?.length) return originalResult
53
56
 
54
- const inputMessages = convertVercelPromptToMessages(options.prompt)
55
- if (!inputMessages.length) return originalResult
57
+ const beforeEvaluation = doGenerateBeforeChannel.hasSubscribers
58
+ ? publishLifecycle(doGenerateBeforeChannel, { prompt: options.prompt, options })
59
+ : Promise.resolve()
56
60
 
57
- // Run AI Guard input evaluation and LLM call in parallel.
58
- // The LLM has no side effects so it is safe to discard its result if AI Guard blocks.
59
- return Promise.all([publishEvaluation(inputMessages), originalResult])
61
+ return Promise.all([beforeEvaluation, originalResult])
60
62
  .then(([, result]) => {
61
- if (!result.content?.length) return result
62
- const outputMessages = buildOutputMessages(inputMessages, result.content)
63
- return publishEvaluation(outputMessages)
64
- .then(() => result)
63
+ if (!doGenerateAfterChannel.hasSubscribers || !result.content?.length) return result
64
+ const payload = { prompt: options.prompt, options, result }
65
+ return publishLifecycle(doGenerateAfterChannel, payload).then(() => result)
65
66
  })
66
67
  }
67
68
  })
@@ -72,16 +73,17 @@ function wrapModelWithAIGuard (model) {
72
73
  return function (options) {
73
74
  const originalResult = original.call(this, options)
74
75
 
75
- if (!aiguardChannel.hasSubscribers) return originalResult
76
+ if (!doStreamBeforeChannel.hasSubscribers && !doStreamAfterChannel.hasSubscribers) return originalResult
76
77
  if (!options.prompt?.length) return originalResult
77
78
 
78
- const inputMessages = convertVercelPromptToMessages(options.prompt)
79
- if (!inputMessages.length) return originalResult
79
+ const beforeEvaluation = doStreamBeforeChannel.hasSubscribers
80
+ ? publishLifecycle(doStreamBeforeChannel, { prompt: options.prompt, options })
81
+ : Promise.resolve()
80
82
 
81
- // Run AI Guard input evaluation and LLM call in parallel.
82
- // The LLM has no side effects so it is safe to discard its result if AI Guard blocks.
83
- return Promise.all([publishEvaluation(inputMessages), originalResult])
83
+ return Promise.all([beforeEvaluation, originalResult])
84
84
  .then(([, result]) => {
85
+ if (!doStreamAfterChannel.hasSubscribers) return result
86
+
85
87
  const chunks = []
86
88
  const reader = result.stream.getReader()
87
89
 
@@ -94,26 +96,19 @@ function wrapModelWithAIGuard (model) {
94
96
  }
95
97
 
96
98
  return readAll().then(() => {
97
- const toolCalls = chunks.filter(c => c?.type === 'tool-call')
98
- const text = chunks.filter(c => c?.type === 'text-delta').map(c => c.textDelta).join('')
99
- const content = toolCalls.length ? toolCalls : text ? [{ type: 'text', text }] : []
100
-
101
- const evaluate = content.length
102
- ? publishEvaluation(buildOutputMessages(inputMessages, content))
103
- : Promise.resolve()
104
-
105
- return evaluate.then(() => {
106
- // eslint-disable-next-line n/no-unsupported-features/node-builtins
107
- const stream = new ReadableStream({
108
- start (controller) {
109
- for (const chunk of chunks) {
110
- controller.enqueue(chunk)
111
- }
112
- controller.close()
113
- },
99
+ return publishLifecycle(doStreamAfterChannel, { prompt: options.prompt, options, chunks })
100
+ .then(() => {
101
+ // eslint-disable-next-line n/no-unsupported-features/node-builtins
102
+ const stream = new ReadableStream({
103
+ start (controller) {
104
+ for (const chunk of chunks) {
105
+ controller.enqueue(chunk)
106
+ }
107
+ controller.close()
108
+ },
109
+ })
110
+ return { ...result, stream }
114
111
  })
115
- return { ...result, stream }
116
- })
117
112
  })
118
113
  })
119
114
  }
@@ -230,7 +225,7 @@ for (const hook of getHooks('ai')) {
230
225
  // generateObject, streamObject)
231
226
  tracingChannel('orchestrion:ai:resolveLanguageModel').subscribe({
232
227
  end (ctx) {
233
- wrapModelWithAIGuard(ctx.result)
228
+ wrapModelWithLifecycle(ctx.result)
234
229
  },
235
230
  })
236
231
 
@@ -238,4 +233,4 @@ for (const hook of getHooks('ai')) {
238
233
  })
239
234
  }
240
235
 
241
- module.exports = { wrapModelWithAIGuard }
236
+ module.exports = { wrapModelWithLifecycle }
@@ -0,0 +1,18 @@
1
+ 'use strict'
2
+
3
+ // Methods on `DurableContextImpl` that return a lazy DurablePromise. The rewriter
4
+ // instrumentation generates a `kind: 'Sync'` Orchestrion hook for each so Orchestrion
5
+ // does not eagerly side-chain `.then()`; the runtime instrumentation side-chains the
6
+ // returned DurablePromise itself and publishes a `:settle` channel once it settles,
7
+ // preserving the SDK's lazy semantics. Shared between both so the lists cannot drift.
8
+ module.exports = [
9
+ 'step',
10
+ 'invoke',
11
+ 'runInChildContext',
12
+ 'wait',
13
+ 'waitForCondition',
14
+ 'waitForCallback',
15
+ 'createCallback',
16
+ 'map',
17
+ 'parallel',
18
+ ]
@@ -0,0 +1,111 @@
1
+ 'use strict'
2
+
3
+ const { channel, tracingChannel } = require('dc-polyfill')
4
+ const shimmer = require('../../datadog-shimmer')
5
+ const { addHook, getHooks } = require('./helpers/instrument')
6
+ const LAZY_DURABLE_PROMISE_METHODS = require('./aws-durable-execution-sdk-js-context-methods')
7
+
8
+ for (const method of LAZY_DURABLE_PROMISE_METHODS) {
9
+ const orchestrionCh = tracingChannel(`orchestrion:@aws/durable-execution-sdk-js:DurableContextImpl_${method}`)
10
+ const settleCh = channel(`apm:aws-durable-execution-sdk-js:${method}:settle`)
11
+
12
+ orchestrionCh.end.subscribe(ctx => {
13
+ if (!settleCh.hasSubscribers) return
14
+ observeDurablePromise(ctx.result, error => {
15
+ if (error !== undefined) ctx.error = error
16
+ settleCh.publish(ctx)
17
+ })
18
+ })
19
+ }
20
+
21
+ // runHandler(event, context, executionContext, mode, checkpointToken, handler) creates the
22
+ // DurableContext internally and passes it to the user handler, and drives suspension through
23
+ // executionContext.terminationManager.terminate(). The cross-invocation checkpoint plugin needs
24
+ // both: the DurableContext (to reach the checkpoint manager) and a signal that the execution is
25
+ // suspending. Capture the former off the handler call and surface the latter on a channel.
26
+ const HANDLER_ARG_INDEX = 5
27
+ const EXECUTION_CONTEXT_ARG_INDEX = 2
28
+
29
+ const withDurableExecutionCh = tracingChannel('orchestrion:@aws/durable-execution-sdk-js:withDurableExecution')
30
+ const terminateCh = channel('apm:aws-durable-execution-sdk-js:terminate')
31
+
32
+ withDurableExecutionCh.start.subscribe(ctx => {
33
+ // Only wrap while the plugin is listening (cross-invocation tracing enabled).
34
+ if (!terminateCh.hasSubscribers) return
35
+
36
+ // ctx.arguments is an array-like `arguments` object, not a true Array.
37
+ const args = ctx.arguments
38
+ if (!args) return
39
+
40
+ if (typeof args[HANDLER_ARG_INDEX] === 'function') {
41
+ shimmer.wrap(args, HANDLER_ARG_INDEX, handler => function (event, durableContext) {
42
+ ctx.durableContext = durableContext
43
+ return handler.apply(this, arguments)
44
+ })
45
+ }
46
+
47
+ const terminationManager = args[EXECUTION_CONTEXT_ARG_INDEX]?.terminationManager
48
+ if (typeof terminationManager?.terminate === 'function') {
49
+ shimmer.wrap(terminationManager, 'terminate', terminate => function (options) {
50
+ // Publish before the original runs so the plugin can enqueue the checkpoint while the
51
+ // checkpoint manager is still accepting writes (it flips to "terminating" inside terminate()).
52
+ ctx.terminationReason = options?.reason
53
+ terminateCh.publish(ctx)
54
+ return terminate.apply(this, arguments)
55
+ })
56
+ }
57
+ })
58
+
59
+ // Per-instance settle callback read by the shared prototype wrappers installed by
60
+ // `instrumentDurablePromiseProto`. Stored on the DurablePromise instance so the prototype
61
+ // wrappers stay allocation-free and the promise carries its own observer.
62
+ const ON_SETTLE = Symbol('_dd.durableExecution.onSettle')
63
+
64
+ const instrumentedProtos = new WeakSet()
65
+
66
+ /**
67
+ * Wraps `then`/`catch`/`finally` on a DurablePromise prototype once so every instance shares
68
+ * the same wrappers instead of being re-wrapped individually. The wrappers side-chain a settle
69
+ * observer the first time user code chains off the promise, then clear `ON_SETTLE` so the
70
+ * observer attaches at most once (a promise settles only once). Instances we don't observe
71
+ * never set `ON_SETTLE`, so for them the wrappers are a single property read and a passthrough.
72
+ * @param {object} proto - The DurablePromise prototype (`Object.getPrototypeOf(dp)`).
73
+ * @returns {void}
74
+ */
75
+ function instrumentDurablePromiseProto (proto) {
76
+ if (instrumentedProtos.has(proto)) return
77
+ instrumentedProtos.add(proto)
78
+
79
+ // Capture the genuine `.then` before wrapping so the settle observer side-chains without
80
+ // recursing back into the wrapper below.
81
+ const originalThen = proto.then
82
+
83
+ shimmer.massWrap(proto, ['then', 'catch', 'finally'], original => function (...args) {
84
+ const onSettle = this[ON_SETTLE]
85
+ if (onSettle !== undefined) {
86
+ this[ON_SETTLE] = undefined
87
+ originalThen.call(this, () => onSettle(), err => onSettle(err))
88
+ }
89
+ return original.apply(this, args)
90
+ })
91
+ }
92
+
93
+ /**
94
+ * Registers a settle observer on a returned DurablePromise. Callers pair `kind: 'Sync'` with this
95
+ * helper so `onSettle` only fires after user code first awaits / chains, preserving the SDK's
96
+ * lazy semantics.
97
+ * @param {object} dp - The returned DurablePromise instance.
98
+ * @param {(err: unknown) => void} onSettle - Called once with `undefined` on success or the
99
+ * rejection reason on failure.
100
+ * @returns {void}
101
+ */
102
+ function observeDurablePromise (dp, onSettle) {
103
+ if (!dp || typeof dp.then !== 'function') return
104
+ instrumentDurablePromiseProto(Object.getPrototypeOf(dp))
105
+ dp[ON_SETTLE] = onSettle
106
+ }
107
+
108
+ for (const hook of getHooks('@aws/durable-execution-sdk-js')) {
109
+ hook.file = null
110
+ addHook(hook, exports => exports)
111
+ }
@@ -150,6 +150,7 @@ function wrapDeserialize (deserialize, headersCh, responseIndex = 0) {
150
150
  function wrapSmithySend (send) {
151
151
  return function (command, ...args) {
152
152
  const cb = args.at(-1)
153
+ const cbExists = typeof cb === 'function'
153
154
  const serviceIdentifier = this.config.serviceId.toLowerCase()
154
155
  const channelSuffix = getChannelSuffix(serviceIdentifier)
155
156
  const channels = getChannelBag(channelSuffix)
@@ -188,6 +189,7 @@ function wrapSmithySend (send) {
188
189
  operation,
189
190
  awsService: clientName,
190
191
  request,
192
+ cbExists,
191
193
  }
192
194
 
193
195
  return channels.start.runStores(ctx, () => {
@@ -197,7 +199,7 @@ function wrapSmithySend (send) {
197
199
  channels.region.publish(ctx)
198
200
  })
199
201
 
200
- if (typeof cb === 'function') {
202
+ if (cbExists) {
201
203
  args[args.length - 1] = shimmer.wrapCallback(cb, cb => function (err, result) {
202
204
  addResponse(ctx, err, result)
203
205
 
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const shimmer = require('../../datadog-shimmer')
4
- const { addHook, channel } = require('./helpers/instrument')
4
+ const { addHook, channel, createErrorPublisher } = require('./helpers/instrument')
5
5
 
6
6
  const enterChannel = channel('apm:connect:middleware:enter')
7
7
  const exitChannel = channel('apm:connect:middleware:exit')
@@ -9,6 +9,7 @@ const errorChannel = channel('apm:connect:middleware:error')
9
9
  const nextChannel = channel('apm:connect:middleware:next')
10
10
  const finishChannel = channel('apm:connect:middleware:finish')
11
11
  const handleChannel = channel('apm:connect:request:handle')
12
+ const publishError = createErrorPublisher(errorChannel)
12
13
 
13
14
  function wrapConnect (connect) {
14
15
  if (typeof connect !== 'function') return connect
@@ -78,7 +79,7 @@ function wrapLayerHandle (layer) {
78
79
  try {
79
80
  return original.apply(this, args)
80
81
  } catch (error) {
81
- errorChannel.publish({ req, error })
82
+ publishError({ req, error })
82
83
  nextChannel.publish({ req })
83
84
  finishChannel.publish({ req })
84
85
 
@@ -93,7 +94,7 @@ function wrapNext (req, next) {
93
94
  // Mirror next's name/arity so wrapCallback skips its per-call identity rewrite.
94
95
  return shimmer.wrapCallback(next, original => function next (error) {
95
96
  if (error) {
96
- errorChannel.publish({ req, error })
97
+ publishError({ req, error })
97
98
  }
98
99
 
99
100
  nextChannel.publish({ req })
@@ -218,7 +218,7 @@ addHook({ name: 'electron', versions: ['>=37.0.0'] }, electron => {
218
218
  wrap(ipcRenderer, 'removeAllListeners', createWrapRemoveAllListeners(listeners))
219
219
 
220
220
  ipcRenderer.send('datadog:apm:renderer:patched')
221
- } else {
221
+ } else if (ipcMain) {
222
222
  wrap(ipcMain, 'addListener', createWrapAddListener(mainReceiveCh, listeners))
223
223
  wrap(ipcMain, 'handle', createWrapAddListener(mainHandleCh, handlers))
224
224
  wrap(ipcMain, 'handleOnce', createWrapAddListener(mainHandleCh, handlers))
@@ -1,9 +1,10 @@
1
1
  'use strict'
2
2
 
3
3
  const shimmer = require('../../datadog-shimmer')
4
- const { addHook, channel } = require('./helpers/instrument')
4
+ const { addHook, channel, createErrorPublisher } = require('./helpers/instrument')
5
5
 
6
6
  const errorChannel = channel('apm:fastify:middleware:error')
7
+ const publishErrorChannel = createErrorPublisher(errorChannel)
7
8
  const handleChannel = channel('apm:fastify:request:handle')
8
9
  const routeAddedChannel = channel('apm:fastify:route:added')
9
10
  const bodyParserReadCh = channel('datadog:fastify:body-parser:finish')
@@ -303,18 +304,9 @@ function getRouteConfig (request) {
303
304
  return request?.routeOptions?.config
304
305
  }
305
306
 
306
- let publishingError = false
307
-
308
307
  function publishError (ctx) {
309
- // `errorChannel` is public: a subscriber that re-enters the hook pipeline while
310
- // handling the error republishes here and recurses until the stack overflows.
311
- if (ctx.error && !publishingError) {
312
- publishingError = true
313
- try {
314
- errorChannel.publish(ctx)
315
- } finally {
316
- publishingError = false
317
- }
308
+ if (ctx.error) {
309
+ publishErrorChannel(ctx)
318
310
  }
319
311
 
320
312
  return ctx.error
@@ -455,15 +455,26 @@ function finishResolvers ({ fields }) {
455
455
  }
456
456
  }
457
457
 
458
- addHook({ name: '@graphql-tools/executor', versions: ['>=0.0.14'] }, executor => {
459
- // graphql-yoga uses the normalizedExecutor function, so we need to wrap both. There is no risk in wrapping both
460
- // since the functions are closely related, and our wrappedExecute function prevents double calls with the
461
- // contexts.has(contextValue) check.
462
- shimmer.wrap(executor, 'execute', wrapExecute(executor))
463
- shimmer.wrap(executor, 'normalizedExecutor', wrapExecute(executor))
458
+ // The CJS package root re-exports `execute` and `normalizedExecutor` as
459
+ // non-configurable tslib `__exportStar` getters that shimmer cannot rewrite in
460
+ // place, so the inner execute.js hook below carries CJS instead: graphql-yoga
461
+ // (`normalizedExecutor`) and direct callers both funnel through that `execute`.
462
+ // Under iitm (ESM) the namespace is settable and the inner-file hook never
463
+ // matches the `esm/` path, so the root wrap is the only option there.
464
+ addHook({ name: '@graphql-tools/executor', versions: ['>=0.0.14'] }, (executor, _version, isIitm) => {
465
+ /* istanbul ignore if: covered only by the graphql-yoga ESM integration test (subprocess) */
466
+ if (isIitm) {
467
+ shimmer.wrap(executor, 'execute', wrapExecute(executor))
468
+ shimmer.wrap(executor, 'normalizedExecutor', wrapExecute(executor))
469
+ }
464
470
  return executor
465
471
  })
466
472
 
473
+ addHook({ name: '@graphql-tools/executor', file: 'cjs/execution/execute.js', versions: ['>=0.0.14'] }, execute => {
474
+ shimmer.wrap(execute, 'execute', wrapExecute(execute))
475
+ return execute
476
+ })
477
+
467
478
  // TODO(BridgeAR): graphql >=17.0.0-alpha.9 routes execute() through
468
479
  // experimentalExecuteIncrementally(), bypassing this hook. The same
469
480
  // function returns { initialResult, subsequentResults } for @defer /
@@ -1,8 +1,9 @@
1
1
  'use strict'
2
2
 
3
- // This code runs before the tracer is configured and before a logger is ready
4
- // For that reason we queue up the messages now and decide what to do with them later
5
- const warnings = []
3
+ // Queued before the logger is ready; flushed once the tracer knows its config.
4
+ const warnings = [] // conflicts; flushed under DD_TRACE_DEBUG
5
+ const loadOrderWarnings = [] // "loaded before dd-trace"; flushed under startupLogs
6
+ const frameworkWarnings = [] // curated (e.g. Next.js); flushed unconditionally
6
7
 
7
8
  /**
8
9
  * Here we maintain a list of packages that an application
@@ -28,6 +29,24 @@ const potentialConflicts = new Set([
28
29
 
29
30
  const extractPackageAndModulePath = require('./extract-package-and-module-path')
30
31
 
32
+ /**
33
+ * Frameworks that load their own server code before any user code (and thus
34
+ * before a late `tracer.init()`) can run. When their server module is already
35
+ * in `require.cache` at init time, dd-trace was loaded too late to instrument
36
+ * them and the integration silently no-ops. Unlike the broad scan below, this
37
+ * set is high-signal enough to warn on by default. `file` is the module whose
38
+ * presence proves the server is already loaded; `guidance` is the bundler note
39
+ * appended to the warning.
40
+ */
41
+ const earlyLoadFrameworks = new Map([
42
+ ['next', {
43
+ // dist/server/next-server.js (>=11.1), dist/next-server/server/next-server.js (older)
44
+ file: 'next-server.js',
45
+ guidance: "add 'dd-trace' to `serverExternalPackages` (Next.js >=15) or " +
46
+ '`experimental.serverComponentsExternalPackages` (13-14) so it is not bundled',
47
+ }],
48
+ ])
49
+
31
50
  /**
32
51
  * The lowest hanging fruit to debug an app that isn't tracing
33
52
  * properly is to check that it is loaded before any modules
@@ -43,25 +62,53 @@ const extractPackageAndModulePath = require('./extract-package-and-module-path')
43
62
  * app loads a package we instrument but outside of an
44
63
  * unsupported version then a warning would still be displayed.
45
64
  * This is OK as the tracer should be loaded earlier anyway.
65
+ *
66
+ * Curated frameworks (see `earlyLoadFrameworks`) surface unconditionally; the
67
+ * broad list of packages loaded before dd-trace is queued for the startupLogs-
68
+ * gated flush (`flushLoadOrderWarnings`).
46
69
  */
47
70
  module.exports.checkForRequiredModules = function () {
48
71
  const packages = require('./hooks')
49
72
  const naughties = new Set()
73
+ const frameworksSeen = new Set()
50
74
  let didWarn = false
51
75
 
52
76
  for (const pathToModule of Object.keys(require.cache)) {
53
- const { pkg } = extractPackageAndModulePath(pathToModule)
77
+ // require.cache keys use the platform separator; normalize so the
78
+ // `node_modules/<pkg>` parsing works on Windows (backslash paths).
79
+ const { pkg, path } = extractPackageAndModulePath(pathToModule.replaceAll('\\', '/'))
54
80
 
55
- if (naughties.has(pkg)) continue
56
- if (!(pkg in packages)) continue
81
+ if (pkg === null) continue
82
+
83
+ // A curated framework loads its own server before user code, so its server
84
+ // module being cached means dd-trace was too late to instrument it. These
85
+ // surface unconditionally (see flushFrameworkWarnings) with an actionable
86
+ // message, so they never fall through to the broad load-order list below.
87
+ const framework = earlyLoadFrameworks.get(pkg)
88
+ if (framework !== undefined) {
89
+ if (!frameworksSeen.has(pkg) && path?.endsWith(framework.file)) {
90
+ frameworksSeen.add(pkg)
91
+ frameworkWarnings.push(
92
+ `'${pkg}' was loaded before dd-trace, so the ${pkg} integration will not be applied. ` +
93
+ 'Initialize dd-trace before your application starts — ' +
94
+ "NODE_OPTIONS='--require dd-trace/init' (CommonJS) or '--import dd-trace/initialize.mjs' (ESM) — " +
95
+ `and ${framework.guidance}.`
96
+ )
97
+ }
98
+ continue
99
+ }
57
100
 
58
- warnings.push(() => `Warning: Package '${pkg}' was loaded before dd-trace! This may break instrumentation.`)
101
+ if (naughties.has(pkg) || !(pkg in packages)) continue
102
+
103
+ loadOrderWarnings.push(
104
+ () => `Warning: Package '${pkg}' was loaded before dd-trace! This may break instrumentation.`
105
+ )
59
106
 
60
107
  naughties.add(pkg)
61
108
  didWarn = true
62
109
  }
63
110
 
64
- if (didWarn) warnings.push('Warning: Please ensure dd-trace is loaded before other modules.')
111
+ if (didWarn) loadOrderWarnings.push('Warning: Please ensure dd-trace is loaded before other modules.')
65
112
  }
66
113
 
67
114
  /**
@@ -104,3 +151,29 @@ module.exports.flushStartupLogs = function (log) {
104
151
  log.warn(typeof entry === 'function' ? entry() : entry)
105
152
  }
106
153
  }
154
+
155
+ /**
156
+ * Drains the broad "loaded before dd-trace" warnings collected by
157
+ * `checkForRequiredModules`. The tracer gates this on startupLogs (these are
158
+ * startup diagnostics), unlike the unconditional `flushFrameworkWarnings`.
159
+ * @param {(message: string) => void} warn
160
+ */
161
+ module.exports.flushLoadOrderWarnings = function (warn) {
162
+ while (loadOrderWarnings.length) {
163
+ const entry = loadOrderWarnings.shift()
164
+ warn(typeof entry === 'function' ? entry() : entry)
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Drains the framework warnings collected by `checkForRequiredModules`. The
170
+ * tracer surfaces these unconditionally (not gated on startupLogs or
171
+ * DD_TRACE_DEBUG), unlike the DD_TRACE_DEBUG-only `flushStartupLogs` queue,
172
+ * because the affected users run with neither enabled (#5430 / #5432).
173
+ * @param {(message: string) => void} warn
174
+ */
175
+ module.exports.flushFrameworkWarnings = function (warn) {
176
+ while (frameworkWarnings.length) {
177
+ warn(frameworkWarnings.shift())
178
+ }
179
+ }
@@ -22,6 +22,7 @@ module.exports = {
22
22
  '@modelcontextprotocol/sdk': () => require('../modelcontextprotocol-sdk'),
23
23
  'apollo-server-core': () => require('../apollo-server-core'),
24
24
  '@aws-sdk/smithy-client': () => require('../aws-sdk'),
25
+ '@aws/durable-execution-sdk-js': () => require('../aws-durable-execution-sdk-js'),
25
26
  '@azure/cosmos': { esmFirst: true, fn: () => require('../azure-cosmos') },
26
27
  '@azure/event-hubs': () => require('../azure-event-hubs'),
27
28
  '@azure/functions': () => require('../azure-functions'),
@@ -42,6 +42,31 @@ exports.tracingChannel = function (name) {
42
42
  return tc
43
43
  }
44
44
 
45
+ /**
46
+ * Build a guarded publisher for a public error channel. A subscriber that
47
+ * re-enters the same wrapped dispatch while handling the error would otherwise
48
+ * republish here and recurse until the stack overflows. Each framework binds
49
+ * its own publisher, so the in-flight flag stays private to one channel: a
50
+ * genuinely nested error on a different framework's channel (a Koa app mounted
51
+ * inside Express) still reaches its subscribers instead of being dropped, and
52
+ * the guard costs a closure read rather than a per-publish channel lookup.
53
+ *
54
+ * @param {Channel} errorChannel
55
+ */
56
+ exports.createErrorPublisher = function createErrorPublisher (errorChannel) {
57
+ let publishing = false
58
+ /** @param {object} message */
59
+ return function publishError (message) {
60
+ if (publishing) return
61
+ publishing = true
62
+ try {
63
+ errorChannel.publish(message)
64
+ } finally {
65
+ publishing = false
66
+ }
67
+ }
68
+ }
69
+
45
70
  exports.getHooks = function getHooks (names) {
46
71
  names = [names].flat()
47
72
 
@@ -55,9 +80,9 @@ exports.getHooks = function getHooks (names) {
55
80
  * @param {object} args
56
81
  * @param {string} args.name module name
57
82
  * @param {string[]} [args.versions] array of semver range strings
58
- * @param {string} [args.file] path to file within package to instrument. Defaults to 'index.js'.
83
+ * @param {string} [args.file] path to file within package to instrument. Defaults to 'index.js'.
59
84
  * @param {string} [args.filePattern] pattern to match files within package to instrument
60
- * @param {boolean} [args.patchDefault] whether to patch the default export. Defaults to true.
85
+ * @param {boolean} [args.patchDefault] whether to patch the default export. Defaults to true.
61
86
  * @param {(moduleExports: unknown, version: string, isIitm?: boolean, hookMeta?: object) => unknown} [hook]
62
87
  * Patches module exports
63
88
  */