dd-trace 5.87.0 → 5.89.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 (119) hide show
  1. package/LICENSE-3rdparty.csv +60 -32
  2. package/ext/exporters.d.ts +1 -0
  3. package/ext/exporters.js +1 -0
  4. package/ext/tags.js +2 -0
  5. package/index.d.ts +234 -4
  6. package/package.json +18 -11
  7. package/packages/datadog-instrumentations/src/ai.js +54 -90
  8. package/packages/datadog-instrumentations/src/helpers/hook.js +17 -11
  9. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +27 -110
  10. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +103 -0
  11. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/bullmq.js +108 -0
  12. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -1
  13. package/packages/datadog-instrumentations/src/helpers/rewriter/orchestrion/compiler.js +74 -0
  14. package/packages/datadog-instrumentations/src/helpers/rewriter/orchestrion/index.js +43 -0
  15. package/packages/datadog-instrumentations/src/helpers/rewriter/orchestrion/matcher.js +49 -0
  16. package/packages/datadog-instrumentations/src/helpers/rewriter/orchestrion/transformer.js +121 -0
  17. package/packages/datadog-instrumentations/src/helpers/rewriter/{transforms.js → orchestrion/transforms.js} +143 -17
  18. package/packages/datadog-instrumentations/src/jest.js +176 -54
  19. package/packages/datadog-instrumentations/src/kafkajs.js +20 -17
  20. package/packages/datadog-instrumentations/src/playwright.js +1 -1
  21. package/packages/datadog-plugin-amqplib/src/consumer.js +14 -10
  22. package/packages/datadog-plugin-amqplib/src/producer.js +23 -19
  23. package/packages/datadog-plugin-bullmq/src/consumer.js +33 -11
  24. package/packages/datadog-plugin-bullmq/src/producer.js +60 -31
  25. package/packages/datadog-plugin-cucumber/src/index.js +9 -6
  26. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +62 -5
  27. package/packages/datadog-plugin-cypress/src/source-map-utils.js +297 -0
  28. package/packages/datadog-plugin-cypress/src/support.js +52 -9
  29. package/packages/datadog-plugin-jest/src/index.js +12 -2
  30. package/packages/datadog-plugin-jest/src/util.js +2 -1
  31. package/packages/datadog-plugin-kafkajs/src/consumer.js +22 -12
  32. package/packages/datadog-plugin-kafkajs/src/producer.js +33 -22
  33. package/packages/datadog-plugin-mocha/src/index.js +9 -6
  34. package/packages/datadog-plugin-playwright/src/index.js +10 -6
  35. package/packages/datadog-plugin-vitest/src/index.js +13 -8
  36. package/packages/dd-trace/src/aiguard/sdk.js +5 -1
  37. package/packages/dd-trace/src/appsec/iast/analyzers/cookie-analyzer.js +1 -1
  38. package/packages/dd-trace/src/appsec/iast/analyzers/ssrf-analyzer.js +1 -1
  39. package/packages/dd-trace/src/appsec/iast/analyzers/unvalidated-redirect-analyzer.js +1 -1
  40. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +4 -5
  41. package/packages/dd-trace/src/appsec/iast/path-line.js +36 -25
  42. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +1 -1
  43. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +3 -4
  44. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +3 -2
  45. package/packages/dd-trace/src/azure_metadata.js +0 -2
  46. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +1 -1
  47. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +3 -0
  48. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +1 -1
  49. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -1
  50. package/packages/dd-trace/src/ci-visibility/requests/request.js +236 -0
  51. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +1 -1
  52. package/packages/dd-trace/src/config/defaults.js +148 -197
  53. package/packages/dd-trace/src/config/helper.js +43 -1
  54. package/packages/dd-trace/src/config/index.js +38 -14
  55. package/packages/dd-trace/src/config/supported-configurations.json +4125 -512
  56. package/packages/dd-trace/src/constants.js +0 -2
  57. package/packages/dd-trace/src/crashtracking/crashtracker.js +10 -3
  58. package/packages/dd-trace/src/datastreams/checkpointer.js +13 -0
  59. package/packages/dd-trace/src/datastreams/index.js +3 -0
  60. package/packages/dd-trace/src/datastreams/manager.js +9 -0
  61. package/packages/dd-trace/src/datastreams/pathway.js +22 -3
  62. package/packages/dd-trace/src/datastreams/processor.js +140 -4
  63. package/packages/dd-trace/src/encode/agentless-json.js +155 -0
  64. package/packages/dd-trace/src/exporter.js +2 -0
  65. package/packages/dd-trace/src/exporters/agent/writer.js +21 -8
  66. package/packages/dd-trace/src/exporters/agentless/index.js +89 -0
  67. package/packages/dd-trace/src/exporters/agentless/writer.js +184 -0
  68. package/packages/dd-trace/src/exporters/common/request.js +4 -4
  69. package/packages/dd-trace/src/llmobs/plugins/ai/index.js +5 -3
  70. package/packages/dd-trace/src/opentelemetry/context_manager.js +19 -46
  71. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +3 -4
  72. package/packages/dd-trace/src/opentracing/propagation/text_map.js +3 -5
  73. package/packages/dd-trace/src/opentracing/span.js +6 -4
  74. package/packages/dd-trace/src/pkg.js +1 -1
  75. package/packages/dd-trace/src/plugins/ci_plugin.js +57 -5
  76. package/packages/dd-trace/src/plugins/database.js +15 -2
  77. package/packages/dd-trace/src/plugins/util/test.js +48 -0
  78. package/packages/dd-trace/src/profiling/exporter_cli.js +1 -0
  79. package/packages/dd-trace/src/propagation-hash/index.js +145 -0
  80. package/packages/dd-trace/src/proxy.js +6 -1
  81. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +1 -1
  82. package/packages/dd-trace/src/startup-log.js +53 -19
  83. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  84. package/vendor/dist/@datadog/source-map/index.js +1 -1
  85. package/vendor/dist/@isaacs/ttlcache/index.js +1 -1
  86. package/vendor/dist/@opentelemetry/core/index.js +1 -1
  87. package/vendor/dist/@opentelemetry/resources/index.js +1 -1
  88. package/vendor/dist/astring/index.js +1 -1
  89. package/vendor/dist/crypto-randomuuid/index.js +1 -1
  90. package/vendor/dist/escape-string-regexp/index.js +1 -1
  91. package/vendor/dist/esquery/index.js +1 -1
  92. package/vendor/dist/ignore/index.js +1 -1
  93. package/vendor/dist/istanbul-lib-coverage/index.js +1 -1
  94. package/vendor/dist/jest-docblock/index.js +1 -1
  95. package/vendor/dist/jsonpath-plus/index.js +1 -1
  96. package/vendor/dist/limiter/index.js +1 -1
  97. package/vendor/dist/lodash.sortby/index.js +1 -1
  98. package/vendor/dist/lru-cache/index.js +1 -1
  99. package/vendor/dist/meriyah/index.js +1 -1
  100. package/vendor/dist/module-details-from-path/index.js +1 -1
  101. package/vendor/dist/mutexify/promise/index.js +1 -1
  102. package/vendor/dist/opentracing/index.js +1 -1
  103. package/vendor/dist/path-to-regexp/index.js +1 -1
  104. package/vendor/dist/pprof-format/index.js +1 -1
  105. package/vendor/dist/protobufjs/index.js +1 -1
  106. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  107. package/vendor/dist/retry/index.js +1 -1
  108. package/vendor/dist/rfdc/index.js +1 -1
  109. package/vendor/dist/semifies/index.js +1 -1
  110. package/vendor/dist/shell-quote/index.js +1 -1
  111. package/vendor/dist/source-map/index.js +1 -1
  112. package/vendor/dist/source-map/lib/util/index.js +1 -1
  113. package/vendor/dist/tlhunter-sorted-set/index.js +1 -1
  114. package/vendor/dist/ttl-set/index.js +1 -1
  115. package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +0 -33
  116. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/bullmq.json +0 -106
  117. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secrets-rules.js +0 -741
  118. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-regex.js +0 -11
  119. package/packages/dd-trace/src/scope/noop/scope.js +0 -21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "5.87.0",
3
+ "version": "5.89.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -16,6 +16,8 @@
16
16
  "lint": "node scripts/check_licenses.js && node scripts/check-no-coverage-artifacts.js && eslint . --concurrency=auto --max-warnings 0",
17
17
  "lint:fix": "node scripts/check_licenses.js && node scripts/check-no-coverage-artifacts.js && eslint . --concurrency=auto --max-warnings 0 --fix",
18
18
  "lint:inspect": "npx @eslint/config-inspector@latest",
19
+ "lint:codeowners": "codeowners-audit",
20
+ "lint:codeowners:ci": "codeowners-audit --glob='**/*.spec.js'",
19
21
  "release:proposal": "node scripts/release/proposal",
20
22
  "services": "node ./scripts/install_plugin_modules && node packages/dd-trace/test/setup/services",
21
23
  "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",
@@ -59,6 +61,7 @@
59
61
  "test:integration": "mocha --timeout 60000 \"integration-tests/*.spec.js\"",
60
62
  "test:integration:aiguard": "mocha --timeout 60000 \"integration-tests/aiguard/*.spec.js\"",
61
63
  "test:integration:appsec": "mocha --timeout 60000 \"integration-tests/appsec/*.spec.js\"",
64
+ "test:integration:bun": "mocha --timeout 60000 \"integration-tests/bun/*.spec.js\"",
62
65
  "test:integration:cucumber": "mocha --timeout 60000 \"integration-tests/cucumber/*.spec.js\"",
63
66
  "test:integration:cypress": "mocha --timeout 60000 \"integration-tests/cypress/*.spec.js\"",
64
67
  "test:integration:debugger": "mocha --timeout 60000 \"integration-tests/debugger/*.spec.js\"",
@@ -131,20 +134,23 @@
131
134
  ],
132
135
  "dependencies": {
133
136
  "dc-polyfill": "^0.1.10",
134
- "import-in-the-middle": "^2.0.6"
137
+ "import-in-the-middle": "^3.0.0"
135
138
  },
136
139
  "optionalDependencies": {
137
- "@datadog/libdatadog": "0.7.0",
138
- "@datadog/native-appsec": "10.3.0",
140
+ "@datadog/libdatadog": "0.8.1",
141
+ "@datadog/native-appsec": "11.0.1",
139
142
  "@datadog/native-iast-taint-tracking": "4.1.0",
140
143
  "@datadog/native-metrics": "3.1.1",
141
- "@datadog/openfeature-node-server": "^0.3.3",
142
- "@datadog/pprof": "5.13.3",
144
+ "@datadog/openfeature-node-server": "^1.1.0",
145
+ "@datadog/pprof": "5.13.4",
143
146
  "@datadog/wasm-js-rewriter": "5.0.1",
144
147
  "@opentelemetry/api": ">=1.0.0 <1.10.0",
145
- "@opentelemetry/api-logs": "<1.0.0"
148
+ "@opentelemetry/api-logs": "<1.0.0",
149
+ "oxc-parser": "^0.115.0"
146
150
  },
147
151
  "devDependencies": {
152
+ "@actions/core": "^3.0.0",
153
+ "@actions/github": "^9.0.0",
148
154
  "@babel/helpers": "^7.28.6",
149
155
  "@eslint/eslintrc": "^3.3.1",
150
156
  "@eslint/js": "^9.39.2",
@@ -158,19 +164,21 @@
158
164
  "axios": "^1.13.4",
159
165
  "benchmark": "^2.1.4",
160
166
  "body-parser": "^2.2.2",
161
- "bun": "1.3.8",
167
+ "bun": "1.3.10",
168
+ "codeowners-audit": "^2.0.0",
162
169
  "eslint": "^9.39.2",
163
- "eslint-plugin-cypress": "^5.2.1",
170
+ "eslint-plugin-cypress": "^6.1.0",
164
171
  "eslint-plugin-import": "^2.32.0",
165
172
  "eslint-plugin-jsdoc": "^62.5.0",
166
173
  "eslint-plugin-mocha": "^11.2.0",
167
174
  "eslint-plugin-n": "^17.23.2",
168
175
  "eslint-plugin-promise": "^7.2.1",
169
- "eslint-plugin-unicorn": "^62.0.0",
176
+ "eslint-plugin-unicorn": "^63.0.0",
170
177
  "express": "^5.1.0",
171
178
  "glob": "^10.4.5",
172
179
  "globals": "^17.2.0",
173
180
  "graphql": "*",
181
+ "husky": "^9.1.7",
174
182
  "jszip": "^3.10.1",
175
183
  "mocha": "^11.6.0",
176
184
  "mocha-junit-reporter": "^2.2.1",
@@ -190,7 +198,6 @@
190
198
  "typescript": "^5.9.2",
191
199
  "workerpool": "^10.0.0",
192
200
  "yaml": "^2.8.0",
193
- "husky": "^9.1.7",
194
201
  "yarn-deduplicate": "^6.0.2"
195
202
  }
196
203
  }
@@ -2,45 +2,11 @@
2
2
 
3
3
  const { channel, tracingChannel } = require('dc-polyfill')
4
4
  const shimmer = require('../../datadog-shimmer')
5
- const { addHook } = require('./helpers/instrument')
6
-
7
- const toolCreationChannel = channel('dd-trace:vercel-ai:tool')
8
-
9
- const TRACED_FUNCTIONS = {
10
- generateText: wrapWithTracer,
11
- streamText: wrapWithTracer,
12
- generateObject: wrapWithTracer,
13
- streamObject: wrapWithTracer,
14
- embed: wrapWithTracer,
15
- embedMany: wrapWithTracer,
16
- tool: wrapTool,
17
- }
5
+ const { addHook, getHooks } = require('./helpers/instrument')
18
6
 
19
7
  const vercelAiTracingChannel = tracingChannel('dd-trace:vercel-ai')
20
8
  const vercelAiSpanSetAttributesChannel = channel('dd-trace:vercel-ai:span:setAttributes')
21
9
 
22
- const noopTracer = {
23
- startActiveSpan () {
24
- const fn = arguments[arguments.length - 1]
25
-
26
- const span = {
27
- spanContext () { return { traceId: '', spanId: '', traceFlags: 0 } },
28
- setAttribute () { return this },
29
- setAttributes () { return this },
30
- addEvent () { return this },
31
- addLink () { return this },
32
- addLinks () { return this },
33
- setStatus () { return this },
34
- updateName () { return this },
35
- end () { return this },
36
- isRecording () { return false },
37
- recordException () { return this },
38
- }
39
-
40
- return fn(span)
41
- },
42
- }
43
-
44
10
  const tracers = new WeakSet()
45
11
 
46
12
  function wrapTracer (tracer) {
@@ -63,21 +29,27 @@ function wrapTracer (tracer) {
63
29
 
64
30
  arguments[arguments.length - 1] = shimmer.wrapFunction(cb, function (originalCb) {
65
31
  return function (span) {
66
- shimmer.wrap(span, 'end', function (spanEnd) {
32
+ // the below is necessary in the case that the span is vercel ai's noopSpan.
33
+ // while we don't want to patch the noopSpan more than once, we do want to treat each as a
34
+ // fresh instance. However, this is really not necessary for non-noop spans, but not sure
35
+ // how to differentiate.
36
+ const freshSpan = Object.create(span) // TODO: does this cause memory leaks?
37
+
38
+ shimmer.wrap(freshSpan, 'end', function (spanEnd) {
67
39
  return function () {
68
40
  vercelAiTracingChannel.asyncEnd.publish(ctx)
69
41
  return spanEnd.apply(this, arguments)
70
42
  }
71
43
  })
72
44
 
73
- shimmer.wrap(span, 'setAttributes', function (setAttributes) {
45
+ shimmer.wrap(freshSpan, 'setAttributes', function (setAttributes) {
74
46
  return function (attributes) {
75
47
  vercelAiSpanSetAttributesChannel.publish({ ctx, attributes })
76
48
  return setAttributes.apply(this, arguments)
77
49
  }
78
50
  })
79
51
 
80
- shimmer.wrap(span, 'recordException', function (recordException) {
52
+ shimmer.wrap(freshSpan, 'recordException', function (recordException) {
81
53
  return function (exception) {
82
54
  ctx.error = exception
83
55
  vercelAiTracingChannel.error.publish(ctx)
@@ -85,7 +57,7 @@ function wrapTracer (tracer) {
85
57
  }
86
58
  })
87
59
 
88
- return originalCb.apply(this, arguments)
60
+ return originalCb.call(this, freshSpan)
89
61
  }
90
62
  })
91
63
 
@@ -98,58 +70,50 @@ function wrapTracer (tracer) {
98
70
  })
99
71
  }
100
72
 
101
- function wrapWithTracer (fn) {
102
- return function () {
103
- const options = arguments[0]
104
-
105
- const experimentalTelemetry = options.experimental_telemetry
106
- if (experimentalTelemetry?.isEnabled === false) {
107
- return fn.apply(this, arguments)
108
- }
109
-
110
- if (experimentalTelemetry == null) {
111
- options.experimental_telemetry = { isEnabled: true, tracer: noopTracer }
112
- } else {
113
- experimentalTelemetry.isEnabled = true
114
- experimentalTelemetry.tracer ??= noopTracer
115
- }
116
-
117
- wrapTracer(options.experimental_telemetry.tracer)
118
-
119
- return fn.apply(this, arguments)
73
+ for (const hook of getHooks('ai')) {
74
+ if (hook.file === 'dist/index.js') {
75
+ // if not removed, the below hook will never match correctly
76
+ // however, it is still needed in the orchestrion definition
77
+ hook.file = null
120
78
  }
121
- }
122
-
123
- function wrapTool (tool) {
124
- return function () {
125
- const args = arguments[0]
126
- toolCreationChannel.publish(args)
127
-
128
- return tool.apply(this, arguments)
129
- }
130
- }
131
79
 
132
- // CJS exports
133
- addHook({
134
- name: 'ai',
135
- versions: ['>=4.0.0'],
136
- }, exports => {
137
- for (const [fnName, patchingFn] of Object.entries(TRACED_FUNCTIONS)) {
138
- exports = shimmer.wrap(exports, fnName, patchingFn, { replaceGetter: true })
139
- }
80
+ addHook(hook, exports => {
81
+ const getTracerChannel = tracingChannel('orchestrion:ai:getTracer')
82
+ getTracerChannel.subscribe({
83
+ end (ctx) {
84
+ const { arguments: args, result: tracer } = ctx
85
+ const { isEnabled } = args[0] ?? {}
140
86
 
141
- return exports
142
- })
143
-
144
- // ESM exports
145
- addHook({
146
- name: 'ai',
147
- versions: ['>=4.0.0'],
148
- file: 'dist/index.mjs',
149
- }, exports => {
150
- for (const [fnName, patchingFn] of Object.entries(TRACED_FUNCTIONS)) {
151
- exports = shimmer.wrap(exports, fnName, patchingFn, { replaceGetter: true })
152
- }
87
+ if (isEnabled !== false) {
88
+ wrapTracer(tracer)
89
+ }
90
+ },
91
+ })
92
+
93
+ /**
94
+ * We patch this function to ensure that the telemetry attributes/tags are set always,
95
+ * even when telemetry options are not specified. This is to ensure easy use of this integration.
96
+ *
97
+ * If it is explicitly disabled, however, we will not change the options.
98
+ */
99
+ const selectTelemetryAttributesChannel = tracingChannel('orchestrion:ai:selectTelemetryAttributes')
100
+ selectTelemetryAttributesChannel.subscribe({
101
+ start (ctx) {
102
+ const { arguments: args } = ctx
103
+ const options = args[0]
104
+
105
+ if (options.telemetry?.isEnabled !== false) {
106
+ args[0] = {
107
+ ...options,
108
+ telemetry: {
109
+ ...options.telemetry,
110
+ isEnabled: true,
111
+ },
112
+ }
113
+ }
114
+ },
115
+ })
153
116
 
154
- return exports
155
- })
117
+ return exports
118
+ })
119
+ }
@@ -26,31 +26,37 @@ function Hook (modules, hookOptions, onrequire) {
26
26
  const parts = [moduleBaseDir, moduleName].filter(Boolean)
27
27
  const filename = path.join(...parts)
28
28
 
29
- if (this._patched[filename] && patched.has(moduleExports)) {
30
- return patched.get(moduleExports)
31
- }
32
-
33
29
  let defaultWrapResult
34
30
 
31
+ const wrappedOnrequire = (moduleExports, ...args) => {
32
+ if (this._patched[filename] && patched.has(moduleExports)) {
33
+ return patched.get(moduleExports)
34
+ }
35
+
36
+ const result = onrequire(moduleExports, ...args)
37
+ if (result && (typeof result === 'object' || typeof result === 'function')) {
38
+ patched.set(moduleExports, result)
39
+ patched.set(result, result)
40
+ }
41
+
42
+ return result
43
+ }
44
+
35
45
  if (
36
46
  isIitm &&
37
47
  moduleExports.default &&
38
48
  (typeof moduleExports.default === 'object' ||
39
49
  typeof moduleExports.default === 'function')
40
50
  ) {
41
- defaultWrapResult = onrequire(moduleExports.default, moduleName, moduleBaseDir, moduleVersion, isIitm)
51
+ defaultWrapResult = wrappedOnrequire(moduleExports.default, moduleName, moduleBaseDir, moduleVersion, isIitm)
42
52
  }
43
53
 
44
- const newExports = onrequire(moduleExports, moduleName, moduleBaseDir, moduleVersion, isIitm)
54
+ const newExports = wrappedOnrequire(moduleExports, moduleName, moduleBaseDir, moduleVersion, isIitm)
45
55
 
46
56
  if (defaultWrapResult) newExports.default = defaultWrapResult
47
57
 
48
58
  this._patched[filename] = true
49
- if (newExports &&
50
- (typeof newExports === 'object' ||
51
- typeof newExports === 'function')) {
52
- patched.set(moduleExports, newExports)
53
- }
59
+
54
60
  return newExports
55
61
  }
56
62
 
@@ -1,95 +1,45 @@
1
1
  'use strict'
2
2
 
3
- /*
4
- This rewriter is basically a JavaScript version of Orchestrion-JS. The goal is
5
- not to replace Orchestrion-JS, but rather to make it easier and faster to write
6
- new integrations in the short-term, especially as many changes to the rewriter
7
- will be needed as all the patterns we need have not been identified yet. This
8
- will avoid the back and forth of having to make Rust changes to an external
9
- library for every integration change or addition that requires something new.
10
-
11
- In the meantime, we'll work concurrently on a change to Orchestrion-JS that
12
- adds an "arbitrary transform" or "plugin" system that can be used from
13
- JavaScript, in order to enable quick iteration while still using Orchestrion-JS.
14
- Once that's done we'll use that, so that we can remove this JS approach and
15
- return to using Orchestrion-JS.
16
-
17
- The long term goal is to backport any additional features we add to the JS
18
- rewriter (or using the plugin system in Orchestrion-JS once we're using that)
19
- to Orchestrion-JS once we're confident that the implementation is fairly
20
- complete and has all features we need.
21
-
22
- Here is a list of the additions and changes in this rewriter compared to
23
- Orchestrion-JS that will need to be backported:
24
-
25
- (NOTE: Please keep this list up-to-date whenever new features are added)
26
-
27
- - Supports an `astQuery` field to filter AST nodes with an esquery query. This
28
- is mostly meant to be used when experimenting or if what needs to be queried
29
- is not a function. We'll see over time if something like this is needed to be
30
- backported or if it can be replaced by simpler queries.
31
- - Supports replacing methods of child class instances in the base constructor.
32
- */
33
-
34
3
  const { readFileSync } = require('fs')
35
4
  const { join } = require('path')
36
- const semifies = require('../../../../../vendor/dist/semifies')
37
5
  const log = require('../../../../dd-trace/src/log')
38
- const { getEnvironmentVariable } = require('../../../../dd-trace/src/config/helper')
39
- const transforms = require('./transforms')
40
- const { generate, parse, traverse } = require('./compiler')
41
6
  const instrumentations = require('./instrumentations')
7
+ const { create } = require('./orchestrion')
42
8
 
43
- const NODE_OPTIONS = getEnvironmentVariable('NODE_OPTIONS')
44
-
45
- const supported = {}
9
+ /** @type {Record<string, string>} map of module base name to version */
10
+ const moduleVersions = {}
46
11
  const disabled = new Set()
47
-
48
- // TODO: Source maps without `--enable-source-maps`.
49
- const enableSourceMaps = NODE_OPTIONS?.includes('--enable-source-maps') ||
50
- process.execArgv?.some(arg => arg.includes('--enable-source-maps'))
51
-
52
- let SourceMapGenerator
12
+ const matcher = create(instrumentations, 'dc-polyfill')
53
13
 
54
14
  function rewrite (content, filename, format) {
55
15
  if (!content) return content
16
+ if (!filename.includes('node_modules')) return content
56
17
 
57
- try {
58
- let ast
59
-
60
- filename = filename.replace('file://', '')
61
-
62
- for (const inst of instrumentations) {
63
- const { astQuery, functionQuery = {}, module: { name, versionRange, filePath } } = inst
64
- const { kind } = functionQuery
65
- const operator = kind === 'Async' ? 'tracePromise' : kind === 'Callback' ? 'traceCallback' : 'traceSync'
66
- const transform = transforms[operator]
18
+ filename = filename.replace('file://', '')
67
19
 
68
- if (disabled.has(name)) continue
69
- if (!filename.endsWith(`${name}/${filePath}`)) continue
70
- if (!transform) continue
71
- if (!satisfies(filename, filePath, versionRange)) continue
20
+ const moduleType = format === 'module' ? 'esm' : 'cjs'
21
+ const [modulePath] = filename.split('/node_modules/').reverse()
22
+ const moduleParts = modulePath.split('/')
23
+ const splitIndex = moduleParts[0].startsWith('@') ? 2 : 1
24
+ const moduleName = moduleParts.slice(0, splitIndex).join('/')
25
+ const filePath = moduleParts.slice(splitIndex).join('/')
26
+ const version = getVersion(filename, filePath)
72
27
 
73
- ast ??= parse(content.toString(), { loc: true, ranges: true, module: format === 'module' })
28
+ if (disabled.has(moduleName)) return content
74
29
 
75
- const query = astQuery || fromFunctionQuery(functionQuery)
76
- const state = { ...inst, format, functionQuery, operator }
30
+ const transformer = matcher.getTransformer(moduleName, version, filePath)
77
31
 
78
- traverse(ast, query, (...args) => transform(state, ...args))
79
- }
32
+ if (!transformer) return content
80
33
 
81
- if (ast) {
82
- if (!enableSourceMaps) return generate(ast)
34
+ try {
35
+ // TODO: pass existing sourcemap as input for remapping
36
+ const { code, map } = transformer.transform(content, moduleType)
83
37
 
84
- // TODO: Can we use the same version of `source-map` that DI uses?
85
- SourceMapGenerator ??= require('../../../../../vendor/dist/@datadog/source-map').SourceMapGenerator
38
+ if (!map) return code
86
39
 
87
- const sourceMap = new SourceMapGenerator({ file: filename })
88
- const code = generate(ast, { sourceMap })
89
- const map = Buffer.from(sourceMap.toString()).toString('base64')
40
+ const inlineMap = Buffer.from(map).toString('base64')
90
41
 
91
- return code + '\n' + `//# sourceMappingURL=data:application/json;base64,${map}`
92
- }
42
+ return code + '\n' + `//# sourceMappingURL=data:application/json;base64,${inlineMap}`
93
43
  } catch (e) {
94
44
  log.error(e)
95
45
  }
@@ -101,53 +51,20 @@ function disable (instrumentation) {
101
51
  disabled.add(instrumentation)
102
52
  }
103
53
 
104
- function satisfies (filename, filePath, versions) {
54
+ function getVersion (filename, filePath) {
105
55
  const [basename] = filename.split(filePath)
106
56
 
107
- if (supported[basename] === undefined) {
57
+ if (!moduleVersions[basename]) {
108
58
  try {
109
59
  const pkg = JSON.parse(readFileSync(
110
60
  join(basename, 'package.json'), 'utf8'
111
61
  ))
112
62
 
113
- supported[basename] = semifies(pkg.version, versions)
114
- } catch {
115
- supported[basename] = false
116
- }
117
- }
118
-
119
- return supported[basename]
120
- }
121
-
122
- // TODO: Support index
123
- function fromFunctionQuery (functionQuery) {
124
- const { methodName, functionName, expressionName, className } = functionQuery
125
- const queries = []
126
-
127
- if (className) {
128
- queries.push(
129
- `[id.name="${className}"]`,
130
- `[id.name="${className}"] > ClassExpression`,
131
- `[id.name="${className}"] > ClassBody > [key.name="${methodName}"] > [async]`,
132
- `[id.name="${className}"] > ClassExpression > ClassBody > [key.name="${methodName}"] > [async]`
133
- )
134
- } else if (methodName) {
135
- queries.push(
136
- `ClassBody > [key.name="${methodName}"] > [async]`,
137
- `Property[key.name="${methodName}"] > [async]`
138
- )
139
- }
140
-
141
- if (functionName) {
142
- queries.push(`FunctionDeclaration[id.name="${functionName}"][async]`)
143
- } else if (expressionName) {
144
- queries.push(
145
- `FunctionExpression[id.name="${expressionName}"][async]`,
146
- `ArrowFunctionExpression[id.name="${expressionName}"][async]`
147
- )
63
+ moduleVersions[basename] = pkg.version
64
+ } catch {}
148
65
  }
149
66
 
150
- return queries.join(', ')
67
+ return moduleVersions[basename]
151
68
  }
152
69
 
153
70
  module.exports = { rewrite, disable }
@@ -0,0 +1,103 @@
1
+ 'use strict'
2
+
3
+ module.exports = [
4
+ // getTracer - for patching tracer
5
+ {
6
+ module: {
7
+ name: 'ai',
8
+ versionRange: '>=4.0.0',
9
+ filePath: 'dist/index.js',
10
+ },
11
+ functionQuery: {
12
+ functionName: 'getTracer',
13
+ kind: 'Sync',
14
+ },
15
+ channelName: 'getTracer',
16
+ },
17
+ {
18
+ module: {
19
+ name: 'ai',
20
+ versionRange: '>=4.0.0',
21
+ filePath: 'dist/index.mjs',
22
+ },
23
+ functionQuery: {
24
+ functionName: 'getTracer',
25
+ kind: 'Sync',
26
+ },
27
+ channelName: 'getTracer',
28
+ },
29
+ // selectTelemetryAttributes - makes sure we set isEnabled properly
30
+ {
31
+ module: {
32
+ name: 'ai',
33
+ versionRange: '>=4.0.0 <6.0.0',
34
+ filePath: 'dist/index.js',
35
+ },
36
+ functionQuery: {
37
+ functionName: 'selectTelemetryAttributes',
38
+ kind: 'Sync',
39
+ },
40
+ channelName: 'selectTelemetryAttributes',
41
+ },
42
+ {
43
+ module: {
44
+ name: 'ai',
45
+ versionRange: '>=4.0.0 <6.0.0',
46
+ filePath: 'dist/index.mjs',
47
+ },
48
+ functionQuery: {
49
+ functionName: 'selectTelemetryAttributes',
50
+ kind: 'Sync',
51
+ },
52
+ channelName: 'selectTelemetryAttributes',
53
+ },
54
+ {
55
+ module: {
56
+ name: 'ai',
57
+ versionRange: '>=6.0.0',
58
+ filePath: 'dist/index.js',
59
+ },
60
+ functionQuery: {
61
+ functionName: 'selectTelemetryAttributes',
62
+ kind: 'Async',
63
+ },
64
+ channelName: 'selectTelemetryAttributes',
65
+ },
66
+ {
67
+ module: {
68
+ name: 'ai',
69
+ versionRange: '>=6.0.0',
70
+ filePath: 'dist/index.mjs',
71
+ },
72
+ functionQuery: {
73
+ functionName: 'selectTelemetryAttributes',
74
+ kind: 'Async',
75
+ },
76
+ channelName: 'selectTelemetryAttributes',
77
+ },
78
+ // tool
79
+ {
80
+ module: {
81
+ name: 'ai',
82
+ versionRange: '>=4.0.0',
83
+ filePath: 'dist/index.js',
84
+ },
85
+ functionQuery: {
86
+ functionName: 'tool',
87
+ kind: 'Sync',
88
+ },
89
+ channelName: 'tool',
90
+ },
91
+ {
92
+ module: {
93
+ name: 'ai',
94
+ versionRange: '>=4.0.0',
95
+ filePath: 'dist/index.mjs',
96
+ },
97
+ functionQuery: {
98
+ functionName: 'tool',
99
+ kind: 'Sync',
100
+ },
101
+ channelName: 'tool',
102
+ },
103
+ ]