dd-trace 2.3.1 → 2.4.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 (53) hide show
  1. package/ci/init.js +26 -2
  2. package/index.d.ts +51 -0
  3. package/package.json +2 -2
  4. package/packages/datadog-instrumentations/index.js +10 -0
  5. package/packages/datadog-instrumentations/src/amqp10.js +70 -0
  6. package/packages/datadog-instrumentations/src/amqplib.js +58 -0
  7. package/packages/datadog-instrumentations/src/cassandra-driver.js +191 -0
  8. package/packages/datadog-instrumentations/src/cucumber.js +27 -12
  9. package/packages/datadog-instrumentations/src/helpers/hook.js +44 -0
  10. package/packages/datadog-instrumentations/src/helpers/instrument.js +31 -58
  11. package/packages/datadog-instrumentations/src/http/client.js +170 -0
  12. package/packages/datadog-instrumentations/src/http/server.js +61 -0
  13. package/packages/datadog-instrumentations/src/http.js +4 -0
  14. package/packages/datadog-instrumentations/src/mocha.js +139 -0
  15. package/packages/datadog-instrumentations/src/mongodb-core.js +179 -0
  16. package/packages/datadog-instrumentations/src/net.js +117 -0
  17. package/packages/datadog-instrumentations/src/pg.js +75 -0
  18. package/packages/datadog-instrumentations/src/rhea.js +224 -0
  19. package/packages/datadog-instrumentations/src/tedious.js +66 -0
  20. package/packages/datadog-plugin-amqp10/src/index.js +79 -122
  21. package/packages/datadog-plugin-amqplib/src/index.js +77 -142
  22. package/packages/datadog-plugin-cassandra-driver/src/index.js +52 -224
  23. package/packages/datadog-plugin-cucumber/src/index.js +3 -1
  24. package/packages/datadog-plugin-elasticsearch/src/index.js +4 -2
  25. package/packages/datadog-plugin-http/src/client.js +112 -252
  26. package/packages/datadog-plugin-http/src/index.js +29 -3
  27. package/packages/datadog-plugin-http/src/server.js +54 -32
  28. package/packages/datadog-plugin-jest/src/jest-environment.js +3 -3
  29. package/packages/datadog-plugin-jest/src/jest-jasmine2.js +5 -3
  30. package/packages/datadog-plugin-mocha/src/index.js +96 -207
  31. package/packages/datadog-plugin-mongodb-core/src/index.js +119 -3
  32. package/packages/datadog-plugin-net/src/index.js +65 -121
  33. package/packages/datadog-plugin-next/src/index.js +10 -10
  34. package/packages/datadog-plugin-pg/src/index.js +32 -69
  35. package/packages/datadog-plugin-rhea/src/index.js +59 -225
  36. package/packages/datadog-plugin-tedious/src/index.js +38 -86
  37. package/packages/dd-trace/lib/version.js +1 -1
  38. package/packages/dd-trace/src/appsec/recommended.json +235 -315
  39. package/packages/dd-trace/src/config.js +6 -0
  40. package/packages/dd-trace/src/iitm.js +5 -1
  41. package/packages/dd-trace/src/loader.js +6 -4
  42. package/packages/dd-trace/src/noop/tracer.js +4 -0
  43. package/packages/dd-trace/src/opentracing/propagation/text_map.js +34 -1
  44. package/packages/dd-trace/src/opentracing/span.js +34 -0
  45. package/packages/dd-trace/src/plugin_manager.js +4 -0
  46. package/packages/dd-trace/src/plugins/plugin.js +3 -1
  47. package/packages/dd-trace/src/plugins/util/web.js +99 -93
  48. package/packages/dd-trace/src/proxy.js +4 -0
  49. package/packages/dd-trace/src/ritm.js +60 -25
  50. package/packages/dd-trace/src/tracer.js +16 -0
  51. package/packages/datadog-plugin-mongodb-core/src/legacy.js +0 -59
  52. package/packages/datadog-plugin-mongodb-core/src/unified.js +0 -138
  53. package/packages/datadog-plugin-mongodb-core/src/util.js +0 -143
@@ -3,11 +3,10 @@
3
3
  const dc = require('diagnostics_channel')
4
4
  const path = require('path')
5
5
  const semver = require('semver')
6
- const iitm = require('../../../dd-trace/src/iitm')
7
- const ritm = require('../../../dd-trace/src/ritm')
8
- const parse = require('module-details-from-path')
6
+ const Hook = require('./hook')
9
7
  const requirePackageJson = require('../../../dd-trace/src/require-package-json')
10
8
  const { AsyncResource } = require('async_hooks')
9
+ const log = require('../../../dd-trace/src/log')
11
10
 
12
11
  const pathSepExpr = new RegExp(`\\${path.sep}`, 'g')
13
12
  const channelMap = {}
@@ -20,18 +19,22 @@ exports.channel = function channel (name) {
20
19
  }
21
20
 
22
21
  exports.addHook = function addHook ({ name, versions, file }, hook) {
23
- file = filename(name, file)
24
- const loaderHook = (moduleExports, moduleName, moduleBaseDir) => {
22
+ const fullFilename = filename(name, file)
23
+
24
+ Hook([name], (moduleExports, moduleName, moduleBaseDir) => {
25
25
  moduleName = moduleName.replace(pathSepExpr, '/')
26
- const moduleVersion = getVersion(moduleBaseDir)
27
- if (moduleName !== file || !matchVersion(moduleVersion, versions)) {
26
+
27
+ if (moduleName !== fullFilename || !matchVersion(getVersion(moduleBaseDir), versions)) {
28
28
  return moduleExports
29
29
  }
30
- return hook(moduleExports)
31
- }
32
- ritm([name], loaderHook)
33
- cjsPostLoad({ name, versions, file }, hook)
34
- iitm([name], loaderHook)
30
+
31
+ try {
32
+ return hook(moduleExports)
33
+ } catch (e) {
34
+ log.error(e)
35
+ return moduleExports
36
+ }
37
+ })
35
38
  }
36
39
 
37
40
  function matchVersion (version, ranges) {
@@ -48,48 +51,9 @@ function filename (name, file) {
48
51
  return [name, file].filter(val => val).join('/')
49
52
  }
50
53
 
51
- // TODO this is basically Loader#_getModules + running the hook. DRY up.
52
- function cjsPostLoad (instrumentation, hook) {
53
- const ids = Object.keys(require.cache)
54
-
55
- let pkg
56
-
57
- for (let i = 0, l = ids.length; i < l; i++) {
58
- if (ids[i] === instrumentation.name) {
59
- hook(require.cache[ids[i]].exports)
60
- continue
61
- }
62
-
63
- const id = ids[i].replace(pathSepExpr, '/')
64
-
65
- if (!id.includes(`/node_modules/${instrumentation.name}/`)) continue
66
-
67
- if (instrumentation.file) {
68
- if (!id.endsWith(`/node_modules/${filename(instrumentation)}`)) continue
69
-
70
- const basedir = getBasedir(ids[i])
71
-
72
- pkg = requirePackageJson(basedir, module)
73
- } else {
74
- const basedir = getBasedir(ids[i])
75
-
76
- pkg = requirePackageJson(basedir, module)
77
-
78
- const mainFile = path.posix.normalize(pkg.main || 'index.js')
79
- if (!id.endsWith(`/node_modules/${instrumentation.name}/${mainFile}`)) continue
80
- }
81
-
82
- if (!matchVersion(pkg.version, instrumentation.versions)) continue
83
-
84
- hook(require.cache[ids[i]].exports)
85
- }
86
- }
87
-
88
- function getBasedir (id) {
89
- return parse(id).basedir.replace(pathSepExpr, '/')
90
- }
91
-
92
- if (semver.satisfies(process.versions.node, '>=16.0.0')) {
54
+ // AsyncResource.bind exists and binds `this` properly only from 17.8.0 and up.
55
+ // https://nodejs.org/api/async_context.html#asyncresourcebindfn-thisarg
56
+ if (semver.satisfies(process.versions.node, '>=17.8.0')) {
93
57
  exports.AsyncResource = AsyncResource
94
58
  } else {
95
59
  exports.AsyncResource = class extends AsyncResource {
@@ -98,9 +62,18 @@ if (semver.satisfies(process.versions.node, '>=16.0.0')) {
98
62
  return (new exports.AsyncResource(type || 'bound-anonymous-fn')).bind(fn, thisArg)
99
63
  }
100
64
 
101
- bind (fn, thisArg = this) {
102
- const ret = this.runInAsyncScope.bind(this, fn, thisArg)
103
- Object.defineProperties(ret, {
65
+ bind (fn, thisArg) {
66
+ let bound
67
+ if (thisArg === undefined) {
68
+ const resource = this
69
+ bound = function (...args) {
70
+ args.unshift(fn, this)
71
+ return Reflect.apply(resource.runInAsyncScope, resource, args)
72
+ }
73
+ } else {
74
+ bound = this.runInAsyncScope.bind(this, fn, thisArg)
75
+ }
76
+ Object.defineProperties(bound, {
104
77
  'length': {
105
78
  configurable: true,
106
79
  enumerable: false,
@@ -114,7 +87,7 @@ if (semver.satisfies(process.versions.node, '>=16.0.0')) {
114
87
  writable: true
115
88
  }
116
89
  })
117
- return ret
90
+ return bound
118
91
  }
119
92
  }
120
93
  }
@@ -0,0 +1,170 @@
1
+ 'use strict'
2
+
3
+ /* eslint-disable no-fallthrough */
4
+
5
+ const url = require('url')
6
+ const {
7
+ channel,
8
+ addHook,
9
+ AsyncResource
10
+ } = require('../helpers/instrument')
11
+ const shimmer = require('../../../datadog-shimmer')
12
+
13
+ const log = require('../../../dd-trace/src/log')
14
+
15
+ const startClientCh = channel('apm:http:client:request:start')
16
+ const asyncEndClientCh = channel('apm:http:client:request:async-end')
17
+ const endClientCh = channel('apm:http:client:request:end')
18
+ const errorClientCh = channel('apm:http:client:request:error')
19
+
20
+ addHook({ name: 'https' }, hookFn)
21
+
22
+ addHook({ name: 'http' }, hookFn)
23
+
24
+ function hookFn (http) {
25
+ patch(http, 'request')
26
+ patch(http, 'get')
27
+
28
+ return http
29
+ }
30
+
31
+ function patch (http, methodName) {
32
+ shimmer.wrap(http, methodName, instrumentRequest)
33
+
34
+ function instrumentRequest (request) {
35
+ return function () {
36
+ if (!startClientCh.hasSubscribers) {
37
+ return request.apply(this, arguments)
38
+ }
39
+ const asyncResource = new AsyncResource('bound-anonymous-fn')
40
+
41
+ let args
42
+
43
+ try {
44
+ args = normalizeArgs.apply(null, arguments)
45
+ } catch (e) {
46
+ log.error(e)
47
+ return request.apply(this, arguments)
48
+ }
49
+ startClientCh.publish({ args, http })
50
+
51
+ const ar = new AsyncResource('bound-anonymous-fn')
52
+
53
+ let finished = false
54
+ let callback = args.callback
55
+
56
+ if (callback) {
57
+ callback = asyncResource.bind(callback)
58
+ }
59
+
60
+ const options = args.options
61
+ const req = ar.bind(request).call(this, options, callback)
62
+ const emit = req.emit
63
+
64
+ const finish = (req, res) => {
65
+ if (!finished) {
66
+ finished = true
67
+ asyncEndClientCh.publish({ req, res })
68
+ }
69
+ }
70
+
71
+ req.emit = function (eventName, arg) {
72
+ ar.runInAsyncScope(() => {
73
+ switch (eventName) {
74
+ case 'response': {
75
+ const res = arg
76
+ const listener = ar.bind(() => finish(req, res))
77
+ res.on('end', listener)
78
+ res.on('error', listener)
79
+ break
80
+ }
81
+ case 'connect':
82
+ case 'upgrade':
83
+ finish(req, arg)
84
+ break
85
+ case 'error':
86
+ errorClientCh.publish(arg)
87
+ case 'abort': // deprecated and replaced by `close` in node 17
88
+ case 'timeout':
89
+ case 'close':
90
+ finish(req)
91
+ }
92
+ })
93
+
94
+ return emit.apply(this, arguments)
95
+ }
96
+
97
+ endClientCh.publish(undefined)
98
+
99
+ return req
100
+ }
101
+ }
102
+
103
+ function normalizeArgs (inputURL, inputOptions, cb) {
104
+ inputURL = normalizeOptions(inputURL)
105
+
106
+ const [callback, inputOptionsNormalized] = normalizeCallback(inputOptions, cb, inputURL)
107
+ const options = combineOptions(inputURL, inputOptionsNormalized)
108
+ normalizeHeaders(options)
109
+ const uri = url.format(options)
110
+
111
+ return { uri, options, callback }
112
+ }
113
+
114
+ function combineOptions (inputURL, inputOptions) {
115
+ if (typeof inputOptions === 'object') {
116
+ return Object.assign(inputURL || {}, inputOptions)
117
+ } else {
118
+ return inputURL
119
+ }
120
+ }
121
+ function normalizeHeaders (options) {
122
+ options.headers = options.headers || {}
123
+ }
124
+
125
+ function normalizeCallback (inputOptions, callback, inputURL) {
126
+ if (typeof inputOptions === 'function') {
127
+ return [inputOptions, inputURL || {}]
128
+ } else {
129
+ return [callback, inputOptions]
130
+ }
131
+ }
132
+
133
+ function normalizeOptions (inputURL) {
134
+ if (typeof inputURL === 'string') {
135
+ try {
136
+ return urlToOptions(new url.URL(inputURL))
137
+ } catch (e) {
138
+ return url.parse(inputURL)
139
+ }
140
+ } else if (inputURL instanceof url.URL) {
141
+ return urlToOptions(inputURL)
142
+ } else {
143
+ return inputURL
144
+ }
145
+ }
146
+
147
+ function urlToOptions (url) {
148
+ const agent = url.agent || http.globalAgent
149
+ const options = {
150
+ protocol: url.protocol || agent.protocol,
151
+ hostname: typeof url.hostname === 'string' && url.hostname.startsWith('[')
152
+ ? url.hostname.slice(1, -1)
153
+ : url.hostname ||
154
+ url.host ||
155
+ 'localhost',
156
+ hash: url.hash,
157
+ search: url.search,
158
+ pathname: url.pathname,
159
+ path: `${url.pathname || ''}${url.search || ''}`,
160
+ href: url.href
161
+ }
162
+ if (url.port !== '') {
163
+ options.port = Number(url.port)
164
+ }
165
+ if (url.username || url.password) {
166
+ options.auth = `${url.username}:${url.password}`
167
+ }
168
+ return options
169
+ }
170
+ }
@@ -0,0 +1,61 @@
1
+ 'use strict'
2
+
3
+ const {
4
+ channel,
5
+ addHook
6
+ } = require('../helpers/instrument')
7
+ const shimmer = require('../../../datadog-shimmer')
8
+
9
+ const startServerCh = channel('apm:http:server:request:start')
10
+ const endServerCh = channel('apm:http:server:request:end')
11
+ const errorServerCh = channel('apm:http:server:request:error')
12
+ const asyncEndServerCh = channel('apm:http:server:request:async-end')
13
+
14
+ addHook({ name: 'https' }, http => {
15
+ // http.ServerResponse not present on https
16
+ shimmer.wrap(http.Server.prototype, 'emit', wrapEmit)
17
+ return http
18
+ })
19
+
20
+ addHook({ name: 'http' }, http => {
21
+ shimmer.wrap(http.ServerResponse.prototype, 'emit', wrapResponseEmit)
22
+ shimmer.wrap(http.Server.prototype, 'emit', wrapEmit)
23
+ return http
24
+ })
25
+
26
+ function wrapResponseEmit (emit) {
27
+ return function (eventName, event) {
28
+ if (!startServerCh.hasSubscribers) {
29
+ return emit.apply(this, arguments)
30
+ }
31
+
32
+ if (eventName === 'finish') {
33
+ asyncEndServerCh.publish({ req: this.req })
34
+ }
35
+
36
+ return emit.apply(this, arguments)
37
+ }
38
+ }
39
+ function wrapEmit (emit) {
40
+ return function (eventName, req, res) {
41
+ if (!startServerCh.hasSubscribers) {
42
+ return emit.apply(this, arguments)
43
+ }
44
+
45
+ if (eventName === 'request') {
46
+ res.req = req
47
+ startServerCh.publish({ req, res })
48
+
49
+ try {
50
+ return emit.apply(this, arguments)
51
+ } catch (err) {
52
+ errorServerCh.publish(err)
53
+
54
+ throw err
55
+ } finally {
56
+ endServerCh.publish(undefined)
57
+ }
58
+ }
59
+ return emit.apply(this, arguments)
60
+ }
61
+ }
@@ -0,0 +1,4 @@
1
+ 'use strict'
2
+
3
+ require('./http/client')
4
+ require('./http/server')
@@ -0,0 +1,139 @@
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
+ // TODO: remove when root hooks and fixtures are implemented
15
+ const patched = new WeakSet()
16
+
17
+ function isRetry (test) {
18
+ return test._currentRetry !== undefined && test._currentRetry !== 0
19
+ }
20
+
21
+ function getAllTestsInSuite (root) {
22
+ const tests = []
23
+ function getTests (suiteOrTest) {
24
+ suiteOrTest.tests.forEach(test => {
25
+ tests.push(test)
26
+ })
27
+ suiteOrTest.suites.forEach(suite => {
28
+ getTests(suite)
29
+ })
30
+ }
31
+ getTests(root)
32
+ return tests
33
+ }
34
+
35
+ function mochaHook (Runner) {
36
+ if (patched.has(Runner)) return Runner
37
+
38
+ patched.add(Runner)
39
+
40
+ shimmer.wrap(Runner.prototype, 'runTest', runTest => function () {
41
+ if (!testStartCh.hasSubscribers) {
42
+ return runTest.apply(this, arguments)
43
+ }
44
+
45
+ if (!isRetry(this.test)) {
46
+ testStartCh.publish(this.test)
47
+ }
48
+
49
+ this.once('test end', AsyncResource.bind(() => {
50
+ let status
51
+
52
+ if (this.test.pending) {
53
+ status = 'skipped'
54
+ } else if (this.test.state !== 'failed' && !this.test.timedOut) {
55
+ status = 'pass'
56
+ } else {
57
+ status = 'fail'
58
+ }
59
+
60
+ testAsyncEndCh.publish(status)
61
+ }))
62
+
63
+ this.once('fail', AsyncResource.bind((test, err) => {
64
+ errorCh.publish(err)
65
+ }))
66
+
67
+ this.once('pending', AsyncResource.bind((test) => {
68
+ skipCh.publish(test)
69
+ }))
70
+
71
+ try {
72
+ return runTest.apply(this, arguments)
73
+ } catch (err) {
74
+ errorCh.publish(err)
75
+ throw err
76
+ } finally {
77
+ testEndCh.publish(undefined)
78
+ }
79
+ })
80
+
81
+ shimmer.wrap(Runner.prototype, 'runTests', runTests => function () {
82
+ if (!suiteEndCh.hasSubscribers) {
83
+ return runTests.apply(this, arguments)
84
+ }
85
+ this.once('end', AsyncResource.bind(() => {
86
+ testRunEndCh.publish(undefined)
87
+ }))
88
+ runTests.apply(this, arguments)
89
+ const suite = arguments[0]
90
+ // We call `getAllTestsInSuite` with the root suite so every skipped test
91
+ // should already have an associated test span.
92
+ const tests = getAllTestsInSuite(suite)
93
+ suiteEndCh.publish(tests)
94
+ })
95
+
96
+ shimmer.wrap(Runner.prototype, 'fail', fail => function (hook, error) {
97
+ if (!hookErrorCh.hasSubscribers) {
98
+ return fail.apply(this, arguments)
99
+ }
100
+ if (error && hook.ctx && hook.ctx.currentTest) {
101
+ error.message = `${hook.title}: ${error.message}`
102
+ hookErrorCh.publish({ test: hook.ctx.currentTest, error })
103
+ }
104
+ return fail.apply(this, arguments)
105
+ })
106
+
107
+ return Runner
108
+ }
109
+
110
+ function mochaEachHook (mochaEach) {
111
+ if (patched.has(mochaEach)) return mochaEach
112
+
113
+ patched.add(mochaEach)
114
+
115
+ return shimmer.wrap(mochaEach, function () {
116
+ const [params] = arguments
117
+ const { it, ...rest } = mochaEach.apply(this, arguments)
118
+ return {
119
+ it: function (name) {
120
+ parameterizedTestCh.publish({ name, params })
121
+ it.apply(this, arguments)
122
+ },
123
+ ...rest
124
+ }
125
+ })
126
+ }
127
+
128
+ addHook({
129
+ name: 'mocha',
130
+ versions: ['>=5.2.0'],
131
+ file: 'lib/runner.js'
132
+ }, mochaHook)
133
+
134
+ addHook({
135
+ name: 'mocha-each',
136
+ versions: ['>=2.0.1']
137
+ }, mochaEachHook)
138
+
139
+ module.exports = { mochaHook, mochaEachHook }
@@ -0,0 +1,179 @@
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:mongodb:query:start`)
11
+ const endCh = channel(`apm:mongodb:query:end`)
12
+ const asyncEndCh = channel(`apm:mongodb:query:async-end`)
13
+ const errorCh = channel(`apm:mongodb:query:error`)
14
+
15
+ addHook({ name: 'mongodb-core', versions: ['2 - 3.1.9'] }, Server => {
16
+ const serverProto = Server.Server.prototype
17
+ shimmer.wrap(serverProto, 'command', command => wrapCommand(command, 'command'))
18
+ shimmer.wrap(serverProto, 'insert', insert => wrapCommand(insert, 'insert', 'insert'))
19
+ shimmer.wrap(serverProto, 'update', update => wrapCommand(update, 'update', 'update'))
20
+ shimmer.wrap(serverProto, 'remove', remove => wrapCommand(remove, 'remove', 'remove'))
21
+
22
+ const cursorProto = Server.Cursor.prototype
23
+ shimmer.wrap(cursorProto, '_getmore', _getmore => wrapCursor(_getmore, 'getMore', 'getMore'))
24
+ shimmer.wrap(cursorProto, '_find', _find => wrapQuery(_find, '_find'))
25
+ shimmer.wrap(cursorProto, 'kill', kill => wrapCursor(kill, 'killCursors', 'killCursors'))
26
+ return Server
27
+ })
28
+
29
+ addHook({ name: 'mongodb', versions: ['>=4'], file: 'lib/cmap/connection.js' }, Connection => {
30
+ const proto = Connection.Connection.prototype
31
+ shimmer.wrap(proto, 'command', command => wrapConnectionCommand(command, 'command'))
32
+ shimmer.wrap(proto, 'query', query => wrapConnectionCommand(query, 'query'))
33
+ return Connection
34
+ })
35
+
36
+ addHook({ name: 'mongodb', versions: ['>=3.3 <4'], file: 'lib/core/wireprotocol/index.js' }, wp => wrapWp(wp))
37
+
38
+ addHook({ name: 'mongodb-core', versions: ['>=3.2'], file: 'lib/wireprotocol/index.js' }, wp => wrapWp(wp))
39
+
40
+ addHook({ name: 'mongodb-core', versions: ['~3.1.10'], file: 'lib/wireprotocol/3_2_support.js' }, WireProtocol => {
41
+ shimmer.wrap(WireProtocol.prototype, 'command', command => wrapUnifiedCommand(command, 'command'))
42
+ return WireProtocol
43
+ })
44
+
45
+ addHook({ name: 'mongodb-core', versions: ['~3.1.10'], file: 'lib/wireprotocol/2_6_support.js' }, WireProtocol => {
46
+ shimmer.wrap(WireProtocol.prototype, 'command', command => wrapUnifiedCommand(command, 'command'))
47
+ return WireProtocol
48
+ })
49
+
50
+ addHook({ name: 'mongodb', versions: ['>=3.5.4'], file: 'lib/utils.js' }, util => {
51
+ shimmer.wrap(util, 'maybePromise', maybePromise => function (parent, callback, fn) {
52
+ const asyncResource = new AsyncResource('bound-anonymous-fn')
53
+ const callbackIndex = arguments.length - 2
54
+
55
+ callback = arguments[callbackIndex]
56
+
57
+ if (typeof callback === 'function') {
58
+ arguments[callbackIndex] = asyncResource.bind(callback)
59
+ }
60
+
61
+ return maybePromise.apply(this, arguments)
62
+ })
63
+ return util
64
+ })
65
+
66
+ function wrapWp (wp) {
67
+ shimmer.wrap(wp, 'command', command => wrapUnifiedCommand(command, 'command'))
68
+ shimmer.wrap(wp, 'insert', insert => wrapUnifiedCommand(insert, 'insert', 'insert'))
69
+ shimmer.wrap(wp, 'update', update => wrapUnifiedCommand(update, 'update', 'update'))
70
+ shimmer.wrap(wp, 'remove', remove => wrapUnifiedCommand(remove, 'remove', 'remove'))
71
+ shimmer.wrap(wp, 'query', query => wrapUnifiedCommand(query, 'query'))
72
+ shimmer.wrap(wp, 'getMore', getMore => wrapUnifiedCommand(getMore, 'getMore', 'getMore'))
73
+ shimmer.wrap(wp, 'killCursors', killCursors => wrapUnifiedCommand(killCursors, 'killCursors', 'killCursors'))
74
+ return wp
75
+ }
76
+
77
+ function wrapUnifiedCommand (command, operation, name) {
78
+ const wrapped = function (server, ns, ops) {
79
+ if (!startCh.hasSubscribers) {
80
+ return command.apply(this, arguments)
81
+ }
82
+ const asyncResource = new AsyncResource('bound-anonymous-fn')
83
+ return instrument(asyncResource, operation, command, this, arguments, server, ns, ops, { name })
84
+ }
85
+ return shimmer.wrap(command, wrapped)
86
+ }
87
+
88
+ function wrapConnectionCommand (command, operation, name) {
89
+ const wrapped = function (ns, ops) {
90
+ if (!startCh.hasSubscribers) {
91
+ return command.apply(this, arguments)
92
+ }
93
+ const asyncResource = new AsyncResource('bound-anonymous-fn')
94
+ const hostParts = typeof this.address === 'string' ? this.address.split(':') : ''
95
+ const options = hostParts.length === 2
96
+ ? { host: hostParts[0], port: hostParts[1] }
97
+ : {} // no port means the address is a random UUID so no host either
98
+ const topology = { s: { options } }
99
+
100
+ ns = `${ns.db}.${ns.collection}`
101
+ return instrument(asyncResource, operation, command, this, arguments, topology, ns, ops, { name })
102
+ }
103
+ return shimmer.wrap(command, wrapped)
104
+ }
105
+
106
+ function wrapQuery (query, operation, name) {
107
+ const wrapped = function () {
108
+ if (!startCh.hasSubscribers) {
109
+ return query.apply(this, arguments)
110
+ }
111
+ const asyncResource = new AsyncResource('bound-anonymous-fn')
112
+ const pool = this.server.s.pool
113
+ const ns = this.ns
114
+ const ops = this.cmd
115
+ return instrument(asyncResource, operation, query, this, arguments, pool, ns, ops)
116
+ }
117
+
118
+ return shimmer.wrap(query, wrapped)
119
+ }
120
+
121
+ function wrapCursor (cursor, operation, name) {
122
+ const wrapped = function () {
123
+ if (!startCh.hasSubscribers) {
124
+ return cursor.apply(this, arguments)
125
+ }
126
+ const asyncResource = new AsyncResource('bound-anonymous-fn')
127
+ const pool = this.server.s.pool
128
+ const ns = this.ns
129
+ return instrument(asyncResource, operation, cursor, this, arguments, pool, ns, {}, { name })
130
+ }
131
+ return shimmer.wrap(cursor, wrapped)
132
+ }
133
+
134
+ function wrapCommand (command, operation, name) {
135
+ const wrapped = function (ns, ops) {
136
+ if (!startCh.hasSubscribers) {
137
+ return command.apply(this, arguments)
138
+ }
139
+ const asyncResource = new AsyncResource('bound-anonymous-fn')
140
+ return instrument(asyncResource, operation, command, this, arguments, this, ns, ops, { name })
141
+ }
142
+ return shimmer.wrap(command, wrapped)
143
+ }
144
+
145
+ function instrument (ar, operation, command, ctx, args, server, ns, ops, options = {}) {
146
+ const name = options.name || (ops && Object.keys(ops)[0])
147
+ const index = args.length - 1
148
+ let callback = args[index]
149
+
150
+ if (typeof callback !== 'function') return command.apply(ctx, args)
151
+
152
+ callback = ar.bind(callback)
153
+
154
+ const serverInfo = server && server.s && server.s.options
155
+
156
+ startCh.publish({ ns, ops, options: serverInfo, name })
157
+
158
+ args[index] = AsyncResource.bind(function (err, res) {
159
+ if (err) {
160
+ errorCh.publish(err)
161
+ }
162
+
163
+ asyncEndCh.publish(undefined)
164
+
165
+ if (callback) {
166
+ return callback.apply(this, arguments)
167
+ }
168
+ })
169
+
170
+ try {
171
+ return command.apply(ctx, args)
172
+ } catch (err) {
173
+ errorCh.publish(err)
174
+
175
+ throw err
176
+ } finally {
177
+ endCh.publish(undefined)
178
+ }
179
+ }