dd-trace 2.3.1 → 2.4.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 (33) hide show
  1. package/index.d.ts +51 -0
  2. package/package.json +2 -2
  3. package/packages/datadog-instrumentations/index.js +8 -0
  4. package/packages/datadog-instrumentations/src/amqp10.js +70 -0
  5. package/packages/datadog-instrumentations/src/amqplib.js +58 -0
  6. package/packages/datadog-instrumentations/src/cassandra-driver.js +191 -0
  7. package/packages/datadog-instrumentations/src/cucumber.js +2 -0
  8. package/packages/datadog-instrumentations/src/helpers/instrument.js +3 -3
  9. package/packages/datadog-instrumentations/src/mocha.js +122 -0
  10. package/packages/datadog-instrumentations/src/mongodb-core.js +179 -0
  11. package/packages/datadog-instrumentations/src/pg.js +75 -0
  12. package/packages/datadog-instrumentations/src/rhea.js +224 -0
  13. package/packages/datadog-instrumentations/src/tedious.js +66 -0
  14. package/packages/datadog-plugin-amqp10/src/index.js +79 -122
  15. package/packages/datadog-plugin-amqplib/src/index.js +77 -142
  16. package/packages/datadog-plugin-cassandra-driver/src/index.js +52 -224
  17. package/packages/datadog-plugin-cucumber/src/index.js +3 -1
  18. package/packages/datadog-plugin-jest/src/jest-jasmine2.js +5 -3
  19. package/packages/datadog-plugin-mocha/src/index.js +96 -207
  20. package/packages/datadog-plugin-mongodb-core/src/index.js +119 -3
  21. package/packages/datadog-plugin-pg/src/index.js +32 -69
  22. package/packages/datadog-plugin-rhea/src/index.js +59 -225
  23. package/packages/datadog-plugin-tedious/src/index.js +38 -86
  24. package/packages/dd-trace/lib/version.js +1 -1
  25. package/packages/dd-trace/src/appsec/recommended.json +137 -116
  26. package/packages/dd-trace/src/config.js +6 -0
  27. package/packages/dd-trace/src/noop/tracer.js +4 -0
  28. package/packages/dd-trace/src/opentracing/propagation/text_map.js +34 -1
  29. package/packages/dd-trace/src/proxy.js +4 -0
  30. package/packages/dd-trace/src/tracer.js +16 -0
  31. package/packages/datadog-plugin-mongodb-core/src/legacy.js +0 -59
  32. package/packages/datadog-plugin-mongodb-core/src/unified.js +0 -138
  33. package/packages/datadog-plugin-mongodb-core/src/util.js +0 -143
package/index.d.ts CHANGED
@@ -108,6 +108,13 @@ export declare interface Tracer extends opentracing.Tracer {
108
108
  * should not be cached.
109
109
  */
110
110
  getRumData(): string;
111
+
112
+ /**
113
+ * Links an authenticated user to the current trace.
114
+ * @param {User} user Properties of the authenticated user. Accepts custom fields.
115
+ * @returns {Tracer} The Tracer instance for chaining.
116
+ */
117
+ setUser(user: User): Tracer;
111
118
  }
112
119
 
113
120
  export declare interface TraceOptions extends Analyzable {
@@ -311,6 +318,7 @@ export declare interface TracerOptions {
311
318
  */
312
319
  experimental?: boolean | {
313
320
  b3?: boolean
321
+ traceparent?: boolean
314
322
 
315
323
  /**
316
324
  * Whether to add an auto-generated `runtime-id` tag to metrics.
@@ -424,6 +432,49 @@ export declare interface TracerOptions {
424
432
  };
425
433
  }
426
434
 
435
+ /**
436
+ * User object that can be passed to `tracer.setUser()`.
437
+ */
438
+ export declare interface User {
439
+ /**
440
+ * Unique identifier of the user.
441
+ * Mandatory.
442
+ */
443
+ id: string,
444
+
445
+ /**
446
+ * Email of the user.
447
+ */
448
+ email?: string,
449
+
450
+ /**
451
+ * User-friendly name of the user.
452
+ */
453
+ name?: string,
454
+
455
+ /**
456
+ * Session ID of the user.
457
+ */
458
+ session_id?: string,
459
+
460
+ /**
461
+ * Role the user is making the request under.
462
+ */
463
+ role?: string,
464
+
465
+ /**
466
+ * Scopes or granted authorizations the user currently possesses.
467
+ * The value could come from the scope associated with an OAuth2
468
+ * Access Token or an attribute value in a SAML 2 Assertion.
469
+ */
470
+ scope?: string,
471
+
472
+ /**
473
+ * Custom fields to attach to the user (RBAC, Oauth, etc…).
474
+ */
475
+ [key: string]: string | undefined
476
+ }
477
+
427
478
  /** @hidden */
428
479
  interface EventEmitter {
429
480
  emit(eventName: string | symbol, ...args: any[]): any;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "2.3.1",
3
+ "version": "2.4.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -61,7 +61,7 @@
61
61
  "node": ">=12"
62
62
  },
63
63
  "dependencies": {
64
- "@datadog/native-appsec": "^0.8.1",
64
+ "@datadog/native-appsec": "^0.8.2",
65
65
  "@datadog/native-metrics": "^1.1.0",
66
66
  "@datadog/pprof": "^0.3.0",
67
67
  "@datadog/sketches-js": "^1.0.4",
@@ -1,7 +1,10 @@
1
1
  'use strict'
2
2
 
3
+ require('./src/amqplib')
4
+ require('./src/amqp10')
3
5
  require('./src/bluebird')
4
6
  require('./src/bunyan')
7
+ require('./src/cassandra-driver')
5
8
  require('./src/couchbase')
6
9
  require('./src/cucumber')
7
10
  require('./src/dns')
@@ -9,14 +12,19 @@ require('./src/elasticsearch')
9
12
  require('./src/generic-pool')
10
13
  require('./src/ioredis')
11
14
  require('./src/memcached')
15
+ require('./src/mongodb-core')
12
16
  require('./src/mongoose')
13
17
  require('./src/mysql')
14
18
  require('./src/mysql2')
19
+ require('./src/mocha')
15
20
  require('./src/pino')
21
+ require('./src/pg')
16
22
  require('./src/promise')
17
23
  require('./src/promise-js')
18
24
  require('./src/q')
19
25
  require('./src/redis')
26
+ require('./src/rhea')
20
27
  require('./src/sharedb')
28
+ require('./src/tedious')
21
29
  require('./src/when')
22
30
  require('./src/winston')
@@ -0,0 +1,70 @@
1
+ 'use strict'
2
+
3
+ const {
4
+ channel,
5
+ addHook,
6
+ AsyncResource
7
+ } = require('./helpers/instrument')
8
+ const shimmer = require('../../datadog-shimmer')
9
+
10
+ addHook({ name: 'amqp10', file: 'lib/sender_link.js', versions: ['>=3'] }, SenderLink => {
11
+ const startCh = channel('apm:amqp10:send:start')
12
+ const asyncEndCh = channel('apm:amqp10:send:async-end')
13
+ const endCh = channel('apm:amqp10:send:end')
14
+ const errorCh = channel('apm:amqp10:send:error')
15
+ shimmer.wrap(SenderLink.prototype, 'send', send => function (msg, options) {
16
+ if (!startCh.hasSubscribers) {
17
+ return send.apply(this, arguments)
18
+ }
19
+ startCh.publish({ link: this })
20
+ try {
21
+ const promise = send.apply(this, arguments)
22
+
23
+ if (!promise) {
24
+ finish(asyncEndCh, errorCh)
25
+ return promise
26
+ }
27
+
28
+ const asyncResource = new AsyncResource('bound-anonymous-fn')
29
+
30
+ promise.then(asyncResource.bind(() => finish(asyncEndCh, errorCh)),
31
+ asyncResource.bind(e => finish(asyncEndCh, errorCh, e)))
32
+
33
+ return promise
34
+ } catch (err) {
35
+ finish(asyncEndCh, errorCh, err)
36
+ throw err
37
+ } finally {
38
+ endCh.publish(undefined)
39
+ }
40
+ })
41
+ return SenderLink
42
+ })
43
+
44
+ addHook({ name: 'amqp10', file: 'lib/receiver_link.js', versions: ['>=3'] }, ReceiverLink => {
45
+ const startCh = channel('apm:amqp10:receive:start')
46
+ const endCh = channel('apm:amqp10:receive:end')
47
+ const errorCh = channel('apm:amqp10:receive:error')
48
+ shimmer.wrap(ReceiverLink.prototype, '_messageReceived', messageReceived => function (transferFrame) {
49
+ if (!transferFrame || transferFrame.aborted || transferFrame.more) {
50
+ return messageReceived.apply(this, arguments)
51
+ }
52
+ startCh.publish({ link: this })
53
+ try {
54
+ return messageReceived.apply(this, arguments)
55
+ } catch (err) {
56
+ errorCh.publish(err)
57
+ throw err
58
+ } finally {
59
+ endCh.publish(undefined)
60
+ }
61
+ })
62
+ return ReceiverLink
63
+ })
64
+
65
+ function finish (asyncEndCh, errorCh, error) {
66
+ if (error) {
67
+ errorCh.publish(error)
68
+ }
69
+ asyncEndCh.publish(undefined)
70
+ }
@@ -0,0 +1,58 @@
1
+ 'use strict'
2
+
3
+ const {
4
+ channel,
5
+ addHook
6
+ } = require('./helpers/instrument')
7
+ const kebabCase = require('lodash.kebabcase')
8
+ const shimmer = require('../../datadog-shimmer')
9
+
10
+ const startCh = channel('apm:amqplib:command:start')
11
+ const endCh = channel('apm:amqplib:command:end')
12
+ const errorCh = channel('apm:amqplib:command:error')
13
+
14
+ let methods = {}
15
+
16
+ addHook({ name: 'amqplib', file: 'lib/defs.js', versions: ['>=0.5'] }, defs => {
17
+ methods = Object.keys(defs)
18
+ .filter(key => Number.isInteger(defs[key]))
19
+ .filter(key => isCamelCase(key))
20
+ .reduce((acc, key) => Object.assign(acc, { [defs[key]]: kebabCase(key).replace('-', '.') }), {})
21
+ return defs
22
+ })
23
+
24
+ addHook({ name: 'amqplib', file: 'lib/channel.js', versions: ['>=0.5'] }, channel => {
25
+ shimmer.wrap(channel.Channel.prototype, 'sendImmediately', sendImmediately => function (method, fields) {
26
+ return instrument(sendImmediately, this, arguments, methods[method], fields)
27
+ })
28
+
29
+ shimmer.wrap(channel.Channel.prototype, 'sendMessage', sendMessage => function (fields) {
30
+ return instrument(sendMessage, this, arguments, 'basic.publish', fields)
31
+ })
32
+
33
+ shimmer.wrap(channel.BaseChannel.prototype, 'dispatchMessage', dispatchMessage => function (fields, message) {
34
+ return instrument(dispatchMessage, this, arguments, 'basic.deliver', fields, message)
35
+ })
36
+ return channel
37
+ })
38
+
39
+ function instrument (send, channel, args, method, fields, message) {
40
+ if (!startCh.hasSubscribers) {
41
+ return send.apply(this, arguments)
42
+ }
43
+ startCh.publish({ channel, method, fields, message })
44
+
45
+ try {
46
+ return send.apply(channel, args)
47
+ } catch (err) {
48
+ errorCh.publish(err)
49
+
50
+ throw err
51
+ } finally {
52
+ endCh.publish(undefined)
53
+ }
54
+ }
55
+
56
+ function isCamelCase (str) {
57
+ return /([A-Z][a-z0-9]+)+/.test(str)
58
+ }
@@ -0,0 +1,191 @@
1
+ 'use strict'
2
+
3
+ const {
4
+ channel,
5
+ addHook,
6
+ AsyncResource
7
+ } = require('./helpers/instrument')
8
+ const shimmer = require('../../datadog-shimmer')
9
+
10
+ const startCh = channel('apm:cassandra:query:start')
11
+ const asyncEndCh = channel('apm:cassandra:query:async-end')
12
+ const endCh = channel('apm:cassandra:query:end')
13
+ const errorCh = channel('apm:cassandra:query:error')
14
+ const addConnectionCh = channel(`apm:cassandra:query:addConnection`)
15
+
16
+ addHook({ name: 'cassandra-driver', versions: ['>=3.0.0'] }, cassandra => {
17
+ shimmer.wrap(cassandra.Client.prototype, 'batch', batch => function (queries, options, callback) {
18
+ if (!startCh.hasSubscribers) {
19
+ return batch.apply(this, arguments)
20
+ }
21
+ const asyncResource = new AsyncResource('bound-anonymous-fn')
22
+ startCh.publish({ keyspace: this.keyspace, query: queries })
23
+
24
+ const lastIndex = arguments.length - 1
25
+ let cb = arguments[lastIndex]
26
+
27
+ if (typeof cb === 'function') {
28
+ cb = asyncResource.bind(cb)
29
+ arguments[lastIndex] = wrapCallback(asyncEndCh, errorCh, cb)
30
+ }
31
+
32
+ try {
33
+ const res = batch.apply(this, arguments)
34
+ if (typeof res === 'function' || !res) {
35
+ return wrapCallback(asyncEndCh, errorCh, res)
36
+ } else {
37
+ const promiseAsyncResource = new AsyncResource('bound-anonymous-fn')
38
+ return res.then(
39
+ promiseAsyncResource.bind(() => finish(asyncEndCh, errorCh)),
40
+ promiseAsyncResource.bind(err => finish(asyncEndCh, errorCh, err))
41
+ )
42
+ }
43
+ } catch (e) {
44
+ finish(asyncEndCh, errorCh, e)
45
+ throw e
46
+ } finally {
47
+ endCh.publish(undefined)
48
+ }
49
+ })
50
+ return cassandra
51
+ })
52
+
53
+ addHook({ name: 'cassandra-driver', versions: ['>=4.4'] }, cassandra => {
54
+ shimmer.wrap(cassandra.Client.prototype, '_execute', _execute => function (query, params, execOptions, callback) {
55
+ if (!startCh.hasSubscribers) {
56
+ return _execute.apply(this, arguments)
57
+ }
58
+ startCh.publish({ keyspace: this.keyspace, query })
59
+ const promise = _execute.apply(this, arguments)
60
+
61
+ const promiseAsyncResource = new AsyncResource('bound-anonymous-fn')
62
+
63
+ promise.then(
64
+ promiseAsyncResource.bind(() => finish(asyncEndCh, errorCh)),
65
+ promiseAsyncResource.bind(err => finish(asyncEndCh, errorCh, err))
66
+ )
67
+ endCh.publish(undefined)
68
+ return promise
69
+ })
70
+ return cassandra
71
+ })
72
+
73
+ addHook({ name: 'cassandra-driver', versions: ['3 - 4.3'] }, cassandra => {
74
+ shimmer.wrap(cassandra.Client.prototype, '_innerExecute', _innerExecute =>
75
+ function (query, params, execOptions, callback) {
76
+ if (!startCh.hasSubscribers) {
77
+ return _innerExecute.apply(this, arguments)
78
+ }
79
+ const asyncResource = new AsyncResource('bound-anonymous-fn')
80
+ const isValid = (args) => {
81
+ return args.length === 4 || typeof args[3] === 'function'
82
+ }
83
+
84
+ if (!isValid(arguments)) {
85
+ return _innerExecute.apply(this, arguments)
86
+ }
87
+
88
+ startCh.publish({ keyspace: this.keyspace, query })
89
+
90
+ const lastIndex = arguments.length - 1
91
+ let cb = arguments[lastIndex]
92
+
93
+ if (typeof cb === 'function') {
94
+ cb = asyncResource.bind(cb)
95
+ arguments[lastIndex] = wrapCallback(asyncEndCh, errorCh, cb)
96
+ }
97
+
98
+ try {
99
+ return _innerExecute.apply(this, arguments)
100
+ } catch (e) {
101
+ finish(asyncEndCh, errorCh, e)
102
+ throw e
103
+ } finally {
104
+ endCh.publish(undefined)
105
+ }
106
+ }
107
+ )
108
+ return cassandra
109
+ })
110
+
111
+ addHook({ name: 'cassandra-driver', versions: ['>=3.3'], file: 'lib/request-execution.js' }, RequestExecution => {
112
+ shimmer.wrap(RequestExecution.prototype, '_sendOnConnection', _sendOnConnection => function () {
113
+ if (!startCh.hasSubscribers) {
114
+ return _sendOnConnection.apply(this, arguments)
115
+ }
116
+ addConnectionCh.publish({ address: this._connection.address, port: this._connection.port })
117
+ return _sendOnConnection.apply(this, arguments)
118
+ })
119
+ return RequestExecution
120
+ })
121
+
122
+ addHook({ name: 'cassandra-driver', versions: ['3.3 - 4.3'], file: 'lib/request-execution.js' }, RequestExecution => {
123
+ shimmer.wrap(RequestExecution.prototype, 'start', start => function (getHostCallback) {
124
+ if (!startCh.hasSubscribers) {
125
+ return getHostCallback.apply(this, arguments)
126
+ }
127
+ const asyncResource = new AsyncResource('bound-anonymous-fn')
128
+ const execution = this
129
+
130
+ if (!isRequestValid(this, arguments, 1)) {
131
+ return start.apply(this, arguments)
132
+ }
133
+
134
+ getHostCallback = asyncResource.bind(getHostCallback)
135
+
136
+ arguments[0] = AsyncResource.bind(function () {
137
+ addConnectionCh.publish({ address: execution._connection.address, port: execution._connection.port })
138
+ return getHostCallback.apply(this, arguments)
139
+ })
140
+
141
+ return start.apply(this, arguments)
142
+ })
143
+ return RequestExecution
144
+ })
145
+
146
+ addHook({ name: 'cassandra-driver', versions: ['3 - 3.2'], file: 'lib/request-handler.js' }, RequestHandler => {
147
+ shimmer.wrap(RequestHandler.prototype, 'send', send => function (request, options, callback) {
148
+ if (!startCh.hasSubscribers) {
149
+ return send.apply(this, arguments)
150
+ }
151
+ const handler = this
152
+
153
+ if (!isRequestValid(this, arguments, 3)) {
154
+ return send.apply(this, arguments)
155
+ }
156
+ const asyncResource = new AsyncResource('bound-anonymous-fn')
157
+
158
+ callback = asyncResource.bind(callback)
159
+
160
+ arguments[2] = AsyncResource.bind(function () {
161
+ addConnectionCh.publish({ address: handler.connection.address, port: handler.connection.port })
162
+ return callback.apply(this, arguments)
163
+ })
164
+
165
+ return send.apply(this, arguments)
166
+ })
167
+ return RequestHandler
168
+ })
169
+
170
+ function finish (asyncEndCh, errorCh, error) {
171
+ if (error) {
172
+ errorCh.publish(error)
173
+ }
174
+ asyncEndCh.publish(undefined)
175
+ }
176
+
177
+ function wrapCallback (asyncEndCh, errorCh, callback) {
178
+ return AsyncResource.bind(function (err) {
179
+ finish(asyncEndCh, errorCh, err)
180
+ if (callback) {
181
+ return callback.apply(this, arguments)
182
+ }
183
+ })
184
+ }
185
+
186
+ function isRequestValid (exec, args, length) {
187
+ if (!exec) return false
188
+ if (args.length !== length || typeof args[length - 1] !== 'function') return false
189
+
190
+ return true
191
+ }
@@ -55,6 +55,7 @@ function wrapRun (pl, isLatestVersion) {
55
55
  return promise
56
56
  } catch (err) {
57
57
  errorCh.publish(err)
58
+ throw err
58
59
  } finally {
59
60
  runEndCh.publish(undefined)
60
61
  }
@@ -85,6 +86,7 @@ function wrapRun (pl, isLatestVersion) {
85
86
  return promise
86
87
  } catch (err) {
87
88
  errorCh.publish(err)
89
+ throw err
88
90
  } finally {
89
91
  runStepEndCh.publish(undefined)
90
92
  }
@@ -20,11 +20,11 @@ exports.channel = function channel (name) {
20
20
  }
21
21
 
22
22
  exports.addHook = function addHook ({ name, versions, file }, hook) {
23
- file = filename(name, file)
23
+ const fullFilename = filename(name, file)
24
24
  const loaderHook = (moduleExports, moduleName, moduleBaseDir) => {
25
25
  moduleName = moduleName.replace(pathSepExpr, '/')
26
26
  const moduleVersion = getVersion(moduleBaseDir)
27
- if (moduleName !== file || !matchVersion(moduleVersion, versions)) {
27
+ if (moduleName !== fullFilename || !matchVersion(moduleVersion, versions)) {
28
28
  return moduleExports
29
29
  }
30
30
  return hook(moduleExports)
@@ -65,7 +65,7 @@ function cjsPostLoad (instrumentation, hook) {
65
65
  if (!id.includes(`/node_modules/${instrumentation.name}/`)) continue
66
66
 
67
67
  if (instrumentation.file) {
68
- if (!id.endsWith(`/node_modules/${filename(instrumentation)}`)) continue
68
+ if (!id.endsWith(`/node_modules/${filename(instrumentation.name, instrumentation.file)}`)) continue
69
69
 
70
70
  const basedir = getBasedir(ids[i])
71
71
 
@@ -0,0 +1,122 @@
1
+ const { addHook, channel, AsyncResource } = require('./helpers/instrument')
2
+ const shimmer = require('../../datadog-shimmer')
3
+
4
+ const testStartCh = channel('ci:mocha:test:start')
5
+ const errorCh = channel('ci:mocha:test:error')
6
+ const skipCh = channel('ci:mocha:test:skip')
7
+ const testEndCh = channel('ci:mocha:test:end')
8
+ const testAsyncEndCh = channel('ci:mocha:test:async-end')
9
+ const suiteEndCh = channel('ci:mocha:suite:end')
10
+ const hookErrorCh = channel('ci:mocha:hook:error')
11
+ const parameterizedTestCh = channel('ci:mocha:test:parameterize')
12
+ const testRunEndCh = channel('ci:mocha:run:end')
13
+
14
+ function isRetry (test) {
15
+ return test._currentRetry !== undefined && test._currentRetry !== 0
16
+ }
17
+
18
+ function getAllTestsInSuite (root) {
19
+ const tests = []
20
+ function getTests (suiteOrTest) {
21
+ suiteOrTest.tests.forEach(test => {
22
+ tests.push(test)
23
+ })
24
+ suiteOrTest.suites.forEach(suite => {
25
+ getTests(suite)
26
+ })
27
+ }
28
+ getTests(root)
29
+ return tests
30
+ }
31
+
32
+ addHook({
33
+ name: 'mocha',
34
+ versions: ['>=5.2.0'],
35
+ file: 'lib/runner.js'
36
+ }, (Runner) => {
37
+ shimmer.wrap(Runner.prototype, 'runTest', runTest => function () {
38
+ if (!testStartCh.hasSubscribers) {
39
+ return runTest.apply(this, arguments)
40
+ }
41
+
42
+ if (!isRetry(this.test)) {
43
+ testStartCh.publish(this.test)
44
+ }
45
+
46
+ this.once('test end', AsyncResource.bind(() => {
47
+ let status
48
+
49
+ if (this.test.pending) {
50
+ status = 'skipped'
51
+ } else if (this.test.state !== 'failed' && !this.test.timedOut) {
52
+ status = 'pass'
53
+ } else {
54
+ status = 'fail'
55
+ }
56
+
57
+ testAsyncEndCh.publish(status)
58
+ }))
59
+
60
+ this.once('fail', AsyncResource.bind((test, err) => {
61
+ errorCh.publish(err)
62
+ }))
63
+
64
+ this.once('pending', AsyncResource.bind((test) => {
65
+ skipCh.publish(test)
66
+ }))
67
+
68
+ try {
69
+ return runTest.apply(this, arguments)
70
+ } catch (err) {
71
+ errorCh.publish(err)
72
+ throw err
73
+ } finally {
74
+ testEndCh.publish(undefined)
75
+ }
76
+ })
77
+
78
+ shimmer.wrap(Runner.prototype, 'runTests', runTests => function () {
79
+ if (!suiteEndCh.hasSubscribers) {
80
+ return runTests.apply(this, arguments)
81
+ }
82
+ this.once('end', AsyncResource.bind(() => {
83
+ testRunEndCh.publish(undefined)
84
+ }))
85
+ runTests.apply(this, arguments)
86
+ const suite = arguments[0]
87
+ // We call `getAllTestsInSuite` with the root suite so every skipped test
88
+ // should already have an associated test span.
89
+ const tests = getAllTestsInSuite(suite)
90
+ suiteEndCh.publish(tests)
91
+ })
92
+
93
+ shimmer.wrap(Runner.prototype, 'fail', fail => function (hook, error) {
94
+ if (!hookErrorCh.hasSubscribers) {
95
+ return fail.apply(this, arguments)
96
+ }
97
+ if (error && hook.ctx && hook.ctx.currentTest) {
98
+ error.message = `${hook.title}: ${error.message}`
99
+ hookErrorCh.publish({ test: hook.ctx.currentTest, error })
100
+ }
101
+ return fail.apply(this, arguments)
102
+ })
103
+
104
+ return Runner
105
+ })
106
+
107
+ addHook({
108
+ name: 'mocha-each',
109
+ versions: ['>=2.0.1']
110
+ }, (mochaEach) => {
111
+ return shimmer.wrap(mochaEach, function () {
112
+ const [params] = arguments
113
+ const { it, ...rest } = mochaEach.apply(this, arguments)
114
+ return {
115
+ it: function (name) {
116
+ parameterizedTestCh.publish({ name, params })
117
+ it.apply(this, arguments)
118
+ },
119
+ ...rest
120
+ }
121
+ })
122
+ })