dd-trace 6.12.0 → 6.13.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 (97) hide show
  1. package/ci/test-optimization-validation/framework-adapters/cypress.js +9 -11
  2. package/ci/test-optimization-validation/scenarios/ci-wiring.js +5 -4
  3. package/index.d.ts +127 -26
  4. package/package.json +10 -10
  5. package/packages/datadog-instrumentations/src/bunyan.js +4 -2
  6. package/packages/datadog-instrumentations/src/helpers/bunyan.js +12 -3
  7. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +1 -0
  8. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +28 -0
  9. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
  10. package/packages/datadog-instrumentations/src/jest.js +140 -18
  11. package/packages/datadog-instrumentations/src/mariadb-bundle.js +1597 -0
  12. package/packages/datadog-instrumentations/src/mariadb.js +7 -0
  13. package/packages/datadog-instrumentations/src/pino.js +15 -4
  14. package/packages/datadog-instrumentations/src/playwright.js +34 -20
  15. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +7 -4
  16. package/packages/datadog-instrumentations/src/vitest-worker.js +11 -0
  17. package/packages/datadog-instrumentations/src/webdriverio.js +26 -0
  18. package/packages/datadog-instrumentations/src/winston.js +6 -3
  19. package/packages/datadog-plugin-cypress/src/support.js +1 -0
  20. package/packages/datadog-plugin-http/src/client.js +2 -15
  21. package/packages/datadog-plugin-http2/src/client.js +2 -15
  22. package/packages/datadog-plugin-openai-agents/src/integration.js +15 -11
  23. package/packages/datadog-plugin-playwright/src/index.js +9 -1
  24. package/packages/datadog-plugin-undici/src/index.js +2 -15
  25. package/packages/dd-trace/src/aiguard/evaluation.js +94 -20
  26. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +2 -3
  27. package/packages/dd-trace/src/aiguard/messages/anthropic.js +21 -9
  28. package/packages/dd-trace/src/aiguard/noop.js +9 -1
  29. package/packages/dd-trace/src/aiguard/redaction.js +183 -0
  30. package/packages/dd-trace/src/aiguard/sdk.js +7 -2
  31. package/packages/dd-trace/src/aiguard/tags.js +2 -0
  32. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +10 -9
  33. package/packages/dd-trace/src/appsec/rule_manager.js +2 -0
  34. package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +51 -26
  35. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +5 -11
  36. package/packages/dd-trace/src/ci-visibility/final-flush.js +20 -0
  37. package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +196 -29
  38. package/packages/dd-trace/src/config/generated-config-types.d.ts +6 -0
  39. package/packages/dd-trace/src/config/index.js +2 -1
  40. package/packages/dd-trace/src/config/supported-configurations.json +27 -0
  41. package/packages/dd-trace/src/debugger/devtools_client/send.js +4 -3
  42. package/packages/dd-trace/src/dogstatsd.js +45 -18
  43. package/packages/dd-trace/src/encode/agentless-json.js +5 -4
  44. package/packages/dd-trace/src/exporters/agent/index.js +22 -2
  45. package/packages/dd-trace/src/exporters/agent/writer.js +4 -4
  46. package/packages/dd-trace/src/exporters/common/writer.js +24 -2
  47. package/packages/dd-trace/src/exporters/span-stats/index.js +23 -4
  48. package/packages/dd-trace/src/external-logger/src/index.js +4 -3
  49. package/packages/dd-trace/src/flush.js +102 -0
  50. package/packages/dd-trace/src/llmobs/experiments/client.js +4 -0
  51. package/packages/dd-trace/src/llmobs/experiments/dataset.js +51 -2
  52. package/packages/dd-trace/src/llmobs/experiments/experiment.js +420 -105
  53. package/packages/dd-trace/src/llmobs/experiments/index.js +54 -42
  54. package/packages/dd-trace/src/llmobs/experiments/noop.js +39 -8
  55. package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
  56. package/packages/dd-trace/src/llmobs/experiments/util.js +21 -0
  57. package/packages/dd-trace/src/llmobs/index.js +80 -17
  58. package/packages/dd-trace/src/llmobs/noop.js +2 -1
  59. package/packages/dd-trace/src/llmobs/plugins/anthropic/util.js +8 -4
  60. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +6 -3
  61. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +13 -6
  62. package/packages/dd-trace/src/llmobs/plugins/openai-agents/utils.js +5 -7
  63. package/packages/dd-trace/src/llmobs/writers/base.js +60 -24
  64. package/packages/dd-trace/src/noop/span.js +5 -0
  65. package/packages/dd-trace/src/openfeature/writers/exposures.js +8 -1
  66. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +45 -3
  67. package/packages/dd-trace/src/opentelemetry/logs/index.js +4 -1
  68. package/packages/dd-trace/src/opentelemetry/logs/logger_provider.js +7 -4
  69. package/packages/dd-trace/src/opentelemetry/metrics/index.js +3 -1
  70. package/packages/dd-trace/src/opentelemetry/metrics/meter_provider.js +8 -0
  71. package/packages/dd-trace/src/opentelemetry/metrics/otlp_http_metric_exporter.js +4 -2
  72. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +4 -2
  73. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +25 -5
  74. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +61 -25
  75. package/packages/dd-trace/src/opentelemetry/tracer.js +26 -2
  76. package/packages/dd-trace/src/opentracing/propagation/text_map.js +410 -342
  77. package/packages/dd-trace/src/opentracing/span.js +28 -0
  78. package/packages/dd-trace/src/otel-thread-ctx.js +23 -5
  79. package/packages/dd-trace/src/plugin_manager.js +2 -0
  80. package/packages/dd-trace/src/plugins/util/status-validator.js +98 -0
  81. package/packages/dd-trace/src/plugins/util/test.js +74 -21
  82. package/packages/dd-trace/src/plugins/util/web.js +2 -65
  83. package/packages/dd-trace/src/profiling/oom.js +4 -3
  84. package/packages/dd-trace/src/profiling/profiler.js +7 -0
  85. package/packages/dd-trace/src/profiling/profilers/events.js +3 -3
  86. package/packages/dd-trace/src/profiling/profilers/wall.js +14 -14
  87. package/packages/dd-trace/src/proxy.js +36 -8
  88. package/packages/dd-trace/src/runtime_metrics/index.js +5 -0
  89. package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +5 -0
  90. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +17 -6
  91. package/packages/dd-trace/src/serverless/telemetry-delivery-tracker.js +55 -0
  92. package/packages/dd-trace/src/serverless/vercel.js +122 -0
  93. package/packages/dd-trace/src/serverless.js +37 -20
  94. package/packages/dd-trace/src/span_stats.js +40 -3
  95. package/packages/dd-trace/src/tracer.js +22 -0
  96. package/packages/dd-trace/src/web-tags-cache.js +67 -13
  97. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
@@ -14,6 +14,7 @@ const {
14
14
  takePoolWaitTime,
15
15
  wrapPoolQueryMethod,
16
16
  } = require('./helpers/pool-acquire')
17
+ const { wrapCallbackBundle, wrapPromiseBundle } = require('./mariadb-bundle')
17
18
 
18
19
  const commandAddCh = channel('apm:mariadb:command:add')
19
20
  const connectionStartCh = channel('apm:mariadb:connection:start')
@@ -443,3 +444,9 @@ addHook({ name, file: 'lib/connection.js', versions: ['>=2.0.4 <=2.5.1'] }, (Con
443
444
  addHook({ name, file: 'lib/pool-base.js', versions: ['>=2.0.4 <3'] }, (PoolBase) => {
444
445
  return shimmer.wrapFunction(PoolBase, wrapPoolBase)
445
446
  })
447
+
448
+ // MariaDB 3.5.3 added single-file CommonJS bundles that do not load the original source modules at runtime.
449
+ // Matching their generated, minified internals would couple instrumentation to unstable bundle output, so wrap
450
+ // the runtime objects returned by the public factories instead.
451
+ addHook({ name, versions: ['>=3.5.3'] }, wrapPromiseBundle)
452
+ addHook({ name, file: 'dist/callback.cjs', versions: ['>=3.5.3'] }, wrapCallbackBundle)
@@ -6,6 +6,8 @@ const {
6
6
  addHook,
7
7
  } = require('./helpers/instrument')
8
8
 
9
+ const logSubmissionCh = channel('ci:log-submission:log')
10
+
9
11
  /**
10
12
  * @param {string} symbol
11
13
  * @param {(original: Function) => Function} wrapper
@@ -36,13 +38,22 @@ function wrapAsJson (asJson) {
36
38
  obj = arguments[0] = obj || {}
37
39
 
38
40
  // Caller-provided `dd` wins -- skip the splice so a bespoke `dd` survives.
41
+ let line
39
42
  if (!jsonCh.hasSubscribers || Object.hasOwn(obj, 'dd')) {
40
- return asJson.apply(this, arguments)
43
+ line = asJson.apply(this, arguments)
44
+ } else {
45
+ const payload = { line: asJson.apply(this, arguments) }
46
+ jsonCh.publish(payload)
47
+ line = payload.line
48
+ }
49
+
50
+ // Submit the serialized line for agentless log collection only when trace
51
+ // correlation is active, matching the Bunyan contract.
52
+ if (jsonCh.hasSubscribers && logSubmissionCh.hasSubscribers) {
53
+ logSubmissionCh.publish({ source: 'pino', message: line })
41
54
  }
42
55
 
43
- const payload = { line: asJson.apply(this, arguments) }
44
- jsonCh.publish(payload)
45
- return payload.line
56
+ return line
46
57
  }
47
58
  }
48
59
 
@@ -59,6 +59,7 @@ const testSuiteStartCh = channel('ci:playwright:test-suite:start')
59
59
  const testSuiteFinishCh = channel('ci:playwright:test-suite:finish')
60
60
 
61
61
  const workerReportCh = channel('ci:playwright:worker:report')
62
+ const logSubmissionFlushCh = channel('ci:log-submission:flush')
62
63
  const workerReportTelemetryCh = channel('ci:playwright:worker-report:telemetry')
63
64
  const testPageGotoCh = channel('ci:playwright:test:page-goto')
64
65
 
@@ -1085,7 +1086,7 @@ function onDispatcherCreateWorker (dispatcher, worker) {
1085
1086
 
1086
1087
  const projects = getProjectsFromDispatcher(dispatcher)
1087
1088
  sessionProjects = projects
1088
- const automaticFailureScreenshotPathsByTestId = new Map()
1089
+ const automaticFailureScreenshotsByTestId = new Map()
1089
1090
 
1090
1091
  if (disabledTestIds.size && !worker[kDdPlaywrightWorkerHostInstrumented] &&
1091
1092
  typeof worker.runTestGroup === 'function') {
@@ -1110,19 +1111,21 @@ function onDispatcherCreateWorker (dispatcher, worker) {
1110
1111
  const test = getTestByTestId(dispatcher, testId)
1111
1112
  if (!test) return
1112
1113
 
1114
+ automaticFailureScreenshotsByTestId.clear()
1113
1115
  const browser = getBrowserNameFromProjects(projects, test)
1114
1116
  const shouldCreateTestSpan = test.expectedStatus === 'skipped'
1115
1117
  testBeginHandler(test, browser, shouldCreateTestSpan)
1116
1118
  })
1117
- worker.on('attach', ({ testId, path, _ddIsAutomaticFailureScreenshot }) => {
1119
+ worker.on('attach', (attachment) => {
1120
+ const { testId, _ddIsAutomaticFailureScreenshot } = attachment
1118
1121
  if (!_ddIsAutomaticFailureScreenshot) return
1119
1122
 
1120
- let screenshotPaths = automaticFailureScreenshotPathsByTestId.get(testId)
1121
- if (!screenshotPaths) {
1122
- screenshotPaths = new Set()
1123
- automaticFailureScreenshotPathsByTestId.set(testId, screenshotPaths)
1123
+ let screenshots = automaticFailureScreenshotsByTestId.get(testId)
1124
+ if (!screenshots) {
1125
+ screenshots = []
1126
+ automaticFailureScreenshotsByTestId.set(testId, screenshots)
1124
1127
  }
1125
- screenshotPaths.add(path)
1128
+ screenshots.push(attachment)
1126
1129
  })
1127
1130
  worker.on('testEnd', ({ testId, status, errors, annotations }) => {
1128
1131
  const test = getTestByTestId(dispatcher, testId)
@@ -1146,19 +1149,20 @@ function onDispatcherCreateWorker (dispatcher, worker) {
1146
1149
  }
1147
1150
  )
1148
1151
  const testResult = test.results.at(-1)
1149
- const automaticFailureScreenshotPaths = automaticFailureScreenshotPathsByTestId.get(testId)
1150
- automaticFailureScreenshotPathsByTestId.delete(testId)
1151
- if (testStatus === 'fail' && automaticFailureScreenshotPaths?.size && testResult?.attachments?.length) {
1152
- const screenshots = []
1153
- for (const attachment of testResult.attachments) {
1154
- if (automaticFailureScreenshotPaths.has(attachment.path)) {
1155
- screenshots.push(attachment)
1152
+ if (isFailureScreenshotUploadEnabled &&
1153
+ !shouldCreateTestSpan &&
1154
+ !test._ddShouldSkipEfdRetry &&
1155
+ !disabledTestIds.has(testId)) {
1156
+ let screenshots
1157
+ if (testStatus === 'fail') {
1158
+ screenshots = automaticFailureScreenshotsByTestId.get(testId)
1159
+ if (!screenshots) {
1160
+ screenshots = []
1161
+ automaticFailureScreenshotsByTestId.set(testId, screenshots)
1156
1162
  }
1157
1163
  }
1158
- if (screenshots.length) {
1159
- worker[kDdPlaywrightFailureScreenshots] ??= []
1160
- worker[kDdPlaywrightFailureScreenshots].push(screenshots)
1161
- }
1164
+ worker[kDdPlaywrightFailureScreenshots] ??= []
1165
+ worker[kDdPlaywrightFailureScreenshots].push(screenshots)
1162
1166
  }
1163
1167
  const isAtrRetry = testResult?.retry > 0 &&
1164
1168
  isFlakyTestRetriesEnabled &&
@@ -2212,7 +2216,8 @@ addHook({
2212
2216
 
2213
2217
  function instrumentWorkerMainMethods (workerMain) {
2214
2218
  if (!workerMain || workerMain[kDdPlaywrightWorkerInstrumented] ||
2215
- typeof workerMain._runTest !== 'function' || typeof workerMain.dispatchEvent !== 'function') {
2219
+ typeof workerMain._runTest !== 'function' || typeof workerMain.dispatchEvent !== 'function' ||
2220
+ typeof workerMain.gracefullyClose !== 'function') {
2216
2221
  return workerMain
2217
2222
  }
2218
2223
 
@@ -2228,6 +2233,15 @@ function instrumentWorkerMainMethods (workerMain) {
2228
2233
  return runTestGroup.apply(this, arguments)
2229
2234
  })
2230
2235
 
2236
+ // Playwright >=1.60 creates WorkerMain through a runtime factory, which Orchestrion cannot wrap statically.
2237
+ shimmer.wrap(workerMain, 'gracefullyClose', gracefullyClose => async function () {
2238
+ try {
2239
+ return await gracefullyClose.apply(this, arguments)
2240
+ } finally {
2241
+ await getChannelPromise(logSubmissionFlushCh)
2242
+ }
2243
+ })
2244
+
2231
2245
  shimmer.wrap(workerMain, '_runTest', _runTest => async function (test) {
2232
2246
  if (this[kDdPlaywrightDisabledTestIds]?.has(test.id)) {
2233
2247
  test._ddIsDisabled = true
@@ -2393,7 +2407,7 @@ function instrumentWorkerMainMethods (workerMain) {
2393
2407
  // We reproduce what happens in `Dispatcher#_onStepBegin` and `Dispatcher#_onStepEnd`,
2394
2408
  // since `startTime` and `duration` are not available directly in the worker process
2395
2409
  shimmer.wrap(workerMain, 'dispatchEvent', dispatchEvent => function (event, payload) {
2396
- if (event === 'testBegin' || event === 'testEnd') {
2410
+ if (event === 'testBegin') {
2397
2411
  automaticFailureScreenshotPaths.clear()
2398
2412
  } else if (event === 'stepBegin') {
2399
2413
  stepInfoByStepId[payload.stepId] = {
@@ -1602,15 +1602,18 @@ function splitNodeOptions (nodeOptions) {
1602
1602
  }
1603
1603
 
1604
1604
  function serializeNodeOptions (tokens) {
1605
- const serializedTokens = []
1605
+ let serializedTokens = ''
1606
+ let hasSerializedToken = false
1606
1607
  for (const token of tokens) {
1608
+ if (hasSerializedToken) serializedTokens += ' '
1607
1609
  if (NODE_OPTIONS_QUOTE_RE.test(token)) {
1608
- serializedTokens.push(JSON.stringify(token))
1610
+ serializedTokens += JSON.stringify(token)
1609
1611
  } else {
1610
- serializedTokens.push(token)
1612
+ serializedTokens += token
1611
1613
  }
1614
+ hasSerializedToken = true
1612
1615
  }
1613
- return serializedTokens.join(' ')
1616
+ return serializedTokens
1614
1617
  }
1615
1618
 
1616
1619
  function getTestSpecificationProject (testSpecification) {
@@ -5,6 +5,7 @@ const { performance } = require('node:perf_hooks')
5
5
  const { fileURLToPath } = require('node:url')
6
6
  const { isMainThread, parentPort } = require('node:worker_threads')
7
7
 
8
+ const { channel } = require('dc-polyfill')
8
9
  const shimmer = require('../../datadog-shimmer')
9
10
  const log = require('../../dd-trace/src/log')
10
11
  const { getEfdRetryCountForDuration } = require('../../dd-trace/src/ci-visibility/efd-retry-policy')
@@ -40,10 +41,12 @@ const {
40
41
  } = require('./vitest-util')
41
42
 
42
43
  const EFD_SUITE_ADMISSION_TIMEOUT_MS = 5000
44
+ const logSubmissionFlushCh = channel('ci:log-submission:flush')
43
45
  const taskToCtx = new WeakMap()
44
46
  const taskToTestProperties = new WeakMap()
45
47
  const taskToStatuses = new WeakMap()
46
48
  const taskToReportedErrorCount = new WeakMap()
49
+ const runnersWithLogSubmissionCleanup = new WeakSet()
47
50
  const attemptToFixTaskToStatuses = new WeakMap()
48
51
  const fileToHasConcurrentTests = new WeakMap()
49
52
  const fileToEfdSuiteAdmission = new WeakMap()
@@ -954,6 +957,14 @@ addHook({
954
957
  if (!testSuiteFinishCh.hasSubscribers) {
955
958
  return startTests.apply(this, arguments)
956
959
  }
960
+ const runner = arguments[1]
961
+ // Vitest 3+ exposes the only awaited worker-shutdown boundary; older versions keep timer/before-exit behavior.
962
+ if (logSubmissionFlushCh.hasSubscribers &&
963
+ typeof runner?.onCleanupWorkerContext === 'function' &&
964
+ !runnersWithLogSubmissionCleanup.has(runner)) {
965
+ runnersWithLogSubmissionCleanup.add(runner)
966
+ runner.onCleanupWorkerContext(() => getChannelPromise(logSubmissionFlushCh))
967
+ }
957
968
  // From >=3.0.1, the first arguments changes from a string to an object containing the filepath
958
969
  const testSuiteAbsolutePath = testPaths[0]?.filepath || testPaths[0]
959
970
  const providedContext = getProvidedContext()
@@ -16,6 +16,7 @@ const {
16
16
  MOCHA_WORKER_TRACE_PAYLOAD_CODE,
17
17
  TEST_SUITE_EXECUTION_ID,
18
18
  } = require('../../dd-trace/src/plugins/util/test')
19
+ const { publishWithCompletion } = require('./helpers/channel')
19
20
  const { addHook, channel, tracingChannel } = require('./helpers/instrument')
20
21
  const {
21
22
  CONFIGURATION_REQUEST,
@@ -38,6 +39,7 @@ const testSuiteStartCh = channel('ci:mocha:test-suite:start')
38
39
  const testSuiteFinishCh = channel('ci:mocha:test-suite:finish')
39
40
  const knownTestsCh = channel('ci:mocha:known-tests')
40
41
  const libraryConfigurationCh = channel('ci:mocha:library-configuration')
42
+ const logSubmissionFlushCh = channel('ci:log-submission:flush')
41
43
  const modifiedFilesCh = channel('ci:mocha:modified-files')
42
44
  const testManagementTestsCh = channel('ci:mocha:test-management-tests')
43
45
  const workerConfigurationCh = channel('ci:mocha:worker:configuration')
@@ -46,6 +48,7 @@ const workerReportTelemetryCh = channel('ci:mocha:worker-report:telemetry')
46
48
  const workerReportTraceCh = channel('ci:mocha:worker-report:trace')
47
49
 
48
50
  const jasmineAdapterInitCh = tracingChannel('orchestrion:@wdio/jasmine-framework:JasmineAdapter_init')
51
+ const baseReporterWaitForSyncCh = tracingChannel('orchestrion:@wdio/runner:BaseReporter_waitForSync')
49
52
  const launcherStartInstanceCh = tracingChannel('orchestrion:@wdio/cli:Launcher_startInstance')
50
53
  const localRunnerRunCh = tracingChannel('orchestrion:@wdio/local-runner:LocalRunner_run')
51
54
  const localRunnerShutdownCh = tracingChannel('orchestrion:@wdio/local-runner:LocalRunner_shutdown')
@@ -1000,6 +1003,29 @@ function finishCoordinator (state, error, onDone) {
1000
1003
  })
1001
1004
  }
1002
1005
 
1006
+ /**
1007
+ * Delays WebdriverIO worker exit until pending log-submission requests settle.
1008
+ *
1009
+ * @param {{
1010
+ * resolveCallback?: (onDone: () => void) => void,
1011
+ * rejectCallback?: (onDone: () => void) => void
1012
+ * }} context
1013
+ * @returns {void}
1014
+ */
1015
+ function waitForLogSubmissionAtWorkerExit (context) {
1016
+ if (!isWebdriverioWorker || !logSubmissionFlushCh.hasSubscribers) {
1017
+ return
1018
+ }
1019
+
1020
+ const waitForLogs = onDone => publishWithCompletion(logSubmissionFlushCh, {}, onDone)
1021
+ context.resolveCallback = waitForLogs
1022
+ context.rejectCallback = waitForLogs
1023
+ }
1024
+
1025
+ baseReporterWaitForSyncCh.asyncEnd.subscribe(
1026
+ /** @type {import('node:diagnostics_channel').ChannelListener} */ (waitForLogSubmissionAtWorkerExit)
1027
+ )
1028
+
1003
1029
  // dc-polyfill supports partial tracing-channel subscribers, unlike the Node.js type definition.
1004
1030
  // @ts-expect-error
1005
1031
  jasmineAdapterInitCh.subscribe({
@@ -12,12 +12,15 @@ const patched = new WeakSet()
12
12
  const configureCh = channel('ci:log-submission:winston:configure')
13
13
  const addTransport = channel('ci:log-submission:winston:add-transport')
14
14
 
15
- addHook({ name: 'winston', file: 'lib/winston/transports/index.js', versions: ['>=3'] }, transportsPackage => {
15
+ addHook({ name: 'winston', versions: ['>=3'] }, winston => {
16
16
  if (configureCh.hasSubscribers) {
17
- configureCh.publish(transportsPackage.Http)
17
+ configureCh.publish({
18
+ createJsonFormat: winston.format.json,
19
+ StreamTransport: winston.transports.Stream,
20
+ })
18
21
  }
19
22
 
20
- return transportsPackage
23
+ return winston
21
24
  })
22
25
 
23
26
  addHook({ name: 'winston', file: 'lib/winston/logger.js', versions: ['>=3'] }, Logger => {
@@ -409,6 +409,7 @@ beforeEach(function () {
409
409
  rumCookiePromise = setRumCorrelationCookie(traceId)
410
410
  }
411
411
  if (shouldSkip) {
412
+ // Test Optimization requested this runtime skip through the Cypress support hook.
412
413
  this.skip()
413
414
  }
414
415
  if (rumCookiePromise) {
@@ -8,8 +8,8 @@ const tags = require('../../../ext/tags')
8
8
  const formats = require('../../../ext/formats')
9
9
  const HTTP_HEADERS = formats.HTTP_HEADERS
10
10
  const urlFilter = require('../../dd-trace/src/plugins/util/urlfilter')
11
+ const { getClientStatusValidator } = require('../../dd-trace/src/plugins/util/status-validator')
11
12
  const { buildClientHttpUrl } = require('../../dd-trace/src/plugins/util/url')
12
- const log = require('../../dd-trace/src/log')
13
13
  const { stripQueryAndFragment } = require('../../dd-trace/src/util')
14
14
  const { CLIENT_PORT_KEY, COMPONENT, ERROR_MESSAGE, ERROR_TYPE, ERROR_STACK } = require('../../dd-trace/src/constants')
15
15
 
@@ -167,7 +167,7 @@ function addRequestHeaders (req, span, config) {
167
167
  }
168
168
 
169
169
  function normalizeClientConfig (config) {
170
- const validateStatus = getStatusValidator(config)
170
+ const validateStatus = getClientStatusValidator(config)
171
171
  const filter = getFilter(config)
172
172
  const propagationFilter = getFilter({ blocklist: config.propagationBlocklist })
173
173
  const headers = getHeaders(config)
@@ -183,19 +183,6 @@ function normalizeClientConfig (config) {
183
183
  }
184
184
  }
185
185
 
186
- function is400ErrorCode (code) {
187
- return code < 400 || code >= 500
188
- }
189
-
190
- function getStatusValidator (config) {
191
- if (typeof config.validateStatus === 'function') {
192
- return config.validateStatus
193
- } else if (config.hasOwnProperty('validateStatus')) {
194
- log.error('Expected `validateStatus` to be a function.')
195
- }
196
- return is400ErrorCode
197
- }
198
-
199
186
  function getFilter (config) {
200
187
  config = { ...config, blocklist: config.blocklist || [] }
201
188
 
@@ -4,12 +4,12 @@ const URL = require('url').URL
4
4
 
5
5
  const { storage } = require('../../datadog-core')
6
6
  const ClientPlugin = require('../../dd-trace/src/plugins/client')
7
- const log = require('../../dd-trace/src/log')
8
7
  const tags = require('../../../ext/tags')
9
8
  const kinds = require('../../../ext/kinds')
10
9
  const formats = require('../../../ext/formats')
11
10
  const { COMPONENT, CLIENT_PORT_KEY } = require('../../dd-trace/src/constants')
12
11
  const urlFilter = require('../../dd-trace/src/plugins/util/urlfilter')
12
+ const { getClientStatusValidator } = require('../../dd-trace/src/plugins/util/status-validator')
13
13
  const { buildClientHttpUrl } = require('../../dd-trace/src/plugins/util/url')
14
14
 
15
15
  const HTTP_HEADERS = formats.HTTP_HEADERS
@@ -165,21 +165,8 @@ function hasAmazonSignature (headers, path) {
165
165
  return false
166
166
  }
167
167
 
168
- function is400ErrorCode (code) {
169
- return code < 400 || code >= 500
170
- }
171
-
172
- function getStatusValidator (config) {
173
- if (typeof config.validateStatus === 'function') {
174
- return config.validateStatus
175
- } else if (config.hasOwnProperty('validateStatus')) {
176
- log.error('Expected `validateStatus` to be a function.')
177
- }
178
- return is400ErrorCode
179
- }
180
-
181
168
  function normalizeConfig (config) {
182
- const validateStatus = getStatusValidator(config)
169
+ const validateStatus = getClientStatusValidator(config)
183
170
  const filter = getFilter(config)
184
171
  const headers = getHeaders(config)
185
172
 
@@ -63,6 +63,7 @@ class OpenAIAgentsIntegration {
63
63
  #tracer
64
64
  #config
65
65
  #enabled = false
66
+ #llmobsEnabled = true
66
67
  #service
67
68
  /**
68
69
  * LLMObs is gated independently of APM tracing: when DD_LLMOBS_ENABLED is
@@ -95,10 +96,11 @@ class OpenAIAgentsIntegration {
95
96
  /**
96
97
  * Apply plugin lifecycle configuration.
97
98
  *
98
- * @param {{ enabled?: boolean, service?: string }} [config]
99
+ * @param {{ enabled?: boolean, llmobs?: boolean, service?: string }} [config]
99
100
  */
100
101
  configure (config) {
101
102
  this.#enabled = !!config?.enabled
103
+ this.#llmobsEnabled = config?.llmobs !== false
102
104
  this.#service = config?.service
103
105
  }
104
106
 
@@ -210,15 +212,17 @@ class OpenAIAgentsIntegration {
210
212
  completionRequested: false,
211
213
  })
212
214
 
213
- this.#tagger.registerLLMObsSpan(ddSpan, {
214
- kind: 'workflow',
215
- name,
216
- integration: COMPONENT,
217
- parent: llmobsParentStore?.span,
218
- sessionId: oaiTrace.groupId || undefined,
219
- })
220
- if (LLMObsTagger.tagMap.has(ddSpan)) {
221
- llmobsStorage.enterWith({ ...llmobsParentStore, span: ddSpan })
215
+ if (this.#isLLMObsEnabled()) {
216
+ this.#tagger.registerLLMObsSpan(ddSpan, {
217
+ kind: 'workflow',
218
+ name,
219
+ integration: COMPONENT,
220
+ parent: llmobsParentStore?.span,
221
+ sessionId: oaiTrace.groupId || undefined,
222
+ })
223
+ if (LLMObsTagger.tagMap.has(ddSpan)) {
224
+ llmobsStorage.enterWith({ ...llmobsParentStore, span: ddSpan })
225
+ }
222
226
  }
223
227
  }
224
228
 
@@ -680,7 +684,7 @@ class OpenAIAgentsIntegration {
680
684
  * @returns {boolean}
681
685
  */
682
686
  #isLLMObsEnabled () {
683
- return !!this.#config.llmobs?.DD_LLMOBS_ENABLED
687
+ return this.#llmobsEnabled && !!this.#config.llmobs?.DD_LLMOBS_ENABLED
684
688
  }
685
689
 
686
690
  /**
@@ -523,7 +523,15 @@ class PlaywrightPlugin extends CiPlugin {
523
523
 
524
524
  finishAllTraceSpans(span)
525
525
  if (this._tracerConfig.DD_PLAYWRIGHT_WORKER) {
526
- this.tracer._exporter.flush(onDone)
526
+ try {
527
+ this.tracer._exporter.flush(onDone)
528
+ } catch (error) {
529
+ // A synchronous flush failure must still release the worker, otherwise it hangs.
530
+ // Log rather than rethrow: an exception from this diagnostics-channel subscriber
531
+ // would surface as an uncaught error in the worker and crash the user's test run.
532
+ onDone?.()
533
+ log.error('Error flushing traces at Playwright worker shutdown', error)
534
+ }
527
535
  }
528
536
  })
529
537
 
@@ -5,7 +5,7 @@ const { storage } = require('../../datadog-core')
5
5
  const tags = require('../../../ext/tags')
6
6
  const formats = require('../../../ext/formats')
7
7
  const HTTP_HEADERS = formats.HTTP_HEADERS
8
- const log = require('../../dd-trace/src/log')
8
+ const { getClientStatusValidator } = require('../../dd-trace/src/plugins/util/status-validator')
9
9
  const { buildClientHttpUrl } = require('../../dd-trace/src/plugins/util/url')
10
10
  const { stripQueryAndFragment } = require('../../dd-trace/src/util')
11
11
  const { CLIENT_PORT_KEY } = require('../../dd-trace/src/constants')
@@ -302,7 +302,7 @@ function normalizeHeaders (headers) {
302
302
  }
303
303
 
304
304
  function normalizeConfig (config) {
305
- const validateStatus = getStatusValidator(config)
305
+ const validateStatus = getClientStatusValidator(config)
306
306
  const hooks = getHooks(config)
307
307
 
308
308
  return {
@@ -312,19 +312,6 @@ function normalizeConfig (config) {
312
312
  }
313
313
  }
314
314
 
315
- function getStatusValidator (config) {
316
- if (typeof config.validateStatus === 'function') {
317
- return config.validateStatus
318
- } else if (Object.hasOwn(config, 'validateStatus')) {
319
- log.error('Expected `validateStatus` to be a function.')
320
- }
321
- return defaultValidateStatus
322
- }
323
-
324
- function defaultValidateStatus (code) {
325
- return code < 400 || code >= 500
326
- }
327
-
328
315
  function getHooks (config) {
329
316
  const request = config.hooks?.request ?? noop
330
317