dd-trace 2.4.2 → 2.6.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 +6 -0
- package/ci/jest/env.js +16 -3
- package/ext/exporters.d.ts +2 -1
- package/ext/exporters.js +2 -1
- package/index.d.ts +17 -1
- package/package.json +5 -4
- package/packages/datadog-instrumentations/index.js +1 -0
- package/packages/datadog-instrumentations/src/cypress.js +8 -0
- package/packages/datadog-instrumentations/src/jest.js +170 -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 +16 -16
- package/packages/datadog-plugin-cypress/src/index.js +10 -5
- package/packages/datadog-plugin-cypress/src/plugin.js +18 -17
- package/packages/datadog-plugin-fs/src/index.js +2 -0
- package/packages/datadog-plugin-http/src/server.js +0 -8
- package/packages/datadog-plugin-jest/src/index.js +101 -3
- package/packages/datadog-plugin-jest/src/util.js +1 -29
- package/packages/datadog-plugin-mocha/src/index.js +14 -15
- package/packages/dd-trace/lib/version.js +1 -1
- package/packages/dd-trace/src/appsec/callbacks/ddwaf.js +29 -12
- package/packages/dd-trace/src/appsec/index.js +7 -3
- package/packages/dd-trace/src/appsec/recommended.json +15 -5
- package/packages/dd-trace/src/appsec/reporter.js +29 -3
- package/packages/dd-trace/src/appsec/rule_manager.js +2 -2
- 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 +33 -4
- 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/format.js +9 -5
- package/packages/dd-trace/src/instrumenter.js +3 -0
- package/packages/dd-trace/src/pkg.js +11 -6
- package/packages/dd-trace/src/plugins/util/test.js +79 -1
- package/packages/dd-trace/src/plugins/util/web.js +11 -10
- package/packages/dd-trace/src/profiling/exporters/agent.js +1 -1
- package/packages/dd-trace/src/profiling/profilers/cpu.js +1 -1
- package/packages/dd-trace/src/proxy.js +2 -0
- package/packages/dd-trace/src/span_processor.js +4 -1
- package/packages/dd-trace/src/telemetry.js +187 -0
- package/scripts/install_plugin_modules.js +1 -0
- package/packages/datadog-plugin-jest/src/jest-environment.js +0 -272
- package/packages/datadog-plugin-jest/src/jest-jasmine2.js +0 -185
- package/packages/dd-trace/src/exporters/agent/request.js +0 -86
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
const path = require('path')
|
|
2
|
+
const fs = require('fs')
|
|
3
|
+
|
|
4
|
+
const ignore = require('ignore')
|
|
2
5
|
|
|
3
6
|
const { getGitMetadata } = require('./git')
|
|
4
7
|
const { getUserProviderGitMetadata } = require('./user-provided-git')
|
|
@@ -16,6 +19,10 @@ const {
|
|
|
16
19
|
} = require('./tags')
|
|
17
20
|
const id = require('../../id')
|
|
18
21
|
|
|
22
|
+
const { SPAN_TYPE, RESOURCE_NAME, SAMPLING_PRIORITY } = require('../../../../../ext/tags')
|
|
23
|
+
const { SAMPLING_RULE_DECISION } = require('../../constants')
|
|
24
|
+
const { AUTO_KEEP } = require('../../../../../ext/priority')
|
|
25
|
+
|
|
19
26
|
const TEST_FRAMEWORK = 'test.framework'
|
|
20
27
|
const TEST_FRAMEWORK_VERSION = 'test.framework_version'
|
|
21
28
|
const TEST_TYPE = 'test.type'
|
|
@@ -25,6 +32,7 @@ const TEST_STATUS = 'test.status'
|
|
|
25
32
|
const TEST_PARAMETERS = 'test.parameters'
|
|
26
33
|
const TEST_SKIP_REASON = 'test.skip_reason'
|
|
27
34
|
const TEST_IS_RUM_ACTIVE = 'test.is_rum_active'
|
|
35
|
+
const TEST_CODE_OWNERS = 'test.codeowners'
|
|
28
36
|
|
|
29
37
|
const ERROR_TYPE = 'error.type'
|
|
30
38
|
const ERROR_MESSAGE = 'error.msg'
|
|
@@ -35,6 +43,7 @@ const CI_APP_ORIGIN = 'ciapp-test'
|
|
|
35
43
|
const JEST_TEST_RUNNER = 'test.jest.test_runner'
|
|
36
44
|
|
|
37
45
|
module.exports = {
|
|
46
|
+
TEST_CODE_OWNERS,
|
|
38
47
|
TEST_FRAMEWORK,
|
|
39
48
|
TEST_FRAMEWORK_VERSION,
|
|
40
49
|
JEST_TEST_RUNNER,
|
|
@@ -53,7 +62,10 @@ module.exports = {
|
|
|
53
62
|
getTestParametersString,
|
|
54
63
|
finishAllTraceSpans,
|
|
55
64
|
getTestParentSpan,
|
|
56
|
-
getTestSuitePath
|
|
65
|
+
getTestSuitePath,
|
|
66
|
+
getCodeOwnersFileEntries,
|
|
67
|
+
getCodeOwnersForFilename,
|
|
68
|
+
getTestCommonTags
|
|
57
69
|
}
|
|
58
70
|
|
|
59
71
|
function getTestEnvironmentMetadata (testFramework, config) {
|
|
@@ -127,6 +139,20 @@ function getTestParentSpan (tracer) {
|
|
|
127
139
|
'x-datadog-parent-id': '0000000000000000'
|
|
128
140
|
})
|
|
129
141
|
}
|
|
142
|
+
|
|
143
|
+
function getTestCommonTags (name, suite, version) {
|
|
144
|
+
return {
|
|
145
|
+
[SPAN_TYPE]: 'test',
|
|
146
|
+
[TEST_TYPE]: 'test',
|
|
147
|
+
[SAMPLING_RULE_DECISION]: 1,
|
|
148
|
+
[SAMPLING_PRIORITY]: AUTO_KEEP,
|
|
149
|
+
[TEST_NAME]: name,
|
|
150
|
+
[TEST_SUITE]: suite,
|
|
151
|
+
[RESOURCE_NAME]: `${suite}.${name}`,
|
|
152
|
+
[TEST_FRAMEWORK_VERSION]: version
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
130
156
|
/**
|
|
131
157
|
* We want to make sure that test suites are reported the same way for
|
|
132
158
|
* every OS, so we replace `path.sep` by `/`
|
|
@@ -140,3 +166,55 @@ function getTestSuitePath (testSuiteAbsolutePath, sourceRoot) {
|
|
|
140
166
|
|
|
141
167
|
return testSuitePath.replace(path.sep, '/')
|
|
142
168
|
}
|
|
169
|
+
|
|
170
|
+
const POSSIBLE_CODEOWNERS_LOCATIONS = [
|
|
171
|
+
'CODEOWNERS',
|
|
172
|
+
'.github/CODEOWNERS',
|
|
173
|
+
'docs/CODEOWNERS',
|
|
174
|
+
'.gitlab/CODEOWNERS'
|
|
175
|
+
]
|
|
176
|
+
|
|
177
|
+
function getCodeOwnersFileEntries (rootDir = process.cwd()) {
|
|
178
|
+
let codeOwnersContent
|
|
179
|
+
|
|
180
|
+
POSSIBLE_CODEOWNERS_LOCATIONS.forEach(location => {
|
|
181
|
+
try {
|
|
182
|
+
codeOwnersContent = fs.readFileSync(`${rootDir}/${location}`).toString()
|
|
183
|
+
} catch (e) {
|
|
184
|
+
// retry with next path
|
|
185
|
+
}
|
|
186
|
+
})
|
|
187
|
+
if (!codeOwnersContent) {
|
|
188
|
+
return null
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const entries = []
|
|
192
|
+
const lines = codeOwnersContent.split('\n')
|
|
193
|
+
|
|
194
|
+
for (const line of lines) {
|
|
195
|
+
const [content] = line.split('#')
|
|
196
|
+
const trimmed = content.trim()
|
|
197
|
+
if (trimmed === '') continue
|
|
198
|
+
const [pattern, ...owners] = trimmed.split(/\s+/)
|
|
199
|
+
entries.push({ pattern, owners })
|
|
200
|
+
}
|
|
201
|
+
// Reverse because rules defined last take precedence
|
|
202
|
+
return entries.reverse()
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function getCodeOwnersForFilename (filename, entries) {
|
|
206
|
+
if (!entries) {
|
|
207
|
+
return null
|
|
208
|
+
}
|
|
209
|
+
for (const entry of entries) {
|
|
210
|
+
try {
|
|
211
|
+
const isResponsible = ignore().add(entry.pattern).ignores(filename)
|
|
212
|
+
if (isResponsible) {
|
|
213
|
+
return JSON.stringify(entry.owners)
|
|
214
|
+
}
|
|
215
|
+
} catch (e) {
|
|
216
|
+
return null
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return null
|
|
220
|
+
}
|
|
@@ -69,6 +69,17 @@ const web = {
|
|
|
69
69
|
context.span = span
|
|
70
70
|
context.res = res
|
|
71
71
|
|
|
72
|
+
if (!config.filter(req.url)) {
|
|
73
|
+
span.setTag(MANUAL_DROP, true)
|
|
74
|
+
span.context()._trace.isRecording = false
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (config.service) {
|
|
78
|
+
span.setTag(SERVICE_NAME, config.service)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
analyticsSampler.sample(span, config.measured, true)
|
|
82
|
+
|
|
72
83
|
return span
|
|
73
84
|
},
|
|
74
85
|
wrap (req) {
|
|
@@ -83,16 +94,6 @@ const web = {
|
|
|
83
94
|
instrument (tracer, config, req, res, name, callback) {
|
|
84
95
|
const span = this.startSpan(tracer, config, req, res, name)
|
|
85
96
|
|
|
86
|
-
if (!config.filter(req.url)) {
|
|
87
|
-
span.setTag(MANUAL_DROP, true)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (config.service) {
|
|
91
|
-
span.setTag(SERVICE_NAME, config.service)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
analyticsSampler.sample(span, config.measured, true)
|
|
95
|
-
|
|
96
97
|
this.wrap(req)
|
|
97
98
|
|
|
98
99
|
return callback && tracer.scope().activate(span, () => callback(span))
|
|
@@ -5,7 +5,7 @@ const { request } = require('http')
|
|
|
5
5
|
const FormData = require('form-data')
|
|
6
6
|
|
|
7
7
|
// TODO: avoid using dd-trace internals. Make this a separate module?
|
|
8
|
-
const docker = require('../../exporters/
|
|
8
|
+
const docker = require('../../exporters/common/docker')
|
|
9
9
|
const version = require('../../../lib/version')
|
|
10
10
|
|
|
11
11
|
const containerId = docker.id()
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
class NativeCpuProfiler {
|
|
4
4
|
constructor (options = {}) {
|
|
5
5
|
this.type = 'wall'
|
|
6
|
-
this._samplingInterval = options.samplingInterval ||
|
|
6
|
+
this._samplingInterval = options.samplingInterval || 1e6 / 99 // 99hz
|
|
7
7
|
this._mapper = undefined
|
|
8
8
|
this._pprof = undefined
|
|
9
9
|
}
|
|
@@ -10,6 +10,7 @@ const metrics = require('./metrics')
|
|
|
10
10
|
const log = require('./log')
|
|
11
11
|
const { isFalse } = require('./util')
|
|
12
12
|
const { setStartupLogInstrumenter } = require('./startup-log')
|
|
13
|
+
const telemetry = require('./telemetry')
|
|
13
14
|
|
|
14
15
|
const noop = new NoopTracer()
|
|
15
16
|
|
|
@@ -63,6 +64,7 @@ class Tracer extends BaseTracer {
|
|
|
63
64
|
this._instrumenter.enable(config)
|
|
64
65
|
this._pluginManager.configure(config)
|
|
65
66
|
setStartupLogInstrumenter(this._instrumenter)
|
|
67
|
+
telemetry.start(config, this._instrumenter, this._pluginManager)
|
|
66
68
|
}
|
|
67
69
|
} catch (e) {
|
|
68
70
|
log.error(e)
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const tracerVersion = require('../lib/version')
|
|
4
|
+
const pkg = require('./pkg')
|
|
5
|
+
const containerId = require('./exporters/common/docker').id()
|
|
6
|
+
const requirePackageJson = require('./require-package-json')
|
|
7
|
+
const path = require('path')
|
|
8
|
+
const os = require('os')
|
|
9
|
+
const request = require('./exporters/common/request')
|
|
10
|
+
|
|
11
|
+
let config
|
|
12
|
+
let instrumenter
|
|
13
|
+
let pluginManager
|
|
14
|
+
|
|
15
|
+
let seqId = 0
|
|
16
|
+
let application
|
|
17
|
+
let host
|
|
18
|
+
let interval
|
|
19
|
+
const sentIntegrations = new Set()
|
|
20
|
+
|
|
21
|
+
function getIntegrations () {
|
|
22
|
+
const newIntegrations = []
|
|
23
|
+
for (const plugin of instrumenter._instrumented.keys()) {
|
|
24
|
+
if (sentIntegrations.has(plugin.name)) {
|
|
25
|
+
continue
|
|
26
|
+
}
|
|
27
|
+
newIntegrations.push({
|
|
28
|
+
name: plugin.name,
|
|
29
|
+
enabled: true,
|
|
30
|
+
auto_enabled: true
|
|
31
|
+
})
|
|
32
|
+
sentIntegrations.add(plugin.name)
|
|
33
|
+
}
|
|
34
|
+
for (const pluginName in pluginManager._pluginsByName) {
|
|
35
|
+
if (sentIntegrations.has(pluginName)) {
|
|
36
|
+
continue
|
|
37
|
+
}
|
|
38
|
+
newIntegrations.push({
|
|
39
|
+
name: pluginName,
|
|
40
|
+
enabled: pluginManager._pluginsByName[pluginName]._enabled,
|
|
41
|
+
auto_enabled: true
|
|
42
|
+
})
|
|
43
|
+
sentIntegrations.add(pluginName)
|
|
44
|
+
}
|
|
45
|
+
return newIntegrations
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getDependencies () {
|
|
49
|
+
const deps = []
|
|
50
|
+
const { dependencies } = pkg
|
|
51
|
+
if (!dependencies) {
|
|
52
|
+
return deps
|
|
53
|
+
}
|
|
54
|
+
const rootDir = pkg.findRoot()
|
|
55
|
+
for (const [name, version] of Object.entries(dependencies)) {
|
|
56
|
+
const dep = { name }
|
|
57
|
+
try {
|
|
58
|
+
dep.version = requirePackageJson(
|
|
59
|
+
path.join(rootDir, 'node_modules', name.replace('/', path.sep))
|
|
60
|
+
).version
|
|
61
|
+
} catch (e) {
|
|
62
|
+
dep.version = version
|
|
63
|
+
}
|
|
64
|
+
deps.push(dep)
|
|
65
|
+
}
|
|
66
|
+
return deps
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function flatten (input, result = [], prefix = [], traversedObjects = null) {
|
|
70
|
+
traversedObjects = traversedObjects || new WeakSet()
|
|
71
|
+
if (traversedObjects.has(input)) {
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
traversedObjects.add(input)
|
|
75
|
+
for (const [key, value] of Object.entries(input)) {
|
|
76
|
+
if (typeof value === 'object' && value !== null) {
|
|
77
|
+
flatten(value, result, [...prefix, key], traversedObjects)
|
|
78
|
+
} else {
|
|
79
|
+
result.push({ name: [...prefix, key].join('.'), value })
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return result
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function appStarted () {
|
|
86
|
+
return {
|
|
87
|
+
integrations: getIntegrations(),
|
|
88
|
+
dependencies: getDependencies(),
|
|
89
|
+
configuration: flatten(config),
|
|
90
|
+
additional_payload: []
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function onBeforeExit () {
|
|
95
|
+
process.removeListener('beforeExit', onBeforeExit)
|
|
96
|
+
sendData('app-closing')
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function createAppObject () {
|
|
100
|
+
return {
|
|
101
|
+
service_name: config.service,
|
|
102
|
+
env: config.env,
|
|
103
|
+
service_version: config.version,
|
|
104
|
+
tracer_version: tracerVersion,
|
|
105
|
+
language_name: 'nodejs',
|
|
106
|
+
language_version: process.versions.node
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function createHostObject () {
|
|
111
|
+
return {
|
|
112
|
+
hostname: os.hostname(), // TODO is this enough?
|
|
113
|
+
container_id: containerId
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function sendData (reqType, payload = {}) {
|
|
118
|
+
const {
|
|
119
|
+
hostname,
|
|
120
|
+
port
|
|
121
|
+
} = config
|
|
122
|
+
const options = {
|
|
123
|
+
hostname,
|
|
124
|
+
port,
|
|
125
|
+
method: 'POST',
|
|
126
|
+
path: '/telemetry/proxy/api/v2/apmtelemetry',
|
|
127
|
+
headers: {
|
|
128
|
+
'content-type': 'application/json',
|
|
129
|
+
'dd-telemetry-api-version': 'v1',
|
|
130
|
+
'dd-telemetry-request-type': reqType
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const data = JSON.stringify({
|
|
134
|
+
api_version: 'v1',
|
|
135
|
+
request_type: reqType,
|
|
136
|
+
tracer_time: Math.floor(Date.now() / 1000),
|
|
137
|
+
runtime_id: config.tags['runtime-id'],
|
|
138
|
+
seq_id: ++seqId,
|
|
139
|
+
payload,
|
|
140
|
+
application,
|
|
141
|
+
host
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
request(data, options, true, () => {
|
|
145
|
+
// ignore errors
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function start (aConfig, theInstrumenter, thePluginManager) {
|
|
150
|
+
if (!aConfig.telemetryEnabled) {
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
config = aConfig
|
|
154
|
+
instrumenter = theInstrumenter
|
|
155
|
+
pluginManager = thePluginManager
|
|
156
|
+
application = createAppObject()
|
|
157
|
+
host = createHostObject()
|
|
158
|
+
sendData('app-started', appStarted())
|
|
159
|
+
interval = setInterval(() => sendData('app-heartbeat'), 60000)
|
|
160
|
+
interval.unref()
|
|
161
|
+
process.on('beforeExit', onBeforeExit)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function stop () {
|
|
165
|
+
if (!config) {
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
clearInterval(interval)
|
|
169
|
+
process.removeListener('beforeExit', onBeforeExit)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function updateIntegrations () {
|
|
173
|
+
if (!config || !config.telemetryEnabled) {
|
|
174
|
+
return
|
|
175
|
+
}
|
|
176
|
+
const integrations = getIntegrations()
|
|
177
|
+
if (integrations.length === 0) {
|
|
178
|
+
return
|
|
179
|
+
}
|
|
180
|
+
sendData('app-integrations-change', { integrations })
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
module.exports = {
|
|
184
|
+
start,
|
|
185
|
+
stop,
|
|
186
|
+
updateIntegrations
|
|
187
|
+
}
|
|
@@ -186,6 +186,7 @@ const requirePackageJson = require('${requirePackageJsonPath}')
|
|
|
186
186
|
|
|
187
187
|
module.exports = {
|
|
188
188
|
get (id) { return require(id || '${name}') },
|
|
189
|
+
getPath (id) { return require.resolve(id || '${name}' ) },
|
|
189
190
|
version () { return requirePackageJson('${name}', module).version }
|
|
190
191
|
}
|
|
191
192
|
`
|
|
@@ -1,272 +0,0 @@
|
|
|
1
|
-
const { promisify } = require('util')
|
|
2
|
-
|
|
3
|
-
const { RESOURCE_NAME } = require('../../../ext/tags')
|
|
4
|
-
const {
|
|
5
|
-
TEST_NAME,
|
|
6
|
-
TEST_SUITE,
|
|
7
|
-
TEST_STATUS,
|
|
8
|
-
TEST_FRAMEWORK_VERSION,
|
|
9
|
-
JEST_TEST_RUNNER,
|
|
10
|
-
ERROR_MESSAGE,
|
|
11
|
-
ERROR_TYPE,
|
|
12
|
-
TEST_PARAMETERS,
|
|
13
|
-
CI_APP_ORIGIN,
|
|
14
|
-
getTestEnvironmentMetadata,
|
|
15
|
-
getTestParametersString,
|
|
16
|
-
finishAllTraceSpans,
|
|
17
|
-
getTestSuitePath
|
|
18
|
-
} = require('../../dd-trace/src/plugins/util/test')
|
|
19
|
-
const {
|
|
20
|
-
getFormattedJestTestParameters,
|
|
21
|
-
getTestSpanTags,
|
|
22
|
-
setSuppressedErrors
|
|
23
|
-
} = require('./util')
|
|
24
|
-
|
|
25
|
-
const originals = new WeakMap()
|
|
26
|
-
|
|
27
|
-
function getVmContext (environment) {
|
|
28
|
-
if (typeof environment.getVmContext === 'function') {
|
|
29
|
-
return environment.getVmContext()
|
|
30
|
-
}
|
|
31
|
-
return null
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function wrapEnvironment (BaseEnvironment) {
|
|
35
|
-
return class DatadogJestEnvironment extends BaseEnvironment {
|
|
36
|
-
constructor (config, context) {
|
|
37
|
-
super(config, context)
|
|
38
|
-
this.testSuite = getTestSuitePath(context.testPath, config.rootDir)
|
|
39
|
-
this.testSpansByTestName = {}
|
|
40
|
-
this.originalTestFnByTestName = {}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function createWrapTeardown (tracer, instrumenter) {
|
|
46
|
-
return function wrapTeardown (teardown) {
|
|
47
|
-
return async function teardownWithTrace () {
|
|
48
|
-
instrumenter.unwrap(this.global.test, 'each')
|
|
49
|
-
nameToParams = {}
|
|
50
|
-
// for jest-jasmine2
|
|
51
|
-
if (this.global.jasmine) {
|
|
52
|
-
instrumenter.unwrap(this.global.jasmine.Spec.prototype, 'onException')
|
|
53
|
-
instrumenter.unwrap(this.global, 'it')
|
|
54
|
-
instrumenter.unwrap(this.global, 'fit')
|
|
55
|
-
instrumenter.unwrap(this.global, 'xit')
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
instrumenter.unwrap(this.global.test, 'each')
|
|
59
|
-
|
|
60
|
-
return teardown.apply(this, arguments).finally(() => {
|
|
61
|
-
return new Promise(resolve => tracer._exporter._writer.flush(resolve))
|
|
62
|
-
})
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
let nameToParams = {}
|
|
68
|
-
|
|
69
|
-
const isTimeout = (event) => {
|
|
70
|
-
return event.error &&
|
|
71
|
-
typeof event.error === 'string' &&
|
|
72
|
-
event.error.startsWith('Exceeded timeout')
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function createHandleTestEvent (tracer, testEnvironmentMetadata, instrumenter) {
|
|
76
|
-
return async function handleTestEventWithTrace (event) {
|
|
77
|
-
if (event.name === 'test_retry') {
|
|
78
|
-
let testName = event.test && event.test.name
|
|
79
|
-
const context = getVmContext(this)
|
|
80
|
-
if (context) {
|
|
81
|
-
const { currentTestName } = context.expect.getState()
|
|
82
|
-
testName = currentTestName
|
|
83
|
-
}
|
|
84
|
-
// If it's a retry, we restore the original test function so that it is not wrapped again
|
|
85
|
-
if (this.originalTestFnByTestName[testName]) {
|
|
86
|
-
event.test.fn = this.originalTestFnByTestName[testName]
|
|
87
|
-
}
|
|
88
|
-
return
|
|
89
|
-
}
|
|
90
|
-
if (event.name === 'test_fn_failure') {
|
|
91
|
-
if (!isTimeout(event)) {
|
|
92
|
-
return
|
|
93
|
-
}
|
|
94
|
-
const context = getVmContext(this)
|
|
95
|
-
if (context) {
|
|
96
|
-
const { currentTestName } = context.expect.getState()
|
|
97
|
-
const testSpan = this.testSpansByTestName[`${currentTestName}_${event.test.invocations}`]
|
|
98
|
-
if (testSpan) {
|
|
99
|
-
testSpan.setTag(ERROR_TYPE, 'Timeout')
|
|
100
|
-
testSpan.setTag(ERROR_MESSAGE, event.error)
|
|
101
|
-
testSpan.setTag(TEST_STATUS, 'fail')
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
return
|
|
105
|
-
}
|
|
106
|
-
if (event.name === 'setup') {
|
|
107
|
-
instrumenter.wrap(this.global.test, 'each', function (original) {
|
|
108
|
-
return function () {
|
|
109
|
-
const testParameters = getFormattedJestTestParameters(arguments)
|
|
110
|
-
const eachBind = original.apply(this, arguments)
|
|
111
|
-
return function () {
|
|
112
|
-
const [testName] = arguments
|
|
113
|
-
nameToParams[testName] = testParameters
|
|
114
|
-
return eachBind.apply(this, arguments)
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
})
|
|
118
|
-
return
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (event.name !== 'test_skip' &&
|
|
122
|
-
event.name !== 'test_todo' &&
|
|
123
|
-
event.name !== 'test_start' &&
|
|
124
|
-
event.name !== 'hook_failure') {
|
|
125
|
-
return
|
|
126
|
-
}
|
|
127
|
-
// for hook_failure events the test entry might not be defined, because the hook
|
|
128
|
-
// is not necessarily associated to a test:
|
|
129
|
-
if (!event.test) {
|
|
130
|
-
return
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const { childOf, commonSpanTags } = getTestSpanTags(tracer, testEnvironmentMetadata)
|
|
134
|
-
|
|
135
|
-
let testName = event.test.name
|
|
136
|
-
const context = getVmContext(this)
|
|
137
|
-
|
|
138
|
-
if (context) {
|
|
139
|
-
const { currentTestName } = context.expect.getState()
|
|
140
|
-
testName = currentTestName
|
|
141
|
-
}
|
|
142
|
-
const spanTags = {
|
|
143
|
-
...commonSpanTags,
|
|
144
|
-
[TEST_NAME]: testName,
|
|
145
|
-
[TEST_SUITE]: this.testSuite,
|
|
146
|
-
[TEST_FRAMEWORK_VERSION]: tracer._version,
|
|
147
|
-
[JEST_TEST_RUNNER]: 'jest-circus'
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const testParametersString = getTestParametersString(nameToParams, event.test.name)
|
|
151
|
-
if (testParametersString) {
|
|
152
|
-
spanTags[TEST_PARAMETERS] = testParametersString
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const resource = `${this.testSuite}.${testName}`
|
|
156
|
-
if (event.name === 'test_skip' || event.name === 'test_todo') {
|
|
157
|
-
const testSpan = tracer.startSpan(
|
|
158
|
-
'jest.test',
|
|
159
|
-
{
|
|
160
|
-
childOf,
|
|
161
|
-
tags: {
|
|
162
|
-
...spanTags,
|
|
163
|
-
[RESOURCE_NAME]: resource,
|
|
164
|
-
[TEST_STATUS]: 'skip'
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
)
|
|
168
|
-
testSpan.context()._trace.origin = CI_APP_ORIGIN
|
|
169
|
-
testSpan.finish()
|
|
170
|
-
return
|
|
171
|
-
}
|
|
172
|
-
if (event.name === 'hook_failure') {
|
|
173
|
-
const testSpan = tracer.startSpan(
|
|
174
|
-
'jest.test',
|
|
175
|
-
{
|
|
176
|
-
childOf,
|
|
177
|
-
tags: {
|
|
178
|
-
...spanTags,
|
|
179
|
-
[RESOURCE_NAME]: resource,
|
|
180
|
-
[TEST_STATUS]: 'fail'
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
)
|
|
184
|
-
testSpan.context()._trace.origin = CI_APP_ORIGIN
|
|
185
|
-
if (event.test.errors && event.test.errors.length) {
|
|
186
|
-
const error = new Error(event.test.errors[0][0])
|
|
187
|
-
error.stack = event.test.errors[0][1].stack
|
|
188
|
-
testSpan.setTag('error', error)
|
|
189
|
-
}
|
|
190
|
-
testSpan.finish()
|
|
191
|
-
return
|
|
192
|
-
}
|
|
193
|
-
// event.name === test_start at this point
|
|
194
|
-
const environment = this
|
|
195
|
-
environment.originalTestFnByTestName[testName] = event.test.fn
|
|
196
|
-
|
|
197
|
-
let specFunction = event.test.fn
|
|
198
|
-
if (specFunction.length) {
|
|
199
|
-
specFunction = promisify(specFunction)
|
|
200
|
-
}
|
|
201
|
-
event.test.fn = tracer.wrap(
|
|
202
|
-
'jest.test',
|
|
203
|
-
{
|
|
204
|
-
type: 'test',
|
|
205
|
-
childOf,
|
|
206
|
-
resource,
|
|
207
|
-
tags: spanTags
|
|
208
|
-
},
|
|
209
|
-
async () => {
|
|
210
|
-
let result
|
|
211
|
-
const testSpan = tracer.scope().active()
|
|
212
|
-
environment.testSpansByTestName[`${testName}_${event.test.invocations}`] = testSpan
|
|
213
|
-
testSpan.context()._trace.origin = CI_APP_ORIGIN
|
|
214
|
-
try {
|
|
215
|
-
result = await specFunction()
|
|
216
|
-
// it may have been set already if the test timed out
|
|
217
|
-
let suppressedErrors = []
|
|
218
|
-
const context = getVmContext(environment)
|
|
219
|
-
if (context) {
|
|
220
|
-
suppressedErrors = context.expect.getState().suppressedErrors
|
|
221
|
-
}
|
|
222
|
-
setSuppressedErrors(suppressedErrors, testSpan)
|
|
223
|
-
if (!testSpan._spanContext._tags[TEST_STATUS]) {
|
|
224
|
-
testSpan.setTag(TEST_STATUS, 'pass')
|
|
225
|
-
}
|
|
226
|
-
} catch (error) {
|
|
227
|
-
testSpan.setTag(TEST_STATUS, 'fail')
|
|
228
|
-
testSpan.setTag('error', error)
|
|
229
|
-
throw error
|
|
230
|
-
} finally {
|
|
231
|
-
finishAllTraceSpans(testSpan)
|
|
232
|
-
}
|
|
233
|
-
return result
|
|
234
|
-
}
|
|
235
|
-
)
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
function patch (Environment, tracer, config) {
|
|
240
|
-
const testEnvironmentMetadata = getTestEnvironmentMetadata('jest', config)
|
|
241
|
-
const proto = Environment.prototype
|
|
242
|
-
|
|
243
|
-
this.wrap(proto, 'teardown', createWrapTeardown(tracer, this))
|
|
244
|
-
|
|
245
|
-
const newHandleTestEvent = createHandleTestEvent(tracer, testEnvironmentMetadata, this)
|
|
246
|
-
originals.set(newHandleTestEvent, proto.handleTestEvent)
|
|
247
|
-
proto.handleTestEvent = newHandleTestEvent
|
|
248
|
-
|
|
249
|
-
return wrapEnvironment(Environment)
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
function unpatch (Environment) {
|
|
253
|
-
const proto = Environment.prototype
|
|
254
|
-
|
|
255
|
-
this.unwrap(Environment.prototype, 'teardown')
|
|
256
|
-
proto.handleTestEvent = originals.get(proto.handleTestEvent)
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
module.exports = [
|
|
260
|
-
{
|
|
261
|
-
name: 'jest-environment-node',
|
|
262
|
-
versions: ['>=24.8.0'],
|
|
263
|
-
patch,
|
|
264
|
-
unpatch
|
|
265
|
-
},
|
|
266
|
-
{
|
|
267
|
-
name: 'jest-environment-jsdom',
|
|
268
|
-
versions: ['>=24.8.0'],
|
|
269
|
-
patch,
|
|
270
|
-
unpatch
|
|
271
|
-
}
|
|
272
|
-
]
|