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
@@ -4,6 +4,7 @@ const pick = require('lodash.pick')
4
4
  const id = require('../../id')
5
5
  const DatadogSpanContext = require('../span_context')
6
6
  const log = require('../../log')
7
+ const TraceState = require('./tracestate')
7
8
 
8
9
  const { AUTO_KEEP, AUTO_REJECT, USER_KEEP } = require('../../../../../ext/priority')
9
10
 
@@ -29,8 +30,15 @@ const tagValueExpr = /^[\x20-\x2b\x2d-\x7e]*$/ // ASCII minus commas
29
30
  const ddKeys = [traceKey, spanKey, samplingKey, originKey]
30
31
  const b3Keys = [b3TraceKey, b3SpanKey, b3ParentKey, b3SampledKey, b3FlagsKey, b3HeaderKey]
31
32
  const logKeys = ddKeys.concat(b3Keys)
32
- const traceparentExpr = /^(\d{2})-([A-Fa-f0-9]{32})-([A-Fa-f0-9]{16})-(\d{2})$/i
33
+ const traceparentExpr = /^([a-f0-9]{2})-([a-f0-9]{32})-([a-f0-9]{16})-([a-f0-9]{2})(-.*)?$/i
33
34
  const traceparentKey = 'traceparent'
35
+ // Origin value in tracestate replaces '~', ',' and ';' with '_"
36
+ const tracestateOriginFilter = /[^\x20-\x2b\x2d-\x3a\x3c-\x7d]/g
37
+ // Tag keys in tracestate replace ' ', ',' and '=' with '_'
38
+ const tracestateTagKeyFilter = /[^\x21-\x2b\x2d-\x3c\x3e-\x7e]/g
39
+ // Tag values in tracestate replace ',', '~' and ';' with '_'
40
+ const tracestateTagValueFilter = /[^\x20-\x2b\x2d-\x3a\x3c-\x7d]/g
41
+ const invalidSegment = /^0+$/
34
42
 
35
43
  class TextMapPropagator {
36
44
  constructor (config) {
@@ -38,15 +46,11 @@ class TextMapPropagator {
38
46
  }
39
47
 
40
48
  inject (spanContext, carrier) {
41
- carrier[traceKey] = spanContext.toTraceId()
42
- carrier[spanKey] = spanContext.toSpanId()
43
-
44
- this._injectOrigin(spanContext, carrier)
45
- this._injectSamplingPriority(spanContext, carrier)
46
49
  this._injectBaggageItems(spanContext, carrier)
47
- this._injectB3(spanContext, carrier)
50
+ this._injectDatadog(spanContext, carrier)
51
+ this._injectB3MultipleHeaders(spanContext, carrier)
52
+ this._injectB3SingleHeader(spanContext, carrier)
48
53
  this._injectTraceparent(spanContext, carrier)
49
- this._injectTags(spanContext, carrier)
50
54
 
51
55
  log.debug(() => `Inject into carrier: ${JSON.stringify(pick(carrier, logKeys))}.`)
52
56
  }
@@ -61,6 +65,17 @@ class TextMapPropagator {
61
65
  return spanContext
62
66
  }
63
67
 
68
+ _injectDatadog (spanContext, carrier) {
69
+ if (!this._hasPropagationStyle('inject', 'datadog')) return
70
+
71
+ carrier[traceKey] = spanContext.toTraceId()
72
+ carrier[spanKey] = spanContext.toSpanId()
73
+
74
+ this._injectOrigin(spanContext, carrier)
75
+ this._injectSamplingPriority(spanContext, carrier)
76
+ this._injectTags(spanContext, carrier)
77
+ }
78
+
64
79
  _injectOrigin (spanContext, carrier) {
65
80
  const origin = spanContext._trace.origin
66
81
 
@@ -112,8 +127,10 @@ class TextMapPropagator {
112
127
  }
113
128
  }
114
129
 
115
- _injectB3 (spanContext, carrier) {
116
- if (!this._config.experimental.b3) return
130
+ _injectB3MultipleHeaders (spanContext, carrier) {
131
+ const hasB3 = this._hasPropagationStyle('inject', 'b3')
132
+ const hasB3multi = this._hasPropagationStyle('inject', 'b3multi')
133
+ if (!(hasB3 || hasB3multi)) return
117
134
 
118
135
  carrier[b3TraceKey] = spanContext._traceId.toString(16)
119
136
  carrier[b3SpanKey] = spanContext._spanId.toString(16)
@@ -128,16 +145,92 @@ class TextMapPropagator {
128
145
  }
129
146
  }
130
147
 
148
+ _injectB3SingleHeader (spanContext, carrier) {
149
+ const hasB3SingleHeader = this._hasPropagationStyle('inject', 'b3 single header')
150
+ if (!hasB3SingleHeader) return null
151
+
152
+ const traceId = spanContext._traceId.toString(16)
153
+ const spanId = spanContext._spanId.toString(16)
154
+ const sampled = spanContext._sampling.priority >= AUTO_KEEP ? '1' : '0'
155
+
156
+ carrier[b3HeaderKey] = `${traceId}-${spanId}-${sampled}`
157
+ if (spanContext._parentId) {
158
+ carrier[b3HeaderKey] += '-' + spanContext._parentId.toString(16)
159
+ }
160
+ }
161
+
131
162
  _injectTraceparent (spanContext, carrier) {
132
- if (!this._config.experimental.traceparent) return
163
+ if (!this._hasPropagationStyle('inject', 'tracecontext')) return
164
+
165
+ const {
166
+ _sampling: { priority, mechanism },
167
+ _tracestate: ts = new TraceState(),
168
+ _trace: { origin, tags }
169
+ } = spanContext
170
+
133
171
  carrier[traceparentKey] = spanContext.toTraceparent()
172
+
173
+ ts.forVendor('dd', state => {
174
+ state.set('s', priority)
175
+ if (mechanism) {
176
+ state.set('t.dm', mechanism)
177
+ }
178
+
179
+ if (typeof origin === 'string') {
180
+ const originValue = origin
181
+ .replace(tracestateOriginFilter, '_')
182
+ .replace(/[\x3d]/g, '~')
183
+
184
+ state.set('o', originValue)
185
+ }
186
+
187
+ for (const key in tags) {
188
+ if (!tags[key] || !key.startsWith('_dd.p.')) continue
189
+
190
+ const tagKey = 't.' + key.slice(6)
191
+ .replace(tracestateTagKeyFilter, '_')
192
+
193
+ const tagValue = tags[key]
194
+ .toString()
195
+ .replace(tracestateTagValueFilter, '_')
196
+ .replace(/[\x3d]/g, '~')
197
+
198
+ state.set(tagKey, tagValue)
199
+ }
200
+ })
201
+
202
+ carrier.tracestate = ts.toString()
203
+ }
204
+
205
+ _hasPropagationStyle (mode, name) {
206
+ return this._config.tracePropagationStyle[mode].includes(name)
134
207
  }
135
208
 
136
209
  _extractSpanContext (carrier) {
137
- return this._extractDatadogContext(carrier) ||
138
- this._extractTraceparentContext(carrier) ||
139
- this._extractB3Context(carrier) ||
140
- this._extractSqsdContext(carrier)
210
+ for (const extractor of this._config.tracePropagationStyle.extract) {
211
+ let spanContext = null
212
+ switch (extractor) {
213
+ case 'datadog':
214
+ spanContext = this._extractDatadogContext(carrier)
215
+ break
216
+ case 'tracecontext':
217
+ spanContext = this._extractTraceparentContext(carrier)
218
+ break
219
+ case 'b3': // TODO: should match "b3 single header" in next major
220
+ case 'b3multi':
221
+ spanContext = this._extractB3MultiContext(carrier)
222
+ break
223
+ case 'b3 single header': // TODO: delete in major after singular "b3"
224
+ spanContext = this._extractB3SingleContext(carrier)
225
+ break
226
+ }
227
+
228
+ if (spanContext !== null) {
229
+ return spanContext
230
+ }
231
+ }
232
+
233
+ return this._extractSqsdContext(carrier)
141
234
  }
142
235
 
143
236
  _extractDatadogContext (carrier) {
@@ -153,10 +246,20 @@ class TextMapPropagator {
153
246
  return spanContext
154
247
  }
155
248
 
156
- _extractB3Context (carrier) {
157
- if (!this._config.experimental.b3) return null
249
+ _extractB3MultiContext (carrier) {
250
+ const b3 = this._extractB3MultipleHeaders(carrier)
251
+ if (!b3) return null
252
+ return this._extractB3Context(b3)
253
+ }
254
+
255
+ _extractB3SingleContext (carrier) {
256
+ if (!b3HeaderExpr.test(carrier[b3HeaderKey])) return null
257
+ const b3 = this._extractB3SingleHeader(carrier)
258
+ if (!b3) return null
259
+ return this._extractB3Context(b3)
260
+ }
158
261
 
159
- const b3 = this._extractB3Headers(carrier)
262
+ _extractB3Context (b3) {
160
263
  const debug = b3[b3FlagsKey] === '1'
161
264
  const priority = this._getPriority(b3[b3SampledKey], debug)
162
265
  const spanContext = this._extractGenericContext(b3, b3TraceKey, b3SpanKey, 16)
@@ -192,25 +295,75 @@ class TextMapPropagator {
192
295
  }
193
296
 
194
297
  _extractTraceparentContext (carrier) {
195
- if (!this._config.experimental.traceparent) return null
196
-
197
298
  const headerValue = carrier[traceparentKey]
198
299
  if (!headerValue) {
199
300
  return null
200
301
  }
201
- const matches = headerValue.match(traceparentExpr)
302
+ const matches = headerValue.trim().match(traceparentExpr)
202
303
  if (matches.length) {
203
- return new DatadogSpanContext({
204
- traceId: id(matches[2], 16),
205
- spanId: id(matches[3], 16),
206
- sampling: { priority: matches[4] === '01' ? 1 : 0 }
304
+ const [ version, traceId, spanId, flags, tail ] = matches.slice(1)
305
+ const traceparent = { version }
306
+ const tracestate = TraceState.fromString(carrier.tracestate)
307
+ if (invalidSegment.test(traceId)) return null
308
+ if (invalidSegment.test(spanId)) return null
309
+
310
+ // Version ff is considered invalid
311
+ if (version === 'ff') return null
312
+
313
+ // Version 00 should have no tail, but future versions may
314
+ if (tail && version === '00') return null
315
+
316
+ const spanContext = new DatadogSpanContext({
317
+ traceId: id(traceId, 16),
318
+ spanId: id(spanId, 16),
319
+ sampling: { priority: parseInt(flags, 10) & 1 ? 1 : 0 },
320
+ traceparent,
321
+ tracestate
207
322
  })
323
+
324
+ tracestate.forVendor('dd', state => {
325
+ for (const [key, value] of state.entries()) {
326
+ switch (key) {
327
+ case 's': {
328
+ const priority = parseInt(value, 10)
329
+ if (!Number.isInteger(priority)) continue
330
+ if (
331
+ (spanContext._sampling.priority === 1 && priority > 0) ||
332
+ (spanContext._sampling.priority === 0 && priority < 0)
333
+ ) {
334
+ spanContext._sampling.priority = priority
335
+ }
336
+ break
337
+ }
338
+ case 'o':
339
+ spanContext._trace.origin = value
340
+ break
341
+ case 't.dm': {
342
+ const mechanism = parseInt(value, 10)
343
+ if (Number.isInteger(mechanism)) {
344
+ spanContext._sampling.mechanism = mechanism
345
+ spanContext._trace.tags['_dd.p.dm'] = mechanism
346
+ }
347
+ break
348
+ }
349
+ default:
350
+ if (!key.startsWith('t.')) continue
351
+ spanContext._trace.tags[`_dd.p.${key.slice(2)}`] = value
352
+ .replace(/[\x7e]/gm, '=')
353
+ }
354
+ }
355
+ })
356
+
357
+ this._extractBaggageItems(carrier, spanContext)
358
+ return spanContext
208
359
  }
209
360
  return null
210
361
  }
211
362
 
212
363
  _extractGenericContext (carrier, traceKey, spanKey, radix) {
213
364
  if (carrier[traceKey] && carrier[spanKey]) {
365
+ if (invalidSegment.test(carrier[traceKey])) return null
366
+
214
367
  return new DatadogSpanContext({
215
368
  traceId: id(carrier[traceKey], radix),
216
369
  spanId: id(carrier[spanKey], radix)
@@ -220,35 +373,34 @@ class TextMapPropagator {
220
373
  return null
221
374
  }
222
375
 
223
- _extractB3Headers (carrier) {
224
- if (b3HeaderExpr.test(carrier[b3HeaderKey])) {
225
- return this._extractB3SingleHeader(carrier)
226
- } else {
227
- return this._extractB3MultipleHeaders(carrier)
228
- }
229
- }
230
-
231
376
  _extractB3MultipleHeaders (carrier) {
377
+ let empty = true
232
378
  const b3 = {}
233
379
 
234
380
  if (b3TraceExpr.test(carrier[b3TraceKey]) && b3SpanExpr.test(carrier[b3SpanKey])) {
235
381
  b3[b3TraceKey] = carrier[b3TraceKey]
236
382
  b3[b3SpanKey] = carrier[b3SpanKey]
383
+ empty = false
237
384
  }
238
385
 
239
386
  if (carrier[b3SampledKey]) {
240
387
  b3[b3SampledKey] = carrier[b3SampledKey]
388
+ empty = false
241
389
  }
242
390
 
243
391
  if (carrier[b3FlagsKey]) {
244
392
  b3[b3FlagsKey] = carrier[b3FlagsKey]
393
+ empty = false
245
394
  }
246
395
 
247
- return b3
396
+ return empty ? null : b3
248
397
  }
249
398
 
250
399
  _extractB3SingleHeader (carrier) {
251
- const parts = carrier[b3HeaderKey].split('-')
400
+ const header = carrier[b3HeaderKey]
401
+ if (!header) return null
402
+
403
+ const parts = header.split('-')
252
404
 
253
405
  if (parts[0] === 'd') {
254
406
  return {
@@ -299,7 +451,7 @@ class TextMapPropagator {
299
451
  const priority = parseInt(carrier[samplingKey], 10)
300
452
 
301
453
  if (Number.isInteger(priority)) {
302
- spanContext._sampling.priority = parseInt(carrier[samplingKey], 10)
454
+ spanContext._sampling.priority = priority
303
455
  }
304
456
  }
305
457
 
@@ -0,0 +1,99 @@
1
+ 'use strict'
2
+
3
+ const traceStateRegex = /[ \t]*([^=]+)=([ \t]*[^, \t]+)[ \t]*(,|$)/gim
4
+ const traceStateDataRegex = /([^:]+):([^;]+)(;|$)/gim
5
+
6
+ function fromString (Type, regex, value) {
7
+ if (typeof value !== 'string' || !value.length) {
8
+ return new Type()
9
+ }
10
+
11
+ const values = []
12
+ for (const row of value.matchAll(regex)) {
13
+ values.unshift(row.slice(1, 3))
14
+ }
15
+
16
+ return new Type(values)
17
+ }
18
+
19
+ function toString (map, pairSeparator, fieldSeparator) {
20
+ return Array.from(map.entries())
21
+ .reverse()
22
+ .map((pair) => pair.join(pairSeparator))
23
+ .join(fieldSeparator)
24
+ }
25
+
26
+ class TraceStateData extends Map {
27
+ constructor (...args) {
28
+ super(...args)
29
+ this.changed = false
30
+ }
31
+
32
+ set (...args) {
33
+ if (this.has(args[0]) && this.get(args[0]) === args[1]) {
34
+ return
35
+ }
36
+ this.changed = true
37
+ return super.set(...args)
38
+ }
39
+
40
+ delete (...args) {
41
+ this.changed = true
42
+ return super.delete(...args)
43
+ }
44
+
45
+ clear (...args) {
46
+ this.changed = true
47
+ return super.clear(...args)
48
+ }
49
+
50
+ static fromString (value) {
51
+ return fromString(TraceStateData, traceStateDataRegex, value)
52
+ }
53
+
54
+ toString () {
55
+ return toString(this, ':', ';')
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Pairs are stored in reverse of the serialized format to rely on set ordering
61
+ * new entries at the end to express update movement.
62
+ */
63
+ class TraceState extends Map {
64
+ // Delete entries on update to ensure they're moved to the end of the list
65
+ set (key, value) {
66
+ if (this.has(key)) {
67
+ this.delete(key)
68
+ }
69
+
70
+ return super.set(key, value)
71
+ }
72
+
73
+ forVendor (vendor, handle) {
74
+ const data = super.get(vendor)
75
+ const state = TraceStateData.fromString(data)
76
+ const result = handle(state)
77
+
78
+ if (state.changed) {
79
+ const value = state.toString()
80
+ if (value) {
81
+ this.set(vendor, state.toString())
82
+ } else {
83
+ this.delete(vendor)
84
+ }
85
+ }
86
+
87
+ return result
88
+ }
89
+
90
+ static fromString (value) {
91
+ return fromString(TraceState, traceStateRegex, value)
92
+ }
93
+
94
+ toString () {
95
+ return toString(this, '=', ',')
96
+ }
97
+ }
98
+
99
+ module.exports = TraceState
@@ -150,7 +150,8 @@ class DatadogSpan {
150
150
  parentId: parent._spanId,
151
151
  sampling: parent._sampling,
152
152
  baggageItems: Object.assign({}, parent._baggageItems),
153
- trace: parent._trace
153
+ trace: parent._trace,
154
+ tracestate: parent._tracestate
154
155
  })
155
156
  } else {
156
157
  const spanId = id()
@@ -12,8 +12,10 @@ class DatadogSpanContext {
12
12
  this._name = props.name
13
13
  this._isFinished = props.isFinished || false
14
14
  this._tags = props.tags || {}
15
- this._sampling = props.sampling || {}
15
+ this._sampling = Object.assign({}, props.sampling)
16
16
  this._baggageItems = props.baggageItems || {}
17
+ this._traceparent = props.traceparent
18
+ this._tracestate = props.tracestate
17
19
  this._noop = props.noop || null
18
20
  this._trace = props.trace || {
19
21
  started: [],
@@ -31,10 +33,11 @@ class DatadogSpanContext {
31
33
  }
32
34
 
33
35
  toTraceparent () {
34
- const sampling = this._sampling.priority >= AUTO_KEEP ? '01' : '00'
36
+ const flags = this._sampling.priority >= AUTO_KEEP ? '01' : '00'
35
37
  const traceId = this._traceId.toString(16).padStart(32, '0')
36
38
  const spanId = this._spanId.toString(16).padStart(16, '0')
37
- return `01-${traceId}-${spanId}-${sampling}`
39
+ const version = (this._traceparent && this._traceparent.version) || '00'
40
+ return `${version}-${traceId}-${spanId}-${flags}`
38
41
  }
39
42
  }
40
43
 
@@ -5,11 +5,18 @@ const {
5
5
  getTestCommonTags,
6
6
  getCodeOwnersForFilename,
7
7
  TEST_CODE_OWNERS,
8
- CI_APP_ORIGIN
8
+ CI_APP_ORIGIN,
9
+ getTestSessionCommonTags,
10
+ getTestModuleCommonTags,
11
+ TEST_SUITE_ID,
12
+ TEST_MODULE_ID,
13
+ TEST_SESSION_ID,
14
+ TEST_COMMAND,
15
+ TEST_BUNDLE
9
16
  } = require('./util/test')
10
- const { COMPONENT } = require('../constants')
11
-
12
17
  const Plugin = require('./plugin')
18
+ const { COMPONENT } = require('../constants')
19
+ const log = require('../log')
13
20
 
14
21
  module.exports = class CiPlugin extends Plugin {
15
22
  constructor (...args) {
@@ -20,7 +27,9 @@ module.exports = class CiPlugin extends Plugin {
20
27
  return onDone({ err: new Error('CI Visibility was not initialized correctly') })
21
28
  }
22
29
  this.tracer._exporter.getItrConfiguration(this.testConfiguration, (err, itrConfig) => {
23
- if (!err) {
30
+ if (err) {
31
+ log.error(`Error fetching intelligent test runner configuration: ${err.message}`)
32
+ } else {
24
33
  this.itrConfig = itrConfig
25
34
  }
26
35
  onDone({ err, itrConfig })
@@ -32,9 +41,40 @@ module.exports = class CiPlugin extends Plugin {
32
41
  return onDone({ err: new Error('CI Visibility was not initialized correctly') })
33
42
  }
34
43
  this.tracer._exporter.getSkippableSuites(this.testConfiguration, (err, skippableSuites) => {
44
+ if (err) {
45
+ log.error(`Error fetching skippable suites: ${err.message}`)
46
+ }
35
47
  onDone({ err, skippableSuites })
36
48
  })
37
49
  })
50
+
51
+ this.addSub(`ci:${this.constructor.name}:session:start`, ({ command, frameworkVersion, rootDir }) => {
52
+ const childOf = getTestParentSpan(this.tracer)
53
+ const testSessionSpanMetadata = getTestSessionCommonTags(command, frameworkVersion)
54
+ const testModuleSpanMetadata = getTestModuleCommonTags(command, frameworkVersion)
55
+
56
+ this.command = command
57
+ this.frameworkVersion = frameworkVersion
58
+ // only for playwright
59
+ this.rootDir = rootDir
60
+
61
+ this.testSessionSpan = this.tracer.startSpan(`${this.constructor.name}.test_session`, {
62
+ childOf,
63
+ tags: {
64
+ [COMPONENT]: this.constructor.name,
65
+ ...this.testEnvironmentMetadata,
66
+ ...testSessionSpanMetadata
67
+ }
68
+ })
69
+ this.testModuleSpan = this.tracer.startSpan(`${this.constructor.name}.test_module`, {
70
+ childOf: this.testSessionSpan,
71
+ tags: {
72
+ [COMPONENT]: this.constructor.name,
73
+ ...this.testEnvironmentMetadata,
74
+ ...testModuleSpanMetadata
75
+ }
76
+ })
77
+ })
38
78
  }
39
79
 
40
80
  configure (config) {
@@ -65,25 +105,45 @@ module.exports = class CiPlugin extends Plugin {
65
105
  }
66
106
  }
67
107
 
68
- startTestSpan (name, suite, extraTags, childOf) {
69
- const parent = childOf || getTestParentSpan(this.tracer)
70
- const testCommonTags = getTestCommonTags(name, suite, this.tracer._version)
108
+ startTestSpan (testName, testSuite, testSuiteSpan, extraTags = {}) {
109
+ const childOf = getTestParentSpan(this.tracer)
71
110
 
72
- const testTags = {
73
- ...testCommonTags,
111
+ let testTags = {
112
+ ...getTestCommonTags(testName, testSuite, this.frameworkVersion),
74
113
  [COMPONENT]: this.constructor.name,
75
114
  ...extraTags
76
115
  }
77
116
 
78
- const codeOwners = getCodeOwnersForFilename(suite, this.codeOwnersEntries)
79
-
117
+ const codeOwners = getCodeOwnersForFilename(testSuite, this.codeOwnersEntries)
80
118
  if (codeOwners) {
81
119
  testTags[TEST_CODE_OWNERS] = codeOwners
82
120
  }
83
121
 
122
+ if (testSuiteSpan) {
123
+ // This is a hack to get good time resolution on test events, while keeping
124
+ // the test event as the root span of its trace.
125
+ childOf._trace.startTime = testSuiteSpan.context()._trace.startTime
126
+ childOf._trace.ticks = testSuiteSpan.context()._trace.ticks
127
+
128
+ const suiteTags = {
129
+ [TEST_SUITE_ID]: testSuiteSpan.context().toSpanId(),
130
+ [TEST_SESSION_ID]: testSuiteSpan.context().toTraceId(),
131
+ [TEST_COMMAND]: testSuiteSpan.context()._tags[TEST_COMMAND],
132
+ [TEST_BUNDLE]: testSuiteSpan.context()._tags[TEST_COMMAND]
133
+ }
134
+ if (testSuiteSpan.context()._parentId) {
135
+ suiteTags[TEST_MODULE_ID] = testSuiteSpan.context()._parentId.toString(10)
136
+ }
137
+
138
+ testTags = {
139
+ ...testTags,
140
+ ...suiteTags
141
+ }
142
+ }
143
+
84
144
  const testSpan = this.tracer
85
145
  .startSpan(`${this.constructor.name}.test`, {
86
- childOf: parent,
146
+ childOf,
87
147
  tags: {
88
148
  ...this.testEnvironmentMetadata,
89
149
  ...testTags
@@ -1,7 +1,9 @@
1
1
  'use strict'
2
2
 
3
3
  module.exports = {
4
+ get '@aws-sdk/smithy-client' () { return require('../../../datadog-plugin-aws-sdk/src') },
4
5
  get '@cucumber/cucumber' () { return require('../../../datadog-plugin-cucumber/src') },
6
+ get '@playwright/test' () { return require('../../../datadog-plugin-playwright/src') },
5
7
  get '@elastic/elasticsearch' () { return require('../../../datadog-plugin-elasticsearch/src') },
6
8
  get '@elastic/transport' () { return require('../../../datadog-plugin-elasticsearch/src') },
7
9
  get '@google-cloud/pubsub' () { return require('../../../datadog-plugin-google-cloud-pubsub/src') },