dd-trace 5.67.0 → 5.68.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 (104) hide show
  1. package/LICENSE-3rdparty.csv +0 -3
  2. package/README.md +0 -2
  3. package/ci/init.js +52 -54
  4. package/ext/exporters.d.ts +2 -1
  5. package/ext/exporters.js +2 -1
  6. package/index.d.ts +47 -2
  7. package/initialize.mjs +1 -1
  8. package/package.json +8 -11
  9. package/packages/datadog-esbuild/index.js +56 -0
  10. package/packages/datadog-instrumentations/src/aws-sdk.js +42 -4
  11. package/packages/datadog-instrumentations/src/azure-functions.js +1 -1
  12. package/packages/datadog-instrumentations/src/azure-service-bus.js +1 -1
  13. package/packages/datadog-instrumentations/src/cassandra-driver.js +2 -2
  14. package/packages/datadog-instrumentations/src/connect.js +6 -2
  15. package/packages/datadog-instrumentations/src/cucumber.js +31 -6
  16. package/packages/datadog-instrumentations/src/express.js +5 -6
  17. package/packages/datadog-instrumentations/src/fastify.js +3 -3
  18. package/packages/datadog-instrumentations/src/helpers/hook.js +28 -15
  19. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  20. package/packages/datadog-instrumentations/src/helpers/instrument.js +11 -2
  21. package/packages/datadog-instrumentations/src/helpers/register.js +10 -3
  22. package/packages/datadog-instrumentations/src/http2/client.js +1 -0
  23. package/packages/datadog-instrumentations/src/http2/server.js +0 -1
  24. package/packages/datadog-instrumentations/src/ioredis.js +12 -1
  25. package/packages/datadog-instrumentations/src/jest.js +48 -36
  26. package/packages/datadog-instrumentations/src/limitd-client.js +2 -1
  27. package/packages/datadog-instrumentations/src/mocha/main.js +15 -7
  28. package/packages/datadog-instrumentations/src/mocha/utils.js +3 -0
  29. package/packages/datadog-instrumentations/src/mongoose.js +2 -1
  30. package/packages/datadog-instrumentations/src/oracledb.js +19 -13
  31. package/packages/datadog-instrumentations/src/pg.js +9 -5
  32. package/packages/datadog-instrumentations/src/pino.js +18 -6
  33. package/packages/datadog-instrumentations/src/playwright.js +15 -1
  34. package/packages/datadog-instrumentations/src/sequelize.js +1 -1
  35. package/packages/datadog-instrumentations/src/vitest.js +155 -62
  36. package/packages/datadog-plugin-ai/src/tracing.js +3 -3
  37. package/packages/datadog-plugin-aws-sdk/src/base.js +23 -8
  38. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/tracing.js +2 -2
  39. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +101 -2
  40. package/packages/datadog-plugin-aws-sdk/src/util.js +1 -1
  41. package/packages/datadog-plugin-cucumber/src/index.js +4 -56
  42. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +6 -2
  43. package/packages/datadog-plugin-cypress/src/support.js +4 -0
  44. package/packages/datadog-plugin-express/src/code_origin.js +2 -2
  45. package/packages/datadog-plugin-fastify/src/code_origin.js +1 -2
  46. package/packages/datadog-plugin-jest/src/index.js +0 -21
  47. package/packages/datadog-plugin-mocha/src/index.js +3 -57
  48. package/packages/datadog-plugin-mongodb-core/src/index.js +20 -7
  49. package/packages/datadog-plugin-playwright/src/index.js +11 -5
  50. package/packages/datadog-plugin-vitest/src/index.js +5 -1
  51. package/packages/datadog-plugin-ws/src/close.js +1 -1
  52. package/packages/datadog-plugin-ws/src/producer.js +6 -1
  53. package/packages/datadog-plugin-ws/src/receiver.js +6 -1
  54. package/packages/dd-trace/src/appsec/iast/security-controls/parser.js +1 -1
  55. package/packages/dd-trace/src/appsec/telemetry/waf.js +2 -2
  56. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +4 -4
  57. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +11 -3
  58. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +10 -1
  59. package/packages/dd-trace/src/config.js +69 -304
  60. package/packages/dd-trace/src/config_defaults.js +186 -0
  61. package/packages/dd-trace/src/crashtracking/crashtracker.js +2 -1
  62. package/packages/dd-trace/src/datastreams/fnv.js +2 -2
  63. package/packages/dd-trace/src/datastreams/writer.js +3 -2
  64. package/packages/dd-trace/src/debugger/devtools_client/config.js +2 -1
  65. package/packages/dd-trace/src/dogstatsd.js +4 -3
  66. package/packages/dd-trace/src/encode/0.4.js +1 -5
  67. package/packages/dd-trace/src/exporter.js +1 -0
  68. package/packages/dd-trace/src/exporters/agent/index.js +3 -2
  69. package/packages/dd-trace/src/exporters/agent/writer.js +1 -1
  70. package/packages/dd-trace/src/exporters/common/agent-info-exporter.js +3 -2
  71. package/packages/dd-trace/src/exporters/common/request.js +2 -1
  72. package/packages/dd-trace/src/exporters/span-stats/index.js +3 -2
  73. package/packages/dd-trace/src/llmobs/constants/tags.js +2 -0
  74. package/packages/dd-trace/src/llmobs/plugins/ai/index.js +4 -3
  75. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +12 -1
  76. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +40 -13
  77. package/packages/dd-trace/src/llmobs/plugins/openai.js +7 -1
  78. package/packages/dd-trace/src/llmobs/tagger.js +8 -0
  79. package/packages/dd-trace/src/llmobs/telemetry.js +2 -1
  80. package/packages/dd-trace/src/log/index.js +28 -17
  81. package/packages/dd-trace/src/log/log.js +29 -5
  82. package/packages/dd-trace/src/log/writer.js +5 -5
  83. package/packages/dd-trace/src/noop/span.js +1 -0
  84. package/packages/dd-trace/src/opentelemetry/span.js +14 -3
  85. package/packages/dd-trace/src/opentracing/span.js +18 -4
  86. package/packages/dd-trace/src/plugin_manager.js +20 -2
  87. package/packages/dd-trace/src/plugins/ci_plugin.js +97 -3
  88. package/packages/dd-trace/src/plugins/index.js +2 -0
  89. package/packages/dd-trace/src/plugins/util/git-cache.js +129 -0
  90. package/packages/dd-trace/src/plugins/util/git.js +40 -26
  91. package/packages/dd-trace/src/plugins/util/test.js +37 -27
  92. package/packages/dd-trace/src/plugins/util/web.js +1 -1
  93. package/packages/dd-trace/src/profiler.js +4 -1
  94. package/packages/dd-trace/src/profiling/config.js +73 -42
  95. package/packages/dd-trace/src/profiling/profiler.js +3 -1
  96. package/packages/dd-trace/src/profiling/profilers/events.js +3 -8
  97. package/packages/dd-trace/src/profiling/profilers/space.js +1 -0
  98. package/packages/dd-trace/src/profiling/profilers/wall.js +196 -117
  99. package/packages/dd-trace/src/remote_config/capabilities.js +5 -0
  100. package/packages/dd-trace/src/remote_config/manager.js +3 -2
  101. package/packages/dd-trace/src/startup-log.js +2 -1
  102. package/packages/dd-trace/src/supported-configurations.json +3 -0
  103. package/packages/dd-trace/src/telemetry/logs/index.js +2 -2
  104. package/register.js +1 -1
@@ -1,11 +1,9 @@
1
1
  'use strict'
2
-
3
- const coalesce = require('koalas')
4
2
  const { inspect } = require('util')
5
3
  const { isTrue } = require('../util')
6
4
  const { traceChannel, debugChannel, infoChannel, warnChannel, errorChannel } = require('./channels')
7
5
  const logWriter = require('./writer')
8
- const { Log } = require('./log')
6
+ const { Log, LogConfig, NoTransmitError } = require('./log')
9
7
  const { memoize } = require('./utils')
10
8
  const { getEnvironmentVariable } = require('../config-helper')
11
9
 
@@ -15,7 +13,14 @@ const config = {
15
13
  logLevel: 'debug'
16
14
  }
17
15
 
16
+ // in most places where we know we want to mute a log we use log.error() directly
17
+ const NO_TRANSMIT = new LogConfig(false)
18
+
18
19
  const log = {
20
+ LogConfig,
21
+ NO_TRANSMIT,
22
+ NoTransmitError,
23
+
19
24
  /**
20
25
  * @returns Read-only version of logging config. To modify config, call `log.use` and `log.toggle`
21
26
  */
@@ -92,18 +97,26 @@ const log = {
92
97
  return this
93
98
  },
94
99
 
100
+ errorWithoutTelemetry (...args) {
101
+ args.push(NO_TRANSMIT)
102
+ if (errorChannel.hasSubscribers) {
103
+ errorChannel.publish(Log.parse(...args))
104
+ }
105
+ return this
106
+ },
107
+
95
108
  deprecate (code, message) {
96
109
  return this._deprecate(code, message)
97
110
  },
98
111
 
99
112
  isEnabled (fleetStableConfigValue, localStableConfigValue) {
100
- return isTrue(coalesce(
101
- fleetStableConfigValue,
102
- getEnvironmentVariable('DD_TRACE_DEBUG'),
103
- getEnvironmentVariable('OTEL_LOG_LEVEL') === 'debug' || undefined,
104
- localStableConfigValue,
105
- config.enabled
106
- ))
113
+ return isTrue(
114
+ (fleetStableConfigValue ??
115
+ getEnvironmentVariable('DD_TRACE_DEBUG') ??
116
+ getEnvironmentVariable('OTEL_LOG_LEVEL') === 'debug') ||
117
+ (localStableConfigValue ??
118
+ config.enabled)
119
+ )
107
120
  },
108
121
 
109
122
  getLogLevel (
@@ -111,14 +124,12 @@ const log = {
111
124
  fleetStableConfigValue,
112
125
  localStableConfigValue
113
126
  ) {
114
- return coalesce(
115
- optionsValue,
116
- fleetStableConfigValue,
117
- getEnvironmentVariable('DD_TRACE_LOG_LEVEL'),
118
- getEnvironmentVariable('OTEL_LOG_LEVEL'),
119
- localStableConfigValue,
127
+ return optionsValue ??
128
+ fleetStableConfigValue ??
129
+ getEnvironmentVariable('DD_TRACE_LOG_LEVEL') ??
130
+ getEnvironmentVariable('OTEL_LOG_LEVEL') ??
131
+ localStableConfigValue ??
120
132
  config.logLevel
121
- )
122
133
  }
123
134
  }
124
135
 
@@ -2,12 +2,16 @@
2
2
 
3
3
  const { format } = require('util')
4
4
 
5
+ // other times we produce an Error in a central location and log it several other places
6
+ class NoTransmitError extends Error {}
7
+
5
8
  class Log {
6
- constructor (message, args, cause, delegate) {
9
+ constructor (message, args, cause, delegate, sendViaTelemetry = true) {
7
10
  this.message = message
8
11
  this.args = args
9
12
  this.cause = cause
10
13
  this.delegate = delegate
14
+ this.sendViaTelemetry = sendViaTelemetry
11
15
  }
12
16
 
13
17
  get formatted () {
@@ -22,10 +26,18 @@ class Log {
22
26
 
23
27
  static parse (...args) {
24
28
  let message, cause, delegate
29
+ let sendViaTelemetry = true
30
+
31
+ const maybeLogConfig = args.at(-1)
32
+ if (maybeLogConfig instanceof LogConfig) {
33
+ args.pop()
34
+ sendViaTelemetry = maybeLogConfig.transmit
35
+ }
25
36
 
26
- const lastArg = args.at(-1)
27
- if (lastArg && typeof lastArg === 'object' && lastArg.stack) { // lastArg instanceof Error?
37
+ const maybeError = args.at(-1)
38
+ if (maybeError && typeof maybeError === 'object' && maybeError.stack) { // maybeError instanceof Error?
28
39
  cause = args.pop()
40
+ if (cause instanceof NoTransmitError) sendViaTelemetry = false
29
41
  }
30
42
 
31
43
  const firstArg = args.shift()
@@ -43,10 +55,22 @@ class Log {
43
55
  message = String(firstArg)
44
56
  }
45
57
 
46
- return new Log(message, args, cause, delegate)
58
+ return new Log(message, args, cause, delegate, sendViaTelemetry)
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Pass instances of this class to logger methods when fine-grain control is needed
64
+ * @property {boolean} transmit - Whether to send the log via telemetry.
65
+ */
66
+ class LogConfig {
67
+ constructor (transmit = true) {
68
+ this.transmit = transmit
47
69
  }
48
70
  }
49
71
 
50
72
  module.exports = {
51
- Log
73
+ Log,
74
+ LogConfig,
75
+ NoTransmitError,
52
76
  }
@@ -75,12 +75,12 @@ function onError (err) {
75
75
  // TODO: replace it with Error(message, { cause }) when cause has broad support
76
76
  if (formatted) {
77
77
  withNoop(() => {
78
- const l = Error.stackTraceLimit
78
+ const stackTraceLimitBackup = Error.stackTraceLimit
79
79
  Error.stackTraceLimit = 0
80
- const e = new Error(formatted)
81
- Error.stackTraceLimit = l
82
- Error.captureStackTrace(e, stackTraceLimitFunction)
83
- logger.error(e)
80
+ const newError = new Error(formatted)
81
+ Error.stackTraceLimit = stackTraceLimitBackup
82
+ Error.captureStackTrace(newError, stackTraceLimitFunction)
83
+ logger.error(newError)
84
84
  })
85
85
  }
86
86
  if (cause) withNoop(() => logger.error(cause))
@@ -22,6 +22,7 @@ class NoopSpan {
22
22
  setTag (key, value) { return this }
23
23
  addTags (keyValueMap) { return this }
24
24
  addLink (link) { return this }
25
+ addLinks (links) { return this }
25
26
  addSpanPointer (ptrKind, ptrDir, ptrHash) { return this }
26
27
  log () { return this }
27
28
  logEvent () {}
@@ -211,10 +211,21 @@ class Span {
211
211
  return this
212
212
  }
213
213
 
214
- addLink (context, attributes) {
215
- // extract dd context
214
+ addLink (link, attrs) {
215
+ // TODO: Remove this once we remove addLink(context, attrs) in v6.0.0
216
+ if (link instanceof SpanContext) {
217
+ link = { context: link, attributes: attrs ?? {} }
218
+ }
219
+
220
+ const { context, attributes } = link
221
+ // Extract dd context
216
222
  const ddSpanContext = context._ddContext
217
- this._ddSpan.addLink(ddSpanContext, attributes)
223
+ this._ddSpan.addLink({ context: ddSpanContext, attributes })
224
+ return this
225
+ }
226
+
227
+ addLinks (links) {
228
+ links.forEach(link => this.addLink(link))
218
229
  return this
219
230
  }
220
231
 
@@ -86,8 +86,10 @@ class DatadogSpan {
86
86
 
87
87
  this._startTime = fields.startTime || this._getTime()
88
88
 
89
- this._links = []
90
- fields.links && fields.links.forEach(link => this.addLink(link.context, link.attributes))
89
+ this._links = fields.links?.map(link => ({
90
+ context: link.context._ddContext ?? link.context,
91
+ attributes: this._sanitizeAttributes(link.attributes)
92
+ })) ?? []
91
93
 
92
94
  if (DD_TRACE_EXPERIMENTAL_SPAN_COUNTS && finishedRegistry) {
93
95
  runtimeMetrics.increment('runtime.node.spans.unfinished')
@@ -196,13 +198,25 @@ class DatadogSpan {
196
198
 
197
199
  logEvent () {}
198
200
 
199
- addLink (context, attributes) {
201
+ addLink (link, attrs) {
202
+ // TODO: Remove this once we remove addLink(context, attrs) in v6.0.0
203
+ if (link instanceof SpanContext) {
204
+ link = { context: link, attributes: attrs ?? {} }
205
+ }
206
+
207
+ const { context, attributes } = link
208
+
200
209
  this._links.push({
201
210
  context: context._ddContext ?? context,
202
211
  attributes: this._sanitizeAttributes(attributes)
203
212
  })
204
213
  }
205
214
 
215
+ addLinks (links) {
216
+ links.forEach(link => this.addLink(link))
217
+ return this
218
+ }
219
+
206
220
  addSpanPointer (ptrKind, ptrDir, ptrHash) {
207
221
  const zeroContext = new SpanContext({
208
222
  traceId: id('0'),
@@ -214,7 +228,7 @@ class DatadogSpan {
214
228
  'ptr.hash': ptrHash,
215
229
  'link.kind': 'span-pointer'
216
230
  }
217
- this.addLink(zeroContext, attributes)
231
+ this.addLink({ context: zeroContext, attributes })
218
232
  }
219
233
 
220
234
  addEvent (name, attributesOrStartTime, startTime) {
@@ -6,6 +6,15 @@ const plugins = require('./plugins')
6
6
  const log = require('./log')
7
7
  const { getEnvironmentVariable } = require('../../dd-trace/src/config-helper')
8
8
 
9
+ // Test optimization plugins that should only be enabled when isCiVisibility is true
10
+ const TEST_OPTIMIZATION_PLUGINS = new Set([
11
+ 'jest',
12
+ 'vitest',
13
+ 'cucumber',
14
+ 'mocha',
15
+ 'playwright'
16
+ ])
17
+
9
18
  const loadChannel = channel('dd-trace:instrumentation:load')
10
19
 
11
20
  // instrument everything that needs Plugin System V2 instrumentation
@@ -74,6 +83,13 @@ module.exports = class PluginManager {
74
83
 
75
84
  if (!Plugin) return
76
85
  if (!this._tracerConfig) return // TODO: don't wait for tracer to be initialized
86
+
87
+ // Check if this is a Test Optimization plugin and Test Optimization is not enabled
88
+ if (TEST_OPTIMIZATION_PLUGINS.has(name) && !this._tracerConfig.isCiVisibility) {
89
+ log.debug('Plugin "%s" is not initialized because Test Optimization mode is not enabled.', name)
90
+ return
91
+ }
92
+
77
93
  if (!this._pluginsByName[name]) {
78
94
  this._pluginsByName[name] = new Plugin(this._tracer, this._tracerConfig)
79
95
  }
@@ -148,7 +164,8 @@ module.exports = class PluginManager {
148
164
  middlewareTracingEnabled,
149
165
  traceWebsocketMessagesEnabled,
150
166
  traceWebsocketMessagesInheritSampling,
151
- traceWebsocketMessagesSeparateTraces
167
+ traceWebsocketMessagesSeparateTraces,
168
+ experimental
152
169
  } = this._tracerConfig
153
170
 
154
171
  const sharedConfig = {
@@ -166,7 +183,8 @@ module.exports = class PluginManager {
166
183
  isServiceUserProvided,
167
184
  traceWebsocketMessagesEnabled,
168
185
  traceWebsocketMessagesInheritSampling,
169
- traceWebsocketMessagesSeparateTraces
186
+ traceWebsocketMessagesSeparateTraces,
187
+ experimental
170
188
  }
171
189
 
172
190
  if (logInjection !== undefined) {
@@ -36,6 +36,7 @@ const {
36
36
  getModifiedTestsFromDiff,
37
37
  getPullRequestBaseBranch
38
38
  } = require('./util/test')
39
+ const { getRepositoryRoot } = require('./util/git')
39
40
  const Plugin = require('./plugin')
40
41
  const { COMPONENT } = require('../constants')
41
42
  const log = require('../log')
@@ -61,6 +62,7 @@ const {
61
62
  const { OS_VERSION, OS_PLATFORM, OS_ARCHITECTURE, RUNTIME_NAME, RUNTIME_VERSION } = require('./util/env')
62
63
  const getDiClient = require('../ci-visibility/dynamic-instrumentation')
63
64
  const { DD_MAJOR } = require('../../../../version')
65
+ const id = require('../id')
64
66
 
65
67
  const FRAMEWORK_TO_TRIMMED_COMMAND = {
66
68
  vitest: 'vitest run',
@@ -70,12 +72,44 @@ const FRAMEWORK_TO_TRIMMED_COMMAND = {
70
72
  jest: 'jest'
71
73
  }
72
74
 
75
+ const WORKER_EXPORTER_TO_TEST_FRAMEWORK = {
76
+ vitest_worker: 'vitest',
77
+ jest_worker: 'jest',
78
+ cucumber_worker: 'cucumber',
79
+ mocha_worker: 'mocha',
80
+ playwright_worker: 'playwright'
81
+ }
82
+
83
+ const TEST_FRAMEWORKS_TO_SKIP_GIT_METADATA_EXTRACTION = new Set([
84
+ 'vitest',
85
+ 'jest',
86
+ 'mocha',
87
+ 'cucumber',
88
+ ])
89
+
90
+ function getTestSuiteLevelVisibilityTags (testSuiteSpan, testFramework) {
91
+ const testSuiteSpanContext = testSuiteSpan.context()
92
+
93
+ const suiteTags = {
94
+ [TEST_SUITE_ID]: testSuiteSpanContext.toSpanId(),
95
+ [TEST_SESSION_ID]: testSuiteSpanContext.toTraceId(),
96
+ [TEST_COMMAND]: testSuiteSpanContext._tags[TEST_COMMAND],
97
+ [TEST_MODULE]: testFramework
98
+ }
99
+
100
+ if (testSuiteSpanContext._parentId) {
101
+ suiteTags[TEST_MODULE_ID] = testSuiteSpanContext._parentId.toString(10)
102
+ }
103
+ return suiteTags
104
+ }
105
+
73
106
  module.exports = class CiPlugin extends Plugin {
74
107
  constructor (...args) {
75
108
  super(...args)
76
109
 
77
110
  this.fileLineToProbeId = new Map()
78
111
  this.rootDir = process.cwd() // fallback in case :session:start events are not emitted
112
+ this._testSuiteSpansByTestSuite = new Map()
79
113
 
80
114
  this.addSub(`ci:${this.constructor.id}:library-configuration`, (ctx) => {
81
115
  const { onDone, isParallel, frameworkVersion } = ctx
@@ -265,6 +299,53 @@ module.exports = class CiPlugin extends Plugin {
265
299
  // TODO: Add telemetry for this type of error
266
300
  return onDone({ err: new Error('No modified tests could have been retrieved') })
267
301
  })
302
+
303
+ this.addSub(`ci:${this.constructor.id}:worker-report:trace`, traces => {
304
+ const formattedTraces = JSON.parse(traces)
305
+
306
+ for (const trace of formattedTraces) {
307
+ for (const span of trace) {
308
+ span.span_id = id(span.span_id)
309
+ span.trace_id = id(span.trace_id)
310
+ span.parent_id = id(span.parent_id)
311
+
312
+ if (span.name?.startsWith(`${this.constructor.id}.`)) {
313
+ // augment with git information (since it will not be available in the worker)
314
+ for (const key in this.testEnvironmentMetadata) {
315
+ // CAREFUL: this bypasses the metadata/metrics distinction
316
+ // Be careful not to pass numbers in `meta`
317
+ if (key.startsWith('git.')) {
318
+ span.meta[key] = this.testEnvironmentMetadata[key]
319
+ }
320
+ }
321
+ }
322
+
323
+ // Only test hooks run in the cucumber worker, so the test events do not have the
324
+ // test session, test module and test suite ids. We have to update them here.
325
+ if (span.name === 'cucumber.test' || span.name === 'mocha.test') {
326
+ const testSuite = span.meta[TEST_SUITE]
327
+ const testSuiteSpan = this._testSuiteSpansByTestSuite.get(testSuite)
328
+ if (!testSuiteSpan) {
329
+ log.warn(`Test suite span not found for test span with test suite ${testSuite}`)
330
+ continue
331
+ }
332
+
333
+ const testSuiteTags = getTestSuiteLevelVisibilityTags(testSuiteSpan, this.constructor.id)
334
+ span.meta = {
335
+ ...span.meta,
336
+ ...testSuiteTags
337
+ }
338
+ }
339
+ }
340
+ this.tracer._exporter.export(trace)
341
+ }
342
+ })
343
+
344
+ this.addSub(`ci:${this.constructor.id}:worker-report:logs`, (logsPayloads) => {
345
+ JSON.parse(logsPayloads).forEach(({ logMessage }) => {
346
+ this.tracer._exporter.exportDiLogs(this.testEnvironmentMetadata, logMessage)
347
+ })
348
+ })
268
349
  }
269
350
 
270
351
  get telemetry () {
@@ -298,7 +379,20 @@ module.exports = class CiPlugin extends Plugin {
298
379
  this.di = getDiClient()
299
380
  }
300
381
 
301
- this.testEnvironmentMetadata = getTestEnvironmentMetadata(this.constructor.id, this.config)
382
+ if (this.testConfiguration) { // no need to recalculate as it's constant
383
+ return
384
+ }
385
+
386
+ const exporter = this.config.experimental?.exporter
387
+ const workerTestFramework = WORKER_EXPORTER_TO_TEST_FRAMEWORK[exporter]
388
+ this.shouldSkipGitMetadataExtraction = workerTestFramework &&
389
+ TEST_FRAMEWORKS_TO_SKIP_GIT_METADATA_EXTRACTION.has(workerTestFramework)
390
+
391
+ this.testEnvironmentMetadata = getTestEnvironmentMetadata(
392
+ this.constructor.id,
393
+ this.config,
394
+ this.shouldSkipGitMetadataExtraction
395
+ )
302
396
 
303
397
  const {
304
398
  [GIT_REPOSITORY_URL]: repositoryUrl,
@@ -318,9 +412,9 @@ module.exports = class CiPlugin extends Plugin {
318
412
  [GIT_COMMIT_HEAD_MESSAGE]: commitHeadMessage
319
413
  } = this.testEnvironmentMetadata
320
414
 
321
- this.repositoryRoot = repositoryRoot || process.cwd()
415
+ this.repositoryRoot = repositoryRoot || getRepositoryRoot() || process.cwd()
322
416
 
323
- this.codeOwnersEntries = getCodeOwnersFileEntries(repositoryRoot)
417
+ this.codeOwnersEntries = getCodeOwnersFileEntries(this.repositoryRoot)
324
418
 
325
419
  this.ciProviderName = ciProviderName
326
420
 
@@ -13,6 +13,7 @@ module.exports = {
13
13
  get '@google-cloud/vertexai' () { return require('../../../datadog-plugin-google-cloud-vertexai/src') },
14
14
  get '@grpc/grpc-js' () { return require('../../../datadog-plugin-grpc/src') },
15
15
  get '@hapi/hapi' () { return require('../../../datadog-plugin-hapi/src') },
16
+ get '@happy-dom/jest-environment' () { return require('../../../datadog-plugin-jest/src') },
16
17
  get '@jest/core' () { return require('../../../datadog-plugin-jest/src') },
17
18
  get '@jest/test-sequencer' () { return require('../../../datadog-plugin-jest/src') },
18
19
  get '@jest/transform' () { return require('../../../datadog-plugin-jest/src') },
@@ -73,6 +74,7 @@ module.exports = {
73
74
  get 'mocha-each' () { return require('../../../datadog-plugin-mocha/src') },
74
75
  get vitest () { return require('../../../datadog-plugin-vitest/src') },
75
76
  get workerpool () { return require('../../../datadog-plugin-mocha/src') },
77
+ get tinypool () { return require('../../../datadog-plugin-vitest/src') },
76
78
  get moleculer () { return require('../../../datadog-plugin-moleculer/src') },
77
79
  get mongodb () { return require('../../../datadog-plugin-mongodb-core/src') },
78
80
  get 'mongodb-core' () { return require('../../../datadog-plugin-mongodb-core/src') },
@@ -0,0 +1,129 @@
1
+ 'use strict'
2
+
3
+ const os = require('os')
4
+ const path = require('path')
5
+ const fs = require('fs')
6
+ const crypto = require('crypto')
7
+ const cp = require('child_process')
8
+
9
+ const log = require('../../log')
10
+ const { getEnvironmentVariable } = require('../../config-helper')
11
+ const { isTrue } = require('../../util')
12
+
13
+ let isGitEnabled = isTrue(getEnvironmentVariable('DD_EXPERIMENTAL_TEST_OPT_GIT_CACHE_ENABLED'))
14
+ const GIT_CACHE_DIR = getEnvironmentVariable('DD_EXPERIMENTAL_TEST_OPT_GIT_CACHE_DIR') ||
15
+ path.join(os.tmpdir(), 'dd-trace-git-cache')
16
+
17
+ function ensureCacheDir () {
18
+ if (!isGitEnabled) return false
19
+
20
+ try {
21
+ if (fs.existsSync(GIT_CACHE_DIR)) {
22
+ const stats = fs.statSync(GIT_CACHE_DIR)
23
+ if (!stats.isDirectory()) {
24
+ throw new Error(`Cache directory path exists but is not a directory: ${GIT_CACHE_DIR}`)
25
+ }
26
+ } else {
27
+ fs.mkdirSync(GIT_CACHE_DIR, { recursive: true })
28
+ }
29
+ return true
30
+ } catch (err) {
31
+ log.error('Failed to create git cache directory, disabling cache', err)
32
+ isGitEnabled = false
33
+ return false
34
+ }
35
+ }
36
+
37
+ // Initialize cache directory at module load time
38
+ ensureCacheDir()
39
+
40
+ function getCacheKey (cmd, flags) {
41
+ // Create a hash of the command and flags to use as cache key
42
+ const commandString = `${cmd} ${flags.join(' ')}`
43
+ return crypto.createHash('sha256').update(commandString).digest('hex')
44
+ }
45
+
46
+ function getCacheFilePath (cacheKey) {
47
+ return path.join(GIT_CACHE_DIR, `${cacheKey}.cache`)
48
+ }
49
+
50
+ function getCache (cacheKey) {
51
+ if (!isGitEnabled) return null
52
+
53
+ try {
54
+ const cacheFilePath = getCacheFilePath(cacheKey)
55
+ if (!fs.existsSync(cacheFilePath)) {
56
+ return null
57
+ }
58
+
59
+ const content = fs.readFileSync(cacheFilePath, 'utf8')
60
+ return content
61
+ } catch (err) {
62
+ log.error('Failed to read git cache', err)
63
+ return null
64
+ }
65
+ }
66
+
67
+ function setCache (cacheKey, result) {
68
+ if (!isGitEnabled) return
69
+
70
+ // Ensure cache directory exists
71
+ if (!ensureCacheDir()) return
72
+
73
+ try {
74
+ const cacheFilePath = getCacheFilePath(cacheKey)
75
+ fs.writeFileSync(cacheFilePath, result, 'utf8')
76
+ } catch (err) {
77
+ log.error('Failed to write git cache', err)
78
+ }
79
+ }
80
+
81
+ function cachedExec (cmd, flags, options) {
82
+ if (options === undefined) {
83
+ options = { stdio: 'pipe' }
84
+ }
85
+ if (!isGitEnabled) {
86
+ return cp.execFileSync(cmd, flags, options)
87
+ }
88
+ const cacheKey = getCacheKey(cmd, flags)
89
+ const cachedResult = getCache(cacheKey)
90
+ if (cachedResult !== null) {
91
+ if (cachedResult.startsWith('__GIT_COMMAND_FAILED__')) {
92
+ let error
93
+ try {
94
+ const errorData = cachedResult.replace('__GIT_COMMAND_FAILED__', '')
95
+ const { message, code, status, errno } = JSON.parse(errorData)
96
+ error = new Error(message)
97
+ error.code = code
98
+ error.status = status
99
+ error.errno = errno
100
+ } catch {
101
+ // we couldn't parse the error data, so we'll throw a generic error
102
+ throw new Error('Git command failed')
103
+ }
104
+ throw error
105
+ }
106
+ return cachedResult
107
+ }
108
+ try {
109
+ const result = cp.execFileSync(cmd, flags, options)
110
+ setCache(cacheKey, result)
111
+ return result
112
+ } catch (err) {
113
+ const cacheValue = '__GIT_COMMAND_FAILED__' +
114
+ JSON.stringify({
115
+ code: err.code,
116
+ status: err.status,
117
+ errno: err.errno,
118
+ message: err.message
119
+ })
120
+ setCache(cacheKey, cacheValue)
121
+ throw err
122
+ }
123
+ }
124
+
125
+ module.exports = {
126
+ getCacheKey,
127
+ getCacheFilePath,
128
+ cachedExec
129
+ }