dd-trace 4.38.1 → 4.40.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 (73) hide show
  1. package/LICENSE-3rdparty.csv +0 -3
  2. package/README.md +8 -18
  3. package/ci/init.js +7 -0
  4. package/ext/exporters.d.ts +1 -0
  5. package/ext/exporters.js +2 -1
  6. package/ext/tags.d.ts +1 -0
  7. package/ext/tags.js +1 -0
  8. package/index.d.ts +18 -3
  9. package/initialize.mjs +52 -0
  10. package/package.json +9 -12
  11. package/packages/datadog-instrumentations/src/amqplib.js +5 -2
  12. package/packages/datadog-instrumentations/src/apollo-server-core.js +0 -1
  13. package/packages/datadog-instrumentations/src/apollo-server.js +0 -1
  14. package/packages/datadog-instrumentations/src/body-parser.js +0 -1
  15. package/packages/datadog-instrumentations/src/check_require_cache.js +67 -5
  16. package/packages/datadog-instrumentations/src/cookie-parser.js +0 -1
  17. package/packages/datadog-instrumentations/src/express.js +0 -1
  18. package/packages/datadog-instrumentations/src/graphql.js +0 -2
  19. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  20. package/packages/datadog-instrumentations/src/helpers/register.js +5 -2
  21. package/packages/datadog-instrumentations/src/http/server.js +0 -1
  22. package/packages/datadog-instrumentations/src/jest.js +6 -3
  23. package/packages/datadog-instrumentations/src/mocha/common.js +48 -0
  24. package/packages/datadog-instrumentations/src/mocha/main.js +487 -0
  25. package/packages/datadog-instrumentations/src/mocha/utils.js +306 -0
  26. package/packages/datadog-instrumentations/src/mocha/worker.js +51 -0
  27. package/packages/datadog-instrumentations/src/mocha.js +4 -673
  28. package/packages/datadog-instrumentations/src/openai.js +188 -17
  29. package/packages/datadog-instrumentations/src/playwright.js +4 -3
  30. package/packages/datadog-instrumentations/src/router.js +1 -1
  31. package/packages/datadog-instrumentations/src/selenium.js +13 -6
  32. package/packages/datadog-plugin-graphql/src/resolve.js +4 -0
  33. package/packages/datadog-plugin-mocha/src/index.js +82 -8
  34. package/packages/datadog-plugin-next/src/index.js +1 -2
  35. package/packages/datadog-plugin-openai/src/index.js +219 -73
  36. package/packages/dd-trace/src/appsec/addresses.js +4 -2
  37. package/packages/dd-trace/src/appsec/blocking.js +19 -25
  38. package/packages/dd-trace/src/appsec/channels.js +2 -1
  39. package/packages/dd-trace/src/appsec/graphql.js +10 -3
  40. package/packages/dd-trace/src/appsec/index.js +11 -4
  41. package/packages/dd-trace/src/appsec/rasp.js +35 -0
  42. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
  43. package/packages/dd-trace/src/appsec/remote_config/index.js +1 -0
  44. package/packages/dd-trace/src/appsec/rule_manager.js +15 -25
  45. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +2 -5
  46. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +3 -1
  47. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +5 -1
  48. package/packages/dd-trace/src/config.js +97 -22
  49. package/packages/dd-trace/src/constants.js +2 -0
  50. package/packages/dd-trace/src/encode/0.4.js +47 -8
  51. package/packages/dd-trace/src/exporter.js +1 -0
  52. package/packages/dd-trace/src/flare/file.js +44 -0
  53. package/packages/dd-trace/src/flare/index.js +98 -0
  54. package/packages/dd-trace/src/log/channels.js +54 -29
  55. package/packages/dd-trace/src/log/writer.js +7 -49
  56. package/packages/dd-trace/src/opentelemetry/span.js +8 -0
  57. package/packages/dd-trace/src/opentracing/propagation/text_map.js +57 -12
  58. package/packages/dd-trace/src/plugins/index.js +1 -0
  59. package/packages/dd-trace/src/plugins/util/ip_extractor.js +1 -1
  60. package/packages/dd-trace/src/plugins/util/test.js +6 -0
  61. package/packages/dd-trace/src/priority_sampler.js +8 -4
  62. package/packages/dd-trace/src/profiler.js +2 -1
  63. package/packages/dd-trace/src/profiling/config.js +1 -0
  64. package/packages/dd-trace/src/profiling/profiler.js +1 -1
  65. package/packages/dd-trace/src/profiling/{ssi-telemetry.js → ssi-heuristics.js} +64 -36
  66. package/packages/dd-trace/src/profiling/ssi-telemetry-mock-profiler.js +4 -9
  67. package/packages/dd-trace/src/proxy.js +49 -15
  68. package/packages/dd-trace/src/ritm.js +13 -1
  69. package/packages/dd-trace/src/sampling_rule.js +2 -1
  70. package/packages/dd-trace/src/startup-log.js +19 -15
  71. package/packages/dd-trace/src/telemetry/index.js +6 -2
  72. package/packages/dd-trace/src/tracer.js +3 -0
  73. package/packages/dd-trace/src/plugins/util/ip_blocklist.js +0 -51
@@ -0,0 +1,44 @@
1
+ 'use strict'
2
+
3
+ const { Writable } = require('stream')
4
+
5
+ const INITIAL_SIZE = 64 * 1024
6
+
7
+ class FlareFile extends Writable {
8
+ constructor () {
9
+ super()
10
+
11
+ this.length = 0
12
+
13
+ this._buffer = Buffer.alloc(INITIAL_SIZE)
14
+ }
15
+
16
+ get data () {
17
+ return this._buffer.subarray(0, this.length)
18
+ }
19
+
20
+ _write (chunk, encoding, callback) {
21
+ const length = Buffer.byteLength(chunk)
22
+
23
+ this._reserve(length)
24
+
25
+ if (Buffer.isBuffer(chunk)) {
26
+ this.length += chunk.copy(this._buffer, this.length)
27
+ } else {
28
+ this.length += this._buffer.write(chunk, encoding)
29
+ }
30
+
31
+ callback()
32
+ }
33
+
34
+ _reserve (length) {
35
+ while (this.length + length > this._buffer.length) {
36
+ const buffer = Buffer.alloc(this.length * 2)
37
+
38
+ this._buffer.copy(buffer)
39
+ this._buffer = buffer
40
+ }
41
+ }
42
+ }
43
+
44
+ module.exports = FlareFile
@@ -0,0 +1,98 @@
1
+ 'use strict'
2
+
3
+ const log = require('../log')
4
+ const startupLog = require('../startup-log')
5
+ const FlareFile = require('./file')
6
+ const { LogChannel } = require('../log/channels')
7
+ const request = require('../exporters/common/request')
8
+ const FormData = require('../exporters/common/form-data')
9
+
10
+ const MAX_LOG_SIZE = 12 * 1024 * 1024 // 12MB soft limit
11
+ const TIMEOUT = 20 * 1000 * 60
12
+
13
+ let logChannel = null
14
+ let tracerLogs = null
15
+ let timer
16
+ let tracerConfig = null
17
+
18
+ const logger = {
19
+ debug: (msg) => recordLog(msg),
20
+ info: (msg) => recordLog(msg),
21
+ warn: (msg) => recordLog(msg),
22
+ error: (err) => recordLog(err.stack)
23
+ }
24
+
25
+ const flare = {
26
+ enable (tracerConfig_) {
27
+ tracerConfig = tracerConfig_
28
+ },
29
+
30
+ disable () {
31
+ tracerConfig = null
32
+
33
+ flare.cleanup()
34
+ },
35
+
36
+ prepare (logLevel) {
37
+ if (!tracerConfig) return
38
+
39
+ logChannel?.unsubscribe(logger)
40
+ logChannel = new LogChannel(logLevel)
41
+ logChannel.subscribe(logger)
42
+ tracerLogs = tracerLogs || new FlareFile()
43
+ timer = timer || setTimeout(flare.cleanup, TIMEOUT)
44
+ },
45
+
46
+ send (task) {
47
+ if (!tracerConfig) return
48
+
49
+ const tracerInfo = new FlareFile()
50
+
51
+ tracerInfo.write(JSON.stringify(startupLog.tracerInfo(), null, 2))
52
+
53
+ flare._sendFile(task, tracerInfo, 'tracer_info.txt')
54
+ flare._sendFile(task, tracerLogs, 'tracer_logs.txt')
55
+
56
+ flare.cleanup()
57
+ },
58
+
59
+ cleanup () {
60
+ logChannel?.unsubscribe(logger)
61
+ timer = clearTimeout(timer)
62
+ logChannel = null
63
+ tracerLogs = null
64
+ },
65
+
66
+ _sendFile (task, file, filename) {
67
+ if (!file) return
68
+
69
+ const form = new FormData()
70
+
71
+ form.append('case_id', task.case_id)
72
+ form.append('hostname', task.hostname)
73
+ form.append('email', task.user_handle)
74
+ form.append('source', 'tracer_nodejs')
75
+ form.append('flare_file', file.data, { filename })
76
+
77
+ request(form, {
78
+ url: tracerConfig.url,
79
+ hostname: tracerConfig.hostname,
80
+ port: tracerConfig.port,
81
+ method: 'POST',
82
+ path: '/tracer_flare/v1',
83
+ headers: form.getHeaders()
84
+ }, (err) => {
85
+ if (err) {
86
+ log.error(err)
87
+ }
88
+ })
89
+ }
90
+ }
91
+
92
+ function recordLog (msg) {
93
+ if (tracerLogs.length > MAX_LOG_SIZE) return
94
+
95
+ tracerLogs.write(`${msg}\n`) // TODO: gzip
96
+ }
97
+
98
+ module.exports = flare
@@ -3,44 +3,69 @@
3
3
  const { channel } = require('dc-polyfill')
4
4
 
5
5
  const Level = {
6
- Debug: 'debug',
7
- Info: 'info',
8
- Warn: 'warn',
9
- Error: 'error'
6
+ trace: 20,
7
+ debug: 20,
8
+ info: 30,
9
+ warn: 40,
10
+ error: 50,
11
+ critical: 50,
12
+ off: 100
10
13
  }
11
14
 
12
- const defaultLevel = Level.Debug
15
+ const debugChannel = channel('datadog:log:debug')
16
+ const infoChannel = channel('datadog:log:info')
17
+ const warnChannel = channel('datadog:log:warn')
18
+ const errorChannel = channel('datadog:log:error')
13
19
 
14
- // based on: https://github.com/trentm/node-bunyan#levels
15
- const logChannels = {
16
- [Level.Debug]: createLogChannel(Level.Debug, 20),
17
- [Level.Info]: createLogChannel(Level.Info, 30),
18
- [Level.Warn]: createLogChannel(Level.Warn, 40),
19
- [Level.Error]: createLogChannel(Level.Error, 50)
20
- }
20
+ const defaultLevel = Level.debug
21
21
 
22
- function createLogChannel (name, logLevel) {
23
- const logChannel = channel(`datadog:log:${name}`)
24
- logChannel.logLevel = logLevel
25
- return logChannel
22
+ function getChannelLogLevel (level) {
23
+ return level && typeof level === 'string'
24
+ ? Level[level.toLowerCase().trim()] || defaultLevel
25
+ : defaultLevel
26
26
  }
27
27
 
28
- function getChannelLogLevel (level) {
29
- let logChannel
30
- if (level && typeof level === 'string') {
31
- logChannel = logChannels[level.toLowerCase().trim()] || logChannels[defaultLevel]
32
- } else {
33
- logChannel = logChannels[defaultLevel]
28
+ class LogChannel {
29
+ constructor (level) {
30
+ this._level = getChannelLogLevel(level)
31
+ }
32
+
33
+ subscribe (logger) {
34
+ if (Level.debug >= this._level) {
35
+ debugChannel.subscribe(logger.debug)
36
+ }
37
+ if (Level.info >= this._level) {
38
+ infoChannel.subscribe(logger.info)
39
+ }
40
+ if (Level.warn >= this._level) {
41
+ warnChannel.subscribe(logger.warn)
42
+ }
43
+ if (Level.error >= this._level) {
44
+ errorChannel.subscribe(logger.error)
45
+ }
46
+ }
47
+
48
+ unsubscribe (logger) {
49
+ if (debugChannel.hasSubscribers) {
50
+ debugChannel.unsubscribe(logger.debug)
51
+ }
52
+ if (infoChannel.hasSubscribers) {
53
+ infoChannel.unsubscribe(logger.info)
54
+ }
55
+ if (warnChannel.hasSubscribers) {
56
+ warnChannel.unsubscribe(logger.warn)
57
+ }
58
+ if (errorChannel.hasSubscribers) {
59
+ errorChannel.unsubscribe(logger.error)
60
+ }
34
61
  }
35
- return logChannel.logLevel
36
62
  }
37
63
 
38
64
  module.exports = {
39
- Level,
40
- getChannelLogLevel,
65
+ LogChannel,
41
66
 
42
- debugChannel: logChannels[Level.Debug],
43
- infoChannel: logChannels[Level.Info],
44
- warnChannel: logChannels[Level.Warn],
45
- errorChannel: logChannels[Level.Error]
67
+ debugChannel,
68
+ infoChannel,
69
+ warnChannel,
70
+ errorChannel
46
71
  }
@@ -1,8 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { storage } = require('../../../datadog-core')
4
- const { getChannelLogLevel, debugChannel, infoChannel, warnChannel, errorChannel } = require('./channels')
5
-
4
+ const { LogChannel } = require('./channels')
6
5
  const defaultLogger = {
7
6
  debug: msg => console.debug(msg), /* eslint-disable-line no-console */
8
7
  info: msg => console.info(msg), /* eslint-disable-line no-console */
@@ -12,7 +11,7 @@ const defaultLogger = {
12
11
 
13
12
  let enabled = false
14
13
  let logger = defaultLogger
15
- let logLevel = getChannelLogLevel()
14
+ let logChannel = new LogChannel()
16
15
 
17
16
  function withNoop (fn) {
18
17
  const store = storage.getStore()
@@ -23,45 +22,21 @@ function withNoop (fn) {
23
22
  }
24
23
 
25
24
  function unsubscribeAll () {
26
- if (debugChannel.hasSubscribers) {
27
- debugChannel.unsubscribe(onDebug)
28
- }
29
- if (infoChannel.hasSubscribers) {
30
- infoChannel.unsubscribe(onInfo)
31
- }
32
- if (warnChannel.hasSubscribers) {
33
- warnChannel.unsubscribe(onWarn)
34
- }
35
- if (errorChannel.hasSubscribers) {
36
- errorChannel.unsubscribe(onError)
37
- }
25
+ logChannel.unsubscribe({ debug, info, warn, error })
38
26
  }
39
27
 
40
- function toggleSubscription (enable) {
28
+ function toggleSubscription (enable, level) {
41
29
  unsubscribeAll()
42
30
 
43
31
  if (enable) {
44
- if (debugChannel.logLevel >= logLevel) {
45
- debugChannel.subscribe(onDebug)
46
- }
47
- if (infoChannel.logLevel >= logLevel) {
48
- infoChannel.subscribe(onInfo)
49
- }
50
- if (warnChannel.logLevel >= logLevel) {
51
- warnChannel.subscribe(onWarn)
52
- }
53
- if (errorChannel.logLevel >= logLevel) {
54
- errorChannel.subscribe(onError)
55
- }
32
+ logChannel = new LogChannel(level)
33
+ logChannel.subscribe({ debug, info, warn, error })
56
34
  }
57
35
  }
58
36
 
59
37
  function toggle (enable, level) {
60
- if (level !== undefined) {
61
- logLevel = getChannelLogLevel(level)
62
- }
63
38
  enabled = enable
64
- toggleSubscription(enabled)
39
+ toggleSubscription(enabled, level)
65
40
  }
66
41
 
67
42
  function use (newLogger) {
@@ -73,26 +48,9 @@ function use (newLogger) {
73
48
  function reset () {
74
49
  logger = defaultLogger
75
50
  enabled = false
76
- logLevel = getChannelLogLevel()
77
51
  toggleSubscription(false)
78
52
  }
79
53
 
80
- function onError (err) {
81
- if (enabled) error(err)
82
- }
83
-
84
- function onWarn (message) {
85
- if (enabled) warn(message)
86
- }
87
-
88
- function onInfo (message) {
89
- if (enabled) info(message)
90
- }
91
-
92
- function onDebug (message) {
93
- if (enabled) debug(message)
94
- }
95
-
96
54
  function error (err) {
97
55
  if (typeof err !== 'object' || !err) {
98
56
  err = String(err)
@@ -179,11 +179,19 @@ class Span {
179
179
  }
180
180
 
181
181
  setAttribute (key, value) {
182
+ if (key === 'http.response.status_code') {
183
+ this._ddSpan.setTag('http.status_code', value.toString())
184
+ }
185
+
182
186
  this._ddSpan.setTag(key, value)
183
187
  return this
184
188
  }
185
189
 
186
190
  setAttributes (attributes) {
191
+ if ('http.response.status_code' in attributes) {
192
+ attributes['http.status_code'] = attributes['http.response.status_code'].toString()
193
+ }
194
+
187
195
  this._ddSpan.addTags(attributes)
188
196
  return this
189
197
  }
@@ -5,6 +5,7 @@ const id = require('../../id')
5
5
  const DatadogSpanContext = require('../span_context')
6
6
  const log = require('../../log')
7
7
  const TraceState = require('./tracestate')
8
+ const tags = require('../../../../../ext/tags')
8
9
 
9
10
  const { AUTO_KEEP, AUTO_REJECT, USER_KEEP } = require('../../../../../ext/priority')
10
11
 
@@ -39,6 +40,7 @@ const tracestateTagKeyFilter = /[^\x21-\x2b\x2d-\x3c\x3e-\x7e]/g
39
40
  // Tag values in tracestate replace ',', '~' and ';' with '_'
40
41
  const tracestateTagValueFilter = /[^\x20-\x2b\x2d-\x3a\x3c-\x7d]/g
41
42
  const invalidSegment = /^0+$/
43
+ const zeroTraceId = '0000000000000000'
42
44
 
43
45
  class TextMapPropagator {
44
46
  constructor (config) {
@@ -175,9 +177,9 @@ class TextMapPropagator {
175
177
  // SpanContext was created by a ddtrace span.
176
178
  // Last datadog span id should be set to the current span.
177
179
  state.set('p', spanContext._spanId)
178
- } else if (spanContext._trace.tags['_dd.parent_id']) {
180
+ } else if (spanContext._trace.tags[tags.DD_PARENT_ID]) {
179
181
  // Propagate the last Datadog span id set on the remote span.
180
- state.set('p', spanContext._trace.tags['_dd.parent_id'])
182
+ state.set('p', spanContext._trace.tags[tags.DD_PARENT_ID])
181
183
  }
182
184
  state.set('s', priority)
183
185
  if (mechanism) {
@@ -214,9 +216,56 @@ class TextMapPropagator {
214
216
  return this._config.tracePropagationStyle[mode].includes(name)
215
217
  }
216
218
 
219
+ _hasTraceIdConflict (w3cSpanContext, firstSpanContext) {
220
+ return w3cSpanContext !== null &&
221
+ firstSpanContext.toTraceId(true) === w3cSpanContext.toTraceId(true) &&
222
+ firstSpanContext.toSpanId() !== w3cSpanContext.toSpanId()
223
+ }
224
+
225
+ _hasParentIdInTags (spanContext) {
226
+ return tags.DD_PARENT_ID in spanContext._trace.tags &&
227
+ spanContext._trace.tags[tags.DD_PARENT_ID] !== zeroTraceId
228
+ }
229
+
230
+ _updateParentIdFromDdHeaders (carrier, firstSpanContext) {
231
+ const ddCtx = this._extractDatadogContext(carrier)
232
+ if (ddCtx !== null) {
233
+ firstSpanContext._trace.tags[tags.DD_PARENT_ID] = ddCtx._spanId.toString().padStart(16, '0')
234
+ }
235
+ }
236
+
237
+ _resolveTraceContextConflicts (w3cSpanContext, firstSpanContext, carrier) {
238
+ if (!this._hasTraceIdConflict(w3cSpanContext, firstSpanContext)) {
239
+ return firstSpanContext
240
+ }
241
+ if (this._hasParentIdInTags(w3cSpanContext)) {
242
+ // tracecontext headers contain a p value, ensure this value is sent to backend
243
+ firstSpanContext._trace.tags[tags.DD_PARENT_ID] = w3cSpanContext._trace.tags[tags.DD_PARENT_ID]
244
+ } else {
245
+ // if p value is not present in tracestate, use the parent id from the datadog headers
246
+ this._updateParentIdFromDdHeaders(carrier, firstSpanContext)
247
+ }
248
+ // the span_id in tracecontext takes precedence over the first extracted propagation style
249
+ firstSpanContext._spanId = w3cSpanContext._spanId
250
+ return firstSpanContext
251
+ }
252
+
217
253
  _extractSpanContext (carrier) {
254
+ let spanContext = null
218
255
  for (const extractor of this._config.tracePropagationStyle.extract) {
219
- let spanContext = null
256
+ // add logic to ensure tracecontext headers takes precedence over other extracted headers
257
+ if (spanContext !== null) {
258
+ if (this._config.tracePropagationExtractFirst) {
259
+ return spanContext
260
+ }
261
+ if (extractor !== 'tracecontext') {
262
+ continue
263
+ }
264
+ spanContext = this._resolveTraceContextConflicts(
265
+ this._extractTraceparentContext(carrier), spanContext, carrier)
266
+ break
267
+ }
268
+
220
269
  switch (extractor) {
221
270
  case 'datadog':
222
271
  spanContext = this._extractDatadogContext(carrier)
@@ -238,13 +287,9 @@ class TextMapPropagator {
238
287
  default:
239
288
  log.warn(`Unknown propagation style: ${extractor}`)
240
289
  }
241
-
242
- if (spanContext !== null) {
243
- return spanContext
244
- }
245
290
  }
246
291
 
247
- return this._extractSqsdContext(carrier)
292
+ return spanContext || this._extractSqsdContext(carrier)
248
293
  }
249
294
 
250
295
  _extractDatadogContext (carrier) {
@@ -354,7 +399,7 @@ class TextMapPropagator {
354
399
  for (const [key, value] of state.entries()) {
355
400
  switch (key) {
356
401
  case 'p': {
357
- spanContext._trace.tags['_dd.parent_id'] = value
402
+ spanContext._trace.tags[tags.DD_PARENT_ID] = value
358
403
  break
359
404
  }
360
405
  case 's': {
@@ -387,8 +432,8 @@ class TextMapPropagator {
387
432
  }
388
433
  })
389
434
 
390
- if (!spanContext._trace.tags['_dd.parent_id']) {
391
- spanContext._trace.tags['_dd.parent_id'] = '0000000000000000'
435
+ if (!spanContext._trace.tags[tags.DD_PARENT_ID]) {
436
+ spanContext._trace.tags[tags.DD_PARENT_ID] = zeroTraceId
392
437
  }
393
438
 
394
439
  this._extractBaggageItems(carrier, spanContext)
@@ -531,7 +576,7 @@ class TextMapPropagator {
531
576
 
532
577
  const tid = traceId.substring(0, 16)
533
578
 
534
- if (tid === '0000000000000000') return
579
+ if (tid === zeroTraceId) return
535
580
 
536
581
  spanContext._trace.tags['_dd.p.tid'] = tid
537
582
  }
@@ -54,6 +54,7 @@ module.exports = {
54
54
  get 'microgateway-core' () { return require('../../../datadog-plugin-microgateway-core/src') },
55
55
  get mocha () { return require('../../../datadog-plugin-mocha/src') },
56
56
  get 'mocha-each' () { return require('../../../datadog-plugin-mocha/src') },
57
+ get workerpool () { return require('../../../datadog-plugin-mocha/src') },
57
58
  get moleculer () { return require('../../../datadog-plugin-moleculer/src') },
58
59
  get mongodb () { return require('../../../datadog-plugin-mongodb-core/src') },
59
60
  get 'mongodb-core' () { return require('../../../datadog-plugin-mongodb-core/src') },
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const BlockList = require('./ip_blocklist')
3
+ const { BlockList } = require('net')
4
4
  const net = require('net')
5
5
 
6
6
  const ipHeaderList = [
@@ -62,6 +62,7 @@ const JEST_TEST_RUNNER = 'test.jest.test_runner'
62
62
  const JEST_DISPLAY_NAME = 'test.jest.display_name'
63
63
 
64
64
  const CUCUMBER_IS_PARALLEL = 'test.cucumber.is_parallel'
65
+ const MOCHA_IS_PARALLEL = 'test.mocha.is_parallel'
65
66
 
66
67
  const TEST_ITR_TESTS_SKIPPED = '_dd.ci.itr.tests_skipped'
67
68
  const TEST_ITR_SKIPPING_ENABLED = 'test.itr.tests_skipping.enabled'
@@ -87,6 +88,9 @@ const JEST_WORKER_COVERAGE_PAYLOAD_CODE = 61
87
88
  // cucumber worker variables
88
89
  const CUCUMBER_WORKER_TRACE_PAYLOAD_CODE = 70
89
90
 
91
+ // mocha worker variables
92
+ const MOCHA_WORKER_TRACE_PAYLOAD_CODE = 80
93
+
90
94
  // Early flake detection util strings
91
95
  const EFD_STRING = "Retried by Datadog's Early Flake Detection"
92
96
  const EFD_TEST_NAME_REGEX = new RegExp(EFD_STRING + ' \\(#\\d+\\): ', 'g')
@@ -98,6 +102,7 @@ module.exports = {
98
102
  JEST_TEST_RUNNER,
99
103
  JEST_DISPLAY_NAME,
100
104
  CUCUMBER_IS_PARALLEL,
105
+ MOCHA_IS_PARALLEL,
101
106
  TEST_TYPE,
102
107
  TEST_NAME,
103
108
  TEST_SUITE,
@@ -111,6 +116,7 @@ module.exports = {
111
116
  JEST_WORKER_TRACE_PAYLOAD_CODE,
112
117
  JEST_WORKER_COVERAGE_PAYLOAD_CODE,
113
118
  CUCUMBER_WORKER_TRACE_PAYLOAD_CODE,
119
+ MOCHA_WORKER_TRACE_PAYLOAD_CODE,
114
120
  TEST_SOURCE_START,
115
121
  TEST_SKIPPED_BY_ITR,
116
122
  TEST_CONFIGURATION_BROWSER_NAME,
@@ -10,6 +10,8 @@ const {
10
10
  SAMPLING_MECHANISM_AGENT,
11
11
  SAMPLING_MECHANISM_RULE,
12
12
  SAMPLING_MECHANISM_MANUAL,
13
+ SAMPLING_MECHANISM_REMOTE_USER,
14
+ SAMPLING_MECHANISM_REMOTE_DYNAMIC,
13
15
  SAMPLING_RULE_DECISION,
14
16
  SAMPLING_LIMIT_DECISION,
15
17
  SAMPLING_AGENT_DECISION,
@@ -41,9 +43,9 @@ class PrioritySampler {
41
43
  this.update({})
42
44
  }
43
45
 
44
- configure (env, { sampleRate, rateLimit = 100, rules = [] } = {}) {
46
+ configure (env, { sampleRate, provenance = undefined, rateLimit = 100, rules = [] } = {}) {
45
47
  this._env = env
46
- this._rules = this._normalizeRules(rules, sampleRate, rateLimit)
48
+ this._rules = this._normalizeRules(rules, sampleRate, rateLimit, provenance)
47
49
  this._limiter = new RateLimiter(rateLimit)
48
50
 
49
51
  setSamplingRules(this._rules)
@@ -137,6 +139,8 @@ class PrioritySampler {
137
139
  _getPriorityByRule (context, rule) {
138
140
  context._trace[SAMPLING_RULE_DECISION] = rule.sampleRate
139
141
  context._sampling.mechanism = SAMPLING_MECHANISM_RULE
142
+ if (rule.provenance === 'customer') context._sampling.mechanism = SAMPLING_MECHANISM_REMOTE_USER
143
+ if (rule.provenance === 'dynamic') context._sampling.mechanism = SAMPLING_MECHANISM_REMOTE_DYNAMIC
140
144
 
141
145
  return rule.sample() && this._isSampledByRateLimit(context)
142
146
  ? USER_KEEP
@@ -181,11 +185,11 @@ class PrioritySampler {
181
185
  }
182
186
  }
183
187
 
184
- _normalizeRules (rules, sampleRate, rateLimit) {
188
+ _normalizeRules (rules, sampleRate, rateLimit, provenance) {
185
189
  rules = [].concat(rules || [])
186
190
 
187
191
  return rules
188
- .concat({ sampleRate, maxPerSecond: rateLimit })
192
+ .concat({ sampleRate, maxPerSecond: rateLimit, provenance })
189
193
  .map(rule => ({ ...rule, sampleRate: parseFloat(rule.sampleRate) }))
190
194
  .filter(rule => !isNaN(rule.sampleRate))
191
195
  .map(SamplingRule.from)
@@ -9,7 +9,7 @@ process.once('beforeExit', () => { profiler.stop() })
9
9
  module.exports = {
10
10
  start: config => {
11
11
  const { service, version, env, url, hostname, port, tags, repositoryUrl, commitSHA } = config
12
- const { enabled, sourceMap, exporters } = config.profiling
12
+ const { enabled, sourceMap, exporters, heuristicsEnabled } = config.profiling
13
13
  const logger = {
14
14
  debug: (message) => log.debug(message),
15
15
  info: (message) => log.info(message),
@@ -19,6 +19,7 @@ module.exports = {
19
19
 
20
20
  return profiler.start({
21
21
  enabled,
22
+ heuristicsEnabled,
22
23
  service,
23
24
  version,
24
25
  env,
@@ -65,6 +65,7 @@ class Config {
65
65
  DD_PROFILING_PPROF_PREFIX, '')
66
66
 
67
67
  this.enabled = enabled
68
+ this.heuristicsEnabled = options.heuristicsEnabled
68
69
  this.service = service
69
70
  this.env = env
70
71
  this.host = host
@@ -55,7 +55,7 @@ class Profiler extends EventEmitter {
55
55
  if (this._enabled) return true
56
56
 
57
57
  const config = this._config = new Config(options)
58
- if (!config.enabled) return false
58
+ if (!config.enabled && !config.heuristicsEnabled) return false
59
59
 
60
60
  this._logger = config.logger
61
61
  this._enabled = true