dd-trace 5.92.0 → 5.94.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 (31) hide show
  1. package/package.json +15 -11
  2. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +23 -0
  3. package/packages/datadog-instrumentations/src/jest.js +118 -32
  4. package/packages/datadog-instrumentations/src/mocha/main.js +6 -0
  5. package/packages/datadog-instrumentations/src/mocha/utils.js +89 -5
  6. package/packages/datadog-instrumentations/src/playwright.js +10 -0
  7. package/packages/datadog-instrumentations/src/vitest.js +119 -0
  8. package/packages/datadog-plugin-aws-sdk/src/base.js +5 -0
  9. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +12 -0
  10. package/packages/datadog-plugin-jest/src/index.js +6 -0
  11. package/packages/datadog-plugin-mocha/src/index.js +11 -0
  12. package/packages/datadog-plugin-playwright/src/index.js +9 -0
  13. package/packages/datadog-plugin-vitest/src/index.js +9 -0
  14. package/packages/datadog-webpack/index.js +187 -0
  15. package/packages/datadog-webpack/src/loader.js +27 -0
  16. package/packages/datadog-webpack/src/log.js +32 -0
  17. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +103 -32
  18. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +10 -21
  19. package/packages/dd-trace/src/config/supported-configurations.json +2 -2
  20. package/packages/dd-trace/src/crashtracking/index.js +7 -1
  21. package/packages/dd-trace/src/exporters/common/docker.js +1 -0
  22. package/packages/dd-trace/src/exporters/common/request.js +26 -17
  23. package/packages/dd-trace/src/opentracing/span.js +5 -0
  24. package/packages/dd-trace/src/plugin_manager.js +10 -7
  25. package/packages/dd-trace/src/plugins/util/test.js +76 -0
  26. package/packages/dd-trace/src/priority_sampler.js +6 -3
  27. package/packages/dd-trace/src/profiling/profiler.js +78 -47
  28. package/packages/dd-trace/src/profiling/profilers/wall.js +35 -28
  29. package/packages/dd-trace/src/proxy.js +4 -3
  30. package/packages/dd-trace/src/tracer_metadata.js +10 -1
  31. package/webpack.js +3 -0
@@ -6,6 +6,9 @@ const log = require('../../dd-trace/src/log')
6
6
  const {
7
7
  VITEST_WORKER_TRACE_PAYLOAD_CODE,
8
8
  VITEST_WORKER_LOGS_PAYLOAD_CODE,
9
+ DYNAMIC_NAME_RE,
10
+ collectDynamicNamesFromTraces,
11
+ logDynamicNamesWarning,
9
12
  } = require('../../dd-trace/src/plugins/util/test')
10
13
  const { addHook, channel } = require('./helpers/instrument')
11
14
 
@@ -20,6 +23,7 @@ const isAttemptToFixCh = channel('ci:vitest:test:is-attempt-to-fix')
20
23
  const isDisabledCh = channel('ci:vitest:test:is-disabled')
21
24
  const isQuarantinedCh = channel('ci:vitest:test:is-quarantined')
22
25
  const isModifiedCh = channel('ci:vitest:test:is-modified')
26
+ const testFnCh = channel('ci:vitest:test:fn')
23
27
 
24
28
  // test suite hooks
25
29
  const testSuiteStartCh = channel('ci:vitest:test-suite:start')
@@ -41,7 +45,10 @@ const codeCoverageReportCh = channel('ci:vitest:coverage-report')
41
45
 
42
46
  const taskToCtx = new WeakMap()
43
47
  const taskToStatuses = new WeakMap()
48
+ const originalHookFns = new WeakMap()
44
49
  const newTasks = new WeakSet()
50
+ const dynamicNameTasks = new WeakSet()
51
+ const newTestsWithDynamicNames = new Set()
45
52
  const disabledTasks = new WeakSet()
46
53
  const quarantinedTasks = new WeakSet()
47
54
  const attemptToFixTasks = new WeakSet()
@@ -58,6 +65,9 @@ let isEarlyFlakeDetectionFaulty = false
58
65
  let isKnownTestsEnabled = false
59
66
  let isTestManagementTestsEnabled = false
60
67
  let isImpactedTestsEnabled = false
68
+ let vitestGetFn = null
69
+ let vitestSetFn = null
70
+ let vitestGetHooks = null
61
71
  let testManagementAttemptToFixRetries = 0
62
72
  let isDiEnabled = false
63
73
  let testCodeCoverageLinesTotal
@@ -241,6 +251,37 @@ function getTestName (task) {
241
251
  return testName
242
252
  }
243
253
 
254
+ /**
255
+ * Wraps a function so it runs inside the current test span context.
256
+ * @param {object} task
257
+ * @param {Function} fn
258
+ * @returns {Function}
259
+ */
260
+ function wrapTestScopedFn (task, fn) {
261
+ return shimmer.wrapFunction(fn, fn => function () {
262
+ return testFnCh.runStores(taskToCtx.get(task), () => fn.apply(this, arguments))
263
+ })
264
+ }
265
+
266
+ /**
267
+ * Wraps a `beforeEach` cleanup callback so it inherits the test span context.
268
+ * Vitest allows `beforeEach` to return a cleanup function, including via a promise.
269
+ * @param {object} task
270
+ * @param {unknown} result
271
+ * @returns {unknown}
272
+ */
273
+ function wrapBeforeEachCleanupResult (task, result) {
274
+ if (typeof result === 'function') {
275
+ return wrapTestScopedFn(task, result)
276
+ }
277
+
278
+ if (result && typeof result.then === 'function') {
279
+ return result.then(cleanupFn => wrapBeforeEachCleanupResult(task, cleanupFn))
280
+ }
281
+
282
+ return result
283
+ }
284
+
244
285
  function getSortWrapper (sort, frameworkVersion) {
245
286
  return async function () {
246
287
  if (!testSessionFinishCh.hasSubscribers) {
@@ -435,6 +476,8 @@ function getFinishWrapper (exitOrClose) {
435
476
  onFinish,
436
477
  })
437
478
 
479
+ logDynamicNamesWarning(newTestsWithDynamicNames)
480
+
438
481
  await flushPromise
439
482
 
440
483
  // If coverage was generated, publish coverage report channel for upload
@@ -495,6 +538,7 @@ function threadHandler (thread) {
495
538
  workerProcess.on('message', (message) => {
496
539
  if (message.__tinypool_worker_message__ && message.data) {
497
540
  if (message.interprocessCode === VITEST_WORKER_TRACE_PAYLOAD_CODE) {
541
+ collectDynamicNamesFromTraces(message.data, newTestsWithDynamicNames)
498
542
  workerReportTraceCh.publish(message.data)
499
543
  } else if (message.interprocessCode === VITEST_WORKER_LOGS_PAYLOAD_CODE) {
500
544
  workerReportLogsCh.publish(message.data)
@@ -536,6 +580,7 @@ function getWrappedOn (on) {
536
580
  if (message.type !== 'Buffer' && Array.isArray(message)) {
537
581
  const [interprocessCode, data] = message
538
582
  if (interprocessCode === VITEST_WORKER_TRACE_PAYLOAD_CODE) {
583
+ collectDynamicNamesFromTraces(data, newTestsWithDynamicNames)
539
584
  workerReportTraceCh.publish(data)
540
585
  } else if (interprocessCode === VITEST_WORKER_LOGS_PAYLOAD_CODE) {
541
586
  workerReportLogsCh.publish(data)
@@ -659,6 +704,9 @@ function wrapVitestTestRunner (VitestTestRunner) {
659
704
  }
660
705
  newTasks.add(task)
661
706
  taskToStatuses.set(task, [])
707
+ if (DYNAMIC_NAME_RE.test(testName)) {
708
+ dynamicNameTasks.add(task)
709
+ }
662
710
  }
663
711
  },
664
712
  })
@@ -828,6 +876,7 @@ function wrapVitestTestRunner (VitestTestRunner) {
828
876
  isRetryReasonEfd,
829
877
  isRetryReasonAttemptToFix: isRetryReasonAttemptToFix && numRepetition > 0,
830
878
  isNew,
879
+ hasDynamicName: dynamicNameTasks.has(task),
831
880
  mightHitProbe: isDiEnabled && numAttempt > 0,
832
881
  isAttemptToFix: attemptToFixTasks.has(task),
833
882
  isDisabled: disabledTasks.has(task),
@@ -838,6 +887,47 @@ function wrapVitestTestRunner (VitestTestRunner) {
838
887
  taskToCtx.set(task, ctx)
839
888
 
840
889
  testStartCh.runStores(ctx, () => {})
890
+
891
+ // Wrap the test function so it runs inside the test span context.
892
+ // Without this, HTTP requests during test execution become orphaned root spans.
893
+ if (vitestGetFn && vitestSetFn) {
894
+ const originalFn = vitestGetFn(task)
895
+ if (originalFn && !originalFn.__ddTraceWrapped) {
896
+ const wrappedFn = wrapTestScopedFn(task, originalFn)
897
+ wrappedFn.__ddTraceWrapped = true
898
+ vitestSetFn(task, wrappedFn)
899
+ }
900
+ }
901
+
902
+ // Wrap beforeEach/afterEach hooks so they also run inside the test span context.
903
+ // In vitest 4+, hooks are in a WeakMap accessed via getHooks(). In older versions, they're on suite.hooks.
904
+ let currentSuite = task.suite
905
+ while (currentSuite) {
906
+ const hooks = vitestGetHooks ? vitestGetHooks(currentSuite) : currentSuite.hooks
907
+ if (hooks) {
908
+ for (const hookType of ['beforeEach', 'afterEach']) {
909
+ const hookArray = hooks[hookType]
910
+ if (!hookArray) continue
911
+ for (let i = 0; i < hookArray.length; i++) {
912
+ const currentFn = hookArray[i]
913
+ const originalFn = originalHookFns.get(currentFn) || currentFn
914
+ const wrappedFn = shimmer.wrapFunction(originalFn, fn => function () {
915
+ const result = testFnCh.runStores(taskToCtx.get(task), () => fn.apply(this, arguments))
916
+
917
+ if (hookType === 'beforeEach') {
918
+ return wrapBeforeEachCleanupResult(task, result)
919
+ }
920
+
921
+ return result
922
+ })
923
+ originalHookFns.set(wrappedFn, originalFn)
924
+ hookArray[i] = wrappedFn
925
+ }
926
+ }
927
+ }
928
+ currentSuite = currentSuite.suite
929
+ }
930
+
841
931
  return onBeforeTryTask.apply(this, arguments)
842
932
  })
843
933
 
@@ -886,6 +976,20 @@ function wrapVitestTestRunner (VitestTestRunner) {
886
976
  })
887
977
  }
888
978
 
979
+ function captureRunnerFunctions (pkg) {
980
+ if (vitestGetFn) return
981
+ const getFnExport = findExportByName(pkg, 'getFn')
982
+ const setFnExport = findExportByName(pkg, 'setFn')
983
+ if (getFnExport && setFnExport) {
984
+ vitestGetFn = getFnExport.value
985
+ vitestSetFn = setFnExport.value
986
+ }
987
+ const getHooksExport = findExportByName(pkg, 'getHooks')
988
+ if (getHooksExport) {
989
+ vitestGetHooks = getHooksExport.value
990
+ }
991
+ }
992
+
889
993
  addHook({
890
994
  name: 'vitest',
891
995
  versions: ['>=4.0.0'],
@@ -896,11 +1000,26 @@ addHook({
896
1000
  return testPackage
897
1001
  }
898
1002
 
1003
+ captureRunnerFunctions(testPackage)
899
1004
  wrapVitestTestRunner(testRunner.value)
900
1005
 
901
1006
  return testPackage
902
1007
  })
903
1008
 
1009
+ addHook({
1010
+ name: '@vitest/runner',
1011
+ versions: ['>=1.6.0'],
1012
+ }, (runnerModule) => {
1013
+ if (!vitestGetFn && runnerModule.getFn && runnerModule.setFn) {
1014
+ vitestGetFn = runnerModule.getFn
1015
+ vitestSetFn = runnerModule.setFn
1016
+ }
1017
+ if (!vitestGetHooks && runnerModule.getHooks) {
1018
+ vitestGetHooks = runnerModule.getHooks
1019
+ }
1020
+ return runnerModule
1021
+ })
1022
+
904
1023
  addHook({
905
1024
  name: 'vitest',
906
1025
  versions: ['>=1.6.0 <4.0.0'],
@@ -154,6 +154,11 @@ class BaseAwsSdkPlugin extends ClientPlugin {
154
154
  })
155
155
 
156
156
  this.finish(ctx)
157
+
158
+ if (IS_SERVERLESS) {
159
+ const peerStore = storage('peerServerless').getStore()
160
+ if (peerStore) delete peerStore.peerHostname
161
+ }
157
162
  })
158
163
 
159
164
  this.addBind(`apm:aws:response:start:${this.serviceIdentifier}`, ctx => {
@@ -49,6 +49,9 @@ const {
49
49
  getSessionRequestErrorTags,
50
50
  DD_CI_LIBRARY_CONFIGURATION_ERROR,
51
51
  TEST_IS_MODIFIED,
52
+ TEST_HAS_DYNAMIC_NAME,
53
+ DYNAMIC_NAME_RE,
54
+ logDynamicNamesWarning,
52
55
  getPullRequestBaseBranch,
53
56
  } = require('../../dd-trace/src/plugins/util/test')
54
57
  const { isMarkedAsUnskippable } = require('../../datadog-plugin-jest/src/util')
@@ -261,6 +264,7 @@ class CypressPlugin {
261
264
  testManagementAttemptToFixRetries = 0
262
265
  isImpactedTestsEnabled = false
263
266
  modifiedFiles = []
267
+ newTestsWithDynamicNames = new Set()
264
268
 
265
269
  constructor () {
266
270
  const {
@@ -675,6 +679,8 @@ class CypressPlugin {
675
679
  this.testSessionSpan.setTag(TEST_MANAGEMENT_ENABLED, 'true')
676
680
  }
677
681
 
682
+ logDynamicNamesWarning(this.newTestsWithDynamicNames)
683
+
678
684
  this.testModuleSpan.finish()
679
685
  this.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
680
686
  this.testSessionSpan.finish()
@@ -990,6 +996,12 @@ class CypressPlugin {
990
996
  this.activeTestSpan.setTag(TEST_IS_RETRY, 'true')
991
997
  this.activeTestSpan.setTag(TEST_RETRY_REASON, TEST_RETRY_REASON_TYPES.efd)
992
998
  }
999
+ if (DYNAMIC_NAME_RE.test(testName)) {
1000
+ this.activeTestSpan.setTag(TEST_HAS_DYNAMIC_NAME, 'true')
1001
+ if (testStatuses.length === 1) {
1002
+ this.newTestsWithDynamicNames.add(`${testSuite} › ${testName}`)
1003
+ }
1004
+ }
993
1005
  }
994
1006
  if (isModified) {
995
1007
  this.activeTestSpan.setTag(TEST_IS_MODIFIED, 'true')
@@ -23,6 +23,7 @@ const {
23
23
  TEST_SOURCE_FILE,
24
24
  TEST_IS_NEW,
25
25
  TEST_IS_RETRY,
26
+ TEST_HAS_DYNAMIC_NAME,
26
27
  TEST_EARLY_FLAKE_ENABLED,
27
28
  TEST_EARLY_FLAKE_ABORT_REASON,
28
29
  JEST_DISPLAY_NAME,
@@ -501,6 +502,7 @@ class JestPlugin extends CiPlugin {
501
502
  isDisabled,
502
503
  isQuarantined,
503
504
  isModified,
505
+ hasDynamicName,
504
506
  testSuiteAbsolutePath,
505
507
  } = test
506
508
 
@@ -549,6 +551,10 @@ class JestPlugin extends CiPlugin {
549
551
  if (isNew) {
550
552
  extraTags[TEST_IS_NEW] = 'true'
551
553
  }
554
+
555
+ if (hasDynamicName) {
556
+ extraTags[TEST_HAS_DYNAMIC_NAME] = 'true'
557
+ }
552
558
  const testSuiteSpan = this.testSuiteSpanPerTestSuiteAbsolutePath.get(testSuiteAbsolutePath) || this.testSuiteSpan
553
559
 
554
560
  return super.startTestSpan(name, suite, testSuiteSpan, extraTags)
@@ -32,6 +32,8 @@ const {
32
32
  TEST_MANAGEMENT_ATTEMPT_TO_FIX_PASSED,
33
33
  TEST_RETRY_REASON_TYPES,
34
34
  TEST_IS_MODIFIED,
35
+ TEST_FINAL_STATUS,
36
+ TEST_HAS_DYNAMIC_NAME,
35
37
  isModifiedTest,
36
38
  } = require('../../dd-trace/src/plugins/util/test')
37
39
  const { COMPONENT } = require('../../dd-trace/src/constants')
@@ -213,9 +215,13 @@ class MochaPlugin extends CiPlugin {
213
215
  attemptToFixFailed,
214
216
  isAttemptToFixRetry,
215
217
  isAtrRetry,
218
+ finalStatus,
216
219
  }) => {
217
220
  if (span) {
218
221
  span.setTag(TEST_STATUS, status)
222
+ if (finalStatus) {
223
+ span.setTag(TEST_FINAL_STATUS, finalStatus)
224
+ }
219
225
  if (hasBeenRetried) {
220
226
  span.setTag(TEST_IS_RETRY, 'true')
221
227
  if (isAtrRetry) {
@@ -422,6 +428,7 @@ class MochaPlugin extends CiPlugin {
422
428
  isDisabled,
423
429
  isQuarantined,
424
430
  isModified,
431
+ hasDynamicName,
425
432
  } = testInfo
426
433
 
427
434
  const extraTags = {}
@@ -473,6 +480,10 @@ class MochaPlugin extends CiPlugin {
473
480
  }
474
481
  }
475
482
 
483
+ if (hasDynamicName) {
484
+ extraTags[TEST_HAS_DYNAMIC_NAME] = 'true'
485
+ }
486
+
476
487
  return super.startTestSpan(testName, testSuite, testSuiteSpan, extraTags)
477
488
  }
478
489
  }
@@ -38,6 +38,8 @@ const {
38
38
  TEST_STATUS,
39
39
  TEST_SUITE_ID,
40
40
  TEST_SUITE,
41
+ TEST_HAS_DYNAMIC_NAME,
42
+ DYNAMIC_NAME_RE,
41
43
  } = require('../../dd-trace/src/plugins/util/test')
42
44
  const { RESOURCE_NAME } = require('../../../ext/tags')
43
45
  const { COMPONENT } = require('../../dd-trace/src/constants')
@@ -221,6 +223,9 @@ class PlaywrightPlugin extends CiPlugin {
221
223
  trace_id: id(span.trace_id),
222
224
  parent_id: id(span.parent_id),
223
225
  }
226
+ if (span.meta[TEST_IS_NEW] === 'true' && DYNAMIC_NAME_RE.test(span.meta[TEST_NAME] || '')) {
227
+ formattedSpan.meta[TEST_HAS_DYNAMIC_NAME] = 'true'
228
+ }
224
229
  if (span.name === 'playwright.test') {
225
230
  // TODO: remove this comment
226
231
  // TODO: Let's pass rootDir, repositoryRoot, command, session id and module id as env vars
@@ -303,6 +308,7 @@ class PlaywrightPlugin extends CiPlugin {
303
308
  error,
304
309
  extraTags,
305
310
  isNew,
311
+ hasDynamicName,
306
312
  isEfdRetry,
307
313
  isRetry,
308
314
  isAttemptToFix,
@@ -335,6 +341,9 @@ class PlaywrightPlugin extends CiPlugin {
335
341
  span.setTag(TEST_RETRY_REASON, TEST_RETRY_REASON_TYPES.efd)
336
342
  }
337
343
  }
344
+ if (hasDynamicName) {
345
+ span.setTag(TEST_HAS_DYNAMIC_NAME, 'true')
346
+ }
338
347
  if (isRetry) {
339
348
  span.setTag(TEST_IS_RETRY, 'true')
340
349
  if (isAtrRetry) {
@@ -33,6 +33,7 @@ const {
33
33
  TEST_RETRY_REASON_TYPES,
34
34
  isModifiedTest,
35
35
  TEST_IS_MODIFIED,
36
+ TEST_HAS_DYNAMIC_NAME,
36
37
  } = require('../../dd-trace/src/plugins/util/test')
37
38
  const { COMPONENT } = require('../../dd-trace/src/constants')
38
39
  const {
@@ -117,6 +118,7 @@ class VitestPlugin extends CiPlugin {
117
118
  testSuiteAbsolutePath,
118
119
  isRetry,
119
120
  isNew,
121
+ hasDynamicName,
120
122
  isAttemptToFix,
121
123
  isQuarantined,
122
124
  isDisabled,
@@ -148,6 +150,9 @@ class VitestPlugin extends CiPlugin {
148
150
  if (isNew) {
149
151
  extraTags[TEST_IS_NEW] = 'true'
150
152
  }
153
+ if (hasDynamicName) {
154
+ extraTags[TEST_HAS_DYNAMIC_NAME] = 'true'
155
+ }
151
156
  if (isAttemptToFix) {
152
157
  extraTags[TEST_MANAGEMENT_IS_ATTEMPT_TO_FIX] = 'true'
153
158
  }
@@ -180,6 +185,10 @@ class VitestPlugin extends CiPlugin {
180
185
  return ctx.currentStore
181
186
  })
182
187
 
188
+ this.addBind('ci:vitest:test:fn', (ctx) => {
189
+ return ctx.currentStore
190
+ })
191
+
183
192
  this.addBind('ci:vitest:test:finish-time', (ctx) => {
184
193
  const { status, task, attemptToFixPassed, attemptToFixFailed } = ctx
185
194
  const span = ctx.currentStore?.span
@@ -0,0 +1,187 @@
1
+ 'use strict'
2
+
3
+ const { execSync } = require('node:child_process')
4
+ const fs = require('node:fs')
5
+
6
+ const instrumentations = require('../datadog-instrumentations/src/helpers/instrumentations')
7
+ const extractPackageAndModulePath = require('../datadog-instrumentations/src/helpers/extract-package-and-module-path')
8
+ const hooks = require('../datadog-instrumentations/src/helpers/hooks')
9
+ const { isESMFile } = require('../datadog-esbuild/src/utils')
10
+ const log = require('./src/log')
11
+
12
+ const PLUGIN_NAME = 'DatadogWebpackPlugin'
13
+
14
+ for (const hook of Object.values(hooks)) {
15
+ if (hook !== null && typeof hook === 'object') {
16
+ hook.fn()
17
+ } else {
18
+ hook()
19
+ }
20
+ }
21
+
22
+ const modulesOfInterest = new Set()
23
+
24
+ for (const instrumentation of Object.values(instrumentations)) {
25
+ for (const entry of instrumentation) {
26
+ if (entry.file) {
27
+ modulesOfInterest.add(`${entry.name}/${entry.file}`) // e.g. "redis/my/file.js"
28
+ } else {
29
+ modulesOfInterest.add(entry.name) // e.g. "redis"
30
+ }
31
+ }
32
+ }
33
+
34
+ /**
35
+ * @returns {{ repositoryURL: string | null, commitSHA: string | null }}
36
+ */
37
+ function getGitMetadata () {
38
+ const gitMetadata = {
39
+ repositoryURL: null,
40
+ commitSHA: null,
41
+ }
42
+
43
+ try {
44
+ gitMetadata.repositoryURL = execSync('git config --get remote.origin.url', {
45
+ encoding: 'utf8',
46
+ stdio: ['pipe', 'pipe', 'ignore'],
47
+ cwd: process.cwd(),
48
+ }).trim()
49
+ } catch (e) {
50
+ log.warn('failed to get git repository URL:', e.message)
51
+ }
52
+
53
+ try {
54
+ gitMetadata.commitSHA = execSync('git rev-parse HEAD', {
55
+ encoding: 'utf8',
56
+ stdio: ['pipe', 'pipe', 'ignore'],
57
+ cwd: process.cwd(),
58
+ }).trim()
59
+ } catch (e) {
60
+ log.warn('failed to get git commit SHA:', e.message)
61
+ }
62
+
63
+ return gitMetadata
64
+ }
65
+
66
+ class DatadogWebpackPlugin {
67
+ /**
68
+ * @param {object} compiler
69
+ */
70
+ apply (compiler) {
71
+ // optimization.minimize is not yet set when apply() is called in webpack 5.54.0+
72
+ // (applyWebpackOptionsDefaults runs after plugins), so we defer the check to the
73
+ // environment hook which fires synchronously after defaults are applied.
74
+ compiler.hooks.environment.tap(PLUGIN_NAME, () => {
75
+ if (compiler.options.optimization?.minimize) {
76
+ throw new Error(
77
+ 'optimization.minimize is not compatible with DatadogWebpackPlugin and will break dd-trace ' +
78
+ 'instrumentation. Disable optimization.minimize when using this plugin.'
79
+ )
80
+ }
81
+ })
82
+
83
+ const gitMetadata = getGitMetadata()
84
+ if (gitMetadata.repositoryURL || gitMetadata.commitSHA) {
85
+ const banner =
86
+ 'if (typeof process === \'object\' && process !== null &&\n' +
87
+ ' process.env !== null && typeof process.env === \'object\') {\n' +
88
+ (gitMetadata.repositoryURL
89
+ ? ` process.env.DD_GIT_REPOSITORY_URL = ${JSON.stringify(gitMetadata.repositoryURL)};\n`
90
+ : '') +
91
+ (gitMetadata.commitSHA
92
+ ? ` process.env.DD_GIT_COMMIT_SHA = ${JSON.stringify(gitMetadata.commitSHA)};\n`
93
+ : '') +
94
+ '}\n'
95
+
96
+ compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
97
+ compilation.hooks.processAssets.tap(
98
+ { name: PLUGIN_NAME, stage: -2000 },
99
+ () => {
100
+ for (const chunk of compilation.chunks) {
101
+ if (!chunk.canBeInitial()) continue
102
+ for (const filename of chunk.files) {
103
+ if (!filename.endsWith('.js') && !filename.endsWith('.mjs')) continue
104
+ compilation.updateAsset(filename, (old) => {
105
+ const content = banner + old.source()
106
+ return {
107
+ source () { return content },
108
+ size () { return Buffer.byteLength(content, 'utf8') },
109
+ map () { return old.map() },
110
+ sourceAndMap () { return { source: content, map: old.map() } },
111
+ updateHash (hash) { hash.update(content) },
112
+ }
113
+ })
114
+ }
115
+ }
116
+ }
117
+ )
118
+ })
119
+
120
+ log.debug(
121
+ 'Automatically injected git metadata (DD_GIT_REPOSITORY_URL: %s, DD_GIT_COMMIT_SHA: %s)',
122
+ gitMetadata.repositoryURL || 'not available',
123
+ gitMetadata.commitSHA || 'not available'
124
+ )
125
+ } else {
126
+ log.warn('No git metadata available - skipping injection')
127
+ }
128
+
129
+ compiler.hooks.normalModuleFactory.tap(PLUGIN_NAME, (nmf) => {
130
+ nmf.hooks.afterResolve.tap(PLUGIN_NAME, (resolveData) => {
131
+ const { createData } = resolveData
132
+ const resource = createData?.resource
133
+ if (!resource || !resource.includes('node_modules')) {
134
+ return
135
+ }
136
+
137
+ const normalizedResource = resource.replaceAll('\\', '/')
138
+ const { pkg, path: modulePath, pkgJson } = extractPackageAndModulePath(normalizedResource)
139
+ if (!pkg) {
140
+ return
141
+ }
142
+
143
+ const request = resolveData.request
144
+
145
+ if (!modulesOfInterest.has(request) && !modulesOfInterest.has(`${pkg}/${modulePath}`)) {
146
+ return
147
+ }
148
+
149
+ if (!pkgJson) {
150
+ return
151
+ }
152
+
153
+ let packageJson
154
+ try {
155
+ packageJson = JSON.parse(fs.readFileSync(pkgJson).toString())
156
+ } catch (e) {
157
+ if (e.code === 'ENOENT') {
158
+ log.debug(
159
+ 'Skipping `package.json` lookup for %s. The package may be vendored.',
160
+ pkg
161
+ )
162
+ return
163
+ }
164
+ throw e
165
+ }
166
+
167
+ if (isESMFile(normalizedResource, pkgJson, packageJson)) {
168
+ log.warn('Skipping ESM module (ESM support is not available in the webpack plugin): %s', resource)
169
+ return
170
+ }
171
+
172
+ const version = packageJson.version
173
+ const pkgPath = request === pkg ? pkg : `${pkg}/${modulePath}`
174
+
175
+ createData.loaders = createData.loaders || []
176
+ createData.loaders.unshift({
177
+ loader: require.resolve('./src/loader'),
178
+ options: { pkg, version, path: pkgPath },
179
+ })
180
+
181
+ log.debug('LOAD: %s@%s, pkg "%s"', pkg, version, pkgPath)
182
+ })
183
+ })
184
+ }
185
+ }
186
+
187
+ module.exports = DatadogWebpackPlugin
@@ -0,0 +1,27 @@
1
+ 'use strict'
2
+
3
+ const CHANNEL = 'dd-trace:bundler:load'
4
+
5
+ /**
6
+ * Webpack loader that appends a dc-polyfill channel publish to a CJS module.
7
+ * Called for each module-of-interest identified by DatadogWebpackPlugin.
8
+ *
9
+ * @param {string} source
10
+ * @returns {string}
11
+ */
12
+ module.exports = function loader (source) {
13
+ this.cacheable(false)
14
+ const { pkg, version, path: pkgPath } = this.getOptions()
15
+
16
+ return (
17
+ source +
18
+ '\n;{\n' +
19
+ ' const __dd_dc = require(\'dc-polyfill\');\n' +
20
+ ` const __dd_ch = __dd_dc.channel('${CHANNEL}');\n` +
21
+ ' const __dd_mod = module.exports;\n' +
22
+ ` const __dd_payload = { module: __dd_mod, version: '${version}', package: '${pkg}', path: '${pkgPath}' };\n` +
23
+ ' __dd_ch.publish(__dd_payload);\n' +
24
+ ' module.exports = __dd_payload.module;\n' +
25
+ '}\n'
26
+ )
27
+ }
@@ -0,0 +1,32 @@
1
+ 'use strict'
2
+
3
+ const { format } = require('util')
4
+
5
+ // eslint-disable-next-line eslint-rules/eslint-process-env
6
+ const DD_TRACE_DEBUG = (process.env.DD_TRACE_DEBUG || '').trim().toLowerCase()
7
+ const DEBUG = DD_TRACE_DEBUG === 'true' || DD_TRACE_DEBUG === '1'
8
+
9
+ const noop = () => {}
10
+
11
+ const formatWithLogPrefix = (prefix, str, ...args) => {
12
+ if (typeof str === 'string') {
13
+ return format(`${prefix} ${str}`, ...args)
14
+ }
15
+ return format(prefix, str, ...args)
16
+ }
17
+
18
+ module.exports = DEBUG
19
+ ? {
20
+ debug (...args) {
21
+ // eslint-disable-next-line no-console
22
+ console.log(formatWithLogPrefix('[dd-trace/webpack]', ...args))
23
+ },
24
+ warn (...args) {
25
+ // eslint-disable-next-line no-console
26
+ console.warn(formatWithLogPrefix('[dd-trace/webpack] Warning:', ...args))
27
+ },
28
+ }
29
+ : {
30
+ debug: noop,
31
+ warn: noop,
32
+ }