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
|
@@ -8,7 +8,8 @@ const { getCompileToRegexp } = require('./path-to-regexp')
|
|
|
8
8
|
const {
|
|
9
9
|
getRouterMountPaths,
|
|
10
10
|
joinPath,
|
|
11
|
-
|
|
11
|
+
setLayerMeta,
|
|
12
|
+
getLayerMeta,
|
|
12
13
|
isAppMounted,
|
|
13
14
|
setRouterMountPath,
|
|
14
15
|
extractMountPaths,
|
|
@@ -25,137 +26,209 @@ function isFastSlash (layer, matchers) {
|
|
|
25
26
|
return layer.regexp?.fast_slash ?? matchers.hasSlashPath
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
// TODO: Move this function to a shared file between Express and Router
|
|
29
29
|
/**
|
|
30
|
+
* Cache the per-layer dispatch metadata in a side table instead of replacing
|
|
31
|
+
* `layer.handle`. Phase-sorting hosts (loopback's `_findLayerByHandler`) map a
|
|
32
|
+
* layer back to the user handler by scanning the handle, so the handle has to
|
|
33
|
+
* stay the user's function.
|
|
34
|
+
*
|
|
35
|
+
* @param {{ handle: Function, name?: string, path?: string,
|
|
36
|
+
* regexp?: { fast_star?: boolean, fast_slash?: boolean } }} layer
|
|
37
|
+
* @param {Array<{ path?: string, regex?: RegExp }> & { hasStarPath?: boolean, hasSlashPath?: boolean }} matchers
|
|
38
|
+
*/
|
|
39
|
+
function annotateLayer (layer, matchers) {
|
|
40
|
+
const handle = layer.handle
|
|
41
|
+
const name = handle._name || layer.name || handle.name
|
|
42
|
+
|
|
43
|
+
let captureRoute
|
|
44
|
+
let needMultiMatch = false
|
|
45
|
+
if (matchers.length !== 0 && !isFastStar(layer, matchers) && !isFastSlash(layer, matchers)) {
|
|
46
|
+
if (matchers.length === 1) {
|
|
47
|
+
captureRoute = matchers[0].path
|
|
48
|
+
} else {
|
|
49
|
+
needMultiMatch = true
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
setLayerMeta(layer, { name, captureRoute, needMultiMatch, matchers })
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Resolve the route for a dispatched layer. Single-pattern layers carry a
|
|
58
|
+
* constant route; only multi-pattern stacks need the per-request `layer.path`
|
|
59
|
+
* match the host already computed.
|
|
60
|
+
*
|
|
61
|
+
* @param {{ captureRoute?: string, needMultiMatch: boolean,
|
|
62
|
+
* matchers: Array<{ path?: string, regex?: RegExp }> }} meta
|
|
63
|
+
* @param {{ path?: string }} layer
|
|
64
|
+
* @returns {string | undefined}
|
|
65
|
+
*/
|
|
66
|
+
function resolveLayerRoute (meta, layer) {
|
|
67
|
+
if (!meta.needMultiMatch) return meta.captureRoute
|
|
68
|
+
|
|
69
|
+
for (const matcher of meta.matchers) {
|
|
70
|
+
if (matcher.regex?.test(layer.path)) return matcher.path
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Build the request/error dispatch wrappers for one host (`express` / `router`).
|
|
76
|
+
* They wrap the layer's prototype dispatch and read the side-table metadata, so
|
|
77
|
+
* `layer.handle` is never replaced. The arity guard mirrors the host's own
|
|
78
|
+
* (`handle_request` skips 4-arg handlers, `handle_error` skips the rest), so a
|
|
79
|
+
* span is published only for the layer the host actually runs.
|
|
80
|
+
*
|
|
30
81
|
* @param {string} name Channel namespace (`apm:<name>:middleware:*`).
|
|
31
|
-
* @param {((pattern: string | RegExp) => RegExp | undefined) | undefined} compile
|
|
32
|
-
* Host-resolved path-to-regexp compile adapter, or undefined when the host
|
|
33
|
-
* instance ships no path-to-regexp. Captured here so each express/router
|
|
34
|
-
* instance keeps the dialect it actually loaded.
|
|
35
82
|
*/
|
|
36
|
-
function
|
|
83
|
+
function createLayerDispatchWrappers (name) {
|
|
37
84
|
const enterChannel = channel(`apm:${name}:middleware:enter`)
|
|
38
85
|
const exitChannel = channel(`apm:${name}:middleware:exit`)
|
|
39
86
|
const finishChannel = channel(`apm:${name}:middleware:finish`)
|
|
40
87
|
const errorChannel = channel(`apm:${name}:middleware:error`)
|
|
41
88
|
const nextChannel = channel(`apm:${name}:middleware:next`)
|
|
42
|
-
const routeAddedChannel = channel(`apm:${name}:route:added`)
|
|
43
89
|
// Bound per name so express and a bare router keep independent guards.
|
|
44
90
|
const publishError = createErrorPublisher(errorChannel)
|
|
45
91
|
|
|
46
|
-
function
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
} else {
|
|
61
|
-
needMultiMatch = true
|
|
62
|
-
}
|
|
63
|
-
}
|
|
92
|
+
function wrapNext (req, originalNext) {
|
|
93
|
+
// Per layer dispatch, N per request. Named `next`/arity-1 mirrors the
|
|
94
|
+
// router continuation so wrapCallback skips its name/length rewrite.
|
|
95
|
+
let published = false
|
|
96
|
+
return shimmer.wrapCallback(originalNext, original => function next (error) {
|
|
97
|
+
// A handler that calls `next()` and then rejects (`next(); await bg()`)
|
|
98
|
+
// makes the host call this continuation twice. Publish once so the second
|
|
99
|
+
// pass cannot tag the already-finished span's parent with a late error.
|
|
100
|
+
if (!published) {
|
|
101
|
+
published = true
|
|
102
|
+
|
|
103
|
+
if (error && error !== 'route' && error !== 'router') {
|
|
104
|
+
publishError({ req, error })
|
|
105
|
+
}
|
|
64
106
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
// parameters and `.call`, avoiding the rest-spread Array allocation that
|
|
69
|
-
// the unified shape forced on every middleware invocation.
|
|
70
|
-
const wrapped = original.length === 4
|
|
71
|
-
? shimmer.wrapFunction(original, errorHandlerLayerWrap(layer, name, captureRoute, needMultiMatch, matchers))
|
|
72
|
-
: shimmer.wrapFunction(original, requestHandlerLayerWrap(layer, name, captureRoute, needMultiMatch, matchers))
|
|
73
|
-
|
|
74
|
-
// Workaround for loopback's phase-based middleware sorting. Its
|
|
75
|
-
// `_findLayerByHandler` maps a layer back to the user handler by scanning
|
|
76
|
-
// the layer handle's enumerable properties for the original function.
|
|
77
|
-
// Replacing `layer.handle` with this wrapper hides that handler, so without
|
|
78
|
-
// the back-reference loopback cannot tag the layer with its phase and
|
|
79
|
-
// `app.middleware(phase, ...)` handlers run in insertion order instead of
|
|
80
|
-
// phase order. The property name is part of that contract; keep it stable.
|
|
81
|
-
wrapped._datadog_orig = original
|
|
107
|
+
nextChannel.publish({ req })
|
|
108
|
+
finishChannel.publish({ req })
|
|
109
|
+
}
|
|
82
110
|
|
|
83
|
-
|
|
111
|
+
original.apply(this, arguments)
|
|
112
|
+
})
|
|
84
113
|
}
|
|
85
114
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
115
|
+
// Every host dispatch turns a synchronous throw into `next(error)`, and the
|
|
116
|
+
// hosts that await the handler (router >=2, express 5, express 4 +
|
|
117
|
+
// express-async-errors) do the same for a rejected promise. Passing
|
|
118
|
+
// `wrappedNext` through captures both without a tracer-side try/catch; only
|
|
119
|
+
// `exit` needs the `finally`. express 4's native dispatch converts only the
|
|
120
|
+
// synchronous throw — exactly what the pre-refactor handle wrap caught.
|
|
121
|
+
function wrapLayerRequest (originalRequest) {
|
|
122
|
+
return function (req, res, next) {
|
|
123
|
+
if (!enterChannel.hasSubscribers) return originalRequest.call(this, req, res, next)
|
|
89
124
|
|
|
90
|
-
const
|
|
125
|
+
const meta = getLayerMeta(this)
|
|
126
|
+
if (meta === undefined || this.handle.length > 3) return originalRequest.call(this, req, res, next)
|
|
91
127
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
for (const matcher of matchers) {
|
|
95
|
-
if (matcher.regex?.test(layer.path)) {
|
|
96
|
-
route = matcher.path
|
|
97
|
-
break
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
enterChannel.publish({ name, req, route, layer })
|
|
128
|
+
const wrappedNext = typeof next === 'function' ? wrapNext(req, next) : next
|
|
129
|
+
enterChannel.publish({ name: meta.name, req, route: resolveLayerRoute(meta, this), layer: this })
|
|
103
130
|
|
|
104
131
|
try {
|
|
105
|
-
return
|
|
106
|
-
} catch (error) {
|
|
107
|
-
publishError({ req, error })
|
|
108
|
-
nextChannel.publish({ req })
|
|
109
|
-
finishChannel.publish({ req })
|
|
110
|
-
|
|
111
|
-
throw error
|
|
132
|
+
return originalRequest.call(this, req, res, wrappedNext)
|
|
112
133
|
} finally {
|
|
113
134
|
exitChannel.publish({ req })
|
|
114
135
|
}
|
|
115
136
|
}
|
|
116
137
|
}
|
|
117
138
|
|
|
118
|
-
function
|
|
119
|
-
return
|
|
120
|
-
if (!enterChannel.hasSubscribers) return
|
|
139
|
+
function wrapLayerError (originalError) {
|
|
140
|
+
return function (error, req, res, next) {
|
|
141
|
+
if (!enterChannel.hasSubscribers) return originalError.call(this, error, req, res, next)
|
|
142
|
+
|
|
143
|
+
const meta = getLayerMeta(this)
|
|
144
|
+
if (meta === undefined || this.handle.length !== 4) return originalError.call(this, error, req, res, next)
|
|
121
145
|
|
|
122
146
|
const wrappedNext = typeof next === 'function' ? wrapNext(req, next) : next
|
|
147
|
+
enterChannel.publish({ name: meta.name, req, route: resolveLayerRoute(meta, this), layer: this })
|
|
123
148
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
route = matcher.path
|
|
129
|
-
break
|
|
130
|
-
}
|
|
131
|
-
}
|
|
149
|
+
try {
|
|
150
|
+
return originalError.call(this, error, req, res, wrappedNext)
|
|
151
|
+
} finally {
|
|
152
|
+
exitChannel.publish({ req })
|
|
132
153
|
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
133
156
|
|
|
134
|
-
|
|
157
|
+
// express <4.6.0 has no `Layer` prototype dispatch: the router invokes
|
|
158
|
+
// `layer.handle` directly and routes errors by its arity. There the handle is
|
|
159
|
+
// replaced in place, with the arity preserved so the host still routes
|
|
160
|
+
// correctly. Newer express, express 5, and the router package keep `handle`
|
|
161
|
+
// pristine and are traced through the prototype wraps above.
|
|
162
|
+
function wrapLegacyHandle (layer, original) {
|
|
163
|
+
// `annotateLayer` always runs first in `wrapStack`, so the captured meta is
|
|
164
|
+
// never undefined here (unlike the prototype wraps, where `this` can be any
|
|
165
|
+
// layer the host dispatches).
|
|
166
|
+
const meta = getLayerMeta(layer)
|
|
167
|
+
const wrapped = shimmer.wrapFunction(original, inner => function (...args) {
|
|
168
|
+
if (!enterChannel.hasSubscribers) return inner.apply(this, args)
|
|
169
|
+
|
|
170
|
+
const isErrorHandler = original.length === 4
|
|
171
|
+
const req = args[isErrorHandler ? 1 : 0]
|
|
172
|
+
const nextIndex = isErrorHandler ? 3 : 2
|
|
173
|
+
if (typeof args[nextIndex] === 'function') args[nextIndex] = wrapNext(req, args[nextIndex])
|
|
174
|
+
|
|
175
|
+
enterChannel.publish({ name: meta.name, req, route: resolveLayerRoute(meta, layer), layer })
|
|
135
176
|
|
|
136
177
|
try {
|
|
137
|
-
return
|
|
138
|
-
} catch (
|
|
139
|
-
|
|
178
|
+
return inner.apply(this, args)
|
|
179
|
+
} catch (error) {
|
|
180
|
+
// Unlike the prototype hosts, this router catches a synchronous throw
|
|
181
|
+
// outside the layer and calls its own `next(error)`, never `wrappedNext`.
|
|
182
|
+
// Mirror `wrapNext` here so the throwing layer still tags its error and
|
|
183
|
+
// finishes, rather than lingering on the stack until request finish.
|
|
184
|
+
publishError({ req, error })
|
|
140
185
|
nextChannel.publish({ req })
|
|
141
186
|
finishChannel.publish({ req })
|
|
142
187
|
|
|
143
|
-
throw
|
|
188
|
+
throw error
|
|
144
189
|
} finally {
|
|
145
190
|
exitChannel.publish({ req })
|
|
146
191
|
}
|
|
147
|
-
}
|
|
192
|
+
})
|
|
193
|
+
Object.defineProperty(wrapped, 'length', { value: original.length, configurable: true })
|
|
194
|
+
return wrapped
|
|
148
195
|
}
|
|
149
196
|
|
|
197
|
+
return { wrapLayerRequest, wrapLayerError, wrapLegacyHandle }
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* @param {{ handle_request?: unknown, handleRequest?: unknown }} layer
|
|
202
|
+
*/
|
|
203
|
+
function hasLayerDispatch (layer) {
|
|
204
|
+
return typeof layer.handle_request === 'function' || typeof layer.handleRequest === 'function'
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// TODO: Move this function to a shared file between Express and Router
|
|
208
|
+
/**
|
|
209
|
+
* @param {string} name Channel namespace (`apm:<name>:middleware:*`).
|
|
210
|
+
* @param {((pattern: string | RegExp) => RegExp | undefined) | undefined} compile
|
|
211
|
+
* Host-resolved path-to-regexp compile adapter, or undefined when the host
|
|
212
|
+
* instance ships no path-to-regexp. Captured here so each express/router
|
|
213
|
+
* instance keeps the dialect it actually loaded.
|
|
214
|
+
* @param {((layer: object, original: Function) => Function) | undefined} [wrapLegacyHandle]
|
|
215
|
+
* Fallback that replaces `layer.handle` for hosts without a `Layer` prototype
|
|
216
|
+
* dispatch (express <4.6.0). Omitted for hosts that always ship one.
|
|
217
|
+
*/
|
|
218
|
+
function createWrapRouterMethod (name, compile, wrapLegacyHandle) {
|
|
219
|
+
const routeAddedChannel = channel(`apm:${name}:route:added`)
|
|
220
|
+
|
|
150
221
|
function wrapStack (layers, matchers) {
|
|
151
222
|
for (const layer of layers) {
|
|
152
|
-
|
|
153
|
-
layer.__handle = wrapLayerHandle(layer, layer.__handle, matchers)
|
|
154
|
-
} else {
|
|
155
|
-
layer.handle = wrapLayerHandle(layer, layer.handle, matchers)
|
|
156
|
-
}
|
|
223
|
+
annotateLayer(layer, matchers)
|
|
157
224
|
|
|
158
|
-
|
|
225
|
+
if (wrapLegacyHandle !== undefined && !hasLayerDispatch(layer)) {
|
|
226
|
+
if (layer.__handle) { // express-async-errors
|
|
227
|
+
layer.__handle = wrapLegacyHandle(layer, layer.__handle)
|
|
228
|
+
} else {
|
|
229
|
+
layer.handle = wrapLegacyHandle(layer, layer.handle)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
159
232
|
|
|
160
233
|
if (layer.route) {
|
|
161
234
|
for (const method of METHODS) {
|
|
@@ -169,21 +242,6 @@ function createWrapRouterMethod (name, compile) {
|
|
|
169
242
|
}
|
|
170
243
|
}
|
|
171
244
|
|
|
172
|
-
function wrapNext (req, originalNext) {
|
|
173
|
-
// Per layer dispatch, N per request. Named `next`/arity-1 mirrors the
|
|
174
|
-
// router continuation so wrapCallback skips its name/length rewrite.
|
|
175
|
-
return shimmer.wrapCallback(originalNext, original => function next (error) {
|
|
176
|
-
if (error && error !== 'route' && error !== 'router') {
|
|
177
|
-
publishError({ req, error })
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
nextChannel.publish({ req })
|
|
181
|
-
finishChannel.publish({ req })
|
|
182
|
-
|
|
183
|
-
original.apply(this, arguments)
|
|
184
|
-
})
|
|
185
|
-
}
|
|
186
|
-
|
|
187
245
|
function extractMatchers (fn) {
|
|
188
246
|
const arg = Array.isArray(fn) ? fn.flat(Infinity) : [fn]
|
|
189
247
|
|
|
@@ -304,6 +362,15 @@ addHook({ name: 'router', versions: ['>=1 <2'] }, Router => {
|
|
|
304
362
|
return Router
|
|
305
363
|
})
|
|
306
364
|
|
|
365
|
+
addHook({ name: 'router', file: 'lib/layer.js', versions: ['>=1 <2'] }, Layer => {
|
|
366
|
+
const { wrapLayerRequest, wrapLayerError } = createLayerDispatchWrappers('router')
|
|
367
|
+
|
|
368
|
+
shimmer.wrap(Layer.prototype, 'handle_request', wrapLayerRequest)
|
|
369
|
+
shimmer.wrap(Layer.prototype, 'handle_error', wrapLayerError)
|
|
370
|
+
|
|
371
|
+
return Layer
|
|
372
|
+
})
|
|
373
|
+
|
|
307
374
|
const queryParserReadCh = channel('datadog:query:read:finish')
|
|
308
375
|
|
|
309
376
|
addHook({ name: 'router', versions: ['>=2'] }, Router => {
|
|
@@ -365,6 +432,14 @@ function wrapHandleRequest (original) {
|
|
|
365
432
|
addHook({
|
|
366
433
|
name: 'router', file: 'lib/layer.js', versions: ['>=2'],
|
|
367
434
|
}, Layer => {
|
|
435
|
+
const { wrapLayerRequest, wrapLayerError } = createLayerDispatchWrappers('router')
|
|
436
|
+
|
|
437
|
+
// `handleRequest` carries two concerns: the middleware dispatch span and the
|
|
438
|
+
// param-start publish (`wrapHandleRequest`). Wrap the dispatch first so it
|
|
439
|
+
// sits inner and param-start still fires before `middleware:enter`, matching
|
|
440
|
+
// the order from when the handle itself was wrapped.
|
|
441
|
+
shimmer.wrap(Layer.prototype, 'handleRequest', wrapLayerRequest)
|
|
442
|
+
shimmer.wrap(Layer.prototype, 'handleError', wrapLayerError)
|
|
368
443
|
shimmer.wrap(Layer.prototype, 'handleRequest', wrapHandleRequest)
|
|
369
444
|
return Layer
|
|
370
445
|
})
|
|
@@ -404,4 +479,4 @@ addHook({
|
|
|
404
479
|
return router
|
|
405
480
|
})
|
|
406
481
|
|
|
407
|
-
module.exports = { createWrapRouterMethod }
|
|
482
|
+
module.exports = { createWrapRouterMethod, createLayerDispatchWrappers }
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
const { DsmPathwayCodec, getSizeOrZero } = require('../../../dd-trace/src/datastreams')
|
|
3
3
|
const log = require('../../../dd-trace/src/log')
|
|
4
4
|
const BaseAwsSdkPlugin = require('../base')
|
|
5
|
-
const { isEmpty } = require('../util')
|
|
6
5
|
|
|
7
6
|
function recordDataAsString (data) {
|
|
8
7
|
return Buffer.isBuffer(data) ? data.toString('utf8') : Buffer.from(data).toString('utf8')
|
|
@@ -13,6 +12,16 @@ function recordDataAsString (data) {
|
|
|
13
12
|
// delete on consume, so their working set is ~the active shard count.
|
|
14
13
|
const MAX_TRACKED_SHARD_ITERATORS = 1000
|
|
15
14
|
|
|
15
|
+
// The default Kinesis record limit is 1 MiB. Streams configured for larger records
|
|
16
|
+
// conservatively skip propagation above this point because the request does not expose that limit.
|
|
17
|
+
const KINESIS_DEFAULT_MAX_RECORD_BYTES = 1_048_576
|
|
18
|
+
|
|
19
|
+
// The DSM pathway field (`dd-pathway-ctx-base64`) always serializes to a fixed 55 bytes: a
|
|
20
|
+
// 21-char key, a 28-char base64 value, and 6 bytes of JSON framing. Mirrors PATHWAY_HEADER_BYTES
|
|
21
|
+
// in dd-trace/src/datastreams/processor.js. Reserved in the size gate so setDSMCheckpoint never
|
|
22
|
+
// runs for a record that would ship over the cap once the pathway context is attached.
|
|
23
|
+
const DSM_PATHWAY_FIELD_BYTES = 55
|
|
24
|
+
|
|
16
25
|
class Kinesis extends BaseAwsSdkPlugin {
|
|
17
26
|
static id = 'kinesis'
|
|
18
27
|
static peerServicePrecursors = ['streamname']
|
|
@@ -237,6 +246,12 @@ class Kinesis extends BaseAwsSdkPlugin {
|
|
|
237
246
|
}
|
|
238
247
|
}
|
|
239
248
|
|
|
249
|
+
/**
|
|
250
|
+
* @param {import('../../../dd-trace/src/opentracing/span') | null} span
|
|
251
|
+
* @param {{ Data: Buffer|string|Uint8Array, PartitionKey?: string } | undefined} params
|
|
252
|
+
* @param {string} stream
|
|
253
|
+
* @param {boolean} injectTraceContext
|
|
254
|
+
*/
|
|
240
255
|
injectToMessage (span, params, stream, injectTraceContext) {
|
|
241
256
|
if (!params) {
|
|
242
257
|
return
|
|
@@ -251,30 +266,40 @@ class Kinesis extends BaseAwsSdkPlugin {
|
|
|
251
266
|
}
|
|
252
267
|
}
|
|
253
268
|
|
|
254
|
-
|
|
269
|
+
let ddInfo
|
|
255
270
|
// For now we only inject to the first message; batches may change later.
|
|
256
271
|
if (injectTraceContext) {
|
|
257
|
-
this.tracer.inject(span, 'text_map'
|
|
272
|
+
ddInfo = this.tracer.inject(span, 'text_map')
|
|
258
273
|
}
|
|
259
274
|
|
|
260
|
-
|
|
261
|
-
|
|
275
|
+
const dsmEnabled = this.config.dsmEnabled
|
|
276
|
+
if (!ddInfo && !dsmEnabled) return
|
|
277
|
+
|
|
278
|
+
parsedData._datadog = ddInfo ?? {}
|
|
279
|
+
// Gate before setDSMCheckpoint: a record we can't ship must not record a checkpoint.
|
|
280
|
+
// Reserve the pathway field that DSM appends after the gate.
|
|
281
|
+
let serialized = JSON.stringify(parsedData)
|
|
282
|
+
const reservedBytes = dsmEnabled ? DSM_PATHWAY_FIELD_BYTES : 0
|
|
283
|
+
const partitionKeyBytes = Buffer.byteLength(params.PartitionKey ?? '', 'utf8')
|
|
284
|
+
if (
|
|
285
|
+
Buffer.byteLength(serialized, 'utf8') + partitionKeyBytes + reservedBytes >
|
|
286
|
+
KINESIS_DEFAULT_MAX_RECORD_BYTES
|
|
287
|
+
) {
|
|
288
|
+
log.info('Payload size too large to pass context')
|
|
289
|
+
return
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (dsmEnabled) {
|
|
262
293
|
const dataStreamsContext = this.setDSMCheckpoint(span, params, stream)
|
|
263
|
-
|
|
264
|
-
|
|
294
|
+
ddInfo = DsmPathwayCodec.encode(dataStreamsContext, ddInfo) ?? ddInfo
|
|
295
|
+
if (ddInfo) {
|
|
296
|
+
parsedData._datadog = ddInfo
|
|
297
|
+
serialized = JSON.stringify(parsedData)
|
|
265
298
|
}
|
|
266
299
|
}
|
|
267
300
|
|
|
268
|
-
if (
|
|
301
|
+
if (!ddInfo) return
|
|
269
302
|
|
|
270
|
-
parsedData._datadog = ddInfo
|
|
271
|
-
const serialized = JSON.stringify(parsedData)
|
|
272
|
-
const byteSize = Buffer.byteLength(serialized, 'utf8')
|
|
273
|
-
// Kinesis max payload size is 1 MiB; bail if our context push tipped us over.
|
|
274
|
-
if (byteSize >= 1_048_576) {
|
|
275
|
-
log.info('Payload size too large to pass context')
|
|
276
|
-
return
|
|
277
|
-
}
|
|
278
303
|
params.Data = Buffer.from(serialized, 'utf8')
|
|
279
304
|
}
|
|
280
305
|
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
const { DsmPathwayCodec, getHeadersSize } = require('../../../dd-trace/src/datastreams')
|
|
3
3
|
const log = require('../../../dd-trace/src/log')
|
|
4
4
|
const BaseAwsSdkPlugin = require('../base')
|
|
5
|
-
const { isEmpty } = require('../util')
|
|
6
5
|
|
|
7
6
|
class Sns extends BaseAwsSdkPlugin {
|
|
8
7
|
static id = 'sns'
|
|
@@ -71,6 +70,12 @@ class Sns extends BaseAwsSdkPlugin {
|
|
|
71
70
|
}
|
|
72
71
|
}
|
|
73
72
|
|
|
73
|
+
/**
|
|
74
|
+
* @param {import('../../../dd-trace/src/opentracing/span') | null} span
|
|
75
|
+
* @param {{ Message?: string, MessageAttributes?: Record<string, object> }} params
|
|
76
|
+
* @param {string | undefined} topicArn
|
|
77
|
+
* @param {boolean} injectTraceContext
|
|
78
|
+
*/
|
|
74
79
|
injectToMessage (span, params, topicArn, injectTraceContext) {
|
|
75
80
|
if (!params.MessageAttributes) {
|
|
76
81
|
params.MessageAttributes = {}
|
|
@@ -79,37 +84,32 @@ class Sns extends BaseAwsSdkPlugin {
|
|
|
79
84
|
return
|
|
80
85
|
}
|
|
81
86
|
|
|
82
|
-
|
|
87
|
+
let ddInfo
|
|
83
88
|
// for now, we only want to inject to the first message, this may change for batches in the future
|
|
84
89
|
if (injectTraceContext) {
|
|
85
|
-
this.tracer.inject(span, 'text_map'
|
|
86
|
-
// add ddInfo before checking DSM so we can include DD attributes in payload size
|
|
87
|
-
params.MessageAttributes._datadog = {
|
|
88
|
-
DataType: 'Binary',
|
|
89
|
-
BinaryValue: ddInfo,
|
|
90
|
-
}
|
|
90
|
+
ddInfo = this.tracer.inject(span, 'text_map')
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
if (this.config.dsmEnabled) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
94
|
+
// Add the placeholder before the checkpoint so its payload size includes DD attributes.
|
|
95
|
+
params.MessageAttributes._datadog = {
|
|
96
|
+
DataType: 'Binary',
|
|
97
|
+
BinaryValue: ddInfo ?? {},
|
|
99
98
|
}
|
|
100
99
|
|
|
101
100
|
const dataStreamsContext = this.setDSMCheckpoint(span, params, topicArn)
|
|
102
|
-
DsmPathwayCodec.encode(dataStreamsContext, ddInfo)
|
|
101
|
+
ddInfo = DsmPathwayCodec.encode(dataStreamsContext, ddInfo) ?? ddInfo
|
|
103
102
|
}
|
|
104
103
|
|
|
105
|
-
if (
|
|
106
|
-
if (params.MessageAttributes._datadog) {
|
|
107
|
-
// let's avoid adding any additional information to payload if we failed to inject
|
|
108
|
-
delete params.MessageAttributes._datadog
|
|
109
|
-
}
|
|
110
|
-
} else {
|
|
104
|
+
if (ddInfo) {
|
|
111
105
|
// BINARY types are automatically base64 encoded
|
|
112
|
-
params.MessageAttributes._datadog
|
|
106
|
+
params.MessageAttributes._datadog = {
|
|
107
|
+
DataType: 'Binary',
|
|
108
|
+
BinaryValue: Buffer.from(JSON.stringify(ddInfo)),
|
|
109
|
+
}
|
|
110
|
+
} else if (params.MessageAttributes._datadog) {
|
|
111
|
+
// let's avoid adding any additional information to payload if we failed to inject
|
|
112
|
+
delete params.MessageAttributes._datadog
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
115
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const log = require('../../../dd-trace/src/log')
|
|
4
4
|
const BaseAwsSdkPlugin = require('../base')
|
|
5
5
|
const { DsmPathwayCodec, getHeadersSize } = require('../../../dd-trace/src/datastreams')
|
|
6
|
-
const { extractQueueMetadata
|
|
6
|
+
const { extractQueueMetadata } = require('../util')
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* @typedef {{
|
|
@@ -322,6 +322,12 @@ class Sqs extends BaseAwsSdkPlugin {
|
|
|
322
322
|
}
|
|
323
323
|
}
|
|
324
324
|
|
|
325
|
+
/**
|
|
326
|
+
* @param {import('../../../dd-trace/src/opentracing/span') | null} span
|
|
327
|
+
* @param {{ MessageBody?: string, MessageAttributes?: Record<string, object> } | undefined} params
|
|
328
|
+
* @param {string} queueUrl
|
|
329
|
+
* @param {boolean} injectTraceContext
|
|
330
|
+
*/
|
|
325
331
|
injectToMessage (span, params, queueUrl, injectTraceContext) {
|
|
326
332
|
if (!params) {
|
|
327
333
|
params = {}
|
|
@@ -333,10 +339,10 @@ class Sqs extends BaseAwsSdkPlugin {
|
|
|
333
339
|
return
|
|
334
340
|
}
|
|
335
341
|
|
|
336
|
-
|
|
342
|
+
let ddInfo
|
|
337
343
|
// For now we only inject to the first message; batches may change later.
|
|
338
344
|
if (injectTraceContext) {
|
|
339
|
-
this.tracer.inject(span, 'text_map'
|
|
345
|
+
ddInfo = this.tracer.inject(span, 'text_map')
|
|
340
346
|
}
|
|
341
347
|
|
|
342
348
|
if (this.config.dsmEnabled) {
|
|
@@ -344,19 +350,19 @@ class Sqs extends BaseAwsSdkPlugin {
|
|
|
344
350
|
// matches the on-wire payload, then update with the encoded context.
|
|
345
351
|
params.MessageAttributes._datadog = {
|
|
346
352
|
DataType: 'String',
|
|
347
|
-
StringValue: JSON.stringify(ddInfo),
|
|
353
|
+
StringValue: JSON.stringify(ddInfo ?? {}),
|
|
348
354
|
}
|
|
349
355
|
const dataStreamsContext = this.setDSMCheckpoint(span, params, queueUrl)
|
|
350
|
-
|
|
351
|
-
|
|
356
|
+
ddInfo = DsmPathwayCodec.encode(dataStreamsContext, ddInfo) ?? ddInfo
|
|
357
|
+
if (ddInfo) {
|
|
352
358
|
params.MessageAttributes._datadog.StringValue = JSON.stringify(ddInfo)
|
|
353
|
-
} else
|
|
359
|
+
} else {
|
|
354
360
|
delete params.MessageAttributes._datadog
|
|
355
361
|
}
|
|
356
362
|
return
|
|
357
363
|
}
|
|
358
364
|
|
|
359
|
-
if (
|
|
365
|
+
if (!ddInfo) return
|
|
360
366
|
|
|
361
367
|
params.MessageAttributes._datadog = {
|
|
362
368
|
DataType: 'String',
|
|
@@ -143,31 +143,9 @@ const extractQueueMetadata = queueURL => {
|
|
|
143
143
|
return { queueName, arn }
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
/**
|
|
147
|
-
* Returns true when `obj` has no own enumerable properties. The
|
|
148
|
-
* `for-in` loop with an early return is the only allocation-free shape
|
|
149
|
-
* for this check; benchmarks pin it as 1.3-1.4x faster than
|
|
150
|
-
* `Object.keys(obj).length === 0` across small / medium / large
|
|
151
|
-
* objects, and this is the hot path on every AWS messaging send.
|
|
152
|
-
*
|
|
153
|
-
* Callers in this package only pass plain objects they construct
|
|
154
|
-
* locally, so prototype-enumerable keys are not a concern here. Do not
|
|
155
|
-
* reuse this helper on caller-supplied objects without revisiting that
|
|
156
|
-
* assumption.
|
|
157
|
-
*
|
|
158
|
-
* @param {object} obj
|
|
159
|
-
* @returns {boolean}
|
|
160
|
-
*/
|
|
161
|
-
const isEmpty = obj => {
|
|
162
|
-
// eslint-disable-next-line no-unreachable-loop
|
|
163
|
-
for (const _ in obj) return false
|
|
164
|
-
return true
|
|
165
|
-
}
|
|
166
|
-
|
|
167
146
|
module.exports = {
|
|
168
147
|
generatePointerHash,
|
|
169
148
|
encodeValue,
|
|
170
149
|
extractPrimaryKeys,
|
|
171
150
|
extractQueueMetadata,
|
|
172
|
-
isEmpty,
|
|
173
151
|
}
|
|
@@ -65,6 +65,7 @@ class AzureFunctionsPlugin extends TracingPlugin {
|
|
|
65
65
|
service: this.serviceName(),
|
|
66
66
|
type: 'serverless',
|
|
67
67
|
meta,
|
|
68
|
+
childOf: null,
|
|
68
69
|
}, ctx)
|
|
69
70
|
|
|
70
71
|
if (isMessagingService) {
|
|
@@ -142,7 +143,7 @@ function mapTriggerTag (methodName) {
|
|
|
142
143
|
// message & messages & batch with cardinality of 1 == applicationProperties
|
|
143
144
|
// messages with cardinality of many == applicationPropertiesArray
|
|
144
145
|
function setSpanLinks (triggerType, tracer, span, ctx) {
|
|
145
|
-
const cardinality = ctx.invocationContext.options.trigger.cardinality
|
|
146
|
+
const cardinality = ctx.invocationContext.options.trigger.cardinality ?? 'one'
|
|
146
147
|
const triggerMetadata = ctx.invocationContext.triggerMetadata
|
|
147
148
|
const isServiceBus = triggerType === 'ServiceBus'
|
|
148
149
|
|
|
@@ -1835,13 +1835,13 @@ class CypressPlugin {
|
|
|
1835
1835
|
}
|
|
1836
1836
|
this.activeTestSpan.setTag(TEST_STATUS, testStatus)
|
|
1837
1837
|
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1838
|
+
const testIdentifier = `${testSuite}\0${testName}`
|
|
1839
|
+
let testStatuses = this.testStatuses[testIdentifier]
|
|
1840
|
+
if (testStatuses) {
|
|
1841
|
+
testStatuses.push(testStatus)
|
|
1841
1842
|
} else {
|
|
1842
|
-
this.testStatuses[
|
|
1843
|
+
testStatuses = this.testStatuses[testIdentifier] = [testStatus]
|
|
1843
1844
|
}
|
|
1844
|
-
const testStatuses = this.testStatuses[testName]
|
|
1845
1845
|
const activeSpanTags = this.activeTestSpan.context().getTags()
|
|
1846
1846
|
|
|
1847
1847
|
if (error) {
|