dd-trace 2.9.1 → 2.10.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/package.json +5 -5
- package/packages/datadog-instrumentations/index.js +1 -0
- package/packages/datadog-instrumentations/src/graphql.js +352 -0
- package/packages/datadog-instrumentations/src/http/client.js +1 -1
- package/packages/datadog-instrumentations/src/jest.js +14 -14
- package/packages/datadog-instrumentations/src/koa.js +1 -1
- package/packages/datadog-instrumentations/src/tedious.js +1 -1
- package/packages/datadog-plugin-graphql/src/index.js +123 -412
- package/packages/datadog-plugin-graphql/src/resolve.js +125 -0
- package/packages/datadog-plugin-http/src/client.js +10 -7
- package/packages/datadog-plugin-jest/src/util.js +13 -1
- package/packages/dd-trace/src/config.js +1 -1
- package/packages/dd-trace/src/exporters/agent/writer.js +1 -1
- package/packages/dd-trace/src/plugin_manager.js +21 -0
- package/packages/dd-trace/src/plugins/util/test.js +3 -0
- package/packages/dd-trace/src/profiling/exporters/agent.js +1 -1
- package/packages/dd-trace/src/startup-log.js +1 -1
- package/packages/dd-trace/src/telemetry.js +1 -1
- package/packages/dd-trace/lib/version.js +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dd-trace",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.10.0",
|
|
4
4
|
"description": "Datadog APM tracing client for JavaScript",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"typings": "index.d.ts",
|
|
@@ -59,9 +59,9 @@
|
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
61
|
"@datadog/native-appsec": "^1.2.0",
|
|
62
|
-
"@datadog/native-metrics": "^1.
|
|
63
|
-
"@datadog/pprof": "^0.
|
|
64
|
-
"@datadog/sketches-js": "^1.0.
|
|
62
|
+
"@datadog/native-metrics": "^1.4.0",
|
|
63
|
+
"@datadog/pprof": "^0.5.1",
|
|
64
|
+
"@datadog/sketches-js": "^1.0.5",
|
|
65
65
|
"@types/node": ">=12",
|
|
66
66
|
"crypto-randomuuid": "^1.0.0",
|
|
67
67
|
"diagnostics_channel": "^1.1.0",
|
|
@@ -106,8 +106,8 @@
|
|
|
106
106
|
"jszip": "^3.5.0",
|
|
107
107
|
"mkdirp": "^0.5.1",
|
|
108
108
|
"mocha": "8",
|
|
109
|
-
"multer": "^1.4.2",
|
|
110
109
|
"msgpack-lite": "^0.1.26",
|
|
110
|
+
"multer": "^1.4.5-lts.1",
|
|
111
111
|
"nock": "^11.3.3",
|
|
112
112
|
"nyc": "^15.1.0",
|
|
113
113
|
"proxyquire": "^1.8.0",
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
addHook,
|
|
5
|
+
channel,
|
|
6
|
+
AsyncResource
|
|
7
|
+
} = require('./helpers/instrument')
|
|
8
|
+
const shimmer = require('../../datadog-shimmer')
|
|
9
|
+
|
|
10
|
+
/** cached objects */
|
|
11
|
+
|
|
12
|
+
const contexts = new WeakMap()
|
|
13
|
+
const documentSources = new WeakMap()
|
|
14
|
+
const patchedResolvers = new WeakSet()
|
|
15
|
+
const patchedTypes = new WeakSet()
|
|
16
|
+
|
|
17
|
+
/** CHANNELS */
|
|
18
|
+
|
|
19
|
+
// execute channels
|
|
20
|
+
const startResolveCh = channel('apm:graphql:resolve:start')
|
|
21
|
+
const startExecuteCh = channel('apm:graphql:execute:start')
|
|
22
|
+
const finishExecuteCh = channel('apm:graphql:execute:finish')
|
|
23
|
+
const finishResolveCh = channel('apm:graphql:resolve:finish')
|
|
24
|
+
const updateFieldCh = channel('apm:graphql:resolve:updateField')
|
|
25
|
+
const executeErrorCh = channel('apm:graphql:execute:error')
|
|
26
|
+
|
|
27
|
+
// parse channels
|
|
28
|
+
const parseStartCh = channel('apm:graphql:parser:start')
|
|
29
|
+
const parseFinishCh = channel('apm:graphql:parser:finish')
|
|
30
|
+
const parseErrorCh = channel('apm:graphql:parser:error')
|
|
31
|
+
|
|
32
|
+
// validate channels
|
|
33
|
+
const validateStartCh = channel('apm:graphql:validate:start')
|
|
34
|
+
const validateFinishCh = channel('apm:graphql:validate:finish')
|
|
35
|
+
const validateErrorCh = channel('apm:graphql:validate:error')
|
|
36
|
+
|
|
37
|
+
function getOperation (document, operationName) {
|
|
38
|
+
if (!document || !Array.isArray(document.definitions)) {
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const definitions = document.definitions.filter(def => def)
|
|
43
|
+
const types = ['query', 'mutation', 'subscription']
|
|
44
|
+
|
|
45
|
+
if (operationName) {
|
|
46
|
+
return definitions
|
|
47
|
+
.filter(def => types.indexOf(def.operation) !== -1)
|
|
48
|
+
.find(def => operationName === (def.name && def.name.value))
|
|
49
|
+
} else {
|
|
50
|
+
return definitions.find(def => types.indexOf(def.operation) !== -1)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function normalizeArgs (args, defaultFieldResolver) {
|
|
55
|
+
if (args.length !== 1) return normalizePositional(args, defaultFieldResolver)
|
|
56
|
+
|
|
57
|
+
args[0].contextValue = args[0].contextValue || {}
|
|
58
|
+
args[0].fieldResolver = wrapResolve(args[0].fieldResolver || defaultFieldResolver)
|
|
59
|
+
|
|
60
|
+
return args[0]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function normalizePositional (args, defaultFieldResolver) {
|
|
64
|
+
args[3] = args[3] || {} // contextValue
|
|
65
|
+
args[6] = wrapResolve(args[6] || defaultFieldResolver) // fieldResolver
|
|
66
|
+
args.length = Math.max(args.length, 7)
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
schema: args[0],
|
|
70
|
+
document: args[1],
|
|
71
|
+
rootValue: args[2],
|
|
72
|
+
contextValue: args[3],
|
|
73
|
+
variableValues: args[4],
|
|
74
|
+
operationName: args[5],
|
|
75
|
+
fieldResolver: args[6]
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function wrapParse (parse) {
|
|
80
|
+
return function (source) {
|
|
81
|
+
if (!parseStartCh.hasSubscribers) {
|
|
82
|
+
return parse.apply(this, arguments)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const asyncResource = new AsyncResource('bound-anonymous-fn')
|
|
86
|
+
|
|
87
|
+
return asyncResource.runInAsyncScope(() => {
|
|
88
|
+
parseStartCh.publish()
|
|
89
|
+
let document
|
|
90
|
+
try {
|
|
91
|
+
document = parse.apply(this, arguments)
|
|
92
|
+
const operation = getOperation(document)
|
|
93
|
+
|
|
94
|
+
if (!operation) return document
|
|
95
|
+
|
|
96
|
+
if (source) {
|
|
97
|
+
documentSources.set(document, source.body || source)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return document
|
|
101
|
+
} catch (err) {
|
|
102
|
+
err.stack
|
|
103
|
+
parseErrorCh.publish(err)
|
|
104
|
+
|
|
105
|
+
throw err
|
|
106
|
+
} finally {
|
|
107
|
+
parseFinishCh.publish({ source, document, docSource: documentSources.get(document) })
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function wrapValidate (validate) {
|
|
114
|
+
return function (_schema, document, _rules, _typeInfo) {
|
|
115
|
+
if (!validateStartCh.hasSubscribers) {
|
|
116
|
+
return validate.apply(this, arguments)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const asyncResource = new AsyncResource('bound-anonymous-fn')
|
|
120
|
+
|
|
121
|
+
return asyncResource.runInAsyncScope(() => {
|
|
122
|
+
validateStartCh.publish({ docSource: documentSources.get(document), document })
|
|
123
|
+
|
|
124
|
+
let errors
|
|
125
|
+
try {
|
|
126
|
+
errors = validate.apply(this, arguments)
|
|
127
|
+
validateErrorCh.publish(errors && errors[0])
|
|
128
|
+
return errors
|
|
129
|
+
} catch (err) {
|
|
130
|
+
err.stack
|
|
131
|
+
validateErrorCh.publish(err)
|
|
132
|
+
|
|
133
|
+
throw err
|
|
134
|
+
} finally {
|
|
135
|
+
validateFinishCh.publish({ document, errors })
|
|
136
|
+
}
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function wrapExecute (execute) {
|
|
142
|
+
return function (exe) {
|
|
143
|
+
const defaultFieldResolver = execute.defaultFieldResolver
|
|
144
|
+
return function () {
|
|
145
|
+
if (!startExecuteCh.hasSubscribers) {
|
|
146
|
+
return exe.apply(this, arguments)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const asyncResource = new AsyncResource('bound-anonymous-fn')
|
|
150
|
+
return asyncResource.runInAsyncScope(() => {
|
|
151
|
+
const args = normalizeArgs(arguments, defaultFieldResolver)
|
|
152
|
+
const schema = args.schema
|
|
153
|
+
const document = args.document
|
|
154
|
+
const source = documentSources.get(document)
|
|
155
|
+
const contextValue = args.contextValue
|
|
156
|
+
const operation = getOperation(document, args.operationName)
|
|
157
|
+
|
|
158
|
+
if (contexts.has(contextValue)) {
|
|
159
|
+
return exe.apply(this, arguments)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (schema) {
|
|
163
|
+
wrapFields(schema._queryType)
|
|
164
|
+
wrapFields(schema._mutationType)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
startExecuteCh.publish({
|
|
168
|
+
operation,
|
|
169
|
+
args,
|
|
170
|
+
docSource: documentSources.get(document)
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
const context = { source, asyncResource, fields: {} }
|
|
174
|
+
|
|
175
|
+
contexts.set(contextValue, context)
|
|
176
|
+
|
|
177
|
+
return callInAsyncScope(exe, asyncResource, this, arguments, (err, res) => {
|
|
178
|
+
if (finishResolveCh.hasSubscribers) finishResolvers(context)
|
|
179
|
+
|
|
180
|
+
executeErrorCh.publish(err || (res && res.errors && res.errors[0]))
|
|
181
|
+
finishExecuteCh.publish({ res, args })
|
|
182
|
+
})
|
|
183
|
+
})
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function wrapResolve (resolve) {
|
|
189
|
+
if (typeof resolve !== 'function' || patchedResolvers.has(resolve)) return resolve
|
|
190
|
+
|
|
191
|
+
function resolveAsync (source, args, contextValue, info) {
|
|
192
|
+
if (!startResolveCh.hasSubscribers) return resolve.apply(this, arguments)
|
|
193
|
+
|
|
194
|
+
const context = contexts.get(contextValue)
|
|
195
|
+
|
|
196
|
+
if (!context) return resolve.apply(this, arguments)
|
|
197
|
+
|
|
198
|
+
const path = pathToArray(info && info.path)
|
|
199
|
+
|
|
200
|
+
const field = assertField(context, info, path)
|
|
201
|
+
|
|
202
|
+
return callInAsyncScope(resolve, field.asyncResource, this, arguments, (err) => {
|
|
203
|
+
updateFieldCh.publish({ field, info, err })
|
|
204
|
+
})
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
patchedResolvers.add(resolveAsync)
|
|
208
|
+
|
|
209
|
+
return resolveAsync
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function callInAsyncScope (fn, aR, thisArg, args, cb) {
|
|
213
|
+
cb = cb || (() => {})
|
|
214
|
+
|
|
215
|
+
return aR.runInAsyncScope(() => {
|
|
216
|
+
try {
|
|
217
|
+
const result = fn.apply(thisArg, args)
|
|
218
|
+
if (result && typeof result.then === 'function') {
|
|
219
|
+
// bind callback to this scope
|
|
220
|
+
result.then(
|
|
221
|
+
aR.bind(res => cb(null, res)),
|
|
222
|
+
aR.bind(err => cb(err))
|
|
223
|
+
)
|
|
224
|
+
} else {
|
|
225
|
+
cb(null, result)
|
|
226
|
+
}
|
|
227
|
+
return result
|
|
228
|
+
} catch (err) {
|
|
229
|
+
cb(err)
|
|
230
|
+
throw err
|
|
231
|
+
}
|
|
232
|
+
})
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function pathToArray (path) {
|
|
236
|
+
const flattened = []
|
|
237
|
+
let curr = path
|
|
238
|
+
while (curr) {
|
|
239
|
+
flattened.push(curr.key)
|
|
240
|
+
curr = curr.prev
|
|
241
|
+
}
|
|
242
|
+
return flattened.reverse()
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function assertField (context, info, path) {
|
|
246
|
+
const pathString = path.join('.')
|
|
247
|
+
const fields = context.fields
|
|
248
|
+
|
|
249
|
+
let field = fields[pathString]
|
|
250
|
+
|
|
251
|
+
if (!field) {
|
|
252
|
+
const parent = getParentField(context, path)
|
|
253
|
+
|
|
254
|
+
// we want to spawn the new span off of the parent, not a new async resource
|
|
255
|
+
parent.asyncResource.runInAsyncScope(() => {
|
|
256
|
+
/* this child resource will run a branched scope off of the parent resource, which
|
|
257
|
+
accesses the parent span from the storage unit in its own scope */
|
|
258
|
+
const childResource = new AsyncResource('bound-anonymous-fn')
|
|
259
|
+
|
|
260
|
+
childResource.runInAsyncScope(() => {
|
|
261
|
+
startResolveCh.publish({
|
|
262
|
+
info,
|
|
263
|
+
context
|
|
264
|
+
})
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
field = fields[pathString] = {
|
|
268
|
+
parent,
|
|
269
|
+
asyncResource: childResource,
|
|
270
|
+
error: null
|
|
271
|
+
}
|
|
272
|
+
})
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return field
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function getParentField (context, path) {
|
|
279
|
+
for (let i = path.length - 1; i > 0; i--) {
|
|
280
|
+
const field = getField(context, path.slice(0, i))
|
|
281
|
+
if (field) {
|
|
282
|
+
return field
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return {
|
|
287
|
+
asyncResource: context.asyncResource
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function getField (context, path) {
|
|
292
|
+
return context.fields[path.join('.')]
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function wrapFields (type) {
|
|
296
|
+
if (!type || !type._fields || patchedTypes.has(type)) {
|
|
297
|
+
return
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
patchedTypes.add(type)
|
|
301
|
+
|
|
302
|
+
Object.keys(type._fields).forEach(key => {
|
|
303
|
+
const field = type._fields[key]
|
|
304
|
+
|
|
305
|
+
wrapFieldResolve(field)
|
|
306
|
+
wrapFieldType(field)
|
|
307
|
+
})
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function wrapFieldResolve (field) {
|
|
311
|
+
if (!field || !field.resolve) return
|
|
312
|
+
field.resolve = wrapResolve(field.resolve)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function wrapFieldType (field) {
|
|
316
|
+
if (!field || !field.type) return
|
|
317
|
+
|
|
318
|
+
let unwrappedType = field.type
|
|
319
|
+
|
|
320
|
+
while (unwrappedType.ofType) {
|
|
321
|
+
unwrappedType = unwrappedType.ofType
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
wrapFields(unwrappedType)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function finishResolvers ({ fields }) {
|
|
328
|
+
Object.keys(fields).reverse().forEach(key => {
|
|
329
|
+
const field = fields[key]
|
|
330
|
+
const asyncResource = field.asyncResource
|
|
331
|
+
asyncResource.runInAsyncScope(() => {
|
|
332
|
+
executeErrorCh.publish(field.error)
|
|
333
|
+
finishResolveCh.publish(field.finishTime)
|
|
334
|
+
})
|
|
335
|
+
})
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
addHook({ name: 'graphql', file: 'execution/execute.js', versions: ['>=0.10'] }, execute => {
|
|
339
|
+
shimmer.wrap(execute, 'execute', wrapExecute(execute))
|
|
340
|
+
return execute
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
addHook({ name: 'graphql', file: 'language/parser.js', versions: ['>=0.10'] }, parser => {
|
|
344
|
+
shimmer.wrap(parser, 'parse', wrapParse)
|
|
345
|
+
return parser
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
addHook({ name: 'graphql', file: 'validation/validate.js', versions: ['>=0.10'] }, validate => {
|
|
349
|
+
shimmer.wrap(validate, 'validate', wrapValidate)
|
|
350
|
+
|
|
351
|
+
return validate
|
|
352
|
+
})
|
|
@@ -14,7 +14,7 @@ const {
|
|
|
14
14
|
getTestParametersString
|
|
15
15
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
16
16
|
|
|
17
|
-
const { getFormattedJestTestParameters } = require('../../datadog-plugin-jest/src/util')
|
|
17
|
+
const { getFormattedJestTestParameters, getJestTestName } = require('../../datadog-plugin-jest/src/util')
|
|
18
18
|
|
|
19
19
|
const specStatusToTestStatus = {
|
|
20
20
|
'pending': 'skip',
|
|
@@ -64,30 +64,30 @@ function getWrappedEnvironment (BaseEnvironment) {
|
|
|
64
64
|
await super.handleTestEvent(event, state)
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
const globalExpect = this.global.expect
|
|
68
67
|
const setNameToParams = (name, params) => { this.nameToParams[name] = params }
|
|
69
68
|
|
|
70
69
|
if (event.name === 'setup') {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
70
|
+
if (this.global.test) {
|
|
71
|
+
shimmer.wrap(this.global.test, 'each', each => function () {
|
|
72
|
+
const testParameters = getFormattedJestTestParameters(arguments)
|
|
73
|
+
const eachBind = each.apply(this, arguments)
|
|
74
|
+
return function () {
|
|
75
|
+
const [testName] = arguments
|
|
76
|
+
setNameToParams(testName, testParameters)
|
|
77
|
+
return eachBind.apply(this, arguments)
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
}
|
|
80
81
|
}
|
|
81
82
|
if (event.name === 'test_start') {
|
|
82
83
|
const testParameters = getTestParametersString(this.nameToParams, event.test.name)
|
|
83
|
-
|
|
84
84
|
// Async resource for this test is created here
|
|
85
85
|
// It is used later on by the test_done handler
|
|
86
86
|
const asyncResource = new AsyncResource('bound-anonymous-fn')
|
|
87
87
|
asyncResources.set(event.test, asyncResource)
|
|
88
88
|
asyncResource.runInAsyncScope(() => {
|
|
89
89
|
testStartCh.publish({
|
|
90
|
-
name:
|
|
90
|
+
name: getJestTestName(event.test),
|
|
91
91
|
suite: this.testSuite,
|
|
92
92
|
runner: 'jest-circus',
|
|
93
93
|
testParameters
|
|
@@ -112,7 +112,7 @@ function getWrappedEnvironment (BaseEnvironment) {
|
|
|
112
112
|
}
|
|
113
113
|
if (event.name === 'test_skip' || event.name === 'test_todo') {
|
|
114
114
|
testSkippedCh.publish({
|
|
115
|
-
name:
|
|
115
|
+
name: getJestTestName(event.test),
|
|
116
116
|
suite: this.testSuite,
|
|
117
117
|
runner: 'jest-circus'
|
|
118
118
|
})
|
|
@@ -90,7 +90,7 @@ function wrapMiddleware (fn, layer) {
|
|
|
90
90
|
|
|
91
91
|
return middlewareResource.runInAsyncScope(() => {
|
|
92
92
|
const path = layer && layer.path
|
|
93
|
-
const route = path
|
|
93
|
+
const route = typeof path === 'string' && !path.endsWith('(.*)') && !path.endsWith('([^/]*)') && path
|
|
94
94
|
|
|
95
95
|
enterChannel.publish({ req, name, route })
|
|
96
96
|
|
|
@@ -13,7 +13,7 @@ addHook({ name: 'tedious', versions: [ '>=1.0.0' ] }, tedious => {
|
|
|
13
13
|
const errorCh = channel('apm:tedious:request:error')
|
|
14
14
|
shimmer.wrap(tedious.Connection.prototype, 'makeRequest', makeRequest => function (request) {
|
|
15
15
|
if (!startCh.hasSubscribers) {
|
|
16
|
-
return
|
|
16
|
+
return makeRequest.apply(this, arguments)
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
const queryOrProcedure = getQueryOrProcedure(request)
|