dd-trace 3.13.2 → 3.14.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 (38) hide show
  1. package/ci/init.js +2 -1
  2. package/index.d.ts +20 -0
  3. package/package.json +1 -1
  4. package/packages/datadog-instrumentations/src/aws-sdk.js +86 -0
  5. package/packages/datadog-instrumentations/src/cucumber.js +74 -15
  6. package/packages/datadog-instrumentations/src/cypress.js +1 -1
  7. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  8. package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
  9. package/packages/datadog-instrumentations/src/jest.js +24 -33
  10. package/packages/datadog-instrumentations/src/mocha.js +4 -7
  11. package/packages/datadog-instrumentations/src/playwright.js +2 -4
  12. package/packages/datadog-plugin-aws-sdk/src/base.js +12 -5
  13. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +2 -2
  14. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +29 -24
  15. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +31 -16
  16. package/packages/datadog-plugin-cucumber/src/index.js +42 -11
  17. package/packages/datadog-plugin-cypress/src/plugin.js +129 -4
  18. package/packages/datadog-plugin-cypress/src/support.js +5 -0
  19. package/packages/datadog-plugin-jest/src/index.js +18 -67
  20. package/packages/datadog-plugin-mocha/src/index.js +35 -84
  21. package/packages/datadog-plugin-playwright/src/index.js +2 -61
  22. package/packages/datadog-shimmer/src/shimmer.js +28 -11
  23. package/packages/dd-trace/src/appsec/reporter.js +14 -14
  24. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +1 -5
  25. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +1 -5
  26. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +32 -10
  27. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +1 -1
  28. package/packages/dd-trace/src/config.js +55 -6
  29. package/packages/dd-trace/src/encode/0.4.js +1 -1
  30. package/packages/dd-trace/src/encode/0.5.js +1 -1
  31. package/packages/dd-trace/src/encode/tags-processors.js +3 -2
  32. package/packages/dd-trace/src/opentracing/propagation/text_map.js +186 -36
  33. package/packages/dd-trace/src/opentracing/propagation/tracestate.js +99 -0
  34. package/packages/dd-trace/src/opentracing/span.js +2 -1
  35. package/packages/dd-trace/src/opentracing/span_context.js +1 -0
  36. package/packages/dd-trace/src/plugins/ci_plugin.js +69 -12
  37. package/packages/dd-trace/src/plugins/index.js +1 -0
  38. package/packages/dd-trace/src/telemetry/send-data.js +4 -1
@@ -6,16 +6,8 @@ const CiPlugin = require('../../dd-trace/src/plugins/ci_plugin')
6
6
  const {
7
7
  TEST_STATUS,
8
8
  finishAllTraceSpans,
9
- getTestParentSpan,
10
- getTestSessionCommonTags,
11
9
  getTestSuitePath,
12
- TEST_SESSION_ID,
13
- TEST_COMMAND,
14
- TEST_SUITE_ID,
15
- getTestSuiteCommonTags,
16
- getTestModuleCommonTags,
17
- TEST_MODULE_ID,
18
- TEST_BUNDLE
10
+ getTestSuiteCommonTags
19
11
  } = require('../../dd-trace/src/plugins/util/test')
20
12
  const { RESOURCE_NAME } = require('../../../ext/tags')
21
13
  const { COMPONENT } = require('../../dd-trace/src/constants')
@@ -30,32 +22,6 @@ class PlaywrightPlugin extends CiPlugin {
30
22
 
31
23
  this._testSuites = new Map()
32
24
 
33
- this.addSub('ci:playwright:session:start', ({ command, frameworkVersion, rootDir }) => {
34
- const childOf = getTestParentSpan(this.tracer)
35
- this.command = command
36
- this.frameworkVersion = frameworkVersion
37
- this.rootDir = rootDir
38
-
39
- const testSessionSpanMetadata = getTestSessionCommonTags(command, frameworkVersion)
40
- this.testSessionSpan = this.tracer.startSpan('playwright.test_session', {
41
- childOf,
42
- tags: {
43
- [COMPONENT]: this.constructor.name,
44
- ...this.testEnvironmentMetadata,
45
- ...testSessionSpanMetadata
46
- }
47
- })
48
- const testModuleSpanMetadata = getTestModuleCommonTags(command, frameworkVersion)
49
- this.testModuleSpan = this.tracer.startSpan('playwright.test_module', {
50
- childOf: this.testSessionSpan,
51
- tags: {
52
- [COMPONENT]: this.constructor.name,
53
- ...this.testEnvironmentMetadata,
54
- ...testModuleSpanMetadata
55
- }
56
- })
57
- })
58
-
59
25
  this.addSub('ci:playwright:session:finish', ({ status, onDone }) => {
60
26
  this.testModuleSpan.setTag(TEST_STATUS, status)
61
27
  this.testSessionSpan.setTag(TEST_STATUS, status)
@@ -138,33 +104,8 @@ class PlaywrightPlugin extends CiPlugin {
138
104
  }
139
105
 
140
106
  startTestSpan (testName, testSuite) {
141
- const childOf = getTestParentSpan(this.tracer)
142
- // This is a hack to get good time resolution on test events, while keeping
143
- // the test event as the root span of its trace.
144
- childOf._trace.startTime = this.testSessionSpan.context()._trace.startTime
145
- childOf._trace.ticks = this.testSessionSpan.context()._trace.ticks
146
-
147
- const testSuiteTags = {}
148
107
  const testSuiteSpan = this._testSuites.get(testSuite)
149
- if (testSuiteSpan) {
150
- const testSuiteId = testSuiteSpan.context().toSpanId()
151
- testSuiteTags[TEST_SUITE_ID] = testSuiteId
152
- }
153
-
154
- if (this.testSessionSpan) {
155
- const testSessionId = this.testSessionSpan.context().toTraceId()
156
- testSuiteTags[TEST_SESSION_ID] = testSessionId
157
- testSuiteTags[TEST_COMMAND] = this.command
158
- }
159
-
160
- if (this.testModuleSpan) {
161
- const testModuleId = this.testModuleSpan.context().toSpanId()
162
- testSuiteTags[TEST_MODULE_ID] = testModuleId
163
- testSuiteTags[TEST_COMMAND] = this.command
164
- testSuiteTags[TEST_BUNDLE] = this.command
165
- }
166
-
167
- return super.startTestSpan(testName, testSuite, testSuiteTags, childOf)
108
+ return super.startTestSpan(testName, testSuite, testSuiteSpan)
168
109
  }
169
110
  }
170
111
 
@@ -10,7 +10,11 @@ function copyProperties (original, wrapped) {
10
10
  const keys = Reflect.ownKeys(props)
11
11
 
12
12
  for (const key of keys) {
13
- Object.defineProperty(wrapped, key, props[key])
13
+ try {
14
+ Object.defineProperty(wrapped, key, props[key])
15
+ } catch (e) {
16
+ // TODO: figure out how to handle this without a try/catch
17
+ }
14
18
  }
15
19
  }
16
20
 
@@ -33,28 +37,41 @@ function wrapFn (original, delegate) {
33
37
 
34
38
  function wrapMethod (target, name, wrapper) {
35
39
  assertMethod(target, name)
36
- assertNotClass(target[name]) // TODO: support constructors of native classes
37
40
  assertFunction(wrapper)
38
41
 
39
42
  const original = target[name]
40
43
  const wrapped = wrapper(original)
41
44
  const descriptor = Object.getOwnPropertyDescriptor(target, name)
42
45
 
46
+ const attributes = {
47
+ configurable: true,
48
+ ...descriptor
49
+ }
50
+
51
+ copyProperties(original, wrapped)
52
+
43
53
  if (descriptor) {
44
54
  unwrappers.set(wrapped, () => Object.defineProperty(target, name, descriptor))
55
+
56
+ if (descriptor.get || descriptor.set) {
57
+ attributes.get = () => wrapped
58
+ } else {
59
+ attributes.value = wrapped
60
+ }
61
+
62
+ // TODO: create a single object for multiple wrapped methods
63
+ if (descriptor.configurable === false) {
64
+ return Object.create(target, {
65
+ [name]: attributes
66
+ })
67
+ }
45
68
  } else { // no descriptor means original was on the prototype
46
69
  unwrappers.set(wrapped, () => delete target[name])
70
+ attributes.value = wrapped
71
+ attributes.writable = true
47
72
  }
48
73
 
49
- Object.defineProperty(target, name, {
50
- configurable: true,
51
- writable: true,
52
- enumerable: false,
53
- ...descriptor,
54
- value: wrapped
55
- })
56
-
57
- copyProperties(original, wrapped)
74
+ Object.defineProperty(target, name, attributes)
58
75
 
59
76
  return target
60
77
  }
@@ -86,28 +86,28 @@ function formatHeaderName (name) {
86
86
 
87
87
  function reportMetrics (metrics, store) {
88
88
  const req = store && store.get('req')
89
- const topSpan = web.root(req)
90
- if (!topSpan) return false
89
+ const rootSpan = web.root(req)
90
+ if (!rootSpan) return false
91
91
 
92
92
  if (metrics.duration) {
93
- topSpan.setTag('_dd.appsec.waf.duration', metrics.duration)
93
+ rootSpan.setTag('_dd.appsec.waf.duration', metrics.duration)
94
94
  }
95
95
 
96
96
  if (metrics.durationExt) {
97
- topSpan.setTag('_dd.appsec.waf.duration_ext', metrics.durationExt)
97
+ rootSpan.setTag('_dd.appsec.waf.duration_ext', metrics.durationExt)
98
98
  }
99
99
 
100
100
  if (metrics.rulesVersion) {
101
- topSpan.setTag('_dd.appsec.event_rules.version', metrics.rulesVersion)
101
+ rootSpan.setTag('_dd.appsec.event_rules.version', metrics.rulesVersion)
102
102
  }
103
103
  }
104
104
 
105
105
  function reportAttack (attackData, store) {
106
106
  const req = store && store.get('req')
107
- const topSpan = web.root(req)
108
- if (!topSpan) return false
107
+ const rootSpan = web.root(req)
108
+ if (!rootSpan) return false
109
109
 
110
- const currentTags = topSpan.context()._tags
110
+ const currentTags = rootSpan.context()._tags
111
111
 
112
112
  const newTags = {
113
113
  'appsec.event': 'true'
@@ -146,20 +146,20 @@ function reportAttack (attackData, store) {
146
146
  newTags['network.client.ip'] = resolvedRequest.remote_ip
147
147
  }
148
148
 
149
- topSpan.addTags(newTags)
149
+ rootSpan.addTags(newTags)
150
150
  }
151
151
 
152
152
  function finishRequest (req, context) {
153
- const topSpan = web.root(req)
154
- if (!topSpan) return false
153
+ const rootSpan = web.root(req)
154
+ if (!rootSpan) return false
155
155
 
156
156
  if (metricsQueue.size) {
157
- topSpan.addTags(Object.fromEntries(metricsQueue))
157
+ rootSpan.addTags(Object.fromEntries(metricsQueue))
158
158
 
159
159
  metricsQueue.clear()
160
160
  }
161
161
 
162
- if (!context || !topSpan.context()._tags['appsec.event']) return false
162
+ if (!context || !rootSpan.context()._tags['appsec.event']) return false
163
163
 
164
164
  const resolvedResponse = resolveHTTPResponse(context)
165
165
 
@@ -169,7 +169,7 @@ function finishRequest (req, context) {
169
169
  newTags['http.endpoint'] = resolvedResponse.endpoint
170
170
  }
171
171
 
172
- topSpan.addTags(newTags)
172
+ rootSpan.addTags(newTags)
173
173
  }
174
174
 
175
175
  function setRateLimit (rateLimit) {
@@ -20,8 +20,7 @@ class AgentProxyCiVisibilityExporter extends CiVisibilityExporter {
20
20
  prioritySampler,
21
21
  lookup,
22
22
  protocolVersion,
23
- headers,
24
- isGitUploadEnabled
23
+ headers
25
24
  } = config
26
25
 
27
26
  this.getAgentInfo((err, agentInfo) => {
@@ -38,9 +37,6 @@ class AgentProxyCiVisibilityExporter extends CiVisibilityExporter {
38
37
  url: this._url,
39
38
  evpProxyPrefix: AGENT_EVP_PROXY_PATH
40
39
  })
41
- if (isGitUploadEnabled) {
42
- this.sendGitMetadata({ url: this._url, isEvpProxy: true })
43
- }
44
40
  } else {
45
41
  this._writer = new AgentWriter({
46
42
  url: this._url,
@@ -9,7 +9,7 @@ const log = require('../../../log')
9
9
  class AgentlessCiVisibilityExporter extends CiVisibilityExporter {
10
10
  constructor (config) {
11
11
  super(config)
12
- const { tags, site, url, isGitUploadEnabled } = config
12
+ const { tags, site, url } = config
13
13
  // we don't need to request /info because we are using agentless by configuration
14
14
  this._isInitialized = true
15
15
  this._resolveCanUseCiVisProtocol(true)
@@ -21,10 +21,6 @@ class AgentlessCiVisibilityExporter extends CiVisibilityExporter {
21
21
  this._coverageWriter = new CoverageWriter({ url: this._coverageUrl })
22
22
 
23
23
  this._apiUrl = url || new URL(`https://api.${site}`)
24
-
25
- if (isGitUploadEnabled) {
26
- this.sendGitMetadata({ url: this._getApiUrl() })
27
- }
28
24
  }
29
25
 
30
26
  setUrl (url, coverageUrl = url, apiUrl = url) {
@@ -14,6 +14,9 @@ function getIsTestSessionTrace (trace) {
14
14
  )
15
15
  }
16
16
 
17
+ const GIT_UPLOAD_TIMEOUT = 60000 // 60 seconds
18
+ const CAN_USE_CI_VIS_PROTOCOL_TIMEOUT = GIT_UPLOAD_TIMEOUT
19
+
17
20
  class CiVisibilityExporter extends AgentInfoExporter {
18
21
  constructor (config) {
19
22
  super(config)
@@ -24,14 +27,24 @@ class CiVisibilityExporter extends AgentInfoExporter {
24
27
  // AKA CI Vis Protocol
25
28
  this._canUseCiVisProtocol = false
26
29
 
27
- // TODO: add timeout to reject this promise
30
+ const gitUploadTimeoutId = setTimeout(() => {
31
+ this._resolveGit(new Error('Timeout while uploading git metadata'))
32
+ }, GIT_UPLOAD_TIMEOUT).unref()
33
+
34
+ const canUseCiVisProtocolTimeoutId = setTimeout(() => {
35
+ this._resolveCanUseCiVisProtocol(false)
36
+ }, CAN_USE_CI_VIS_PROTOCOL_TIMEOUT).unref()
37
+
28
38
  this._gitUploadPromise = new Promise(resolve => {
29
- this._resolveGit = resolve
39
+ this._resolveGit = (err) => {
40
+ clearTimeout(gitUploadTimeoutId)
41
+ resolve(err)
42
+ }
30
43
  })
31
44
 
32
- // TODO: add timeout to reject this promise
33
45
  this._canUseCiVisProtocolPromise = new Promise(resolve => {
34
46
  this._resolveCanUseCiVisProtocol = (canUseCiVisProtocol) => {
47
+ clearTimeout(canUseCiVisProtocolTimeoutId)
35
48
  this._canUseCiVisProtocol = canUseCiVisProtocol
36
49
  resolve(canUseCiVisProtocol)
37
50
  }
@@ -93,6 +106,7 @@ class CiVisibilityExporter extends AgentInfoExporter {
93
106
  * CI Visibility Protocol, hence the this._canUseCiVisProtocol promise.
94
107
  */
95
108
  getItrConfiguration (testConfiguration, callback) {
109
+ this.sendGitMetadata()
96
110
  if (!this.shouldRequestItrConfiguration()) {
97
111
  return callback(null, {})
98
112
  }
@@ -118,14 +132,22 @@ class CiVisibilityExporter extends AgentInfoExporter {
118
132
  })
119
133
  }
120
134
 
121
- sendGitMetadata ({ url, isEvpProxy }) {
122
- sendGitMetadataRequest(url, isEvpProxy, (err) => {
123
- if (err) {
124
- log.error(`Error uploading git metadata: ${err.message}`)
125
- } else {
126
- log.debug('Successfully uploaded git metadata')
135
+ sendGitMetadata () {
136
+ if (!this._config.isGitUploadEnabled) {
137
+ return
138
+ }
139
+ this._canUseCiVisProtocolPromise.then((canUseCiVisProtocol) => {
140
+ if (!canUseCiVisProtocol) {
141
+ return
127
142
  }
128
- this._resolveGit(err)
143
+ sendGitMetadataRequest(this._getApiUrl(), !!this._isUsingEvpProxy, (err) => {
144
+ if (err) {
145
+ log.error(`Error uploading git metadata: ${err.message}`)
146
+ } else {
147
+ log.debug('Successfully uploaded git metadata')
148
+ }
149
+ this._resolveGit(err)
150
+ })
129
151
  })
130
152
  }
131
153
 
@@ -19,7 +19,7 @@ function getSkippableSuites ({
19
19
  headers: {
20
20
  'Content-Type': 'application/json'
21
21
  },
22
- timeout: 15000,
22
+ timeout: 20000,
23
23
  url
24
24
  }
25
25
 
@@ -54,6 +54,32 @@ function remapify (input, mappings) {
54
54
  return output
55
55
  }
56
56
 
57
+ function propagationStyle (key, option, defaultValue) {
58
+ // Extract by key if in object-form value
59
+ if (typeof option === 'object' && !Array.isArray(option)) {
60
+ option = option[key]
61
+ }
62
+
63
+ // Should be an array at this point
64
+ if (Array.isArray(option)) return option.map(v => v.toLowerCase())
65
+
66
+ // If it's not an array but not undefined there's something wrong with the input
67
+ if (typeof option !== 'undefined') {
68
+ log.warn('Unexpected input for config.tracePropagationStyle')
69
+ }
70
+
71
+ // Otherwise, fallback to env var parsing
72
+ const envKey = `DD_TRACE_PROPAGATION_STYLE_${key.toUpperCase()}`
73
+ const envVar = coalesce(process.env[envKey], process.env.DD_TRACE_PROPAGATION_STYLE)
74
+ if (typeof envVar !== 'undefined') {
75
+ return envVar.split(',')
76
+ .filter(v => v !== '')
77
+ .map(v => v.trim().toLowerCase())
78
+ }
79
+
80
+ return defaultValue
81
+ }
82
+
57
83
  class Config {
58
84
  constructor (options) {
59
85
  options = options || {}
@@ -195,15 +221,36 @@ class Config {
195
221
  process.env.DD_TRACE_CLIENT_IP_HEADER,
196
222
  null
197
223
  )
224
+ // TODO: Remove the experimental env vars as a major?
198
225
  const DD_TRACE_B3_ENABLED = coalesce(
199
226
  options.experimental && options.experimental.b3,
200
227
  process.env.DD_TRACE_EXPERIMENTAL_B3_ENABLED,
201
228
  false
202
229
  )
203
- const DD_TRACE_TRACEPARENT_ENABLED = coalesce(
204
- options.experimental && options.experimental.traceparent,
205
- process.env.DD_TRACE_EXPERIMENTAL_TRACEPARENT_ENABLED,
206
- false
230
+ const defaultPropagationStyle = ['tracecontext', 'datadog']
231
+ if (isTrue(DD_TRACE_B3_ENABLED)) {
232
+ defaultPropagationStyle.push('b3')
233
+ defaultPropagationStyle.push('b3 single header')
234
+ }
235
+ if (process.env.DD_TRACE_PROPAGATION_STYLE && (
236
+ process.env.DD_TRACE_PROPAGATION_STYLE_INJECT ||
237
+ process.env.DD_TRACE_PROPAGATION_STYLE_EXTRACT
238
+ )) {
239
+ log.warn(
240
+ 'Use either the DD_TRACE_PROPAGATION_STYLE environment variable or separate ' +
241
+ 'DD_TRACE_PROPAGATION_STYLE_INJECT and DD_TRACE_PROPAGATION_STYLE_EXTRACT ' +
242
+ 'environment variables'
243
+ )
244
+ }
245
+ const DD_TRACE_PROPAGATION_STYLE_INJECT = propagationStyle(
246
+ 'inject',
247
+ options.tracePropagationStyle,
248
+ defaultPropagationStyle
249
+ )
250
+ const DD_TRACE_PROPAGATION_STYLE_EXTRACT = propagationStyle(
251
+ 'extract',
252
+ options.tracePropagationStyle,
253
+ defaultPropagationStyle
207
254
  )
208
255
  const DD_TRACE_RUNTIME_ID_ENABLED = coalesce(
209
256
  options.experimental && options.experimental.runtimeId,
@@ -389,9 +436,11 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
389
436
  port: String(coalesce(dogstatsd.port, process.env.DD_DOGSTATSD_PORT, 8125))
390
437
  }
391
438
  this.runtimeMetrics = isTrue(DD_RUNTIME_METRICS_ENABLED)
439
+ this.tracePropagationStyle = {
440
+ inject: DD_TRACE_PROPAGATION_STYLE_INJECT,
441
+ extract: DD_TRACE_PROPAGATION_STYLE_EXTRACT
442
+ }
392
443
  this.experimental = {
393
- b3: isTrue(DD_TRACE_B3_ENABLED),
394
- traceparent: isTrue(DD_TRACE_TRACEPARENT_ENABLED),
395
444
  runtimeId: isTrue(DD_TRACE_RUNTIME_ID_ENABLED),
396
445
  exporter: DD_TRACE_EXPORTER,
397
446
  enableGetRumData: isTrue(DD_TRACE_GET_RUM_DATA_ENABLED)
@@ -14,7 +14,7 @@ float64Array[0] = -1
14
14
  const bigEndian = uInt8Float64Array[7] === 0
15
15
 
16
16
  function formatSpan (span) {
17
- return normalizeSpan(truncateSpan(span))
17
+ return normalizeSpan(truncateSpan(span, false))
18
18
  }
19
19
 
20
20
  class AgentEncoder {
@@ -7,7 +7,7 @@ const ARRAY_OF_TWO = 0x92
7
7
  const ARRAY_OF_TWELVE = 0x9c
8
8
 
9
9
  function formatSpan (span) {
10
- return normalizeSpan(truncateSpan(span))
10
+ return normalizeSpan(truncateSpan(span, false))
11
11
  }
12
12
 
13
13
  class AgentEncoder extends BaseEncoder {
@@ -38,11 +38,12 @@ function truncateToLength (value, maxLength) {
38
38
  return value
39
39
  }
40
40
 
41
- function truncateSpan (span) {
41
+ // normally the agent truncates the resource and parses it in certain scenarios (e.g. SQL Queries)
42
+ function truncateSpan (span, shouldTruncateResourceName = true) {
42
43
  return fromEntries(Object.entries(span).map(([key, value]) => {
43
44
  switch (key) {
44
45
  case 'resource':
45
- return ['resource', truncateToLength(value, MAX_RESOURCE_NAME_LENGTH)]
46
+ return ['resource', shouldTruncateResourceName ? truncateToLength(value, MAX_RESOURCE_NAME_LENGTH) : value]
46
47
  case 'meta':
47
48
  return ['meta', fromEntries(Object.entries(value).map(([metaKey, metaValue]) =>
48
49
  [truncateToLength(metaKey, MAX_META_KEY_LENGTH), truncateToLength(metaValue, MAX_META_VALUE_LENGTH)]