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.
- package/LICENSE-3rdparty.csv +1 -0
- package/ci/init.js +32 -2
- package/ci/jest/env.js +16 -3
- package/ext/exporters.d.ts +2 -1
- package/ext/exporters.js +2 -1
- package/package.json +3 -2
- package/packages/datadog-instrumentations/index.js +2 -0
- package/packages/datadog-instrumentations/src/amqplib.js +1 -1
- package/packages/datadog-instrumentations/src/cucumber.js +25 -12
- package/packages/datadog-instrumentations/src/cypress.js +8 -0
- package/packages/datadog-instrumentations/src/helpers/hook.js +44 -0
- package/packages/datadog-instrumentations/src/helpers/instrument.js +30 -57
- package/packages/datadog-instrumentations/src/http/client.js +170 -0
- package/packages/datadog-instrumentations/src/http/server.js +61 -0
- package/packages/datadog-instrumentations/src/http.js +4 -0
- package/packages/datadog-instrumentations/src/mocha.js +28 -11
- package/packages/datadog-instrumentations/src/net.js +117 -0
- package/packages/datadog-plugin-aws-sdk/src/helpers.js +4 -4
- package/packages/datadog-plugin-aws-sdk/src/index.js +1 -1
- package/packages/datadog-plugin-cucumber/src/index.js +24 -12
- package/packages/datadog-plugin-cypress/src/index.js +10 -5
- package/packages/datadog-plugin-cypress/src/plugin.js +13 -1
- package/packages/datadog-plugin-elasticsearch/src/index.js +4 -2
- package/packages/datadog-plugin-http/src/client.js +112 -252
- package/packages/datadog-plugin-http/src/index.js +29 -3
- package/packages/datadog-plugin-http/src/server.js +54 -32
- package/packages/datadog-plugin-jest/src/jest-environment.js +3 -3
- package/packages/datadog-plugin-mocha/src/index.js +10 -1
- package/packages/datadog-plugin-net/src/index.js +65 -121
- package/packages/datadog-plugin-next/src/index.js +10 -10
- package/packages/dd-trace/lib/version.js +1 -1
- package/packages/dd-trace/src/appsec/callbacks/ddwaf.js +3 -1
- package/packages/dd-trace/src/appsec/recommended.json +119 -210
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +32 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +51 -0
- package/packages/dd-trace/src/config.js +8 -1
- package/packages/dd-trace/src/encode/0.4.js +0 -1
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +193 -0
- package/packages/dd-trace/src/encode/tags-processors.js +116 -0
- package/packages/dd-trace/src/exporter.js +3 -0
- package/packages/dd-trace/src/exporters/agent/index.js +1 -1
- package/packages/dd-trace/src/exporters/agent/writer.js +7 -32
- package/packages/dd-trace/src/exporters/{agent → common}/docker.js +0 -0
- package/packages/dd-trace/src/exporters/common/request.js +83 -0
- package/packages/dd-trace/src/exporters/common/writer.js +36 -0
- package/packages/dd-trace/src/exporters/{agent/scheduler.js → scheduler.js} +0 -0
- package/packages/dd-trace/src/iitm.js +5 -1
- package/packages/dd-trace/src/instrumenter.js +3 -0
- package/packages/dd-trace/src/loader.js +6 -4
- package/packages/dd-trace/src/opentracing/span.js +34 -0
- package/packages/dd-trace/src/pkg.js +11 -6
- package/packages/dd-trace/src/plugin_manager.js +4 -0
- package/packages/dd-trace/src/plugins/plugin.js +3 -1
- package/packages/dd-trace/src/plugins/util/test.js +60 -1
- package/packages/dd-trace/src/plugins/util/web.js +99 -93
- package/packages/dd-trace/src/profiling/exporters/agent.js +1 -1
- package/packages/dd-trace/src/proxy.js +2 -0
- package/packages/dd-trace/src/ritm.js +60 -25
- package/packages/dd-trace/src/telemetry.js +187 -0
- package/packages/dd-trace/src/exporters/agent/request.js +0 -86
package/LICENSE-3rdparty.csv
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
package/ext/exporters.d.ts
CHANGED
package/ext/exporters.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dd-trace",
|
|
3
|
-
"version": "2.
|
|
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.
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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,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
|
|
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
|
-
|
|
23
|
+
|
|
24
|
+
Hook([name], (moduleExports, moduleName, moduleBaseDir) => {
|
|
25
25
|
moduleName = moduleName.replace(pathSepExpr, '/')
|
|
26
|
-
|
|
27
|
-
if (moduleName !== fullFilename || !matchVersion(
|
|
26
|
+
|
|
27
|
+
if (moduleName !== fullFilename || !matchVersion(getVersion(moduleBaseDir), versions)) {
|
|
28
28
|
return moduleExports
|
|
29
29
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
//
|
|
52
|
-
|
|
53
|
-
|
|
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
|
|
102
|
-
|
|
103
|
-
|
|
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
|
|
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
|
+
}
|