dd-trace 5.106.0 → 5.108.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 (105) hide show
  1. package/index.d.ts +20 -1
  2. package/package.json +9 -11
  3. package/packages/datadog-core/src/storage.js +47 -48
  4. package/packages/datadog-esbuild/index.js +6 -1
  5. package/packages/datadog-instrumentations/src/ai.js +12 -3
  6. package/packages/datadog-instrumentations/src/body-parser.js +5 -2
  7. package/packages/datadog-instrumentations/src/connect.js +3 -2
  8. package/packages/datadog-instrumentations/src/cookie-parser.js +3 -2
  9. package/packages/datadog-instrumentations/src/cucumber.js +7 -0
  10. package/packages/datadog-instrumentations/src/express-mongo-sanitize.js +7 -5
  11. package/packages/datadog-instrumentations/src/express-session.js +12 -11
  12. package/packages/datadog-instrumentations/src/express.js +24 -20
  13. package/packages/datadog-instrumentations/src/fastify.js +18 -6
  14. package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +27 -12
  15. package/packages/datadog-instrumentations/src/http/client.js +9 -12
  16. package/packages/datadog-instrumentations/src/http/server.js +30 -16
  17. package/packages/datadog-instrumentations/src/http2/client.js +15 -12
  18. package/packages/datadog-instrumentations/src/http2/server.js +15 -8
  19. package/packages/datadog-instrumentations/src/jest/bail-reporter.js +42 -0
  20. package/packages/datadog-instrumentations/src/jest.js +143 -73
  21. package/packages/datadog-instrumentations/src/mocha/main.js +43 -8
  22. package/packages/datadog-instrumentations/src/mocha/utils.js +128 -17
  23. package/packages/datadog-instrumentations/src/multer.js +3 -2
  24. package/packages/datadog-instrumentations/src/mysql2.js +34 -0
  25. package/packages/datadog-instrumentations/src/net.js +8 -6
  26. package/packages/datadog-instrumentations/src/openai.js +19 -7
  27. package/packages/datadog-instrumentations/src/pg.js +19 -0
  28. package/packages/datadog-instrumentations/src/router.js +12 -10
  29. package/packages/datadog-instrumentations/src/vitest.js +29 -4
  30. package/packages/datadog-plugin-aws-sdk/src/base.js +0 -3
  31. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +62 -11
  32. package/packages/datadog-plugin-cucumber/src/index.js +2 -0
  33. package/packages/datadog-plugin-cypress/src/support.js +31 -1
  34. package/packages/datadog-plugin-http/src/client.js +0 -3
  35. package/packages/datadog-plugin-http/src/server.js +11 -1
  36. package/packages/datadog-plugin-mocha/src/index.js +2 -0
  37. package/packages/datadog-plugin-pg/src/index.js +10 -0
  38. package/packages/dd-trace/src/aiguard/index.js +34 -15
  39. package/packages/dd-trace/src/aiguard/sdk.js +34 -3
  40. package/packages/dd-trace/src/aiguard/tags.js +6 -0
  41. package/packages/dd-trace/src/appsec/downstream_requests.js +3 -2
  42. package/packages/dd-trace/src/appsec/iast/index.js +3 -2
  43. package/packages/dd-trace/src/appsec/rasp/ssrf.js +2 -1
  44. package/packages/dd-trace/src/appsec/reporter.js +1 -1
  45. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +1 -1
  46. package/packages/dd-trace/src/config/defaults.js +14 -0
  47. package/packages/dd-trace/src/config/generated-config-types.d.ts +2 -1
  48. package/packages/dd-trace/src/config/helper.js +1 -0
  49. package/packages/dd-trace/src/config/index.js +5 -9
  50. package/packages/dd-trace/src/config/parsers.js +8 -0
  51. package/packages/dd-trace/src/config/supported-configurations.json +20 -6
  52. package/packages/dd-trace/src/crashtracking/crashtracker.js +2 -2
  53. package/packages/dd-trace/src/datastreams/writer.js +1 -2
  54. package/packages/dd-trace/src/debugger/config.js +1 -1
  55. package/packages/dd-trace/src/debugger/devtools_client/config.js +3 -2
  56. package/packages/dd-trace/src/debugger/index.js +1 -2
  57. package/packages/dd-trace/src/dogstatsd.js +2 -3
  58. package/packages/dd-trace/src/encode/0.4.js +49 -41
  59. package/packages/dd-trace/src/encode/agentless-json.js +5 -1
  60. package/packages/dd-trace/src/encode/tags-processors.js +14 -0
  61. package/packages/dd-trace/src/exporters/agent/index.js +1 -2
  62. package/packages/dd-trace/src/exporters/agentless/index.js +6 -10
  63. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +1 -2
  64. package/packages/dd-trace/src/exporters/common/request.js +26 -0
  65. package/packages/dd-trace/src/exporters/span-stats/index.js +1 -2
  66. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +4 -0
  67. package/packages/dd-trace/src/llmobs/plugins/genai/util.js +45 -0
  68. package/packages/dd-trace/src/llmobs/sdk.js +4 -1
  69. package/packages/dd-trace/src/llmobs/span_processor.js +17 -1
  70. package/packages/dd-trace/src/llmobs/tagger.js +5 -3
  71. package/packages/dd-trace/src/llmobs/util.js +54 -0
  72. package/packages/dd-trace/src/llmobs/writers/base.js +1 -2
  73. package/packages/dd-trace/src/llmobs/writers/util.js +1 -2
  74. package/packages/dd-trace/src/openfeature/writers/base.js +1 -10
  75. package/packages/dd-trace/src/openfeature/writers/util.js +1 -2
  76. package/packages/dd-trace/src/opentelemetry/metrics/instruments.js +26 -13
  77. package/packages/dd-trace/src/opentelemetry/metrics/meter.js +7 -10
  78. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +92 -0
  79. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +3 -2
  80. package/packages/dd-trace/src/opentracing/span.js +23 -18
  81. package/packages/dd-trace/src/opentracing/tracer.js +16 -12
  82. package/packages/dd-trace/src/plugins/ci_plugin.js +131 -46
  83. package/packages/dd-trace/src/priority_sampler.js +6 -5
  84. package/packages/dd-trace/src/profiling/config.js +3 -2
  85. package/packages/dd-trace/src/profiling/profilers/events.js +26 -4
  86. package/packages/dd-trace/src/profiling/profilers/space.js +3 -1
  87. package/packages/dd-trace/src/proxy.js +13 -10
  88. package/packages/dd-trace/src/remote_config/index.js +1 -2
  89. package/packages/dd-trace/src/runtime_metrics/client.js +30 -0
  90. package/packages/dd-trace/src/runtime_metrics/index.js +12 -2
  91. package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +284 -0
  92. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +2 -11
  93. package/packages/dd-trace/src/service-naming/source-resolver.js +5 -1
  94. package/packages/dd-trace/src/span_format.js +33 -25
  95. package/packages/dd-trace/src/span_stats.js +1 -1
  96. package/packages/dd-trace/src/startup-log.js +1 -2
  97. package/packages/dd-trace/src/telemetry/send-data.js +1 -1
  98. package/packages/dd-trace/src/tracer.js +1 -1
  99. package/vendor/dist/@apm-js-collab/code-transformer/index.js +2 -2
  100. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  101. package/vendor/dist/protobufjs/index.js +1 -1
  102. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  103. package/vendor/dist/shell-quote/index.js +1 -1
  104. package/packages/dd-trace/src/agent/url.js +0 -28
  105. package/scripts/preinstall.js +0 -34
package/index.d.ts CHANGED
@@ -19,6 +19,12 @@ interface Tracer extends opentracing.Tracer {
19
19
 
20
20
  /**
21
21
  * Starts and returns a new Span representing a logical unit of work.
22
+ *
23
+ * The returned span is not activated on the current scope. Spans created
24
+ * while it is open — via {@link Tracer.trace} or auto-instrumentation — only
25
+ * nest under it when it is the active span, so wrap the work in
26
+ * {@link Scope.activate} (or use {@link Tracer.trace}) when child spans
27
+ * should descend from it.
22
28
  * @param {string} name The name of the operation.
23
29
  * @param {tracer.SpanOptions} [options] Options for the newly created span.
24
30
  * @returns {Span} A new Span object.
@@ -4000,6 +4006,14 @@ declare namespace tracer {
4000
4006
  template?: string | Message[]
4001
4007
  }
4002
4008
 
4009
+ interface ToolDefinition {
4010
+ name : string,
4011
+ description? : string,
4012
+ schema? : {[key : string] : any}
4013
+ version? : string
4014
+ }
4015
+
4016
+
4003
4017
  /**
4004
4018
  * Annotation options for LLM Observability spans.
4005
4019
  */
@@ -4046,6 +4060,12 @@ declare namespace tracer {
4046
4060
  * A Prompt object that represents the prompt used for an LLM call. Only used on `llm` spans.
4047
4061
  */
4048
4062
  prompt?: Prompt,
4063
+
4064
+ /**
4065
+ * A list of ToolDefinition object that represents the tools available to the LLM for this span
4066
+ * Each definition requires a `name` and optionally accepts `description`, `schema`, and `version`.
4067
+ * */
4068
+ toolDefinitions?: ToolDefinition[]
4049
4069
  }
4050
4070
 
4051
4071
  interface AnnotationContextOptions {
@@ -4161,7 +4181,6 @@ declare namespace tracer {
4161
4181
  */
4162
4182
  agentlessEnabled?: boolean,
4163
4183
  }
4164
-
4165
4184
  /** @hidden */
4166
4185
  type spanKind = 'agent' | 'workflow' | 'task' | 'tool' | 'retrieval' | 'embedding' | 'llm'
4167
4186
  }
package/package.json CHANGED
@@ -1,13 +1,12 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "5.106.0",
3
+ "version": "5.108.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
7
7
  "scripts": {
8
8
  "env": "bash ./plugin-env",
9
9
  "prepare": "node scripts/patch-istanbul-lib-coverage.js && cd vendor && npm ci --include=dev",
10
- "preinstall": "node scripts/preinstall.js",
11
10
  "prepack": "node scripts/release/swap-v5-types.js",
12
11
  "bench": "node benchmark/index.js",
13
12
  "bench:e2e:test-optimization": "node benchmark/e2e-test-optimization/benchmark-run.js",
@@ -23,7 +22,7 @@
23
22
  "lint:fix": "node scripts/check_licenses.js && node scripts/check-no-coverage-artifacts.js && node scripts/check-no-mcr-images.js && node scripts/check-docker-image-shas.js && eslint . --concurrency=auto --max-warnings 0 --fix",
24
23
  "lint:inspect": "npx @eslint/config-inspector@latest",
25
24
  "lint:codeowners": "codeowners-audit",
26
- "lint:codeowners:ci": "codeowners-audit --glob='**/*.spec.js' --glob='benchmark/sirun/**'",
25
+ "lint:codeowners:ci": "codeowners-audit --glob='**/*.spec.js' --glob='benchmark/sirun/**' --glob='.agents/**' --glob='.claude/**'",
27
26
  "release:proposal": "node scripts/release/proposal",
28
27
  "services": "node ./scripts/install_plugin_modules && node packages/dd-trace/test/setup/services",
29
28
  "test": "echo '\nError: The root \"npm test\" command is intentionally disabled.\n\nInstead, run specific test suites:\n - npm run test:trace:core\n - npm run test:appsec\n - etc.\n\nOr run individual test files:\n npx mocha path/to/test.spec.js\n\nSee CONTRIBUTING.md (Testing section) for more details.\n' && exit 1",
@@ -153,7 +152,6 @@
153
152
  "packages/datadog-instrumentations/orchestrion.yml",
154
153
  "README.md",
155
154
  "register.js",
156
- "scripts/preinstall.js",
157
155
  "vendor/dist/**/*.d.ts",
158
156
  "vendor/dist/**/*.js",
159
157
  "vendor/dist/**/*.wasm",
@@ -166,12 +164,12 @@
166
164
  "opentracing": ">=0.14.7"
167
165
  },
168
166
  "optionalDependencies": {
169
- "@datadog/libdatadog": "0.9.3",
167
+ "@datadog/libdatadog": "0.9.4",
170
168
  "@datadog/native-appsec": "11.0.1",
171
169
  "@datadog/native-iast-taint-tracking": "4.2.0",
172
170
  "@datadog/native-metrics": "3.1.2",
173
- "@datadog/openfeature-node-server": "1.2.1",
174
- "@datadog/pprof": "5.14.4",
171
+ "@datadog/openfeature-node-server": "2.0.0",
172
+ "@datadog/pprof": "5.15.0",
175
173
  "@datadog/wasm-js-rewriter": "5.0.1",
176
174
  "@opentelemetry/api": ">=1.0.0 <1.10.0",
177
175
  "@opentelemetry/api-logs": "<1.0.0",
@@ -180,7 +178,7 @@
180
178
  "devDependencies": {
181
179
  "@actions/core": "^3.0.1",
182
180
  "@actions/github": "^9.1.1",
183
- "@babel/helpers": "^7.29.2",
181
+ "@babel/helpers": "^7.29.7",
184
182
  "@eslint/eslintrc": "^3.3.5",
185
183
  "@eslint/js": "^9.39.2",
186
184
  "@msgpack/msgpack": "^3.1.3",
@@ -190,7 +188,7 @@
190
188
  "@types/mocha": "^10.0.10",
191
189
  "@types/node": "^18.19.106",
192
190
  "@types/sinon": "^21.0.1",
193
- "axios": "^1.16.1",
191
+ "axios": "^1.17.0",
194
192
  "benchmark": "^2.1.4",
195
193
  "body-parser": "^2.2.2",
196
194
  "bun": "1.3.14",
@@ -198,7 +196,7 @@
198
196
  "eslint": "^9.39.2",
199
197
  "eslint-plugin-cypress": "^6.4.1",
200
198
  "eslint-plugin-import": "^2.32.0",
201
- "eslint-plugin-jsdoc": "^63.0.0",
199
+ "eslint-plugin-jsdoc": "^63.0.1",
202
200
  "eslint-plugin-mocha": "^11.3.0",
203
201
  "eslint-plugin-n": "^18.0.1",
204
202
  "eslint-plugin-promise": "^7.3.0",
@@ -224,7 +222,7 @@
224
222
  "proxyquire": "^2.1.3",
225
223
  "retry": "^0.13.1",
226
224
  "semifies": "^1.0.0",
227
- "semver": "^7.8.1",
225
+ "semver": "^7.8.2",
228
226
  "sinon": "^22.0.0",
229
227
  "tiktoken": "^1.0.21",
230
228
  "typescript": "^6.0.3",
@@ -3,37 +3,26 @@
3
3
  const { AsyncLocalStorage } = require('async_hooks')
4
4
 
5
5
  /**
6
- * This is exactly the same as AsyncLocalStorage, with the exception that it
7
- * uses a WeakMap to store the store object. This is because ALS stores the
8
- * store object as a property of the resource object, which causes all sorts
9
- * of problems with logging and memory. We substitute the `store` object with
10
- * a "handle" object, which is used as a key in a WeakMap, where the values
11
- * are the real store objects.
6
+ * `AsyncLocalStorage` with a `getHandle()` escape hatch: a span stashes the
7
+ * active handle at creation (see opentracing/span.js) so a later context can
8
+ * recover its store without holding the store itself.
9
+ *
10
+ * Under AsyncContextFrame Node's default ALS backend on every release that
11
+ * ships it — the active value lives in the context frame and is never written
12
+ * to the async resource, so the store is held directly and the handle is the
13
+ * store. Pre-ACF `async_hooks` instead pinned the value onto the resource
14
+ * object, where it was visible to logging and retained with the resource
15
+ * graph; the `!isACFActive` block below restores the original WeakMap-handle
16
+ * indirection that kept the real store off the resource on those runtimes.
12
17
  *
13
18
  * @template T
14
19
  * @typedef {Record<string, T>} Store
15
20
  */
16
21
  class DatadogStorage extends AsyncLocalStorage {
17
22
  /**
18
- * @param {Store<unknown>} [store]
19
- * @override
20
- */
21
- enterWith (store) {
22
- const handle = { noop: store?.noop }
23
- stores.set(handle, store)
24
- super.enterWith(handle)
25
- }
26
-
27
- /**
28
- * This is method is a passthrough to the real `getStore()`, so that, when we
29
- * need it, we can use the handle rather than our mapped store.
30
- *
31
- * It's only here because stores are currently used for a bunch of things,
32
- * and we don't want to hold on to all of them in spans
33
- * (see opentracing/span.js). Using a namespaced storage for spans would
34
- * solve this.
35
- *
36
- * TODO: Refactor the Scope class to use a span-only store and remove this.
23
+ * Passthrough to the real `getStore()`. A span stashes this handle and feeds
24
+ * it back to `getStore(handle)` later. Identical in both modes: under ACF the
25
+ * handle is the store; without ACF it is the WeakMap key.
37
26
  *
38
27
  * @returns {Store<unknown>}
39
28
  */
@@ -42,22 +31,13 @@ class DatadogStorage extends AsyncLocalStorage {
42
31
  }
43
32
 
44
33
  /**
45
- * Here, we replicate the behavior of the original `getStore()` method by
46
- * passing in the handle, which we retrieve by calling it on super. Handles
47
- * retrieved through `getHandle()` can also be passed in to be used as the
48
- * key. This is useful if you've stashed a handle somewhere and want to
49
- * retrieve the store with it.
50
- * @param {object} [handle]
34
+ * @param {Store<unknown>} [handle] A handle from `getHandle()`; defaults to
35
+ * the active one. Under ACF the handle is the store, so it is returned as-is.
51
36
  * @returns {Store<unknown> | undefined}
52
37
  * @override
53
38
  */
54
39
  getStore (handle) {
55
- if (!handle) {
56
- handle = super.getStore()
57
- }
58
- if (handle) {
59
- return stores.get(handle)
60
- }
40
+ return handle ?? super.getStore()
61
41
  }
62
42
  }
63
43
 
@@ -77,12 +57,37 @@ if (!isACFActive) {
77
57
  const superGetStore = AsyncLocalStorage.prototype.getStore
78
58
  const superEnterWith = AsyncLocalStorage.prototype.enterWith
79
59
 
60
+ // Without ACF, ALS writes the entered value onto the async resource. Keep the
61
+ // real store off the resource by entering a small handle and mapping it to
62
+ // the store through a WeakMap, then reversing the lookup on read.
63
+ const stores = new WeakMap()
64
+
80
65
  /**
81
- * Override the `run` method to manually call `enterWith` and `getStore`
82
- * when not using AsyncContextFrame.
83
- *
84
- * Without ACF, super.run() won't call this.enterWith(), so the WeakMap handle
85
- * is never created and getStore() would fail.
66
+ * @param {Store<unknown>} [store]
67
+ */
68
+ DatadogStorage.prototype.enterWith = function enterWith (store) {
69
+ const handle = { noop: store?.noop }
70
+ stores.set(handle, store)
71
+ superEnterWith.call(this, handle)
72
+ }
73
+
74
+ /**
75
+ * @param {object} [handle]
76
+ * @returns {Store<unknown> | undefined}
77
+ */
78
+ DatadogStorage.prototype.getStore = function getStore (handle) {
79
+ if (!handle) {
80
+ handle = superGetStore.call(this)
81
+ }
82
+ if (handle) {
83
+ return stores.get(handle)
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Without ACF, `super.run()` does not delegate to `enterWith()`, so the
89
+ * WeakMap handle is never created and `getStore()` would miss. Drive the
90
+ * handle path manually and restore the prior handle on the way out.
86
91
  *
87
92
  * @template R
88
93
  * @template {unknown[]} TArgs
@@ -103,12 +108,6 @@ if (!isACFActive) {
103
108
  }
104
109
  }
105
110
 
106
- /**
107
- * This is the map from handles to real stores, used in the class above.
108
- * @type {WeakMap<WeakKey, Store<unknown>|undefined>}
109
- */
110
- const stores = new WeakMap()
111
-
112
111
  /**
113
112
  * For convenience, we use the `storage` function as a registry of namespaces
114
113
  * corresponding to DatadogStorage instances. This lets us have separate
@@ -347,10 +347,15 @@ register(${JSON.stringify(toRegister)}, _, set, get, ${JSON.stringify(data.raw)}
347
347
  }
348
348
  } else {
349
349
  const fileCode = fs.readFileSync(args.path, 'utf8')
350
+ // Don't spread `...arguments`: esbuild's minifier can rewrite the surrounding
351
+ // `__commonJS` factory into an arrow function whose `arguments` resolves to the
352
+ // ESM top-level scope (see issue #8681). Pass `(module.exports, module)`
353
+ // explicitly; the IIFE declares no parameters so esbuild's static `require()`
354
+ // resolution inside `fileCode` is preserved through the factory's closure.
350
355
  contents = `
351
356
  (function() {
352
357
  ${fileCode}
353
- })(...arguments);
358
+ })(module.exports, module);
354
359
  {
355
360
  const dc = require('dc-polyfill');
356
361
  const ch = dc.channel('${CHANNEL}');
@@ -2,8 +2,8 @@
2
2
 
3
3
  const { channel, tracingChannel } = require('dc-polyfill')
4
4
  const shimmer = require('../../datadog-shimmer')
5
- const { addHook, getHooks } = require('./helpers/instrument')
6
5
  const { convertVercelPromptToMessages, buildOutputMessages } = require('./helpers/ai-messages')
6
+ const { addHook, getHooks } = require('./helpers/instrument')
7
7
 
8
8
  const vercelAiTracingChannel = tracingChannel('dd-trace:vercel-ai')
9
9
  const vercelAiSpanSetAttributesChannel = channel('dd-trace:vercel-ai:span:setAttributes')
@@ -15,12 +15,21 @@ const wrappedModels = new WeakSet()
15
15
  /**
16
16
  * Publishes already-converted AI-style messages to the AI Guard evaluation channel.
17
17
  *
18
+ * Subscribers push async work into `pending` and abort `abortController` to block.
19
+ *
18
20
  * @param {Array<object>} messages - AI-style messages to evaluate.
19
21
  * @returns {Promise<void>}
20
22
  */
21
23
  function publishEvaluation (messages) {
22
- return new Promise((resolve, reject) => {
23
- aiguardChannel.publish({ messages, integration: 'ai', resolve, reject })
24
+ const abortController = new AbortController()
25
+ const ctx = { messages, integration: 'ai', abortController, pending: [] }
26
+
27
+ aiguardChannel.publish(ctx)
28
+
29
+ return Promise.all(ctx.pending).then(() => {
30
+ if (abortController.signal.aborted) {
31
+ throw abortController.signal.reason
32
+ }
24
33
  })
25
34
  }
26
35
 
@@ -6,7 +6,10 @@ const { channel, addHook, AsyncResource } = require('./helpers/instrument')
6
6
  const bodyParserReadCh = channel('datadog:body-parser:read:finish')
7
7
 
8
8
  function publishRequestBodyAndNext (req, res, next) {
9
- return shimmer.wrapFunction(next, next => function (...args) {
9
+ // Named `next`/arity-1 mirrors the express continuation so wrapCallback skips
10
+ // its name/length rewrite; `_error` only pads the arity and `arguments`
11
+ // forwards whatever next was called with.
12
+ return shimmer.wrapCallback(next, original => function next (_error) {
10
13
  if (bodyParserReadCh.hasSubscribers && req) {
11
14
  const abortController = new AbortController()
12
15
  const body = req.body
@@ -16,7 +19,7 @@ function publishRequestBodyAndNext (req, res, next) {
16
19
  if (abortController.signal.aborted) return
17
20
  }
18
21
 
19
- return next.apply(this, args)
22
+ return original.apply(this, arguments)
20
23
  })
21
24
  }
22
25
 
@@ -90,7 +90,8 @@ function wrapLayerHandle (layer) {
90
90
  }
91
91
 
92
92
  function wrapNext (req, next) {
93
- return shimmer.wrapFunction(next, next => function (error) {
93
+ // Mirror next's name/arity so wrapCallback skips its per-call identity rewrite.
94
+ return shimmer.wrapCallback(next, original => function next (error) {
94
95
  if (error) {
95
96
  errorChannel.publish({ req, error })
96
97
  }
@@ -98,7 +99,7 @@ function wrapNext (req, next) {
98
99
  nextChannel.publish({ req })
99
100
  finishChannel.publish({ req })
100
101
 
101
- next.apply(this, arguments)
102
+ original.apply(this, arguments)
102
103
  })
103
104
  }
104
105
 
@@ -6,7 +6,8 @@ const { channel, addHook } = require('./helpers/instrument')
6
6
  const cookieParserReadCh = channel('datadog:cookie-parser:read:finish')
7
7
 
8
8
  function publishRequestCookieAndNext (req, res, next) {
9
- return shimmer.wrapFunction(next, next => function cookieParserWrapper (...args) {
9
+ // Mirror next's name/arity so wrapCallback skips its per-call identity rewrite.
10
+ return shimmer.wrapCallback(next, original => function next (_error) {
10
11
  if (cookieParserReadCh.hasSubscribers && req) {
11
12
  const abortController = new AbortController()
12
13
 
@@ -17,7 +18,7 @@ function publishRequestCookieAndNext (req, res, next) {
17
18
  if (abortController.signal.aborted) return
18
19
  }
19
20
 
20
- return next.apply(this, args)
21
+ return original.apply(this, arguments)
21
22
  })
22
23
  }
23
24
 
@@ -1252,6 +1252,11 @@ function getWrappedRunTestCase (runTestCaseFunction, isNewerCucumberVersion = fa
1252
1252
  numRetriesByPickleId.set(pickle.id, 0)
1253
1253
  }
1254
1254
  }
1255
+ const originalRetry = this.options.retry
1256
+ const isManagedRetry = isAttemptToFix || (isEarlyFlakeDetectionEnabled && (isNew || isModified))
1257
+ if (isManagedRetry) {
1258
+ this.options.retry = 0
1259
+ }
1255
1260
  // TODO: for >=11 we could use `runTestCaseResult` instead of accumulating results in `lastStatusByPickleId`
1256
1261
  const firstExecutionStart = performance.now()
1257
1262
  let runTestCaseResult = await runTestCaseFunction.apply(this, arguments)
@@ -1351,6 +1356,8 @@ function getWrappedRunTestCase (runTestCaseFunction, isNewerCucumberVersion = fa
1351
1356
  testSuiteFinishCh.publish({ status: testSuiteStatus, testSuitePath })
1352
1357
  }
1353
1358
 
1359
+ this.options.retry = originalRetry
1360
+
1354
1361
  if (isNewerCucumberVersion && isEarlyFlakeDetectionEnabled && (isNew || isModified)) {
1355
1362
  return shouldBePassedByEFD
1356
1363
  }
@@ -25,21 +25,23 @@ addHook({ name: 'express-mongo-sanitize', versions: ['>=1.0.0'] }, expressMongoS
25
25
  return shimmer.wrapFunction(expressMongoSanitize, expressMongoSanitize => function (...args) {
26
26
  const middleware = expressMongoSanitize.apply(this, args)
27
27
 
28
- return shimmer.wrapFunction(middleware, middleware => function (req, res, next) {
28
+ return shimmer.wrapFunction(middleware, middleware => function (...args) {
29
29
  if (!sanitizeMiddlewareFinished.hasSubscribers) {
30
- return middleware.apply(this, arguments)
30
+ return Reflect.apply(middleware, this, args)
31
31
  }
32
32
 
33
- const wrappedNext = shimmer.wrapFunction(next, next => function (...args) {
33
+ const req = args[0]
34
+ // Mirror next's name/arity so wrapCallback skips its per-call identity rewrite.
35
+ const wrappedNext = shimmer.wrapCallback(args[2], original => function next (_error) {
34
36
  sanitizeMiddlewareFinished.publish({
35
37
  sanitizedProperties: propertiesToSanitize,
36
38
  req,
37
39
  })
38
40
 
39
- return next.apply(this, args)
41
+ return original.apply(this, arguments)
40
42
  })
41
43
 
42
- return middleware.call(this, req, res, wrappedNext)
44
+ return middleware.call(this, req, args[1], wrappedNext)
43
45
  })
44
46
  })
45
47
  })
@@ -6,22 +6,23 @@ const { channel, addHook } = require('./helpers/instrument')
6
6
  const sessionMiddlewareFinishCh = channel('datadog:express-session:middleware:finish')
7
7
 
8
8
  function wrapSessionMiddleware (sessionMiddleware) {
9
- return function wrappedSessionMiddleware (req, res, next) {
10
- shimmer.wrap(arguments, 2, function wrapNext (next) {
11
- return function wrappedNext (...args) {
12
- if (sessionMiddlewareFinishCh.hasSubscribers) {
13
- const abortController = new AbortController()
9
+ return function wrappedSessionMiddleware (...args) {
10
+ const req = args[0]
11
+ const res = args[1]
12
+ // Mirror next's name/arity so wrapCallback skips its per-call identity rewrite.
13
+ args[2] = shimmer.wrapCallback(args[2], original => function next (_error) {
14
+ if (sessionMiddlewareFinishCh.hasSubscribers) {
15
+ const abortController = new AbortController()
14
16
 
15
- sessionMiddlewareFinishCh.publish({ req, res, sessionId: req.sessionID, abortController })
17
+ sessionMiddlewareFinishCh.publish({ req, res, sessionId: req.sessionID, abortController })
16
18
 
17
- if (abortController.signal.aborted) return
18
- }
19
-
20
- return next.apply(this, args)
19
+ if (abortController.signal.aborted) return
21
20
  }
21
+
22
+ return original.apply(this, arguments)
22
23
  })
23
24
 
24
- return sessionMiddleware.apply(this, arguments)
25
+ return Reflect.apply(sessionMiddleware, this, args)
25
26
  }
26
27
  }
27
28
 
@@ -18,57 +18,60 @@ const handleChannel = channel('apm:express:request:handle')
18
18
  const routeAddedChannel = channel('apm:express:route:added')
19
19
 
20
20
  function wrapHandle (handle) {
21
- return function handleWithTrace (req, res) {
21
+ return function handleWithTrace (...args) {
22
22
  if (handleChannel.hasSubscribers) {
23
- handleChannel.publish({ req })
23
+ handleChannel.publish({ req: args[0] })
24
24
  }
25
25
 
26
- return handle.apply(this, arguments)
26
+ return Reflect.apply(handle, this, args)
27
27
  }
28
28
  }
29
29
 
30
30
  const responseJsonChannel = channel('datadog:express:response:json:start')
31
31
 
32
32
  function wrapResponseJson (json) {
33
- return function wrappedJson (obj) {
33
+ return function wrappedJson (...args) {
34
34
  if (responseJsonChannel.hasSubscribers) {
35
- // backward compat as express 4.x supports deprecated 3.x signature
36
- if (arguments.length === 2 && typeof arguments[1] !== 'number') {
37
- obj = arguments[1]
35
+ let obj = args[0]
36
+ // Deprecated express 3.x res.json(status, body) form, still honored by 4.x but not
37
+ // exercised by any suite (the unit harness can't drive res.json's freshness path).
38
+ /* istanbul ignore if */
39
+ if (args.length === 2 && typeof args[1] !== 'number') {
40
+ obj = args[1]
38
41
  }
39
42
 
40
43
  responseJsonChannel.publish({ req: this.req, res: this, body: obj })
41
44
  }
42
45
 
43
- return json.apply(this, arguments)
46
+ return Reflect.apply(json, this, args)
44
47
  }
45
48
  }
46
49
 
47
50
  const responseRenderChannel = tracingChannel('datadog:express:response:render')
48
51
 
49
52
  function wrapResponseRender (render) {
50
- return function wrappedRender (view, options, callback) {
53
+ return function wrappedRender (...args) {
51
54
  if (!responseRenderChannel.start.hasSubscribers) {
52
- return render.apply(this, arguments)
55
+ return Reflect.apply(render, this, args)
53
56
  }
54
57
 
55
58
  const abortController = new AbortController()
56
59
  return responseRenderChannel.traceSync(
57
- function (...args) {
60
+ function (...renderArgs) {
58
61
  if (abortController.signal.aborted) {
59
62
  throw abortController.signal.reason || new Error('Aborted')
60
63
  }
61
64
 
62
- return render.apply(this, args)
65
+ return Reflect.apply(render, this, renderArgs)
63
66
  },
64
67
  {
65
68
  req: this.req,
66
- view,
67
- options,
69
+ view: args[0],
70
+ options: args[1],
68
71
  abortController,
69
72
  },
70
73
  this,
71
- ...arguments
74
+ ...args
72
75
  )
73
76
  }
74
77
  }
@@ -172,7 +175,8 @@ addHook({ name: 'express', versions: ['4'], file: 'lib/express.js' }, express =>
172
175
  const queryParserReadCh = channel('datadog:query:read:finish')
173
176
 
174
177
  function publishQueryParsedAndNext (req, res, next) {
175
- return shimmer.wrapFunction(next, next => function (...args) {
178
+ // Mirror next's name/arity so wrapCallback skips its per-call identity rewrite.
179
+ return shimmer.wrapCallback(next, original => function next (_error) {
176
180
  if (queryParserReadCh.hasSubscribers && req) {
177
181
  const abortController = new AbortController()
178
182
  const query = req.query
@@ -182,7 +186,7 @@ function publishQueryParsedAndNext (req, res, next) {
182
186
  if (abortController.signal.aborted) return
183
187
  }
184
188
 
185
- return next.apply(this, args)
189
+ return original.apply(this, arguments)
186
190
  })
187
191
  }
188
192
 
@@ -194,9 +198,9 @@ addHook({
194
198
  return shimmer.wrapFunction(query, query => function (...args) {
195
199
  const queryMiddleware = query.apply(this, args)
196
200
 
197
- return shimmer.wrapFunction(queryMiddleware, queryMiddleware => function (req, res, next) {
198
- arguments[2] = publishQueryParsedAndNext(req, res, next)
199
- return queryMiddleware.apply(this, arguments)
201
+ return shimmer.wrapFunction(queryMiddleware, queryMiddleware => function (...args) {
202
+ args[2] = publishQueryParsedAndNext(args[0], args[1], args[2])
203
+ return Reflect.apply(queryMiddleware, this, args)
200
204
  })
201
205
  })
202
206
  })
@@ -207,11 +207,14 @@ function preHandler (request, reply, done) {
207
207
 
208
208
  function preValidation (request, reply, done) {
209
209
  const req = getReq(request)
210
- const res = getRes(reply)
211
210
  const ctx = parsingContexts.get(req)
212
- ctx.res = res
213
211
 
214
- if (!ctx) return processInContext(request, ctx, done, req)
212
+ // No stored context means the onRequest/preParsing fast path ran (no error /
213
+ // cookie / callback subscribers), so there is nothing to publish on; forward
214
+ // `done` instead of dereferencing a missing ctx in processInContext.
215
+ if (!ctx) return done()
216
+
217
+ ctx.res = getRes(reply)
215
218
 
216
219
  preValidationCh.runStores(ctx, processInContext, undefined, request, ctx, done, req)
217
220
  }
@@ -276,7 +279,7 @@ function wrapSend (send, req) {
276
279
  const ctx = { req }
277
280
  if (payload instanceof Error) {
278
281
  ctx.error = payload
279
- errorChannel.publish(ctx)
282
+ publishError(ctx)
280
283
  } else if (canPublishResponsePayload(payload)) {
281
284
  const res = getRes(this)
282
285
  ctx.res = res
@@ -300,9 +303,18 @@ function getRouteConfig (request) {
300
303
  return request?.routeOptions?.config
301
304
  }
302
305
 
306
+ let publishingError = false
307
+
303
308
  function publishError (ctx) {
304
- if (ctx.error) {
305
- errorChannel.publish(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
+ }
306
318
  }
307
319
 
308
320
  return ctx.error