dd-trace 5.109.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 (128) hide show
  1. package/ci/init.js +4 -3
  2. package/index.d.ts +66 -24
  3. package/loader-hook.mjs +88 -2
  4. package/package.json +18 -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/connect.js +4 -3
  8. package/packages/datadog-instrumentations/src/fastify.js +4 -12
  9. package/packages/datadog-instrumentations/src/graphql.js +17 -6
  10. package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +81 -8
  11. package/packages/datadog-instrumentations/src/helpers/instrument.js +27 -2
  12. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +65 -0
  13. package/packages/datadog-instrumentations/src/helpers/register.js +6 -4
  14. package/packages/datadog-instrumentations/src/helpers/require-optional-peer.js +17 -0
  15. package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +18 -2
  16. package/packages/datadog-instrumentations/src/koa.js +3 -2
  17. package/packages/datadog-instrumentations/src/mariadb.js +4 -2
  18. package/packages/datadog-instrumentations/src/mocha/main.js +3 -3
  19. package/packages/datadog-instrumentations/src/next.js +10 -10
  20. package/packages/datadog-instrumentations/src/otel-sdk-trace.js +7 -6
  21. package/packages/datadog-instrumentations/src/playwright.js +1 -1
  22. package/packages/datadog-instrumentations/src/restify.js +4 -3
  23. package/packages/datadog-instrumentations/src/router.js +18 -5
  24. package/packages/datadog-instrumentations/src/selenium.js +1 -2
  25. package/packages/datadog-instrumentations/src/tedious.js +28 -0
  26. package/packages/datadog-instrumentations/src/vitest.js +264 -7
  27. package/packages/datadog-plugin-avsc/src/schema_iterator.js +2 -2
  28. package/packages/datadog-plugin-aws-sdk/src/base.js +13 -9
  29. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +43 -6
  30. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +83 -91
  31. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +9 -1
  32. package/packages/datadog-plugin-http/src/client.js +8 -3
  33. package/packages/datadog-plugin-http2/src/client.js +5 -2
  34. package/packages/datadog-plugin-mysql/src/index.js +7 -2
  35. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +2 -2
  36. package/packages/datadog-plugin-undici/src/index.js +5 -2
  37. package/packages/datadog-plugin-vitest/src/index.js +7 -1
  38. package/packages/datadog-webpack/index.js +17 -1
  39. package/packages/datadog-webpack/src/optional-peer-loader.js +17 -0
  40. package/packages/dd-trace/src/aiguard/sdk.js +13 -1
  41. package/packages/dd-trace/src/appsec/api_security/index.js +55 -0
  42. package/packages/dd-trace/src/appsec/api_security/sampler.js +147 -0
  43. package/packages/dd-trace/src/appsec/index.js +10 -6
  44. package/packages/dd-trace/src/appsec/reporter.js +1 -0
  45. package/packages/dd-trace/src/appsec/telemetry/api_security.js +34 -0
  46. package/packages/dd-trace/src/appsec/telemetry/index.js +26 -0
  47. package/packages/dd-trace/src/ci-visibility/requests/request.js +1 -17
  48. package/packages/dd-trace/src/config/defaults.js +22 -3
  49. package/packages/dd-trace/src/config/generated-config-types.d.ts +535 -1
  50. package/packages/dd-trace/src/config/helper.js +133 -33
  51. package/packages/dd-trace/src/config/index.js +4 -1
  52. package/packages/dd-trace/src/config/parsers.js +19 -1
  53. package/packages/dd-trace/src/config/supported-configurations.json +21 -3
  54. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +47 -35
  55. package/packages/dd-trace/src/debugger/devtools_client/index.js +31 -66
  56. package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +83 -0
  57. package/packages/dd-trace/src/debugger/devtools_client/state.js +1 -0
  58. package/packages/dd-trace/src/debugger/index.js +5 -0
  59. package/packages/dd-trace/src/debugger/probe_sampler.js +112 -0
  60. package/packages/dd-trace/src/debugger/probe_sampler_constants.js +19 -0
  61. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +77 -26
  62. package/packages/dd-trace/src/exporters/common/request.js +1 -20
  63. package/packages/dd-trace/src/exporters/common/url.js +34 -0
  64. package/packages/dd-trace/src/exporters/span-stats/writer.js +1 -3
  65. package/packages/dd-trace/src/guardrails/index.js +8 -5
  66. package/packages/dd-trace/src/index.js +6 -4
  67. package/packages/dd-trace/src/llmobs/constants/tags.js +6 -0
  68. package/packages/dd-trace/src/llmobs/index.js +16 -5
  69. package/packages/dd-trace/src/llmobs/plugins/genai/util.js +3 -3
  70. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +5 -1
  71. package/packages/dd-trace/src/llmobs/sdk.js +5 -3
  72. package/packages/dd-trace/src/llmobs/span_processor.js +4 -0
  73. package/packages/dd-trace/src/llmobs/tagger.js +55 -0
  74. package/packages/dd-trace/src/llmobs/telemetry.js +1 -0
  75. package/packages/dd-trace/src/log/index.js +18 -20
  76. package/packages/dd-trace/src/openfeature/flagging_provider.js +2 -7
  77. package/packages/dd-trace/src/opentelemetry/bridge-span-base.js +5 -4
  78. package/packages/dd-trace/src/opentelemetry/metrics/instruments.js +2 -1
  79. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +4 -3
  80. package/packages/dd-trace/src/opentelemetry/metrics/time.js +19 -0
  81. package/packages/dd-trace/src/opentelemetry/otlp/common.proto +2 -2
  82. package/packages/dd-trace/src/opentelemetry/otlp/logs.proto +1 -1
  83. package/packages/dd-trace/src/opentelemetry/otlp/logs_service.proto +1 -1
  84. package/packages/dd-trace/src/opentelemetry/otlp/metrics.proto +1 -1
  85. package/packages/dd-trace/src/opentelemetry/otlp/metrics_service.proto +1 -1
  86. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +11 -4
  87. package/packages/dd-trace/src/opentelemetry/span-helpers.js +29 -13
  88. package/packages/dd-trace/src/opentelemetry/span.js +14 -3
  89. package/packages/dd-trace/src/opentelemetry/span_context.js +1 -0
  90. package/packages/dd-trace/src/opentelemetry/trace/index.js +2 -1
  91. package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +3 -2
  92. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +26 -13
  93. package/packages/dd-trace/src/opentracing/propagation/text_map.js +3 -3
  94. package/packages/dd-trace/src/opentracing/span_context.js +17 -0
  95. package/packages/dd-trace/src/plugin_manager.js +7 -1
  96. package/packages/dd-trace/src/plugins/ci_plugin.js +9 -1
  97. package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +276 -0
  98. package/packages/dd-trace/src/plugins/util/test.js +80 -0
  99. package/packages/dd-trace/src/plugins/util/url.js +68 -1
  100. package/packages/dd-trace/src/plugins/util/web.js +19 -36
  101. package/packages/dd-trace/src/priority_sampler.js +1 -13
  102. package/packages/dd-trace/src/profiling/config.js +129 -198
  103. package/packages/dd-trace/src/profiling/constants.js +4 -7
  104. package/packages/dd-trace/src/profiling/exporter_cli.js +10 -10
  105. package/packages/dd-trace/src/profiling/exporters/agent.js +23 -16
  106. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +45 -14
  107. package/packages/dd-trace/src/profiling/exporters/file.js +16 -5
  108. package/packages/dd-trace/src/profiling/oom.js +71 -0
  109. package/packages/dd-trace/src/profiling/profiler.js +35 -23
  110. package/packages/dd-trace/src/profiling/profilers/events.js +14 -7
  111. package/packages/dd-trace/src/profiling/profilers/space.js +27 -18
  112. package/packages/dd-trace/src/profiling/profilers/wall.js +35 -25
  113. package/packages/dd-trace/src/ritm.js +4 -2
  114. package/packages/dd-trace/src/serverless.js +1 -2
  115. package/packages/dd-trace/src/service-naming/schemas/util.js +36 -5
  116. package/packages/dd-trace/src/service-naming/schemas/v1/web.js +5 -3
  117. package/packages/dd-trace/src/span_format.js +2 -1
  118. package/packages/dd-trace/src/span_processor.js +6 -0
  119. package/packages/dd-trace/src/telemetry/send-data.js +12 -10
  120. package/packages/dd-trace/src/tracer.js +15 -1
  121. package/packages/dd-trace/src/util.js +14 -0
  122. package/register.js +58 -1
  123. package/vendor/dist/@apm-js-collab/code-transformer/index.js +1 -1
  124. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  125. package/vendor/dist/pprof-format/index.js +1 -1
  126. package/vendor/dist/protobufjs/index.js +1 -1
  127. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  128. package/packages/dd-trace/src/appsec/api_security_sampler.js +0 -117
@@ -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
+ }
@@ -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
  */
@@ -0,0 +1,65 @@
1
+ 'use strict'
2
+
3
+ const path = require('node:path')
4
+
5
+ // Build-time half of the optional-peer mechanism shared by the webpack and esbuild plugins.
6
+ //
7
+ // Runtime files load an optional peer through `requireOptionalPeer('name')` (see
8
+ // `require-optional-peer.js`), which bundlers cannot follow, so a build that does not opt into
9
+ // the feature never pulls in the peer's (possibly optional) dependency chain (#8635). When the
10
+ // peer is installed at build time the user has opted in, so the plugins rewrite the call into a
11
+ // literal `require('name')` and let the bundler inline the peer, which keeps it working after
12
+ // the bundle is relocated to a tree without the peer on disk (#8980). Peers that are absent at
13
+ // build time stay opaque, so the rewrite is a no-op and the #8635 guarantee holds.
14
+
15
+ // Files that load an optional peer this way, as suffixes of the resolved module path. The same
16
+ // suffix matches the repo layout and `node_modules/dd-trace`. Add a file here to extend the
17
+ // mechanism to a new optional peer; no plugin change is needed.
18
+ const OPTIONAL_PEER_FILES = [
19
+ 'packages/dd-trace/src/openfeature/flagging_provider.js',
20
+ ]
21
+
22
+ // Captures the peer name from `requireOptionalPeer('name')` / `requireOptionalPeer("name")`.
23
+ const OPTIONAL_PEER_LOAD = /requireOptionalPeer\((['"])(.+?)\1\)/g
24
+
25
+ // esbuild's `onLoad` needs a path filter; match the basenames so the callback only fires for
26
+ // the candidate files, then `matchesOptionalPeerFile` confirms the full suffix on a normalized
27
+ // path (basenames carry no separators, so the filter is OS-agnostic).
28
+ const OPTIONAL_PEER_FILTER = new RegExp(
29
+ `(?:${OPTIONAL_PEER_FILES.map((file) => path.basename(file).replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`)).join('|')})$`
30
+ )
31
+
32
+ /**
33
+ * @param {string} normalizedResource - Resolved module path with forward slashes
34
+ * @returns {boolean} Whether the module is one of the optional-peer loader files
35
+ */
36
+ function matchesOptionalPeerFile (normalizedResource) {
37
+ return OPTIONAL_PEER_FILES.some((suffix) => normalizedResource.endsWith(suffix))
38
+ }
39
+
40
+ /**
41
+ * Rewrites each `requireOptionalPeer('name')` whose peer resolves from `fromDir` into a literal
42
+ * `require('name')` so the bundler inlines it. Peers that do not resolve stay opaque, so a build
43
+ * without the peer keeps the #8635 guarantee.
44
+ *
45
+ * @param {string} source - Source of an optional-peer loader file
46
+ * @param {string} fromDir - Directory to resolve the optional peers from
47
+ * @returns {string} Source with installed optional peers turned into literal requires
48
+ */
49
+ function rewriteOptionalPeerLoads (source, fromDir) {
50
+ return source.replaceAll(OPTIONAL_PEER_LOAD, (match, _quote, request) => {
51
+ try {
52
+ require.resolve(request, { paths: [fromDir] })
53
+ } catch {
54
+ return match
55
+ }
56
+ return `require('${request}')`
57
+ })
58
+ }
59
+
60
+ module.exports = {
61
+ OPTIONAL_PEER_FILES,
62
+ OPTIONAL_PEER_FILTER,
63
+ matchesOptionalPeerFile,
64
+ rewriteOptionalPeerLoads,
65
+ }
@@ -13,8 +13,9 @@ const Hook = require('./hook')
13
13
  const { isRelativeRequire } = require('./shared-utils')
14
14
  const rewriter = require('./rewriter')
15
15
 
16
- const DD_TRACE_DISABLED_INSTRUMENTATIONS = getValueFromEnvSources('DD_TRACE_DISABLED_INSTRUMENTATIONS') || ''
17
- const DD_TRACE_DEBUG = getValueFromEnvSources('DD_TRACE_DEBUG') || ''
16
+ const DD_TRACE_DISABLED_INSTRUMENTATIONS =
17
+ getValueFromEnvSources('DD_TRACE_DISABLED_INSTRUMENTATIONS')
18
+ const DD_TRACE_DEBUG = getValueFromEnvSources('DD_TRACE_DEBUG')
18
19
 
19
20
  const hooks = require('./hooks')
20
21
  const instrumentations = require('./instrumentations')
@@ -36,8 +37,9 @@ if (!disabledInstrumentations.has('process')) {
36
37
  require('../process')
37
38
  }
38
39
 
39
- if (DD_TRACE_DEBUG && DD_TRACE_DEBUG.toLowerCase() !== 'false') {
40
- checkRequireCache.checkForRequiredModules()
40
+ const debugEnabled = DD_TRACE_DEBUG
41
+ checkRequireCache.checkForRequiredModules()
42
+ if (debugEnabled) {
41
43
  setImmediate(checkRequireCache.checkForPotentialConflicts)
42
44
  }
43
45
 
@@ -0,0 +1,17 @@
1
+ 'use strict'
2
+
3
+ // Loads an optional peer through a require bundlers cannot follow: the package name is an
4
+ // argument, not a `require('literal')`, so a build that does not install the peer never pulls
5
+ // in its (possibly optional) dependency chain (#8635). When the peer is installed at build
6
+ // time, the webpack and esbuild plugins rewrite `requireOptionalPeer('name')` into a literal
7
+ // `require('name')` so the peer is bundled and survives the bundle being relocated without it
8
+ // on disk (#8980). See `optional-peer-bundler.js` for the build-time half.
9
+
10
+ /**
11
+ * @param {string} request - Module specifier of the optional peer
12
+ */
13
+ module.exports = function requireOptionalPeer (request) {
14
+ // eslint-disable-next-line camelcase, no-undef
15
+ const runtimeRequire = typeof __non_webpack_require__ === 'function' ? __non_webpack_require__ : require
16
+ return runtimeRequire(request)
17
+ }
@@ -3,9 +3,25 @@ import { rewrite } from './index.js'
3
3
  async function load (url, context, nextLoad) {
4
4
  const result = await nextLoad(url, context)
5
5
 
6
- result.source = rewrite(result.source, url, context.format)
6
+ return rewriteResult(result, url, context)
7
+ }
8
+
9
+ function loadSync (url, context, nextLoad) {
10
+ const result = nextLoad(url, context)
11
+
12
+ return rewriteResult(result, url, context)
13
+ }
14
+
15
+ function rewriteResult (result, url, context) {
16
+ const format = result.format || context.format
17
+
18
+ // CommonJS source is rewritten by Module._compile. Rewriting it here too
19
+ // double-instruments CommonJS entrypoints loaded through sync hooks.
20
+ if (format === 'commonjs') return result
21
+
22
+ result.source = rewrite(result.source, url, format)
7
23
 
8
24
  return result
9
25
  }
10
26
 
11
- export { load }
27
+ export { load, loadSync }
@@ -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:koa:middleware:enter')
7
7
  const exitChannel = channel('apm:koa:middleware:exit')
@@ -10,6 +10,7 @@ const nextChannel = channel('apm:koa:middleware:next')
10
10
  const finishChannel = channel('apm:koa:middleware:finish')
11
11
  const handleChannel = channel('apm:koa:request:handle')
12
12
  const routeChannel = channel('apm:koa:request:route')
13
+ const publishError = createErrorPublisher(errorChannel)
13
14
 
14
15
  const originals = new WeakMap()
15
16
 
@@ -132,7 +133,7 @@ function fulfill (ctx, error) {
132
133
  const route = ctx.routePath
133
134
 
134
135
  if (error) {
135
- errorChannel.publish({ req, error })
136
+ publishError({ req, error })
136
137
  }
137
138
 
138
139
  // TODO: make sure that the parent class cannot override this in `enter`
@@ -160,14 +160,16 @@ addHook({ name, file: 'lib/cmd/execute.js', versions: ['>=3'] }, (Execute) => {
160
160
  return wrapCommand(Execute)
161
161
  })
162
162
 
163
- // in 3.4.1 getConnection method start to use callbacks instead of promises
163
+ // mariadb 3.4.1 refactored the pool: getConnection switched from promises to
164
+ // callbacks and _createConnection was renamed to _createPoolConnection.
164
165
  addHook({ name, file: 'lib/pool.js', versions: ['>=3.4.1'] }, (Pool) => {
165
166
  shimmer.wrap(Pool.prototype, 'getConnection', wrapPoolGetConnectionMethod)
167
+ shimmer.wrap(Pool.prototype, '_createPoolConnection', wrapPoolMethod)
166
168
 
167
169
  return Pool
168
170
  })
169
171
 
170
- addHook({ name, file: 'lib/pool.js', versions: ['>=3'] }, (Pool) => {
172
+ addHook({ name, file: 'lib/pool.js', versions: ['>=3 <3.4.1'] }, (Pool) => {
171
173
  shimmer.wrap(Pool.prototype, '_createConnection', wrapPoolMethod)
172
174
 
173
175
  return Pool
@@ -1093,9 +1093,9 @@ addHook({
1093
1093
  shimmer.wrap(BufferedWorkerPool.prototype, 'run', run => async function (testSuiteAbsolutePath, workerArgs) {
1094
1094
  if (!testFinishCh.hasSubscribers ||
1095
1095
  (!config.isKnownTestsEnabled &&
1096
- !config.isTestManagementTestsEnabled &&
1097
- !config.isImpactedTestsEnabled &&
1098
- !config.isFlakyTestRetriesEnabled)) {
1096
+ !config.isTestManagementTestsEnabled &&
1097
+ !config.isImpactedTestsEnabled &&
1098
+ !config.isFlakyTestRetriesEnabled)) {
1099
1099
  return run.apply(this, arguments)
1100
1100
  }
1101
1101
 
@@ -141,19 +141,19 @@ function instrument (req, res, handler, error) {
141
141
  requests.add(req)
142
142
 
143
143
  const ctx = { req, res }
144
- // Parse query parameters from request URL
145
144
  if (queryParsedChannel.hasSubscribers && req.url) {
146
- // req.url is only the relative path (/foo?bar=baz) and new URL() needs a full URL
147
- // so we give it a dummy base
148
- const { searchParams } = new URL(req.url, 'http://dummy')
149
- const query = {}
150
- for (const key of searchParams.keys()) {
151
- if (!query[key]) {
152
- query[key] = searchParams.getAll(key)
145
+ const queryIndex = req.url.indexOf('?')
146
+ if (queryIndex !== -1) {
147
+ const searchParams = new URLSearchParams(req.url.slice(queryIndex + 1))
148
+ const query = {}
149
+ for (const key of searchParams.keys()) {
150
+ if (!query[key]) {
151
+ query[key] = searchParams.getAll(key)
152
+ }
153
153
  }
154
- }
155
154
 
156
- queryParsedChannel.publish({ query })
155
+ queryParsedChannel.publish({ query })
156
+ }
157
157
  }
158
158
 
159
159
  return startChannel.runStores(ctx, () => {
@@ -3,7 +3,6 @@
3
3
  const shimmer = require('../../datadog-shimmer')
4
4
  const tracer = require('../../dd-trace')
5
5
  const { getValueFromEnvSources } = require('../../dd-trace/src/config/helper')
6
- const { isFalse, isTrue } = require('../../dd-trace/src/util')
7
6
  const { addHook } = require('./helpers/instrument')
8
7
 
9
8
  if (isOtelSdkEnabled()) {
@@ -21,9 +20,11 @@ if (isOtelSdkEnabled()) {
21
20
 
22
21
  function isOtelSdkEnabled () {
23
22
  // Datadog explicit opt-out wins over every OTel signal; check it first.
24
- const ddTraceOtelEnabled = getValueFromEnvSources('DD_TRACE_OTEL_ENABLED')
25
- if (isFalse(ddTraceOtelEnabled)) return false
26
- const otelSdkDisabled = getValueFromEnvSources('OTEL_SDK_DISABLED')
27
- if (isTrue(otelSdkDisabled)) return false
28
- return isTrue(ddTraceOtelEnabled) || isFalse(otelSdkDisabled)
23
+ // skipDefault: an unset option must stay undefined so the OTel signal still decides — the
24
+ // registered defaults (false / true) would otherwise force-disable before that check.
25
+ const ddTraceOtelEnabled = getValueFromEnvSources('DD_TRACE_OTEL_ENABLED', true)
26
+ if (ddTraceOtelEnabled === false) return false
27
+ const otelSdkDisabled = getValueFromEnvSources('OTEL_SDK_DISABLED', true)
28
+ if (otelSdkDisabled) return false
29
+ return ddTraceOtelEnabled || otelSdkDisabled === false
29
30
  }
@@ -59,7 +59,7 @@ const testSuiteToTestStatuses = new Map()
59
59
  const testSuiteToErrors = new Map()
60
60
  const testsToTestStatuses = new Map()
61
61
 
62
- const RUM_FLUSH_WAIT_TIME = Number(getValueFromEnvSources('DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS')) || 500
62
+ const RUM_FLUSH_WAIT_TIME = getValueFromEnvSources('DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS')
63
63
  const DD_PROPERTIES_TIMEOUT = 5000
64
64
 
65
65
  let applyRepeatEachIndex = null
@@ -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
  const handlers = ['use', 'pre']
6
6
  const methods = ['del', 'get', 'head', 'opts', 'post', 'put', 'patch']
7
7
 
@@ -11,6 +11,7 @@ const enterChannel = channel('apm:restify:middleware:enter')
11
11
  const exitChannel = channel('apm:restify:middleware:exit')
12
12
  const finishChannel = channel('apm:restify:middleware:finish')
13
13
  const nextChannel = channel('apm:restify:middleware:next')
14
+ const publishError = createErrorPublisher(errorChannel)
14
15
 
15
16
  function wrapSetupRequest (setupRequest) {
16
17
  return function (req, res) {
@@ -53,7 +54,7 @@ function wrapFn (fn) {
53
54
  finishChannel.publish({ req })
54
55
  return result
55
56
  }).catch(function (error) {
56
- errorChannel.publish({ req, error })
57
+ publishError({ req, error })
57
58
  nextChannel.publish({ req })
58
59
  finishChannel.publish({ req })
59
60
  throw error
@@ -61,7 +62,7 @@ function wrapFn (fn) {
61
62
  }
62
63
  return result
63
64
  } catch (error) {
64
- errorChannel.publish({ req, error })
65
+ publishError({ req, error })
65
66
  nextChannel.publish({ req })
66
67
  finishChannel.publish({ req })
67
68
  throw error
@@ -2,7 +2,7 @@
2
2
 
3
3
  const METHODS = [...require('http').METHODS.map(v => v.toLowerCase()), 'all']
4
4
  const shimmer = require('../../datadog-shimmer')
5
- const { addHook, channel } = require('./helpers/instrument')
5
+ const { addHook, channel, createErrorPublisher } = require('./helpers/instrument')
6
6
  const { getCompileToRegexp } = require('./path-to-regexp')
7
7
 
8
8
  const {
@@ -40,6 +40,8 @@ function createWrapRouterMethod (name, compile) {
40
40
  const errorChannel = channel(`apm:${name}:middleware:error`)
41
41
  const nextChannel = channel(`apm:${name}:middleware:next`)
42
42
  const routeAddedChannel = channel(`apm:${name}:route:added`)
43
+ // Bound per name so express and a bare router keep independent guards.
44
+ const publishError = createErrorPublisher(errorChannel)
43
45
 
44
46
  function wrapLayerHandle (layer, original, matchers) {
45
47
  // Resolve `name` once at wrap time: cached on the original for any code
@@ -65,9 +67,20 @@ function createWrapRouterMethod (name, compile) {
65
67
  // `Layer.handleError`. Specialising lets the per-call body use named
66
68
  // parameters and `.call`, avoiding the rest-spread Array allocation that
67
69
  // the unified shape forced on every middleware invocation.
68
- return original.length === 4
70
+ const wrapped = original.length === 4
69
71
  ? shimmer.wrapFunction(original, errorHandlerLayerWrap(layer, name, captureRoute, needMultiMatch, matchers))
70
72
  : shimmer.wrapFunction(original, requestHandlerLayerWrap(layer, name, captureRoute, needMultiMatch, matchers))
73
+
74
+ // Workaround for loopback's phase-based middleware sorting. Its
75
+ // `_findLayerByHandler` maps a layer back to the user handler by scanning
76
+ // the layer handle's enumerable properties for the original function.
77
+ // Replacing `layer.handle` with this wrapper hides that handler, so without
78
+ // the back-reference loopback cannot tag the layer with its phase and
79
+ // `app.middleware(phase, ...)` handlers run in insertion order instead of
80
+ // phase order. The property name is part of that contract; keep it stable.
81
+ wrapped._datadog_orig = original
82
+
83
+ return wrapped
71
84
  }
72
85
 
73
86
  function requestHandlerLayerWrap (layer, name, captureRoute, needMultiMatch, matchers) {
@@ -91,7 +104,7 @@ function createWrapRouterMethod (name, compile) {
91
104
  try {
92
105
  return original.call(this, req, res, wrappedNext)
93
106
  } catch (error) {
94
- errorChannel.publish({ req, error })
107
+ publishError({ req, error })
95
108
  nextChannel.publish({ req })
96
109
  finishChannel.publish({ req })
97
110
 
@@ -123,7 +136,7 @@ function createWrapRouterMethod (name, compile) {
123
136
  try {
124
137
  return original.call(this, error, req, res, wrappedNext)
125
138
  } catch (caught) {
126
- errorChannel.publish({ req, error: caught })
139
+ publishError({ req, error: caught })
127
140
  nextChannel.publish({ req })
128
141
  finishChannel.publish({ req })
129
142
 
@@ -161,7 +174,7 @@ function createWrapRouterMethod (name, compile) {
161
174
  // router continuation so wrapCallback skips its name/length rewrite.
162
175
  return shimmer.wrapCallback(originalNext, original => function next (error) {
163
176
  if (error && error !== 'route' && error !== 'router') {
164
- errorChannel.publish({ req, error })
177
+ publishError({ req, error })
165
178
  }
166
179
 
167
180
  nextChannel.publish({ req })
@@ -19,8 +19,7 @@ if (window.DD_RUM && window.DD_RUM.stopSession) {
19
19
  `
20
20
  const IS_RUM_ACTIVE_SCRIPT = 'return !!window.DD_RUM'
21
21
 
22
- const DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS =
23
- Number(getValueFromEnvSources('DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS')) || 500
22
+ const DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS = getValueFromEnvSources('DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS')
24
23
  const DD_CIVISIBILITY_TEST_EXECUTION_ID_COOKIE_NAME = 'datadog-ci-visibility-test-execution-id'
25
24
 
26
25
  // TODO: can we increase the supported version range?
@@ -4,12 +4,40 @@ const shimmer = require('../../datadog-shimmer')
4
4
  const {
5
5
  channel,
6
6
  addHook,
7
+ AsyncResource,
7
8
  } = require('./helpers/instrument')
8
9
 
10
+ // Listeners added to the BulkLoad row stream (`bulkLoad.getRowStream()`) by the caller would
11
+ // otherwise run in whatever async context emits the stream event, losing the span that was active
12
+ // when the listener was registered. Bind each registered listener to its registration context so
13
+ // `'finish'`/`'error'`/`'data'` handlers run in the caller's span, as they did before the tracer
14
+ // moved to AsyncLocalStorage. tedious' own internal listeners are attached before `getRowStream`
15
+ // returns, so only caller listeners are bound.
16
+ const ROW_STREAM_LISTENER_METHODS = ['addListener', 'on', 'once', 'prependListener', 'prependOnceListener']
17
+
18
+ function bindRowStreamListeners (rowStream) {
19
+ for (const method of ROW_STREAM_LISTENER_METHODS) {
20
+ shimmer.wrap(rowStream, method, register => function (eventName, listener) {
21
+ if (typeof listener !== 'function') {
22
+ return register.apply(this, arguments)
23
+ }
24
+ return register.call(this, eventName, AsyncResource.bind(listener))
25
+ })
26
+ }
27
+ return rowStream
28
+ }
29
+
9
30
  addHook({ name: 'tedious', versions: ['>=1.0.0'] }, tedious => {
10
31
  const startCh = channel('apm:tedious:request:start')
11
32
  const finishCh = channel('apm:tedious:request:finish')
12
33
  const errorCh = channel('apm:tedious:request:error')
34
+
35
+ if (typeof tedious.BulkLoad?.prototype?.getRowStream === 'function') {
36
+ shimmer.wrap(tedious.BulkLoad.prototype, 'getRowStream', getRowStream => function () {
37
+ return bindRowStreamListeners(getRowStream.apply(this, arguments))
38
+ })
39
+ }
40
+
13
41
  shimmer.wrap(tedious.Connection.prototype, 'makeRequest', makeRequest => function (request) {
14
42
  if (!startCh.hasSubscribers) {
15
43
  return makeRequest.apply(this, arguments)