dd-trace 3.12.1 → 3.15.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 (101) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/README.md +5 -5
  3. package/ci/init.js +3 -1
  4. package/index.d.ts +100 -1
  5. package/package.json +5 -4
  6. package/packages/datadog-instrumentations/src/aws-sdk.js +86 -0
  7. package/packages/datadog-instrumentations/src/cucumber.js +74 -15
  8. package/packages/datadog-instrumentations/src/cypress.js +1 -1
  9. package/packages/datadog-instrumentations/src/fs.js +358 -0
  10. package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
  11. package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
  12. package/packages/datadog-instrumentations/src/jest.js +24 -23
  13. package/packages/datadog-instrumentations/src/ldapjs.js +12 -2
  14. package/packages/datadog-instrumentations/src/mocha.js +10 -7
  15. package/packages/datadog-instrumentations/src/mongoose.js +1 -1
  16. package/packages/datadog-instrumentations/src/mysql.js +7 -1
  17. package/packages/datadog-instrumentations/src/mysql2.js +7 -1
  18. package/packages/datadog-instrumentations/src/next.js +2 -1
  19. package/packages/datadog-instrumentations/src/playwright.js +263 -0
  20. package/packages/datadog-plugin-aws-sdk/src/base.js +12 -5
  21. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +2 -2
  22. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +29 -24
  23. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +31 -16
  24. package/packages/datadog-plugin-cucumber/src/index.js +42 -11
  25. package/packages/datadog-plugin-cypress/src/plugin.js +129 -4
  26. package/packages/datadog-plugin-cypress/src/support.js +5 -0
  27. package/packages/datadog-plugin-fs/src/index.js +45 -0
  28. package/packages/datadog-plugin-hapi/src/index.js +5 -1
  29. package/packages/datadog-plugin-http/src/server.js +1 -1
  30. package/packages/datadog-plugin-http2/src/server.js +1 -1
  31. package/packages/datadog-plugin-jest/src/index.js +40 -70
  32. package/packages/datadog-plugin-mocha/src/index.js +44 -64
  33. package/packages/datadog-plugin-mysql/src/index.js +8 -7
  34. package/packages/datadog-plugin-playwright/src/index.js +112 -0
  35. package/packages/datadog-shimmer/src/shimmer.js +28 -11
  36. package/packages/dd-trace/src/appsec/addresses.js +3 -1
  37. package/packages/dd-trace/src/appsec/blocking.js +35 -9
  38. package/packages/dd-trace/src/appsec/callbacks/ddwaf.js +1 -1
  39. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
  40. package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +60 -0
  41. package/packages/dd-trace/src/appsec/iast/iast-context.js +6 -2
  42. package/packages/dd-trace/src/appsec/iast/index.js +3 -2
  43. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +5 -2
  44. package/packages/dd-trace/src/appsec/index.js +5 -5
  45. package/packages/dd-trace/src/appsec/recommended.json +320 -184
  46. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
  47. package/packages/dd-trace/src/appsec/remote_config/index.js +3 -0
  48. package/packages/dd-trace/src/appsec/reporter.js +14 -14
  49. package/packages/dd-trace/src/appsec/sdk/index.js +41 -0
  50. package/packages/dd-trace/src/appsec/sdk/noop.js +17 -0
  51. package/packages/dd-trace/src/appsec/sdk/set_user.js +30 -0
  52. package/packages/dd-trace/src/appsec/sdk/track_event.js +74 -0
  53. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +73 -0
  54. package/packages/dd-trace/src/appsec/sdk/utils.js +10 -0
  55. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +1 -5
  56. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +1 -5
  57. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +48 -11
  58. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +7 -1
  59. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +4 -2
  60. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +5 -3
  61. package/packages/dd-trace/src/config.js +63 -7
  62. package/packages/dd-trace/src/encode/0.4.js +1 -1
  63. package/packages/dd-trace/src/encode/0.5.js +1 -1
  64. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +44 -4
  65. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +52 -37
  66. package/packages/dd-trace/src/encode/tags-processors.js +3 -2
  67. package/packages/dd-trace/src/exporters/common/request.js +10 -3
  68. package/packages/dd-trace/src/lambda/handler.js +5 -6
  69. package/packages/dd-trace/src/log/channels.js +47 -0
  70. package/packages/dd-trace/src/log/index.js +79 -0
  71. package/packages/dd-trace/src/log/writer.js +124 -0
  72. package/packages/dd-trace/src/metrics.js +18 -0
  73. package/packages/dd-trace/src/noop/proxy.js +5 -2
  74. package/packages/dd-trace/src/opentracing/propagation/text_map.js +188 -36
  75. package/packages/dd-trace/src/opentracing/propagation/tracestate.js +99 -0
  76. package/packages/dd-trace/src/opentracing/span.js +2 -1
  77. package/packages/dd-trace/src/opentracing/span_context.js +6 -3
  78. package/packages/dd-trace/src/plugins/ci_plugin.js +72 -12
  79. package/packages/dd-trace/src/plugins/index.js +2 -0
  80. package/packages/dd-trace/src/plugins/util/ci.js +13 -21
  81. package/packages/dd-trace/src/plugins/util/exec.js +2 -2
  82. package/packages/dd-trace/src/plugins/util/git.js +16 -1
  83. package/packages/dd-trace/src/{appsec → plugins/util}/ip_extractor.js +1 -1
  84. package/packages/dd-trace/src/plugins/util/test.js +53 -10
  85. package/packages/dd-trace/src/plugins/util/user-provided-git.js +2 -7
  86. package/packages/dd-trace/src/plugins/util/web.js +11 -0
  87. package/packages/dd-trace/src/profiler.js +3 -0
  88. package/packages/dd-trace/src/profiling/config.js +8 -3
  89. package/packages/dd-trace/src/profiling/exporters/file.js +13 -2
  90. package/packages/dd-trace/src/profiling/profiler.js +23 -6
  91. package/packages/dd-trace/src/profiling/profilers/wall.js +1 -0
  92. package/packages/dd-trace/src/proxy.js +2 -0
  93. package/packages/dd-trace/src/span_processor.js +1 -1
  94. package/packages/dd-trace/src/span_sampler.js +68 -52
  95. package/packages/dd-trace/src/startup-log.js +3 -6
  96. package/packages/dd-trace/src/telemetry/index.js +23 -2
  97. package/packages/dd-trace/src/telemetry/send-data.js +4 -1
  98. package/packages/dd-trace/src/tracer.js +0 -16
  99. package/scripts/check-proposal-labels.js +71 -0
  100. package/packages/dd-trace/src/log.js +0 -143
  101. /package/packages/dd-trace/src/{appsec → plugins/util}/ip_blocklist.js +0 -0
@@ -0,0 +1,263 @@
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 getTestsBySuiteFromTestGroups (testGroups) {
29
+ return testGroups.reduce((acc, { requireFile, tests }) => {
30
+ if (acc[requireFile]) {
31
+ acc[requireFile] = acc[requireFile].concat(tests)
32
+ } else {
33
+ acc[requireFile] = tests
34
+ }
35
+ return acc
36
+ }, {})
37
+ }
38
+
39
+ function getTestsBySuiteFromTestsById (testsById) {
40
+ const testsByTestSuite = {}
41
+ for (const { test } of testsById.values()) {
42
+ const { _requireFile } = test
43
+ if (test._type === 'beforeAll' || test._type === 'afterAll') {
44
+ continue
45
+ }
46
+ if (testsByTestSuite[_requireFile]) {
47
+ testsByTestSuite[_requireFile].push(test)
48
+ } else {
49
+ testsByTestSuite[_requireFile] = [test]
50
+ }
51
+ }
52
+ return testsByTestSuite
53
+ }
54
+
55
+ function getPlaywrightConfig (playwrightRunner) {
56
+ try {
57
+ return playwrightRunner._configLoader.fullConfig()
58
+ } catch (e) {
59
+ try {
60
+ return playwrightRunner._loader.fullConfig()
61
+ } catch (e) {
62
+ return playwrightRunner._config || {}
63
+ }
64
+ }
65
+ }
66
+
67
+ function getRootDir (playwrightRunner) {
68
+ const config = getPlaywrightConfig(playwrightRunner)
69
+ if (config.rootDir) {
70
+ return config.rootDir
71
+ }
72
+ if (playwrightRunner._configDir) {
73
+ return playwrightRunner._configDir
74
+ }
75
+ return process.cwd()
76
+ }
77
+
78
+ function testBeginHandler (test) {
79
+ const { title: testName, location: { file: testSuiteAbsolutePath }, _type } = test
80
+
81
+ if (_type === 'beforeAll' || _type === 'afterAll') {
82
+ return
83
+ }
84
+
85
+ const isNewTestSuite = !startedSuites.includes(testSuiteAbsolutePath)
86
+
87
+ if (isNewTestSuite) {
88
+ startedSuites.push(testSuiteAbsolutePath)
89
+ const testSuiteAsyncResource = new AsyncResource('bound-anonymous-fn')
90
+ testSuiteToAr.set(testSuiteAbsolutePath, testSuiteAsyncResource)
91
+ testSuiteAsyncResource.runInAsyncScope(() => {
92
+ testSuiteStartCh.publish(testSuiteAbsolutePath)
93
+ })
94
+ }
95
+
96
+ const testAsyncResource = new AsyncResource('bound-anonymous-fn')
97
+ testToAr.set(test, testAsyncResource)
98
+ testAsyncResource.runInAsyncScope(() => {
99
+ testStartCh.publish({ testName, testSuiteAbsolutePath })
100
+ })
101
+ }
102
+
103
+ function testEndHandler (test, testStatus, error) {
104
+ const { location: { file: testSuiteAbsolutePath }, results, _type } = test
105
+
106
+ if (_type === 'beforeAll' || _type === 'afterAll') {
107
+ return
108
+ }
109
+
110
+ const testResult = results[results.length - 1]
111
+ const testAsyncResource = testToAr.get(test)
112
+ testAsyncResource.runInAsyncScope(() => {
113
+ testFinishCh.publish({ testStatus, steps: testResult.steps, error })
114
+ })
115
+
116
+ if (!testSuiteToTestStatuses.has(testSuiteAbsolutePath)) {
117
+ testSuiteToTestStatuses.set(testSuiteAbsolutePath, [testStatus])
118
+ } else {
119
+ testSuiteToTestStatuses.get(testSuiteAbsolutePath).push(testStatus)
120
+ }
121
+
122
+ remainingTestsByFile[testSuiteAbsolutePath] = remainingTestsByFile[testSuiteAbsolutePath]
123
+ .filter(currentTest => currentTest !== test)
124
+
125
+ if (!remainingTestsByFile[testSuiteAbsolutePath].length) {
126
+ const testStatuses = testSuiteToTestStatuses.get(testSuiteAbsolutePath)
127
+
128
+ let testSuiteStatus = 'pass'
129
+ if (testStatuses.some(status => status === 'fail')) {
130
+ testSuiteStatus = 'fail'
131
+ } else if (testStatuses.every(status => status === 'skip')) {
132
+ testSuiteStatus = 'skip'
133
+ }
134
+
135
+ const testSuiteAsyncResource = testSuiteToAr.get(testSuiteAbsolutePath)
136
+ testSuiteAsyncResource.runInAsyncScope(() => {
137
+ testSuiteFinishCh.publish(testSuiteStatus)
138
+ })
139
+ }
140
+ }
141
+
142
+ function dispatcherRunWrapper (run) {
143
+ return function () {
144
+ remainingTestsByFile = getTestsBySuiteFromTestsById(this._testById)
145
+ return run.apply(this, arguments)
146
+ }
147
+ }
148
+
149
+ function dispatcherRunWrapperNew (run) {
150
+ return function () {
151
+ remainingTestsByFile = getTestsBySuiteFromTestGroups(arguments[0])
152
+ return run.apply(this, arguments)
153
+ }
154
+ }
155
+
156
+ function dispatcherHook (dispatcherExport) {
157
+ shimmer.wrap(dispatcherExport.Dispatcher.prototype, 'run', dispatcherRunWrapper)
158
+ shimmer.wrap(dispatcherExport.Dispatcher.prototype, '_createWorker', createWorker => function () {
159
+ const dispatcher = this
160
+ const worker = createWorker.apply(this, arguments)
161
+
162
+ worker.process.on('message', ({ method, params }) => {
163
+ if (method === 'testBegin') {
164
+ const { test } = dispatcher._testById.get(params.testId)
165
+ testBeginHandler(test)
166
+ } else if (method === 'testEnd') {
167
+ const { test } = dispatcher._testById.get(params.testId)
168
+
169
+ const { results } = test
170
+ const testResult = results[results.length - 1]
171
+
172
+ testEndHandler(test, STATUS_TO_TEST_STATUS[testResult.status], testResult.error)
173
+ }
174
+ })
175
+
176
+ return worker
177
+ })
178
+ return dispatcherExport
179
+ }
180
+
181
+ function dispatcherHookNew (dispatcherExport, runWrapper) {
182
+ shimmer.wrap(dispatcherExport.Dispatcher.prototype, 'run', runWrapper)
183
+ shimmer.wrap(dispatcherExport.Dispatcher.prototype, '_createWorker', createWorker => function () {
184
+ const dispatcher = this
185
+ const worker = createWorker.apply(this, arguments)
186
+
187
+ worker.on('testBegin', ({ testId }) => {
188
+ const { test } = dispatcher._testById.get(testId)
189
+ testBeginHandler(test)
190
+ })
191
+ worker.on('testEnd', ({ testId, status, errors }) => {
192
+ const { test } = dispatcher._testById.get(testId)
193
+
194
+ testEndHandler(test, STATUS_TO_TEST_STATUS[status], errors && errors[0])
195
+ })
196
+
197
+ return worker
198
+ })
199
+ return dispatcherExport
200
+ }
201
+
202
+ function runnerHook (runnerExport, playwrightVersion) {
203
+ shimmer.wrap(runnerExport.Runner.prototype, 'runAllTests', runAllTests => async function () {
204
+ const testSessionAsyncResource = new AsyncResource('bound-anonymous-fn')
205
+ const rootDir = getRootDir(this)
206
+
207
+ const processArgv = process.argv.slice(2).join(' ')
208
+ const command = `playwright ${processArgv}`
209
+ testSessionAsyncResource.runInAsyncScope(() => {
210
+ testSessionStartCh.publish({ command, frameworkVersion: playwrightVersion, rootDir })
211
+ })
212
+
213
+ const runAllTestsReturn = await runAllTests.apply(this, arguments)
214
+ const sessionStatus = runAllTestsReturn.status || runAllTestsReturn
215
+
216
+ let onDone
217
+
218
+ const flushWait = new Promise(resolve => {
219
+ onDone = resolve
220
+ })
221
+ testSessionAsyncResource.runInAsyncScope(() => {
222
+ testSessionFinishCh.publish({ status: STATUS_TO_TEST_STATUS[sessionStatus], onDone })
223
+ })
224
+ await flushWait
225
+
226
+ startedSuites = []
227
+ remainingTestsByFile = {}
228
+
229
+ return runAllTestsReturn
230
+ })
231
+
232
+ return runnerExport
233
+ }
234
+
235
+ addHook({
236
+ name: '@playwright/test',
237
+ file: 'lib/runner.js',
238
+ versions: ['>=1.18.0 <1.30.0']
239
+ }, runnerHook)
240
+
241
+ addHook({
242
+ name: '@playwright/test',
243
+ file: 'lib/dispatcher.js',
244
+ versions: ['>=1.18.0 <1.30.0']
245
+ }, dispatcherHook)
246
+
247
+ addHook({
248
+ name: '@playwright/test',
249
+ file: 'lib/dispatcher.js',
250
+ versions: ['>=1.30.0 <1.31.0']
251
+ }, (dispatcher) => dispatcherHookNew(dispatcher, dispatcherRunWrapper))
252
+
253
+ addHook({
254
+ name: '@playwright/test',
255
+ file: 'lib/runner/dispatcher.js',
256
+ versions: ['>=1.31.0']
257
+ }, (dispatcher) => dispatcherHookNew(dispatcher, dispatcherRunWrapperNew))
258
+
259
+ addHook({
260
+ name: '@playwright/test',
261
+ file: 'lib/runner/runner.js',
262
+ versions: ['>=1.31.0']
263
+ }, runnerHook)
@@ -23,13 +23,12 @@ class BaseAwsSdkPlugin extends Plugin {
23
23
  request,
24
24
  operation,
25
25
  awsRegion,
26
- awsService,
27
- serviceIdentifier
26
+ awsService
28
27
  }) => {
29
28
  if (!this.isEnabled(request)) {
30
29
  return
31
30
  }
32
- const serviceName = this.getServiceName(serviceIdentifier)
31
+ const serviceName = this.getServiceName()
33
32
  const childOf = this.tracer.scope().active()
34
33
  const tags = {
35
34
  'span.kind': 'client',
@@ -52,6 +51,14 @@ class BaseAwsSdkPlugin extends Plugin {
52
51
  this.enter(span, store)
53
52
  })
54
53
 
54
+ this.addSub(`apm:aws:request:region:${this.serviceIdentifier}`, region => {
55
+ const store = storage.getStore()
56
+ if (!store) return
57
+ const { span } = store
58
+ if (!span) return
59
+ span.setTag('aws.region', region)
60
+ })
61
+
55
62
  this.addSub(`apm:aws:request:complete:${this.serviceIdentifier}`, ({ response }) => {
56
63
  const store = storage.getStore()
57
64
  if (!store) return
@@ -109,10 +116,10 @@ class BaseAwsSdkPlugin extends Plugin {
109
116
  }
110
117
 
111
118
  // TODO: test splitByAwsService when the test suite is fixed
112
- getServiceName (serviceIdentifier) {
119
+ getServiceName () {
113
120
  return this.config.service
114
121
  ? this.config.service
115
- : `${this.tracer._service}-aws-${serviceIdentifier}`
122
+ : `${this.tracer._service}-aws-${this.serviceIdentifier}`
116
123
  }
117
124
  }
118
125
 
@@ -48,8 +48,8 @@ class Kinesis extends BaseAwsSdkPlugin {
48
48
  const parsedData = this._tryParse(injectPath.Data)
49
49
  if (parsedData) {
50
50
  parsedData._datadog = traceData
51
- const finalData = JSON.stringify(parsedData)
52
- const byteSize = Buffer.byteLength(finalData, 'ascii')
51
+ const finalData = Buffer.from(JSON.stringify(parsedData))
52
+ const byteSize = finalData.length
53
53
  // Kinesis max payload size is 1MB
54
54
  // So we must ensure adding DD context won't go over that (512b is an estimate)
55
55
  if (byteSize >= 1048576) {
@@ -18,30 +18,35 @@ class Sns extends BaseAwsSdkPlugin {
18
18
  }
19
19
 
20
20
  requestInject (span, request) {
21
- const operation = request.operation
22
- if (operation === 'publish' || operation === 'publishBatch') {
23
- if (!request.params) {
24
- request.params = {}
25
- }
26
- let injectPath
27
- if (request.params.PublishBatchRequestEntries && request.params.PublishBatchRequestEntries.length > 0) {
28
- injectPath = request.params.PublishBatchRequestEntries[0]
29
- } else if (request.params.Message) {
30
- injectPath = request.params
31
- }
32
- if (!injectPath.MessageAttributes) {
33
- injectPath.MessageAttributes = {}
34
- }
35
- if (Object.keys(injectPath.MessageAttributes).length >= 10) { // SNS quota
36
- log.info('Message attributes full, skipping trace context injection')
37
- return
38
- }
39
- const ddInfo = {}
40
- this.tracer.inject(span, 'text_map', ddInfo)
41
- injectPath.MessageAttributes._datadog = {
42
- DataType: 'Binary',
43
- BinaryValue: JSON.stringify(ddInfo) // BINARY types are automatically base64 encoded
44
- }
21
+ const { operation, params } = request
22
+
23
+ if (!params) return
24
+
25
+ switch (operation) {
26
+ case 'publish':
27
+ this._injectMessageAttributes(span, params)
28
+ break
29
+ case 'publishBatch':
30
+ if (params.PublishBatchRequestEntries && params.PublishBatchRequestEntries.length > 0) {
31
+ this._injectMessageAttributes(span, params.PublishBatchRequestEntries[0])
32
+ }
33
+ break
34
+ }
35
+ }
36
+
37
+ _injectMessageAttributes (span, params) {
38
+ if (!params.MessageAttributes) {
39
+ params.MessageAttributes = {}
40
+ }
41
+ if (Object.keys(params.MessageAttributes).length >= 10) { // SNS quota
42
+ log.info('Message attributes full, skipping trace context injection')
43
+ return
44
+ }
45
+ const ddInfo = {}
46
+ this.tracer.inject(span, 'text_map', ddInfo)
47
+ params.MessageAttributes._datadog = {
48
+ DataType: 'Binary',
49
+ BinaryValue: Buffer.from(JSON.stringify(ddInfo)) // BINARY types are automatically base64 encoded
45
50
  }
46
51
  }
47
52
  }
@@ -78,24 +78,39 @@ class Sqs extends BaseAwsSdkPlugin {
78
78
  }
79
79
 
80
80
  responseExtract (params, operation, response) {
81
- if (operation === 'receiveMessage') {
82
- if (
83
- (!params.MaxNumberOfMessages || params.MaxNumberOfMessages === 1) &&
84
- response &&
85
- response.Messages &&
86
- response.Messages[0] &&
87
- response.Messages[0].MessageAttributes &&
88
- response.Messages[0].MessageAttributes._datadog &&
89
- response.Messages[0].MessageAttributes._datadog.StringValue
90
- ) {
91
- const textMap = response.Messages[0].MessageAttributes._datadog.StringValue
92
- try {
93
- return this.tracer.extract('text_map', JSON.parse(textMap))
94
- } catch (err) {
95
- log.error(err)
96
- return undefined
81
+ if (operation !== 'receiveMessage') return
82
+ if (params.MaxNumberOfMessages && params.MaxNumberOfMessages !== 1) return
83
+ if (!response || !response.Messages || !response.Messages[0]) return
84
+
85
+ let message = response.Messages[0]
86
+
87
+ if (message.Body) {
88
+ try {
89
+ const body = JSON.parse(message.Body)
90
+
91
+ // SNS to SQS
92
+ if (body.Type === 'Notification') {
93
+ message = body
97
94
  }
95
+ } catch (e) {
96
+ // SQS to SQS
97
+ }
98
+ }
99
+
100
+ if (!message.MessageAttributes || !message.MessageAttributes._datadog) return
101
+
102
+ const datadogAttribute = message.MessageAttributes._datadog
103
+
104
+ try {
105
+ if (datadogAttribute.StringValue) {
106
+ const textMap = datadogAttribute.StringValue
107
+ return this.tracer.extract('text_map', JSON.parse(textMap))
108
+ } else if (datadogAttribute.Type === 'Binary') {
109
+ const buffer = Buffer.from(datadogAttribute.Value, 'base64')
110
+ return this.tracer.extract('text_map', JSON.parse(buffer))
98
111
  }
112
+ } catch (e) {
113
+ log.error(e)
99
114
  }
100
115
  }
101
116
 
@@ -7,7 +7,8 @@ const {
7
7
  TEST_SKIP_REASON,
8
8
  TEST_STATUS,
9
9
  finishAllTraceSpans,
10
- getTestSuitePath
10
+ getTestSuitePath,
11
+ getTestSuiteCommonTags
11
12
  } = require('../../dd-trace/src/plugins/util/test')
12
13
  const { RESOURCE_NAME } = require('../../../ext/tags')
13
14
  const { COMPONENT, ERROR_MESSAGE } = require('../../dd-trace/src/constants')
@@ -20,21 +21,47 @@ class CucumberPlugin extends CiPlugin {
20
21
  constructor (...args) {
21
22
  super(...args)
22
23
 
23
- this.addSub('ci:cucumber:session:finish', () => {
24
+ this.sourceRoot = process.cwd()
25
+
26
+ this.addSub('ci:cucumber:session:finish', (status) => {
27
+ this.testSessionSpan.setTag(TEST_STATUS, status)
28
+ this.testModuleSpan.setTag(TEST_STATUS, status)
29
+ this.testModuleSpan.finish()
30
+ this.testSessionSpan.finish()
31
+ finishAllTraceSpans(this.testSessionSpan)
24
32
  this.tracer._exporter.flush()
25
33
  })
26
34
 
27
- this.addSub('ci:cucumber:run:start', ({ testName, fullTestSuite }) => {
28
- const store = storage.getStore()
29
- const childOf = store ? store.span : store
30
- const testSuite = getTestSuitePath(fullTestSuite, process.cwd())
35
+ this.addSub('ci:cucumber:test-suite:start', (testSuiteFullPath) => {
36
+ const testSuiteMetadata = getTestSuiteCommonTags(
37
+ this.command,
38
+ this.frameworkVersion,
39
+ getTestSuitePath(testSuiteFullPath, this.sourceRoot)
40
+ )
41
+ this.testSuiteSpan = this.tracer.startSpan('cucumber.test_suite', {
42
+ childOf: this.testModuleSpan,
43
+ tags: {
44
+ [COMPONENT]: this.constructor.name,
45
+ ...this.testEnvironmentMetadata,
46
+ ...testSuiteMetadata
47
+ }
48
+ })
49
+ })
31
50
 
32
- const testSpan = this.startTestSpan(testName, testSuite, childOf)
51
+ this.addSub('ci:cucumber:test-suite:finish', status => {
52
+ this.testSuiteSpan.setTag(TEST_STATUS, status)
53
+ this.testSuiteSpan.finish()
54
+ })
55
+
56
+ this.addSub('ci:cucumber:test:start', ({ testName, fullTestSuite }) => {
57
+ const store = storage.getStore()
58
+ const testSuite = getTestSuitePath(fullTestSuite, this.sourceRoot)
59
+ const testSpan = this.startTestSpan(testName, testSuite)
33
60
 
34
61
  this.enter(testSpan, store)
35
62
  })
36
63
 
37
- this.addSub('ci:cucumber:run-step:start', ({ resource }) => {
64
+ this.addSub('ci:cucumber:test-step:start', ({ resource }) => {
38
65
  const store = storage.getStore()
39
66
  const childOf = store ? store.span : store
40
67
  const span = this.tracer.startSpan('cucumber.step', {
@@ -48,7 +75,7 @@ class CucumberPlugin extends CiPlugin {
48
75
  this.enter(span, store)
49
76
  })
50
77
 
51
- this.addSub('ci:cucumber:run:finish', ({ isStep, status, skipReason, errorMessage }) => {
78
+ this.addSub('ci:cucumber:test:finish', ({ isStep, status, skipReason, errorMessage }) => {
52
79
  const span = storage.getStore().span
53
80
  const statusTag = isStep ? 'step.status' : TEST_STATUS
54
81
 
@@ -76,8 +103,12 @@ class CucumberPlugin extends CiPlugin {
76
103
  })
77
104
  }
78
105
 
79
- startTestSpan (testName, testSuite, childOf) {
80
- return super.startTestSpan(testName, testSuite, {}, childOf)
106
+ startTestSpan (testName, testSuite) {
107
+ return super.startTestSpan(
108
+ testName,
109
+ testSuite,
110
+ this.testSuiteSpan
111
+ )
81
112
  }
82
113
  }
83
114
 
@@ -7,7 +7,16 @@ const {
7
7
  getTestParentSpan,
8
8
  getCodeOwnersFileEntries,
9
9
  getCodeOwnersForFilename,
10
- getTestCommonTags
10
+ getTestCommonTags,
11
+ getTestSessionCommonTags,
12
+ getTestModuleCommonTags,
13
+ getTestSuiteCommonTags,
14
+ TEST_SUITE_ID,
15
+ TEST_MODULE_ID,
16
+ TEST_SESSION_ID,
17
+ TEST_COMMAND,
18
+ TEST_BUNDLE,
19
+ finishAllTraceSpans
11
20
  } = require('../../dd-trace/src/plugins/util/test')
12
21
 
13
22
  const { ORIGIN_KEY, COMPONENT } = require('../../dd-trace/src/constants')
@@ -30,6 +39,43 @@ function getTestSpanMetadata (tracer, testName, testSuite, cypressConfig) {
30
39
  }
31
40
  }
32
41
 
42
+ function getCypressVersion (details) {
43
+ if (details && details.cypressVersion) {
44
+ return details.cypressVersion
45
+ }
46
+ if (details && details.config && details.config.version) {
47
+ return details.config.version
48
+ }
49
+ return ''
50
+ }
51
+
52
+ function getCypressCommand (details) {
53
+ if (!details) {
54
+ return 'cypress'
55
+ }
56
+ return `cypress ${details.specPattern || ''}`
57
+ }
58
+
59
+ function getSessionStatus (summary) {
60
+ if (summary.totalFailed !== undefined && summary.totalFailed > 0) {
61
+ return 'fail'
62
+ }
63
+ if (summary.totalSkipped !== undefined && summary.totalSkipped === summary.totalTests) {
64
+ return 'skip'
65
+ }
66
+ return 'pass'
67
+ }
68
+
69
+ function getSuiteStatus (suiteStats) {
70
+ if (suiteStats.failures !== undefined && suiteStats.failures > 0) {
71
+ return 'fail'
72
+ }
73
+ if (suiteStats.tests !== undefined && suiteStats.tests === suiteStats.pending) {
74
+ return 'skip'
75
+ }
76
+ return 'pass'
77
+ }
78
+
33
79
  module.exports = (on, config) => {
34
80
  const tracer = require('../../dd-trace')
35
81
  const testEnvironmentMetadata = getTestEnvironmentMetadata('cypress')
@@ -37,14 +83,92 @@ module.exports = (on, config) => {
37
83
  const codeOwnersEntries = getCodeOwnersFileEntries()
38
84
 
39
85
  let activeSpan = null
40
- on('after:run', () => {
86
+ let testSessionSpan = null
87
+ let testModuleSpan = null
88
+ let testSuiteSpan = null
89
+ let command = null
90
+ let frameworkVersion
91
+
92
+ on('before:run', (details) => {
93
+ const childOf = getTestParentSpan(tracer)
94
+
95
+ command = getCypressCommand(details)
96
+ frameworkVersion = getCypressVersion(details)
97
+
98
+ const testSessionSpanMetadata = getTestSessionCommonTags(command, frameworkVersion)
99
+ const testModuleSpanMetadata = getTestModuleCommonTags(command, frameworkVersion)
100
+
101
+ testSessionSpan = tracer.startSpan('cypress.test_session', {
102
+ childOf,
103
+ tags: {
104
+ [COMPONENT]: 'cypress',
105
+ ...testEnvironmentMetadata,
106
+ ...testSessionSpanMetadata
107
+ }
108
+ })
109
+ testModuleSpan = tracer.startSpan('cypress.test_module', {
110
+ childOf: testSessionSpan,
111
+ tags: {
112
+ [COMPONENT]: 'cypress',
113
+ ...testEnvironmentMetadata,
114
+ ...testModuleSpanMetadata
115
+ }
116
+ })
117
+ })
118
+
119
+ on('after:run', (suiteStats) => {
120
+ const testStatus = getSessionStatus(suiteStats)
121
+ testModuleSpan.setTag(TEST_STATUS, testStatus)
122
+ testSessionSpan.setTag(TEST_STATUS, testStatus)
123
+
124
+ testModuleSpan.finish()
125
+ testSessionSpan.finish()
126
+
127
+ finishAllTraceSpans(testSessionSpan)
128
+
41
129
  return new Promise(resolve => {
42
- tracer._tracer._exporter._writer.flush(() => resolve(null))
130
+ tracer._tracer._exporter._writer.flush(() => {
131
+ resolve(null)
132
+ })
43
133
  })
44
134
  })
45
135
  on('task', {
136
+ 'dd:testSuiteStart': (suite) => {
137
+ if (testSuiteSpan) {
138
+ return null
139
+ }
140
+ const testSuiteSpanMetadata = getTestSuiteCommonTags(command, frameworkVersion, suite)
141
+ testSuiteSpan = tracer.startSpan('cypress.test_suite', {
142
+ childOf: testModuleSpan,
143
+ tags: {
144
+ [COMPONENT]: 'cypress',
145
+ ...testEnvironmentMetadata,
146
+ ...testSuiteSpanMetadata
147
+ }
148
+ })
149
+ return null
150
+ },
151
+ 'dd:testSuiteFinish': (suiteStats) => {
152
+ const status = getSuiteStatus(suiteStats)
153
+ testSuiteSpan.setTag(TEST_STATUS, status)
154
+ testSuiteSpan.finish()
155
+ testSuiteSpan = null
156
+ return null
157
+ },
46
158
  'dd:beforeEach': (test) => {
47
159
  const { testName, testSuite } = test
160
+ const testSuiteId = testSuiteSpan.context().toSpanId()
161
+ const testSessionId = testSessionSpan.context().toTraceId()
162
+ const testModuleId = testModuleSpan.context().toSpanId()
163
+
164
+ const testSuiteTags = {
165
+ [TEST_SUITE_ID]: testSuiteId,
166
+ [TEST_SESSION_ID]: testSessionId,
167
+ [TEST_COMMAND]: command,
168
+ [TEST_MODULE_ID]: testModuleId,
169
+ [TEST_COMMAND]: command,
170
+ [TEST_BUNDLE]: command
171
+ }
48
172
 
49
173
  const {
50
174
  childOf,
@@ -65,7 +189,8 @@ module.exports = (on, config) => {
65
189
  [COMPONENT]: 'cypress',
66
190
  [ORIGIN_KEY]: CI_APP_ORIGIN,
67
191
  ...testSpanMetadata,
68
- ...testEnvironmentMetadata
192
+ ...testEnvironmentMetadata,
193
+ ...testSuiteTags
69
194
  }
70
195
  })
71
196
  }