dd-trace 2.11.0 → 2.12.2

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 (42) hide show
  1. package/LICENSE-3rdparty.csv +0 -2
  2. package/ext/formats.js +3 -5
  3. package/index.d.ts +3 -3
  4. package/package.json +4 -6
  5. package/packages/datadog-core/src/storage/async_hooks.js +4 -4
  6. package/packages/datadog-core/src/storage/async_resource.js +14 -4
  7. package/packages/datadog-instrumentations/src/connect.js +5 -5
  8. package/packages/datadog-instrumentations/src/couchbase.js +166 -61
  9. package/packages/datadog-instrumentations/src/fastify.js +12 -25
  10. package/packages/datadog-instrumentations/src/graphql.js +17 -5
  11. package/packages/datadog-instrumentations/src/koa.js +5 -5
  12. package/packages/datadog-instrumentations/src/mocha.js +92 -19
  13. package/packages/datadog-instrumentations/src/restify.js +28 -10
  14. package/packages/datadog-instrumentations/src/router.js +5 -5
  15. package/packages/datadog-plugin-aws-sdk/src/base.js +1 -2
  16. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -2
  17. package/packages/datadog-plugin-couchbase/src/index.js +8 -10
  18. package/packages/datadog-plugin-graphql/src/resolve.js +2 -0
  19. package/packages/datadog-plugin-http/src/server.js +3 -8
  20. package/packages/datadog-plugin-mocha/src/index.js +80 -3
  21. package/packages/datadog-plugin-next/src/index.js +1 -1
  22. package/packages/datadog-plugin-restify/src/index.js +7 -0
  23. package/packages/datadog-plugin-router/src/index.js +39 -10
  24. package/packages/dd-trace/src/encode/0.4.js +4 -0
  25. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +111 -15
  26. package/packages/dd-trace/src/exporters/common/request.js +49 -34
  27. package/packages/dd-trace/src/exporters/common/writer.js +8 -1
  28. package/packages/dd-trace/src/noop/span.js +12 -12
  29. package/packages/dd-trace/src/noop/tracer.js +8 -5
  30. package/packages/dd-trace/src/opentracing/span.js +63 -49
  31. package/packages/dd-trace/src/opentracing/span_context.js +1 -5
  32. package/packages/dd-trace/src/opentracing/tracer.js +31 -36
  33. package/packages/dd-trace/src/plugin_manager.js +49 -33
  34. package/packages/dd-trace/src/plugins/util/test.js +32 -1
  35. package/packages/dd-trace/src/plugins/util/web.js +26 -18
  36. package/packages/dd-trace/src/profiling/config.js +10 -2
  37. package/packages/dd-trace/src/profiling/exporters/agent.js +1 -2
  38. package/packages/dd-trace/src/profiling/exporters/form-data.js +53 -0
  39. package/packages/dd-trace/src/profiling/index.js +2 -0
  40. package/packages/dd-trace/src/profiling/profiler.js +6 -1
  41. package/packages/dd-trace/src/profiling/profilers/cpu.js +126 -0
  42. package/packages/dd-trace/src/proxy.js +1 -4
@@ -6,7 +6,6 @@ require,@datadog/sketches-js,Apache license 2.0,Copyright 2020 Datadog Inc.
6
6
  require,@types/node,MIT,Copyright Authors
7
7
  require,crypto-randomuuid,MIT,Copyright 2021 Node.js Foundation and contributors
8
8
  require,diagnostics_channel,MIT,Copyright 2021 Simon D.
9
- require,form-data,MIT,Copyright 2012 Felix Geisendörfer and contributors
10
9
  require,ignore,MIT,Copyright 2013 Kael Zhang and contributors
11
10
  require,import-in-the-middle,Apache license 2.0,Copyright 2021 Datadog Inc.
12
11
  require,koalas,MIT,Copyright 2013-2017 Brian Woodward
@@ -19,7 +18,6 @@ require,methods,MIT,Copyright 2013-2014 TJ Holowaychuk
19
18
  require,module-details-from-path,MIT,Copyright 2016 Thomas Watson Steen
20
19
  require,opentracing,MIT,Copyright 2016 Resonance Labs Inc
21
20
  require,path-to-regexp,MIT,Copyright 2014 Blake Embrey
22
- require,performance-now,MIT,Copyright 2013 Braveg1rl
23
21
  require,retry,MIT,Copyright 2011 Tim Koschützki Felix Geisendörfer
24
22
  require,semver,ISC,Copyright Isaac Z. Schlueter and Contributors
25
23
  dev,autocannon,MIT,Copyright 2016 Matteo Collina
package/ext/formats.js CHANGED
@@ -1,10 +1,8 @@
1
1
  'use strict'
2
2
 
3
- const opentracing = require('opentracing')
4
-
5
3
  module.exports = {
6
- TEXT_MAP: opentracing.FORMAT_TEXT_MAP,
7
- HTTP_HEADERS: opentracing.FORMAT_HTTP_HEADERS,
8
- BINARY: opentracing.FORMAT_BINARY,
4
+ TEXT_MAP: 'text_map',
5
+ HTTP_HEADERS: 'http_headers',
6
+ BINARY: 'binary',
9
7
  LOG: 'log'
10
8
  }
package/index.d.ts CHANGED
@@ -313,10 +313,10 @@ export declare interface TracerOptions {
313
313
  };
314
314
 
315
315
  /**
316
- * Experimental features can be enabled all at once by using true or individually using key / value pairs.
316
+ * Experimental features can be enabled individually using key / value pairs.
317
317
  * @default {}
318
318
  */
319
- experimental?: boolean | {
319
+ experimental?: {
320
320
  b3?: boolean
321
321
  traceparent?: boolean
322
322
 
@@ -1216,7 +1216,7 @@ declare namespace plugins {
1216
1216
 
1217
1217
  /**
1218
1218
  * This plugin automatically instruments the
1219
- * [mysql2](https://github.com/brianmario/mysql2) module.
1219
+ * [mysql2](https://github.com/sidorares/node-mysql2) module.
1220
1220
  */
1221
1221
  interface mysql2 extends mysql {}
1222
1222
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "2.11.0",
3
+ "version": "2.12.2",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -59,15 +59,14 @@
59
59
  },
60
60
  "dependencies": {
61
61
  "@datadog/native-appsec": "^1.2.1",
62
- "@datadog/native-metrics": "^1.4.0",
63
- "@datadog/pprof": "^0.5.1",
62
+ "@datadog/native-metrics": "^1.4.2",
63
+ "@datadog/pprof": "^1.0.2",
64
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",
68
- "form-data": "^3.0.0",
69
68
  "ignore": "^5.2.0",
70
- "import-in-the-middle": "^1.2.1",
69
+ "import-in-the-middle": "^1.3.0",
71
70
  "koalas": "^1.0.2",
72
71
  "limiter": "^1.1.4",
73
72
  "lodash.kebabcase": "^4.1.1",
@@ -78,7 +77,6 @@
78
77
  "module-details-from-path": "^1.0.3",
79
78
  "opentracing": ">=0.12.1",
80
79
  "path-to-regexp": "^0.1.2",
81
- "performance-now": "^2.1.0",
82
80
  "retry": "^0.10.1",
83
81
  "semver": "^5.5.0"
84
82
  },
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const { createHook, executionAsyncId } = require('async_hooks')
3
+ const { executionAsyncId } = require('async_hooks')
4
4
  const AsyncResourceStorage = require('./async_resource')
5
5
 
6
6
  class AsyncHooksStorage extends AsyncResourceStorage {
@@ -17,10 +17,10 @@ class AsyncHooksStorage extends AsyncResourceStorage {
17
17
  }
18
18
 
19
19
  _createHook () {
20
- return createHook({
21
- init: this._init.bind(this),
20
+ return {
21
+ ...super._createHook(),
22
22
  destroy: this._destroy.bind(this)
23
- })
23
+ }
24
24
  }
25
25
 
26
26
  _init (asyncId, type, triggerAsyncId, resource) {
@@ -1,12 +1,16 @@
1
1
  'use strict'
2
2
 
3
3
  const { createHook, executionAsyncResource } = require('async_hooks')
4
+ const { channel } = require('diagnostics_channel')
5
+
6
+ const beforeCh = channel('dd-trace:storage:before')
7
+ const afterCh = channel('dd-trace:storage:after')
4
8
 
5
9
  class AsyncResourceStorage {
6
10
  constructor () {
7
11
  this._ddResourceStore = Symbol('ddResourceStore')
8
12
  this._enabled = false
9
- this._hook = this._createHook()
13
+ this._hook = createHook(this._createHook())
10
14
  }
11
15
 
12
16
  disable () {
@@ -48,9 +52,15 @@ class AsyncResourceStorage {
48
52
  }
49
53
 
50
54
  _createHook () {
51
- return createHook({
52
- init: this._init.bind(this)
53
- })
55
+ return {
56
+ init: this._init.bind(this),
57
+ before () {
58
+ beforeCh.publish()
59
+ },
60
+ after () {
61
+ afterCh.publish()
62
+ }
63
+ }
54
64
  }
55
65
 
56
66
  _enable () {
@@ -78,12 +78,12 @@ function wrapLayerHandle (layer) {
78
78
 
79
79
  try {
80
80
  return original.apply(this, arguments)
81
- } catch (e) {
82
- errorChannel.publish(e)
81
+ } catch (error) {
82
+ errorChannel.publish({ req, error })
83
83
  nextChannel.publish({ req })
84
84
  exitChannel.publish({ req })
85
85
 
86
- throw e
86
+ throw error
87
87
  }
88
88
  })
89
89
  })
@@ -92,13 +92,13 @@ function wrapLayerHandle (layer) {
92
92
  function wrapNext (req, next) {
93
93
  return function (error) {
94
94
  if (error) {
95
- errorChannel.publish(error)
95
+ errorChannel.publish({ req, error })
96
96
  }
97
97
 
98
98
  nextChannel.publish({ req })
99
99
  exitChannel.publish({ req })
100
100
 
101
- next.apply(null, arguments)
101
+ next.apply(this, arguments)
102
102
  }
103
103
  }
104
104
 
@@ -7,70 +7,23 @@ const {
7
7
  } = require('./helpers/instrument')
8
8
  const shimmer = require('../../datadog-shimmer')
9
9
 
10
- addHook({ name: 'couchbase', file: 'lib/bucket.js', versions: ['^2.6.5'] }, Bucket => {
11
- const startCh = channel('apm:couchbase:query:start')
12
- const finishCh = channel('apm:couchbase:query:finish')
13
- const errorCh = channel('apm:couchbase:query:error')
14
-
15
- Bucket.prototype._maybeInvoke = wrapMaybeInvoke(Bucket.prototype._maybeInvoke)
16
- Bucket.prototype.query = wrapQuery(Bucket.prototype.query)
17
-
18
- shimmer.wrap(Bucket.prototype, '_n1qlReq', _n1qlReq => function (host, q, adhoc, emitter) {
19
- if (!startCh.hasSubscribers) {
20
- return _n1qlReq.apply(this, arguments)
21
- }
22
-
23
- if (!emitter || !emitter.once) return _n1qlReq.apply(this, arguments)
24
-
25
- const n1qlQuery = q && q.statement
26
-
27
- const asyncResource = new AsyncResource('bound-anonymous-fn')
28
- return asyncResource.runInAsyncScope(() => {
29
- startCh.publish({ resource: n1qlQuery, bucket: this })
30
-
31
- emitter.once('rows', asyncResource.bind(() => {
32
- finishCh.publish(undefined)
33
- }))
34
-
35
- emitter.once('error', asyncResource.bind((error) => {
36
- errorCh.publish(error)
37
- finishCh.publish(undefined)
38
- }))
39
-
40
- try {
41
- return _n1qlReq.apply(this, arguments)
42
- } catch (err) {
43
- err.stack // trigger getting the stack at the original throwing point
44
- errorCh.publish(err)
45
-
46
- throw err
47
- }
48
- })
49
- })
50
-
51
- Bucket.prototype.upsert = wrap('apm:couchbase:upsert', Bucket.prototype.upsert)
52
- Bucket.prototype.insert = wrap('apm:couchbase:insert', Bucket.prototype.insert)
53
- Bucket.prototype.replace = wrap('apm:couchbase:replace', Bucket.prototype.replace)
54
- Bucket.prototype.append = wrap('apm:couchbase:append', Bucket.prototype.append)
55
- Bucket.prototype.prepend = wrap('apm:couchbase:prepend', Bucket.prototype.prepend)
56
-
57
- return Bucket
58
- })
59
-
60
- addHook({ name: 'couchbase', file: 'lib/cluster.js', versions: ['^2.6.5'] }, Cluster => {
61
- Cluster.prototype._maybeInvoke = wrapMaybeInvoke(Cluster.prototype._maybeInvoke)
62
- Cluster.prototype.query = wrapQuery(Cluster.prototype.query)
63
-
64
- return Cluster
65
- })
66
-
67
- function findCallbackIndex (args) {
68
- for (let i = args.length - 1; i >= 2; i--) {
10
+ function findCallbackIndex (args, lowerbound = 2) {
11
+ for (let i = args.length - 1; i >= lowerbound; i--) {
69
12
  if (typeof args[i] === 'function') return i
70
13
  }
71
14
  return -1
72
15
  }
73
16
 
17
+ // handles n1ql and string queries
18
+ function getQueryResource (q) {
19
+ return q && (typeof q === 'string' ? q : q.statement)
20
+ }
21
+
22
+ function wrapAllNames (names, action) {
23
+ names.forEach(name => action(name))
24
+ }
25
+
26
+ // semver >=2 <3
74
27
  function wrapMaybeInvoke (_maybeInvoke) {
75
28
  const wrapped = function (fn, args) {
76
29
  if (!Array.isArray(args)) return _maybeInvoke.apply(this, arguments)
@@ -106,7 +59,7 @@ function wrap (prefix, fn) {
106
59
  const finishCh = channel(prefix + ':finish')
107
60
  const errorCh = channel(prefix + ':error')
108
61
 
109
- const wrapped = function (key, value, options, callback) {
62
+ const wrapped = function () {
110
63
  if (!startCh.hasSubscribers) {
111
64
  return fn.apply(this, arguments)
112
65
  }
@@ -121,7 +74,7 @@ function wrap (prefix, fn) {
121
74
  return asyncResource.runInAsyncScope(() => {
122
75
  const cb = callbackResource.bind(arguments[callbackIndex])
123
76
 
124
- startCh.publish({ bucket: this })
77
+ startCh.publish({ bucket: { name: this.name || this._name } })
125
78
 
126
79
  arguments[callbackIndex] = asyncResource.bind(function (error, result) {
127
80
  if (error) {
@@ -143,3 +96,155 @@ function wrap (prefix, fn) {
143
96
  }
144
97
  return shimmer.wrap(fn, wrapped)
145
98
  }
99
+
100
+ // semver >=3
101
+
102
+ function wrapCBandPromise (fn, name, startData, thisArg, args) {
103
+ const startCh = channel(`apm:couchbase:${name}:start`)
104
+ const finishCh = channel(`apm:couchbase:${name}:finish`)
105
+ const errorCh = channel(`apm:couchbase:${name}:error`)
106
+
107
+ if (!startCh.hasSubscribers) return fn.apply(thisArg, args)
108
+
109
+ const asyncResource = new AsyncResource('bound-anonymous-fn')
110
+ const callbackResource = new AsyncResource('bound-anonymous-fn')
111
+
112
+ return asyncResource.runInAsyncScope(() => {
113
+ startCh.publish(startData)
114
+
115
+ try {
116
+ const cbIndex = findCallbackIndex(args, 1)
117
+ if (cbIndex >= 0) {
118
+ // v3 offers callback or promises event handling
119
+ // NOTE: this does not work with v3.2.0-3.2.1 cluster.query, as there is a bug in the couchbase source code
120
+ const cb = callbackResource.bind(args[cbIndex])
121
+ args[cbIndex] = asyncResource.bind(function (error, result) {
122
+ if (error) {
123
+ errorCh.publish(error)
124
+ }
125
+ finishCh.publish({ result })
126
+ return cb.apply(thisArg, arguments)
127
+ })
128
+ }
129
+ const res = fn.apply(thisArg, args)
130
+
131
+ // semver >=3 will always return promise by default
132
+ res.then(
133
+ asyncResource.bind((result) => finishCh.publish({ result })),
134
+ asyncResource.bind((err) => errorCh.publish(err)))
135
+ return res
136
+ } catch (e) {
137
+ e.stack
138
+ errorCh.publish(e)
139
+ throw e
140
+ }
141
+ })
142
+ }
143
+
144
+ function wrapWithName (name) {
145
+ return function (operation) {
146
+ return function () { // no arguments used by us
147
+ return wrapCBandPromise(operation, name, {
148
+ collection: { name: this._name || '_default' },
149
+ bucket: { name: this._scope._bucket._name }
150
+ }, this, arguments)
151
+ }
152
+ }
153
+ }
154
+
155
+ function wrapV3Query (query) {
156
+ return function (q) {
157
+ const resource = getQueryResource(q)
158
+ return wrapCBandPromise(query, 'query', { resource }, this, arguments)
159
+ }
160
+ }
161
+
162
+ // semver >=2 <3
163
+ addHook({ name: 'couchbase', file: 'lib/bucket.js', versions: ['^2.6.5'] }, Bucket => {
164
+ const startCh = channel('apm:couchbase:query:start')
165
+ const finishCh = channel('apm:couchbase:query:finish')
166
+ const errorCh = channel('apm:couchbase:query:error')
167
+
168
+ Bucket.prototype._maybeInvoke = wrapMaybeInvoke(Bucket.prototype._maybeInvoke)
169
+ Bucket.prototype.query = wrapQuery(Bucket.prototype.query)
170
+
171
+ shimmer.wrap(Bucket.prototype, '_n1qlReq', _n1qlReq => function (host, q, adhoc, emitter) {
172
+ if (!startCh.hasSubscribers) {
173
+ return _n1qlReq.apply(this, arguments)
174
+ }
175
+
176
+ if (!emitter || !emitter.once) return _n1qlReq.apply(this, arguments)
177
+
178
+ const n1qlQuery = getQueryResource(q)
179
+
180
+ const asyncResource = new AsyncResource('bound-anonymous-fn')
181
+ return asyncResource.runInAsyncScope(() => {
182
+ startCh.publish({ resource: n1qlQuery, bucket: { name: this.name || this._name } })
183
+
184
+ emitter.once('rows', asyncResource.bind(() => {
185
+ finishCh.publish(undefined)
186
+ }))
187
+
188
+ emitter.once('error', asyncResource.bind((error) => {
189
+ errorCh.publish(error)
190
+ finishCh.publish(undefined)
191
+ }))
192
+
193
+ try {
194
+ return _n1qlReq.apply(this, arguments)
195
+ } catch (err) {
196
+ err.stack // trigger getting the stack at the original throwing point
197
+ errorCh.publish(err)
198
+
199
+ throw err
200
+ }
201
+ })
202
+ })
203
+
204
+ wrapAllNames(['upsert', 'insert', 'replace', 'append', 'prepend'], name => {
205
+ Bucket.prototype[name] = wrap(`apm:couchbase:${name}`, Bucket.prototype[name])
206
+ })
207
+
208
+ return Bucket
209
+ })
210
+
211
+ addHook({ name: 'couchbase', file: 'lib/cluster.js', versions: ['^2.6.5'] }, Cluster => {
212
+ Cluster.prototype._maybeInvoke = wrapMaybeInvoke(Cluster.prototype._maybeInvoke)
213
+ Cluster.prototype.query = wrapQuery(Cluster.prototype.query)
214
+
215
+ return Cluster
216
+ })
217
+
218
+ // semver >=3 <3.2.0
219
+
220
+ addHook({ name: 'couchbase', file: 'lib/collection.js', versions: ['>=3.0.0 <3.2.0'] }, Collection => {
221
+ wrapAllNames(['upsert', 'insert', 'replace'], name => {
222
+ shimmer.wrap(Collection.prototype, name, wrapWithName(name))
223
+ })
224
+
225
+ return Collection
226
+ })
227
+
228
+ addHook({ name: 'couchbase', file: 'lib/cluster.js', versions: ['>=3.0.0 <3.2.0'] }, Cluster => {
229
+ shimmer.wrap(Cluster.prototype, 'query', wrapV3Query)
230
+ return Cluster
231
+ })
232
+
233
+ // semver >=3.2.0
234
+
235
+ addHook({ name: 'couchbase', file: 'dist/collection.js', versions: ['>=3.2.0'] }, collection => {
236
+ const Collection = collection.Collection
237
+
238
+ wrapAllNames(['upsert', 'insert', 'replace'], name => {
239
+ shimmer.wrap(Collection.prototype, name, wrapWithName(name))
240
+ })
241
+
242
+ return collection
243
+ })
244
+
245
+ addHook({ name: 'couchbase', file: 'dist/cluster.js', versions: ['3.2.0 - 3.2.1', '>=3.2.2'] }, cluster => {
246
+ const Cluster = cluster.Cluster
247
+
248
+ shimmer.wrap(Cluster.prototype, 'query', wrapV3Query)
249
+ return cluster
250
+ })
@@ -6,7 +6,6 @@ const { addHook, channel, AsyncResource } = require('./helpers/instrument')
6
6
  const errorChannel = channel('apm:fastify:middleware:error')
7
7
  const handleChannel = channel('apm:fastify:request:handle')
8
8
 
9
- const requestResources = new WeakMap()
10
9
  const parsingResources = new WeakMap()
11
10
 
12
11
  function wrapFastify (fastify, hasParsingEvents) {
@@ -42,16 +41,13 @@ function wrapAddHook (addHook) {
42
41
 
43
42
  arguments[arguments.length - 1] = shimmer.wrap(fn, function (request, reply, done) {
44
43
  const req = getReq(request)
45
- const requestResource = requestResources.get(req)
46
-
47
- if (!requestResource) return fn.apply(this, arguments)
48
44
 
49
45
  try {
50
46
  if (typeof done === 'function') {
51
47
  done = arguments[arguments.length - 1]
52
48
 
53
49
  arguments[arguments.length - 1] = function (err) {
54
- publishError(err, requestResource)
50
+ publishError(err, req)
55
51
 
56
52
  if (name === 'onRequest' || name === 'preParsing') {
57
53
  const parsingResource = new AsyncResource('bound-anonymous-fn')
@@ -71,13 +67,13 @@ function wrapAddHook (addHook) {
71
67
  const promise = fn.apply(this, arguments)
72
68
 
73
69
  if (promise && typeof promise.catch === 'function') {
74
- return promise.catch(err => publishError(err, requestResource))
70
+ return promise.catch(err => publishError(err, req))
75
71
  }
76
72
 
77
73
  return promise
78
74
  }
79
75
  } catch (e) {
80
- throw publishError(e, requestResource)
76
+ throw publishError(e, req)
81
77
  }
82
78
  })
83
79
 
@@ -90,14 +86,10 @@ function onRequest (request, reply, done) {
90
86
 
91
87
  const req = getReq(request)
92
88
  const res = getRes(reply)
93
- const requestResource = new AsyncResource('bound-anonymous-fn')
94
89
 
95
- requestResources.set(req, requestResource)
90
+ handleChannel.publish({ req, res })
96
91
 
97
- return requestResource.runInAsyncScope(() => {
98
- handleChannel.publish({ req, res })
99
- return done()
100
- })
92
+ return done()
101
93
  }
102
94
 
103
95
  function preHandler (request, reply, done) {
@@ -105,9 +97,8 @@ function preHandler (request, reply, done) {
105
97
  if (!reply || typeof reply.send !== 'function') return done()
106
98
 
107
99
  const req = getReq(request)
108
- const requestResource = requestResources.get(req)
109
100
 
110
- reply.send = wrapSend(reply.send, requestResource)
101
+ reply.send = wrapSend(reply.send, req)
111
102
 
112
103
  done()
113
104
  }
@@ -133,12 +124,10 @@ function preParsing (request, reply, payload, done) {
133
124
  parsingResource.runInAsyncScope(() => done())
134
125
  }
135
126
 
136
- function wrapSend (send, resource) {
137
- return function sendWithTrace (payload) {
138
- if (payload instanceof Error) {
139
- resource.runInAsyncScope(() => {
140
- errorChannel.publish(payload)
141
- })
127
+ function wrapSend (send, req) {
128
+ return function sendWithTrace (error) {
129
+ if (error instanceof Error) {
130
+ errorChannel.publish({ req, error })
142
131
  }
143
132
 
144
133
  return send.apply(this, arguments)
@@ -153,11 +142,9 @@ function getRes (reply) {
153
142
  return reply && (reply.raw || reply.res || reply)
154
143
  }
155
144
 
156
- function publishError (error, resource) {
145
+ function publishError (error, req) {
157
146
  if (error) {
158
- resource.runInAsyncScope(() => {
159
- errorChannel.publish(error)
160
- })
147
+ errorChannel.publish({ error, req })
161
148
  }
162
149
 
163
150
  return error
@@ -17,12 +17,15 @@ const patchedTypes = new WeakSet()
17
17
  /** CHANNELS */
18
18
 
19
19
  // execute channels
20
- const startResolveCh = channel('apm:graphql:resolve:start')
21
20
  const startExecuteCh = channel('apm:graphql:execute:start')
22
21
  const finishExecuteCh = channel('apm:graphql:execute:finish')
22
+ const executeErrorCh = channel('apm:graphql:execute:error')
23
+
24
+ // resolve channels
25
+ const startResolveCh = channel('apm:graphql:resolve:start')
23
26
  const finishResolveCh = channel('apm:graphql:resolve:finish')
24
27
  const updateFieldCh = channel('apm:graphql:resolve:updateField')
25
- const executeErrorCh = channel('apm:graphql:execute:error')
28
+ const resolveErrorCh = channel('apm:graphql:resolve:error')
26
29
 
27
30
  // parse channels
28
31
  const parseStartCh = channel('apm:graphql:parser:start')
@@ -124,7 +127,9 @@ function wrapValidate (validate) {
124
127
  let errors
125
128
  try {
126
129
  errors = validate.apply(this, arguments)
127
- validateErrorCh.publish(errors && errors[0])
130
+ if (errors && errors[0]) {
131
+ validateErrorCh.publish(errors && errors[0])
132
+ }
128
133
  return errors
129
134
  } catch (err) {
130
135
  err.stack
@@ -177,7 +182,12 @@ function wrapExecute (execute) {
177
182
  return callInAsyncScope(exe, asyncResource, this, arguments, (err, res) => {
178
183
  if (finishResolveCh.hasSubscribers) finishResolvers(context)
179
184
 
180
- executeErrorCh.publish(err || (res && res.errors && res.errors[0]))
185
+ const error = err || (res && res.errors && res.errors[0])
186
+
187
+ if (error) {
188
+ executeErrorCh.publish(error)
189
+ }
190
+
181
191
  finishExecuteCh.publish({ res, args })
182
192
  })
183
193
  })
@@ -331,7 +341,9 @@ function finishResolvers ({ fields }) {
331
341
  const field = fields[key]
332
342
  const asyncResource = field.asyncResource
333
343
  asyncResource.runInAsyncScope(() => {
334
- executeErrorCh.publish(field.error)
344
+ if (field.error) {
345
+ resolveErrorCh.publish(field.error)
346
+ }
335
347
  finishResolveCh.publish(field.finishTime)
336
348
  })
337
349
  })
@@ -126,13 +126,13 @@ function wrapMiddleware (fn, layer) {
126
126
  }
127
127
 
128
128
  function fulfill (ctx, error) {
129
- if (error) {
130
- errorChannel.publish(error)
131
- }
132
-
133
129
  const req = ctx.req
134
130
  const route = ctx.routePath
135
131
 
132
+ if (error) {
133
+ errorChannel.publish({ req, error })
134
+ }
135
+
136
136
  // TODO: make sure that the parent class cannot override this in `enter`
137
137
  if (route) {
138
138
  routeChannel.publish({ req, route })
@@ -145,7 +145,7 @@ function wrapNext (req, next) {
145
145
  return function () {
146
146
  nextChannel.publish({ req })
147
147
 
148
- return next.apply(null, arguments)
148
+ return next.apply(this, arguments)
149
149
  }
150
150
  }
151
151