dd-trace 6.9.0 → 6.11.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 (175) hide show
  1. package/README.md +0 -17
  2. package/ci/vitest-no-worker-init-setup.mjs +112 -10
  3. package/index.d.ts +150 -3
  4. package/initialize.mjs +28 -17
  5. package/openfeature.js +2 -2
  6. package/package.json +14 -10
  7. package/packages/datadog-esbuild/index.js +0 -34
  8. package/packages/datadog-instrumentations/src/anthropic.js +210 -15
  9. package/packages/datadog-instrumentations/src/aws-sdk.js +9 -1
  10. package/packages/datadog-instrumentations/src/browser-bunyan.js +11 -0
  11. package/packages/datadog-instrumentations/src/bunyan.js +3 -16
  12. package/packages/datadog-instrumentations/src/confluentinc-kafka-javascript.js +111 -6
  13. package/packages/datadog-instrumentations/src/cucumber.js +26 -11
  14. package/packages/datadog-instrumentations/src/cypress-config.js +182 -16
  15. package/packages/datadog-instrumentations/src/cypress-legacy-finalizers.js +30 -0
  16. package/packages/datadog-instrumentations/src/cypress.js +2 -0
  17. package/packages/datadog-instrumentations/src/fastify.js +2 -0
  18. package/packages/datadog-instrumentations/src/helpers/bunyan.js +22 -0
  19. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -1
  20. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +634 -0
  21. package/packages/datadog-instrumentations/src/helpers/register.js +10 -8
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -1
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +81 -0
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +83 -1
  26. package/packages/datadog-instrumentations/src/jest.js +26 -18
  27. package/packages/datadog-instrumentations/src/mariadb.js +278 -21
  28. package/packages/datadog-instrumentations/src/mocha/main.js +540 -56
  29. package/packages/datadog-instrumentations/src/mocha/utils.js +132 -25
  30. package/packages/datadog-instrumentations/src/mysql.js +45 -15
  31. package/packages/datadog-instrumentations/src/mysql2.js +119 -18
  32. package/packages/datadog-instrumentations/src/path-to-regexp.js +67 -1
  33. package/packages/datadog-instrumentations/src/pg.js +116 -92
  34. package/packages/datadog-instrumentations/src/playwright-reporter.js +99 -0
  35. package/packages/datadog-instrumentations/src/playwright.js +149 -2
  36. package/packages/datadog-instrumentations/src/router.js +2 -0
  37. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +43 -4
  38. package/packages/datadog-instrumentations/src/vitest-main.js +180 -20
  39. package/packages/datadog-instrumentations/src/vitest-util.js +64 -5
  40. package/packages/datadog-instrumentations/src/vitest-worker.js +150 -25
  41. package/packages/datadog-instrumentations/src/webdriverio.js +31 -17
  42. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/trace-checkpoint.js +12 -6
  43. package/packages/datadog-plugin-aws-sdk/src/base.js +1 -0
  44. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +163 -44
  45. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +4 -13
  46. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -5
  47. package/packages/datadog-plugin-azure-functions/src/index.js +1 -1
  48. package/packages/datadog-plugin-browser-bunyan/src/index.js +9 -0
  49. package/packages/datadog-plugin-bunyan/src/index.js +5 -8
  50. package/packages/datadog-plugin-cucumber/src/index.js +11 -0
  51. package/packages/datadog-plugin-cypress/src/after-run.js +5 -1
  52. package/packages/datadog-plugin-cypress/src/after-spec.js +5 -1
  53. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +82 -30
  54. package/packages/datadog-plugin-cypress/src/finalization.js +76 -0
  55. package/packages/datadog-plugin-cypress/src/index.js +12 -6
  56. package/packages/datadog-plugin-cypress/src/plugin.js +16 -3
  57. package/packages/datadog-plugin-dd-trace-api/src/index.js +4 -0
  58. package/packages/datadog-plugin-electron/src/index.js +2 -0
  59. package/packages/datadog-plugin-fs/src/index.js +1 -1
  60. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +31 -17
  61. package/packages/datadog-plugin-jest/src/index.js +11 -4
  62. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +2 -3
  63. package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -3
  64. package/packages/datadog-plugin-kafkajs/src/producer.js +3 -2
  65. package/packages/datadog-plugin-kafkajs/src/utils.js +65 -7
  66. package/packages/datadog-plugin-mocha/src/index.js +606 -40
  67. package/packages/datadog-plugin-mysql/src/index.js +37 -0
  68. package/packages/datadog-plugin-nats/src/index.js +1 -1
  69. package/packages/datadog-plugin-next/src/index.js +1 -19
  70. package/packages/datadog-plugin-openai/src/tracing.js +41 -30
  71. package/packages/datadog-plugin-openai-agents/src/integration.js +279 -69
  72. package/packages/datadog-plugin-openai-agents/src/processor.js +16 -4
  73. package/packages/datadog-plugin-pg/src/index.js +1 -0
  74. package/packages/datadog-plugin-playwright/src/index.js +42 -13
  75. package/packages/datadog-plugin-vitest/src/index.js +14 -20
  76. package/packages/datadog-plugin-ws/src/util.js +2 -1
  77. package/packages/datadog-webpack/index.js +0 -11
  78. package/packages/dd-trace/index.js +0 -1
  79. package/packages/dd-trace/src/agent/info.js +6 -5
  80. package/packages/dd-trace/src/aiguard/client.js +100 -1
  81. package/packages/dd-trace/src/aiguard/errors.js +41 -0
  82. package/packages/dd-trace/src/aiguard/evaluation.js +357 -0
  83. package/packages/dd-trace/src/aiguard/integrations/anthropic.js +70 -0
  84. package/packages/dd-trace/src/aiguard/integrations/index.js +4 -0
  85. package/packages/dd-trace/src/aiguard/messages/anthropic.js +423 -0
  86. package/packages/dd-trace/src/aiguard/messages/openai.js +1 -24
  87. package/packages/dd-trace/src/aiguard/messages/utils.js +28 -0
  88. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +1 -11
  89. package/packages/dd-trace/src/aiguard/sdk.js +22 -278
  90. package/packages/dd-trace/src/appsec/api_security/normalized-route.js +453 -0
  91. package/packages/dd-trace/src/appsec/handlers/http-request.js +13 -0
  92. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +9 -12
  93. package/packages/dd-trace/src/carrier.js +356 -0
  94. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +1 -0
  95. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +27 -3
  96. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +22 -6
  97. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +21 -5
  98. package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +164 -0
  99. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +22 -6
  100. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +365 -32
  101. package/packages/dd-trace/src/ci-visibility/exporters/request.js +201 -0
  102. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +5 -3
  103. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -5
  104. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +2 -1
  105. package/packages/dd-trace/src/ci-visibility/requests/rate-limit.js +39 -0
  106. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -6
  107. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +6 -2
  108. package/packages/dd-trace/src/config/generated-config-types.d.ts +7 -0
  109. package/packages/dd-trace/src/config/index.js +13 -2
  110. package/packages/dd-trace/src/config/major-overrides.js +7 -0
  111. package/packages/dd-trace/src/config/supported-configurations.json +24 -0
  112. package/packages/dd-trace/src/datastreams/context.js +6 -1
  113. package/packages/dd-trace/src/datastreams/index.js +2 -0
  114. package/packages/dd-trace/src/datastreams/manager.js +14 -3
  115. package/packages/dd-trace/src/datastreams/pathway.js +23 -22
  116. package/packages/dd-trace/src/datastreams/processor.js +16 -13
  117. package/packages/dd-trace/src/datastreams/size.js +5 -0
  118. package/packages/dd-trace/src/debugger/devtools_client/index.js +3 -2
  119. package/packages/dd-trace/src/exporters/agent/writer.js +49 -9
  120. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +6 -4
  121. package/packages/dd-trace/src/exporters/common/writer.js +11 -6
  122. package/packages/dd-trace/src/llmobs/constants/tags.js +5 -0
  123. package/packages/dd-trace/src/llmobs/experiments/client.js +163 -5
  124. package/packages/dd-trace/src/llmobs/experiments/dataset.js +241 -87
  125. package/packages/dd-trace/src/llmobs/experiments/experiment.js +280 -20
  126. package/packages/dd-trace/src/llmobs/experiments/index.js +58 -59
  127. package/packages/dd-trace/src/llmobs/experiments/noop.js +71 -3
  128. package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
  129. package/packages/dd-trace/src/llmobs/experiments/util.js +41 -5
  130. package/packages/dd-trace/src/llmobs/index.js +34 -2
  131. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +12 -3
  132. package/packages/dd-trace/src/llmobs/plugins/base.js +4 -2
  133. package/packages/dd-trace/src/llmobs/span_processor.js +16 -0
  134. package/packages/dd-trace/src/llmobs/tagger.js +96 -0
  135. package/packages/dd-trace/src/llmobs/util.js +78 -0
  136. package/packages/dd-trace/src/noop/proxy.js +3 -4
  137. package/packages/dd-trace/src/openfeature/flagging_provider.js +18 -2
  138. package/packages/dd-trace/src/openfeature/remote_config.js +1 -1
  139. package/packages/dd-trace/src/opentelemetry/context_manager.js +7 -7
  140. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +2 -1
  141. package/packages/dd-trace/src/opentelemetry/logs/logger.js +67 -11
  142. package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +14 -3
  143. package/packages/dd-trace/src/opentelemetry/metrics/index.js +25 -12
  144. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +2 -5
  145. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +96 -43
  146. package/packages/dd-trace/src/opentracing/propagation/text_map.js +126 -120
  147. package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +4 -8
  148. package/packages/dd-trace/src/opentracing/tracer.js +5 -2
  149. package/packages/dd-trace/src/otel-thread-ctx.js +387 -0
  150. package/packages/dd-trace/src/plugin_manager.js +5 -3
  151. package/packages/dd-trace/src/plugins/ci_plugin.js +6 -1
  152. package/packages/dd-trace/src/plugins/index.js +1 -0
  153. package/packages/{datadog-plugin-jest/src/util.js → dd-trace/src/plugins/util/jest.js} +5 -65
  154. package/packages/dd-trace/src/plugins/util/test.js +93 -14
  155. package/packages/dd-trace/src/plugins/util/web.js +56 -2
  156. package/packages/dd-trace/src/profiling/profiler.js +1 -1
  157. package/packages/dd-trace/src/profiling/profilers/space.js +1 -3
  158. package/packages/dd-trace/src/profiling/profilers/wall.js +37 -90
  159. package/packages/dd-trace/src/profiling/webspan-utils.js +45 -0
  160. package/packages/dd-trace/src/proxy.js +38 -29
  161. package/packages/dd-trace/src/ritm.js +9 -2
  162. package/packages/dd-trace/src/serverless.js +37 -0
  163. package/packages/dd-trace/src/span_format.js +1 -0
  164. package/packages/dd-trace/src/span_stats.js +30 -3
  165. package/packages/dd-trace/src/storage-channels.js +86 -0
  166. package/packages/dd-trace/src/tracer.js +9 -2
  167. package/packages/dd-trace/src/tracer_metadata.js +14 -1
  168. package/packages/dd-trace/src/web-tags-cache.js +132 -0
  169. package/vendor/dist/@datadog/openfeature-node-server/LICENSE +201 -0
  170. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -0
  171. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +0 -65
  172. package/packages/datadog-webpack/src/optional-peer-loader.js +0 -17
  173. package/packages/dd-trace/src/feature-registry.js +0 -29
  174. package/packages/dd-trace/src/openfeature/register.js +0 -35
  175. package/packages/dd-trace/src/openfeature/require-provider.js +0 -18
@@ -8,11 +8,6 @@ const { pathToFileURL, fileURLToPath } = require('node:url')
8
8
  const instrumentations = require('../datadog-instrumentations/src/helpers/instrumentations')
9
9
  const extractPackageAndModulePath = require('../datadog-instrumentations/src/helpers/extract-package-and-module-path')
10
10
  const hooks = require('../datadog-instrumentations/src/helpers/hooks')
11
- const {
12
- OPTIONAL_PEER_FILTER,
13
- matchesOptionalPeerFile,
14
- rewriteOptionalPeerLoads,
15
- } = require('../datadog-instrumentations/src/helpers/optional-peer-bundler')
16
11
  const { processModule, isESMFile } = require('./src/utils')
17
12
  const log = require('./src/log')
18
13
 
@@ -133,19 +128,6 @@ module.exports.setup = function (build) {
133
128
  ${build.initialOptions.banner.js}`
134
129
  }
135
130
 
136
- // Keep the build from failing on the optional `@openfeature/core` peer of
137
- // `@openfeature/server-sdk` when it is not installed (#8635). esbuild follows the chain
138
- // whenever `@openfeature/server-sdk` is reachable -- the app importing it directly, or the
139
- // bundled provider when the optional peer is present -- so mark `@openfeature/core` external
140
- // when it is absent rather than erroring at bundle time.
141
- try {
142
- // eslint-disable-next-line n/no-unpublished-require
143
- require.resolve('@openfeature/core')
144
- } catch {
145
- build.initialOptions.external ??= []
146
- build.initialOptions.external.push('@openfeature/core')
147
- }
148
-
149
131
  const esmBuild = isESMBuild(build)
150
132
  if (
151
133
  esmBuild &&
@@ -182,22 +164,6 @@ ${build.initialOptions.banner.js}`
182
164
  log.warn('No git metadata available - skipping injection')
183
165
  }
184
166
 
185
- // Rewrite optional-peer loads so installed peers get bundled and survive the bundle being
186
- // relocated without them on disk (#8980). Registered before the generic onLoad so it wins for
187
- // these files. Absent peers stay opaque, so a build that does not opt into the feature does not
188
- // follow their dependency chain (#8635).
189
- build.onLoad({ filter: OPTIONAL_PEER_FILTER }, args => {
190
- const normalizedPath = args.path.replaceAll('\\', '/')
191
- if (!matchesOptionalPeerFile(normalizedPath)) return
192
-
193
- log.debug('INLINE: optional-peer loader applied to %s', normalizedPath)
194
- return {
195
- contents: rewriteOptionalPeerLoads(fs.readFileSync(args.path, 'utf8'), path.dirname(args.path)),
196
- loader: 'js',
197
- resolveDir: path.dirname(args.path),
198
- }
199
- })
200
-
201
167
  // first time is intercepted, proxy should be created, next time the original should be loaded
202
168
  const interceptedESMModules = new Set()
203
169
 
@@ -6,6 +6,144 @@ const { addHook } = require('./helpers/instrument')
6
6
 
7
7
  const anthropicTracingChannel = tracingChannel('apm:anthropic:request')
8
8
  const onStreamedChunkCh = channel('apm:anthropic:request:chunk')
9
+ const messagesBeforeChannel = channel('dd-trace:anthropic:messages:before')
10
+ const messagesAfterChannel = channel('dd-trace:anthropic:messages:after')
11
+
12
+ /**
13
+ * Publishes a provider-native lifecycle payload to a cancelable lifecycle channel.
14
+ *
15
+ * Subscribers push async work into `pending` synchronously during publication and
16
+ * abort `abortController` with an error before the pushed promise resolves to block.
17
+ *
18
+ * @param {object} channel
19
+ * @param {object} payload
20
+ * @returns {Promise<void>}
21
+ */
22
+ function publishLifecycle (channel, payload) {
23
+ const abortController = new AbortController()
24
+ const ctx = { ...payload, abortController, pending: [] }
25
+
26
+ channel.publish(ctx)
27
+
28
+ return Promise.all(ctx.pending).then(() => {
29
+ if (abortController.signal.aborted) {
30
+ throw abortController.signal.reason
31
+ }
32
+ })
33
+ }
34
+
35
+ /**
36
+ * @template T
37
+ * @param {Promise<T>} promise
38
+ * @param {Promise<void>|undefined} verdict
39
+ * @returns {Promise<T>}
40
+ */
41
+ function waitForVerdict (promise, verdict) {
42
+ if (!verdict) return promise
43
+
44
+ // The lifecycle verdict takes precedence over an earlier SDK rejection.
45
+ promise.catch(() => {})
46
+ return verdict.then(() => promise)
47
+ }
48
+
49
+ /**
50
+ * @param {Array<unknown>} args
51
+ * @returns {Array<unknown>|undefined}
52
+ */
53
+ function snapshotLifecycleArgs (args) {
54
+ const options = args[0]
55
+ if (!options || typeof options !== 'object') return
56
+
57
+ const input = { messages: options.messages }
58
+ if (options.system !== undefined) input.system = options.system
59
+
60
+ // Snapshot via JSON so AI Guard evaluates exactly what the SDK serializes and sends, immune to
61
+ // later caller mutation. If it can't serialize (e.g. circular), the SDK's own serialization would
62
+ // fail too, so skipping evaluation is safe — nothing reaches the model.
63
+ try {
64
+ const snapshot = [...args]
65
+ // eslint-disable-next-line unicorn/prefer-structured-clone
66
+ snapshot[0] = { ...options, ...JSON.parse(JSON.stringify(input)) }
67
+ return snapshot
68
+ } catch {
69
+ // Unserializable input — leave AI Guard inactive for this call (the SDK send would fail too).
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Finishes after output evaluation so a block propagates to the caller and span.
75
+ *
76
+ * @param {object} ctx
77
+ * @param {object} result
78
+ * @param {(body: object) => Promise<void>|undefined} getVerdict
79
+ * @param {object|string} [returnedResult]
80
+ * @returns {object|string|Promise<object|string>}
81
+ */
82
+ function finishResult (ctx, result, getVerdict, returnedResult = result) {
83
+ const verdict = getVerdict(result)
84
+ if (!verdict) {
85
+ finish(ctx, result)
86
+ return returnedResult
87
+ }
88
+
89
+ return verdict.then(() => {
90
+ finish(ctx, result)
91
+ return returnedResult
92
+ })
93
+ }
94
+
95
+ /**
96
+ * @param {object} ctx
97
+ * @param {Error} error
98
+ * @throws {Error} Always rethrows the supplied error.
99
+ */
100
+ function finishAndThrow (ctx, error) {
101
+ finish(ctx, null, error)
102
+ throw error
103
+ }
104
+
105
+ /**
106
+ * @param {object} response
107
+ * @param {'json'|'text'} method
108
+ * @param {object} ctx
109
+ * @param {(body: object) => Promise<void>|undefined} getVerdict
110
+ */
111
+ function wrapResponseReader (response, method, ctx, getVerdict) {
112
+ if (typeof response[method] !== 'function') return
113
+
114
+ shimmer.wrap(response, method, original => function (...args) {
115
+ return original.apply(this, args)
116
+ .then(body => {
117
+ if (method === 'json') return finishResult(ctx, body, getVerdict)
118
+
119
+ try {
120
+ return finishResult(ctx, JSON.parse(body), getVerdict, body)
121
+ } catch {
122
+ finish(ctx)
123
+ return body
124
+ }
125
+ })
126
+ .catch(error => finishAndThrow(ctx, error))
127
+ })
128
+ }
129
+
130
+ /**
131
+ * @param {object} response
132
+ * @param {object} ctx
133
+ * @param {(body: object) => Promise<void>|undefined} getVerdict
134
+ */
135
+ function wrapRawResponse (response, ctx, getVerdict) {
136
+ wrapResponseReader(response, 'json', ctx, getVerdict)
137
+ wrapResponseReader(response, 'text', ctx, getVerdict)
138
+
139
+ if (typeof response.clone !== 'function') return
140
+
141
+ shimmer.wrap(response, 'clone', clone => function (...args) {
142
+ const clonedResponse = clone.apply(this, args)
143
+ wrapRawResponse(clonedResponse, ctx, getVerdict)
144
+ return clonedResponse
145
+ })
146
+ }
9
147
 
10
148
  function wrapStreamIterator (iterator, ctx) {
11
149
  return function (...args) {
@@ -34,40 +172,94 @@ function wrapStreamIterator (iterator, ctx) {
34
172
 
35
173
  function wrapCreate (create) {
36
174
  return function (...args) {
37
- if (!anthropicTracingChannel.start.hasSubscribers) {
175
+ const options = args[0]
176
+ const stream = options?.stream
177
+
178
+ // Streaming is out of scope for lifecycle evaluation.
179
+ const lifecycleArgs = !stream &&
180
+ (messagesBeforeChannel.hasSubscribers || messagesAfterChannel.hasSubscribers)
181
+ ? snapshotLifecycleArgs(args)
182
+ : undefined
183
+
184
+ if (!anthropicTracingChannel.start.hasSubscribers && !lifecycleArgs) {
38
185
  return create.apply(this, args)
39
186
  }
40
187
 
41
- const options = args[0]
42
- const stream = options.stream
43
-
44
- const ctx = { options, resource: 'create', baseUrl: this._client?.baseURL }
188
+ const ctx = { options: lifecycleArgs?.[0] ?? options, resource: 'create', baseUrl: this._client?.baseURL }
45
189
 
46
190
  return anthropicTracingChannel.start.runStores(ctx, () => {
191
+ const parentSpan = lifecycleArgs ? ctx.currentStore?.span : undefined
192
+
47
193
  let apiPromise
48
194
  try {
49
- apiPromise = create.apply(this, args)
195
+ // Anthropic starts the request eagerly; the input verdict only gates result delivery.
196
+ apiPromise = create.apply(this, lifecycleArgs ?? args)
50
197
  } catch (error) {
51
198
  finish(ctx, null, error)
52
199
  throw error
53
200
  }
54
201
 
55
- shimmer.wrap(apiPromise, 'parse', parse => function (...args) {
56
- return parse.apply(this, args)
202
+ let beforeVerdict
203
+ let afterVerdict
204
+ let parseResult
205
+ let wrappedResponse
206
+
207
+ function getBeforeVerdict () {
208
+ if (!lifecycleArgs || beforeVerdict) return beforeVerdict
209
+ if (!messagesBeforeChannel.hasSubscribers) return
210
+
211
+ beforeVerdict = publishLifecycle(messagesBeforeChannel, { args: lifecycleArgs, parentSpan })
212
+ return beforeVerdict
213
+ }
214
+
215
+ /**
216
+ * @param {object|string} body
217
+ */
218
+ function getAfterVerdict (body) {
219
+ if (!lifecycleArgs || afterVerdict) return afterVerdict
220
+ if (!messagesAfterChannel.hasSubscribers) return
221
+
222
+ afterVerdict = publishLifecycle(messagesAfterChannel, { args: lifecycleArgs, body, parentSpan })
223
+ return afterVerdict
224
+ }
225
+
226
+ shimmer.wrap(apiPromise, 'parse', parse => function (...parseArgs) {
227
+ if (parseResult) return parseResult
228
+
229
+ const parsed = parse.apply(this, parseArgs)
230
+ parseResult = waitForVerdict(parsed, getBeforeVerdict())
57
231
  .then(response => {
58
232
  if (stream) {
59
233
  shimmer.wrap(response, Symbol.asyncIterator, iterator => wrapStreamIterator(iterator, ctx))
60
- } else {
61
- finish(ctx, response, null)
234
+ return response
62
235
  }
63
-
64
- return response
65
- }).catch(error => {
66
- finish(ctx, null, error)
67
- throw error
236
+ return finishResult(ctx, response, getAfterVerdict)
68
237
  })
238
+ .catch(error => finishAndThrow(ctx, error))
239
+
240
+ return parseResult
69
241
  })
70
242
 
243
+ if (typeof apiPromise.asResponse === 'function') {
244
+ shimmer.wrap(apiPromise, 'asResponse', origAsResponse => function (...asResponseArgs) {
245
+ return waitForVerdict(origAsResponse.apply(this, asResponseArgs), getBeforeVerdict())
246
+ .then(response => {
247
+ // Wrap the raw response's json()/text()/clone() readers to evaluate output on read,
248
+ // but only while someone still consumes the result and not twice for the same response.
249
+ const outputObserved = anthropicTracingChannel.start.hasSubscribers ||
250
+ afterVerdict || messagesAfterChannel.hasSubscribers
251
+ if (!stream && outputObserved && wrappedResponse !== response) {
252
+ wrappedResponse = response
253
+ wrapRawResponse(response, ctx, getAfterVerdict)
254
+ }
255
+
256
+ if (afterVerdict) return afterVerdict.then(() => response)
257
+ return response
258
+ })
259
+ .catch(error => finishAndThrow(ctx, error))
260
+ })
261
+ }
262
+
71
263
  anthropicTracingChannel.end.publish(ctx)
72
264
 
73
265
  return apiPromise
@@ -76,6 +268,8 @@ function wrapCreate (create) {
76
268
  }
77
269
 
78
270
  function finish (ctx, result, error) {
271
+ if (ctx.finished) return
272
+
79
273
  if (error) {
80
274
  ctx.error = error
81
275
  anthropicTracingChannel.error.publish(ctx)
@@ -83,6 +277,7 @@ function finish (ctx, result, error) {
83
277
 
84
278
  // streamed responses are handled and set separately
85
279
  ctx.result ??= result
280
+ ctx.finished = true
86
281
 
87
282
  anthropicTracingChannel.asyncEnd.publish(ctx)
88
283
  }
@@ -25,6 +25,13 @@ const KNOWN_CHANNEL_SUFFIXES = new Set([
25
25
  'bedrockruntime',
26
26
  ])
27
27
 
28
+ // Older SDK clients report EventBridge under its CloudWatch Events name or its `events` endpoint
29
+ // prefix; both share the EventBridge channels.
30
+ const CHANNEL_SUFFIX_ALIASES = new Map([
31
+ ['cloudwatchevents', 'eventbridge'],
32
+ ['events', 'eventbridge'],
33
+ ])
34
+
28
35
  /**
29
36
  * @typedef {object} ChannelBag
30
37
  * @property {ReturnType<typeof channel>} start
@@ -314,7 +321,8 @@ function addResponse (ctx, error, result) {
314
321
  function getChannelSuffix (name) {
315
322
  // some resource identifiers have spaces between ex: bedrock runtime
316
323
  name = String(name).replaceAll(' ', '')
317
- return KNOWN_CHANNEL_SUFFIXES.has(name) ? name : 'default'
324
+ if (KNOWN_CHANNEL_SUFFIXES.has(name)) return name
325
+ return CHANNEL_SUFFIX_ALIASES.get(name) ?? 'default'
318
326
  }
319
327
 
320
328
  addHook({ name: '@smithy/smithy-client', versions: ['>=1.0.3'] }, smithy => {
@@ -0,0 +1,11 @@
1
+ 'use strict'
2
+
3
+ const wrapLogger = require('./helpers/bunyan')
4
+ const { addHook } = require('./helpers/instrument')
5
+
6
+ addHook({ name: 'browser-bunyan', versions: ['>=1'] }, browserBunyan => {
7
+ // Published versions generate `_emit` through different bundle shapes,
8
+ // so Orchestrion has no stable function to match.
9
+ wrapLogger(browserBunyan.Logger, 'browser-bunyan')
10
+ return browserBunyan
11
+ })
@@ -1,22 +1,9 @@
1
1
  'use strict'
2
2
 
3
- const shimmer = require('../../datadog-shimmer')
4
- const {
5
- channel,
6
- addHook,
7
- } = require('./helpers/instrument')
3
+ const wrapLogger = require('./helpers/bunyan')
4
+ const { addHook } = require('./helpers/instrument')
8
5
 
9
6
  addHook({ name: 'bunyan', versions: ['>=1'] }, Logger => {
10
- const logCh = channel('apm:bunyan:log')
11
- shimmer.wrap(Logger.prototype, '_emit', emit => {
12
- return function wrappedEmit (rec) {
13
- if (logCh.hasSubscribers) {
14
- const payload = { message: rec }
15
- logCh.publish(payload)
16
- arguments[0] = payload.message
17
- }
18
- return emit.apply(this, arguments)
19
- }
20
- })
7
+ wrapLogger(Logger, 'bunyan')
21
8
  return Logger
22
9
  })
@@ -29,6 +29,17 @@ const channels = {
29
29
 
30
30
  const disabledHeaderWeakSet = new WeakSet()
31
31
 
32
+ const EMPTY_CARRIER = Object.create(null)
33
+ /** @type {string[]} */
34
+ const NO_MERGEABLE_KEYS = []
35
+
36
+ /** @typedef {string | Buffer} NativeHeaderValue */
37
+ /** @typedef {Array<Record<string, NativeHeaderValue>>} NativeHeaders */
38
+ /** @typedef {Record<string, NativeHeaderValue | NativeHeaderValue[]>} NativeHeaderCarrier */
39
+
40
+ /** @type {NativeHeaders} */
41
+ const EMPTY_NATIVE_HEADERS = []
42
+
32
43
  // we need to store the offset per partition per topic for the consumer to track offsets for DSM
33
44
  const latestConsumerOffsets = new Map()
34
45
 
@@ -60,17 +71,47 @@ function instrumentBaseModule (module) {
60
71
  }
61
72
 
62
73
  const brokers = this.globalConfig?.['bootstrap.servers']
74
+ const nativeHeaders = Array.isArray(headers) ? headers : EMPTY_NATIVE_HEADERS
75
+
76
+ let carrier
77
+ let forwardCallerHeaders = false
78
+ let applicationValues = EMPTY_CARRIER
79
+ let headerKeys = NO_MERGEABLE_KEYS
80
+ if (nativeHeaders.length !== 0) {
81
+ // `__proto__` is a legal Kafka header key and propagation never
82
+ // writes it, so only the seeded carrier needs a null prototype.
83
+ const candidateCarrier = Object.create(null)
84
+ const candidateValues = Object.create(null)
85
+ const candidateKeys = seedNativeHeaderCarrier(nativeHeaders, candidateCarrier, candidateValues)
86
+ if (candidateKeys === undefined) {
87
+ forwardCallerHeaders = true
88
+ } else if (candidateKeys.length !== 0) {
89
+ carrier = candidateCarrier
90
+ applicationValues = candidateValues
91
+ headerKeys = candidateKeys
92
+ }
93
+ }
63
94
 
64
95
  const ctx = {
65
96
  topic,
66
- messages: [{ key, value: message }],
97
+ messages: [{
98
+ key,
99
+ value: message,
100
+ headers: carrier,
101
+ }],
67
102
  bootstrapServers: brokers,
68
103
  }
69
104
 
70
105
  return channels.producerStart.runStores(ctx, () => {
71
106
  try {
72
- const headers = convertHeaders(ctx.messages[0].headers)
73
- const result = produce.apply(this, [topic, partition, message, key, timestamp, opaque, headers])
107
+ const producedHeaders = forwardCallerHeaders
108
+ ? nativeHeaders
109
+ : mergeNativeHeaders(
110
+ nativeHeaders, headerKeys, applicationValues, ctx.messages[0].headers ?? EMPTY_CARRIER
111
+ )
112
+ const result = produce.apply(this, [
113
+ topic, partition, message, key, timestamp, opaque, producedHeaders,
114
+ ])
74
115
 
75
116
  ctx.result = result
76
117
  channels.producerCommit.publish(ctx)
@@ -432,7 +473,71 @@ function getLatestOffsets () {
432
473
  return [...latestConsumerOffsets.values()]
433
474
  }
434
475
 
435
- function convertHeaders (headers) {
436
- // convert headers from object to array of objects with 1 key and value per array entry
437
- return Object.entries(headers).map(([key, value]) => ({ [key.toString()]: value.toString() }))
476
+ /**
477
+ * `Producer::NodeProduce` reads the first *enumerable* own key of each entry and
478
+ * validates the value itself. A `null` or `undefined` entry aborts the process
479
+ * there, so those are the only entries we refuse to hand back to it.
480
+ *
481
+ * @param {NativeHeaders} headers Caller-owned.
482
+ * @param {NativeHeaderCarrier} carrier
483
+ * @param {NativeHeaderCarrier} applicationValues
484
+ * @returns {string[] | undefined} `undefined` when the binding has to reject the
485
+ * array itself; empty when it cannot consume the array at all.
486
+ * @see https://github.com/confluentinc/confluent-kafka-javascript/blob/v1.9.1/src/producer.cc
487
+ */
488
+ function seedNativeHeaderCarrier (headers, carrier, applicationValues) {
489
+ const keys = new Array(headers.length)
490
+ for (let i = 0; i < headers.length; i++) {
491
+ const header = headers[i]
492
+ if (header === null || header === undefined) return NO_MERGEABLE_KEYS
493
+
494
+ const key = Object.keys(header)[0]
495
+ if (key === undefined) return
496
+
497
+ keys[i] = key
498
+ const value = header[key]
499
+ if (typeof value !== 'string' && !Buffer.isBuffer(value)) return
500
+
501
+ if (i === 0) {
502
+ carrier[key] = value
503
+ applicationValues[key] = value
504
+ continue
505
+ }
506
+
507
+ const previousValue = carrier[key]
508
+ if (previousValue === undefined) {
509
+ carrier[key] = value
510
+ } else if (Array.isArray(previousValue)) {
511
+ previousValue.push(value)
512
+ } else {
513
+ carrier[key] = [previousValue, value]
514
+ }
515
+ applicationValues[key] = carrier[key]
516
+ }
517
+ return keys
518
+ }
519
+
520
+ /**
521
+ * @param {NativeHeaders} headers Caller-owned; surviving entries are forwarded
522
+ * by reference, never copied or mutated.
523
+ * @param {string[]} headerKeys Parallel to `headers`.
524
+ * @param {NativeHeaderCarrier} applicationValues Carrier contents before
525
+ * propagation ran.
526
+ * @param {NativeHeaderCarrier} carrier
527
+ */
528
+ function mergeNativeHeaders (headers, headerKeys, applicationValues, carrier) {
529
+ const merged = []
530
+ for (let i = 0; i < headerKeys.length; i++) {
531
+ const key = headerKeys[i]
532
+ if (carrier[key] === applicationValues[key]) {
533
+ merged.push(headers[i])
534
+ }
535
+ }
536
+
537
+ for (const key of Object.keys(carrier)) {
538
+ if (carrier[key] !== applicationValues[key]) {
539
+ merged.push({ [key]: carrier[key] })
540
+ }
541
+ }
542
+ return merged
438
543
  }
@@ -116,7 +116,7 @@ let pickleByFile = {}
116
116
  const pickleResultByFile = {}
117
117
 
118
118
  let skippableSuites = []
119
- let skippableSuitesCoverage = {}
119
+ let skippableSuitesCoverage
120
120
  let skippedSuitesCoverage = {}
121
121
  let itrCorrelationId = ''
122
122
  let isForcedToRun = false
@@ -153,12 +153,6 @@ function isValidKnownTests (receivedKnownTests) {
153
153
  return !!receivedKnownTests.cucumber
154
154
  }
155
155
 
156
- function hasSkippableSuitesCoverage () {
157
- return skippableSuitesCoverage &&
158
- typeof skippableSuitesCoverage === 'object' &&
159
- Object.keys(skippableSuitesCoverage).length > 0
160
- }
161
-
162
156
  function isTiaCoverageBackfillEnabled () {
163
157
  return isItrEnabled && isCoverageReportUploadEnabled
164
158
  }
@@ -172,7 +166,7 @@ function shouldReportCodeCoverageLinesPct (hasBackfilledCoverage) {
172
166
  }
173
167
 
174
168
  function getSkippedSuitesCoverageForRun () {
175
- return isSuitesSkipped && isTiaCoverageBackfillEnabled() && hasSkippableSuitesCoverage()
169
+ return isSuitesSkipped && isTiaCoverageBackfillEnabled() && skippableSuitesCoverage !== undefined
176
170
  ? skippableSuitesCoverage
177
171
  : {}
178
172
  }
@@ -188,7 +182,7 @@ function getCucumberTestSessionCoverageFiles () {
188
182
 
189
183
  function resetSuiteSkippingRunState () {
190
184
  skippableSuites = []
191
- skippableSuitesCoverage = {}
185
+ skippableSuitesCoverage = undefined
192
186
  skippedSuitesCoverage = {}
193
187
  skippedSuites = []
194
188
  isSuitesSkipped = false
@@ -1143,7 +1137,7 @@ function getWrappedStart (start, frameworkVersion, isParallel = false, isCoordin
1143
1137
 
1144
1138
  errorSkippableRequest = skippableResponse.err
1145
1139
  skippableSuites = skippableResponse.skippableSuites ?? []
1146
- skippableSuitesCoverage = skippableResponse.skippableSuitesCoverage ?? {}
1140
+ skippableSuitesCoverage = skippableResponse.skippableSuitesCoverage
1147
1141
 
1148
1142
  if (!errorSkippableRequest) {
1149
1143
  const filteredPickles = isCoordinator
@@ -1222,7 +1216,28 @@ function getWrappedStart (start, frameworkVersion, isParallel = false, isCoordin
1222
1216
  itrSkippedSuitesCh.publish({ skippedSuites, frameworkVersion })
1223
1217
  }
1224
1218
 
1225
- const result = await start.apply(this, arguments)
1219
+ let result
1220
+ try {
1221
+ result = await start.apply(this, arguments)
1222
+ } catch (error) {
1223
+ try {
1224
+ await getChannelPromise(sessionFinishCh, {
1225
+ status: 'fail',
1226
+ error,
1227
+ isSuitesSkipped,
1228
+ numSkippedSuites: skippedSuites.length,
1229
+ hasUnskippableSuites: isUnskippable,
1230
+ hasForcedToRunSuites: isForcedToRun,
1231
+ isEarlyFlakeDetectionEnabled,
1232
+ isEarlyFlakeDetectionFaulty,
1233
+ isTestManagementTestsEnabled,
1234
+ isParallel,
1235
+ })
1236
+ } catch (finalizationError) {
1237
+ log.error('Cucumber test session finalization error: %s', finalizationError)
1238
+ }
1239
+ throw error
1240
+ }
1226
1241
  const success = satisfies(frameworkVersion, '>=13.1.0') ? result.success : result
1227
1242
 
1228
1243
  let untestedCoverage