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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "2.9.1",
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.2.0",
63
- "@datadog/pprof": "^0.4.0",
64
- "@datadog/sketches-js": "^1.0.4",
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",
@@ -16,6 +16,7 @@ require('./src/fastify')
16
16
  require('./src/find-my-way')
17
17
  require('./src/generic-pool')
18
18
  require('./src/google-cloud-pubsub')
19
+ require('./src/graphql')
19
20
  require('./src/hapi')
20
21
  require('./src/http')
21
22
  require('./src/ioredis')
@@ -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
+ })
@@ -84,9 +84,9 @@ function patch (http, methodName) {
84
84
  finish(req, arg)
85
85
  break
86
86
  case 'error':
87
+ case 'timeout':
87
88
  errorClientCh.publish(arg)
88
89
  case 'abort': // deprecated and replaced by `close` in node 17
89
- case 'timeout':
90
90
  case 'close':
91
91
  finish(req)
92
92
  }
@@ -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
- 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
- })
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: globalExpect.getState().currentTestName,
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: globalExpect.getState().currentTestName,
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 !== '(.*)' && path !== '([^/]*)' && 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 request.apply(this, arguments)
16
+ return makeRequest.apply(this, arguments)
17
17
  }
18
18
 
19
19
  const queryOrProcedure = getQueryOrProcedure(request)