dd-trace 4.21.0 → 4.22.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/index.d.ts +5 -0
- package/package.json +5 -5
- package/packages/datadog-instrumentations/src/apollo-server-core.js +41 -0
- package/packages/datadog-instrumentations/src/apollo-server.js +83 -0
- package/packages/datadog-instrumentations/src/graphql.js +18 -4
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
- package/packages/datadog-instrumentations/src/http/client.js +2 -14
- package/packages/datadog-instrumentations/src/next.js +15 -5
- package/packages/datadog-instrumentations/src/rhea.js +15 -9
- package/packages/datadog-plugin-graphql/src/resolve.js +26 -18
- package/packages/datadog-plugin-http/src/client.js +1 -1
- package/packages/datadog-plugin-next/src/index.js +32 -6
- package/packages/dd-trace/src/appsec/activation.js +29 -0
- package/packages/dd-trace/src/appsec/addresses.js +1 -0
- package/packages/dd-trace/src/appsec/api_security_sampler.js +48 -0
- package/packages/dd-trace/src/appsec/blocked_templates.js +4 -1
- package/packages/dd-trace/src/appsec/blocking.js +95 -43
- package/packages/dd-trace/src/appsec/channels.js +4 -1
- package/packages/dd-trace/src/appsec/graphql.js +146 -0
- package/packages/dd-trace/src/appsec/index.js +29 -40
- package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
- package/packages/dd-trace/src/appsec/remote_config/index.js +36 -15
- package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +25 -13
- package/packages/dd-trace/src/config.js +5 -0
- package/packages/dd-trace/src/plugins/util/user-provided-git.js +3 -2
- package/packages/dd-trace/src/profiling/profiler.js +7 -6
- package/packages/dd-trace/src/profiling/profilers/events.js +17 -12
- package/packages/dd-trace/src/profiling/profilers/shared.js +33 -3
- package/packages/dd-trace/src/profiling/profilers/space.js +2 -1
- package/packages/dd-trace/src/profiling/profilers/wall.js +17 -12
- package/packages/dd-trace/src/proxy.js +4 -0
package/index.d.ts
CHANGED
|
@@ -567,6 +567,11 @@ export declare interface TracerOptions {
|
|
|
567
567
|
*/
|
|
568
568
|
blockedTemplateJson?: string,
|
|
569
569
|
|
|
570
|
+
/**
|
|
571
|
+
* Specifies a path to a custom blocking template json file for graphql requests
|
|
572
|
+
*/
|
|
573
|
+
blockedTemplateGraphql?: string,
|
|
574
|
+
|
|
570
575
|
/**
|
|
571
576
|
* Controls the automated user event tracking configuration
|
|
572
577
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dd-trace",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.22.0",
|
|
4
4
|
"description": "Datadog APM tracing client for JavaScript",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"typings": "index.d.ts",
|
|
@@ -68,8 +68,8 @@
|
|
|
68
68
|
"node": ">=16"
|
|
69
69
|
},
|
|
70
70
|
"dependencies": {
|
|
71
|
-
"@datadog/native-appsec": "
|
|
72
|
-
"@datadog/native-iast-rewriter": "2.2.
|
|
71
|
+
"@datadog/native-appsec": "6.0.0",
|
|
72
|
+
"@datadog/native-iast-rewriter": "2.2.2",
|
|
73
73
|
"@datadog/native-iast-taint-tracking": "1.6.4",
|
|
74
74
|
"@datadog/native-metrics": "^2.0.0",
|
|
75
75
|
"@datadog/pprof": "4.1.0",
|
|
@@ -99,9 +99,9 @@
|
|
|
99
99
|
"path-to-regexp": "^0.1.2",
|
|
100
100
|
"pprof-format": "^2.0.7",
|
|
101
101
|
"protobufjs": "^7.2.5",
|
|
102
|
-
"tlhunter-sorted-set": "^0.1.0",
|
|
103
102
|
"retry": "^0.13.1",
|
|
104
|
-
"semver": "^7.5.4"
|
|
103
|
+
"semver": "^7.5.4",
|
|
104
|
+
"tlhunter-sorted-set": "^0.1.0"
|
|
105
105
|
},
|
|
106
106
|
"devDependencies": {
|
|
107
107
|
"@types/node": ">=16",
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { AbortController } = require('node-abort-controller')
|
|
4
|
+
const { addHook } = require('./helpers/instrument')
|
|
5
|
+
const shimmer = require('../../datadog-shimmer')
|
|
6
|
+
const dc = require('dc-polyfill')
|
|
7
|
+
|
|
8
|
+
const requestChannel = dc.tracingChannel('datadog:apollo-server-core:request')
|
|
9
|
+
|
|
10
|
+
addHook({ name: 'apollo-server-core', file: 'dist/runHttpQuery.js', versions: ['>3.0.0'] }, runHttpQueryModule => {
|
|
11
|
+
const HttpQueryError = runHttpQueryModule.HttpQueryError
|
|
12
|
+
|
|
13
|
+
shimmer.wrap(runHttpQueryModule, 'runHttpQuery', function wrapRunHttpQuery (originalRunHttpQuery) {
|
|
14
|
+
return async function runHttpQuery () {
|
|
15
|
+
if (!requestChannel.start.hasSubscribers) {
|
|
16
|
+
return originalRunHttpQuery.apply(this, arguments)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const abortController = new AbortController()
|
|
20
|
+
const abortData = {}
|
|
21
|
+
|
|
22
|
+
const runHttpQueryResult = requestChannel.tracePromise(
|
|
23
|
+
originalRunHttpQuery,
|
|
24
|
+
{ abortController, abortData },
|
|
25
|
+
this,
|
|
26
|
+
...arguments)
|
|
27
|
+
|
|
28
|
+
const abortPromise = new Promise((resolve, reject) => {
|
|
29
|
+
abortController.signal.addEventListener('abort', (event) => {
|
|
30
|
+
// runHttpQuery callbacks are writing the response on resolve/reject.
|
|
31
|
+
// We should return blocking data in the apollo-server-core HttpQueryError object
|
|
32
|
+
reject(new HttpQueryError(abortData.statusCode, abortData.message, true, abortData.headers))
|
|
33
|
+
}, { once: true })
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
return Promise.race([runHttpQueryResult, abortPromise])
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
return runHttpQueryModule
|
|
41
|
+
})
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { AbortController } = require('node-abort-controller')
|
|
4
|
+
const dc = require('dc-polyfill')
|
|
5
|
+
|
|
6
|
+
const { addHook } = require('./helpers/instrument')
|
|
7
|
+
const shimmer = require('../../datadog-shimmer')
|
|
8
|
+
|
|
9
|
+
const graphqlMiddlewareChannel = dc.tracingChannel('datadog:apollo:middleware')
|
|
10
|
+
|
|
11
|
+
const requestChannel = dc.tracingChannel('datadog:apollo:request')
|
|
12
|
+
|
|
13
|
+
let HeaderMap
|
|
14
|
+
|
|
15
|
+
function wrapExecuteHTTPGraphQLRequest (originalExecuteHTTPGraphQLRequest) {
|
|
16
|
+
return async function executeHTTPGraphQLRequest () {
|
|
17
|
+
if (!HeaderMap || !requestChannel.start.hasSubscribers) {
|
|
18
|
+
return originalExecuteHTTPGraphQLRequest.apply(this, arguments)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const abortController = new AbortController()
|
|
22
|
+
const abortData = {}
|
|
23
|
+
|
|
24
|
+
const graphqlResponseData = requestChannel.tracePromise(
|
|
25
|
+
originalExecuteHTTPGraphQLRequest,
|
|
26
|
+
{ abortController, abortData },
|
|
27
|
+
this,
|
|
28
|
+
...arguments)
|
|
29
|
+
|
|
30
|
+
const abortPromise = new Promise((resolve, reject) => {
|
|
31
|
+
abortController.signal.addEventListener('abort', (event) => {
|
|
32
|
+
// This method is expected to return response data
|
|
33
|
+
// with headers, status and body
|
|
34
|
+
const headers = new HeaderMap()
|
|
35
|
+
Object.keys(abortData.headers).forEach(key => {
|
|
36
|
+
headers.set(key, abortData.headers[key])
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
resolve({
|
|
40
|
+
headers: headers,
|
|
41
|
+
status: abortData.statusCode,
|
|
42
|
+
body: {
|
|
43
|
+
kind: 'complete',
|
|
44
|
+
string: abortData.message
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
}, { once: true })
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
return Promise.race([abortPromise, graphqlResponseData])
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function apolloExpress4Hook (express4) {
|
|
55
|
+
shimmer.wrap(express4, 'expressMiddleware', function wrapExpressMiddleware (originalExpressMiddleware) {
|
|
56
|
+
return function expressMiddleware (server, options) {
|
|
57
|
+
const originalMiddleware = originalExpressMiddleware.apply(this, arguments)
|
|
58
|
+
|
|
59
|
+
return shimmer.wrap(originalMiddleware, function (req, res, next) {
|
|
60
|
+
if (!graphqlMiddlewareChannel.start.hasSubscribers) {
|
|
61
|
+
return originalMiddleware.apply(this, arguments)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return graphqlMiddlewareChannel.traceSync(originalMiddleware, { req }, this, ...arguments)
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
return express4
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function apolloHeaderMapHook (headerMap) {
|
|
72
|
+
HeaderMap = headerMap.HeaderMap
|
|
73
|
+
return headerMap
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function apolloServerHook (apolloServer) {
|
|
77
|
+
shimmer.wrap(apolloServer.ApolloServer.prototype, 'executeHTTPGraphQLRequest', wrapExecuteHTTPGraphQLRequest)
|
|
78
|
+
return apolloServer
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
addHook({ name: '@apollo/server', file: 'dist/cjs/ApolloServer.js', versions: ['>=4.0.0'] }, apolloServerHook)
|
|
82
|
+
addHook({ name: '@apollo/server', file: 'dist/cjs/express4/index.js', versions: ['>=4.0.0'] }, apolloExpress4Hook)
|
|
83
|
+
addHook({ name: '@apollo/server', file: 'dist/cjs/utils/HeaderMap.js', versions: ['>=4.0.0'] }, apolloHeaderMapHook)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { AbortController } = require('node-abort-controller')
|
|
4
|
+
|
|
3
5
|
const {
|
|
4
6
|
addHook,
|
|
5
7
|
channel,
|
|
@@ -37,6 +39,13 @@ const validateStartCh = channel('apm:graphql:validate:start')
|
|
|
37
39
|
const validateFinishCh = channel('apm:graphql:validate:finish')
|
|
38
40
|
const validateErrorCh = channel('apm:graphql:validate:error')
|
|
39
41
|
|
|
42
|
+
class AbortError extends Error {
|
|
43
|
+
constructor (message) {
|
|
44
|
+
super(message)
|
|
45
|
+
this.name = 'AbortError'
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
40
49
|
function getOperation (document, operationName) {
|
|
41
50
|
if (!document || !Array.isArray(document.definitions)) {
|
|
42
51
|
return
|
|
@@ -175,11 +184,11 @@ function wrapExecute (execute) {
|
|
|
175
184
|
docSource: documentSources.get(document)
|
|
176
185
|
})
|
|
177
186
|
|
|
178
|
-
const context = { source, asyncResource, fields: {} }
|
|
187
|
+
const context = { source, asyncResource, fields: {}, abortController: new AbortController() }
|
|
179
188
|
|
|
180
189
|
contexts.set(contextValue, context)
|
|
181
190
|
|
|
182
|
-
return callInAsyncScope(exe, asyncResource, this, arguments, (err, res) => {
|
|
191
|
+
return callInAsyncScope(exe, asyncResource, this, arguments, context.abortController, (err, res) => {
|
|
183
192
|
if (finishResolveCh.hasSubscribers) finishResolvers(context)
|
|
184
193
|
|
|
185
194
|
const error = err || (res && res.errors && res.errors[0])
|
|
@@ -207,7 +216,7 @@ function wrapResolve (resolve) {
|
|
|
207
216
|
|
|
208
217
|
const field = assertField(context, info, args)
|
|
209
218
|
|
|
210
|
-
return callInAsyncScope(resolve, field.asyncResource, this, arguments, (err) => {
|
|
219
|
+
return callInAsyncScope(resolve, field.asyncResource, this, arguments, context.abortController, (err) => {
|
|
211
220
|
updateFieldCh.publish({ field, info, err })
|
|
212
221
|
})
|
|
213
222
|
}
|
|
@@ -217,10 +226,15 @@ function wrapResolve (resolve) {
|
|
|
217
226
|
return resolveAsync
|
|
218
227
|
}
|
|
219
228
|
|
|
220
|
-
function callInAsyncScope (fn, aR, thisArg, args, cb) {
|
|
229
|
+
function callInAsyncScope (fn, aR, thisArg, args, abortController, cb) {
|
|
221
230
|
cb = cb || (() => {})
|
|
222
231
|
|
|
223
232
|
return aR.runInAsyncScope(() => {
|
|
233
|
+
if (abortController?.signal.aborted) {
|
|
234
|
+
cb(null, null)
|
|
235
|
+
throw new AbortError('Aborted')
|
|
236
|
+
}
|
|
237
|
+
|
|
224
238
|
try {
|
|
225
239
|
const result = fn.apply(thisArg, args)
|
|
226
240
|
if (result && typeof result.then === 'function') {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
module.exports = {
|
|
4
|
+
'@apollo/server': () => require('../apollo-server'),
|
|
5
|
+
'apollo-server-core': () => require('../apollo-server-core'),
|
|
4
6
|
'@aws-sdk/smithy-client': () => require('../aws-sdk'),
|
|
5
7
|
'@cucumber/cucumber': () => require('../cucumber'),
|
|
6
8
|
'@playwright/test': () => require('../playwright'),
|
|
@@ -69,29 +69,17 @@ function patch (http, methodName) {
|
|
|
69
69
|
try {
|
|
70
70
|
const req = request.call(this, options, callback)
|
|
71
71
|
const emit = req.emit
|
|
72
|
-
|
|
73
|
-
const requestSetTimeout = req.setTimeout
|
|
72
|
+
const setTimeout = req.setTimeout
|
|
74
73
|
|
|
75
74
|
ctx.req = req
|
|
76
75
|
|
|
77
76
|
// tracked to accurately discern custom request socket timeout
|
|
78
77
|
let customRequestTimeout = false
|
|
79
|
-
|
|
80
78
|
req.setTimeout = function () {
|
|
81
79
|
customRequestTimeout = true
|
|
82
|
-
return
|
|
80
|
+
return setTimeout.apply(this, arguments)
|
|
83
81
|
}
|
|
84
82
|
|
|
85
|
-
req.on('socket', socket => {
|
|
86
|
-
if (socket) {
|
|
87
|
-
const socketSetTimeout = socket.setTimeout
|
|
88
|
-
socket.setTimeout = function () {
|
|
89
|
-
customRequestTimeout = true
|
|
90
|
-
return socketSetTimeout.apply(this, arguments)
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
})
|
|
94
|
-
|
|
95
83
|
req.emit = function (eventName, arg) {
|
|
96
84
|
switch (eventName) {
|
|
97
85
|
case 'response': {
|
|
@@ -65,7 +65,7 @@ function wrapRenderToHTML (renderToHTML) {
|
|
|
65
65
|
|
|
66
66
|
function wrapRenderErrorToHTML (renderErrorToHTML) {
|
|
67
67
|
return function (err, req, res, pathname, query) {
|
|
68
|
-
return instrument(req, res, () => renderErrorToHTML.apply(this, arguments))
|
|
68
|
+
return instrument(req, res, err, () => renderErrorToHTML.apply(this, arguments))
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
|
|
@@ -76,8 +76,8 @@ function wrapRenderToResponse (renderToResponse) {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
function wrapRenderErrorToResponse (renderErrorToResponse) {
|
|
79
|
-
return function (ctx) {
|
|
80
|
-
return instrument(ctx.req, ctx.res, () => renderErrorToResponse.apply(this, arguments))
|
|
79
|
+
return function (ctx, err) {
|
|
80
|
+
return instrument(ctx.req, ctx.res, err, () => renderErrorToResponse.apply(this, arguments))
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
|
|
@@ -111,13 +111,23 @@ function getPageFromPath (page, dynamicRoutes = []) {
|
|
|
111
111
|
return getPagePath(page)
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
function instrument (req, res, handler) {
|
|
114
|
+
function instrument (req, res, error, handler) {
|
|
115
|
+
if (typeof error === 'function') {
|
|
116
|
+
handler = error
|
|
117
|
+
error = null
|
|
118
|
+
}
|
|
119
|
+
|
|
115
120
|
req = req.originalRequest || req
|
|
116
121
|
res = res.originalResponse || res
|
|
117
122
|
|
|
118
123
|
// TODO support middleware properly in the future?
|
|
119
124
|
const isMiddleware = req.headers[MIDDLEWARE_HEADER]
|
|
120
|
-
if (isMiddleware || requests.has(req))
|
|
125
|
+
if (isMiddleware || requests.has(req)) {
|
|
126
|
+
if (error) {
|
|
127
|
+
errorChannel.publish({ error })
|
|
128
|
+
}
|
|
129
|
+
return handler()
|
|
130
|
+
}
|
|
121
131
|
|
|
122
132
|
requests.add(req)
|
|
123
133
|
|
|
@@ -22,7 +22,7 @@ const dispatchReceiveCh = channel('apm:rhea:receive:dispatch')
|
|
|
22
22
|
const errorReceiveCh = channel('apm:rhea:receive:error')
|
|
23
23
|
const finishReceiveCh = channel('apm:rhea:receive:finish')
|
|
24
24
|
|
|
25
|
-
const contexts = new WeakMap()
|
|
25
|
+
const contexts = new WeakMap() // key: delivery Fn, val: context
|
|
26
26
|
|
|
27
27
|
addHook({ name: 'rhea', versions: ['>=1'] }, rhea => {
|
|
28
28
|
shimmer.wrap(rhea.message, 'encode', encode => function (msg) {
|
|
@@ -52,7 +52,8 @@ addHook({ name: 'rhea', versions: ['>=1'], file: 'lib/link.js' }, obj => {
|
|
|
52
52
|
startSendCh.publish({ targetAddress, host, port, msg })
|
|
53
53
|
const delivery = send.apply(this, arguments)
|
|
54
54
|
const context = {
|
|
55
|
-
asyncResource
|
|
55
|
+
asyncResource,
|
|
56
|
+
connection: this.connection
|
|
56
57
|
}
|
|
57
58
|
contexts.set(delivery, context)
|
|
58
59
|
|
|
@@ -80,7 +81,8 @@ addHook({ name: 'rhea', versions: ['>=1'], file: 'lib/link.js' }, obj => {
|
|
|
80
81
|
|
|
81
82
|
if (msgObj.delivery) {
|
|
82
83
|
const context = {
|
|
83
|
-
asyncResource
|
|
84
|
+
asyncResource,
|
|
85
|
+
connection: this.connection
|
|
84
86
|
}
|
|
85
87
|
contexts.set(msgObj.delivery, context)
|
|
86
88
|
msgObj.delivery.update = wrapDeliveryUpdate(msgObj.delivery, msgObj.delivery.update)
|
|
@@ -114,7 +116,7 @@ addHook({ name: 'rhea', versions: ['>=1'], file: 'lib/connection.js' }, Connecti
|
|
|
114
116
|
|
|
115
117
|
asyncResource.runInAsyncScope(() => {
|
|
116
118
|
errorReceiveCh.publish(error)
|
|
117
|
-
beforeFinish(delivery, null)
|
|
119
|
+
exports.beforeFinish(delivery, null)
|
|
118
120
|
finishReceiveCh.publish()
|
|
119
121
|
})
|
|
120
122
|
})
|
|
@@ -187,7 +189,7 @@ function patchCircularBuffer (proto, Session) {
|
|
|
187
189
|
const state = remoteState && remoteState.constructor
|
|
188
190
|
? entry.remote_state.constructor.composite_type : undefined
|
|
189
191
|
asyncResource.runInAsyncScope(() => {
|
|
190
|
-
beforeFinish(entry, state)
|
|
192
|
+
exports.beforeFinish(entry, state)
|
|
191
193
|
finishSendCh.publish()
|
|
192
194
|
})
|
|
193
195
|
}
|
|
@@ -217,13 +219,13 @@ function addToInFlightDeliveries (connection, delivery) {
|
|
|
217
219
|
}
|
|
218
220
|
|
|
219
221
|
function beforeFinish (delivery, state) {
|
|
220
|
-
const
|
|
221
|
-
if (
|
|
222
|
+
const context = contexts.get(delivery)
|
|
223
|
+
if (context) {
|
|
222
224
|
if (state) {
|
|
223
225
|
dispatchReceiveCh.publish({ state })
|
|
224
226
|
}
|
|
225
|
-
if (
|
|
226
|
-
|
|
227
|
+
if (context.connection && context.connection[inFlightDeliveries]) {
|
|
228
|
+
context.connection[inFlightDeliveries].delete(delivery)
|
|
227
229
|
}
|
|
228
230
|
}
|
|
229
231
|
}
|
|
@@ -238,3 +240,7 @@ function getStateFromData (stateData) {
|
|
|
238
240
|
}
|
|
239
241
|
}
|
|
240
242
|
}
|
|
243
|
+
|
|
244
|
+
module.exports.inFlightDeliveries = inFlightDeliveries
|
|
245
|
+
module.exports.beforeFinish = beforeFinish
|
|
246
|
+
module.exports.contexts = contexts
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
|
|
4
|
+
const dc = require('dc-polyfill')
|
|
4
5
|
|
|
5
6
|
const collapsedPathSym = Symbol('collapsedPaths')
|
|
6
7
|
|
|
@@ -14,8 +15,6 @@ class GraphQLResolvePlugin extends TracingPlugin {
|
|
|
14
15
|
if (!shouldInstrument(this.config, path)) return
|
|
15
16
|
const computedPathString = path.join('.')
|
|
16
17
|
|
|
17
|
-
addResolver(context, info, args)
|
|
18
|
-
|
|
19
18
|
if (this.config.collapse) {
|
|
20
19
|
if (!context[collapsedPathSym]) {
|
|
21
20
|
context[collapsedPathSym] = {}
|
|
@@ -55,6 +54,10 @@ class GraphQLResolvePlugin extends TracingPlugin {
|
|
|
55
54
|
span.setTag(`graphql.variables.${name}`, variables[name])
|
|
56
55
|
})
|
|
57
56
|
}
|
|
57
|
+
|
|
58
|
+
if (this.resolverStartCh.hasSubscribers) {
|
|
59
|
+
this.resolverStartCh.publish({ context, resolverInfo: getResolverInfo(info, args) })
|
|
60
|
+
}
|
|
58
61
|
}
|
|
59
62
|
|
|
60
63
|
constructor (...args) {
|
|
@@ -69,6 +72,8 @@ class GraphQLResolvePlugin extends TracingPlugin {
|
|
|
69
72
|
field.finishTime = span._getTime ? span._getTime() : 0
|
|
70
73
|
field.error = field.error || err
|
|
71
74
|
})
|
|
75
|
+
|
|
76
|
+
this.resolverStartCh = dc.channel('datadog:graphql:resolver:start')
|
|
72
77
|
}
|
|
73
78
|
|
|
74
79
|
configure (config) {
|
|
@@ -109,28 +114,31 @@ function withCollapse (responsePathAsArray) {
|
|
|
109
114
|
}
|
|
110
115
|
}
|
|
111
116
|
|
|
112
|
-
function
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
117
|
+
function getResolverInfo (info, args) {
|
|
118
|
+
let resolverInfo = null
|
|
119
|
+
const resolverVars = {}
|
|
116
120
|
|
|
117
|
-
if (
|
|
118
|
-
|
|
121
|
+
if (args && Object.keys(args).length) {
|
|
122
|
+
Object.assign(resolverVars, args)
|
|
119
123
|
}
|
|
120
124
|
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
} else {
|
|
127
|
-
resolvers[info.fieldName] = []
|
|
125
|
+
const directives = info.fieldNodes[0].directives
|
|
126
|
+
for (const directive of directives) {
|
|
127
|
+
const argList = {}
|
|
128
|
+
for (const argument of directive['arguments']) {
|
|
129
|
+
argList[argument.name.value] = argument.value.value
|
|
128
130
|
}
|
|
129
|
-
|
|
130
|
-
if (
|
|
131
|
-
|
|
131
|
+
|
|
132
|
+
if (Object.keys(argList).length) {
|
|
133
|
+
resolverVars[directive.name.value] = argList
|
|
132
134
|
}
|
|
133
135
|
}
|
|
136
|
+
|
|
137
|
+
if (Object.keys(resolverVars).length) {
|
|
138
|
+
resolverInfo = { [info.fieldName]: resolverVars }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return resolverInfo
|
|
134
142
|
}
|
|
135
143
|
|
|
136
144
|
module.exports = GraphQLResolvePlugin
|
|
@@ -121,7 +121,7 @@ class HttpClientPlugin extends ClientPlugin {
|
|
|
121
121
|
} else {
|
|
122
122
|
// conditions for no error:
|
|
123
123
|
// 1. not using a custom agent instance with custom timeout specified
|
|
124
|
-
// 2. no invocation of `req.setTimeout`
|
|
124
|
+
// 2. no invocation of `req.setTimeout`
|
|
125
125
|
if (!args.options.agent?.options.timeout && !customRequestTimeout) return
|
|
126
126
|
|
|
127
127
|
span.setTag('error', 1)
|
|
@@ -6,6 +6,8 @@ const analyticsSampler = require('../../dd-trace/src/analytics_sampler')
|
|
|
6
6
|
const { COMPONENT } = require('../../dd-trace/src/constants')
|
|
7
7
|
const web = require('../../dd-trace/src/plugins/util/web')
|
|
8
8
|
|
|
9
|
+
const errorPages = ['/404', '/500', '/_error', '/_not-found']
|
|
10
|
+
|
|
9
11
|
class NextPlugin extends ServerPlugin {
|
|
10
12
|
static get id () {
|
|
11
13
|
return 'next'
|
|
@@ -40,6 +42,13 @@ class NextPlugin extends ServerPlugin {
|
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
error ({ span, error }) {
|
|
45
|
+
if (!span) {
|
|
46
|
+
const store = storage.getStore()
|
|
47
|
+
if (!store) return
|
|
48
|
+
|
|
49
|
+
span = store.span
|
|
50
|
+
}
|
|
51
|
+
|
|
43
52
|
this.addError(error, span)
|
|
44
53
|
}
|
|
45
54
|
|
|
@@ -50,10 +59,20 @@ class NextPlugin extends ServerPlugin {
|
|
|
50
59
|
|
|
51
60
|
const span = store.span
|
|
52
61
|
const error = span.context()._tags['error']
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
62
|
+
const requestError = req.error || nextRequest.error
|
|
63
|
+
|
|
64
|
+
if (requestError) {
|
|
65
|
+
// prioritize user-set errors from API routes
|
|
66
|
+
span.setTag('error', requestError)
|
|
67
|
+
web.addError(req, requestError)
|
|
68
|
+
} else if (error) {
|
|
69
|
+
// general error handling
|
|
70
|
+
span.setTag('error', error)
|
|
71
|
+
web.addError(req, requestError || error)
|
|
72
|
+
} else if (!this.config.validateStatus(res.statusCode)) {
|
|
73
|
+
// where there's no error, we still need to validate status
|
|
74
|
+
span.setTag('error', true)
|
|
75
|
+
web.addError(req, true)
|
|
57
76
|
}
|
|
58
77
|
|
|
59
78
|
span.addTags({
|
|
@@ -73,14 +92,21 @@ class NextPlugin extends ServerPlugin {
|
|
|
73
92
|
const span = store.span
|
|
74
93
|
const req = this._requests.get(span)
|
|
75
94
|
|
|
95
|
+
// safeguard against missing req in complicated timeout scenarios
|
|
96
|
+
if (!req) return
|
|
97
|
+
|
|
76
98
|
// Only use error page names if there's not already a name
|
|
77
99
|
const current = span.context()._tags['next.page']
|
|
78
|
-
|
|
100
|
+
const isErrorPage = errorPages.includes(page)
|
|
101
|
+
|
|
102
|
+
if (current && isErrorPage) {
|
|
79
103
|
return
|
|
80
104
|
}
|
|
81
105
|
|
|
82
106
|
// remove ending /route or /page for appDir projects
|
|
83
|
-
if
|
|
107
|
+
// need to check if not an error page too, as those are marked as app directory
|
|
108
|
+
// in newer versions
|
|
109
|
+
if (isAppPath && !isErrorPage) page = page.substring(0, page.lastIndexOf('/'))
|
|
84
110
|
|
|
85
111
|
// handle static resource
|
|
86
112
|
if (isStatic) {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const Activation = {
|
|
4
|
+
ONECLICK: 'OneClick',
|
|
5
|
+
ENABLED: 'Enabled',
|
|
6
|
+
DISABLED: 'Disabled',
|
|
7
|
+
|
|
8
|
+
fromConfig (config) {
|
|
9
|
+
switch (config.appsec.enabled) {
|
|
10
|
+
// ASM is activated by an env var DD_APPSEC_ENABLED=true
|
|
11
|
+
case true:
|
|
12
|
+
return Activation.ENABLED
|
|
13
|
+
|
|
14
|
+
// ASM is disabled by an env var DD_APPSEC_ENABLED=false
|
|
15
|
+
case false:
|
|
16
|
+
return Activation.DISABLED
|
|
17
|
+
|
|
18
|
+
// ASM is activated by one click remote config
|
|
19
|
+
case undefined:
|
|
20
|
+
return Activation.ONECLICK
|
|
21
|
+
|
|
22
|
+
// Any other value should never occur
|
|
23
|
+
default:
|
|
24
|
+
return Activation.DISABLED
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = Activation
|
|
@@ -13,6 +13,7 @@ module.exports = {
|
|
|
13
13
|
HTTP_INCOMING_RESPONSE_HEADERS: 'server.response.headers.no_cookies',
|
|
14
14
|
// TODO: 'server.response.trailers',
|
|
15
15
|
HTTP_INCOMING_GRAPHQL_RESOLVERS: 'graphql.server.all_resolvers',
|
|
16
|
+
HTTP_INCOMING_GRAPHQL_RESOLVER: 'graphql.server.resolver',
|
|
16
17
|
|
|
17
18
|
HTTP_CLIENT_IP: 'http.client_ip',
|
|
18
19
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const log = require('../log')
|
|
4
|
+
|
|
5
|
+
let enabled
|
|
6
|
+
let requestSampling
|
|
7
|
+
|
|
8
|
+
function configure ({ apiSecurity }) {
|
|
9
|
+
enabled = apiSecurity.enabled
|
|
10
|
+
setRequestSampling(apiSecurity.requestSampling)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function disable () {
|
|
14
|
+
enabled = false
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function setRequestSampling (sampling) {
|
|
18
|
+
requestSampling = parseRequestSampling(sampling)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function parseRequestSampling (requestSampling) {
|
|
22
|
+
let parsed = parseFloat(requestSampling)
|
|
23
|
+
|
|
24
|
+
if (isNaN(parsed)) {
|
|
25
|
+
log.warn(`Incorrect API Security request sampling value: ${requestSampling}`)
|
|
26
|
+
|
|
27
|
+
parsed = 0
|
|
28
|
+
} else {
|
|
29
|
+
parsed = Math.min(1, Math.max(0, parsed))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return parsed
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function sampleRequest () {
|
|
36
|
+
if (!enabled || !requestSampling) {
|
|
37
|
+
return false
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return Math.random() <= requestSampling
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = {
|
|
44
|
+
configure,
|
|
45
|
+
disable,
|
|
46
|
+
setRequestSampling,
|
|
47
|
+
sampleRequest
|
|
48
|
+
}
|
|
@@ -5,7 +5,10 @@ const html = `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta n
|
|
|
5
5
|
|
|
6
6
|
const json = `{"errors":[{"title":"You've been blocked","detail":"Sorry, you cannot access this page. Please contact the customer service team. Security provided by Datadog."}]}`
|
|
7
7
|
|
|
8
|
+
const graphqlJson = `{"errors":[{"message":"You've been blocked","extensions":{"detail":"Sorry, you cannot perform this operation. Please contact the customer service team. Security provided by Datadog."}}]}`
|
|
9
|
+
|
|
8
10
|
module.exports = {
|
|
9
11
|
html,
|
|
10
|
-
json
|
|
12
|
+
json,
|
|
13
|
+
graphqlJson
|
|
11
14
|
}
|