dd-trace 3.12.1 → 3.13.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 (43) hide show
  1. package/ci/init.js +1 -0
  2. package/index.d.ts +50 -0
  3. package/package.json +1 -1
  4. package/packages/datadog-instrumentations/src/fs.js +357 -0
  5. package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -0
  6. package/packages/datadog-instrumentations/src/jest.js +11 -1
  7. package/packages/datadog-instrumentations/src/mocha.js +3 -2
  8. package/packages/datadog-instrumentations/src/mysql.js +7 -1
  9. package/packages/datadog-instrumentations/src/mysql2.js +7 -1
  10. package/packages/datadog-instrumentations/src/playwright.js +236 -0
  11. package/packages/datadog-plugin-fs/src/index.js +45 -0
  12. package/packages/datadog-plugin-jest/src/index.js +45 -23
  13. package/packages/datadog-plugin-mocha/src/index.js +34 -6
  14. package/packages/datadog-plugin-mysql/src/index.js +8 -7
  15. package/packages/datadog-plugin-playwright/src/index.js +171 -0
  16. package/packages/dd-trace/src/appsec/callbacks/ddwaf.js +1 -1
  17. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
  18. package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +60 -0
  19. package/packages/dd-trace/src/appsec/index.js +1 -1
  20. package/packages/dd-trace/src/appsec/recommended.json +247 -112
  21. package/packages/dd-trace/src/appsec/sdk/index.js +23 -0
  22. package/packages/dd-trace/src/appsec/sdk/noop.js +11 -0
  23. package/packages/dd-trace/src/appsec/sdk/track_event.js +74 -0
  24. package/packages/dd-trace/src/appsec/sdk/utils.js +10 -0
  25. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +1 -1
  26. package/packages/dd-trace/src/config.js +7 -0
  27. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +44 -4
  28. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +52 -37
  29. package/packages/dd-trace/src/log/channels.js +47 -0
  30. package/packages/dd-trace/src/log/index.js +79 -0
  31. package/packages/dd-trace/src/log/writer.js +108 -0
  32. package/packages/dd-trace/src/noop/proxy.js +3 -0
  33. package/packages/dd-trace/src/plugins/index.js +1 -0
  34. package/packages/dd-trace/src/plugins/util/ci.js +13 -21
  35. package/packages/dd-trace/src/{appsec → plugins/util}/ip_blocklist.js +0 -0
  36. package/packages/dd-trace/src/{appsec → plugins/util}/ip_extractor.js +1 -1
  37. package/packages/dd-trace/src/plugins/util/test.js +27 -10
  38. package/packages/dd-trace/src/plugins/util/user-provided-git.js +2 -7
  39. package/packages/dd-trace/src/plugins/util/web.js +11 -0
  40. package/packages/dd-trace/src/proxy.js +2 -0
  41. package/packages/dd-trace/src/startup-log.js +1 -1
  42. package/scripts/check-proposal-labels.js +71 -0
  43. package/packages/dd-trace/src/log.js +0 -143
@@ -0,0 +1,236 @@
1
+ const { addHook, channel, AsyncResource } = require('./helpers/instrument')
2
+ const shimmer = require('../../datadog-shimmer')
3
+
4
+ const testStartCh = channel('ci:playwright:test:start')
5
+ const testFinishCh = channel('ci:playwright:test:finish')
6
+
7
+ const testSessionStartCh = channel('ci:playwright:session:start')
8
+ const testSessionFinishCh = channel('ci:playwright:session:finish')
9
+
10
+ const testSuiteStartCh = channel('ci:playwright:test-suite:start')
11
+ const testSuiteFinishCh = channel('ci:playwright:test-suite:finish')
12
+
13
+ const testToAr = new WeakMap()
14
+ const testSuiteToAr = new Map()
15
+ const testSuiteToTestStatuses = new Map()
16
+
17
+ let startedSuites = []
18
+
19
+ const STATUS_TO_TEST_STATUS = {
20
+ passed: 'pass',
21
+ failed: 'fail',
22
+ timedOut: 'fail',
23
+ skipped: 'skip'
24
+ }
25
+
26
+ let remainingTestsByFile = {}
27
+
28
+ function getTestsBySuiteFromTestsById (testsById) {
29
+ const testsByTestSuite = {}
30
+ for (const { test } of testsById.values()) {
31
+ const { _requireFile } = test
32
+ if (test._type === 'beforeAll' || test._type === 'afterAll') {
33
+ continue
34
+ }
35
+ if (testsByTestSuite[_requireFile]) {
36
+ testsByTestSuite[_requireFile].push(test)
37
+ } else {
38
+ testsByTestSuite[_requireFile] = [test]
39
+ }
40
+ }
41
+ return testsByTestSuite
42
+ }
43
+
44
+ function getPlaywrightConfig (playwrightRunner) {
45
+ try {
46
+ return playwrightRunner._configLoader.fullConfig()
47
+ } catch (e) {
48
+ try {
49
+ return playwrightRunner._loader.fullConfig()
50
+ } catch (e) {
51
+ return {}
52
+ }
53
+ }
54
+ }
55
+
56
+ function getRootDir (playwrightRunner) {
57
+ const config = getPlaywrightConfig(playwrightRunner)
58
+ if (config.rootDir) {
59
+ return config.rootDir
60
+ }
61
+ if (playwrightRunner._configDir) {
62
+ return playwrightRunner._configDir
63
+ }
64
+ return process.cwd()
65
+ }
66
+
67
+ function testBeginHandler (test) {
68
+ const { title: testName, location: { file: testSuiteAbsolutePath }, _type } = test
69
+
70
+ if (_type === 'beforeAll' || _type === 'afterAll') {
71
+ return
72
+ }
73
+
74
+ const isNewTestSuite = !startedSuites.includes(testSuiteAbsolutePath)
75
+
76
+ if (isNewTestSuite) {
77
+ startedSuites.push(testSuiteAbsolutePath)
78
+ const testSuiteAsyncResource = new AsyncResource('bound-anonymous-fn')
79
+ testSuiteToAr.set(testSuiteAbsolutePath, testSuiteAsyncResource)
80
+ testSuiteAsyncResource.runInAsyncScope(() => {
81
+ testSuiteStartCh.publish(testSuiteAbsolutePath)
82
+ })
83
+ }
84
+
85
+ const testAsyncResource = new AsyncResource('bound-anonymous-fn')
86
+ testToAr.set(test, testAsyncResource)
87
+ testAsyncResource.runInAsyncScope(() => {
88
+ testStartCh.publish({ testName, testSuiteAbsolutePath })
89
+ })
90
+ }
91
+
92
+ function testEndHandler (test, testStatus, error) {
93
+ const { location: { file: testSuiteAbsolutePath }, results, _type } = test
94
+
95
+ if (_type === 'beforeAll' || _type === 'afterAll') {
96
+ return
97
+ }
98
+
99
+ const testResult = results[results.length - 1]
100
+ const testAsyncResource = testToAr.get(test)
101
+ testAsyncResource.runInAsyncScope(() => {
102
+ testFinishCh.publish({ testStatus, steps: testResult.steps, error })
103
+ })
104
+
105
+ if (!testSuiteToTestStatuses.has(testSuiteAbsolutePath)) {
106
+ testSuiteToTestStatuses.set(testSuiteAbsolutePath, [testStatus])
107
+ } else {
108
+ testSuiteToTestStatuses.get(testSuiteAbsolutePath).push(testStatus)
109
+ }
110
+
111
+ remainingTestsByFile[testSuiteAbsolutePath] = remainingTestsByFile[testSuiteAbsolutePath]
112
+ .filter(currentTest => currentTest !== test)
113
+
114
+ if (!remainingTestsByFile[testSuiteAbsolutePath].length) {
115
+ const testStatuses = testSuiteToTestStatuses.get(testSuiteAbsolutePath)
116
+
117
+ let testSuiteStatus = 'pass'
118
+ if (testStatuses.some(status => status === 'fail')) {
119
+ testSuiteStatus = 'fail'
120
+ } else if (testStatuses.every(status => status === 'skip')) {
121
+ testSuiteStatus = 'skip'
122
+ }
123
+
124
+ const testSuiteAsyncResource = testSuiteToAr.get(testSuiteAbsolutePath)
125
+ testSuiteAsyncResource.runInAsyncScope(() => {
126
+ testSuiteFinishCh.publish(testSuiteStatus)
127
+ })
128
+ }
129
+ }
130
+
131
+ function dispatcherRunWrapper (run) {
132
+ return function () {
133
+ remainingTestsByFile = getTestsBySuiteFromTestsById(this._testById)
134
+ return run.apply(this, arguments)
135
+ }
136
+ }
137
+
138
+ function dispatcherHook (dispatcherExport) {
139
+ shimmer.wrap(dispatcherExport.Dispatcher.prototype, 'run', dispatcherRunWrapper)
140
+ shimmer.wrap(dispatcherExport.Dispatcher.prototype, '_createWorker', createWorker => function () {
141
+ const dispatcher = this
142
+ const worker = createWorker.apply(this, arguments)
143
+
144
+ worker.process.on('message', ({ method, params }) => {
145
+ if (method === 'testBegin') {
146
+ const { test } = dispatcher._testById.get(params.testId)
147
+ testBeginHandler(test)
148
+ } else if (method === 'testEnd') {
149
+ const { test } = dispatcher._testById.get(params.testId)
150
+
151
+ const { results } = test
152
+ const testResult = results[results.length - 1]
153
+
154
+ testEndHandler(test, STATUS_TO_TEST_STATUS[testResult.status], testResult.error)
155
+ }
156
+ })
157
+
158
+ return worker
159
+ })
160
+ return dispatcherExport
161
+ }
162
+
163
+ function dispatcherHookNew (dispatcherExport) {
164
+ shimmer.wrap(dispatcherExport.Dispatcher.prototype, 'run', dispatcherRunWrapper)
165
+ shimmer.wrap(dispatcherExport.Dispatcher.prototype, '_createWorker', createWorker => function () {
166
+ const dispatcher = this
167
+ const worker = createWorker.apply(this, arguments)
168
+
169
+ worker.on('testBegin', ({ testId }) => {
170
+ const { test } = dispatcher._testById.get(testId)
171
+ testBeginHandler(test)
172
+ })
173
+ worker.on('testEnd', ({ testId, status, errors }) => {
174
+ const { test } = dispatcher._testById.get(testId)
175
+
176
+ testEndHandler(test, STATUS_TO_TEST_STATUS[status], errors && errors[0])
177
+ })
178
+
179
+ return worker
180
+ })
181
+ return dispatcherExport
182
+ }
183
+
184
+ function runnerHook (runnerExport) {
185
+ shimmer.wrap(runnerExport.Runner.prototype, 'runAllTests', runAllTests => async function () {
186
+ const testSessionAsyncResource = new AsyncResource('bound-anonymous-fn')
187
+ const { version: frameworkVersion } = getPlaywrightConfig(this)
188
+
189
+ const rootDir = getRootDir(this)
190
+
191
+ const processArgv = process.argv.slice(2).join(' ')
192
+ const command = `playwright ${processArgv}`
193
+ testSessionAsyncResource.runInAsyncScope(() => {
194
+ testSessionStartCh.publish({ command, frameworkVersion, rootDir })
195
+ })
196
+
197
+ const res = await runAllTests.apply(this, arguments)
198
+ const sessionStatus = STATUS_TO_TEST_STATUS[res.status]
199
+
200
+ let onDone
201
+
202
+ const flushWait = new Promise(resolve => {
203
+ onDone = resolve
204
+ })
205
+
206
+ testSessionAsyncResource.runInAsyncScope(() => {
207
+ testSessionFinishCh.publish({ status: sessionStatus, onDone })
208
+ })
209
+ await flushWait
210
+
211
+ startedSuites = []
212
+ remainingTestsByFile = {}
213
+
214
+ return res
215
+ })
216
+
217
+ return runnerExport
218
+ }
219
+
220
+ addHook({
221
+ name: '@playwright/test',
222
+ file: 'lib/runner.js',
223
+ versions: ['>=1.18.0']
224
+ }, runnerHook)
225
+
226
+ addHook({
227
+ name: '@playwright/test',
228
+ file: 'lib/dispatcher.js',
229
+ versions: ['>=1.18.0 <1.30.0']
230
+ }, dispatcherHook)
231
+
232
+ addHook({
233
+ name: '@playwright/test',
234
+ file: 'lib/dispatcher.js',
235
+ versions: ['>=1.30.0']
236
+ }, dispatcherHookNew)
@@ -0,0 +1,45 @@
1
+ 'use strict'
2
+
3
+ const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
4
+
5
+ class FsPlugin extends TracingPlugin {
6
+ static get name () { return 'fs' }
7
+ static get operation () { return 'operation' }
8
+
9
+ configure (...args) {
10
+ return super.configure(...args)
11
+ }
12
+
13
+ start ({ operation, ...params }) {
14
+ if (!this.activeSpan) return this.skip()
15
+
16
+ const lowerOp = operation.toLowerCase()
17
+ const flag = params.flag || params.flags || (params.options && (params.options.flag || params.options.flags))
18
+ const defaultFlag = ((lowerOp.includes('open') || lowerOp.includes('read')) && 'r') ||
19
+ (lowerOp.includes('write') && 'w') ||
20
+ (lowerOp.includes('append') && 'a')
21
+ const fd = params.fd || (typeof params.file === 'number' && params.file)
22
+ const path = params.path || params.prefix || params.filename || (typeof params.file === 'string' && params.file)
23
+ const uid = typeof params.uid === 'number' && params.uid.toString()
24
+ const gid = typeof params.gid === 'number' && params.gid.toString()
25
+ const mode = typeof params.mode === 'number' ? params.mode.toString(8) : params.mode
26
+
27
+ this.startSpan('fs.operation', {
28
+ service: this.config.service,
29
+ resource: operation,
30
+ kind: 'internal',
31
+ meta: {
32
+ 'file.descriptor': (typeof fd === 'object' || typeof fd === 'number') ? fd.toString() : '',
33
+ 'file.dest': params.dest || params.newPath || (params.target && params.path),
34
+ 'file.flag': String(flag || defaultFlag || ''),
35
+ 'file.gid': gid || '',
36
+ 'file.mode': mode,
37
+ 'file.path': path || '',
38
+ 'file.src': params.src || params.oldPath || params.existingPath || params.target,
39
+ 'file.uid': uid || ''
40
+ }
41
+ })
42
+ }
43
+ }
44
+
45
+ module.exports = FsPlugin
@@ -8,16 +8,19 @@ const {
8
8
  getTestEnvironmentMetadata,
9
9
  getTestParentSpan,
10
10
  getTestSessionCommonTags,
11
+ getTestModuleCommonTags,
11
12
  getTestSuiteCommonTags,
12
13
  TEST_PARAMETERS,
13
14
  getCodeOwnersFileEntries,
14
15
  TEST_SESSION_ID,
16
+ TEST_MODULE_ID,
15
17
  TEST_SUITE_ID,
16
18
  TEST_COMMAND,
17
19
  TEST_ITR_TESTS_SKIPPED,
18
20
  TEST_SESSION_CODE_COVERAGE_ENABLED,
19
21
  TEST_SESSION_ITR_SKIPPING_ENABLED,
20
- TEST_CODE_COVERAGE_LINES_TOTAL
22
+ TEST_CODE_COVERAGE_LINES_TOTAL,
23
+ TEST_BUNDLE
21
24
  } = require('../../dd-trace/src/plugins/util/test')
22
25
  const { COMPONENT } = require('../../dd-trace/src/constants')
23
26
 
@@ -49,12 +52,12 @@ class JestPlugin extends CiPlugin {
49
52
  this.testEnvironmentMetadata = getTestEnvironmentMetadata('jest', this.config)
50
53
  this.codeOwnersEntries = getCodeOwnersFileEntries()
51
54
 
52
- this.addSub('ci:jest:session:start', (command) => {
53
- const store = storage.getStore()
55
+ this.addSub('ci:jest:session:start', ({ command, testFrameworkVersion }) => {
54
56
  const childOf = getTestParentSpan(this.tracer)
55
- const testSessionSpanMetadata = getTestSessionCommonTags(command, this.tracer._version)
57
+ const testSessionSpanMetadata = getTestSessionCommonTags(command, testFrameworkVersion)
58
+ const testModuleSpanMetadata = getTestModuleCommonTags(command, testFrameworkVersion)
56
59
 
57
- const testSessionSpan = this.tracer.startSpan('jest.test_session', {
60
+ this.testSessionSpan = this.tracer.startSpan('jest.test_session', {
58
61
  childOf,
59
62
  tags: {
60
63
  [COMPONENT]: this.constructor.name,
@@ -62,7 +65,14 @@ class JestPlugin extends CiPlugin {
62
65
  ...testSessionSpanMetadata
63
66
  }
64
67
  })
65
- this.enter(testSessionSpan, store)
68
+ this.testModuleSpan = this.tracer.startSpan('jest.test_module', {
69
+ childOf: this.testSessionSpan,
70
+ tags: {
71
+ [COMPONENT]: this.constructor.name,
72
+ ...this.testEnvironmentMetadata,
73
+ ...testModuleSpanMetadata
74
+ }
75
+ })
66
76
  })
67
77
 
68
78
  this.addSub('ci:jest:session:finish', ({
@@ -72,18 +82,18 @@ class JestPlugin extends CiPlugin {
72
82
  isCodeCoverageEnabled,
73
83
  testCodeCoverageLinesTotal
74
84
  }) => {
75
- const testSessionSpan = storage.getStore().span
76
-
77
- testSessionSpan.setTag(TEST_STATUS, status)
78
- testSessionSpan.setTag(TEST_ITR_TESTS_SKIPPED, isSuitesSkipped ? 'true' : 'false')
79
- testSessionSpan.setTag(TEST_SESSION_ITR_SKIPPING_ENABLED, isSuitesSkippingEnabled ? 'true' : 'false')
80
- testSessionSpan.setTag(TEST_SESSION_CODE_COVERAGE_ENABLED, isCodeCoverageEnabled ? 'true' : 'false')
85
+ this.testSessionSpan.setTag(TEST_STATUS, status)
86
+ this.testSessionSpan.setTag(TEST_ITR_TESTS_SKIPPED, isSuitesSkipped ? 'true' : 'false')
87
+ this.testSessionSpan.setTag(TEST_SESSION_ITR_SKIPPING_ENABLED, isSuitesSkippingEnabled ? 'true' : 'false')
88
+ this.testSessionSpan.setTag(TEST_SESSION_CODE_COVERAGE_ENABLED, isCodeCoverageEnabled ? 'true' : 'false')
81
89
 
82
90
  if (testCodeCoverageLinesTotal !== undefined) {
83
- testSessionSpan.setTag(TEST_CODE_COVERAGE_LINES_TOTAL, testCodeCoverageLinesTotal)
91
+ this.testSessionSpan.setTag(TEST_CODE_COVERAGE_LINES_TOTAL, testCodeCoverageLinesTotal)
84
92
  }
85
- testSessionSpan.finish()
86
- finishAllTraceSpans(testSessionSpan)
93
+ this.testModuleSpan.setTag(TEST_STATUS, status)
94
+ this.testModuleSpan.finish()
95
+ this.testSessionSpan.finish()
96
+ finishAllTraceSpans(this.testSessionSpan)
87
97
  this.tracer._exporter.flush()
88
98
  })
89
99
 
@@ -91,21 +101,25 @@ class JestPlugin extends CiPlugin {
91
101
  // This subscriber changes the configuration objects from jest to inject the trace id
92
102
  // of the test session to the processes that run the test suites.
93
103
  this.addSub('ci:jest:session:configuration', configs => {
94
- const testSessionSpan = storage.getStore().span
95
104
  configs.forEach(config => {
96
- config._ddTestSessionId = testSessionSpan.context()._traceId.toString(10)
97
- config._ddTestCommand = testSessionSpan.context()._tags[TEST_COMMAND]
105
+ config._ddTestSessionId = this.testSessionSpan.context().toTraceId()
106
+ config._ddTestModuleId = this.testModuleSpan.context().toSpanId()
107
+ config._ddTestCommand = this.testSessionSpan.context()._tags[TEST_COMMAND]
98
108
  })
99
109
  })
100
110
 
101
111
  this.addSub('ci:jest:test-suite:start', ({ testSuite, testEnvironmentOptions }) => {
102
- const { _ddTestSessionId: testSessionId, _ddTestCommand: testCommand } = testEnvironmentOptions
112
+ const {
113
+ _ddTestSessionId: testSessionId,
114
+ _ddTestCommand: testCommand,
115
+ _ddTestModuleId: testModuleId
116
+ } = testEnvironmentOptions
103
117
 
104
118
  const store = storage.getStore()
105
119
 
106
120
  const testSessionSpanContext = this.tracer.extract('text_map', {
107
121
  'x-datadog-trace-id': testSessionId,
108
- 'x-datadog-parent-id': '0000000000000000'
122
+ 'x-datadog-parent-id': testModuleId
109
123
  })
110
124
 
111
125
  const testSuiteMetadata = getTestSuiteCommonTags(testCommand, this.tracer._version, testSuite)
@@ -173,14 +187,22 @@ class JestPlugin extends CiPlugin {
173
187
  }
174
188
 
175
189
  startTestSpan (test) {
190
+ let childOf
176
191
  const suiteTags = {}
177
192
  const store = storage.getStore()
178
193
  const testSuiteSpan = store ? store.span : undefined
179
194
  if (testSuiteSpan) {
180
- const testSuiteId = testSuiteSpan.context()._spanId.toString(10)
195
+ const testSuiteId = testSuiteSpan.context().toSpanId()
181
196
  suiteTags[TEST_SUITE_ID] = testSuiteId
182
- suiteTags[TEST_SESSION_ID] = testSuiteSpan.context()._traceId.toString(10)
197
+ suiteTags[TEST_SESSION_ID] = testSuiteSpan.context().toTraceId()
198
+ suiteTags[TEST_MODULE_ID] = testSuiteSpan.context()._parentId.toString(10)
183
199
  suiteTags[TEST_COMMAND] = testSuiteSpan.context()._tags[TEST_COMMAND]
200
+ suiteTags[TEST_BUNDLE] = testSuiteSpan.context()._tags[TEST_COMMAND]
201
+ // This is a hack to get good time resolution on test events, while keeping
202
+ // the test event as the root span of its trace.
203
+ childOf = getTestParentSpan(this.tracer)
204
+ childOf._trace.startTime = testSuiteSpan.context()._trace.startTime
205
+ childOf._trace.ticks = testSuiteSpan.context()._trace.ticks
184
206
  }
185
207
 
186
208
  const { suite, name, runner, testParameters } = test
@@ -191,7 +213,7 @@ class JestPlugin extends CiPlugin {
191
213
  ...suiteTags
192
214
  }
193
215
 
194
- return super.startTestSpan(name, suite, extraTags)
216
+ return super.startTestSpan(name, suite, extraTags, childOf)
195
217
  }
196
218
  }
197
219
 
@@ -11,9 +11,12 @@ const {
11
11
  getTestParentSpan,
12
12
  getTestParametersString,
13
13
  getTestSessionCommonTags,
14
+ getTestModuleCommonTags,
14
15
  getTestSuiteCommonTags,
15
16
  TEST_SUITE_ID,
16
17
  TEST_SESSION_ID,
18
+ TEST_MODULE_ID,
19
+ TEST_BUNDLE,
17
20
  TEST_COMMAND,
18
21
  TEST_ITR_TESTS_SKIPPED,
19
22
  TEST_SESSION_CODE_COVERAGE_ENABLED,
@@ -48,9 +51,9 @@ class MochaPlugin extends CiPlugin {
48
51
  })
49
52
  })
50
53
 
51
- this.addSub('ci:mocha:session:start', (command) => {
54
+ this.addSub('ci:mocha:session:start', ({ command, frameworkVersion }) => {
52
55
  const childOf = getTestParentSpan(this.tracer)
53
- const testSessionSpanMetadata = getTestSessionCommonTags(command, this.tracer._version)
56
+ const testSessionSpanMetadata = getTestSessionCommonTags(command, frameworkVersion)
54
57
 
55
58
  this.command = command
56
59
  this.testSessionSpan = this.tracer.startSpan('mocha.test_session', {
@@ -61,6 +64,16 @@ class MochaPlugin extends CiPlugin {
61
64
  ...testSessionSpanMetadata
62
65
  }
63
66
  })
67
+
68
+ const testModuleSpanMetadata = getTestModuleCommonTags(command, frameworkVersion)
69
+ this.testModuleSpan = this.tracer.startSpan('mocha.test_module', {
70
+ childOf: this.testSessionSpan,
71
+ tags: {
72
+ [COMPONENT]: this.constructor.name,
73
+ ...this.testEnvironmentMetadata,
74
+ ...testModuleSpanMetadata
75
+ }
76
+ })
64
77
  })
65
78
 
66
79
  this.addSub('ci:mocha:test-suite:start', (suite) => {
@@ -71,7 +84,7 @@ class MochaPlugin extends CiPlugin {
71
84
  getTestSuitePath(suite.file, this.sourceRoot)
72
85
  )
73
86
  const testSuiteSpan = this.tracer.startSpan('mocha.test_suite', {
74
- childOf: this.testSessionSpan,
87
+ childOf: this.testModuleSpan,
75
88
  tags: {
76
89
  [COMPONENT]: this.constructor.name,
77
90
  ...this.testEnvironmentMetadata,
@@ -147,6 +160,8 @@ class MochaPlugin extends CiPlugin {
147
160
  this.testSessionSpan.setTag(TEST_SESSION_ITR_SKIPPING_ENABLED, isSuitesSkippingEnabled ? 'true' : 'false')
148
161
  this.testSessionSpan.setTag(TEST_SESSION_CODE_COVERAGE_ENABLED, isCodeCoverageEnabled ? 'true' : 'false')
149
162
 
163
+ this.testModuleSpan.setTag(TEST_STATUS, status)
164
+ this.testModuleSpan.finish()
150
165
  this.testSessionSpan.finish()
151
166
  finishAllTraceSpans(this.testSessionSpan)
152
167
  }
@@ -156,19 +171,32 @@ class MochaPlugin extends CiPlugin {
156
171
  }
157
172
 
158
173
  startTestSpan (test) {
174
+ const childOf = getTestParentSpan(this.tracer)
175
+ // This is a hack to get good time resolution on test events, while keeping
176
+ // the test event as the root span of its trace.
177
+ childOf._trace.startTime = this.testSessionSpan.context()._trace.startTime
178
+ childOf._trace.ticks = this.testSessionSpan.context()._trace.ticks
179
+
159
180
  const testSuiteTags = {}
160
181
  const testSuiteSpan = this._testSuites.get(test.parent.file)
161
182
  if (testSuiteSpan) {
162
- const testSuiteId = testSuiteSpan.context()._spanId.toString(10)
183
+ const testSuiteId = testSuiteSpan.context().toSpanId()
163
184
  testSuiteTags[TEST_SUITE_ID] = testSuiteId
164
185
  }
165
186
 
166
187
  if (this.testSessionSpan) {
167
- const testSessionId = this.testSessionSpan.context()._traceId.toString(10)
188
+ const testSessionId = this.testSessionSpan.context().toTraceId()
168
189
  testSuiteTags[TEST_SESSION_ID] = testSessionId
169
190
  testSuiteTags[TEST_COMMAND] = this.command
170
191
  }
171
192
 
193
+ if (this.testModuleSpan) {
194
+ const testModuleId = this.testModuleSpan.context().toSpanId()
195
+ testSuiteTags[TEST_MODULE_ID] = testModuleId
196
+ testSuiteTags[TEST_COMMAND] = this.command
197
+ testSuiteTags[TEST_BUNDLE] = this.command
198
+ }
199
+
172
200
  const { file: testSuiteAbsolutePath } = test
173
201
  const fullTestName = test.fullTitle()
174
202
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.sourceRoot)
@@ -182,7 +210,7 @@ class MochaPlugin extends CiPlugin {
182
210
  extraTags[TEST_PARAMETERS] = testParametersString
183
211
  }
184
212
 
185
- return super.startTestSpan(fullTestName, testSuite, extraTags)
213
+ return super.startTestSpan(fullTestName, testSuite, extraTags, childOf)
186
214
  }
187
215
  }
188
216
 
@@ -6,22 +6,23 @@ class MySQLPlugin extends DatabasePlugin {
6
6
  static get name () { return 'mysql' }
7
7
  static get system () { return 'mysql' }
8
8
 
9
- start ({ sql, conf: dbConfig }) {
10
- const service = getServiceName(this.config, dbConfig)
9
+ start (payload) {
10
+ const service = getServiceName(this.config, payload.conf)
11
11
 
12
12
  this.startSpan(`${this.system}.query`, {
13
13
  service,
14
- resource: sql,
14
+ resource: payload.sql,
15
15
  type: 'sql',
16
16
  kind: 'client',
17
17
  meta: {
18
18
  'db.type': this.system,
19
- 'db.user': dbConfig.user,
20
- 'db.name': dbConfig.database,
21
- 'out.host': dbConfig.host,
22
- 'out.port': dbConfig.port
19
+ 'db.user': payload.conf.user,
20
+ 'db.name': payload.conf.database,
21
+ 'out.host': payload.conf.host,
22
+ 'out.port': payload.conf.port
23
23
  }
24
24
  })
25
+ payload.sql = this.injectDbmQuery(payload.sql, service)
25
26
  }
26
27
  }
27
28