dd-trace 2.9.0 → 2.11.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.
Files changed (45) hide show
  1. package/package.json +6 -6
  2. package/packages/datadog-instrumentations/index.js +5 -0
  3. package/packages/datadog-instrumentations/src/connect.js +3 -0
  4. package/packages/datadog-instrumentations/src/fastify.js +21 -15
  5. package/packages/datadog-instrumentations/src/graphql.js +354 -0
  6. package/packages/datadog-instrumentations/src/grpc/client.js +250 -0
  7. package/packages/datadog-instrumentations/src/grpc/server.js +144 -0
  8. package/packages/{datadog-plugin-grpc/src/kinds.js → datadog-instrumentations/src/grpc/types.js} +0 -0
  9. package/packages/datadog-instrumentations/src/grpc.js +4 -0
  10. package/packages/datadog-instrumentations/src/http/client.js +1 -1
  11. package/packages/datadog-instrumentations/src/http2/client.js +67 -0
  12. package/packages/datadog-instrumentations/src/http2/server.js +3 -0
  13. package/packages/datadog-instrumentations/src/http2.js +4 -0
  14. package/packages/datadog-instrumentations/src/jest.js +14 -14
  15. package/packages/datadog-instrumentations/src/koa.js +12 -3
  16. package/packages/datadog-instrumentations/src/microgateway-core.js +66 -0
  17. package/packages/datadog-instrumentations/src/mocha.js +129 -63
  18. package/packages/datadog-instrumentations/src/next.js +140 -0
  19. package/packages/datadog-instrumentations/src/router.js +3 -0
  20. package/packages/datadog-instrumentations/src/tedious.js +1 -1
  21. package/packages/datadog-plugin-graphql/src/index.js +123 -412
  22. package/packages/datadog-plugin-graphql/src/resolve.js +120 -0
  23. package/packages/datadog-plugin-grpc/src/client.js +54 -283
  24. package/packages/datadog-plugin-grpc/src/index.js +31 -3
  25. package/packages/datadog-plugin-grpc/src/server.js +50 -145
  26. package/packages/datadog-plugin-http/src/client.js +10 -7
  27. package/packages/datadog-plugin-http2/src/client.js +72 -120
  28. package/packages/datadog-plugin-http2/src/index.js +32 -3
  29. package/packages/datadog-plugin-http2/src/server.js +6 -214
  30. package/packages/datadog-plugin-jest/src/util.js +13 -1
  31. package/packages/datadog-plugin-microgateway-core/src/index.js +14 -145
  32. package/packages/datadog-plugin-mocha/src/index.js +8 -36
  33. package/packages/datadog-plugin-next/src/index.js +56 -168
  34. package/packages/datadog-plugin-router/src/index.js +7 -3
  35. package/packages/dd-trace/src/config.js +3 -2
  36. package/packages/dd-trace/src/exporters/agent/writer.js +1 -1
  37. package/packages/dd-trace/src/opentracing/span.js +6 -3
  38. package/packages/dd-trace/src/plugin_manager.js +21 -0
  39. package/packages/dd-trace/src/plugins/plugin.js +3 -1
  40. package/packages/dd-trace/src/plugins/util/test.js +3 -0
  41. package/packages/dd-trace/src/plugins/util/web.js +0 -7
  42. package/packages/dd-trace/src/profiling/exporters/agent.js +1 -1
  43. package/packages/dd-trace/src/startup-log.js +1 -1
  44. package/packages/dd-trace/src/telemetry.js +1 -1
  45. 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.9.0",
3
+ "version": "2.11.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -58,10 +58,10 @@
58
58
  "node": ">=12"
59
59
  },
60
60
  "dependencies": {
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",
61
+ "@datadog/native-appsec": "^1.2.1",
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,14 +16,18 @@ 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')
20
+ require('./src/grpc')
19
21
  require('./src/hapi')
20
22
  require('./src/http')
23
+ require('./src/http2')
21
24
  require('./src/ioredis')
22
25
  require('./src/jest')
23
26
  require('./src/kafkajs')
24
27
  require('./src/knex')
25
28
  require('./src/koa')
26
29
  require('./src/memcached')
30
+ require('./src/microgateway-core')
27
31
  require('./src/moleculer')
28
32
  require('./src/mongodb-core')
29
33
  require('./src/mongoose')
@@ -31,6 +35,7 @@ require('./src/mysql')
31
35
  require('./src/mysql2')
32
36
  require('./src/mocha')
33
37
  require('./src/net')
38
+ require('./src/next')
34
39
  require('./src/oracledb')
35
40
  require('./src/paperplane')
36
41
  require('./src/pino')
@@ -4,6 +4,7 @@ const shimmer = require('../../datadog-shimmer')
4
4
  const { addHook, channel, AsyncResource } = require('./helpers/instrument')
5
5
 
6
6
  const enterChannel = channel('apm:connect:middleware:enter')
7
+ const exitChannel = channel('apm:connect:middleware:exit')
7
8
  const errorChannel = channel('apm:connect:middleware:error')
8
9
  const nextChannel = channel('apm:connect:middleware:next')
9
10
  const handleChannel = channel('apm:connect:request:handle')
@@ -80,6 +81,7 @@ function wrapLayerHandle (layer) {
80
81
  } catch (e) {
81
82
  errorChannel.publish(e)
82
83
  nextChannel.publish({ req })
84
+ exitChannel.publish({ req })
83
85
 
84
86
  throw e
85
87
  }
@@ -94,6 +96,7 @@ function wrapNext (req, next) {
94
96
  }
95
97
 
96
98
  nextChannel.publish({ req })
99
+ exitChannel.publish({ req })
97
100
 
98
101
  next.apply(null, arguments)
99
102
  }
@@ -85,8 +85,8 @@ function wrapAddHook (addHook) {
85
85
  }
86
86
  }
87
87
 
88
- function onRequest (request, reply, next) {
89
- if (typeof next !== 'function') return
88
+ function onRequest (request, reply, done) {
89
+ if (typeof done !== 'function') return
90
90
 
91
91
  const req = getReq(request)
92
92
  const res = getRes(reply)
@@ -96,37 +96,41 @@ function onRequest (request, reply, next) {
96
96
 
97
97
  return requestResource.runInAsyncScope(() => {
98
98
  handleChannel.publish({ req, res })
99
- return next()
99
+ return done()
100
100
  })
101
101
  }
102
102
 
103
- function preHandler (request, reply, next) {
104
- if (typeof next !== 'function') return
105
- if (!reply || typeof reply.send !== 'function') return next()
103
+ function preHandler (request, reply, done) {
104
+ if (typeof done !== 'function') return
105
+ if (!reply || typeof reply.send !== 'function') return done()
106
106
 
107
107
  const req = getReq(request)
108
108
  const requestResource = requestResources.get(req)
109
109
 
110
110
  reply.send = wrapSend(reply.send, requestResource)
111
111
 
112
- next()
112
+ done()
113
113
  }
114
114
 
115
- function preValidation (request, reply, next) {
115
+ function preValidation (request, reply, done) {
116
116
  const req = getReq(request)
117
117
  const parsingResource = parsingResources.get(req)
118
118
 
119
- if (!parsingResource) return next()
119
+ if (!parsingResource) return done()
120
120
 
121
- parsingResource.runInAsyncScope(() => next())
121
+ parsingResource.runInAsyncScope(() => done())
122
122
  }
123
123
 
124
- function preParsing (request, reply, next) {
124
+ function preParsing (request, reply, payload, done) {
125
+ if (typeof done !== 'function') {
126
+ done = payload
127
+ }
128
+
125
129
  const req = getReq(request)
126
130
  const parsingResource = new AsyncResource('bound-anonymous-fn')
127
131
 
128
132
  parsingResources.set(req, parsingResource)
129
- parsingResource.runInAsyncScope(() => next())
133
+ parsingResource.runInAsyncScope(() => done())
130
134
  }
131
135
 
132
136
  function wrapSend (send, resource) {
@@ -150,9 +154,11 @@ function getRes (reply) {
150
154
  }
151
155
 
152
156
  function publishError (error, resource) {
153
- resource.runInAsyncScope(() => {
154
- errorChannel.publish(error)
155
- })
157
+ if (error) {
158
+ resource.runInAsyncScope(() => {
159
+ errorChannel.publish(error)
160
+ })
161
+ }
156
162
 
157
163
  return error
158
164
  }
@@ -0,0 +1,354 @@
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 field = assertField(context, info)
199
+
200
+ return callInAsyncScope(resolve, field.asyncResource, this, arguments, (err) => {
201
+ updateFieldCh.publish({ field, info, err })
202
+ })
203
+ }
204
+
205
+ patchedResolvers.add(resolveAsync)
206
+
207
+ return resolveAsync
208
+ }
209
+
210
+ function callInAsyncScope (fn, aR, thisArg, args, cb) {
211
+ cb = cb || (() => {})
212
+
213
+ return aR.runInAsyncScope(() => {
214
+ try {
215
+ const result = fn.apply(thisArg, args)
216
+ if (result && typeof result.then === 'function') {
217
+ // bind callback to this scope
218
+ result.then(
219
+ aR.bind(res => cb(null, res)),
220
+ aR.bind(err => cb(err))
221
+ )
222
+ } else {
223
+ cb(null, result)
224
+ }
225
+ return result
226
+ } catch (err) {
227
+ cb(err)
228
+ throw err
229
+ }
230
+ })
231
+ }
232
+
233
+ function pathToArray (path) {
234
+ const flattened = []
235
+ let curr = path
236
+ while (curr) {
237
+ flattened.push(curr.key)
238
+ curr = curr.prev
239
+ }
240
+ return flattened.reverse()
241
+ }
242
+
243
+ function assertField (context, info) {
244
+ const pathInfo = info && info.path
245
+
246
+ const path = pathToArray(pathInfo)
247
+
248
+ const pathString = path.join('.')
249
+ const fields = context.fields
250
+
251
+ let field = fields[pathString]
252
+
253
+ if (!field) {
254
+ const parent = getParentField(context, path)
255
+
256
+ // we want to spawn the new span off of the parent, not a new async resource
257
+ parent.asyncResource.runInAsyncScope(() => {
258
+ /* this child resource will run a branched scope off of the parent resource, which
259
+ accesses the parent span from the storage unit in its own scope */
260
+ const childResource = new AsyncResource('bound-anonymous-fn')
261
+
262
+ childResource.runInAsyncScope(() => {
263
+ startResolveCh.publish({
264
+ info,
265
+ context
266
+ })
267
+ })
268
+
269
+ field = fields[pathString] = {
270
+ parent,
271
+ asyncResource: childResource,
272
+ error: null
273
+ }
274
+ })
275
+ }
276
+
277
+ return field
278
+ }
279
+
280
+ function getParentField (context, path) {
281
+ for (let i = path.length - 1; i > 0; i--) {
282
+ const field = getField(context, path.slice(0, i))
283
+ if (field) {
284
+ return field
285
+ }
286
+ }
287
+
288
+ return {
289
+ asyncResource: context.asyncResource
290
+ }
291
+ }
292
+
293
+ function getField (context, path) {
294
+ return context.fields[path.join('.')]
295
+ }
296
+
297
+ function wrapFields (type) {
298
+ if (!type || !type._fields || patchedTypes.has(type)) {
299
+ return
300
+ }
301
+
302
+ patchedTypes.add(type)
303
+
304
+ Object.keys(type._fields).forEach(key => {
305
+ const field = type._fields[key]
306
+
307
+ wrapFieldResolve(field)
308
+ wrapFieldType(field)
309
+ })
310
+ }
311
+
312
+ function wrapFieldResolve (field) {
313
+ if (!field || !field.resolve) return
314
+ field.resolve = wrapResolve(field.resolve)
315
+ }
316
+
317
+ function wrapFieldType (field) {
318
+ if (!field || !field.type) return
319
+
320
+ let unwrappedType = field.type
321
+
322
+ while (unwrappedType.ofType) {
323
+ unwrappedType = unwrappedType.ofType
324
+ }
325
+
326
+ wrapFields(unwrappedType)
327
+ }
328
+
329
+ function finishResolvers ({ fields }) {
330
+ Object.keys(fields).reverse().forEach(key => {
331
+ const field = fields[key]
332
+ const asyncResource = field.asyncResource
333
+ asyncResource.runInAsyncScope(() => {
334
+ executeErrorCh.publish(field.error)
335
+ finishResolveCh.publish(field.finishTime)
336
+ })
337
+ })
338
+ }
339
+
340
+ addHook({ name: 'graphql', file: 'execution/execute.js', versions: ['>=0.10'] }, execute => {
341
+ shimmer.wrap(execute, 'execute', wrapExecute(execute))
342
+ return execute
343
+ })
344
+
345
+ addHook({ name: 'graphql', file: 'language/parser.js', versions: ['>=0.10'] }, parser => {
346
+ shimmer.wrap(parser, 'parse', wrapParse)
347
+ return parser
348
+ })
349
+
350
+ addHook({ name: 'graphql', file: 'validation/validate.js', versions: ['>=0.10'] }, validate => {
351
+ shimmer.wrap(validate, 'validate', wrapValidate)
352
+
353
+ return validate
354
+ })