dd-trace 5.96.0 → 5.98.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 (173) hide show
  1. package/index.d.ts +60 -2
  2. package/package.json +9 -7
  3. package/packages/datadog-esbuild/index.js +20 -9
  4. package/packages/datadog-instrumentations/src/child_process.js +7 -17
  5. package/packages/datadog-instrumentations/src/crypto.js +1 -2
  6. package/packages/datadog-instrumentations/src/cucumber.js +69 -4
  7. package/packages/datadog-instrumentations/src/cypress-config.js +318 -0
  8. package/packages/datadog-instrumentations/src/cypress.js +86 -4
  9. package/packages/datadog-instrumentations/src/dns.js +1 -2
  10. package/packages/datadog-instrumentations/src/express.js +4 -4
  11. package/packages/datadog-instrumentations/src/fs.js +27 -29
  12. package/packages/datadog-instrumentations/src/graphql.js +1 -1
  13. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +41 -13
  14. package/packages/datadog-instrumentations/src/helpers/hook.js +31 -6
  15. package/packages/datadog-instrumentations/src/helpers/hooks.js +12 -19
  16. package/packages/datadog-instrumentations/src/helpers/instrument.js +27 -13
  17. package/packages/datadog-instrumentations/src/helpers/register.js +103 -142
  18. package/packages/datadog-instrumentations/src/http/client.js +2 -3
  19. package/packages/datadog-instrumentations/src/http/server.js +2 -5
  20. package/packages/datadog-instrumentations/src/http2/client.js +1 -3
  21. package/packages/datadog-instrumentations/src/http2/server.js +1 -3
  22. package/packages/datadog-instrumentations/src/jest.js +117 -16
  23. package/packages/datadog-instrumentations/src/limitd-client.js +1 -1
  24. package/packages/datadog-instrumentations/src/mocha/utils.js +12 -1
  25. package/packages/datadog-instrumentations/src/net.js +2 -8
  26. package/packages/datadog-instrumentations/src/pino.js +1 -1
  27. package/packages/datadog-instrumentations/src/playwright.js +4 -1
  28. package/packages/datadog-instrumentations/src/prisma.js +1 -2
  29. package/packages/datadog-instrumentations/src/redis.js +12 -6
  30. package/packages/datadog-instrumentations/src/selenium.js +4 -1
  31. package/packages/datadog-instrumentations/src/sequelize.js +1 -1
  32. package/packages/datadog-instrumentations/src/url.js +1 -3
  33. package/packages/datadog-instrumentations/src/vitest.js +5 -1
  34. package/packages/datadog-instrumentations/src/vm.js +1 -3
  35. package/packages/datadog-plugin-aws-sdk/src/base.js +5 -4
  36. package/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js +1 -0
  37. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +1 -0
  38. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -0
  39. package/packages/datadog-plugin-aws-sdk/src/services/redshift.js +1 -0
  40. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +1 -0
  41. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -0
  42. package/packages/datadog-plugin-cucumber/src/index.js +13 -3
  43. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +166 -6
  44. package/packages/datadog-plugin-cypress/src/index.js +59 -2
  45. package/packages/datadog-plugin-fs/src/index.js +1 -1
  46. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -1
  47. package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +2 -7
  48. package/packages/datadog-plugin-graphql/src/resolve.js +1 -1
  49. package/packages/datadog-plugin-http/src/client.js +1 -1
  50. package/packages/datadog-plugin-http/src/server.js +10 -2
  51. package/packages/datadog-plugin-http2/src/client.js +1 -1
  52. package/packages/datadog-plugin-http2/src/server.js +10 -2
  53. package/packages/datadog-plugin-jest/src/index.js +4 -2
  54. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +31 -4
  55. package/packages/datadog-plugin-mocha/src/index.js +5 -2
  56. package/packages/datadog-plugin-mongodb-core/src/index.js +3 -3
  57. package/packages/datadog-plugin-mysql/src/index.js +1 -1
  58. package/packages/datadog-plugin-next/src/index.js +10 -16
  59. package/packages/datadog-plugin-openai/src/services.js +1 -0
  60. package/packages/datadog-plugin-pg/src/index.js +1 -1
  61. package/packages/datadog-plugin-tedious/src/index.js +1 -1
  62. package/packages/datadog-plugin-ws/src/close.js +1 -1
  63. package/packages/datadog-plugin-ws/src/receiver.js +1 -1
  64. package/packages/datadog-webpack/index.js +3 -3
  65. package/packages/dd-trace/index.js +12 -10
  66. package/packages/dd-trace/src/agent/url.js +2 -2
  67. package/packages/dd-trace/src/aiguard/sdk.js +26 -22
  68. package/packages/dd-trace/src/appsec/blocked_templates.js +4 -3
  69. package/packages/dd-trace/src/appsec/blocking.js +64 -33
  70. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +1 -1
  71. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +1 -1
  72. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
  73. package/packages/dd-trace/src/appsec/remote_config.js +1 -0
  74. package/packages/dd-trace/src/appsec/sdk/index.js +4 -0
  75. package/packages/dd-trace/src/appsec/sdk/set_user.js +1 -1
  76. package/packages/dd-trace/src/appsec/sdk/track_event.js +5 -5
  77. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +2 -2
  78. package/packages/dd-trace/src/appsec/sdk/utils.js +4 -2
  79. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +6 -1
  80. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +4 -0
  81. package/packages/dd-trace/src/config/defaults.js +315 -146
  82. package/packages/dd-trace/src/config/generated-config-types.d.ts +9 -1
  83. package/packages/dd-trace/src/config/helper.js +59 -10
  84. package/packages/dd-trace/src/config/index.js +587 -1496
  85. package/packages/dd-trace/src/config/parsers.js +256 -0
  86. package/packages/dd-trace/src/config/remote_config.js +59 -2
  87. package/packages/dd-trace/src/config/supported-configurations.json +406 -432
  88. package/packages/dd-trace/src/constants.js +1 -0
  89. package/packages/dd-trace/src/crashtracking/crashtracker.js +7 -1
  90. package/packages/dd-trace/src/crashtracking/index.js +1 -7
  91. package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +5 -2
  92. package/packages/dd-trace/src/debugger/index.js +1 -1
  93. package/packages/dd-trace/src/dogstatsd.js +12 -9
  94. package/packages/dd-trace/src/encode/0.4.js +8 -7
  95. package/packages/dd-trace/src/encode/span-stats.js +4 -1
  96. package/packages/dd-trace/src/exporters/agent/writer.js +7 -1
  97. package/packages/dd-trace/src/exporters/common/request.js +9 -0
  98. package/packages/dd-trace/src/exporters/common/writer.js +12 -2
  99. package/packages/dd-trace/src/heap_snapshots.js +3 -0
  100. package/packages/dd-trace/src/index.js +5 -2
  101. package/packages/dd-trace/src/lambda/runtime/ritm.js +6 -6
  102. package/packages/dd-trace/src/llmobs/index.js +4 -1
  103. package/packages/dd-trace/src/llmobs/plugins/ai/index.js +5 -1
  104. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +60 -12
  105. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +4 -2
  106. package/packages/dd-trace/src/llmobs/sdk.js +12 -8
  107. package/packages/dd-trace/src/llmobs/span_processor.js +1 -1
  108. package/packages/dd-trace/src/llmobs/tagger.js +9 -6
  109. package/packages/dd-trace/src/llmobs/writers/base.js +2 -0
  110. package/packages/dd-trace/src/llmobs/writers/util.js +3 -0
  111. package/packages/dd-trace/src/log/index.js +20 -59
  112. package/packages/dd-trace/src/log/writer.js +7 -19
  113. package/packages/dd-trace/src/noop/proxy.js +8 -0
  114. package/packages/dd-trace/src/openfeature/remote_config.js +6 -1
  115. package/packages/dd-trace/src/opentelemetry/context_manager.js +6 -4
  116. package/packages/dd-trace/src/opentelemetry/logs/index.js +1 -1
  117. package/packages/dd-trace/src/opentelemetry/metrics/index.js +1 -1
  118. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +17 -2
  119. package/packages/dd-trace/src/opentelemetry/otlp/protobuf_loader.js +14 -2
  120. package/packages/dd-trace/src/opentelemetry/otlp/trace.proto +358 -0
  121. package/packages/dd-trace/src/opentelemetry/otlp/trace_service.proto +78 -0
  122. package/packages/dd-trace/src/opentelemetry/trace/index.js +75 -0
  123. package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +66 -0
  124. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +332 -0
  125. package/packages/dd-trace/src/opentracing/propagation/text_map.js +9 -4
  126. package/packages/dd-trace/src/opentracing/tracer.js +9 -4
  127. package/packages/dd-trace/src/payload-tagging/config/index.js +6 -5
  128. package/packages/dd-trace/src/plugin_manager.js +8 -6
  129. package/packages/dd-trace/src/plugins/ci_plugin.js +4 -0
  130. package/packages/dd-trace/src/plugins/log_plugin.js +3 -0
  131. package/packages/dd-trace/src/plugins/plugin.js +11 -13
  132. package/packages/dd-trace/src/plugins/storage.js +2 -2
  133. package/packages/dd-trace/src/plugins/tracing.js +22 -5
  134. package/packages/dd-trace/src/plugins/util/test.js +2 -0
  135. package/packages/dd-trace/src/plugins/util/web.js +6 -88
  136. package/packages/dd-trace/src/process-tags/index.js +3 -0
  137. package/packages/dd-trace/src/profiler.js +27 -2
  138. package/packages/dd-trace/src/profiling/config.js +73 -241
  139. package/packages/dd-trace/src/profiling/exporter_cli.js +1 -4
  140. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +6 -2
  141. package/packages/dd-trace/src/profiling/profiler.js +78 -109
  142. package/packages/dd-trace/src/profiling/profilers/events.js +2 -3
  143. package/packages/dd-trace/src/profiling/profilers/wall.js +89 -6
  144. package/packages/dd-trace/src/profiling/ssi-heuristics.js +4 -1
  145. package/packages/dd-trace/src/propagation-hash/index.js +2 -1
  146. package/packages/dd-trace/src/proxy.js +40 -6
  147. package/packages/dd-trace/src/remote_config/index.js +3 -0
  148. package/packages/dd-trace/src/require-package-json.js +8 -4
  149. package/packages/dd-trace/src/ritm.js +58 -26
  150. package/packages/dd-trace/src/runtime_metrics/index.js +3 -0
  151. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +18 -11
  152. package/packages/dd-trace/src/sampler.js +1 -1
  153. package/packages/dd-trace/src/service-naming/index.js +1 -1
  154. package/packages/dd-trace/src/service-naming/schemas/definition.js +4 -1
  155. package/packages/dd-trace/src/service-naming/schemas/util.js +15 -1
  156. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +24 -1
  157. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +60 -0
  158. package/packages/dd-trace/src/service-naming/schemas/v0/web.js +17 -1
  159. package/packages/dd-trace/src/service-naming/schemas/v0/websocket.js +5 -0
  160. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +17 -0
  161. package/packages/dd-trace/src/service-naming/schemas/v1/web.js +11 -1
  162. package/packages/dd-trace/src/service-naming/schemas/v1/websocket.js +6 -0
  163. package/packages/dd-trace/src/span_stats.js +5 -1
  164. package/packages/dd-trace/src/standalone/index.js +3 -0
  165. package/packages/dd-trace/src/telemetry/index.js +2 -3
  166. package/packages/dd-trace/src/telemetry/send-data.js +5 -19
  167. package/packages/dd-trace/src/telemetry/session-propagation.js +19 -44
  168. package/packages/dd-trace/src/telemetry/telemetry.js +28 -171
  169. package/packages/dd-trace/src/tracer.js +2 -2
  170. package/packages/dd-trace/src/util.js +0 -9
  171. package/vendor/dist/@apm-js-collab/code-transformer/index.js +28 -6
  172. package/vendor/dist/protobufjs/index.js +1 -1
  173. package/packages/dd-trace/src/log/utils.js +0 -16
@@ -1,17 +1,41 @@
1
1
  'use strict'
2
+
2
3
  const path = require('path')
4
+
3
5
  const iitm = require('../../../dd-trace/src/iitm')
4
6
  const ritm = require('../../../dd-trace/src/ritm')
7
+ const log = require('../../../dd-trace/src/log')
8
+ const requirePackageJson = require('../../../dd-trace/src/require-package-json')
9
+
10
+ /**
11
+ * @param {string} moduleBaseDir
12
+ * @returns {string|undefined}
13
+ */
14
+ function getVersion (moduleBaseDir) {
15
+ if (moduleBaseDir) {
16
+ return requirePackageJson(moduleBaseDir, /** @type {import('module').Module} */ (module)).version
17
+ }
18
+
19
+ return process.version
20
+ }
5
21
 
6
22
  /**
7
23
  * This is called for every package/internal-module that dd-trace supports instrumentation for
8
24
  * In practice, `modules` is always an array with a single entry.
9
25
  *
26
+ * @overload
27
+ * @param {string[]} modules list of modules to hook into
28
+ * @param {object} hookOptions hook options
29
+ * @param {Function} onrequire callback to be executed upon encountering module
30
+ */
31
+ /**
32
+ * @overload
10
33
  * @param {string[]} modules list of modules to hook into
11
34
  * @param {object} hookOptions hook options
12
35
  * @param {Function} onrequire callback to be executed upon encountering module
13
36
  */
14
37
  function Hook (modules, hookOptions, onrequire) {
38
+ // TODO: Rewrite this to use class syntax. The same should be done for ritm.
15
39
  if (!(this instanceof Hook)) return new Hook(modules, hookOptions, onrequire)
16
40
 
17
41
  if (typeof hookOptions === 'function') {
@@ -42,6 +66,13 @@ function Hook (modules, hookOptions, onrequire) {
42
66
  return result
43
67
  }
44
68
 
69
+ try {
70
+ moduleVersion ||= getVersion(moduleBaseDir)
71
+ } catch (error) {
72
+ log.error('Error getting version for "%s": %s', moduleName, error.message, error)
73
+ return
74
+ }
75
+
45
76
  if (
46
77
  isIitm &&
47
78
  moduleExports.default &&
@@ -66,10 +97,4 @@ function Hook (modules, hookOptions, onrequire) {
66
97
  })
67
98
  }
68
99
 
69
- Hook.prototype.unhook = function () {
70
- this._ritmHook.unhook()
71
- this._iitmHook.unhook()
72
- this._patched = Object.create(null)
73
- }
74
-
75
100
  module.exports = Hook
@@ -1,6 +1,18 @@
1
1
  'use strict'
2
2
 
3
3
  module.exports = {
4
+ // Only list unprefixed node modules. They will automatically be instrumented as prefixed and unprefixed.
5
+ child_process: () => require('../child_process'),
6
+ crypto: () => require('../crypto'),
7
+ dns: () => require('../dns'),
8
+ fs: { serverless: false, fn: () => require('../fs') },
9
+ http: () => require('../http'),
10
+ http2: () => require('../http2'),
11
+ https: () => require('../http'),
12
+ net: () => require('../net'),
13
+ url: () => require('../url'),
14
+ vm: () => require('../vm'),
15
+ // Non Node.js modules
4
16
  '@anthropic-ai/sdk': { esmFirst: true, fn: () => require('../anthropic') },
5
17
  '@apollo/server': () => require('../apollo-server'),
6
18
  '@apollo/gateway': () => require('../apollo'),
@@ -47,31 +59,24 @@ module.exports = {
47
59
  bullmq: () => require('../bullmq'),
48
60
  bunyan: () => require('../bunyan'),
49
61
  'cassandra-driver': () => require('../cassandra-driver'),
50
- child_process: () => require('../child_process'),
51
62
  connect: () => require('../connect'),
52
63
  cookie: () => require('../cookie'),
53
64
  'cookie-parser': () => require('../cookie-parser'),
54
65
  couchbase: () => require('../couchbase'),
55
- crypto: () => require('../crypto'),
56
66
  cypress: () => require('../cypress'),
57
67
  'dd-trace-api': () => require('../dd-trace-api'),
58
- dns: () => require('../dns'),
59
68
  elasticsearch: () => require('../elasticsearch'),
60
69
  express: () => require('../express'),
61
70
  'express-mongo-sanitize': () => require('../express-mongo-sanitize'),
62
71
  'express-session': () => require('../express-session'),
63
72
  fastify: () => require('../fastify'),
64
73
  'find-my-way': () => require('../find-my-way'),
65
- fs: { serverless: false, fn: () => require('../fs') },
66
74
  'generic-pool': () => require('../generic-pool'),
67
75
  graphql: () => require('../graphql'),
68
76
  grpc: () => require('../grpc'),
69
77
  handlebars: () => require('../handlebars'),
70
78
  hapi: () => require('../hapi'),
71
79
  hono: { esmFirst: true, fn: () => require('../hono') },
72
- http: () => require('../http'),
73
- http2: () => require('../http2'),
74
- https: () => require('../http'),
75
80
  ioredis: () => require('../ioredis'),
76
81
  iovalkey: () => require('../iovalkey'),
77
82
  'jest-circus': () => require('../jest'),
@@ -103,18 +108,8 @@ module.exports = {
103
108
  multer: () => require('../multer'),
104
109
  mysql: () => require('../mysql'),
105
110
  mysql2: () => require('../mysql2'),
106
- net: () => require('../net'),
107
111
  next: () => require('../next'),
108
112
  'node-serialize': () => require('../node-serialize'),
109
- 'node:child_process': () => require('../child_process'),
110
- 'node:crypto': () => require('../crypto'),
111
- 'node:dns': () => require('../dns'),
112
- 'node:http': () => require('../http'),
113
- 'node:http2': () => require('../http2'),
114
- 'node:https': () => require('../http'),
115
- 'node:net': () => require('../net'),
116
- 'node:url': () => require('../url'),
117
- 'node:vm': () => require('../vm'),
118
113
  nyc: () => require('../nyc'),
119
114
  oracledb: () => require('../oracledb'),
120
115
  openai: { esmFirst: true, fn: () => require('../openai') },
@@ -142,9 +137,7 @@ module.exports = {
142
137
  tedious: () => require('../tedious'),
143
138
  tinypool: { esmFirst: true, fn: () => require('../vitest') },
144
139
  undici: () => require('../undici'),
145
- url: () => require('../url'),
146
140
  vitest: { esmFirst: true, fn: () => require('../vitest') },
147
- vm: () => require('../vm'),
148
141
  when: () => require('../when'),
149
142
  winston: () => require('../winston'),
150
143
  workerpool: () => require('../mocha'),
@@ -1,11 +1,23 @@
1
1
  'use strict'
2
2
 
3
3
  const { AsyncResource } = require('async_hooks')
4
- const dc = require('dc-polyfill')
4
+ const dc = /** @type {typeof import('node:diagnostics_channel')} */ (require('dc-polyfill'))
5
5
  const instrumentations = require('./instrumentations')
6
6
  const rewriterInstrumentations = require('./rewriter/instrumentations')
7
7
 
8
+ /**
9
+ * @typedef {import('node:diagnostics_channel').Channel} Channel
10
+ * @typedef {import('node:diagnostics_channel').TracingChannel} TracingChannel
11
+ */
12
+
13
+ /**
14
+ * @type {Record<string, Channel>}
15
+ */
8
16
  const channelMap = {}
17
+ /**
18
+ * @param {string} name
19
+ * @returns {Channel}
20
+ */
9
21
  exports.channel = function (name) {
10
22
  const maybe = channelMap[name]
11
23
  if (maybe) return maybe
@@ -14,7 +26,14 @@ exports.channel = function (name) {
14
26
  return ch
15
27
  }
16
28
 
29
+ /**
30
+ * @type {Record<string, TracingChannel>}
31
+ */
17
32
  const tracingChannelMap = {}
33
+ /**
34
+ * @param {string} name
35
+ * @returns {TracingChannel}
36
+ */
18
37
  exports.tracingChannel = function (name) {
19
38
  const maybe = tracingChannelMap[name]
20
39
  if (maybe) return maybe
@@ -34,24 +53,19 @@ exports.getHooks = function getHooks (names) {
34
53
 
35
54
  /**
36
55
  * @param {object} args
37
- * @param {string|string[]} args.name module name
38
- * @param {string[]} args.versions array of semver range strings
56
+ * @param {string} args.name module name
57
+ * @param {string[]} [args.versions] array of semver range strings
39
58
  * @param {string} [args.file='index.js'] path to file within package to instrument
40
59
  * @param {string} [args.filePattern] pattern to match files within package to instrument
41
- * @param {boolean} [args.patchDefault] whether to patch the default export
42
- * @param {(moduleExports: unknown, version: string) => unknown} hook
60
+ * @param {boolean} [args.patchDefault=true] whether to patch the default export
61
+ * @param {(moduleExports: unknown, version: string, isIitm?: boolean) => unknown} [hook] Patches module exports
43
62
  */
44
63
  exports.addHook = function addHook ({ name, versions, file, filePattern, patchDefault }, hook) {
45
- if (typeof name === 'string') {
46
- name = [name]
64
+ if (!instrumentations[name]) {
65
+ instrumentations[name] = []
47
66
  }
48
67
 
49
- for (const val of name) {
50
- if (!instrumentations[val]) {
51
- instrumentations[val] = []
52
- }
53
- instrumentations[val].push({ name: val, versions, file, filePattern, hook, patchDefault })
54
- }
68
+ instrumentations[name].push({ versions, file, filePattern, hook, patchDefault })
55
69
  }
56
70
 
57
71
  exports.AsyncResource = AsyncResource
@@ -1,9 +1,9 @@
1
1
  'use strict'
2
2
 
3
+ const { builtinModules } = require('module')
3
4
  const path = require('path')
4
5
  const { channel } = require('dc-polyfill')
5
6
  const satisfies = require('../../../../vendor/dist/semifies')
6
- const requirePackageJson = require('../../../dd-trace/src/require-package-json')
7
7
  const log = require('../../../dd-trace/src/log')
8
8
  const telemetry = require('../../../dd-trace/src/guardrails/telemetry')
9
9
  const { IS_SERVERLESS } = require('../../../dd-trace/src/serverless')
@@ -36,27 +36,47 @@ if (!disabledInstrumentations.has('process')) {
36
36
  require('../process')
37
37
  }
38
38
 
39
- const HOOK_SYMBOL = Symbol('hookExportsSet')
40
-
41
39
  if (DD_TRACE_DEBUG && DD_TRACE_DEBUG.toLowerCase() !== 'false') {
42
40
  checkRequireCache.checkForRequiredModules()
43
41
  setImmediate(checkRequireCache.checkForPotentialConflicts)
44
42
  }
45
43
 
46
- const seenCombo = new Set()
47
- const allInstrumentations = {}
48
-
49
44
  for (const inst of disabledInstrumentations) {
50
45
  rewriter.disable(inst)
51
46
  }
52
47
 
53
- // TODO: make this more efficient
54
- for (const packageName of names) {
55
- if (disabledInstrumentations.has(packageName)) continue
48
+ /** @type {Map<string, object>} */
49
+ const instrumentedNodeModules = new Map()
50
+ /** @type {Map<string, boolean>} */
51
+ const instrumentedIntegrationsSuccess = new Map()
52
+ /** @type {Set<string>} */
53
+ const alreadyLoggedIncompatibleIntegrations = new Set()
54
+
55
+ // Always disable prefixed and unprefixed node modules if one is disabled.
56
+ if (disabledInstrumentations.size) {
57
+ const builtinsSet = new Set(builtinModules)
58
+ for (const name of disabledInstrumentations) {
59
+ const hasPrefix = name.startsWith('node:')
60
+ if (hasPrefix || builtinsSet.has(name)) {
61
+ if (hasPrefix) {
62
+ const unprefixedName = name.slice(5)
63
+ if (!disabledInstrumentations.has(unprefixedName)) {
64
+ disabledInstrumentations.add(unprefixedName)
65
+ }
66
+ } else if (!disabledInstrumentations.has(`node:${name}`)) {
67
+ disabledInstrumentations.add(`node:${name}`)
68
+ }
69
+ }
70
+ }
71
+ builtinsSet.clear()
72
+ }
73
+
74
+ for (const name of names) {
75
+ if (disabledInstrumentations.has(name)) continue
56
76
 
57
77
  const hookOptions = {}
58
78
 
59
- let hook = hooks[packageName]
79
+ let hook = hooks[name]
60
80
 
61
81
  if (hook !== null && typeof hook === 'object') {
62
82
  if (hook.serverless === false && IS_SERVERLESS) continue
@@ -65,173 +85,114 @@ for (const packageName of names) {
65
85
  hook = hook.fn
66
86
  }
67
87
 
68
- // get the instrumentation file name to save all hooked versions
69
- const instrumentationFileName = parseHookInstrumentationFileName(packageName)
70
-
71
- Hook([packageName], hookOptions, (moduleExports, moduleName, moduleBaseDir, moduleVersion, isIitm) => {
72
- moduleName = moduleName.replace(pathSepExpr, '/')
88
+ Hook([name], hookOptions, (moduleExports, moduleName, moduleBaseDir, moduleVersion, isIitm) => {
89
+ // All loaded versions are first expected to fail instrumentation.
90
+ if (!instrumentedIntegrationsSuccess.has(`${name}@${moduleVersion}`)) {
91
+ instrumentedIntegrationsSuccess.set(`${name}@${moduleVersion}`, false)
92
+ }
73
93
 
74
94
  // This executes the integration file thus adding its entries to `instrumentations`
75
95
  hook()
76
96
 
77
- if (!instrumentations[packageName]) {
97
+ if (!instrumentations[name] || moduleExports === instrumentedNodeModules.get(name)) {
78
98
  return moduleExports
79
99
  }
80
100
 
81
- const namesAndSuccesses = {}
82
- for (const { name, file, versions, hook, filePattern, patchDefault } of instrumentations[packageName]) {
83
- if (patchDefault === false && !moduleExports.default && isIitm) {
84
- return moduleExports
85
- } else if (patchDefault === true && moduleExports.default && isIitm) {
86
- moduleExports = moduleExports.default
101
+ // Used for node: prefixed modules to prevent double instrumentation.
102
+ if (moduleBaseDir) {
103
+ moduleName = moduleName.replace(pathSepExpr, '/')
104
+ } else {
105
+ instrumentedNodeModules.set(name, moduleExports)
106
+ }
107
+
108
+ for (const { file, versions, hook, filePattern, patchDefault } of instrumentations[name]) {
109
+ if (isIitm && patchDefault === !!moduleExports.default) {
110
+ if (patchDefault) {
111
+ moduleExports = moduleExports.default
112
+ } else {
113
+ return moduleExports
114
+ }
87
115
  }
88
116
 
89
- let fullFilePattern = filePattern
90
117
  const fullFilename = filename(name, file)
91
- if (fullFilePattern) {
92
- fullFilePattern = filename(name, fullFilePattern)
93
- }
94
118
 
95
- // Create a WeakSet associated with the hook function so that patches on the same moduleExport only happens once
96
- // for example by instrumenting both dns and node:dns double the spans would be created
97
- // since they both patch the same moduleExport, this WeakSet is used to mitigate that
98
- // TODO(BridgeAR): Instead of using a WeakSet here, why not just use aliases for the hook in register?
99
- // That way it would also not be duplicated. The actual name being used has to be identified else wise.
100
- // Maybe it is also not important to know what name was actually used?
101
- hook[HOOK_SYMBOL] ??= new WeakSet()
102
119
  let matchesFile = moduleName === fullFilename
103
120
 
104
121
  if (!matchesFile && isRelativeRequire(name)) matchesFile = true
105
122
 
123
+ const fullFilePattern = filePattern && filename(name, filePattern)
106
124
  if (fullFilePattern) {
107
125
  // Some libraries include a hash in their filenames when installed,
108
126
  // so our instrumentation has to include a '.*' to match them for more than a single version.
109
- matchesFile = matchesFile || new RegExp(fullFilePattern).test(moduleName)
127
+ matchesFile ||= new RegExp(fullFilePattern).test(moduleName)
110
128
  }
111
129
 
112
- if (matchesFile) {
113
- let version = moduleVersion
130
+ if (matchesFile && matchVersion(moduleVersion, versions)) {
131
+ // Do not log in case of an error to prevent duplicate telemetry for the same integration version.
132
+ instrumentedIntegrationsSuccess.set(`${name}@${moduleVersion}`, true)
114
133
  try {
115
- version = version || getVersion(moduleBaseDir)
116
- allInstrumentations[instrumentationFileName] = allInstrumentations[instrumentationFileName] || false
117
- } catch (e) {
118
- log.error('Error getting version for "%s": %s', name, e.message, e)
119
- continue
120
- }
121
- if (namesAndSuccesses[`${name}@${version}`] === undefined && !file) {
122
- // TODO If `file` is present, we might elsewhere instrument the result of the module
123
- // for a version range that actually matches, so we can't assume that we're _not_
124
- // going to instrument that. However, the way the data model around instrumentation
125
- // works, we can't know either way just yet, so to avoid false positives, we'll just
126
- // ignore this if there is a `file` in the hook. The thing to do here is rework
127
- // everything so that we can be sure that there are _no_ instrumentations that it
128
- // could match.
129
- namesAndSuccesses[`${name}@${version}`] = false
130
- }
131
-
132
- if (matchVersion(version, versions)) {
133
- allInstrumentations[instrumentationFileName] = true
134
-
135
- // Check if the hook already has a set moduleExport
136
- if (hook[HOOK_SYMBOL].has(moduleExports)) {
137
- namesAndSuccesses[`${name}@${version}`] = true
138
- return moduleExports
139
- }
140
-
141
- try {
142
- loadChannel.publish({ name, version, file })
143
- // Send the name and version of the module back to the callback because now addHook
144
- // takes in an array of names so by passing the name the callback will know which module name is being used
145
- // TODO(BridgeAR): This is only true in case the name is identical
146
- // in all loads. If they deviate, the deviating name would not be
147
- // picked up due to the unification. Check what modules actually use the name.
148
- // TODO(BridgeAR): Only replace moduleExports if the hook returns a new value.
149
- // This allows to reduce the instrumentation code (no return needed).
150
-
151
- moduleExports = hook(moduleExports, version, name, isIitm) ?? moduleExports
152
- // Set the moduleExports in the hooks WeakSet
153
- hook[HOOK_SYMBOL].add(moduleExports)
154
- } catch (e) {
155
- log.info('Error during ddtrace instrumentation of application, aborting.', e)
156
- telemetry('error', [
157
- `error_type:${e.constructor.name}`,
158
- `integration:${name}`,
159
- `integration_version:${version}`,
160
- ], {
161
- result: 'error',
162
- result_class: 'internal_error',
163
- result_reason: `Error during instrumentation of ${name}@${version}: ${e.message}`,
164
- })
165
- }
166
- namesAndSuccesses[`${name}@${version}`] = true
134
+ loadChannel.publish({ name })
135
+
136
+ moduleExports = hook(moduleExports, moduleVersion, isIitm) ?? moduleExports
137
+ } catch (error) {
138
+ log.info('Error during ddtrace instrumentation of application, aborting.', error)
139
+ telemetry('error', [
140
+ `error_type:${error.constructor.name}`,
141
+ `integration:${name}`,
142
+ `integration_version:${moduleVersion}`,
143
+ ], {
144
+ result: 'error',
145
+ result_class: 'internal_error',
146
+ result_reason: `Error during instrumentation of ${name}@${moduleVersion}: ${error.message}`,
147
+ })
167
148
  }
168
149
  }
169
150
  }
170
- for (const nameVersion of Object.keys(namesAndSuccesses)) {
171
- const [name, version] = nameVersion.split('@')
172
- const success = namesAndSuccesses[nameVersion]
173
- // we check allVersions to see if any version of the integration was successfully instrumented
174
- if (!success && !seenCombo.has(nameVersion) && !allInstrumentations[instrumentationFileName]) {
175
- telemetry('abort.integration', [
176
- `integration:${name}`,
177
- `integration_version:${version}`,
178
- ], {
179
- result: 'abort',
180
- result_class: 'incompatible_library',
181
- result_reason: `Incompatible integration version: ${name}@${version}`,
182
- })
183
- log.info('Found incompatible integration version: %s', nameVersion)
184
- seenCombo.add(nameVersion)
185
- }
186
- }
187
151
 
188
152
  return moduleExports
189
153
  })
190
154
  }
191
155
 
192
- function matchVersion (version, ranges) {
193
- return !version || !ranges || ranges.some(range => satisfies(version, range))
194
- }
156
+ globalThis[Symbol.for('dd-trace')]?.beforeExitHandlers.add(logAbortedIntegrations)
157
+ // TODO: check if we want to stop using channels for single subscriber tasks
158
+ channel('dd-trace:exporter:first-flush').subscribe(logAbortedIntegrations)
195
159
 
196
- function getVersion (moduleBaseDir) {
197
- if (moduleBaseDir) {
198
- return requirePackageJson(moduleBaseDir, module).version
160
+ function logAbortedIntegrations () {
161
+ for (const [nameVersion, success] of instrumentedIntegrationsSuccess) {
162
+ // Only ever log a single version of an integration, even if it is loaded later.
163
+ if (!success && !alreadyLoggedIncompatibleIntegrations.has(nameVersion)) {
164
+ const [name, version] = nameVersion.split('@')
165
+ telemetry('abort.integration', [
166
+ `integration:${name}`,
167
+ `integration_version:${version}`,
168
+ ], {
169
+ result: 'abort',
170
+ result_class: 'incompatible_library',
171
+ result_reason: `Incompatible integration version: ${name}@${version}`,
172
+ })
173
+ log.info('Found incompatible integration version: %s', nameVersion)
174
+ alreadyLoggedIncompatibleIntegrations.add(nameVersion)
175
+ }
199
176
  }
177
+ // Clear the map to avoid reporting the same integration version again.
178
+ instrumentedIntegrationsSuccess.clear()
200
179
  }
201
180
 
202
- function filename (name, file) {
203
- return [name, file].filter(Boolean).join('/')
181
+ /**
182
+ * @param {string|undefined} version
183
+ * @param {string[]|undefined} ranges
184
+ */
185
+ function matchVersion (version, ranges) {
186
+ return !version || !ranges || ranges.some(range => satisfies(version, range))
204
187
  }
205
188
 
206
- // This function captures the instrumentation file name for a given package by parsing the hook require
207
- // function given the module name. It is used to ensure that instrumentations such as redis
208
- // that have several different modules being hooked, ie: 'redis' main package, and @redis/client submodule
209
- // return a consistent instrumentation name. This is used later to ensure that at least some portion of
210
- // the integration was successfully instrumented. Prevents incorrect `Found incompatible integration version: ` messages
211
- // Example:
212
- // redis -> "() => require('../redis')" -> redis
213
- // @redis/client -> "() => require('../redis')" -> redis
214
- //
215
- function parseHookInstrumentationFileName (packageName) {
216
- let hook = hooks[packageName]
217
- if (hook.fn) {
218
- hook = hook.fn
219
- }
220
- const hookString = hook.toString()
221
- const regex = /require\('([^']*)'\)/
222
- const match = hookString.match(regex)
223
-
224
- // try to capture the hook require file location.
225
- if (match && match[1]) {
226
- let moduleName = match[1]
227
- // Remove leading '../' if present
228
- if (moduleName.startsWith('../')) {
229
- moduleName = moduleName.slice(3)
230
- }
231
- return moduleName
232
- }
233
-
234
- return null
189
+ /**
190
+ * @param {string} name
191
+ * @param {string} [file]
192
+ * @returns {string}
193
+ */
194
+ function filename (name, file) {
195
+ return file ? `${name}/${file}` : name
235
196
  }
236
197
 
237
198
  module.exports = {
@@ -16,9 +16,8 @@ const asyncStartChannel = channel('apm:http:client:request:asyncStart')
16
16
  const errorChannel = channel('apm:http:client:request:error')
17
17
  const responseFinishChannel = channel('apm:http:client:response:finish')
18
18
 
19
- const names = ['http', 'https', 'node:http', 'node:https']
20
-
21
- addHook({ name: names }, hookFn)
19
+ addHook({ name: 'http' }, hookFn)
20
+ addHook({ name: 'https' }, hookFn)
22
21
 
23
22
  function hookFn (http) {
24
23
  patch(http, 'request')
@@ -16,10 +16,7 @@ const startSetHeaderCh = channel('datadog:http:server:response:set-header:start'
16
16
 
17
17
  const requestFinishedSet = new WeakSet()
18
18
 
19
- const httpNames = ['http', 'node:http']
20
- const httpsNames = ['https', 'node:https']
21
-
22
- addHook({ name: httpNames }, http => {
19
+ addHook({ name: 'http' }, http => {
23
20
  shimmer.wrap(http.ServerResponse.prototype, 'emit', wrapResponseEmit)
24
21
  shimmer.wrap(http.Server.prototype, 'emit', wrapEmit)
25
22
  shimmer.wrap(http.ServerResponse.prototype, 'writeHead', wrapWriteHead)
@@ -34,7 +31,7 @@ addHook({ name: httpNames }, http => {
34
31
  return http
35
32
  })
36
33
 
37
- addHook({ name: httpsNames }, http => {
34
+ addHook({ name: 'https' }, http => {
38
35
  // http.ServerResponse not present on https
39
36
  shimmer.wrap(http.Server.prototype, 'emit', wrapEmit)
40
37
  return http
@@ -10,8 +10,6 @@ const asyncStartChannel = channel('apm:http2:client:request:asyncStart')
10
10
  const asyncEndChannel = channel('apm:http2:client:request:asyncEnd')
11
11
  const errorChannel = channel('apm:http2:client:request:error')
12
12
 
13
- const names = ['http2', 'node:http2']
14
-
15
13
  function createWrapEmit (ctx) {
16
14
  return function wrapEmit (emit) {
17
15
  return function (event, arg1) {
@@ -68,7 +66,7 @@ function wrapConnect (connect) {
68
66
  }
69
67
  }
70
68
 
71
- addHook({ name: names }, http2 => {
69
+ addHook({ name: 'http2' }, http2 => {
72
70
  shimmer.wrap(http2, 'connect', wrapConnect)
73
71
  if (http2.default) http2.default.connect = http2.connect
74
72
 
@@ -13,9 +13,7 @@ const startServerCh = channel('apm:http2:server:request:start')
13
13
  const errorServerCh = channel('apm:http2:server:request:error')
14
14
  const emitCh = channel('apm:http2:server:response:emit')
15
15
 
16
- const names = ['http2', 'node:http2']
17
-
18
- addHook({ name: names }, http2 => {
16
+ addHook({ name: 'http2' }, http2 => {
19
17
  shimmer.wrap(http2, 'createSecureServer', wrapCreateServer)
20
18
  shimmer.wrap(http2, 'createServer', wrapCreateServer)
21
19
  })