dd-trace 6.1.0 → 6.3.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.d.ts +7 -0
- package/initialize.mjs +4 -2
- package/package.json +5 -5
- package/packages/datadog-instrumentations/src/cookie.js +7 -1
- package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -1
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +5 -1
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/mercurius.js +31 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/modelcontextprotocol-sdk.js +30 -54
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +19 -5
- package/packages/datadog-instrumentations/src/http/server.js +27 -0
- package/packages/datadog-instrumentations/src/mercurius.js +11 -0
- package/packages/datadog-instrumentations/src/modelcontextprotocol-sdk.js +248 -1
- package/packages/datadog-instrumentations/src/otel-sdk-trace.js +15 -0
- package/packages/datadog-instrumentations/src/playwright.js +40 -3
- package/packages/datadog-instrumentations/src/vitest-main.js +0 -2
- package/packages/datadog-instrumentations/src/vitest-util.js +0 -3
- package/packages/datadog-instrumentations/src/vitest-worker.js +0 -1
- package/packages/datadog-plugin-azure-functions/src/index.js +2 -1
- package/packages/datadog-plugin-graphql/src/execute.js +4 -15
- package/packages/datadog-plugin-graphql/src/index.js +6 -0
- package/packages/datadog-plugin-graphql/src/request.js +104 -0
- package/packages/datadog-plugin-graphql/src/utils.js +177 -0
- package/packages/datadog-plugin-graphql/src/validate.js +24 -1
- 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 +0 -8
- package/packages/datadog-shimmer/src/shimmer.js +9 -2
- 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/lambda.js +8 -8
- package/packages/dd-trace/src/appsec/waf/waf_manager.js +1 -1
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +23 -16
- 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/index.js +13 -3
- package/packages/dd-trace/src/config/parsers.js +79 -0
- package/packages/dd-trace/src/datastreams/writer.js +14 -2
- package/packages/dd-trace/src/encode/0.4.js +23 -2
- package/packages/dd-trace/src/encode/0.5.js +12 -1
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +11 -1
- package/packages/dd-trace/src/encode/coverage-ci-visibility.js +20 -2
- package/packages/dd-trace/src/exporters/common/writer.js +17 -1
- package/packages/dd-trace/src/guardrails/index.js +3 -1
- package/packages/dd-trace/src/guardrails/telemetry.js +40 -3
- package/packages/dd-trace/src/llmobs/constants/tags.js +1 -0
- package/packages/dd-trace/src/llmobs/tagger.js +15 -1
- package/packages/dd-trace/src/msgpack/chunk.js +33 -1
- package/packages/dd-trace/src/msgpack/index.js +6 -1
- 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/plugins/index.js +3 -0
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +20 -11
- package/packages/dd-trace/src/service-naming/schemas/v0/graphql.js +6 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/graphql.js +8 -0
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +5 -6
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/index.d.ts
CHANGED
|
@@ -2514,6 +2514,13 @@ declare namespace tracer {
|
|
|
2514
2514
|
* This plugin automatically instruments the
|
|
2515
2515
|
* [graphql](https://github.com/graphql/graphql-js) module.
|
|
2516
2516
|
*
|
|
2517
|
+
* It also instruments [mercurius](https://github.com/mercurius-js/mercurius)
|
|
2518
|
+
* (the Fastify GraphQL adapter): every request through `app.graphql` /
|
|
2519
|
+
* `reply.graphql` opens a top-level `graphql.request` span that parents the
|
|
2520
|
+
* `graphql.parse`/`graphql.validate`/`graphql.execute` spans and carries the
|
|
2521
|
+
* request text. This span is produced even when mercurius serves the query
|
|
2522
|
+
* from its JIT-compiled path, where `graphql.execute` does not run.
|
|
2523
|
+
*
|
|
2517
2524
|
* The `graphql` integration uses the operation name as the span resource name.
|
|
2518
2525
|
* If no operation name is set, the resource name will always be just `query`,
|
|
2519
2526
|
* `mutation` or `subscription`.
|
package/initialize.mjs
CHANGED
|
@@ -75,8 +75,10 @@ export const resolve = brokenLoaders ? undefined : hookResolve
|
|
|
75
75
|
|
|
76
76
|
if (isMainThread) {
|
|
77
77
|
const require = Module.createRequire(import.meta.url)
|
|
78
|
-
require('./init.js')
|
|
79
|
-
|
|
78
|
+
const initialized = require('./init.js')
|
|
79
|
+
// Only register the loader hook when instrumentation initialized. On a bailout the
|
|
80
|
+
// loader has nothing to instrument and can keep a short-lived process from exiting.
|
|
81
|
+
if (Module.register && initialized) {
|
|
80
82
|
// The loader builds its own include/exclude matcher in `initialize`, so no
|
|
81
83
|
// options need to cross the registration boundary.
|
|
82
84
|
Module.register('./loader-hook.mjs', import.meta.url)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dd-trace",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.3.0",
|
|
4
4
|
"description": "Datadog APM tracing client for JavaScript",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"typings": "index.d.ts",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"lint:inspect": "npx @eslint/config-inspector@latest",
|
|
24
24
|
"lint:codeowners": "codeowners-audit",
|
|
25
25
|
"lint:codeowners:ci": "codeowners-audit --glob='**/*.spec.js' --glob='benchmark/sirun/**' --glob='.agents/**' --glob='.claude/**' --glob='integration-tests/**'",
|
|
26
|
-
"lint:editorconfig": "editorconfig-checker",
|
|
26
|
+
"lint:editorconfig": "docker build -q -t ec .github/editorconfig-checker && docker run --rm --volume=$PWD:/check ec",
|
|
27
27
|
"release:proposal": "node scripts/release/proposal",
|
|
28
28
|
"services": "node ./scripts/install_plugin_modules && node packages/dd-trace/test/setup/services",
|
|
29
29
|
"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",
|
|
@@ -191,18 +191,17 @@
|
|
|
191
191
|
"@stylistic/eslint-plugin": "^5.10.0",
|
|
192
192
|
"@types/mocha": "^10.0.10",
|
|
193
193
|
"@types/node": "^18.19.106",
|
|
194
|
-
"@types/sinon": "^
|
|
194
|
+
"@types/sinon": "^22.0.0",
|
|
195
195
|
"axios": "^1.18.1",
|
|
196
196
|
"benchmark": "^2.1.4",
|
|
197
197
|
"body-parser": "^2.3.0",
|
|
198
198
|
"bun": "1.3.14",
|
|
199
199
|
"c8": "^11.0.0",
|
|
200
200
|
"codeowners-audit": "^2.9.0",
|
|
201
|
-
"editorconfig-checker": "^6.1.1",
|
|
202
201
|
"eslint": "^9.39.2",
|
|
203
202
|
"eslint-plugin-cypress": "^6.4.2",
|
|
204
203
|
"eslint-plugin-import": "^2.32.0",
|
|
205
|
-
"eslint-plugin-jsdoc": "^63.0.
|
|
204
|
+
"eslint-plugin-jsdoc": "^63.0.12",
|
|
206
205
|
"eslint-plugin-mocha": "^11.3.0",
|
|
207
206
|
"eslint-plugin-n": "^18.2.1",
|
|
208
207
|
"eslint-plugin-promise": "^7.3.0",
|
|
@@ -230,6 +229,7 @@
|
|
|
230
229
|
"semifies": "^1.0.0",
|
|
231
230
|
"semver": "^7.8.5",
|
|
232
231
|
"sinon": "^22.0.0",
|
|
232
|
+
"source-map-support": "^0.5.21",
|
|
233
233
|
"test-exclude": "^8.0.0",
|
|
234
234
|
"tiktoken": "^1.0.21",
|
|
235
235
|
"typescript": "^6.0.3",
|
|
@@ -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
|
})
|
|
@@ -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'),
|
|
@@ -48,6 +48,7 @@ module.exports = {
|
|
|
48
48
|
'@langchain/core': { esmFirst: true, fn: () => require('../langchain') },
|
|
49
49
|
'@node-redis/client': () => require('../redis'),
|
|
50
50
|
'@opensearch-project/opensearch': () => require('../opensearch'),
|
|
51
|
+
'@opentelemetry/sdk-trace': () => require('../otel-sdk-trace'),
|
|
51
52
|
'@opentelemetry/sdk-trace-node': () => require('../otel-sdk-trace'),
|
|
52
53
|
'@prisma/client': { esmFirst: true, fn: () => require('../prisma') },
|
|
53
54
|
'./runtime/library.js': () => require('../prisma'),
|
|
@@ -105,6 +106,7 @@ module.exports = {
|
|
|
105
106
|
lodash: () => require('../lodash'),
|
|
106
107
|
mariadb: () => require('../mariadb'),
|
|
107
108
|
memcached: () => require('../memcached'),
|
|
109
|
+
mercurius: () => require('../mercurius'),
|
|
108
110
|
'microgateway-core': () => require('../microgateway-core'),
|
|
109
111
|
mocha: () => require('../mocha'),
|
|
110
112
|
'mocha-each': () => require('../mocha'),
|
|
@@ -37,6 +37,10 @@ for (const matcher of [matcherCjs, matcherEsm]) {
|
|
|
37
37
|
matcher.addTransform('waitForAsyncEnd', waitForAsyncEnd)
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
// Keep the marker split: source-map scanners can read a contiguous token in
|
|
41
|
+
// string literals as this file's own inline map.
|
|
42
|
+
const SOURCE_MAP_PREFIX = '//# sourceMapping' + 'URL=data:application/json;base64,'
|
|
43
|
+
|
|
40
44
|
function rewrite (content, filename, format) {
|
|
41
45
|
if (!content) return content
|
|
42
46
|
if (!filename.includes('node_modules')) return content
|
|
@@ -66,7 +70,7 @@ function rewrite (content, filename, format) {
|
|
|
66
70
|
|
|
67
71
|
const inlineMap = Buffer.from(map).toString('base64')
|
|
68
72
|
|
|
69
|
-
return code + '\n' +
|
|
73
|
+
return code + '\n' + SOURCE_MAP_PREFIX + inlineMap
|
|
70
74
|
} catch (e) {
|
|
71
75
|
log.error(e)
|
|
72
76
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// Mercurius funnels every GraphQL request through the named async function
|
|
4
|
+
// `fastifyGraphQl` (decorated as both `app.graphql` and, via `reply.graphql`,
|
|
5
|
+
// the per-request entry point). Wrapping that one function gives a single
|
|
6
|
+
// top-level span per operation regardless of how the query reaches mercurius
|
|
7
|
+
// — HTTP POST/GET, batched queries, or a programmatic `app.graphql()` call —
|
|
8
|
+
// and regardless of whether the query runs through graphql's `execute` (cold)
|
|
9
|
+
// or a JIT-compiled query (warm), where no `graphql.execute` span fires.
|
|
10
|
+
//
|
|
11
|
+
// The function name and signature `(source, context, variables, operationName)`
|
|
12
|
+
// are stable across the supported major range (verified against 10.x and 16.x),
|
|
13
|
+
// so a `functionName` match needs no per-version file paths.
|
|
14
|
+
module.exports = [
|
|
15
|
+
{
|
|
16
|
+
module: {
|
|
17
|
+
// Floor at 13: it is the oldest major whose fastify-plugin peer (^4)
|
|
18
|
+
// accepts fastify 4, which installs and runs on the oldest supported Node
|
|
19
|
+
// (18). 15+ requires Node 20 and fastify 5, covered on the latest-Node CI
|
|
20
|
+
// leg. The `fastifyGraphQl` funnel is unchanged across this whole range.
|
|
21
|
+
name: 'mercurius',
|
|
22
|
+
versionRange: '>=13',
|
|
23
|
+
filePath: 'index.js',
|
|
24
|
+
},
|
|
25
|
+
functionQuery: {
|
|
26
|
+
functionName: 'fastifyGraphQl',
|
|
27
|
+
kind: 'Async',
|
|
28
|
+
},
|
|
29
|
+
channelName: 'apm:graphql:request',
|
|
30
|
+
},
|
|
31
|
+
]
|
|
@@ -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
|
]
|
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
// the library, replace the custom registration with the built-in option and
|
|
9
9
|
// remove the entry here.
|
|
10
10
|
|
|
11
|
+
const assert = require('node:assert')
|
|
12
|
+
|
|
13
|
+
const clone = require('../../../../../vendor/dist/rfdc')({ proto: false, circles: false })
|
|
14
|
+
|
|
11
15
|
const { parse, query } = require('./compiler')
|
|
12
16
|
|
|
13
17
|
module.exports = { waitForAsyncEnd }
|
|
@@ -28,20 +32,30 @@ function waitForAsyncEnd (_state, node) {
|
|
|
28
32
|
return
|
|
29
33
|
}
|
|
30
34
|
|
|
31
|
-
const returnIndex = statements.findIndex(statement =>
|
|
32
|
-
statement.type === 'ReturnStatement' && statement.argument
|
|
33
|
-
)
|
|
35
|
+
const returnIndex = statements.findIndex(statement =>
|
|
36
|
+
statement.type === 'ReturnStatement' && statement.argument
|
|
37
|
+
)
|
|
34
38
|
|
|
35
|
-
|
|
39
|
+
// The generated fulfillment handler always ends in a return; a miss means the
|
|
40
|
+
// upstream template changed and the caller's try/catch falls back to the
|
|
41
|
+
// unwrapped source.
|
|
42
|
+
assert(returnIndex !== -1, 'waitForAsyncEnd: no return statement to wait on')
|
|
36
43
|
|
|
37
44
|
const waitStatements = parse(`
|
|
38
45
|
function wrapper () {
|
|
39
46
|
const __apm$asyncEndPromise = __apm$ctx.asyncEndPromise;
|
|
40
47
|
if (__apm$asyncEndPromise && typeof __apm$asyncEndPromise.then === 'function') {
|
|
41
|
-
return __apm$asyncEndPromise.then(() => result, () => result);
|
|
48
|
+
return __apm$asyncEndPromise.then(() => __apm$result, () => __apm$result);
|
|
42
49
|
}
|
|
43
50
|
}
|
|
44
51
|
`).body[0].body.body
|
|
45
52
|
|
|
53
|
+
// Resolve to whatever the fulfillment handler returns (its return argument),
|
|
54
|
+
// so a subscriber that reassigned `__apm$ctx.result` in `asyncEnd` still wins.
|
|
55
|
+
const returnArgument = statements[returnIndex].argument
|
|
56
|
+
const { arguments: onSettled } = waitStatements[1].consequent.body[0].argument
|
|
57
|
+
onSettled[0].body = clone(returnArgument)
|
|
58
|
+
onSettled[1].body = clone(returnArgument)
|
|
59
|
+
|
|
46
60
|
statements.splice(returnIndex, 0, ...waitStatements)
|
|
47
61
|
}
|
|
@@ -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) {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// AUTO-GENERATED by dd-compile
|
|
2
|
+
// Orchestrion rewriter handles the actual instrumentation via JSON config.
|
|
3
|
+
// This file registers the module hooks for the rewriter to process.
|
|
4
|
+
|
|
5
|
+
'use strict'
|
|
6
|
+
|
|
7
|
+
const { addHook, getHooks } = require('./helpers/instrument')
|
|
8
|
+
|
|
9
|
+
for (const hook of getHooks('mercurius')) {
|
|
10
|
+
addHook(hook, exports => exports)
|
|
11
|
+
}
|
|
@@ -1,7 +1,254 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const {
|
|
3
|
+
const { tracingChannel } = require('dc-polyfill')
|
|
4
|
+
const shimmer = require('../../datadog-shimmer')
|
|
5
|
+
const { addHook, channel, getHooks } = require('./helpers/instrument')
|
|
4
6
|
|
|
5
7
|
for (const hook of getHooks('@modelcontextprotocol/sdk')) {
|
|
6
8
|
addHook(hook, exports => exports)
|
|
7
9
|
}
|
|
10
|
+
|
|
11
|
+
const serverRequestCh = tracingChannel('apm:mcp:server:request')
|
|
12
|
+
const clientRequestInjectCh = channel('apm:mcp:client:request:inject')
|
|
13
|
+
const serverToolRegisteredCh = channel('apm:mcp:server:tool:registered')
|
|
14
|
+
|
|
15
|
+
const DISTRIBUTED_TRACE_META_KEY = '_dd_trace_context'
|
|
16
|
+
const TRACED_METHOD_PREFIX = /^(?:tools|resources|prompts)\//
|
|
17
|
+
|
|
18
|
+
// Maps Protocol instance → Map<requestId, ctx>. Shares the ctx object between
|
|
19
|
+
// _onrequest (span start, in the correct HTTP async context), the SDK request
|
|
20
|
+
// handler wrapper (result/error capture), and the SDK cleanup path (span finish).
|
|
21
|
+
const pendingContexts = new WeakMap()
|
|
22
|
+
const wrappedAbortControllerMaps = new WeakSet()
|
|
23
|
+
const wrappedRequestHandlerMaps = new WeakSet()
|
|
24
|
+
const wrappedRequestHandlers = new WeakSet()
|
|
25
|
+
|
|
26
|
+
function addTraceContextToRequest (request, traceContext) {
|
|
27
|
+
return {
|
|
28
|
+
...request,
|
|
29
|
+
params: {
|
|
30
|
+
...request.params,
|
|
31
|
+
_meta: {
|
|
32
|
+
...request.params?._meta,
|
|
33
|
+
[DISTRIBUTED_TRACE_META_KEY]: traceContext,
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function getPendingContext (protocol, requestId) {
|
|
40
|
+
return pendingContexts.get(protocol)?.get(requestId)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function finishServerRequest (protocol, requestId) {
|
|
44
|
+
const pending = pendingContexts.get(protocol)
|
|
45
|
+
const ctx = pending?.get(requestId)
|
|
46
|
+
if (!ctx) return
|
|
47
|
+
|
|
48
|
+
pending.delete(requestId)
|
|
49
|
+
serverRequestCh.asyncEnd.publish(ctx)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function wrapRequestHandler (protocol, handler) {
|
|
53
|
+
if (wrappedRequestHandlers.has(handler)) return handler
|
|
54
|
+
|
|
55
|
+
const wrappedHandler = function requestHandlerWithTrace (request, extra) {
|
|
56
|
+
const ctx = getPendingContext(protocol, extra?.requestId)
|
|
57
|
+
if (!ctx) return handler.apply(this, arguments)
|
|
58
|
+
|
|
59
|
+
let result
|
|
60
|
+
try {
|
|
61
|
+
result = handler.apply(this, arguments)
|
|
62
|
+
} catch (err) {
|
|
63
|
+
ctx.error = err
|
|
64
|
+
throw err
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (result && typeof result.then === 'function') {
|
|
68
|
+
result.then(result => {
|
|
69
|
+
ctx.result = result
|
|
70
|
+
}, err => {
|
|
71
|
+
ctx.error = err
|
|
72
|
+
})
|
|
73
|
+
return result
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
ctx.result = result
|
|
77
|
+
return result
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
wrappedRequestHandlers.add(wrappedHandler)
|
|
81
|
+
return wrappedHandler
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function wrapRequestHandlers (protocol) {
|
|
85
|
+
const handlers = protocol._requestHandlers
|
|
86
|
+
if (!handlers || wrappedRequestHandlerMaps.has(handlers)) return
|
|
87
|
+
|
|
88
|
+
wrappedRequestHandlerMaps.add(handlers)
|
|
89
|
+
shimmer.wrap(handlers, 'set', function (original) {
|
|
90
|
+
return function setRequestHandlerWithTrace (method, handler) {
|
|
91
|
+
if (typeof handler === 'function') {
|
|
92
|
+
return original.call(this, method, wrapRequestHandler(protocol, handler))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return original.apply(this, arguments)
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function wrapAbortControllers (protocol) {
|
|
101
|
+
const controllers = protocol._requestHandlerAbortControllers
|
|
102
|
+
if (!controllers || wrappedAbortControllerMaps.has(controllers)) return
|
|
103
|
+
|
|
104
|
+
wrappedAbortControllerMaps.add(controllers)
|
|
105
|
+
shimmer.wrap(controllers, 'delete', function (original) {
|
|
106
|
+
return function deleteAbortControllerWithTrace (requestId) {
|
|
107
|
+
finishServerRequest(protocol, requestId)
|
|
108
|
+
return original.apply(this, arguments)
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
shimmer.wrap(controllers, 'clear', function (original) {
|
|
112
|
+
return function clearAbortControllersWithTrace () {
|
|
113
|
+
for (const requestId of this.keys()) {
|
|
114
|
+
finishServerRequest(protocol, requestId)
|
|
115
|
+
}
|
|
116
|
+
return original.apply(this, arguments)
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function wrapProtocol (Protocol) {
|
|
122
|
+
// Inject trace context into MCP request metadata so out-of-process MCP servers
|
|
123
|
+
// can parent server spans to the client operation span.
|
|
124
|
+
shimmer.wrap(Protocol.prototype, 'request', function (original) {
|
|
125
|
+
return function requestWithTraceContext (request, resultSchema, options) {
|
|
126
|
+
if (!clientRequestInjectCh.hasSubscribers || !TRACED_METHOD_PREFIX.test(request?.method)) {
|
|
127
|
+
return original.apply(this, arguments)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const ctx = {}
|
|
131
|
+
clientRequestInjectCh.publish(ctx)
|
|
132
|
+
|
|
133
|
+
if (!ctx.traceContext) {
|
|
134
|
+
return original.apply(this, arguments)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return original.call(this, addTraceContextToRequest(request, ctx.traceContext), resultSchema, options)
|
|
138
|
+
}
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
// Start spans in _onrequest — this runs inside the express POST /mcp handler's
|
|
142
|
+
// async context, so ALS correctly parents server spans under the HTTP span.
|
|
143
|
+
shimmer.wrap(Protocol.prototype, '_onrequest', function (original) {
|
|
144
|
+
return function _onrequestWithTrace (request, extra) {
|
|
145
|
+
if (!serverRequestCh.hasSubscribers || !TRACED_METHOD_PREFIX.test(request.method)) {
|
|
146
|
+
return original.call(this, request, extra)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
wrapAbortControllers(this)
|
|
150
|
+
const ctx = { request, extra }
|
|
151
|
+
serverRequestCh.start.runStores(ctx, () => {
|
|
152
|
+
let pending = pendingContexts.get(this)
|
|
153
|
+
if (!pending) {
|
|
154
|
+
pending = new Map()
|
|
155
|
+
pendingContexts.set(this, pending)
|
|
156
|
+
}
|
|
157
|
+
pending.set(request.id, ctx)
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
original.call(this, request, extra)
|
|
161
|
+
} catch (err) {
|
|
162
|
+
ctx.error = err
|
|
163
|
+
finishServerRequest(this, request.id)
|
|
164
|
+
throw err
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// The SDK registers an AbortController only when it actually dispatches a handler.
|
|
168
|
+
// If none was registered (MethodNotFound path), no handler will run and our
|
|
169
|
+
// SDK cleanup hook will never fire — finish the span immediately.
|
|
170
|
+
if (!this._requestHandlerAbortControllers?.has(request.id)) {
|
|
171
|
+
ctx.error = { name: 'McpError', message: 'Method not found' }
|
|
172
|
+
finishServerRequest(this, request.id)
|
|
173
|
+
}
|
|
174
|
+
})
|
|
175
|
+
}
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
// The SDK's handler closure Zod-parses the request, stripping the JSON-RPC `id`.
|
|
179
|
+
// Use `extra.requestId` (the SDK's fullExtra field) to correlate with the pending ctx.
|
|
180
|
+
shimmer.wrap(Protocol.prototype, 'setRequestHandler', function (original) {
|
|
181
|
+
return function setRequestHandlerWithTrace () {
|
|
182
|
+
wrapRequestHandlers(this)
|
|
183
|
+
return original.apply(this, arguments)
|
|
184
|
+
}
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
return Protocol
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function wrapMcpServer (McpServer) {
|
|
191
|
+
// Both public registration methods (tool/registerTool) delegate here — one hook covers both.
|
|
192
|
+
// Publishes to serverToolRegisteredCh so tracing.js can build a WeakMap of tool → name
|
|
193
|
+
// for O(1) lookup in McpServerToolCallPlugin (executeToolHandler receives the tool object,
|
|
194
|
+
// not the name string).
|
|
195
|
+
shimmer.wrap(McpServer.prototype, '_createRegisteredTool', function (original) {
|
|
196
|
+
return function (name) {
|
|
197
|
+
const result = original.apply(this, arguments)
|
|
198
|
+
if (serverToolRegisteredCh.hasSubscribers) {
|
|
199
|
+
const tool = this._registeredTools?.[name]
|
|
200
|
+
if (tool) {
|
|
201
|
+
serverToolRegisteredCh.publish({ tool, name })
|
|
202
|
+
shimmer.wrap(tool, 'update', function (update) {
|
|
203
|
+
return function updateWithTrace (updates) {
|
|
204
|
+
const result = update.apply(this, arguments)
|
|
205
|
+
if (updates && Object.hasOwn(updates, 'name')) {
|
|
206
|
+
serverToolRegisteredCh.publish({ tool, name: updates.name || undefined })
|
|
207
|
+
}
|
|
208
|
+
return result
|
|
209
|
+
}
|
|
210
|
+
})
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return result
|
|
214
|
+
}
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
return McpServer
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
addHook({
|
|
221
|
+
name: '@modelcontextprotocol/sdk',
|
|
222
|
+
versions: ['>=1.27.1'],
|
|
223
|
+
file: 'dist/cjs/shared/protocol.js',
|
|
224
|
+
}, exports => {
|
|
225
|
+
wrapProtocol(exports.Protocol)
|
|
226
|
+
return exports
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
addHook({
|
|
230
|
+
name: '@modelcontextprotocol/sdk',
|
|
231
|
+
versions: ['>=1.27.1'],
|
|
232
|
+
file: 'dist/esm/shared/protocol.js',
|
|
233
|
+
}, exports => {
|
|
234
|
+
wrapProtocol(exports.Protocol)
|
|
235
|
+
return exports
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
addHook({
|
|
239
|
+
name: '@modelcontextprotocol/sdk',
|
|
240
|
+
versions: ['>=1.27.1'],
|
|
241
|
+
file: 'dist/cjs/server/mcp.js',
|
|
242
|
+
}, exports => {
|
|
243
|
+
wrapMcpServer(exports.McpServer)
|
|
244
|
+
return exports
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
addHook({
|
|
248
|
+
name: '@modelcontextprotocol/sdk',
|
|
249
|
+
versions: ['>=1.27.1'],
|
|
250
|
+
file: 'dist/esm/server/mcp.js',
|
|
251
|
+
}, exports => {
|
|
252
|
+
wrapMcpServer(exports.McpServer)
|
|
253
|
+
return exports
|
|
254
|
+
})
|
|
@@ -16,6 +16,21 @@ if (isOtelSdkEnabled()) {
|
|
|
16
16
|
})
|
|
17
17
|
return mod
|
|
18
18
|
})
|
|
19
|
+
|
|
20
|
+
// As of @opentelemetry/sdk-node 0.220.0, NodeSDK builds its provider from
|
|
21
|
+
// @opentelemetry/sdk-trace's TracerProvider instead of sdk-trace-node's
|
|
22
|
+
// NodeTracerProvider, so the hook above no longer intercepts it. Wrap this
|
|
23
|
+
// export too, otherwise DD_TRACE_OTEL_ENABLED spans never reach the tracer.
|
|
24
|
+
addHook({
|
|
25
|
+
name: '@opentelemetry/sdk-trace',
|
|
26
|
+
file: 'build/src/TracerProvider.js',
|
|
27
|
+
versions: ['*'],
|
|
28
|
+
}, (mod) => {
|
|
29
|
+
shimmer.wrap(mod, 'TracerProvider', () => {
|
|
30
|
+
return tracer.TracerProvider
|
|
31
|
+
})
|
|
32
|
+
return mod
|
|
33
|
+
})
|
|
19
34
|
}
|
|
20
35
|
|
|
21
36
|
function isOtelSdkEnabled () {
|