dd-trace 6.14.0 → 6.16.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.
- package/README.md +15 -0
- package/ci/test-optimization-validation/framework-adapters/cucumber.js +4 -2
- package/ci/test-optimization-validation/package-check.js +4 -1
- package/ci/test-optimization-validation/scenarios/ci-wiring.js +1 -1
- package/index.d.ts +12 -0
- package/next.d.ts +27 -0
- package/next.js +3 -0
- package/package.json +17 -13
- package/packages/datadog-esbuild/index.js +14 -1
- package/packages/datadog-esbuild/src/resolver.js +284 -0
- package/packages/datadog-esbuild/src/utils.js +163 -119
- package/packages/datadog-instrumentations/src/ai.js +39 -97
- package/packages/datadog-instrumentations/src/anthropic.js +76 -134
- package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js.js +1 -1
- package/packages/datadog-instrumentations/src/azure-cosmos.js +1 -1
- package/packages/datadog-instrumentations/src/azure-durable-functions.js +23 -9
- package/packages/datadog-instrumentations/src/azure-functions.js +48 -0
- package/packages/datadog-instrumentations/src/bullmq.js +1 -1
- package/packages/datadog-instrumentations/src/claude-agent-sdk.js +1 -1
- package/packages/datadog-instrumentations/src/dd-trace-api.js +2 -2
- package/packages/datadog-instrumentations/src/graphql.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/bundler-register.js +39 -14
- package/packages/datadog-instrumentations/src/helpers/instrument.js +25 -8
- package/packages/datadog-instrumentations/src/helpers/instrumentation-utils.js +72 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +9 -64
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +46 -14
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/azure-durable-functions.js +17 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +1 -5
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +6 -7
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +99 -70
- package/packages/datadog-instrumentations/src/jest.js +141 -25
- package/packages/datadog-instrumentations/src/langchain.js +1 -1
- package/packages/datadog-instrumentations/src/langgraph.js +1 -1
- package/packages/datadog-instrumentations/src/mercurius.js +1 -1
- package/packages/datadog-instrumentations/src/mocha/main.js +41 -5
- package/packages/datadog-instrumentations/src/modelcontextprotocol-sdk.js +1 -1
- package/packages/datadog-instrumentations/src/openai-agents.js +1 -1
- package/packages/datadog-instrumentations/src/openai.js +54 -76
- package/packages/datadog-instrumentations/src/playwright.js +4 -0
- package/packages/datadog-instrumentations/src/vitest-worker.js +7 -10
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +7 -1
- package/packages/datadog-plugin-azure-cosmos/src/index.js +1 -1
- package/packages/datadog-plugin-azure-durable-functions/src/index.js +96 -1
- package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -24
- package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +2 -36
- package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-request-context.js +31 -0
- package/packages/datadog-plugin-graphql/src/resolve-error.js +33 -11
- package/packages/datadog-turbopack/index.js +212 -0
- package/packages/datadog-turbopack/src/constants.js +5 -0
- package/packages/datadog-turbopack/src/loader.js +500 -0
- package/packages/dd-trace/src/aiguard/index.js +2 -1
- package/packages/dd-trace/src/aiguard/integrations/anthropic.js +53 -25
- package/packages/dd-trace/src/aiguard/integrations/evaluate.js +49 -25
- package/packages/dd-trace/src/aiguard/integrations/index.js +3 -2
- package/packages/dd-trace/src/aiguard/integrations/openai.js +59 -30
- package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +116 -42
- package/packages/dd-trace/src/aiguard/messages/utils.js +29 -1
- package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +25 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +3 -1
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +531 -70
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +2 -2
- package/packages/dd-trace/src/appsec/telemetry/api_security.js +1 -3
- package/packages/dd-trace/src/appsec/telemetry/common.js +4 -0
- package/packages/dd-trace/src/appsec/telemetry/index.js +23 -5
- package/packages/dd-trace/src/appsec/telemetry/rasp.js +1 -20
- package/packages/dd-trace/src/appsec/telemetry/user.js +1 -3
- package/packages/dd-trace/src/appsec/telemetry/waf.js +1 -20
- package/packages/dd-trace/src/ci-visibility/requests/request.js +11 -0
- package/packages/dd-trace/src/ci-visibility/requests/video-request.js +4 -0
- package/packages/dd-trace/src/config/generated-config-types.d.ts +2 -0
- package/packages/dd-trace/src/config/helper.js +10 -2
- package/packages/dd-trace/src/config/index.js +20 -5
- package/packages/dd-trace/src/config/supported-configurations.json +10 -0
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +14 -7
- package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +14 -1
- package/packages/dd-trace/src/debugger/devtools_client/request-options.js +1 -6
- package/packages/dd-trace/src/evp_proxy/direct.js +2 -28
- package/packages/dd-trace/src/evp_proxy/discovery.js +3 -4
- package/packages/dd-trace/src/evp_proxy/path.js +12 -3
- package/packages/dd-trace/src/exporters/agentless/writer.js +3 -1
- package/packages/dd-trace/src/exporters/common/docker.js +4 -1
- package/packages/dd-trace/src/exporters/common/proxy.js +55 -0
- package/packages/dd-trace/src/exporters/common/request.js +11 -3
- package/packages/dd-trace/src/opentelemetry/metrics/index.js +1 -1
- package/packages/dd-trace/src/opentelemetry/metrics/meter_provider.js +5 -4
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +106 -25
- package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +12 -1
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +8 -2
- package/packages/dd-trace/src/plugin_manager.js +9 -3
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/util/test.js +4 -1
- package/packages/dd-trace/src/priority_sampler.js +4 -0
- package/packages/dd-trace/src/sampling_rule.js +3 -1
- package/packages/dd-trace/src/span_processor.js +18 -3
- package/packages/dd-trace/src/telemetry/send-data.js +5 -4
- package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/utils.js +0 -10
|
@@ -11,7 +11,7 @@ const path = require('path')
|
|
|
11
11
|
const satisfies = require('../../../vendor/dist/semifies')
|
|
12
12
|
const { DD_MAJOR } = require('../../../version')
|
|
13
13
|
const shimmer = require('../../datadog-shimmer')
|
|
14
|
-
const { getEnvironmentVariable } = require('../../dd-trace/src/config/helper')
|
|
14
|
+
const { getEnvironmentVariable, getValueFromEnvSources } = require('../../dd-trace/src/config/helper')
|
|
15
15
|
const log = require('../../dd-trace/src/log')
|
|
16
16
|
const {
|
|
17
17
|
EMPTY_EFD_RETRY_POLICY,
|
|
@@ -64,6 +64,7 @@ const { addHook, channel } = require('./helpers/instrument')
|
|
|
64
64
|
const testSessionStartCh = channel('ci:jest:session:start')
|
|
65
65
|
const testSessionFinishCh = channel('ci:jest:session:finish')
|
|
66
66
|
const codeCoverageReportCh = channel('ci:jest:coverage-report')
|
|
67
|
+
const bundlerLoadCh = channel('dd-trace:bundler:load')
|
|
67
68
|
|
|
68
69
|
const testSessionConfigurationCh = channel('ci:jest:session:configuration')
|
|
69
70
|
|
|
@@ -179,7 +180,9 @@ const wrappedJestEsmLoaders = new WeakSet()
|
|
|
179
180
|
const wrappedJestObjects = new WeakSet()
|
|
180
181
|
const wrappedWorkerInitializers = new WeakSet()
|
|
181
182
|
const publishedRuntimeReferenceErrors = new WeakMap()
|
|
182
|
-
const
|
|
183
|
+
const jestEsmLoggingModulePathsByRuntime = new WeakMap()
|
|
184
|
+
const jestLoggingPackagesByRuntime = new WeakMap()
|
|
185
|
+
const instrumentedJestLoggingModules = new WeakMap()
|
|
183
186
|
const wrappedCoverageReporters = new WeakSet()
|
|
184
187
|
const coverageReporterRequires = new WeakMap()
|
|
185
188
|
const handledJestEvents = new WeakSet()
|
|
@@ -3733,13 +3736,15 @@ if (DD_MAJOR < 6) {
|
|
|
3733
3736
|
}, jestConfigSyncWrapper)
|
|
3734
3737
|
}
|
|
3735
3738
|
|
|
3736
|
-
const
|
|
3739
|
+
const JEST_LOGGING_LIBRARIES = new Set([
|
|
3737
3740
|
'bunyan',
|
|
3738
3741
|
'pino',
|
|
3739
3742
|
'winston',
|
|
3740
3743
|
])
|
|
3744
|
+
const disabledJestInstrumentations = new Set(
|
|
3745
|
+
getValueFromEnvSources('DD_TRACE_DISABLED_INSTRUMENTATIONS')?.split(',')
|
|
3746
|
+
)
|
|
3741
3747
|
const LIBRARIES_BYPASSING_JEST_REQUIRE_ENGINE = new Set([
|
|
3742
|
-
...LOGGING_LIBRARIES_BYPASSING_JEST_REQUIRE_ENGINE,
|
|
3743
3748
|
'selenium-webdriver',
|
|
3744
3749
|
'selenium-webdriver/chrome',
|
|
3745
3750
|
'selenium-webdriver/edge',
|
|
@@ -3884,13 +3889,17 @@ function requireOutsideJestRequireEngine (runtime, moduleName) {
|
|
|
3884
3889
|
* @param {string} moduleName
|
|
3885
3890
|
* @returns {void}
|
|
3886
3891
|
*/
|
|
3887
|
-
function
|
|
3888
|
-
if (
|
|
3892
|
+
function recordJestEsmLoggingModulePath (runtime, from, moduleName) {
|
|
3893
|
+
if (
|
|
3894
|
+
typeof from !== 'string' ||
|
|
3895
|
+
!JEST_LOGGING_LIBRARIES.has(moduleName) ||
|
|
3896
|
+
disabledJestInstrumentations.has(moduleName)
|
|
3897
|
+
) return
|
|
3889
3898
|
|
|
3890
|
-
let pathsByParent =
|
|
3899
|
+
let pathsByParent = jestEsmLoggingModulePathsByRuntime.get(runtime)
|
|
3891
3900
|
if (!pathsByParent) {
|
|
3892
3901
|
pathsByParent = new Map()
|
|
3893
|
-
|
|
3902
|
+
jestEsmLoggingModulePathsByRuntime.set(runtime, pathsByParent)
|
|
3894
3903
|
}
|
|
3895
3904
|
|
|
3896
3905
|
let modulePaths = pathsByParent.get(from)
|
|
@@ -3911,16 +3920,15 @@ function recordJestEsmBypassModulePath (runtime, from, moduleName) {
|
|
|
3911
3920
|
* @param {object} runtime
|
|
3912
3921
|
* @param {string} from
|
|
3913
3922
|
* @param {string} modulePath
|
|
3914
|
-
* @returns {
|
|
3923
|
+
* @returns {string | undefined}
|
|
3915
3924
|
*/
|
|
3916
|
-
function
|
|
3917
|
-
const modulePaths =
|
|
3918
|
-
if (!modulePaths) return
|
|
3925
|
+
function getJestEsmLoggingModuleName (runtime, from, modulePath) {
|
|
3926
|
+
const modulePaths = jestEsmLoggingModulePathsByRuntime.get(runtime)?.get(from)
|
|
3927
|
+
if (!modulePaths) return
|
|
3919
3928
|
|
|
3920
|
-
for (const resolvedPath of modulePaths
|
|
3921
|
-
if (resolvedPath === modulePath) return
|
|
3929
|
+
for (const [moduleName, resolvedPath] of modulePaths) {
|
|
3930
|
+
if (resolvedPath === modulePath) return moduleName
|
|
3922
3931
|
}
|
|
3923
|
-
return false
|
|
3924
3932
|
}
|
|
3925
3933
|
|
|
3926
3934
|
/**
|
|
@@ -3934,7 +3942,7 @@ function wrapJestEsmLoader (runtime) {
|
|
|
3934
3942
|
wrappedJestEsmLoaders.add(esmLoader)
|
|
3935
3943
|
if (typeof esmLoader.resolveModule === 'function') {
|
|
3936
3944
|
shimmer.wrap(esmLoader, 'resolveModule', resolveModule => function (moduleName, from) {
|
|
3937
|
-
|
|
3945
|
+
recordJestEsmLoggingModulePath(runtime, from, moduleName)
|
|
3938
3946
|
return resolveModule.apply(this, arguments)
|
|
3939
3947
|
})
|
|
3940
3948
|
}
|
|
@@ -3943,7 +3951,7 @@ function wrapJestEsmLoader (runtime) {
|
|
|
3943
3951
|
esmLoader,
|
|
3944
3952
|
'resolveSpecifierForSyncGraph',
|
|
3945
3953
|
resolveSpecifier => function (from, moduleName) {
|
|
3946
|
-
|
|
3954
|
+
recordJestEsmLoggingModulePath(runtime, from, moduleName)
|
|
3947
3955
|
return resolveSpecifier.apply(this, arguments)
|
|
3948
3956
|
}
|
|
3949
3957
|
)
|
|
@@ -3960,11 +3968,10 @@ function getJestBypassModulePath (runtime, from, moduleName) {
|
|
|
3960
3968
|
if (typeof from !== 'string' || typeof moduleName !== 'string') return
|
|
3961
3969
|
|
|
3962
3970
|
if (!LIBRARIES_BYPASSING_JEST_REQUIRE_ENGINE.has(moduleName)) {
|
|
3963
|
-
//
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
return
|
|
3971
|
+
// Keep the native fallback for logging packages that cannot be instrumented in Jest's realm, such as a
|
|
3972
|
+
// package-name-preserving symlink whose target is a user wrapper rather than the package itself.
|
|
3973
|
+
if (path.isAbsolute(moduleName) && getJestEsmLoggingModuleName(runtime, from, moduleName)) return moduleName
|
|
3974
|
+
if (!JEST_LOGGING_LIBRARIES.has(moduleName) || disabledJestInstrumentations.has(moduleName)) return
|
|
3968
3975
|
}
|
|
3969
3976
|
|
|
3970
3977
|
try {
|
|
@@ -3983,6 +3990,113 @@ function getJestBypassModulePath (runtime, from, moduleName) {
|
|
|
3983
3990
|
}
|
|
3984
3991
|
}
|
|
3985
3992
|
|
|
3993
|
+
/**
|
|
3994
|
+
* @param {object} runtime
|
|
3995
|
+
* @param {string} from
|
|
3996
|
+
* @param {string} moduleName
|
|
3997
|
+
* @returns {string}
|
|
3998
|
+
*/
|
|
3999
|
+
function resolveJestModulePath (runtime, from, moduleName) {
|
|
4000
|
+
if (path.isAbsolute(moduleName)) return moduleName
|
|
4001
|
+
|
|
4002
|
+
if (typeof runtime._resolveCjsModule === 'function') {
|
|
4003
|
+
return runtime._resolveCjsModule(from, moduleName)
|
|
4004
|
+
} else if (typeof runtime.cjsLoader?.resolution?.resolveCjs === 'function') {
|
|
4005
|
+
return runtime.cjsLoader.resolution.resolveCjs(from, moduleName)
|
|
4006
|
+
}
|
|
4007
|
+
return runtime._resolveModule(from, moduleName)
|
|
4008
|
+
}
|
|
4009
|
+
|
|
4010
|
+
/**
|
|
4011
|
+
* @param {object} runtime
|
|
4012
|
+
* @param {string} from
|
|
4013
|
+
* @param {string} moduleName
|
|
4014
|
+
* @returns {{ name: string, path: string, version: string } | undefined}
|
|
4015
|
+
*/
|
|
4016
|
+
function getJestLoggingPackage (runtime, from, moduleName) {
|
|
4017
|
+
if (typeof from !== 'string' || typeof moduleName !== 'string') return
|
|
4018
|
+
|
|
4019
|
+
const directModuleName = JEST_LOGGING_LIBRARIES.has(moduleName) && !disabledJestInstrumentations.has(moduleName)
|
|
4020
|
+
? moduleName
|
|
4021
|
+
: getJestEsmLoggingModuleName(runtime, from, moduleName)
|
|
4022
|
+
const packages = jestLoggingPackagesByRuntime.get(runtime)
|
|
4023
|
+
let containingPackage
|
|
4024
|
+
if (!directModuleName) {
|
|
4025
|
+
if (!packages) return
|
|
4026
|
+
|
|
4027
|
+
const normalizedFrom = from.replaceAll(path.sep, '/')
|
|
4028
|
+
for (const [packageRoot, loggingPackage] of packages) {
|
|
4029
|
+
if (normalizedFrom.startsWith(`${packageRoot}/`)) {
|
|
4030
|
+
containingPackage = { packageRoot, ...loggingPackage }
|
|
4031
|
+
break
|
|
4032
|
+
}
|
|
4033
|
+
}
|
|
4034
|
+
if (!containingPackage) return
|
|
4035
|
+
}
|
|
4036
|
+
|
|
4037
|
+
try {
|
|
4038
|
+
const modulePath = resolveJestModulePath(runtime, from, moduleName)
|
|
4039
|
+
const normalizedModulePath = modulePath.replaceAll(path.sep, '/')
|
|
4040
|
+
|
|
4041
|
+
if (directModuleName) {
|
|
4042
|
+
if (modulePath !== createRequire(from).resolve(directModuleName)) return
|
|
4043
|
+
|
|
4044
|
+
const nodeModulesPath = `/node_modules/${directModuleName}/`
|
|
4045
|
+
const packagePathIndex = normalizedModulePath.lastIndexOf(nodeModulesPath)
|
|
4046
|
+
if (packagePathIndex === -1) return
|
|
4047
|
+
|
|
4048
|
+
const packageRoot = normalizedModulePath.slice(0, packagePathIndex + nodeModulesPath.length - 1)
|
|
4049
|
+
const { version } = JSON.parse(readFileSync(`${packageRoot}/package.json`, 'utf8'))
|
|
4050
|
+
if (typeof version !== 'string') return
|
|
4051
|
+
|
|
4052
|
+
let runtimePackages = packages
|
|
4053
|
+
if (!runtimePackages) {
|
|
4054
|
+
runtimePackages = new Map()
|
|
4055
|
+
jestLoggingPackagesByRuntime.set(runtime, runtimePackages)
|
|
4056
|
+
}
|
|
4057
|
+
runtimePackages.set(packageRoot, { name: directModuleName, version })
|
|
4058
|
+
return { name: directModuleName, path: directModuleName, version }
|
|
4059
|
+
}
|
|
4060
|
+
|
|
4061
|
+
const { packageRoot, name, version } = containingPackage
|
|
4062
|
+
if (!normalizedModulePath.startsWith(`${packageRoot}/`)) return
|
|
4063
|
+
|
|
4064
|
+
const modulePathWithinPackage = normalizedModulePath.slice(packageRoot.length + 1)
|
|
4065
|
+
return { name, path: `${name}/${modulePathWithinPackage}`, version }
|
|
4066
|
+
} catch {
|
|
4067
|
+
// Let Jest load unresolved, resolver-only, or virtual modules without instrumentation.
|
|
4068
|
+
}
|
|
4069
|
+
}
|
|
4070
|
+
|
|
4071
|
+
/**
|
|
4072
|
+
* @param {unknown} moduleExports
|
|
4073
|
+
* @param {{ name: string, path: string, version: string } | undefined} loggingPackage
|
|
4074
|
+
* @returns {unknown}
|
|
4075
|
+
*/
|
|
4076
|
+
function instrumentJestLoggingModule (moduleExports, loggingPackage) {
|
|
4077
|
+
if (!loggingPackage || (typeof moduleExports !== 'object' && typeof moduleExports !== 'function')) {
|
|
4078
|
+
return moduleExports
|
|
4079
|
+
}
|
|
4080
|
+
if (moduleExports === null) return moduleExports
|
|
4081
|
+
|
|
4082
|
+
const instrumentedModule = instrumentedJestLoggingModules.get(moduleExports)
|
|
4083
|
+
if (instrumentedModule) return instrumentedModule
|
|
4084
|
+
|
|
4085
|
+
const payload = {
|
|
4086
|
+
module: moduleExports,
|
|
4087
|
+
package: loggingPackage.name,
|
|
4088
|
+
path: loggingPackage.path,
|
|
4089
|
+
version: loggingPackage.version,
|
|
4090
|
+
}
|
|
4091
|
+
// Apply the regular instrumentation after Jest has evaluated the module so its built-ins stay in Jest's realm.
|
|
4092
|
+
bundlerLoadCh.publish(payload)
|
|
4093
|
+
instrumentedJestLoggingModules.set(moduleExports, payload.module)
|
|
4094
|
+
if (payload.module && (typeof payload.module === 'object' || typeof payload.module === 'function')) {
|
|
4095
|
+
instrumentedJestLoggingModules.set(payload.module, payload.module)
|
|
4096
|
+
}
|
|
4097
|
+
return payload.module
|
|
4098
|
+
}
|
|
4099
|
+
|
|
3986
4100
|
function formatDefaultStackTrace (error, structuredStackTrace) {
|
|
3987
4101
|
const errorString = Error.prototype.toString.call(error)
|
|
3988
4102
|
if (structuredStackTrace.length === 0) return errorString
|
|
@@ -4009,7 +4123,7 @@ addHook({
|
|
|
4009
4123
|
// Jest 28 through 30.3 keeps ESM dependency resolution on Runtime itself.
|
|
4010
4124
|
if (typeof Runtime.prototype.resolveModule === 'function') {
|
|
4011
4125
|
shimmer.wrap(Runtime.prototype, 'resolveModule', resolveModule => function (moduleName, from) {
|
|
4012
|
-
|
|
4126
|
+
recordJestEsmLoggingModulePath(this, from, moduleName)
|
|
4013
4127
|
return resolveModule.apply(this, arguments)
|
|
4014
4128
|
})
|
|
4015
4129
|
}
|
|
@@ -4024,11 +4138,13 @@ addHook({
|
|
|
4024
4138
|
shimmer.wrap(Runtime.prototype, 'requireModule', requireModule => function (from, moduleName) {
|
|
4025
4139
|
wrapJestGlobalsForRuntime(this)
|
|
4026
4140
|
try {
|
|
4141
|
+
const loggingPackage = getJestLoggingPackage(this, from, moduleName)
|
|
4027
4142
|
// Jest calls requireModule only after deciding that the module should not be mocked.
|
|
4028
|
-
const bypassModulePath = getJestBypassModulePath(this, from, moduleName)
|
|
4029
|
-
|
|
4143
|
+
const bypassModulePath = loggingPackage ? undefined : getJestBypassModulePath(this, from, moduleName)
|
|
4144
|
+
let returnedValue = bypassModulePath
|
|
4030
4145
|
? requireOutsideJestRequireEngine(this, bypassModulePath)
|
|
4031
4146
|
: requireModule.apply(this, arguments)
|
|
4147
|
+
returnedValue = instrumentJestLoggingModule(returnedValue, loggingPackage)
|
|
4032
4148
|
if (moduleName === '@jest/globals') {
|
|
4033
4149
|
wrapConcurrentJestGlobalsForRuntime(this, returnedValue)
|
|
4034
4150
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { AsyncResource } = require('node:async_hooks')
|
|
4
|
+
|
|
3
5
|
const { createCoverageMap } = require('../../../../vendor/dist/istanbul-lib-coverage')
|
|
4
6
|
const satisfies = require('../../../../vendor/dist/semifies')
|
|
5
7
|
const { DD_MAJOR } = require('../../../../version')
|
|
@@ -80,6 +82,8 @@ const runnerTestEndHandlers = new WeakMap()
|
|
|
80
82
|
const runnerFailuresAdjusted = new WeakSet()
|
|
81
83
|
const runnerFrameworkErrors = new WeakMap()
|
|
82
84
|
const runnerStarted = new WeakSet()
|
|
85
|
+
const readyRunners = new WeakSet()
|
|
86
|
+
const pendingRunnerStarts = new WeakMap()
|
|
83
87
|
const runnerRecoveryStates = new WeakMap()
|
|
84
88
|
const runnersWithPendingCoverageReset = new WeakSet()
|
|
85
89
|
const parallelRunners = new WeakSet()
|
|
@@ -1066,6 +1070,38 @@ function getExecutionConfiguration (runner, isParallel, frameworkVersion, onFini
|
|
|
1066
1070
|
runStoresWithCompletion(libraryConfigurationCh, ctx, onReceivedConfiguration)
|
|
1067
1071
|
}
|
|
1068
1072
|
|
|
1073
|
+
/**
|
|
1074
|
+
* @param {import('mocha').Runner} runner
|
|
1075
|
+
* @returns {void}
|
|
1076
|
+
*/
|
|
1077
|
+
function startMochaRunner (runner) {
|
|
1078
|
+
if (readyRunners.has(runner)) {
|
|
1079
|
+
runner.suite.run()
|
|
1080
|
+
} else {
|
|
1081
|
+
// Global setup can finish after configuration. Preserve the configuration
|
|
1082
|
+
// context until Runner#run has installed its delayed-start listener.
|
|
1083
|
+
pendingRunnerStarts.set(runner, AsyncResource.bind(() => runner.suite.run()))
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
/**
|
|
1088
|
+
* @param {import('mocha').Runner['run']} run
|
|
1089
|
+
* @param {import('mocha').Runner} runner
|
|
1090
|
+
* @param {Parameters<import('mocha').Runner['run']>} args
|
|
1091
|
+
* @returns {import('mocha').Runner}
|
|
1092
|
+
*/
|
|
1093
|
+
function runMochaRunner (run, runner, args) {
|
|
1094
|
+
const result = run.apply(runner, args)
|
|
1095
|
+
// Once delay mode is enabled, startup must complete even if the plugin is disabled during global setup.
|
|
1096
|
+
readyRunners.add(runner)
|
|
1097
|
+
const start = pendingRunnerStarts.get(runner)
|
|
1098
|
+
if (start) {
|
|
1099
|
+
pendingRunnerStarts.delete(runner)
|
|
1100
|
+
start()
|
|
1101
|
+
}
|
|
1102
|
+
return result
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1069
1105
|
// In this hook we delay the execution with options.delay to grab library configuration,
|
|
1070
1106
|
// skippable and known tests.
|
|
1071
1107
|
// It is called but skipped in parallel mode.
|
|
@@ -1118,11 +1154,11 @@ function wrapMochaRun (Mocha, frameworkVersion) {
|
|
|
1118
1154
|
getCodeCoverageCh.publish({
|
|
1119
1155
|
onDone: (receivedCodeCoverage) => {
|
|
1120
1156
|
untestedCoverage = receivedCodeCoverage
|
|
1121
|
-
|
|
1157
|
+
startMochaRunner(runner)
|
|
1122
1158
|
},
|
|
1123
1159
|
})
|
|
1124
1160
|
} else {
|
|
1125
|
-
|
|
1161
|
+
startMochaRunner(runner)
|
|
1126
1162
|
}
|
|
1127
1163
|
})
|
|
1128
1164
|
|
|
@@ -1146,7 +1182,7 @@ addHook({
|
|
|
1146
1182
|
const mocha = args[0]
|
|
1147
1183
|
|
|
1148
1184
|
/**
|
|
1149
|
-
* This
|
|
1185
|
+
* This enables the delayed root suite, which we'll release after
|
|
1150
1186
|
* our configuration and skippable suites requests.
|
|
1151
1187
|
* You need this both here and in Mocha#run hook: the programmatic API
|
|
1152
1188
|
* does not call `runMocha`, so it needs to be in Mocha#run. When using
|
|
@@ -1178,7 +1214,7 @@ addHook({
|
|
|
1178
1214
|
|
|
1179
1215
|
shimmer.wrap(Runner.prototype, 'run', run => function (...args) {
|
|
1180
1216
|
if (!testFinishCh.hasSubscribers) {
|
|
1181
|
-
return run
|
|
1217
|
+
return runMochaRunner(run, this, args)
|
|
1182
1218
|
}
|
|
1183
1219
|
|
|
1184
1220
|
const { onRunDone, onFlushDone } = getRunCompletionCallbacks(args[0])
|
|
@@ -1511,7 +1547,7 @@ addHook({
|
|
|
1511
1547
|
}
|
|
1512
1548
|
})
|
|
1513
1549
|
|
|
1514
|
-
return run
|
|
1550
|
+
return runMochaRunner(run, this, args)
|
|
1515
1551
|
})
|
|
1516
1552
|
|
|
1517
1553
|
return Runner
|
|
@@ -4,7 +4,7 @@ const { tracingChannel } = require('dc-polyfill')
|
|
|
4
4
|
const shimmer = require('../../datadog-shimmer')
|
|
5
5
|
const { addHook, channel, getHooks } = require('./helpers/instrument')
|
|
6
6
|
|
|
7
|
-
for (const hook of getHooks('@modelcontextprotocol/sdk')) {
|
|
7
|
+
for (const hook of getHooks('@modelcontextprotocol/sdk').values()) {
|
|
8
8
|
addHook(hook, exports => exports)
|
|
9
9
|
}
|
|
10
10
|
|
|
@@ -24,7 +24,7 @@ function captureChatCompletionsModelBaseURL (ctx) {
|
|
|
24
24
|
|
|
25
25
|
chatCompletionsModelConstructorCh.end.subscribe(captureChatCompletionsModelBaseURL)
|
|
26
26
|
|
|
27
|
-
for (const hook of getHooks('@openai/agents-openai')) {
|
|
27
|
+
for (const hook of getHooks('@openai/agents-openai').values()) {
|
|
28
28
|
addHook(hook, moduleExports => moduleExports)
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -8,46 +8,28 @@ const ch = dc.tracingChannel('apm:openai:request')
|
|
|
8
8
|
const onStreamedChunkCh = dc.channel('apm:openai:request:chunk')
|
|
9
9
|
const responsePromiseContexts = new WeakMap()
|
|
10
10
|
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
const responsesAfterChannel = dc.channel('dd-trace:openai:responses:after')
|
|
18
|
-
|
|
19
|
-
const LIFECYCLE_CHANNELS = {
|
|
20
|
-
'chat.completions': {
|
|
21
|
-
before: chatCompletionsBeforeChannel,
|
|
22
|
-
after: chatCompletionsAfterChannel,
|
|
23
|
-
},
|
|
24
|
-
responses: {
|
|
25
|
-
before: responsesBeforeChannel,
|
|
26
|
-
after: responsesAfterChannel,
|
|
27
|
-
},
|
|
28
|
-
}
|
|
11
|
+
// Published synchronously with the native call data. A subscriber may set either callback and
|
|
12
|
+
// this instrumentation applies it, so the wrapping stays here and only the policy lives outside:
|
|
13
|
+
// beforeResult () => Promise<void>|undefined holds the result back until it settles
|
|
14
|
+
// onResult (body) => unknown|Promise<unknown> inspects or replaces the delivered body
|
|
15
|
+
const chatCompletionsInterceptChannel = dc.channel('dd-trace:openai:chat.completions:intercept')
|
|
16
|
+
const responsesInterceptChannel = dc.channel('dd-trace:openai:responses:intercept')
|
|
29
17
|
|
|
30
18
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* Subscribers push async work into `pending` synchronously during publication and
|
|
34
|
-
* abort `abortController` with an error before the pushed promise resolves to block.
|
|
19
|
+
* Returns `promise`, but not before `settled` settles, so a rejection from an interceptor
|
|
20
|
+
* reaches the caller instead of the SDK's value.
|
|
35
21
|
*
|
|
36
|
-
* @
|
|
37
|
-
* @param {
|
|
38
|
-
* @
|
|
22
|
+
* @template T
|
|
23
|
+
* @param {Promise<T>} promise
|
|
24
|
+
* @param {Promise<void>|undefined} settled
|
|
25
|
+
* @returns {Promise<T>}
|
|
39
26
|
*/
|
|
40
|
-
function
|
|
41
|
-
|
|
42
|
-
const ctx = { ...payload, abortController, pending: [] }
|
|
27
|
+
function heldUntil (promise, settled) {
|
|
28
|
+
if (!settled) return promise
|
|
43
29
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return
|
|
47
|
-
if (abortController.signal.aborted) {
|
|
48
|
-
throw abortController.signal.reason
|
|
49
|
-
}
|
|
50
|
-
})
|
|
30
|
+
// The interceptor's rejection wins over an earlier SDK rejection, so keep that one handled.
|
|
31
|
+
promise.catch(() => {})
|
|
32
|
+
return settled.then(() => promise)
|
|
51
33
|
}
|
|
52
34
|
|
|
53
35
|
const V4_PACKAGE_SHIMS = [
|
|
@@ -57,6 +39,7 @@ const V4_PACKAGE_SHIMS = [
|
|
|
57
39
|
baseResource: 'chat.completions',
|
|
58
40
|
methods: ['create'],
|
|
59
41
|
streamedResponse: true,
|
|
42
|
+
interceptChannel: chatCompletionsInterceptChannel,
|
|
60
43
|
},
|
|
61
44
|
{
|
|
62
45
|
file: 'resources/completions',
|
|
@@ -72,6 +55,7 @@ const V4_PACKAGE_SHIMS = [
|
|
|
72
55
|
methods: ['create'], // implicitly covers `parse` as well which calls `create` under the hood
|
|
73
56
|
streamedResponse: true,
|
|
74
57
|
versions: ['>=4.87.0'],
|
|
58
|
+
interceptChannel: responsesInterceptChannel,
|
|
75
59
|
},
|
|
76
60
|
{
|
|
77
61
|
file: 'resources/embeddings',
|
|
@@ -185,6 +169,7 @@ const V4_PACKAGE_SHIMS = [
|
|
|
185
169
|
methods: ['create'],
|
|
186
170
|
streamedResponse: true,
|
|
187
171
|
versions: ['>=4.85.0'],
|
|
172
|
+
interceptChannel: chatCompletionsInterceptChannel,
|
|
188
173
|
},
|
|
189
174
|
]
|
|
190
175
|
|
|
@@ -287,13 +272,11 @@ for (const extension of extensions) {
|
|
|
287
272
|
return handleUnwrappedAPIPromise(parsedPromise, state)
|
|
288
273
|
})
|
|
289
274
|
|
|
275
|
+
// Raw-response callers bypass `parse`, so run the interceptor here too.
|
|
290
276
|
shimmer.wrap(prototype, 'asResponse', asResponse => function (...args) {
|
|
291
277
|
const state = responsePromiseContexts.get(this.responsePromise)
|
|
292
|
-
const responsePromise = asResponse.apply(this, args)
|
|
293
|
-
|
|
294
|
-
if (!state?.getBeforeVerdict) return responsePromise
|
|
295
278
|
|
|
296
|
-
return
|
|
279
|
+
return heldUntil(asResponse.apply(this, args), state?.interceptCtx?.beforeResult?.())
|
|
297
280
|
})
|
|
298
281
|
|
|
299
282
|
return exports
|
|
@@ -303,7 +286,7 @@ for (const extension of extensions) {
|
|
|
303
286
|
|
|
304
287
|
for (const extension of extensions) {
|
|
305
288
|
for (const shim of V4_PACKAGE_SHIMS) {
|
|
306
|
-
const { file, targetClass, baseResource, methods, versions, streamedResponse } = shim
|
|
289
|
+
const { file, targetClass, baseResource, methods, versions, streamedResponse, interceptChannel } = shim
|
|
307
290
|
addHook({ name: 'openai', file: file + extension, versions: versions || ['>=4'] }, exports => {
|
|
308
291
|
const targetPrototype = exports[targetClass].prototype
|
|
309
292
|
|
|
@@ -314,10 +297,9 @@ for (const extension of extensions) {
|
|
|
314
297
|
// chat.completions and completions
|
|
315
298
|
const stream = streamedResponse && getOption(args, 'stream', false)
|
|
316
299
|
|
|
317
|
-
const
|
|
318
|
-
const hasLifecycle = !!channels && (channels.before.hasSubscribers || channels.after.hasSubscribers)
|
|
300
|
+
const intercepted = !stream && interceptChannel?.hasSubscribers
|
|
319
301
|
|
|
320
|
-
if (!ch.start.hasSubscribers && !
|
|
302
|
+
if (!ch.start.hasSubscribers && !intercepted) {
|
|
321
303
|
return methodFn.apply(this, args)
|
|
322
304
|
}
|
|
323
305
|
|
|
@@ -330,24 +312,21 @@ for (const extension of extensions) {
|
|
|
330
312
|
}
|
|
331
313
|
|
|
332
314
|
return ch.start.runStores(ctx, () => {
|
|
333
|
-
// Capture the parent span explicitly: the _thenUnwrap/parse path decouples
|
|
334
|
-
// the lazy evaluation from the active scope at call time.
|
|
335
|
-
const parentSpan = hasLifecycle ? ctx.currentStore?.span : undefined
|
|
336
|
-
|
|
337
315
|
const apiProm = methodFn.apply(this, args)
|
|
338
316
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
beforeVerdict ??= publishLifecycle(beforeChannel, { args, parentSpan })
|
|
345
|
-
return beforeVerdict
|
|
346
|
-
}
|
|
347
|
-
: null
|
|
317
|
+
let interceptCtx
|
|
318
|
+
if (intercepted) {
|
|
319
|
+
interceptCtx = { arguments: args, tracingContext: ctx }
|
|
320
|
+
interceptChannel.publish(interceptCtx)
|
|
321
|
+
}
|
|
348
322
|
|
|
349
|
-
|
|
350
|
-
|
|
323
|
+
// Keyed by the response promise rather than the APIPromise: `_thenUnwrap` builds a new
|
|
324
|
+
// APIPromise around the same response, which is how `responses.parse` and the
|
|
325
|
+
// pagination page types reach the prototype wrappers above. A method that resolves to
|
|
326
|
+
// a plain value has none, and a WeakMap rejects a non-object key.
|
|
327
|
+
if (apiProm?.responsePromise) {
|
|
328
|
+
responsePromiseContexts.set(apiProm.responsePromise, { ctx, stream, interceptCtx })
|
|
329
|
+
}
|
|
351
330
|
|
|
352
331
|
ch.end.publish(ctx)
|
|
353
332
|
|
|
@@ -361,20 +340,17 @@ for (const extension of extensions) {
|
|
|
361
340
|
}
|
|
362
341
|
|
|
363
342
|
function handleUnwrappedAPIPromise (apiProm, state) {
|
|
364
|
-
const { ctx, stream,
|
|
365
|
-
const gatedApiProm = getBeforeVerdict
|
|
366
|
-
? Promise.all([getBeforeVerdict(), apiProm]).then(([, result]) => result)
|
|
367
|
-
: apiProm
|
|
343
|
+
const { ctx, stream, interceptCtx } = state
|
|
368
344
|
|
|
369
|
-
return
|
|
345
|
+
return heldUntil(apiProm, interceptCtx?.beforeResult?.())
|
|
370
346
|
.then(([{ response, options }, body]) => {
|
|
371
347
|
if (stream) {
|
|
348
|
+
const wrapIterator = wrapStreamIterator(response, options, ctx)
|
|
349
|
+
|
|
372
350
|
if (body.iterator) {
|
|
373
|
-
shimmer.wrap(body, 'iterator',
|
|
351
|
+
shimmer.wrap(body, 'iterator', wrapIterator)
|
|
374
352
|
} else {
|
|
375
|
-
shimmer.wrap(
|
|
376
|
-
body.response.body, Symbol.asyncIterator, wrapStreamIterator(response, options, ctx)
|
|
377
|
-
)
|
|
353
|
+
shimmer.wrap(body.response.body, Symbol.asyncIterator, wrapIterator)
|
|
378
354
|
}
|
|
379
355
|
return body
|
|
380
356
|
}
|
|
@@ -388,28 +364,29 @@ function handleUnwrappedAPIPromise (apiProm, state) {
|
|
|
388
364
|
},
|
|
389
365
|
}
|
|
390
366
|
|
|
391
|
-
if (!
|
|
367
|
+
if (!interceptCtx?.onResult) {
|
|
392
368
|
finish(ctx, responseData)
|
|
393
369
|
return body
|
|
394
370
|
}
|
|
395
371
|
|
|
396
|
-
// Finish after
|
|
397
|
-
//
|
|
398
|
-
return
|
|
372
|
+
// Finish after the callback settles so a rejection propagates to openai.request and the
|
|
373
|
+
// span wraps its child instead of closing before it.
|
|
374
|
+
return Promise.resolve(interceptCtx.onResult(body)).then(deliveredBody => {
|
|
399
375
|
finish(ctx, responseData)
|
|
400
|
-
return
|
|
376
|
+
return deliveredBody
|
|
401
377
|
})
|
|
402
378
|
})
|
|
403
379
|
.catch(error => {
|
|
404
|
-
|
|
405
|
-
// branch, Before Model block, After Model block, or openai error) — record the error now so
|
|
406
|
-
// the openai.request span is marked errored. If finish already ran, don't double-publish.
|
|
407
|
-
if (!ctx.result) finish(ctx, undefined, error)
|
|
380
|
+
finish(ctx, undefined, error)
|
|
408
381
|
throw error
|
|
409
382
|
})
|
|
410
383
|
}
|
|
411
384
|
|
|
412
385
|
function finish (ctx, response, error) {
|
|
386
|
+
// `parse` is re-entered on every await of the same APIPromise, and a stream may be iterated
|
|
387
|
+
// more than once, so the span must only close once.
|
|
388
|
+
if (ctx.finished) return
|
|
389
|
+
|
|
413
390
|
if (error) {
|
|
414
391
|
ctx.error = error
|
|
415
392
|
ch.error.publish(ctx)
|
|
@@ -419,6 +396,7 @@ function finish (ctx, response, error) {
|
|
|
419
396
|
// so we don't want to override it here
|
|
420
397
|
ctx.result ??= {}
|
|
421
398
|
Object.assign(ctx.result, response)
|
|
399
|
+
ctx.finished = true
|
|
422
400
|
|
|
423
401
|
ch.asyncEnd.publish(ctx)
|
|
424
402
|
}
|
|
@@ -81,6 +81,7 @@ const testsToTestStatuses = new Map()
|
|
|
81
81
|
const activeRumPages = new Set()
|
|
82
82
|
|
|
83
83
|
const RUM_FLUSH_WAIT_TIME = getValueFromEnvSources('DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS')
|
|
84
|
+
const isPlaywrightWorker = getValueFromEnvSources('DD_PLAYWRIGHT_WORKER') === '1'
|
|
84
85
|
const DD_PROPERTIES_TIMEOUT = 5000
|
|
85
86
|
const isFailureScreenshotUploadEnabled =
|
|
86
87
|
getValueFromEnvSources('DD_TEST_FAILURE_SCREENSHOTS_ENABLED') === true
|
|
@@ -1712,6 +1713,9 @@ createRootSuiteCh.subscribe({
|
|
|
1712
1713
|
|
|
1713
1714
|
pageGotoCh.subscribe({
|
|
1714
1715
|
asyncEnd (ctx) {
|
|
1716
|
+
// Playwright library consumers such as Vitest have no Playwright test span and may navigate during page startup.
|
|
1717
|
+
if (!isPlaywrightWorker) return
|
|
1718
|
+
|
|
1715
1719
|
// The Page.goto rewriter waits for this so tests closing immediately after navigation still get RUM tags.
|
|
1716
1720
|
const rumDetectionPromise = handlePageGoto(ctx.self)
|
|
1717
1721
|
ctx.resolveCallback = onDone => rumDetectionPromise.then(onDone, onDone)
|