dd-trace 2.4.0 → 2.5.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 (60) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/ci/init.js +32 -2
  3. package/ci/jest/env.js +16 -3
  4. package/ext/exporters.d.ts +2 -1
  5. package/ext/exporters.js +2 -1
  6. package/package.json +3 -2
  7. package/packages/datadog-instrumentations/index.js +2 -0
  8. package/packages/datadog-instrumentations/src/amqplib.js +1 -1
  9. package/packages/datadog-instrumentations/src/cucumber.js +25 -12
  10. package/packages/datadog-instrumentations/src/cypress.js +8 -0
  11. package/packages/datadog-instrumentations/src/helpers/hook.js +44 -0
  12. package/packages/datadog-instrumentations/src/helpers/instrument.js +30 -57
  13. package/packages/datadog-instrumentations/src/http/client.js +170 -0
  14. package/packages/datadog-instrumentations/src/http/server.js +61 -0
  15. package/packages/datadog-instrumentations/src/http.js +4 -0
  16. package/packages/datadog-instrumentations/src/mocha.js +28 -11
  17. package/packages/datadog-instrumentations/src/net.js +117 -0
  18. package/packages/datadog-plugin-aws-sdk/src/helpers.js +4 -4
  19. package/packages/datadog-plugin-aws-sdk/src/index.js +1 -1
  20. package/packages/datadog-plugin-cucumber/src/index.js +24 -12
  21. package/packages/datadog-plugin-cypress/src/index.js +10 -5
  22. package/packages/datadog-plugin-cypress/src/plugin.js +13 -1
  23. package/packages/datadog-plugin-elasticsearch/src/index.js +4 -2
  24. package/packages/datadog-plugin-http/src/client.js +112 -252
  25. package/packages/datadog-plugin-http/src/index.js +29 -3
  26. package/packages/datadog-plugin-http/src/server.js +54 -32
  27. package/packages/datadog-plugin-jest/src/jest-environment.js +3 -3
  28. package/packages/datadog-plugin-mocha/src/index.js +10 -1
  29. package/packages/datadog-plugin-net/src/index.js +65 -121
  30. package/packages/datadog-plugin-next/src/index.js +10 -10
  31. package/packages/dd-trace/lib/version.js +1 -1
  32. package/packages/dd-trace/src/appsec/callbacks/ddwaf.js +3 -1
  33. package/packages/dd-trace/src/appsec/recommended.json +119 -210
  34. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +32 -0
  35. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +51 -0
  36. package/packages/dd-trace/src/config.js +8 -1
  37. package/packages/dd-trace/src/encode/0.4.js +0 -1
  38. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +193 -0
  39. package/packages/dd-trace/src/encode/tags-processors.js +116 -0
  40. package/packages/dd-trace/src/exporter.js +3 -0
  41. package/packages/dd-trace/src/exporters/agent/index.js +1 -1
  42. package/packages/dd-trace/src/exporters/agent/writer.js +7 -32
  43. package/packages/dd-trace/src/exporters/{agent → common}/docker.js +0 -0
  44. package/packages/dd-trace/src/exporters/common/request.js +83 -0
  45. package/packages/dd-trace/src/exporters/common/writer.js +36 -0
  46. package/packages/dd-trace/src/exporters/{agent/scheduler.js → scheduler.js} +0 -0
  47. package/packages/dd-trace/src/iitm.js +5 -1
  48. package/packages/dd-trace/src/instrumenter.js +3 -0
  49. package/packages/dd-trace/src/loader.js +6 -4
  50. package/packages/dd-trace/src/opentracing/span.js +34 -0
  51. package/packages/dd-trace/src/pkg.js +11 -6
  52. package/packages/dd-trace/src/plugin_manager.js +4 -0
  53. package/packages/dd-trace/src/plugins/plugin.js +3 -1
  54. package/packages/dd-trace/src/plugins/util/test.js +60 -1
  55. package/packages/dd-trace/src/plugins/util/web.js +99 -93
  56. package/packages/dd-trace/src/profiling/exporters/agent.js +1 -1
  57. package/packages/dd-trace/src/proxy.js +2 -0
  58. package/packages/dd-trace/src/ritm.js +60 -25
  59. package/packages/dd-trace/src/telemetry.js +187 -0
  60. package/packages/dd-trace/src/exporters/agent/request.js +0 -86
@@ -7,6 +7,7 @@ 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
9
  require,form-data,MIT,Copyright 2012 Felix Geisendörfer and contributors
10
+ require,ignore,MIT,Copyright 2013 Kael Zhang and contributors
10
11
  require,import-in-the-middle,Apache license 2.0,Copyright 2021 Datadog Inc.
11
12
  require,koalas,MIT,Copyright 2013-2017 Brian Woodward
12
13
  require,limiter,MIT,Copyright 2011 John Hurliman
package/ci/init.js CHANGED
@@ -1,12 +1,42 @@
1
+ const path = require('path')
1
2
  const tracer = require('../packages/dd-trace')
2
3
  const { ORIGIN_KEY } = require('../packages/dd-trace/src/constants')
4
+ const { mochaHook } = require('../packages/datadog-instrumentations/src/mocha')
5
+ const { pickleHook, testCaseHook } = require('../packages/datadog-instrumentations/src/cucumber')
3
6
 
4
- tracer.init({
7
+ const options = {
5
8
  startupLogs: false,
6
9
  tags: {
7
10
  [ORIGIN_KEY]: 'ciapp-test'
8
11
  }
9
- })
12
+ }
13
+
14
+ if (process.env.DD_CIVISIBILITY_AGENTLESS_ENABLED && (process.env.DATADOG_API_KEY || process.env.DD_API_KEY)) {
15
+ options.experimental = {
16
+ exporter: 'datadog'
17
+ }
18
+ }
19
+
20
+ // TODO: remove this in a later major version since we now recommend using
21
+ // `NODE_OPTIONS='-r dd-trace/ci/init'`.
22
+ try {
23
+ for (const filename in require.cache) {
24
+ const cache = require.cache[filename]
25
+ const id = filename.split(path.sep).join('/')
26
+
27
+ if (id.includes('/node_modules/mocha/lib/runner.js')) {
28
+ cache.exports = mochaHook(cache.exports)
29
+ } else if (id.includes('/node_modules/@cucumber/cucumber/lib/runtime/pickle_runner.js')) {
30
+ cache.exports = pickleHook(cache.exports)
31
+ } else if (id.includes('/node_modules/@cucumber/cucumber/lib/runtime/test_case_runner.js')) {
32
+ cache.exports = testCaseHook(cache.exports)
33
+ }
34
+ }
35
+ } catch (e) {
36
+ // ignore error and let the tracer initialize anyway
37
+ }
38
+
39
+ tracer.init(options)
10
40
 
11
41
  tracer.use('fs', false)
12
42
 
package/ci/jest/env.js CHANGED
@@ -1,13 +1,26 @@
1
1
  const tracer = require('../../packages/dd-trace')
2
2
  const { ORIGIN_KEY } = require('../../packages/dd-trace/src/constants')
3
3
 
4
- tracer.init({
4
+ const options = {
5
5
  startupLogs: false,
6
- flushInterval: 400000,
7
6
  tags: {
8
7
  [ORIGIN_KEY]: 'ciapp-test'
9
8
  }
10
- })
9
+ }
10
+
11
+ if (process.env.DD_CIVISIBILITY_AGENTLESS_ENABLED && (process.env.DATADOG_API_KEY || process.env.DD_API_KEY)) {
12
+ tracer.init({
13
+ ...options,
14
+ experimental: {
15
+ exporter: 'datadog'
16
+ }
17
+ })
18
+ } else {
19
+ tracer.init({
20
+ ...options,
21
+ flushInterval: 400000
22
+ })
23
+ }
11
24
 
12
25
  tracer.use('fs', false)
13
26
 
@@ -1,6 +1,7 @@
1
1
  declare const exporters: {
2
2
  LOG: 'log',
3
- AGENT: 'agent'
3
+ AGENT: 'agent',
4
+ DATADOG: 'datadog'
4
5
  }
5
6
 
6
7
  export = exporters
package/ext/exporters.js CHANGED
@@ -1,5 +1,6 @@
1
1
  'use strict'
2
2
  module.exports = {
3
3
  LOG: 'log',
4
- AGENT: 'agent'
4
+ AGENT: 'agent',
5
+ DATADOG: 'datadog'
5
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "2.4.0",
3
+ "version": "2.5.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.2",
64
+ "@datadog/native-appsec": "^1.0.1",
65
65
  "@datadog/native-metrics": "^1.1.0",
66
66
  "@datadog/pprof": "^0.3.0",
67
67
  "@datadog/sketches-js": "^1.0.4",
@@ -69,6 +69,7 @@
69
69
  "crypto-randomuuid": "^1.0.0",
70
70
  "diagnostics_channel": "^1.1.0",
71
71
  "form-data": "^3.0.0",
72
+ "ignore": "^5.2.0",
72
73
  "import-in-the-middle": "^1.2.1",
73
74
  "koalas": "^1.0.2",
74
75
  "limiter": "^1.1.4",
@@ -10,6 +10,7 @@ require('./src/cucumber')
10
10
  require('./src/dns')
11
11
  require('./src/elasticsearch')
12
12
  require('./src/generic-pool')
13
+ require('./src/http')
13
14
  require('./src/ioredis')
14
15
  require('./src/memcached')
15
16
  require('./src/mongodb-core')
@@ -17,6 +18,7 @@ require('./src/mongoose')
17
18
  require('./src/mysql')
18
19
  require('./src/mysql2')
19
20
  require('./src/mocha')
21
+ require('./src/net')
20
22
  require('./src/pino')
21
23
  require('./src/pg')
22
24
  require('./src/promise')
@@ -38,7 +38,7 @@ addHook({ name: 'amqplib', file: 'lib/channel.js', versions: ['>=0.5'] }, channe
38
38
 
39
39
  function instrument (send, channel, args, method, fields, message) {
40
40
  if (!startCh.hasSubscribers) {
41
- return send.apply(this, arguments)
41
+ return send.apply(channel, args)
42
42
  }
43
43
  startCh.publish({ channel, method, fields, message })
44
44
 
@@ -10,6 +10,9 @@ const runStepStartCh = channel('ci:cucumber:run-step:start')
10
10
  const runStepEndCh = channel('ci:cucumber:run-step:end')
11
11
  const errorCh = channel('ci:cucumber:error')
12
12
 
13
+ // TODO: remove in a later major version
14
+ const patched = new WeakSet()
15
+
13
16
  function getStatusFromResult (result) {
14
17
  if (result.status === 1) {
15
18
  return { status: 'pass' }
@@ -37,6 +40,10 @@ function getStatusFromResultLatest (result) {
37
40
  }
38
41
 
39
42
  function wrapRun (pl, isLatestVersion) {
43
+ if (patched.has(pl)) return
44
+
45
+ patched.add(pl)
46
+
40
47
  shimmer.wrap(pl.prototype, 'run', run => function () {
41
48
  if (!runStartCh.hasSubscribers) {
42
49
  return run.apply(this, arguments)
@@ -93,26 +100,32 @@ function wrapRun (pl, isLatestVersion) {
93
100
  })
94
101
  }
95
102
 
96
- addHook({
97
- name: '@cucumber/cucumber',
98
- versions: ['7.0.0 - 7.2.1'],
99
- file: 'lib/runtime/pickle_runner.js'
100
- }, (PickleRunner) => {
103
+ function pickleHook (PickleRunner) {
101
104
  const pl = PickleRunner.default
102
105
 
103
106
  wrapRun(pl, false)
104
107
 
105
108
  return PickleRunner
106
- })
109
+ }
107
110
 
108
- addHook({
109
- name: '@cucumber/cucumber',
110
- versions: ['>=7.3.0'],
111
- file: 'lib/runtime/test_case_runner.js'
112
- }, (TestCaseRunner) => {
111
+ function testCaseHook (TestCaseRunner) {
113
112
  const pl = TestCaseRunner.default
114
113
 
115
114
  wrapRun(pl, true)
116
115
 
117
116
  return TestCaseRunner
118
- })
117
+ }
118
+
119
+ addHook({
120
+ name: '@cucumber/cucumber',
121
+ versions: ['7.0.0 - 7.2.1'],
122
+ file: 'lib/runtime/pickle_runner.js'
123
+ }, pickleHook)
124
+
125
+ addHook({
126
+ name: '@cucumber/cucumber',
127
+ versions: ['>=7.3.0'],
128
+ file: 'lib/runtime/test_case_runner.js'
129
+ }, testCaseHook)
130
+
131
+ module.exports = { pickleHook, testCaseHook }
@@ -0,0 +1,8 @@
1
+ const { addHook } = require('./helpers/instrument')
2
+
3
+ // No handler because this is only useful for testing.
4
+ // Cypress plugin does not patch any library.
5
+ addHook({
6
+ name: 'cypress',
7
+ versions: ['>=6.7.0']
8
+ })
@@ -0,0 +1,44 @@
1
+ 'use strict'
2
+
3
+ const path = require('path')
4
+ const iitm = require('../../../dd-trace/src/iitm')
5
+ const ritm = require('../../../dd-trace/src/ritm')
6
+
7
+ function Hook (modules, onrequire) {
8
+ if (!(this instanceof Hook)) return new Hook(modules, onrequire)
9
+
10
+ this._patched = Object.create(null)
11
+
12
+ const safeHook = (moduleExports, moduleName, moduleBaseDir) => {
13
+ const parts = [moduleBaseDir, moduleName].filter(v => v)
14
+ const filename = path.join(...parts)
15
+
16
+ if (this._patched[filename]) return moduleExports
17
+
18
+ this._patched[filename] = true
19
+
20
+ return onrequire(moduleExports, moduleName, moduleBaseDir)
21
+ }
22
+
23
+ this._ritmHook = ritm(modules, {}, safeHook)
24
+ this._iitmHook = iitm(modules, {}, (moduleExports, moduleName, moduleBaseDir) => {
25
+ // TODO: Move this logic to import-in-the-middle and only do it for CommonJS
26
+ // modules and not ESM. In the meantime, all the modules we instrument are
27
+ // CommonJS modules for which the default export is always moved to
28
+ // `default` anyway.
29
+ if (moduleExports && moduleExports.default) {
30
+ moduleExports.default = safeHook(moduleExports.default, moduleName, moduleBaseDir)
31
+ return moduleExports
32
+ } else {
33
+ return safeHook(moduleExports, moduleName, moduleBaseDir)
34
+ }
35
+ })
36
+ }
37
+
38
+ Hook.prototype.unhook = function () {
39
+ this._ritmHook.unhook()
40
+ this._iitmHook.unhook()
41
+ this._patched = Object.create(null)
42
+ }
43
+
44
+ module.exports = Hook
@@ -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 = {}
@@ -21,17 +20,21 @@ exports.channel = function channel (name) {
21
20
 
22
21
  exports.addHook = function addHook ({ name, versions, file }, hook) {
23
22
  const fullFilename = filename(name, file)
24
- const loaderHook = (moduleExports, moduleName, moduleBaseDir) => {
23
+
24
+ Hook([name], (moduleExports, moduleName, moduleBaseDir) => {
25
25
  moduleName = moduleName.replace(pathSepExpr, '/')
26
- const moduleVersion = getVersion(moduleBaseDir)
27
- if (moduleName !== fullFilename || !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.name, instrumentation.file)}`)) 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')