dd-trace 6.2.0 → 6.4.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.
- package/README.md +1 -1
- package/index.electron.js +3 -0
- package/package.json +8 -5
- package/packages/datadog-instrumentations/src/ai.js +29 -24
- package/packages/datadog-instrumentations/src/cookie.js +7 -1
- package/packages/datadog-instrumentations/src/cucumber.js +4 -3
- package/packages/datadog-instrumentations/src/express.js +20 -2
- package/packages/datadog-instrumentations/src/grpc/server.js +18 -0
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/modelcontextprotocol-sdk.js +30 -54
- package/packages/datadog-instrumentations/src/helpers/router-helper.js +8 -8
- package/packages/datadog-instrumentations/src/http/server.js +27 -0
- package/packages/datadog-instrumentations/src/http2/server.js +143 -17
- package/packages/datadog-instrumentations/src/jest.js +10 -4
- package/packages/datadog-instrumentations/src/modelcontextprotocol-sdk.js +248 -1
- package/packages/datadog-instrumentations/src/playwright.js +40 -3
- package/packages/datadog-instrumentations/src/router.js +180 -105
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +41 -16
- package/packages/datadog-plugin-aws-sdk/src/services/sns.js +21 -21
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +14 -8
- package/packages/datadog-plugin-aws-sdk/src/util.js +0 -22
- package/packages/datadog-plugin-azure-functions/src/index.js +2 -1
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +5 -5
- package/packages/datadog-plugin-graphql/src/execute.js +90 -14
- package/packages/datadog-plugin-graphql/src/parse.js +22 -1
- package/packages/datadog-plugin-graphql/src/request.js +33 -2
- package/packages/datadog-plugin-graphql/src/utils.js +42 -0
- package/packages/datadog-plugin-graphql/src/validate.js +13 -1
- package/packages/datadog-plugin-http2/src/server.js +38 -6
- package/packages/datadog-plugin-jest/src/util.js +21 -6
- package/packages/datadog-plugin-modelcontextprotocol-sdk/src/tracing.js +144 -26
- package/packages/datadog-plugin-modelcontextprotocol-sdk/src/utils.js +30 -0
- package/packages/datadog-plugin-playwright/src/index.js +1 -1
- package/packages/datadog-plugin-vitest/src/index.js +4 -2
- package/packages/datadog-shimmer/src/shimmer.js +9 -2
- package/packages/dd-trace/index.electron.js +3 -0
- package/packages/dd-trace/index.js +2 -37
- package/packages/dd-trace/src/appsec/channels.js +1 -0
- package/packages/dd-trace/src/appsec/index.js +6 -3
- package/packages/dd-trace/src/appsec/waf/waf_manager.js +1 -1
- package/packages/dd-trace/src/bootstrap.js +39 -0
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +23 -16
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +14 -2
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +22 -6
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +5 -0
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +55 -5
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +8 -1
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +5 -1
- package/packages/dd-trace/src/config/config-types.d.ts +8 -0
- package/packages/dd-trace/src/config/defaults.js +20 -15
- package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -0
- package/packages/dd-trace/src/config/index.js +13 -3
- package/packages/dd-trace/src/config/parsers.js +79 -0
- package/packages/dd-trace/src/config/supported-configurations.json +17 -0
- package/packages/dd-trace/src/constants.js +7 -0
- package/packages/dd-trace/src/datastreams/pathway.js +6 -4
- package/packages/dd-trace/src/feature-registry.js +22 -0
- package/packages/dd-trace/src/llmobs/constants/tags.js +2 -0
- package/packages/dd-trace/src/llmobs/index.js +10 -2
- package/packages/dd-trace/src/llmobs/tagger.js +19 -1
- package/packages/dd-trace/src/noop/proxy.js +5 -4
- package/packages/dd-trace/src/openfeature/register.js +46 -0
- package/packages/dd-trace/src/opentelemetry/span_processor.js +7 -5
- package/packages/dd-trace/src/opentelemetry/tracer_provider.js +32 -16
- package/packages/dd-trace/src/opentracing/propagation/log.js +11 -2
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +80 -14
- package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +10 -1
- package/packages/dd-trace/src/opentracing/tracer.js +7 -1
- package/packages/dd-trace/src/plugins/util/test.js +9 -4
- package/packages/dd-trace/src/plugins/util/web.js +12 -0
- package/packages/dd-trace/src/proxy.js +21 -9
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +20 -11
- package/packages/dd-trace/src/standalone/index.js +0 -22
- package/vendor/dist/@datadog/sketches-js/index.js +1 -1
- package/vendor/dist/protobufjs/index.js +1 -1
- package/vendor/dist/protobufjs/minimal/index.js +1 -1
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ Most of the documentation for `dd-trace` is available on these webpages:
|
|
|
28
28
|
| [`v2`](https://github.com/DataDog/dd-trace-js/tree/v2.x) |  | `>= v12` | NO | NO | **EOL** | 2022-01-28 | 2023-08-15 |
|
|
29
29
|
| [`v3`](https://github.com/DataDog/dd-trace-js/tree/v3.x) |  | `>= v14` | NO | YES | **EOL** | 2022-08-15 | 2024-05-15 |
|
|
30
30
|
| [`v4`](https://github.com/DataDog/dd-trace-js/tree/v4.x) |  | `>= v16` | YES | YES | **EOL** | 2023-05-12 | 2025-01-11 |
|
|
31
|
-
| [`v5`](https://github.com/DataDog/dd-trace-js/tree/v5.x) |  | `>= v18` | YES | YES | **Maintenance** | 2024-01-11 |
|
|
31
|
+
| [`v5`](https://github.com/DataDog/dd-trace-js/tree/v5.x) |  | `>= v18` | YES | YES | **Maintenance** | 2024-01-11 | 2027-07-02 |
|
|
32
32
|
| [`v6`](https://github.com/DataDog/dd-trace-js/tree/v6.x) |  | `>= v22` | YES | YES | **Current** | 2026-07-02 | Unknown |
|
|
33
33
|
|
|
34
34
|
* EOL = End-of-life
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dd-trace",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.4.0",
|
|
4
4
|
"description": "Datadog APM tracing client for JavaScript",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"typings": "index.d.ts",
|
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
"bench:e2e:test-optimization": "node benchmark/e2e-test-optimization/benchmark-run.js",
|
|
13
13
|
"dependencies:dedupe": "yarn-deduplicate yarn.lock",
|
|
14
14
|
"generate:config:types": "node scripts/generate-config-types.js",
|
|
15
|
+
"generate:electron-package": "node scripts/generate-electron-package.js",
|
|
15
16
|
"verify:config:types": "node scripts/generate-config-types.js --check",
|
|
17
|
+
"verify:electron-package": "node scripts/generate-electron-package.js --check",
|
|
16
18
|
"generate:supported-integrations": "node scripts/generate-supported-integrations.js",
|
|
17
19
|
"verify:supported-integrations": "node scripts/generate-supported-integrations.js --check",
|
|
18
20
|
"type:check": "tsc --noEmit -p tsconfig.dev.json",
|
|
@@ -23,7 +25,7 @@
|
|
|
23
25
|
"lint:inspect": "npx @eslint/config-inspector@latest",
|
|
24
26
|
"lint:codeowners": "codeowners-audit",
|
|
25
27
|
"lint:codeowners:ci": "codeowners-audit --glob='**/*.spec.js' --glob='benchmark/sirun/**' --glob='.agents/**' --glob='.claude/**' --glob='integration-tests/**'",
|
|
26
|
-
"lint:editorconfig": "editorconfig-checker",
|
|
28
|
+
"lint:editorconfig": "docker build -q -t ec .github/editorconfig-checker && docker run --rm --volume=$PWD:/check ec",
|
|
27
29
|
"release:proposal": "node scripts/release/proposal",
|
|
28
30
|
"services": "node ./scripts/install_plugin_modules && node packages/dd-trace/test/setup/services",
|
|
29
31
|
"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",
|
|
@@ -142,6 +144,7 @@
|
|
|
142
144
|
"webpack.js",
|
|
143
145
|
"ext/**/*",
|
|
144
146
|
"index.d.ts",
|
|
147
|
+
"index.electron.js",
|
|
145
148
|
"index.js",
|
|
146
149
|
"init.js",
|
|
147
150
|
"initialize.mjs",
|
|
@@ -151,6 +154,7 @@
|
|
|
151
154
|
"LICENSE.BSD3",
|
|
152
155
|
"loader-hook.mjs",
|
|
153
156
|
"packages/*/index.js",
|
|
157
|
+
"packages/*/index.electron.js",
|
|
154
158
|
"packages/*/lib/**/*",
|
|
155
159
|
"packages/*/src/**/*",
|
|
156
160
|
"packages/datadog-instrumentations/orchestrion.yml",
|
|
@@ -198,15 +202,14 @@
|
|
|
198
202
|
"bun": "1.3.14",
|
|
199
203
|
"c8": "^11.0.0",
|
|
200
204
|
"codeowners-audit": "^2.9.0",
|
|
201
|
-
"editorconfig-checker": "^6.1.1",
|
|
202
205
|
"eslint": "^9.39.2",
|
|
203
206
|
"eslint-plugin-cypress": "^6.4.2",
|
|
204
207
|
"eslint-plugin-import": "^2.32.0",
|
|
205
|
-
"eslint-plugin-jsdoc": "^63.0.
|
|
208
|
+
"eslint-plugin-jsdoc": "^63.0.12",
|
|
206
209
|
"eslint-plugin-mocha": "^11.3.0",
|
|
207
210
|
"eslint-plugin-n": "^18.2.1",
|
|
208
211
|
"eslint-plugin-promise": "^7.3.0",
|
|
209
|
-
"eslint-plugin-sonarjs": "^4.
|
|
212
|
+
"eslint-plugin-sonarjs": "^4.2.0",
|
|
210
213
|
"eslint-plugin-unicorn": "^64.0.0",
|
|
211
214
|
"express": "^5.1.0",
|
|
212
215
|
"glob": "^10.4.5",
|
|
@@ -136,33 +136,38 @@ function wrapTracer (tracer) {
|
|
|
136
136
|
|
|
137
137
|
args[args.length - 1] = shimmer.wrapFunction(cb, function (originalCb) {
|
|
138
138
|
return function (span) {
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
//
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
return spanEnd.apply(this, args)
|
|
149
|
-
}
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
shimmer.wrap(freshSpan, 'setAttributes', function (setAttributes) {
|
|
153
|
-
return function (attributes) {
|
|
139
|
+
// A plain delegating wrapper is used instead of Object.create(span) because
|
|
140
|
+
// Object.create (and Proxy) cannot cross private-field brand checks — any span
|
|
141
|
+
// method that reads a private field (e.g. BridgeSpanBase#statusCode) would throw
|
|
142
|
+
// "Cannot read private member" on a prototype clone. Every method here calls
|
|
143
|
+
// through to the real span instance so private fields always resolve correctly.
|
|
144
|
+
const freshSpan = {
|
|
145
|
+
spanContext () { return span.spanContext() },
|
|
146
|
+
setAttribute (key, value) { span.setAttribute(key, value); return freshSpan },
|
|
147
|
+
setAttributes (attributes) {
|
|
154
148
|
vercelAiSpanSetAttributesChannel.publish({ ctx, attributes })
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
149
|
+
span.setAttributes(attributes)
|
|
150
|
+
return freshSpan
|
|
151
|
+
},
|
|
152
|
+
addEvent (name, attributesOrStartTime, startTime) {
|
|
153
|
+
span.addEvent(name, attributesOrStartTime, startTime)
|
|
154
|
+
return freshSpan
|
|
155
|
+
},
|
|
156
|
+
addLink (link, attrs) { span.addLink(link, attrs); return freshSpan },
|
|
157
|
+
addLinks (links) { span.addLinks(links); return freshSpan },
|
|
158
|
+
setStatus (status) { span.setStatus(status); return freshSpan },
|
|
159
|
+
updateName (spanName) { span.updateName(spanName); return freshSpan },
|
|
160
|
+
isRecording () { return span.isRecording() },
|
|
161
|
+
recordException (exception, timeInput) {
|
|
161
162
|
ctx.error = exception
|
|
162
163
|
vercelAiTracingChannel.error.publish(ctx)
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
|
|
164
|
+
span.recordException(exception, timeInput)
|
|
165
|
+
},
|
|
166
|
+
end (...endArgs) {
|
|
167
|
+
vercelAiTracingChannel.asyncEnd.publish(ctx)
|
|
168
|
+
span.end(...endArgs)
|
|
169
|
+
},
|
|
170
|
+
}
|
|
166
171
|
|
|
167
172
|
return originalCb.call(this, freshSpan)
|
|
168
173
|
}
|
|
@@ -15,7 +15,13 @@ function wrapParse (originalParse) {
|
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
/** @param {typeof import('cookie')} cookie */
|
|
18
19
|
addHook({ name: 'cookie', versions: ['>=0.4'] }, cookie => {
|
|
19
|
-
|
|
20
|
+
for (const name of ['parse', 'parseCookie']) {
|
|
21
|
+
if (typeof cookie[name] === 'function') {
|
|
22
|
+
// shimmer returns a mutable replacement when an ESM namespace export is non-configurable.
|
|
23
|
+
cookie = shimmer.wrap(cookie, name, wrapParse)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
20
26
|
return cookie
|
|
21
27
|
})
|
|
@@ -180,7 +180,7 @@ function getSuiteStatusFromTestStatuses (testStatuses) {
|
|
|
180
180
|
|
|
181
181
|
function getConfiguredEfdRetryCount () {
|
|
182
182
|
const maxSlowTestRetryCount = getMaxEfdRetryCount(earlyFlakeDetectionSlowTestRetries)
|
|
183
|
-
return maxSlowTestRetryCount
|
|
183
|
+
return maxSlowTestRetryCount ?? earlyFlakeDetectionNumRetries
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
function publishWorkerEfdRetryCount (pickle, retryCount) {
|
|
@@ -1178,7 +1178,8 @@ function getWrappedStart (start, frameworkVersion, isParallel = false, isCoordin
|
|
|
1178
1178
|
itrSkippedSuitesCh.publish({ skippedSuites, frameworkVersion })
|
|
1179
1179
|
}
|
|
1180
1180
|
|
|
1181
|
-
const
|
|
1181
|
+
const result = await start.apply(this, arguments)
|
|
1182
|
+
const success = satisfies(frameworkVersion, '>=13.1.0') ? result.success : result
|
|
1182
1183
|
|
|
1183
1184
|
let untestedCoverage
|
|
1184
1185
|
if (getCodeCoverageCh.hasSubscribers) {
|
|
@@ -1228,7 +1229,7 @@ function getWrappedStart (start, frameworkVersion, isParallel = false, isCoordin
|
|
|
1228
1229
|
logTestOptimizationSummary({ attemptToFixExecutions })
|
|
1229
1230
|
loggedAttemptToFixTests.clear()
|
|
1230
1231
|
eventDataCollector = null
|
|
1231
|
-
return
|
|
1232
|
+
return result
|
|
1232
1233
|
}
|
|
1233
1234
|
}
|
|
1234
1235
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const shimmer = require('../../datadog-shimmer')
|
|
4
|
-
const { createWrapRouterMethod } = require('./router')
|
|
4
|
+
const { createWrapRouterMethod, createLayerDispatchWrappers } = require('./router')
|
|
5
5
|
const { addHook, channel, tracingChannel } = require('./helpers/instrument')
|
|
6
6
|
const { getCompileToRegexp } = require('./path-to-regexp')
|
|
7
7
|
const {
|
|
@@ -164,7 +164,10 @@ addHook({ name: 'express', versions: ['>=4'], file: 'lib/express.js' }, express
|
|
|
164
164
|
// It would otherwise produce spans for router and express, and so duplicating them.
|
|
165
165
|
// We now fall back to router instrumentation
|
|
166
166
|
addHook({ name: 'express', versions: ['4'], file: 'lib/express.js' }, express => {
|
|
167
|
-
|
|
167
|
+
// `wrapLegacyHandle` only fires for express <4.6.0 layers, which have no
|
|
168
|
+
// prototype dispatch; 4.6.0+ keeps `handle` pristine (wrapped below).
|
|
169
|
+
const { wrapLegacyHandle } = createLayerDispatchWrappers('express')
|
|
170
|
+
const wrapRouterMethod = createWrapRouterMethod('express', getCompileToRegexp(), wrapLegacyHandle)
|
|
168
171
|
|
|
169
172
|
shimmer.wrap(express.Router, 'use', wrapRouterMethod)
|
|
170
173
|
shimmer.wrap(express.Router, 'route', wrapRouterMethod)
|
|
@@ -172,6 +175,21 @@ addHook({ name: 'express', versions: ['4'], file: 'lib/express.js' }, express =>
|
|
|
172
175
|
return express
|
|
173
176
|
})
|
|
174
177
|
|
|
178
|
+
// Express 4 bundles its own Layer; express 5 delegates to the `router` package
|
|
179
|
+
// (wrapped in router.js). Both keep `layer.handle` the user's function and trace
|
|
180
|
+
// via the prototype dispatch. express <4.6.0 has no such dispatch and is traced
|
|
181
|
+
// by the legacy handle wrap instead.
|
|
182
|
+
addHook({ name: 'express', file: 'lib/router/layer.js', versions: ['4'] }, Layer => {
|
|
183
|
+
if (typeof Layer.prototype.handle_request !== 'function') return Layer
|
|
184
|
+
|
|
185
|
+
const { wrapLayerRequest, wrapLayerError } = createLayerDispatchWrappers('express')
|
|
186
|
+
|
|
187
|
+
shimmer.wrap(Layer.prototype, 'handle_request', wrapLayerRequest)
|
|
188
|
+
shimmer.wrap(Layer.prototype, 'handle_error', wrapLayerError)
|
|
189
|
+
|
|
190
|
+
return Layer
|
|
191
|
+
})
|
|
192
|
+
|
|
175
193
|
const queryParserReadCh = channel('datadog:query:read:finish')
|
|
176
194
|
|
|
177
195
|
function publishQueryParsedAndNext (req, res, next) {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { channel, addHook } = require('../helpers/instrument')
|
|
4
4
|
const shimmer = require('../../../datadog-shimmer')
|
|
5
|
+
const { FOREIGN_HTTP2_SERVER } = require('../../../dd-trace/src/constants')
|
|
5
6
|
const types = require('./types')
|
|
6
7
|
|
|
7
8
|
const startChannel = channel('apm:grpc:server:request:start')
|
|
@@ -76,6 +77,22 @@ function wrapRegister (register) {
|
|
|
76
77
|
}
|
|
77
78
|
}
|
|
78
79
|
|
|
80
|
+
function wrapSetupHandlers (setupHandlers) {
|
|
81
|
+
return function (http2Server) {
|
|
82
|
+
// gRPC builds its transport on a core Node `Http2Server` and drives every
|
|
83
|
+
// call through its own span lifecycle over the raw 'stream' event. Mark the
|
|
84
|
+
// server so the http2 server instrumentation leaves it untraced; otherwise
|
|
85
|
+
// its re-enabled 'stream' path would wrap every gRPC call in a second
|
|
86
|
+
// web.request span and become the top frame. `_setupHandlers` is the single
|
|
87
|
+
// server-creation funnel across all supported versions (the older
|
|
88
|
+
// `createHttp2Server` does not exist), and it runs at bind time, before any
|
|
89
|
+
// request reaches the wrapped `emit`.
|
|
90
|
+
if (http2Server) http2Server[FOREIGN_HTTP2_SERVER] = true
|
|
91
|
+
|
|
92
|
+
return setupHandlers.apply(this, arguments)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
79
96
|
function createWrapEmit (call, ctx, onCancel) {
|
|
80
97
|
return function wrapEmit (emit) {
|
|
81
98
|
return function (event, arg1) {
|
|
@@ -149,6 +166,7 @@ function isEmitter (obj) {
|
|
|
149
166
|
|
|
150
167
|
addHook({ name: '@grpc/grpc-js', versions: ['>=1.0.3'], file: 'build/src/server.js' }, server => {
|
|
151
168
|
shimmer.wrap(server.Server.prototype, 'register', wrapRegister)
|
|
169
|
+
shimmer.wrap(server.Server.prototype, '_setupHandlers', wrapSetupHandlers)
|
|
152
170
|
|
|
153
171
|
return server
|
|
154
172
|
})
|
|
@@ -20,7 +20,7 @@ module.exports = {
|
|
|
20
20
|
'@apollo/server': () => require('../apollo-server'),
|
|
21
21
|
'@apollo/gateway': () => require('../apollo'),
|
|
22
22
|
'@langchain/langgraph': { esmFirst: true, fn: () => require('../langgraph') },
|
|
23
|
-
'@modelcontextprotocol/sdk': () => require('../modelcontextprotocol-sdk'),
|
|
23
|
+
'@modelcontextprotocol/sdk': { esmFirst: true, fn: () => require('../modelcontextprotocol-sdk') },
|
|
24
24
|
'apollo-server-core': () => require('../apollo-server-core'),
|
|
25
25
|
'@aws-sdk/smithy-client': () => require('../aws-sdk'),
|
|
26
26
|
'@aws/durable-execution-sdk-js': () => require('../aws-durable-execution-sdk-js'),
|
|
@@ -1,59 +1,35 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
const CLIENT_METHODS = [
|
|
4
|
+
'callTool',
|
|
5
|
+
'listTools',
|
|
6
|
+
'listResources',
|
|
7
|
+
'readResource',
|
|
8
|
+
'listPrompts',
|
|
9
|
+
'getPrompt',
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
const SERVER_METHODS = [
|
|
13
|
+
'executeToolHandler',
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
function clientEntries (methodName) {
|
|
17
|
+
return ['dist/esm/client/index.js', 'dist/cjs/client/index.js'].map(filePath => ({
|
|
18
|
+
module: { name: '@modelcontextprotocol/sdk', versionRange: '>=1.27.1', filePath },
|
|
19
|
+
functionQuery: { methodName, className: 'Client', kind: 'Async' },
|
|
20
|
+
channelName: `Client_${methodName}`,
|
|
21
|
+
}))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function serverEntries (methodName) {
|
|
25
|
+
return ['dist/esm/server/mcp.js', 'dist/cjs/server/mcp.js'].map(filePath => ({
|
|
26
|
+
module: { name: '@modelcontextprotocol/sdk', versionRange: '>=1.27.1', filePath },
|
|
27
|
+
functionQuery: { methodName, className: 'McpServer', kind: 'Async' },
|
|
28
|
+
channelName: `McpServer_${methodName}`,
|
|
29
|
+
}))
|
|
30
|
+
}
|
|
5
31
|
|
|
6
32
|
module.exports = [
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
name: '@modelcontextprotocol/sdk',
|
|
10
|
-
versionRange: '>=1.27.1',
|
|
11
|
-
filePath: 'dist/esm/client/index.js',
|
|
12
|
-
},
|
|
13
|
-
functionQuery: {
|
|
14
|
-
methodName: 'callTool',
|
|
15
|
-
className: 'Client',
|
|
16
|
-
kind: 'Async',
|
|
17
|
-
},
|
|
18
|
-
channelName: 'Client_callTool',
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
module: {
|
|
22
|
-
name: '@modelcontextprotocol/sdk',
|
|
23
|
-
versionRange: '>=1.27.1',
|
|
24
|
-
filePath: 'dist/cjs/client/index.js',
|
|
25
|
-
},
|
|
26
|
-
functionQuery: {
|
|
27
|
-
methodName: 'callTool',
|
|
28
|
-
className: 'Client',
|
|
29
|
-
kind: 'Async',
|
|
30
|
-
},
|
|
31
|
-
channelName: 'Client_callTool',
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
module: {
|
|
35
|
-
name: '@modelcontextprotocol/sdk',
|
|
36
|
-
versionRange: '>=1.27.1',
|
|
37
|
-
filePath: 'dist/esm/client/index.js',
|
|
38
|
-
},
|
|
39
|
-
functionQuery: {
|
|
40
|
-
methodName: 'listTools',
|
|
41
|
-
className: 'Client',
|
|
42
|
-
kind: 'Async',
|
|
43
|
-
},
|
|
44
|
-
channelName: 'Client_listTools',
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
module: {
|
|
48
|
-
name: '@modelcontextprotocol/sdk',
|
|
49
|
-
versionRange: '>=1.27.1',
|
|
50
|
-
filePath: 'dist/cjs/client/index.js',
|
|
51
|
-
},
|
|
52
|
-
functionQuery: {
|
|
53
|
-
methodName: 'listTools',
|
|
54
|
-
className: 'Client',
|
|
55
|
-
kind: 'Async',
|
|
56
|
-
},
|
|
57
|
-
channelName: 'Client_listTools',
|
|
58
|
-
},
|
|
33
|
+
...CLIENT_METHODS.flatMap(clientEntries),
|
|
34
|
+
...SERVER_METHODS.flatMap(serverEntries),
|
|
59
35
|
]
|
|
@@ -4,7 +4,7 @@ const shimmer = require('../../../datadog-shimmer')
|
|
|
4
4
|
const { channel } = require('./instrument')
|
|
5
5
|
|
|
6
6
|
const routerMountPaths = new WeakMap() // to track mount paths for router instances
|
|
7
|
-
const
|
|
7
|
+
const layerMeta = new WeakMap() // per-layer middleware dispatch metadata (resolved name + route matchers)
|
|
8
8
|
const appMountedRouters = new WeakSet() // to track routers mounted via app.use()
|
|
9
9
|
|
|
10
10
|
const METHODS = [...require('http').METHODS.map(v => v.toLowerCase()), 'all']
|
|
@@ -70,7 +70,7 @@ function collectRoutesFromRouter (router, prefix) {
|
|
|
70
70
|
// Extract mount path from layer
|
|
71
71
|
const mountPath = typeof layer.path === 'string'
|
|
72
72
|
? layer.path
|
|
73
|
-
:
|
|
73
|
+
: getLayerMeta(layer)?.matchers?.[0]?.path || ''
|
|
74
74
|
|
|
75
75
|
const nestedPrefix = joinPath(prefix, mountPath)
|
|
76
76
|
if (nestedPrefix === null) continue
|
|
@@ -121,12 +121,12 @@ function getRouterMountPaths (router) {
|
|
|
121
121
|
return [...paths]
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
function
|
|
125
|
-
|
|
124
|
+
function setLayerMeta (layer, meta) {
|
|
125
|
+
layerMeta.set(layer, meta)
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
function
|
|
129
|
-
return
|
|
128
|
+
function getLayerMeta (layer) {
|
|
129
|
+
return layerMeta.get(layer)
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
function normalizeMethodName (method) {
|
|
@@ -224,8 +224,8 @@ module.exports = {
|
|
|
224
224
|
setRouterMountPath,
|
|
225
225
|
getRouterMountPaths,
|
|
226
226
|
joinPath,
|
|
227
|
-
|
|
228
|
-
|
|
227
|
+
setLayerMeta,
|
|
228
|
+
getLayerMeta,
|
|
229
229
|
markAppMounted,
|
|
230
230
|
isAppMounted,
|
|
231
231
|
normalizeRoutePath,
|
|
@@ -13,6 +13,7 @@ const finishServerCh = channel('apm:http:server:request:finish')
|
|
|
13
13
|
const startWriteHeadCh = channel('apm:http:server:response:writeHead:start')
|
|
14
14
|
const finishSetHeaderCh = channel('datadog:http:server:response:set-header:finish')
|
|
15
15
|
const startSetHeaderCh = channel('datadog:http:server:response:set-header:start')
|
|
16
|
+
const startInformationalResponseCh = channel('datadog:http:server:informational-response:start')
|
|
16
17
|
|
|
17
18
|
const requestFinishedSet = new WeakSet()
|
|
18
19
|
|
|
@@ -24,10 +25,19 @@ addHook({ name: 'http' }, http => {
|
|
|
24
25
|
shimmer.wrap(http.ServerResponse.prototype, 'end', wrapEnd)
|
|
25
26
|
shimmer.wrap(http.ServerResponse.prototype, 'setHeader', wrapSetHeader)
|
|
26
27
|
shimmer.wrap(http.ServerResponse.prototype, 'removeHeader', wrapAppendOrRemoveHeader)
|
|
28
|
+
if (http.ServerResponse.prototype.writeContinue) {
|
|
29
|
+
shimmer.wrap(http.ServerResponse.prototype, 'writeContinue', wrapInformationalResponse)
|
|
30
|
+
}
|
|
31
|
+
if (http.ServerResponse.prototype.writeProcessing) {
|
|
32
|
+
shimmer.wrap(http.ServerResponse.prototype, 'writeProcessing', wrapInformationalResponse)
|
|
33
|
+
}
|
|
27
34
|
// Added in node v16.17.0
|
|
28
35
|
if (http.ServerResponse.prototype.appendHeader) {
|
|
29
36
|
shimmer.wrap(http.ServerResponse.prototype, 'appendHeader', wrapAppendOrRemoveHeader)
|
|
30
37
|
}
|
|
38
|
+
if (http.ServerResponse.prototype.writeEarlyHints) {
|
|
39
|
+
shimmer.wrap(http.ServerResponse.prototype, 'writeEarlyHints', wrapInformationalResponse)
|
|
40
|
+
}
|
|
31
41
|
return http
|
|
32
42
|
})
|
|
33
43
|
|
|
@@ -212,6 +222,23 @@ function wrapAppendOrRemoveHeader (originalMethod) {
|
|
|
212
222
|
}
|
|
213
223
|
}
|
|
214
224
|
|
|
225
|
+
function wrapInformationalResponse (originalMethod) {
|
|
226
|
+
return function wrappedInformationalResponse (...args) {
|
|
227
|
+
if (!startInformationalResponseCh.hasSubscribers) {
|
|
228
|
+
return Reflect.apply(originalMethod, this, args)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const abortController = new AbortController()
|
|
232
|
+
startInformationalResponseCh.publish({ res: this, abortController })
|
|
233
|
+
|
|
234
|
+
if (abortController.signal.aborted) {
|
|
235
|
+
return
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return Reflect.apply(originalMethod, this, args)
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
215
242
|
function wrapEnd (end) {
|
|
216
243
|
return function wrappedEnd (...args) {
|
|
217
244
|
if (!startWriteHeadCh.hasSubscribers) {
|
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
// Old instrumentation temporarily replaced with compatibility mode only instrumentation.
|
|
4
|
-
// See https://github.com/DataDog/dd-trace-js/issues/312
|
|
5
|
-
|
|
6
3
|
const {
|
|
7
4
|
channel,
|
|
8
5
|
addHook,
|
|
9
6
|
} = require('../helpers/instrument')
|
|
10
7
|
const shimmer = require('../../../datadog-shimmer')
|
|
8
|
+
const { FOREIGN_HTTP2_SERVER } = require('../../../dd-trace/src/constants')
|
|
11
9
|
|
|
12
10
|
const startServerCh = channel('apm:http2:server:request:start')
|
|
13
11
|
const errorServerCh = channel('apm:http2:server:request:error')
|
|
12
|
+
const adoptServerCh = channel('apm:http2:server:request:adopt')
|
|
14
13
|
const emitCh = channel('apm:http2:server:response:emit')
|
|
15
14
|
|
|
15
|
+
const HTTP2_HEADER_METHOD = ':method'
|
|
16
|
+
const HTTP2_HEADER_PATH = ':path'
|
|
17
|
+
const HTTP2_HEADER_STATUS = ':status'
|
|
18
|
+
|
|
19
|
+
// Streams whose server span was already created from the 'stream' event. The
|
|
20
|
+
// compatibility layer synthesizes 'request' from that same stream, so the
|
|
21
|
+
// 'request' branch consults this set to avoid creating a second span.
|
|
22
|
+
const tracedStreams = new WeakSet()
|
|
23
|
+
|
|
16
24
|
addHook({ name: 'http2' }, http2 => {
|
|
17
25
|
shimmer.wrap(http2, 'createSecureServer', wrapCreateServer)
|
|
18
26
|
shimmer.wrap(http2, 'createServer', wrapCreateServer)
|
|
@@ -36,34 +44,152 @@ function wrapResponseEmit (originalEmit, ctx) {
|
|
|
36
44
|
}
|
|
37
45
|
}
|
|
38
46
|
|
|
47
|
+
function wrapStreamEmit (originalEmit, ctx) {
|
|
48
|
+
// Named `emit`/arity-1 mirrors the stream method so the per-stream wrap skips
|
|
49
|
+
// its name/length rewrite. `this` is the Http2Stream; the plugin finishes on
|
|
50
|
+
// 'close', the same finish signal as the compatibility response.
|
|
51
|
+
return function emit (eventName) {
|
|
52
|
+
ctx.eventName = eventName
|
|
53
|
+
return emitCh.runStores(ctx, originalEmit, this, ...arguments)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
39
57
|
function wrapEmit (originalEmit) {
|
|
40
58
|
// Named `emit` mirrors the server method so the one-time wrap skips its name
|
|
41
59
|
// rewrite; rest params keep the per-event forwarding allocation-free.
|
|
42
60
|
return function emit (...args) {
|
|
43
|
-
|
|
61
|
+
// A server owned by another instrumentation (e.g. @grpc/grpc-js) drives its
|
|
62
|
+
// own span lifecycle over the raw 'stream' API, so tracing it here would add
|
|
63
|
+
// a spurious web.request span on top of that integration's span and steal
|
|
64
|
+
// the top frame. Skip it entirely; the mark is set at server creation, so
|
|
65
|
+
// this is one property read on servers we do trace.
|
|
66
|
+
if (!startServerCh.hasSubscribers || this[FOREIGN_HTTP2_SERVER]) {
|
|
44
67
|
return Reflect.apply(originalEmit, this, args)
|
|
45
68
|
}
|
|
46
69
|
|
|
47
70
|
const eventName = args[0]
|
|
48
|
-
if (eventName === '
|
|
71
|
+
if (eventName === 'stream') {
|
|
72
|
+
// The compatibility layer synthesizes 'request' from an internal 'stream'
|
|
73
|
+
// listener it registers exactly once when a 'request' listener is added,
|
|
74
|
+
// so `listenerCount('stream')` exceeds one only when the application also
|
|
75
|
+
// registered a raw-stream listener. Owning the span here for that case
|
|
76
|
+
// keeps it active while the application's stream listener runs; the
|
|
77
|
+
// synthesized 'request' that fires nested below then reuses it. A
|
|
78
|
+
// request-only server (no raw-stream listener) is left to the 'request'
|
|
79
|
+
// branch so the compatibility response keeps its richer req/res.
|
|
80
|
+
const requestListenerCount = this.listenerCount('request')
|
|
81
|
+
if (requestListenerCount === 0 || this.listenerCount('stream') > 1) {
|
|
82
|
+
const stream = args[1]
|
|
83
|
+
const headers = args[2]
|
|
84
|
+
const ctx = createStreamAdapter(stream, headers)
|
|
85
|
+
// Only a mixed server (a 'request' listener is present) synthesizes a
|
|
86
|
+
// real request off this stream and adopts the span later, so only then
|
|
87
|
+
// does the context need keying on the stream. A raw-stream-only server
|
|
88
|
+
// never adopts; leaving the flag unset keeps the stream->context write
|
|
89
|
+
// off its hot path.
|
|
90
|
+
ctx.adoptable = requestListenerCount !== 0
|
|
91
|
+
tracedStreams.add(stream)
|
|
92
|
+
|
|
93
|
+
return traceServerRequest(ctx, () => {
|
|
94
|
+
shimmer.wrap(stream, 'emit', emit => wrapStreamEmit(emit, ctx))
|
|
95
|
+
return Reflect.apply(originalEmit, this, args)
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
} else if (eventName === 'request') {
|
|
49
99
|
const req = args[1]
|
|
50
100
|
const res = args[2]
|
|
51
101
|
res.req = req
|
|
52
102
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
103
|
+
// A mixed server (raw-stream + 'request' listeners) already created the
|
|
104
|
+
// span from the 'stream' event above; the stream's single 'close' is the
|
|
105
|
+
// sole finish source, so skip creating a second span. The synthesized
|
|
106
|
+
// request/response are the real objects a user's 'request' handler and
|
|
107
|
+
// the finish `hooks.request` expect, so hand them to the existing
|
|
108
|
+
// stream-backed context rather than leaving it on the throwaway adapter.
|
|
109
|
+
if (tracedStreams.has(req.stream)) {
|
|
110
|
+
adoptServerCh.publish({ req, res })
|
|
111
|
+
} else {
|
|
112
|
+
const ctx = { req, res }
|
|
113
|
+
return traceServerRequest(ctx, () => {
|
|
114
|
+
shimmer.wrap(res, 'emit', emit => wrapResponseEmit(emit, ctx))
|
|
58
115
|
return Reflect.apply(originalEmit, this, args)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
errorServerCh.publish(ctx)
|
|
62
|
-
|
|
63
|
-
throw error
|
|
64
|
-
}
|
|
65
|
-
})
|
|
116
|
+
})
|
|
117
|
+
}
|
|
66
118
|
}
|
|
119
|
+
|
|
67
120
|
return Reflect.apply(originalEmit, this, args)
|
|
68
121
|
}
|
|
69
122
|
}
|
|
123
|
+
|
|
124
|
+
// Enter the request context and run `emitEvent` (the original `emit`, wrapped to
|
|
125
|
+
// publish per-event for the matching response/stream), publishing any synchronous
|
|
126
|
+
// throw from a user handler before letting it propagate.
|
|
127
|
+
/**
|
|
128
|
+
* @param {StreamRequestContext | { req: object, res: object }} ctx
|
|
129
|
+
* @param {() => unknown} emitEvent
|
|
130
|
+
*/
|
|
131
|
+
function traceServerRequest (ctx, emitEvent) {
|
|
132
|
+
return startServerCh.runStores(ctx, () => {
|
|
133
|
+
try {
|
|
134
|
+
return emitEvent()
|
|
135
|
+
} catch (error) {
|
|
136
|
+
ctx.error = error
|
|
137
|
+
errorServerCh.publish(ctx)
|
|
138
|
+
throw error
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* The minimal req/res pair the shared web lifecycle (`web.js`) keys on, built
|
|
145
|
+
* from a core-API `Http2Stream`. The fields below are exactly the surface
|
|
146
|
+
* `web.js` / `url.js` / `ip_extractor.js` read for a stream-backed request; a
|
|
147
|
+
* new read added there must be mirrored here or it resolves to `undefined` on
|
|
148
|
+
* the core path only.
|
|
149
|
+
*
|
|
150
|
+
* @typedef {object} StreamRequestContext
|
|
151
|
+
* @property {object} req
|
|
152
|
+
* @property {import('node:http2').ServerHttp2Stream} req.stream branch key in `web.js`/`url.js`
|
|
153
|
+
* @property {import('node:http2').IncomingHttpHeaders} req.headers raw pseudo-header map
|
|
154
|
+
* @property {string} [req.method]
|
|
155
|
+
* @property {string} [req.url]
|
|
156
|
+
* @property {import('node:net').Socket} [req.socket] peer address source (OTel)
|
|
157
|
+
* @property {object} res
|
|
158
|
+
* @property {object} res.req back-reference used by `wrapResponseEmit`/finish
|
|
159
|
+
* @property {number} res.statusCode read at finish from `stream.sentHeaders`
|
|
160
|
+
* @property {(name: string) => string | number | string[] | undefined} res.getHeader response-header tagging
|
|
161
|
+
*/
|
|
162
|
+
|
|
163
|
+
// Present the core-API stream + pseudo-header map as the minimal req/res pair
|
|
164
|
+
// the shared web lifecycle (`web.js`) consumes. The response status and headers
|
|
165
|
+
// are getters because `stream.sentHeaders` is empty until `stream.respond()`
|
|
166
|
+
// runs in the user handler; a snapshot taken here would always be `undefined`.
|
|
167
|
+
/**
|
|
168
|
+
* @param {import('node:http2').ServerHttp2Stream} stream
|
|
169
|
+
* @param {import('node:http2').IncomingHttpHeaders} headers
|
|
170
|
+
* @returns {StreamRequestContext}
|
|
171
|
+
*/
|
|
172
|
+
function createStreamAdapter (stream, headers) {
|
|
173
|
+
const req = {
|
|
174
|
+
stream,
|
|
175
|
+
headers,
|
|
176
|
+
method: headers[HTTP2_HEADER_METHOD],
|
|
177
|
+
url: headers[HTTP2_HEADER_PATH],
|
|
178
|
+
socket: stream.session?.socket,
|
|
179
|
+
}
|
|
180
|
+
const res = {
|
|
181
|
+
req,
|
|
182
|
+
get statusCode () {
|
|
183
|
+
// A stream aborted before `stream.respond()` has no `:status`. The
|
|
184
|
+
// compatibility `Http2ServerResponse.statusCode` defaults to 200 in that
|
|
185
|
+
// case, so match it rather than report `undefined` (which `validateStatus`
|
|
186
|
+
// would treat as an error and which drops the `http.status_code` tag).
|
|
187
|
+
return stream.sentHeaders?.[HTTP2_HEADER_STATUS] ?? 200
|
|
188
|
+
},
|
|
189
|
+
getHeader (name) {
|
|
190
|
+
return stream.sentHeaders?.[name]
|
|
191
|
+
},
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return { req, res }
|
|
195
|
+
}
|