Comparing version 6.0.0-pre-31c56e7 to 6.0.0-pre-332b216
@@ -8,4 +8,5 @@ import * as opentracing from 'opentracing' | ||
LOG: 'log' | ||
TEXT_MAP_DSM: 'text_map_dsm' | ||
} | ||
export = formats |
@@ -7,3 +7,4 @@ 'use strict' | ||
BINARY: 'binary', | ||
LOG: 'log' | ||
LOG: 'log', | ||
TEXT_MAP_DSM: 'text_map_dsm' | ||
} |
18
init.js
@@ -5,19 +5,7 @@ 'use strict' | ||
const Module = require('module') | ||
const semver = require('semver') | ||
const log = require('./packages/dd-trace/src/log') | ||
const { isTrue } = require('./packages/dd-trace/src/util') | ||
const telemetry = require('./packages/dd-trace/src/telemetry/init-telemetry') | ||
const semver = require('semver') | ||
function isTrue (envVar) { | ||
return ['1', 'true', 'True'].includes(envVar) | ||
} | ||
// eslint-disable-next-line no-console | ||
let log = { info: isTrue(process.env.DD_TRACE_DEBUG) ? console.log : () => {} } | ||
if (semver.satisfies(process.versions.node, '>=16')) { | ||
const Config = require('./packages/dd-trace/src/config') | ||
log = require('./packages/dd-trace/src/log') | ||
// eslint-disable-next-line no-new | ||
new Config() // we need this to initialize the logger | ||
} | ||
let initBailout = false | ||
@@ -24,0 +12,0 @@ let clobberBailout = false |
{ | ||
"name": "dd-trace", | ||
"version": "6.0.0-pre-31c56e7", | ||
"version": "6.0.0-pre-332b216", | ||
"description": "Datadog APM tracing client for JavaScript", | ||
@@ -17,16 +17,16 @@ "main": "index.js", | ||
"services": "node ./scripts/install_plugin_modules && node packages/dd-trace/test/setup/services", | ||
"test": "SERVICES=* yarn services && mocha --colors --exit --expose-gc 'packages/dd-trace/test/setup/node.js' 'packages/*/test/**/*.spec.js'", | ||
"test:appsec": "mocha --colors --exit -r \"packages/dd-trace/test/setup/mocha.js\" --exclude \"packages/dd-trace/test/appsec/**/*.plugin.spec.js\" \"packages/dd-trace/test/appsec/**/*.spec.js\"", | ||
"test": "SERVICES=* yarn services && mocha --expose-gc 'packages/dd-trace/test/setup/node.js' 'packages/*/test/**/*.spec.js'", | ||
"test:appsec": "mocha -r \"packages/dd-trace/test/setup/mocha.js\" --exclude \"packages/dd-trace/test/appsec/**/*.plugin.spec.js\" \"packages/dd-trace/test/appsec/**/*.spec.js\"", | ||
"test:appsec:ci": "nyc --no-clean --include \"packages/dd-trace/src/appsec/**/*.js\" --exclude \"packages/dd-trace/test/appsec/**/*.plugin.spec.js\" -- npm run test:appsec", | ||
"test:appsec:plugins": "mocha --colors --exit -r \"packages/dd-trace/test/setup/mocha.js\" \"packages/dd-trace/test/appsec/**/*.@($(echo $PLUGINS)).plugin.spec.js\"", | ||
"test:appsec:plugins": "mocha -r \"packages/dd-trace/test/setup/mocha.js\" \"packages/dd-trace/test/appsec/**/*.@($(echo $PLUGINS)).plugin.spec.js\"", | ||
"test:appsec:plugins:ci": "yarn services && nyc --no-clean --include \"packages/dd-trace/src/appsec/**/*.js\" -- npm run test:appsec:plugins", | ||
"test:trace:core": "tap packages/dd-trace/test/*.spec.js \"packages/dd-trace/test/{ci-visibility,datastreams,encode,exporters,opentelemetry,opentracing,plugins,service-naming,telemetry}/**/*.spec.js\"", | ||
"test:trace:core:ci": "npm run test:trace:core -- --coverage --nyc-arg=--include=\"packages/dd-trace/src/**/*.js\"", | ||
"test:instrumentations": "mocha --colors -r 'packages/dd-trace/test/setup/mocha.js' 'packages/datadog-instrumentations/test/**/*.spec.js'", | ||
"test:instrumentations": "mocha -r 'packages/dd-trace/test/setup/mocha.js' 'packages/datadog-instrumentations/test/**/*.spec.js'", | ||
"test:instrumentations:ci": "nyc --no-clean --include 'packages/datadog-instrumentations/src/**/*.js' -- npm run test:instrumentations", | ||
"test:core": "tap \"packages/datadog-core/test/**/*.spec.js\"", | ||
"test:core:ci": "npm run test:core -- --coverage --nyc-arg=--include=\"packages/datadog-core/src/**/*.js\"", | ||
"test:lambda": "mocha --colors --exit -r \"packages/dd-trace/test/setup/mocha.js\" \"packages/dd-trace/test/lambda/**/*.spec.js\"", | ||
"test:lambda": "mocha -r \"packages/dd-trace/test/setup/mocha.js\" \"packages/dd-trace/test/lambda/**/*.spec.js\"", | ||
"test:lambda:ci": "nyc --no-clean --include \"packages/dd-trace/src/lambda/**/*.js\" -- npm run test:lambda", | ||
"test:plugins": "mocha --colors --exit -r \"packages/dd-trace/test/setup/mocha.js\" \"packages/datadog-instrumentations/test/@($(echo $PLUGINS)).spec.js\" \"packages/datadog-plugin-@($(echo $PLUGINS))/test/**/*.spec.js\"", | ||
"test:plugins": "mocha -r \"packages/dd-trace/test/setup/mocha.js\" \"packages/datadog-instrumentations/test/@($(echo $PLUGINS)).spec.js\" \"packages/datadog-plugin-@($(echo $PLUGINS))/test/**/*.spec.js\"", | ||
"test:plugins:ci": "yarn services && nyc --no-clean --include \"packages/datadog-instrumentations/src/@($(echo $PLUGINS)).js\" --include \"packages/datadog-instrumentations/src/@($(echo $PLUGINS))/**/*.js\" --include \"packages/datadog-plugin-@($(echo $PLUGINS))/src/**/*.js\" -- npm run test:plugins", | ||
@@ -36,18 +36,17 @@ "test:plugins:upstream": "node ./packages/dd-trace/test/plugins/suite.js", | ||
"test:profiler:ci": "npm run test:profiler -- --coverage --nyc-arg=--include=\"packages/dd-trace/src/profiling/**/*.js\"", | ||
"test:integration": "mocha --colors --timeout 30000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/*.spec.js\"", | ||
"test:integration:cucumber": "mocha --colors --timeout 30000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/cucumber/*.spec.js\"", | ||
"test:integration:cypress": "mocha --colors --timeout 30000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/cypress/*.spec.js\"", | ||
"test:integration:jest": "mocha --colors --timeout 30000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/jest/*.spec.js\"", | ||
"test:integration:mocha": "mocha --colors --timeout 30000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/mocha/*.spec.js\"", | ||
"test:integration:playwright": "mocha --colors --timeout 30000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/playwright/*.spec.js\"", | ||
"test:integration:selenium": "mocha --colors --timeout 30000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/selenium/*.spec.js\"", | ||
"test:integration:vitest": "mocha --colors --timeout 30000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/vitest/*.spec.js\"", | ||
"test:integration:profiler": "mocha --colors --timeout 180000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/profiler/*.spec.js\"", | ||
"test:integration:serverless": "mocha --colors --timeout 30000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/serverless/*.spec.js\"", | ||
"test:integration:plugins": "mocha --colors --exit -r \"packages/dd-trace/test/setup/mocha.js\" \"packages/datadog-plugin-@($(echo $PLUGINS))/test/integration-test/**/*.spec.js\"", | ||
"test:unit:plugins": "mocha --colors --exit -r \"packages/dd-trace/test/setup/mocha.js\" \"packages/datadog-instrumentations/test/@($(echo $PLUGINS)).spec.js\" \"packages/datadog-plugin-@($(echo $PLUGINS))/test/**/*.spec.js\" --exclude \"packages/datadog-plugin-@($(echo $PLUGINS))/test/integration-test/**/*.spec.js\"", | ||
"test:shimmer": "mocha --colors 'packages/datadog-shimmer/test/**/*.spec.js'", | ||
"test:shimmer:ci": "nyc --no-clean --include 'packages/datadog-shimmer/src/**/*.js' -- npm run test:shimmer", | ||
"leak:core": "node ./scripts/install_plugin_modules && (cd packages/memwatch && yarn) && NODE_PATH=./packages/memwatch/node_modules node --no-warnings ./node_modules/.bin/tape 'packages/dd-trace/test/leak/**/*.js'", | ||
"leak:plugins": "yarn services && (cd packages/memwatch && yarn) && NODE_PATH=./packages/memwatch/node_modules node --no-warnings ./node_modules/.bin/tape \"packages/datadog-plugin-@($(echo $PLUGINS))/test/leak.js\"" | ||
"test:integration": "mocha --timeout 60000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/*.spec.js\"", | ||
"test:integration:appsec": "mocha --timeout 60000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/appsec/*.spec.js\"", | ||
"test:integration:cucumber": "mocha --timeout 60000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/cucumber/*.spec.js\"", | ||
"test:integration:cypress": "mocha --timeout 60000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/cypress/*.spec.js\"", | ||
"test:integration:jest": "mocha --timeout 60000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/jest/*.spec.js\"", | ||
"test:integration:mocha": "mocha --timeout 60000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/mocha/*.spec.js\"", | ||
"test:integration:playwright": "mocha --timeout 60000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/playwright/*.spec.js\"", | ||
"test:integration:selenium": "mocha --timeout 60000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/selenium/*.spec.js\"", | ||
"test:integration:vitest": "mocha --timeout 60000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/vitest/*.spec.js\"", | ||
"test:integration:profiler": "mocha --timeout 180000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/profiler/*.spec.js\"", | ||
"test:integration:serverless": "mocha --timeout 60000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/serverless/*.spec.js\"", | ||
"test:integration:plugins": "mocha -r \"packages/dd-trace/test/setup/mocha.js\" \"packages/datadog-plugin-@($(echo $PLUGINS))/test/integration-test/**/*.spec.js\"", | ||
"test:unit:plugins": "mocha -r \"packages/dd-trace/test/setup/mocha.js\" \"packages/datadog-instrumentations/test/@($(echo $PLUGINS)).spec.js\" \"packages/datadog-plugin-@($(echo $PLUGINS))/test/**/*.spec.js\" --exclude \"packages/datadog-plugin-@($(echo $PLUGINS))/test/integration-test/**/*.spec.js\"", | ||
"test:shimmer": "mocha 'packages/datadog-shimmer/test/**/*.spec.js'", | ||
"test:shimmer:ci": "nyc --no-clean --include 'packages/datadog-shimmer/src/**/*.js' -- npm run test:shimmer" | ||
}, | ||
@@ -79,4 +78,4 @@ "repository": { | ||
"@datadog/native-appsec": "8.0.1", | ||
"@datadog/native-iast-rewriter": "2.3.1", | ||
"@datadog/native-iast-taint-tracking": "3.0.0", | ||
"@datadog/native-iast-rewriter": "2.4.0", | ||
"@datadog/native-iast-taint-tracking": "3.1.0", | ||
"@datadog/native-metrics": "^2.0.0", | ||
@@ -110,3 +109,3 @@ "@datadog/pprof": "5.3.0", | ||
"devDependencies": { | ||
"@types/node": ">=18", | ||
"@types/node": "^16.18.103", | ||
"autocannon": "^4.5.2", | ||
@@ -123,9 +122,8 @@ "aws-sdk": "^2.1446.0", | ||
"esbuild": "0.16.12", | ||
"eslint": "^8.23.0", | ||
"eslint": "^8.57.0", | ||
"eslint-config-standard": "^17.1.0", | ||
"eslint-plugin-import": "^2.8.0", | ||
"eslint-plugin-mocha": "^10.1.0", | ||
"eslint-plugin-n": "^15.7.0", | ||
"eslint-plugin-promise": "^3.6.0", | ||
"eslint-plugin-standard": "^3.0.1", | ||
"eslint-plugin-import": "^2.29.1", | ||
"eslint-plugin-mocha": "^10.4.3", | ||
"eslint-plugin-n": "^16.6.2", | ||
"eslint-plugin-promise": "^6.4.0", | ||
"express": "^4.18.2", | ||
@@ -147,5 +145,4 @@ "get-port": "^3.2.0", | ||
"tap": "^16.3.7", | ||
"tape": "^5.6.5", | ||
"tiktoken": "^1.0.15" | ||
} | ||
} |
@@ -42,6 +42,6 @@ 'use strict' | ||
childProcessInfo.command = childProcessInfo.command + ' ' + args[1].join(' ') | ||
if (args[2] != null && typeof args[2] === 'object') { | ||
if (args[2] !== null && typeof args[2] === 'object') { | ||
childProcessInfo.options = args[2] | ||
} | ||
} else if (args[1] != null && typeof args[1] === 'object') { | ||
} else if (args[1] !== null && typeof args[1] === 'object') { | ||
childProcessInfo.options = args[1] | ||
@@ -48,0 +48,0 @@ } |
'use strict' | ||
const { createCoverageMap } = require('istanbul-lib-coverage') | ||
const { NUM_FAILED_TEST_RETRIES } = require('../../dd-trace/src/plugins/util/test') | ||
const { addHook, channel, AsyncResource } = require('./helpers/instrument') | ||
@@ -9,2 +10,3 @@ const shimmer = require('../../datadog-shimmer') | ||
const testStartCh = channel('ci:cucumber:test:start') | ||
const testRetryCh = channel('ci:cucumber:test:retry') | ||
const testFinishCh = channel('ci:cucumber:test:finish') // used for test steps too | ||
@@ -51,2 +53,3 @@ | ||
const numRetriesByPickleId = new Map() | ||
const numAttemptToAsyncResource = new Map() | ||
@@ -65,2 +68,3 @@ let pickleByFile = {} | ||
let earlyFlakeDetectionNumRetries = 0 | ||
let isFlakyTestRetriesEnabled = false | ||
let knownTests = [] | ||
@@ -168,43 +172,76 @@ let skippedSuites = [] | ||
let numAttempt = 0 | ||
const asyncResource = new AsyncResource('bound-anonymous-fn') | ||
return asyncResource.runInAsyncScope(() => { | ||
const testFileAbsolutePath = this.pickle.uri | ||
const testSourceLine = this.gherkinDocument?.feature?.location?.line | ||
numAttemptToAsyncResource.set(numAttempt, asyncResource) | ||
testStartCh.publish({ | ||
testName: this.pickle.name, | ||
testFileAbsolutePath, | ||
testSourceLine, | ||
isParallel: !!process.env.CUCUMBER_WORKER_ID | ||
}) | ||
try { | ||
const promise = run.apply(this, arguments) | ||
promise.finally(() => { | ||
const result = this.getWorstStepResult() | ||
const { status, skipReason, errorMessage } = isLatestVersion | ||
? getStatusFromResultLatest(result) | ||
: getStatusFromResult(result) | ||
const testFileAbsolutePath = this.pickle.uri | ||
if (lastStatusByPickleId.has(this.pickle.id)) { | ||
lastStatusByPickleId.get(this.pickle.id).push(status) | ||
} else { | ||
lastStatusByPickleId.set(this.pickle.id, [status]) | ||
const testSourceLine = this.gherkinDocument?.feature?.location?.line | ||
const testStartPayload = { | ||
testName: this.pickle.name, | ||
testFileAbsolutePath, | ||
testSourceLine, | ||
isParallel: !!process.env.CUCUMBER_WORKER_ID | ||
} | ||
asyncResource.runInAsyncScope(() => { | ||
testStartCh.publish(testStartPayload) | ||
}) | ||
try { | ||
this.eventBroadcaster.on('envelope', (testCase) => { | ||
// Only supported from >=8.0.0 | ||
if (testCase?.testCaseFinished) { | ||
const { testCaseFinished: { willBeRetried } } = testCase | ||
if (willBeRetried) { // test case failed and will be retried | ||
const failedAttemptAsyncResource = numAttemptToAsyncResource.get(numAttempt) | ||
failedAttemptAsyncResource.runInAsyncScope(() => { | ||
testRetryCh.publish(numAttempt++ > 0) // the current span will be finished and a new one will be created | ||
}) | ||
const newAsyncResource = new AsyncResource('bound-anonymous-fn') | ||
numAttemptToAsyncResource.set(numAttempt, newAsyncResource) | ||
newAsyncResource.runInAsyncScope(() => { | ||
testStartCh.publish(testStartPayload) // a new span will be created | ||
}) | ||
} | ||
let isNew = false | ||
let isEfdRetry = false | ||
if (isEarlyFlakeDetectionEnabled && status !== 'skip') { | ||
const numRetries = numRetriesByPickleId.get(this.pickle.id) | ||
} | ||
}) | ||
let promise | ||
isNew = numRetries !== undefined | ||
isEfdRetry = numRetries > 0 | ||
} | ||
testFinishCh.publish({ status, skipReason, errorMessage, isNew, isEfdRetry }) | ||
asyncResource.runInAsyncScope(() => { | ||
promise = run.apply(this, arguments) | ||
}) | ||
promise.finally(() => { | ||
const result = this.getWorstStepResult() | ||
const { status, skipReason, errorMessage } = isLatestVersion | ||
? getStatusFromResultLatest(result) | ||
: getStatusFromResult(result) | ||
if (lastStatusByPickleId.has(this.pickle.id)) { | ||
lastStatusByPickleId.get(this.pickle.id).push(status) | ||
} else { | ||
lastStatusByPickleId.set(this.pickle.id, [status]) | ||
} | ||
let isNew = false | ||
let isEfdRetry = false | ||
if (isEarlyFlakeDetectionEnabled && status !== 'skip') { | ||
const numRetries = numRetriesByPickleId.get(this.pickle.id) | ||
isNew = numRetries !== undefined | ||
isEfdRetry = numRetries > 0 | ||
} | ||
const attemptAsyncResource = numAttemptToAsyncResource.get(numAttempt) | ||
attemptAsyncResource.runInAsyncScope(() => { | ||
testFinishCh.publish({ status, skipReason, errorMessage, isNew, isEfdRetry, isFlakyRetry: numAttempt > 0 }) | ||
}) | ||
return promise | ||
} catch (err) { | ||
errorCh.publish(err) | ||
throw err | ||
} | ||
}) | ||
}) | ||
return promise | ||
} catch (err) { | ||
errorCh.publish(err) | ||
throw err | ||
} | ||
}) | ||
@@ -274,2 +311,3 @@ shimmer.wrap(pl.prototype, 'runStep', runStep => function () { | ||
isSuitesSkippingEnabled = configurationResponse.libraryConfig?.isSuitesSkippingEnabled | ||
isFlakyTestRetriesEnabled = configurationResponse.libraryConfig?.isFlakyTestRetriesEnabled | ||
@@ -312,2 +350,6 @@ if (isEarlyFlakeDetectionEnabled) { | ||
if (isFlakyTestRetriesEnabled && !this.options.retry) { | ||
this.options.retry = NUM_FAILED_TEST_RETRIES | ||
} | ||
sessionAsyncResource.runInAsyncScope(() => { | ||
@@ -314,0 +356,0 @@ sessionStartCh.publish({ command, frameworkVersion }) |
@@ -275,3 +275,3 @@ 'use strict' | ||
arguments[lastIndex] = innerResource.bind(function (e) { | ||
if (typeof e === 'object') { // fs.exists receives a boolean | ||
if (e !== null && typeof e === 'object') { // fs.exists receives a boolean | ||
errorChannel.publish(e) | ||
@@ -278,0 +278,0 @@ } |
@@ -41,3 +41,3 @@ 'use strict' | ||
return function (events, method, options) { | ||
if (typeof events === 'object') { | ||
if (events !== null && typeof events === 'object') { | ||
arguments[0] = wrapEvents(events) | ||
@@ -44,0 +44,0 @@ } else { |
@@ -32,2 +32,6 @@ 'use strict' | ||
if (!disabledInstrumentations.has('process')) { | ||
require('../process') | ||
} | ||
const HOOK_SYMBOL = Symbol('hookExportsMap') | ||
@@ -92,6 +96,3 @@ | ||
if (!Object.hasOwnProperty(namesAndSuccesses, name)) { | ||
namesAndSuccesses[name] = { | ||
success: false, | ||
version | ||
} | ||
namesAndSuccesses[`${name}@${version}`] = false | ||
} | ||
@@ -102,3 +103,3 @@ | ||
if (hook[HOOK_SYMBOL].has(moduleExports)) { | ||
namesAndSuccesses[name].success = true | ||
namesAndSuccesses[`${name}@${version}`] = true | ||
return moduleExports | ||
@@ -123,9 +124,10 @@ } | ||
} | ||
namesAndSuccesses[name].success = true | ||
namesAndSuccesses[`${name}@${version}`] = true | ||
} | ||
} | ||
} | ||
for (const name of Object.keys(namesAndSuccesses)) { | ||
const { success, version } = namesAndSuccesses[name] | ||
if (!success && !seenCombo.has(`${name}@${version}`)) { | ||
for (const nameVersion of Object.keys(namesAndSuccesses)) { | ||
const [name, version] = nameVersion.split('@') | ||
const success = namesAndSuccesses[nameVersion] | ||
if (!success && !seenCombo.has(nameVersion)) { | ||
telemetry('abort.integration', [ | ||
@@ -135,4 +137,4 @@ `integration:${name}`, | ||
]) | ||
log.info(`Found incompatible integration version: ${name}@${version}`) | ||
seenCombo.add(`${name}@${version}`) | ||
log.info(`Found incompatible integration version: ${nameVersion}`) | ||
seenCombo.add(nameVersion) | ||
} | ||
@@ -139,0 +141,0 @@ } |
@@ -46,4 +46,6 @@ 'use strict' | ||
const ctx = { args, http } | ||
const abortController = new AbortController() | ||
const ctx = { args, http, abortController } | ||
return startChannel.runStores(ctx, () => { | ||
@@ -111,2 +113,6 @@ let finished = false | ||
if (abortController.signal.aborted) { | ||
req.destroy(abortController.signal.reason || new Error('Aborted')) | ||
} | ||
return req | ||
@@ -137,3 +143,3 @@ } catch (e) { | ||
function combineOptions (inputURL, inputOptions) { | ||
if (typeof inputOptions === 'object') { | ||
if (inputOptions !== null && typeof inputOptions === 'object') { | ||
return Object.assign(inputURL || {}, inputOptions) | ||
@@ -140,0 +146,0 @@ } else { |
@@ -15,2 +15,3 @@ 'use strict' | ||
const finishSetHeaderCh = channel('datadog:http:server:response:set-header:finish') | ||
const startSetHeaderCh = channel('datadog:http:server:response:set-header:start') | ||
@@ -28,2 +29,8 @@ const requestFinishedSet = new WeakSet() | ||
shimmer.wrap(http.ServerResponse.prototype, 'end', wrapEnd) | ||
shimmer.wrap(http.ServerResponse.prototype, 'setHeader', wrapSetHeader) | ||
shimmer.wrap(http.ServerResponse.prototype, 'removeHeader', wrapAppendOrRemoveHeader) | ||
// Added in node v16.17.0 | ||
if (http.ServerResponse.prototype.appendHeader) { | ||
shimmer.wrap(http.ServerResponse.prototype, 'appendHeader', wrapAppendOrRemoveHeader) | ||
} | ||
return http | ||
@@ -70,5 +77,3 @@ }) | ||
} | ||
if (finishSetHeaderCh.hasSubscribers) { | ||
wrapSetHeader(res) | ||
} | ||
return emit.apply(this, arguments) | ||
@@ -87,12 +92,2 @@ } catch (err) { | ||
function wrapSetHeader (res) { | ||
shimmer.wrap(res, 'setHeader', setHeader => { | ||
return function (name, value) { | ||
const setHeaderResult = setHeader.apply(this, arguments) | ||
finishSetHeaderCh.publish({ name, value, res }) | ||
return setHeaderResult | ||
} | ||
}) | ||
} | ||
function wrapWriteHead (writeHead) { | ||
@@ -166,2 +161,44 @@ return function wrappedWriteHead (statusCode, reason, obj) { | ||
function wrapSetHeader (setHeader) { | ||
return function wrappedSetHeader (name, value) { | ||
if (!startSetHeaderCh.hasSubscribers && !finishSetHeaderCh.hasSubscribers) { | ||
return setHeader.apply(this, arguments) | ||
} | ||
if (startSetHeaderCh.hasSubscribers) { | ||
const abortController = new AbortController() | ||
startSetHeaderCh.publish({ res: this, abortController }) | ||
if (abortController.signal.aborted) { | ||
return | ||
} | ||
} | ||
const setHeaderResult = setHeader.apply(this, arguments) | ||
if (finishSetHeaderCh.hasSubscribers) { | ||
finishSetHeaderCh.publish({ name, value, res: this }) | ||
} | ||
return setHeaderResult | ||
} | ||
} | ||
function wrapAppendOrRemoveHeader (originalMethod) { | ||
return function wrappedAppendOrRemoveHeader () { | ||
if (!startSetHeaderCh.hasSubscribers) { | ||
return originalMethod.apply(this, arguments) | ||
} | ||
const abortController = new AbortController() | ||
startSetHeaderCh.publish({ res: this, abortController }) | ||
if (abortController.signal.aborted) { | ||
return this | ||
} | ||
return originalMethod.apply(this, arguments) | ||
} | ||
} | ||
function wrapEnd (end) { | ||
@@ -168,0 +205,0 @@ return function wrappedEnd () { |
@@ -15,3 +15,4 @@ 'use strict' | ||
removeEfdStringFromTestName, | ||
getIsFaultyEarlyFlakeDetection | ||
getIsFaultyEarlyFlakeDetection, | ||
NUM_FAILED_TEST_RETRIES | ||
} = require('../../dd-trace/src/plugins/util/test') | ||
@@ -53,2 +54,5 @@ const { | ||
const FLUSH_TIMEOUT = 10000 | ||
// eslint-disable-next-line | ||
// https://github.com/jestjs/jest/blob/41f842a46bb2691f828c3a5f27fc1d6290495b82/packages/jest-circus/src/types.ts#L9C8-L9C54 | ||
const RETRY_TIMES = Symbol.for('RETRY_TIMES') | ||
@@ -132,2 +136,3 @@ let skippableSuites = [] | ||
this.isEarlyFlakeDetectionEnabled = this.testEnvironmentOptions._ddIsEarlyFlakeDetectionEnabled | ||
this.isFlakyTestRetriesEnabled = this.testEnvironmentOptions._ddIsFlakyTestRetriesEnabled | ||
@@ -146,2 +151,9 @@ if (this.isEarlyFlakeDetectionEnabled) { | ||
} | ||
if (this.isFlakyTestRetriesEnabled) { | ||
const currentNumRetries = this.global[RETRY_TIMES] | ||
if (!currentNumRetries) { | ||
this.global[RETRY_TIMES] = NUM_FAILED_TEST_RETRIES | ||
} | ||
} | ||
} | ||
@@ -225,2 +237,3 @@ | ||
} | ||
const isJestRetry = event.test?.invocations > 1 | ||
asyncResource.runInAsyncScope(() => { | ||
@@ -236,3 +249,4 @@ testStartCh.publish({ | ||
isNew: isNewTest, | ||
isEfdRetry: numEfdRetry > 0 | ||
isEfdRetry: numEfdRetry > 0, | ||
isJestRetry | ||
}) | ||
@@ -767,2 +781,3 @@ originalTestFns.set(event.test, event.test.fn) | ||
_ddRepositoryRoot, | ||
_ddIsFlakyTestRetriesEnabled, | ||
...restOfTestEnvironmentOptions | ||
@@ -769,0 +784,0 @@ } = testEnvironmentOptions |
@@ -62,3 +62,3 @@ 'use strict' | ||
for (const message of messages) { | ||
if (typeof message === 'object') { | ||
if (message !== null && typeof message === 'object') { | ||
message.headers = message.headers || {} | ||
@@ -65,0 +65,0 @@ } |
@@ -64,3 +64,3 @@ 'use strict' | ||
filter = options | ||
} else if (typeof options === 'object' && options.filter) { | ||
} else if (options !== null && typeof options === 'object' && options.filter) { | ||
if (isString(options.filter)) { | ||
@@ -82,3 +82,3 @@ filter = options.filter | ||
arguments[callbackIndex] = shimmer.wrap(callback, function (err, corkedEmitter) { | ||
if (typeof corkedEmitter === 'object' && typeof corkedEmitter.on === 'function') { | ||
if (corkedEmitter !== null && typeof corkedEmitter === 'object' && typeof corkedEmitter.on === 'function') { | ||
wrapEmitter(corkedEmitter) | ||
@@ -85,0 +85,0 @@ } |
@@ -398,5 +398,9 @@ 'use strict' | ||
const asyncResource = testFileToSuiteAr.get(suite.file) | ||
asyncResource.runInAsyncScope(() => { | ||
testSuiteFinishCh.publish(status) | ||
}) | ||
if (asyncResource) { | ||
asyncResource.runInAsyncScope(() => { | ||
testSuiteFinishCh.publish(status) | ||
}) | ||
} else { | ||
log.warn(() => `No AsyncResource found for suite ${suite.file}`) | ||
} | ||
}) | ||
@@ -428,12 +432,17 @@ | ||
}, (workerHandlerPackage) => { | ||
shimmer.wrap(workerHandlerPackage.prototype, 'exec', exec => function (message, [testSuiteAbsolutePath]) { | ||
shimmer.wrap(workerHandlerPackage.prototype, 'exec', exec => function (_, path) { | ||
if (!testStartCh.hasSubscribers) { | ||
return exec.apply(this, arguments) | ||
} | ||
if (!path?.length) { | ||
return exec.apply(this, arguments) | ||
} | ||
const [testSuiteAbsolutePath] = path | ||
const testSuiteAsyncResource = new AsyncResource('bound-anonymous-fn') | ||
this.worker.on('message', function (message) { | ||
function onMessage (message) { | ||
if (Array.isArray(message)) { | ||
const [messageCode, payload] = message | ||
if (messageCode === MOCHA_WORKER_TRACE_PAYLOAD_CODE) { | ||
testSessionAsyncResource.runInAsyncScope(() => { | ||
testSuiteAsyncResource.runInAsyncScope(() => { | ||
workerReportTraceCh.publish(payload) | ||
@@ -443,5 +452,6 @@ }) | ||
} | ||
}) | ||
} | ||
const testSuiteAsyncResource = new AsyncResource('bound-anonymous-fn') | ||
this.worker.on('message', onMessage) | ||
testSuiteAsyncResource.runInAsyncScope(() => { | ||
@@ -461,2 +471,3 @@ testSuiteStartCh.publish({ | ||
}) | ||
this.worker.off('message', onMessage) | ||
}, | ||
@@ -468,2 +479,3 @@ (err) => { | ||
}) | ||
this.worker.off('message', onMessage) | ||
} | ||
@@ -477,2 +489,3 @@ ) | ||
}) | ||
this.worker.off('message', onMessage) | ||
throw err | ||
@@ -479,0 +492,0 @@ } |
@@ -6,3 +6,4 @@ 'use strict' | ||
removeEfdStringFromTestName, | ||
addEfdStringToTestName | ||
addEfdStringToTestName, | ||
NUM_FAILED_TEST_RETRIES | ||
} = require('../../../dd-trace/src/plugins/util/test') | ||
@@ -29,4 +30,2 @@ const { channel, AsyncResource } = require('../helpers/instrument') | ||
const NUM_FAILED_TEST_RETRIES = 5 | ||
function isNewTest (test, knownTests) { | ||
@@ -33,0 +32,0 @@ const testSuite = getTestSuitePath(test.file, process.cwd()) |
@@ -28,5 +28,5 @@ 'use strict' | ||
const filters = arg0 && typeof arg0 === 'object' ? [arg0] : [] | ||
const filters = arg0 !== null && typeof arg0 === 'object' ? [arg0] : [] | ||
if (arg1 && typeof arg1 === 'object' && methodsOptionalArgs.includes(methodName)) { | ||
if (arg1 !== null && typeof arg1 === 'object' && methodsOptionalArgs.includes(methodName)) { | ||
filters.push(arg1) | ||
@@ -33,0 +33,0 @@ } |
@@ -49,3 +49,3 @@ 'use strict' | ||
return instrument(req, res, () => { | ||
const page = (typeof match === 'object' && typeof match.definition === 'object') | ||
const page = (match !== null && typeof match === 'object' && typeof match.definition === 'object') | ||
? match.definition.pathname | ||
@@ -52,0 +52,0 @@ : undefined |
@@ -38,3 +38,3 @@ 'use strict' | ||
const pgQuery = arguments[0] && typeof arguments[0] === 'object' | ||
const pgQuery = arguments[0] !== null && typeof arguments[0] === 'object' | ||
? arguments[0] | ||
@@ -113,3 +113,3 @@ : { text: arguments[0] } | ||
const pgQuery = arguments[0] && typeof arguments[0] === 'object' ? arguments[0] : { text: arguments[0] } | ||
const pgQuery = arguments[0] !== null && typeof arguments[0] === 'object' ? arguments[0] : { text: arguments[0] } | ||
@@ -116,0 +116,0 @@ return asyncResource.runInAsyncScope(() => { |
@@ -5,3 +5,3 @@ const semver = require('semver') | ||
const shimmer = require('../../datadog-shimmer') | ||
const { parseAnnotations, getTestSuitePath } = require('../../dd-trace/src/plugins/util/test') | ||
const { parseAnnotations, getTestSuitePath, NUM_FAILED_TEST_RETRIES } = require('../../dd-trace/src/plugins/util/test') | ||
const log = require('../../dd-trace/src/log') | ||
@@ -25,2 +25,3 @@ | ||
const testSuiteToErrors = new Map() | ||
const testSessionAsyncResource = new AsyncResource('bound-anonymous-fn') | ||
@@ -40,2 +41,3 @@ let applyRepeatEachIndex = null | ||
let isEarlyFlakeDetectionEnabled = false | ||
let isFlakyTestRetriesEnabled = false | ||
let earlyFlakeDetectionNumRetries = 0 | ||
@@ -202,2 +204,27 @@ let knownTests = {} | ||
function getTestByTestId (dispatcher, testId) { | ||
if (dispatcher._testById) { | ||
return dispatcher._testById.get(testId)?.test | ||
} | ||
const allTests = dispatcher._allTests || dispatcher._ddAllTests | ||
if (allTests) { | ||
return allTests.find(({ id }) => id === testId) | ||
} | ||
} | ||
function getChannelPromise (channelToPublishTo) { | ||
return new Promise(resolve => { | ||
testSessionAsyncResource.runInAsyncScope(() => { | ||
channelToPublishTo.publish({ onDone: resolve }) | ||
}) | ||
}) | ||
} | ||
// eslint-disable-next-line | ||
// Inspired by https://github.com/microsoft/playwright/blob/2b77ed4d7aafa85a600caa0b0d101b72c8437eeb/packages/playwright/src/reporters/base.ts#L293 | ||
// We can't use test.outcome() directly because it's set on follow up handlers: | ||
// our `testEndHandler` is called before the outcome is set. | ||
function testWillRetry (test, testStatus) { | ||
return testStatus === 'fail' && test.results.length <= test.retries | ||
} | ||
function testBeginHandler (test, browserName) { | ||
@@ -257,2 +284,3 @@ const { | ||
steps: testResult?.steps || [], | ||
isRetry: testResult?.retry > 0, | ||
error, | ||
@@ -275,4 +303,6 @@ extraTags: annotationTags, | ||
remainingTestsByFile[testSuiteAbsolutePath] = remainingTestsByFile[testSuiteAbsolutePath] | ||
.filter(currentTest => currentTest !== test) | ||
if (!testWillRetry(test, testStatus)) { | ||
remainingTestsByFile[testSuiteAbsolutePath] = remainingTestsByFile[testSuiteAbsolutePath] | ||
.filter(currentTest => currentTest !== test) | ||
} | ||
@@ -344,12 +374,2 @@ // Last test, we finish the suite | ||
function getTestByTestId (dispatcher, testId) { | ||
if (dispatcher._testById) { | ||
return dispatcher._testById.get(testId)?.test | ||
} | ||
const allTests = dispatcher._allTests || dispatcher._ddAllTests | ||
if (allTests) { | ||
return allTests.find(({ id }) => id === testId) | ||
} | ||
} | ||
function dispatcherHookNew (dispatcherExport, runWrapper) { | ||
@@ -383,4 +403,2 @@ shimmer.wrap(dispatcherExport.Dispatcher.prototype, 'run', runWrapper) | ||
const testSessionAsyncResource = new AsyncResource('bound-anonymous-fn') | ||
rootDir = getRootDir(this) | ||
@@ -394,17 +412,11 @@ | ||
const configurationPromise = new Promise((resolve) => { | ||
onDone = resolve | ||
}) | ||
testSessionAsyncResource.runInAsyncScope(() => { | ||
libraryConfigurationCh.publish({ onDone }) | ||
}) | ||
try { | ||
const { err, libraryConfig } = await configurationPromise | ||
const { err, libraryConfig } = await getChannelPromise(libraryConfigurationCh) | ||
if (!err) { | ||
isEarlyFlakeDetectionEnabled = libraryConfig.isEarlyFlakeDetectionEnabled | ||
earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries | ||
isFlakyTestRetriesEnabled = libraryConfig.isFlakyTestRetriesEnabled | ||
} | ||
} catch (e) { | ||
isEarlyFlakeDetectionEnabled = false | ||
log.error(e) | ||
@@ -414,11 +426,4 @@ } | ||
if (isEarlyFlakeDetectionEnabled && semver.gte(playwrightVersion, MINIMUM_SUPPORTED_VERSION_EFD)) { | ||
const knownTestsPromise = new Promise((resolve) => { | ||
onDone = resolve | ||
}) | ||
testSessionAsyncResource.runInAsyncScope(() => { | ||
knownTestsCh.publish({ onDone }) | ||
}) | ||
try { | ||
const { err, knownTests: receivedKnownTests } = await knownTestsPromise | ||
const { err, knownTests: receivedKnownTests } = await getChannelPromise(knownTestsCh) | ||
if (!err) { | ||
@@ -430,2 +435,3 @@ knownTests = receivedKnownTests | ||
} catch (err) { | ||
isEarlyFlakeDetectionEnabled = false | ||
log.error(err) | ||
@@ -437,2 +443,10 @@ } | ||
if (isFlakyTestRetriesEnabled) { | ||
projects.forEach(project => { | ||
if (project.retries === 0) { // Only if it hasn't been set by the user | ||
project.retries = NUM_FAILED_TEST_RETRIES | ||
} | ||
}) | ||
} | ||
const runAllTestsReturn = await runAllTests.apply(this, arguments) | ||
@@ -439,0 +453,0 @@ |
@@ -54,3 +54,3 @@ 'use strict' | ||
const result = fn.apply(this, arguments) | ||
if (result && typeof result === 'object' && typeof result.then === 'function') { | ||
if (result !== null && typeof result === 'object' && typeof result.then === 'function') { | ||
return result.then(function () { | ||
@@ -57,0 +57,0 @@ nextChannel.publish({ req }) |
const { addHook, channel, AsyncResource } = require('./helpers/instrument') | ||
const shimmer = require('../../datadog-shimmer') | ||
const { NUM_FAILED_TEST_RETRIES } = require('../../dd-trace/src/plugins/util/test') | ||
@@ -19,2 +20,3 @@ // test hooks | ||
const testSessionFinishCh = channel('ci:vitest:session:finish') | ||
const libraryConfigurationCh = channel('ci:vitest:library-configuration') | ||
@@ -34,2 +36,14 @@ const taskToAsync = new WeakMap() | ||
function isReporterPackageNewest (vitestPackage) { | ||
return vitestPackage.h?.name === 'BaseSequencer' | ||
} | ||
function getChannelPromise (channelToPublishTo) { | ||
return new Promise(resolve => { | ||
sessionAsyncResource.runInAsyncScope(() => { | ||
channelToPublishTo.publish({ onDone: resolve }) | ||
}) | ||
}) | ||
} | ||
function getSessionStatus (state) { | ||
@@ -95,2 +109,34 @@ if (state.getCountOfFailedTests() > 0) { | ||
} | ||
// There isn't any other async function that we seem to be able to hook into | ||
// So we will use the sort from BaseSequencer. This means that a custom sequencer | ||
// will not work. This will be a known limitation. | ||
let isFlakyTestRetriesEnabled = false | ||
try { | ||
const { err, libraryConfig } = await getChannelPromise(libraryConfigurationCh) | ||
if (!err) { | ||
isFlakyTestRetriesEnabled = libraryConfig.isFlakyTestRetriesEnabled | ||
} | ||
} catch (e) { | ||
isFlakyTestRetriesEnabled = false | ||
} | ||
if (isFlakyTestRetriesEnabled && !this.ctx.config.retry) { | ||
this.ctx.config.retry = NUM_FAILED_TEST_RETRIES | ||
} | ||
let testCodeCoverageLinesTotal | ||
if (this.ctx.coverageProvider?.generateCoverage) { | ||
shimmer.wrap(this.ctx.coverageProvider, 'generateCoverage', generateCoverage => async function () { | ||
const totalCodeCoverage = await generateCoverage.apply(this, arguments) | ||
try { | ||
testCodeCoverageLinesTotal = totalCodeCoverage.getCoverageSummary().lines.pct | ||
} catch (e) { | ||
// ignore errors | ||
} | ||
return totalCodeCoverage | ||
}) | ||
} | ||
shimmer.wrap(this.ctx, 'exit', exit => async function () { | ||
@@ -111,4 +157,5 @@ let onFinish | ||
status: getSessionStatus(this.state), | ||
onFinish, | ||
error | ||
testCodeCoverageLinesTotal, | ||
error, | ||
onFinish | ||
}) | ||
@@ -126,2 +173,17 @@ }) | ||
function getCreateCliWrapper (vitestPackage, frameworkVersion) { | ||
shimmer.wrap(vitestPackage, 'c', oldCreateCli => function () { | ||
if (!testSessionStartCh.hasSubscribers) { | ||
return oldCreateCli.apply(this, arguments) | ||
} | ||
sessionAsyncResource.runInAsyncScope(() => { | ||
const processArgv = process.argv.slice(2).join(' ') | ||
testSessionStartCh.publish({ command: `vitest ${processArgv}`, frameworkVersion }) | ||
}) | ||
return oldCreateCli.apply(this, arguments) | ||
}) | ||
return vitestPackage | ||
} | ||
addHook({ | ||
@@ -134,6 +196,18 @@ name: 'vitest', | ||
// test start (only tests that are not marked as skip or todo) | ||
shimmer.wrap(VitestTestRunner.prototype, 'onBeforeTryTask', onBeforeTryTask => async function (task) { | ||
shimmer.wrap(VitestTestRunner.prototype, 'onBeforeTryTask', onBeforeTryTask => async function (task, retryInfo) { | ||
if (!testStartCh.hasSubscribers) { | ||
return onBeforeTryTask.apply(this, arguments) | ||
} | ||
const { retry: numAttempt } = retryInfo | ||
// We finish the previous test here because we know it has failed already | ||
if (numAttempt > 0) { | ||
const asyncResource = taskToAsync.get(task) | ||
const testError = task.result?.errors?.[0] | ||
if (asyncResource) { | ||
asyncResource.runInAsyncScope(() => { | ||
testErrorCh.publish({ error: testError }) | ||
}) | ||
} | ||
} | ||
const asyncResource = new AsyncResource('bound-anonymous-fn') | ||
@@ -143,3 +217,7 @@ taskToAsync.set(task, asyncResource) | ||
asyncResource.runInAsyncScope(() => { | ||
testStartCh.publish({ testName: getTestName(task), testSuiteAbsolutePath: task.suite.file.filepath }) | ||
testStartCh.publish({ | ||
testName: getTestName(task), | ||
testSuiteAbsolutePath: task.file.filepath, | ||
isRetry: numAttempt > 0 | ||
}) | ||
}) | ||
@@ -173,8 +251,21 @@ return onBeforeTryTask.apply(this, arguments) | ||
// There are multiple index* files across different versions of vitest, | ||
// so we check for the existence of BaseSequencer to determine if we are in the right file | ||
addHook({ | ||
name: 'vitest', | ||
versions: ['>=2.0.0'], | ||
versions: ['>=1.6.0 <2.0.0'], | ||
filePattern: 'dist/vendor/index.*' | ||
}, (vitestPackage) => { | ||
// there are multiple index* files so we have to check the exported values | ||
if (isReporterPackage(vitestPackage)) { | ||
shimmer.wrap(vitestPackage.B.prototype, 'sort', getSortWrapper) | ||
} | ||
return vitestPackage | ||
}) | ||
addHook({ | ||
name: 'vitest', | ||
versions: ['>=2.0.0 <2.0.5'], | ||
filePattern: 'dist/vendor/index.*' | ||
}, (vitestPackage) => { | ||
if (isReporterPackageNew(vitestPackage)) { | ||
@@ -189,8 +280,7 @@ shimmer.wrap(vitestPackage.e.prototype, 'sort', getSortWrapper) | ||
name: 'vitest', | ||
versions: ['>=1.6.0'], | ||
filePattern: 'dist/vendor/index.*' | ||
versions: ['>=2.0.5'], | ||
filePattern: 'dist/chunks/index.*' | ||
}, (vitestPackage) => { | ||
// there are multiple index* files so we have to check the exported values | ||
if (isReporterPackage(vitestPackage)) { | ||
shimmer.wrap(vitestPackage.B.prototype, 'sort', getSortWrapper) | ||
if (isReporterPackageNewest(vitestPackage)) { | ||
shimmer.wrap(vitestPackage.h.prototype, 'sort', getSortWrapper) | ||
} | ||
@@ -204,18 +294,11 @@ | ||
name: 'vitest', | ||
versions: ['>=1.6.0'], | ||
versions: ['>=1.6.0 <2.0.5'], | ||
filePattern: 'dist/vendor/cac.*' | ||
}, (vitestPackage, frameworkVersion) => { | ||
shimmer.wrap(vitestPackage, 'c', oldCreateCli => function () { | ||
if (!testSessionStartCh.hasSubscribers) { | ||
return oldCreateCli.apply(this, arguments) | ||
} | ||
sessionAsyncResource.runInAsyncScope(() => { | ||
const processArgv = process.argv.slice(2).join(' ') | ||
testSessionStartCh.publish({ command: `vitest ${processArgv}`, frameworkVersion }) | ||
}) | ||
return oldCreateCli.apply(this, arguments) | ||
}) | ||
}, getCreateCliWrapper) | ||
return vitestPackage | ||
}) | ||
addHook({ | ||
name: 'vitest', | ||
versions: ['>=2.0.5'], | ||
filePattern: 'dist/chunks/cac.*' | ||
}, getCreateCliWrapper) | ||
@@ -228,3 +311,3 @@ // test suite start and finish | ||
file: 'dist/index.js' | ||
}, vitestPackage => { | ||
}, (vitestPackage, frameworkVersion) => { | ||
shimmer.wrap(vitestPackage, 'startTests', startTests => async function (testPath) { | ||
@@ -238,3 +321,3 @@ let testSuiteError = null | ||
testSuiteAsyncResource.runInAsyncScope(() => { | ||
testSuiteStartCh.publish(testPath[0]) | ||
testSuiteStartCh.publish({ testSuiteAbsolutePath: testPath[0], frameworkVersion }) | ||
}) | ||
@@ -250,2 +333,3 @@ const startTestsResponse = await startTests.apply(this, arguments) | ||
// Only one test task per test, even if there are retries | ||
testTasks.forEach(task => { | ||
@@ -258,3 +342,3 @@ const testAsyncResource = taskToAsync.get(task) | ||
if (state === 'skip') { // programmatic skip | ||
testSkipCh.publish({ testName: getTestName(task), testSuiteAbsolutePath: task.suite.file.filepath }) | ||
testSkipCh.publish({ testName: getTestName(task), testSuiteAbsolutePath: task.file.filepath }) | ||
} else if (state === 'pass') { | ||
@@ -275,4 +359,6 @@ if (testAsyncResource) { | ||
if (testAsyncResource) { | ||
const isRetry = task.result?.retryCount > 0 | ||
// `duration` is the duration of all the retries, so it can't be used if there are retries | ||
testAsyncResource.runInAsyncScope(() => { | ||
testErrorCh.publish({ duration, error: testError }) | ||
testErrorCh.publish({ duration: !isRetry ? duration : undefined, error: testError }) | ||
}) | ||
@@ -285,3 +371,3 @@ } | ||
} else { // test.skip or test.todo | ||
testSkipCh.publish({ testName: getTestName(task), testSuiteAbsolutePath: task.suite.file.filepath }) | ||
testSkipCh.publish({ testName: getTestName(task), testSuiteAbsolutePath: task.file.filepath }) | ||
} | ||
@@ -288,0 +374,0 @@ }) |
@@ -7,2 +7,3 @@ 'use strict' | ||
const { isTrue } = require('../../dd-trace/src/util') | ||
const coalesce = require('koalas') | ||
@@ -77,3 +78,3 @@ class BaseAwsSdkPlugin extends ClientPlugin { | ||
const operation = response.request.operation | ||
this.responseExtractDSMContext(operation, params, response.data, span) | ||
this.responseExtractDSMContext(operation, params, response.data ?? response, span) | ||
} | ||
@@ -168,5 +169,18 @@ this.addResponseTags(span, response) | ||
// check if AWS batch propagation or AWS_[SERVICE] batch propagation is enabled via env variable | ||
const serviceId = serviceIdentifier.toUpperCase() | ||
const batchPropagationEnabled = isTrue( | ||
coalesce( | ||
specificConfig.batchPropagationEnabled, | ||
process.env[`DD_TRACE_AWS_SDK_${serviceId}_BATCH_PROPAGATION_ENABLED`], | ||
config.batchPropagationEnabled, | ||
process.env.DD_TRACE_AWS_SDK_BATCH_PROPAGATION_ENABLED, | ||
false | ||
) | ||
) | ||
// Merge the specific config back into the main config | ||
return Object.assign({}, config, specificConfig, { | ||
splitByAwsService: config.splitByAwsService !== false, | ||
batchPropagationEnabled: config.batchPropagationEnabled !== false, | ||
batchPropagationEnabled, | ||
hooks | ||
@@ -173,0 +187,0 @@ }) |
@@ -24,3 +24,3 @@ 'use strict' | ||
// dynamoDB batch TableName | ||
if (params.RequestItems) { | ||
if (params.RequestItems !== null) { | ||
if (typeof params.RequestItems === 'object') { | ||
@@ -27,0 +27,0 @@ if (Object.keys(params.RequestItems).length === 1) { |
@@ -159,3 +159,3 @@ 'use strict' | ||
stream, | ||
i === 0 || (this.config.kinesis && this.config.kinesis.batchPropagationEnabled) | ||
i === 0 || (this.config.batchPropagationEnabled) | ||
) | ||
@@ -162,0 +162,0 @@ } |
@@ -66,3 +66,3 @@ 'use strict' | ||
params.TopicArn, | ||
i === 0 || (this.config.sns && this.config.sns.batchPropagationEnabled) | ||
i === 0 || (this.config.batchPropagationEnabled) | ||
) | ||
@@ -69,0 +69,0 @@ } |
@@ -160,4 +160,4 @@ 'use strict' | ||
return JSON.parse(textMap) | ||
} else if (attributes.Type === 'Binary') { | ||
const buffer = Buffer.from(attributes.Value, 'base64') | ||
} else if (attributes.Type === 'Binary' || attributes.DataType === 'Binary') { | ||
const buffer = Buffer.from(attributes.Value ?? attributes.BinaryValue, 'base64') | ||
return JSON.parse(buffer) | ||
@@ -226,3 +226,3 @@ } | ||
params.QueueUrl, | ||
i === 0 || (this.config.sqs && this.config.sqs.batchPropagationEnabled) | ||
i === 0 || (this.config.batchPropagationEnabled) | ||
) | ||
@@ -229,0 +229,0 @@ } |
@@ -50,3 +50,3 @@ 'use strict' | ||
const inputObj = JSON.parse(input) | ||
if (inputObj && typeof inputObj === 'object') { | ||
if (inputObj !== null && typeof inputObj === 'object') { | ||
// We've parsed the input JSON string | ||
@@ -53,0 +53,0 @@ inputObj._datadog = {} |
@@ -10,6 +10,6 @@ 'use strict' | ||
// eslint-disable-next-line max-len | ||
const PARAM_PATTERN = '^-{0,2}(?:p(?:ass(?:w(?:or)?d)?)?|api_?key|secret|a(?:ccess|uth)_token|mysql_pwd|credentials|(?:stripe)?token)$' | ||
const PARAM_PATTERN = '^-{0,2}(?:p(?:ass(?:w(?:or)?d)?)?|address|api[-_]?key|e?mail|secret(?:[-_]?key)?|a(?:ccess|uth)[-_]?token|mysql_pwd|credentials|(?:stripe)?token)$' | ||
const regexParam = new RegExp(PARAM_PATTERN, 'i') | ||
const ENV_PATTERN = '^(\\w+=\\w+;)*\\w+=\\w+;?$' | ||
const envvarRegex = new RegExp(ENV_PATTERN) | ||
const envVarRegex = new RegExp(ENV_PATTERN) | ||
const REDACTED = '?' | ||
@@ -65,3 +65,5 @@ | ||
if (typeof token === 'object') { | ||
if (token === null) { | ||
continue | ||
} else if (typeof token === 'object') { | ||
if (token.pattern) { | ||
@@ -75,3 +77,3 @@ result.push(token.pattern) | ||
} else if (!foundBinary) { | ||
if (envvarRegex.test(token)) { | ||
if (envVarRegex.test(token)) { | ||
const envSplit = token.split('=') | ||
@@ -78,0 +80,0 @@ |
@@ -198,2 +198,13 @@ 'use strict' | ||
this.addSub('ci:cucumber:test:retry', (isFlakyRetry) => { | ||
const store = storage.getStore() | ||
const span = store.span | ||
if (isFlakyRetry) { | ||
span.setTag(TEST_IS_RETRY, 'true') | ||
} | ||
span.setTag(TEST_STATUS, 'fail') | ||
span.finish() | ||
finishAllTraceSpans(span) | ||
}) | ||
this.addSub('ci:cucumber:test-step:start', ({ resource }) => { | ||
@@ -243,3 +254,11 @@ const store = storage.getStore() | ||
this.addSub('ci:cucumber:test:finish', ({ isStep, status, skipReason, errorMessage, isNew, isEfdRetry }) => { | ||
this.addSub('ci:cucumber:test:finish', ({ | ||
isStep, | ||
status, | ||
skipReason, | ||
errorMessage, | ||
isNew, | ||
isEfdRetry, | ||
isFlakyRetry | ||
}) => { | ||
const span = storage.getStore().span | ||
@@ -265,2 +284,6 @@ const statusTag = isStep ? 'step.status' : TEST_STATUS | ||
if (isFlakyRetry > 0) { | ||
span.setTag(TEST_IS_RETRY, 'true') | ||
} | ||
span.finish() | ||
@@ -267,0 +290,0 @@ if (!isStep) { |
@@ -31,3 +31,4 @@ const { | ||
TEST_IS_RETRY, | ||
TEST_EARLY_FLAKE_ENABLED | ||
TEST_EARLY_FLAKE_ENABLED, | ||
NUM_FAILED_TEST_RETRIES | ||
} = require('../../dd-trace/src/plugins/util/test') | ||
@@ -211,2 +212,5 @@ const { isMarkedAsUnskippable } = require('../../datadog-plugin-jest/src/util') | ||
// Init function returns a promise that resolves with the Cypress configuration | ||
// Depending on the received configuration, the Cypress configuration can be modified: | ||
// for example, to enable retries for failed tests. | ||
init (tracer, cypressConfig) { | ||
@@ -216,2 +220,29 @@ this._isInit = true | ||
this.cypressConfig = cypressConfig | ||
this.libraryConfigurationPromise = getLibraryConfiguration(this.tracer, this.testConfiguration) | ||
.then((libraryConfigurationResponse) => { | ||
if (libraryConfigurationResponse.err) { | ||
log.error(libraryConfigurationResponse.err) | ||
} else { | ||
const { | ||
libraryConfig: { | ||
isSuitesSkippingEnabled, | ||
isCodeCoverageEnabled, | ||
isEarlyFlakeDetectionEnabled, | ||
earlyFlakeDetectionNumRetries, | ||
isFlakyTestRetriesEnabled | ||
} | ||
} = libraryConfigurationResponse | ||
this.isSuitesSkippingEnabled = isSuitesSkippingEnabled | ||
this.isCodeCoverageEnabled = isCodeCoverageEnabled | ||
this.isEarlyFlakeDetectionEnabled = isEarlyFlakeDetectionEnabled | ||
this.earlyFlakeDetectionNumRetries = earlyFlakeDetectionNumRetries | ||
this.isFlakyTestRetriesEnabled = isFlakyTestRetriesEnabled | ||
if (this.isFlakyTestRetriesEnabled) { | ||
this.cypressConfig.retries.runMode = NUM_FAILED_TEST_RETRIES | ||
} | ||
} | ||
return this.cypressConfig | ||
}) | ||
return this.libraryConfigurationPromise | ||
} | ||
@@ -233,3 +264,3 @@ | ||
getTestSpan (testName, testSuite, isUnskippable, isForcedToRun) { | ||
getTestSpan ({ testName, testSuite, isUnskippable, isForcedToRun, testSourceFile }) { | ||
const testSuiteTags = { | ||
@@ -258,4 +289,7 @@ [TEST_COMMAND]: this.command, | ||
const codeOwners = getCodeOwnersForFilename(testSuite, this.codeOwnersEntries) | ||
if (testSourceFile) { | ||
testSpanMetadata[TEST_SOURCE_FILE] = testSourceFile | ||
} | ||
const codeOwners = this.getTestCodeOwners({ testSuite, testSourceFile }) | ||
if (codeOwners) { | ||
@@ -305,2 +339,5 @@ testSpanMetadata[TEST_CODE_OWNERS] = codeOwners | ||
async beforeRun (details) { | ||
// We need to make sure that the plugin is initialized before running the tests | ||
// This is for the case where the user has not returned the promise from the init function | ||
await this.libraryConfigurationPromise | ||
this.command = getCypressCommand(details) | ||
@@ -310,21 +347,2 @@ this.frameworkVersion = getCypressVersion(details) | ||
const libraryConfigurationResponse = await getLibraryConfiguration(this.tracer, this.testConfiguration) | ||
if (libraryConfigurationResponse.err) { | ||
log.error(libraryConfigurationResponse.err) | ||
} else { | ||
const { | ||
libraryConfig: { | ||
isSuitesSkippingEnabled, | ||
isCodeCoverageEnabled, | ||
isEarlyFlakeDetectionEnabled, | ||
earlyFlakeDetectionNumRetries | ||
} | ||
} = libraryConfigurationResponse | ||
this.isSuitesSkippingEnabled = isSuitesSkippingEnabled | ||
this.isCodeCoverageEnabled = isCodeCoverageEnabled | ||
this.isEarlyFlakeDetectionEnabled = isEarlyFlakeDetectionEnabled | ||
this.earlyFlakeDetectionNumRetries = earlyFlakeDetectionNumRetries | ||
} | ||
if (this.isEarlyFlakeDetectionEnabled) { | ||
@@ -475,8 +493,12 @@ const knownTestsResponse = await getKnownTests( | ||
) | ||
const skippedTestSpan = this.getTestSpan(cypressTestName, spec.relative) | ||
let testSourceFile | ||
if (spec.absolute && this.repositoryRoot) { | ||
skippedTestSpan.setTag(TEST_SOURCE_FILE, getTestSuitePath(spec.absolute, this.repositoryRoot)) | ||
testSourceFile = getTestSuitePath(spec.absolute, this.repositoryRoot) | ||
} else { | ||
skippedTestSpan.setTag(TEST_SOURCE_FILE, spec.relative) | ||
testSourceFile = spec.relative | ||
} | ||
const skippedTestSpan = this.getTestSpan({ testName: cypressTestName, testSuite: spec.relative, testSourceFile }) | ||
skippedTestSpan.setTag(TEST_STATUS, 'skip') | ||
@@ -496,25 +518,57 @@ if (isSkippedByItr) { | ||
let latestError | ||
finishedTests.forEach((finishedTest) => { | ||
const cypressTest = cypressTests.find(test => test.title.join(' ') === finishedTest.testName) | ||
if (!cypressTest) { | ||
return | ||
const finishedTestsByTestName = finishedTests.reduce((acc, finishedTest) => { | ||
if (!acc[finishedTest.testName]) { | ||
acc[finishedTest.testName] = [] | ||
} | ||
if (cypressTest.displayError) { | ||
latestError = new Error(cypressTest.displayError) | ||
} | ||
const cypressTestStatus = CYPRESS_STATUS_TO_TEST_STATUS[cypressTest.state] | ||
// update test status | ||
if (cypressTestStatus !== finishedTest.testStatus) { | ||
finishedTest.testSpan.setTag(TEST_STATUS, cypressTestStatus) | ||
finishedTest.testSpan.setTag('error', latestError) | ||
} | ||
if (this.itrCorrelationId) { | ||
finishedTest.testSpan.setTag(ITR_CORRELATION_ID, this.itrCorrelationId) | ||
} | ||
if (spec.absolute && this.repositoryRoot) { | ||
finishedTest.testSpan.setTag(TEST_SOURCE_FILE, getTestSuitePath(spec.absolute, this.repositoryRoot)) | ||
} else { | ||
finishedTest.testSpan.setTag(TEST_SOURCE_FILE, spec.relative) | ||
} | ||
finishedTest.testSpan.finish(finishedTest.finishTime) | ||
acc[finishedTest.testName].push(finishedTest) | ||
return acc | ||
}, {}) | ||
Object.entries(finishedTestsByTestName).forEach(([testName, finishedTestAttempts]) => { | ||
finishedTestAttempts.forEach((finishedTest, attemptIndex) => { | ||
// TODO: there could be multiple if there have been retries! | ||
// potentially we need to match the test status! | ||
const cypressTest = cypressTests.find(test => test.title.join(' ') === testName) | ||
if (!cypressTest) { | ||
return | ||
} | ||
// finishedTests can include multiple tests with the same name if they have been retried | ||
// by early flake detection. Cypress is unaware of this so .attempts does not necessarily have | ||
// the same length as `finishedTestAttempts` | ||
let cypressTestStatus = CYPRESS_STATUS_TO_TEST_STATUS[cypressTest.state] | ||
if (cypressTest.attempts && cypressTest.attempts[attemptIndex]) { | ||
cypressTestStatus = CYPRESS_STATUS_TO_TEST_STATUS[cypressTest.attempts[attemptIndex].state] | ||
if (attemptIndex > 0) { | ||
finishedTest.testSpan.setTag(TEST_IS_RETRY, 'true') | ||
} | ||
} | ||
if (cypressTest.displayError) { | ||
latestError = new Error(cypressTest.displayError) | ||
} | ||
// Update test status | ||
if (cypressTestStatus !== finishedTest.testStatus) { | ||
finishedTest.testSpan.setTag(TEST_STATUS, cypressTestStatus) | ||
finishedTest.testSpan.setTag('error', latestError) | ||
} | ||
if (this.itrCorrelationId) { | ||
finishedTest.testSpan.setTag(ITR_CORRELATION_ID, this.itrCorrelationId) | ||
} | ||
let testSourceFile | ||
if (spec.absolute && this.repositoryRoot) { | ||
testSourceFile = getTestSuitePath(spec.absolute, this.repositoryRoot) | ||
} else { | ||
testSourceFile = spec.relative | ||
} | ||
if (testSourceFile) { | ||
finishedTest.testSpan.setTag(TEST_SOURCE_FILE, testSourceFile) | ||
} | ||
const codeOwners = this.getTestCodeOwners({ testSuite: spec.relative, testSourceFile }) | ||
if (codeOwners) { | ||
finishedTest.testSpan.setTag(TEST_CODE_OWNERS, codeOwners) | ||
} | ||
finishedTest.testSpan.finish(finishedTest.finishTime) | ||
}) | ||
}) | ||
@@ -566,3 +620,8 @@ | ||
if (!this.activeTestSpan) { | ||
this.activeTestSpan = this.getTestSpan(testName, testSuite, isUnskippable, isForcedToRun) | ||
this.activeTestSpan = this.getTestSpan({ | ||
testName, | ||
testSuite, | ||
isUnskippable, | ||
isForcedToRun | ||
}) | ||
} | ||
@@ -634,4 +693,11 @@ | ||
} | ||
getTestCodeOwners ({ testSuite, testSourceFile }) { | ||
if (testSourceFile) { | ||
return getCodeOwnersForFilename(testSourceFile, this.codeOwnersEntries) | ||
} | ||
return getCodeOwnersForFilename(testSuite, this.codeOwnersEntries) | ||
} | ||
} | ||
module.exports = new CypressPlugin() |
@@ -25,7 +25,6 @@ const NoopTracer = require('../../dd-trace/src/noop/tracer') | ||
// We still need to register these tasks or the support file will fail | ||
return on('task', noopTask) | ||
on('task', noopTask) | ||
return config | ||
} | ||
cypressPlugin.init(tracer, config) | ||
on('before:run', cypressPlugin.beforeRun.bind(cypressPlugin)) | ||
@@ -35,2 +34,4 @@ on('after:spec', cypressPlugin.afterSpec.bind(cypressPlugin)) | ||
on('task', cypressPlugin.getTasks()) | ||
return cypressPlugin.init(tracer, config) | ||
} |
@@ -32,3 +32,3 @@ 'use strict' | ||
meta: { | ||
'file.descriptor': (typeof fd === 'object' || typeof fd === 'number') ? fd.toString() : '', | ||
'file.descriptor': ((fd !== null && typeof fd === 'object') || typeof fd === 'number') ? fd.toString() : '', | ||
'file.dest': params.dest || params.newPath || (params.target && params.path), | ||
@@ -35,0 +35,0 @@ 'file.flag': String(flag || defaultFlag || ''), |
@@ -151,2 +151,3 @@ const CiPlugin = require('../../dd-trace/src/plugins/ci_plugin') | ||
config._ddRepositoryRoot = this.repositoryRoot | ||
config._ddIsFlakyTestRetriesEnabled = this.libraryConfig?.isFlakyTestRetriesEnabled ?? false | ||
}) | ||
@@ -328,3 +329,4 @@ }) | ||
isNew, | ||
isEfdRetry | ||
isEfdRetry, | ||
isJestRetry | ||
} = test | ||
@@ -354,2 +356,6 @@ | ||
if (isJestRetry) { | ||
extraTags[TEST_IS_RETRY] = 'true' | ||
} | ||
return super.startTestSpan(name, suite, this.testSuiteSpan, extraTags) | ||
@@ -356,0 +362,0 @@ } |
@@ -84,3 +84,3 @@ 'use strict' | ||
for (const message of messages) { | ||
if (typeof message === 'object') { | ||
if (message !== null && typeof message === 'object') { | ||
this.tracer.inject(span, 'text_map', message.headers) | ||
@@ -87,0 +87,0 @@ if (this.config.dsmEnabled) { |
@@ -118,3 +118,3 @@ 'use strict' | ||
function isObject (val) { | ||
return typeof val === 'object' && val !== null && !(val instanceof Array) | ||
return val !== null && typeof val === 'object' && !Array.isArray(val) | ||
} | ||
@@ -121,0 +121,0 @@ |
@@ -121,3 +121,3 @@ 'use strict' | ||
// createChatCompletion, createCompletion | ||
if (typeof payload.logit_bias === 'object' && payload.logit_bias) { | ||
if (payload.logit_bias !== null && typeof payload.logit_bias === 'object') { | ||
for (const [tokenId, bias] of Object.entries(payload.logit_bias)) { | ||
@@ -431,3 +431,3 @@ tags[`openai.request.logit_bias.${tokenId}`] = bias | ||
const img = payload.file || payload.image | ||
if (img && typeof img === 'object' && img.path) { | ||
if (img !== null && typeof img === 'object' && img.path) { | ||
const file = path.basename(img.path) | ||
@@ -439,3 +439,3 @@ tags['openai.request.image'] = file | ||
// createImageEdit | ||
if (payload.mask && typeof payload.mask === 'object' && payload.mask.path) { | ||
if (payload.mask !== null && typeof payload.mask === 'object' && payload.mask.path) { | ||
const mask = path.basename(payload.mask.path) | ||
@@ -639,3 +639,3 @@ tags['openai.request.mask'] = mask | ||
if (body.file && typeof body.file === 'object' && body.file.path) { | ||
if (body.file !== null && typeof body.file === 'object' && body.file.path) { | ||
const filename = path.basename(body.file.path) | ||
@@ -653,3 +653,3 @@ tags['openai.request.filename'] = filename | ||
// This is a best effort attempt to extract the filename during the request | ||
if (body.file && typeof body.file === 'object' && body.file.path) { | ||
if (body.file !== null && typeof body.file === 'object' && body.file.path) { | ||
tags['openai.request.filename'] = path.basename(body.file.path) | ||
@@ -656,0 +656,0 @@ } |
@@ -119,3 +119,3 @@ 'use strict' | ||
}) | ||
this.addSub('ci:playwright:test:finish', ({ testStatus, steps, error, extraTags, isNew, isEfdRetry }) => { | ||
this.addSub('ci:playwright:test:finish', ({ testStatus, steps, error, extraTags, isNew, isEfdRetry, isRetry }) => { | ||
const store = storage.getStore() | ||
@@ -139,2 +139,5 @@ const span = store && store.span | ||
} | ||
if (isRetry) { | ||
span.setTag(TEST_IS_RETRY, 'true') | ||
} | ||
@@ -141,0 +144,0 @@ steps.forEach(step => { |
@@ -57,5 +57,5 @@ 'use strict' | ||
function isObject (val) { | ||
return typeof val === 'object' && val !== null && !(val instanceof Array) | ||
return val !== null && typeof val === 'object' && !Array.isArray(val) | ||
} | ||
module.exports = SharedbPlugin |
@@ -9,3 +9,5 @@ const CiPlugin = require('../../dd-trace/src/plugins/ci_plugin') | ||
getTestSuiteCommonTags, | ||
TEST_SOURCE_FILE | ||
TEST_SOURCE_FILE, | ||
TEST_IS_RETRY, | ||
TEST_CODE_COVERAGE_LINES_PCT | ||
} = require('../../dd-trace/src/plugins/util/test') | ||
@@ -29,5 +31,13 @@ const { COMPONENT } = require('../../dd-trace/src/constants') | ||
this.addSub('ci:vitest:test:start', ({ testName, testSuiteAbsolutePath }) => { | ||
this.addSub('ci:vitest:test:start', ({ testName, testSuiteAbsolutePath, isRetry }) => { | ||
const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot) | ||
const store = storage.getStore() | ||
const extraTags = { | ||
[TEST_SOURCE_FILE]: testSuite | ||
} | ||
if (isRetry) { | ||
extraTags[TEST_IS_RETRY] = 'true' | ||
} | ||
const span = this.startTestSpan( | ||
@@ -37,5 +47,3 @@ testName, | ||
this.testSuiteSpan, | ||
{ | ||
[TEST_SOURCE_FILE]: testSuite | ||
} | ||
extraTags | ||
) | ||
@@ -79,3 +87,7 @@ | ||
} | ||
span.finish(span._startTime + duration - MILLISECONDS_TO_SUBTRACT_FROM_FAILED_TEST_DURATION) // milliseconds | ||
if (duration) { | ||
span.finish(span._startTime + duration - MILLISECONDS_TO_SUBTRACT_FROM_FAILED_TEST_DURATION) // milliseconds | ||
} else { | ||
span.finish() // retries will not have a duration | ||
} | ||
finishAllTraceSpans(span) | ||
@@ -98,3 +110,4 @@ } | ||
this.addSub('ci:vitest:test-suite:start', (testSuiteAbsolutePath) => { | ||
this.addSub('ci:vitest:test-suite:start', ({ testSuiteAbsolutePath, frameworkVersion }) => { | ||
this.frameworkVersion = frameworkVersion | ||
const testSessionSpanContext = this.tracer.extract('text_map', { | ||
@@ -146,3 +159,3 @@ 'x-datadog-trace-id': process.env.DD_CIVISIBILITY_TEST_SESSION_ID, | ||
this.addSub('ci:vitest:session:finish', ({ status, onFinish, error }) => { | ||
this.addSub('ci:vitest:session:finish', ({ status, onFinish, error, testCodeCoverageLinesTotal }) => { | ||
this.testSessionSpan.setTag(TEST_STATUS, status) | ||
@@ -154,2 +167,6 @@ this.testModuleSpan.setTag(TEST_STATUS, status) | ||
} | ||
if (testCodeCoverageLinesTotal) { | ||
this.testModuleSpan.setTag(TEST_CODE_COVERAGE_LINES_PCT, testCodeCoverageLinesTotal) | ||
this.testSessionSpan.setTag(TEST_CODE_COVERAGE_LINES_PCT, testCodeCoverageLinesTotal) | ||
} | ||
this.testModuleSpan.finish() | ||
@@ -156,0 +173,0 @@ this.testSessionSpan.finish() |
@@ -7,3 +7,3 @@ 'use strict' | ||
sample (span, measured, measuredByDefault) { | ||
if (typeof measured === 'object') { | ||
if (measured !== null && typeof measured === 'object') { | ||
this.sample(span, measured[span.context()._name], measuredByDefault) | ||
@@ -10,0 +10,0 @@ } else if (measured !== undefined) { |
@@ -12,2 +12,4 @@ 'use strict' | ||
const responseBlockedSet = new WeakSet() | ||
const specificBlockingTypes = { | ||
@@ -121,2 +123,4 @@ GRAPHQL: 'graphql' | ||
responseBlockedSet.add(res) | ||
abortController?.abort() | ||
@@ -149,2 +153,6 @@ } | ||
function isBlocked (res) { | ||
return responseBlockedSet.has(res) | ||
} | ||
module.exports = { | ||
@@ -156,3 +164,4 @@ addSpecificEndpoint, | ||
getBlockingAction, | ||
setTemplates | ||
setTemplates, | ||
isBlocked | ||
} |
@@ -22,3 +22,6 @@ 'use strict' | ||
responseWriteHead: dc.channel('apm:http:server:response:writeHead:start'), | ||
httpClientRequestStart: dc.channel('apm:http:client:request:start') | ||
httpClientRequestStart: dc.channel('apm:http:client:request:start'), | ||
responseSetHeader: dc.channel('datadog:http:server:response:set-header:start'), | ||
setUncaughtExceptionCaptureCallbackStart: dc.channel('datadog:process:setUncaughtExceptionCaptureCallback:start') | ||
} |
'use strict' | ||
module.exports = { | ||
CODE_INJECTION_ANALYZER: require('./code-injection-analyzer'), | ||
COMMAND_INJECTION_ANALYZER: require('./command-injection-analyzer'), | ||
@@ -5,0 +6,0 @@ HARCODED_PASSWORD_ANALYZER: require('./hardcoded-password-analyzer'), |
@@ -16,3 +16,3 @@ 'use strict' | ||
function iterateObjectStrings (target, fn, levelKeys = [], depth = 20, visited = new Set()) { | ||
if (target && typeof target === 'object') { | ||
if (target !== null && typeof target === 'object') { | ||
Object.keys(target).forEach((key) => { | ||
@@ -19,0 +19,0 @@ const nextLevelKeys = [...levelKeys, key] |
@@ -50,2 +50,4 @@ 'use strict' | ||
_isExcluded (location) { | ||
if (!location) return false | ||
return EXCLUDED_LOCATIONS.some(excludedLocation => { | ||
@@ -52,0 +54,0 @@ return location.path.includes(excludedLocation) |
@@ -17,3 +17,4 @@ 'use strict' | ||
{ src: 'trimEnd' }, | ||
{ src: 'trimStart', dst: 'trim' } | ||
{ src: 'trimStart', dst: 'trim' }, | ||
{ src: 'eval', allowedWithoutCallee: true } | ||
] | ||
@@ -20,0 +21,0 @@ |
@@ -48,3 +48,3 @@ 'use strict' | ||
({ req }) => { | ||
if (req && req.body && typeof req.body === 'object') { | ||
if (req && req.body !== null && typeof req.body === 'object') { | ||
const iastContext = getIastContext(storage.getStore()) | ||
@@ -67,3 +67,3 @@ if (iastContext && iastContext.body !== req.body) { | ||
({ req }) => { | ||
if (req && req.params && typeof req.params === 'object') { | ||
if (req && req.params !== null && typeof req.params === 'object') { | ||
this._taintTrackingHandler(HTTP_REQUEST_PATH_PARAM, req, 'params') | ||
@@ -70,0 +70,0 @@ } |
@@ -30,3 +30,3 @@ 'use strict' | ||
if (key && typeof key === 'object') { | ||
if (key !== null && typeof key === 'object') { | ||
shimmer.wrap(key, 'toString', | ||
@@ -38,3 +38,3 @@ toString => this.getToStringWrap(toString, iastContext, KAFKA_MESSAGE_KEY)) | ||
if (value && typeof value === 'object') { | ||
if (value !== null && typeof value === 'object') { | ||
shimmer.wrap(value, 'toString', | ||
@@ -41,0 +41,0 @@ toString => this.getToStringWrap(toString, iastContext, KAFKA_MESSAGE_VALUE)) |
@@ -13,2 +13,3 @@ 'use strict' | ||
const mathRandomCallCh = dc.channel('datadog:random:call') | ||
const evalCallCh = dc.channel('datadog:eval:call') | ||
@@ -22,2 +23,3 @@ const JSON_VALUE = 'json.value' | ||
concat: noop, | ||
eval: noop, | ||
join: noop, | ||
@@ -141,2 +143,11 @@ parse: noop, | ||
eval: function (res, fn, target, script) { | ||
// eslint-disable-next-line no-eval | ||
if (evalCallCh.hasSubscribers && fn === globalThis.eval) { | ||
evalCallCh.publish({ script }) | ||
} | ||
return res | ||
}, | ||
parse: function (res, fn, target, json) { | ||
@@ -143,0 +154,0 @@ if (fn === JSON.parse) { |
@@ -8,2 +8,3 @@ 'use strict' | ||
const codeInjectionSensitiveAnalyzer = require('./sensitive-analyzers/code-injection-sensitive-analyzer') | ||
const commandSensitiveAnalyzer = require('./sensitive-analyzers/command-sensitive-analyzer') | ||
@@ -27,2 +28,3 @@ const hardcodedPasswordAnalyzer = require('./sensitive-analyzers/hardcoded-password-analyzer') | ||
this._sensitiveAnalyzers = new Map() | ||
this._sensitiveAnalyzers.set(vulnerabilities.CODE_INJECTION, codeInjectionSensitiveAnalyzer) | ||
this._sensitiveAnalyzers.set(vulnerabilities.COMMAND_INJECTION, commandSensitiveAnalyzer) | ||
@@ -29,0 +31,0 @@ this._sensitiveAnalyzers.set(vulnerabilities.NOSQL_MONGODB_INJECTION, jsonSensitiveAnalyzer) |
// eslint-disable-next-line max-len | ||
const DEFAULT_IAST_REDACTION_NAME_PATTERN = '(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?|access_?|secret_?)key(?:_?id)?|token|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)' | ||
const DEFAULT_IAST_REDACTION_NAME_PATTERN = '(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?|access_?|secret_?)key(?:_?id)?|token|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?|(?:sur|last)name|user(?:name)?|address|e?mail)' | ||
// eslint-disable-next-line max-len | ||
const DEFAULT_IAST_REDACTION_VALUE_PATTERN = '(?:bearer\\s+[a-z0-9\\._\\-]+|glpat-[\\w\\-]{20}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L][\\w=\\-]+\\.ey[I-L][\\w=\\-]+(?:\\.[\\w.+/=\\-]+)?|(?:[\\-]{5}BEGIN[a-z\\s]+PRIVATE\\sKEY[\\-]{5}[^\\-]+[\\-]{5}END[a-z\\s]+PRIVATE\\sKEY[\\-]{5}|ssh-rsa\\s*[a-z0-9/\\.+]{100,}))' | ||
const DEFAULT_IAST_REDACTION_VALUE_PATTERN = '(?:bearer\\s+[a-z0-9\\._\\-]+|glpat-[\\w\\-]{20}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L][\\w=\\-]+\\.ey[I-L][\\w=\\-]+(?:\\.[\\w.+/=\\-]+)?|(?:[\\-]{5}BEGIN[a-z\\s]+PRIVATE\\sKEY[\\-]{5}[^\\-]+[\\-]{5}END[a-z\\s]+PRIVATE\\sKEY[\\-]{5}|ssh-rsa\\s*[a-z0-9/\\.+]{100,})|[\\w\\.-]+@[a-zA-Z\\d\\.-]+\\.[a-zA-Z]{2,})' | ||
@@ -6,0 +6,0 @@ module.exports = { |
@@ -46,2 +46,4 @@ 'use strict' | ||
if (evidence.value == null) return { valueParts } | ||
if (typeof evidence.value === 'object' && evidence.rangesToApply) { | ||
@@ -73,3 +75,3 @@ const { value, ranges } = stringifyWithRanges(evidence.value, evidence.rangesToApply) | ||
formatEvidence (type, evidence, sourcesIndexes, sources) { | ||
if (typeof evidence.value === 'undefined') { | ||
if (evidence.value === undefined) { | ||
return undefined | ||
@@ -76,0 +78,0 @@ } |
module.exports = { | ||
COMMAND_INJECTION: 'COMMAND_INJECTION', | ||
CODE_INJECTION: 'CODE_INJECTION', | ||
HARDCODED_PASSWORD: 'HARDCODED_PASSWORD', | ||
@@ -4,0 +5,0 @@ HARDCODED_SECRET: 'HARDCODED_SECRET', |
@@ -16,3 +16,4 @@ 'use strict' | ||
responseBody, | ||
responseWriteHead | ||
responseWriteHead, | ||
responseSetHeader | ||
} = require('./channels') | ||
@@ -27,3 +28,3 @@ const waf = require('./waf') | ||
const { HTTP_CLIENT_IP } = require('../../../../ext/tags') | ||
const { block, setTemplates, getBlockingAction } = require('./blocking') | ||
const { isBlocked, block, setTemplates, getBlockingAction } = require('./blocking') | ||
const { passportTrackEvent } = require('./passport') | ||
@@ -67,2 +68,3 @@ const { storage } = require('../../../datadog-core') | ||
responseWriteHead.subscribe(onResponseWriteHead) | ||
responseSetHeader.subscribe(onResponseSetHeader) | ||
@@ -127,3 +129,3 @@ if (_config.appsec.eventTracking.enabled) { | ||
// TODO: temporary express instrumentation, will use express plugin later | ||
if (req.params && typeof req.params === 'object') { | ||
if (req.params !== null && typeof req.params === 'object') { | ||
persistent[addresses.HTTP_INCOMING_PARAMS] = req.params | ||
@@ -133,7 +135,7 @@ } | ||
// we need to keep this to support other cookie parsers | ||
if (req.cookies && typeof req.cookies === 'object') { | ||
if (req.cookies !== null && typeof req.cookies === 'object') { | ||
persistent[addresses.HTTP_INCOMING_COOKIES] = req.cookies | ||
} | ||
if (req.query && typeof req.query === 'object') { | ||
if (req.query !== null && typeof req.query === 'object') { | ||
persistent[addresses.HTTP_INCOMING_QUERY] = req.query | ||
@@ -231,7 +233,6 @@ } | ||
const responseAnalyzedSet = new WeakSet() | ||
const responseBlockedSet = new WeakSet() | ||
function onResponseWriteHead ({ req, res, abortController, statusCode, responseHeaders }) { | ||
// avoid "write after end" error | ||
if (responseBlockedSet.has(res)) { | ||
if (isBlocked(res)) { | ||
abortController?.abort() | ||
@@ -264,2 +265,8 @@ return | ||
function onResponseSetHeader ({ res, abortController }) { | ||
if (isBlocked(res)) { | ||
abortController?.abort() | ||
} | ||
} | ||
function handleResults (actions, req, res, rootSpan, abortController) { | ||
@@ -271,5 +278,2 @@ if (!actions || !req || !res || !rootSpan || !abortController) return | ||
block(req, res, rootSpan, abortController, blockingAction) | ||
if (!abortController.signal || abortController.signal.aborted) { | ||
responseBlockedSet.add(res) | ||
} | ||
} | ||
@@ -301,2 +305,3 @@ } | ||
if (responseWriteHead.hasSubscribers) responseWriteHead.unsubscribe(onResponseWriteHead) | ||
if (responseSetHeader.hasSubscribers) responseSetHeader.unsubscribe(onResponseSetHeader) | ||
} | ||
@@ -303,0 +308,0 @@ |
@@ -16,3 +16,3 @@ 'use strict' | ||
if (tags && typeof tags === 'object') { | ||
if (tags !== null && typeof tags === 'object') { | ||
called = Object.entries(tags).some(([key, value]) => regexSdkEvent.test(key) && value === 'true') | ||
@@ -19,0 +19,0 @@ } |
@@ -6,5 +6,7 @@ 'use strict' | ||
const addresses = require('./addresses') | ||
const { httpClientRequestStart } = require('./channels') | ||
const { httpClientRequestStart, setUncaughtExceptionCaptureCallbackStart } = require('./channels') | ||
const { reportStackTrace } = require('./stack_trace') | ||
const waf = require('./waf') | ||
const { getBlockingAction, block } = require('./blocking') | ||
const log = require('../log') | ||
@@ -15,7 +17,98 @@ const RULE_TYPES = { | ||
let config | ||
class DatadogRaspAbortError extends Error { | ||
constructor (req, res, blockingAction) { | ||
super('DatadogRaspAbortError') | ||
this.name = 'DatadogRaspAbortError' | ||
this.req = req | ||
this.res = res | ||
this.blockingAction = blockingAction | ||
} | ||
} | ||
let config, abortOnUncaughtException | ||
function removeAllListeners (emitter, event) { | ||
const listeners = emitter.listeners(event) | ||
emitter.removeAllListeners(event) | ||
let cleaned = false | ||
return function () { | ||
if (cleaned === true) { | ||
return | ||
} | ||
cleaned = true | ||
for (let i = 0; i < listeners.length; ++i) { | ||
emitter.on(event, listeners[i]) | ||
} | ||
} | ||
} | ||
function findDatadogRaspAbortError (err, deep = 10) { | ||
if (err instanceof DatadogRaspAbortError) { | ||
return err | ||
} | ||
if (err.cause && deep > 0) { | ||
return findDatadogRaspAbortError(err.cause, deep - 1) | ||
} | ||
} | ||
function handleUncaughtExceptionMonitor (err) { | ||
const abortError = findDatadogRaspAbortError(err) | ||
if (!abortError) return | ||
const { req, res, blockingAction } = abortError | ||
block(req, res, web.root(req), null, blockingAction) | ||
if (!process.hasUncaughtExceptionCaptureCallback()) { | ||
const cleanUp = removeAllListeners(process, 'uncaughtException') | ||
const handler = () => { | ||
process.removeListener('uncaughtException', handler) | ||
} | ||
setTimeout(() => { | ||
process.removeListener('uncaughtException', handler) | ||
cleanUp() | ||
}) | ||
process.on('uncaughtException', handler) | ||
} else { | ||
// uncaughtException event is not executed when hasUncaughtExceptionCaptureCallback is true | ||
let previousCb | ||
const cb = ({ currentCallback, abortController }) => { | ||
setUncaughtExceptionCaptureCallbackStart.unsubscribe(cb) | ||
if (!currentCallback) { | ||
abortController.abort() | ||
return | ||
} | ||
previousCb = currentCallback | ||
} | ||
setUncaughtExceptionCaptureCallbackStart.subscribe(cb) | ||
process.setUncaughtExceptionCaptureCallback(null) | ||
// For some reason, previous callback was defined before the instrumentation | ||
// We can not restore it, so we let the app decide | ||
if (previousCb) { | ||
process.setUncaughtExceptionCaptureCallback(() => { | ||
process.setUncaughtExceptionCaptureCallback(null) | ||
process.setUncaughtExceptionCaptureCallback(previousCb) | ||
}) | ||
} | ||
} | ||
} | ||
function enable (_config) { | ||
config = _config | ||
httpClientRequestStart.subscribe(analyzeSsrf) | ||
process.on('uncaughtExceptionMonitor', handleUncaughtExceptionMonitor) | ||
abortOnUncaughtException = process.execArgv?.includes('--abort-on-uncaught-exception') | ||
if (abortOnUncaughtException) { | ||
log.warn('The --abort-on-uncaught-exception flag is enabled. The RASP module will not block operations.') | ||
} | ||
} | ||
@@ -25,2 +118,4 @@ | ||
if (httpClientRequestStart.hasSubscribers) httpClientRequestStart.unsubscribe(analyzeSsrf) | ||
process.off('uncaughtExceptionMonitor', handleUncaughtExceptionMonitor) | ||
} | ||
@@ -38,6 +133,7 @@ | ||
} | ||
// TODO: Currently this is only monitoring, we should | ||
// block the request if SSRF attempt | ||
const result = waf.run({ persistent }, req, RULE_TYPES.SSRF) | ||
handleResult(result, req) | ||
const res = store?.res | ||
handleResult(result, req, res, ctx.abortController) | ||
} | ||
@@ -49,3 +145,3 @@ | ||
function handleResult (actions, req) { | ||
function handleResult (actions, req, res, abortController) { | ||
const generateStackTraceAction = getGenerateStackTraceAction(actions) | ||
@@ -61,2 +157,19 @@ if (generateStackTraceAction && config.appsec.stackTrace.enabled) { | ||
} | ||
if (!abortController || abortOnUncaughtException) return | ||
const blockingAction = getBlockingAction(actions) | ||
if (blockingAction) { | ||
const rootSpan = web.root(req) | ||
// Should block only in express | ||
if (rootSpan?.context()._name === 'express.request') { | ||
const abortError = new DatadogRaspAbortError(req, res, blockingAction) | ||
abortController.abort(abortError) | ||
// TODO Delete this when support for node 16 is removed | ||
if (!abortController.signal.reason) { | ||
abortController.signal.reason = abortError | ||
} | ||
} | ||
} | ||
} | ||
@@ -67,3 +180,4 @@ | ||
disable, | ||
handleResult | ||
handleResult, | ||
handleUncaughtExceptionMonitor // exported only for testing purpose | ||
} |
@@ -210,6 +210,2 @@ 'use strict' | ||
const mandatoryTags = filterHeaders(req.headers, REQUEST_HEADERS_MAP) | ||
const ua = mandatoryTags['http.request.headers.user-agent'] | ||
if (ua) { | ||
mandatoryTags['http.useragent'] = ua | ||
} | ||
rootSpan.addTags(mandatoryTags) | ||
@@ -216,0 +212,0 @@ |
@@ -28,3 +28,3 @@ 'use strict' | ||
if (persistent && typeof persistent === 'object') { | ||
if (persistent !== null && typeof persistent === 'object') { | ||
// TODO: possible optimization: only send params that haven't already been sent with same value to this wafContext | ||
@@ -31,0 +31,0 @@ for (const key of Object.keys(persistent)) { |
@@ -32,10 +32,10 @@ 'use strict' | ||
const tags = [] | ||
const ddVarPrefix = 'config.datadog:' | ||
const otelVarPrefix = 'config.opentelemetry:' | ||
const ddVarPrefix = 'config_datadog:' | ||
const otelVarPrefix = 'config_opentelemetry:' | ||
if (ddVar) { | ||
ddVar = ddVarPrefix + ddVar | ||
ddVar = ddVarPrefix + ddVar.toLowerCase() | ||
tags.push(ddVar) | ||
} | ||
if (otelVar) { | ||
otelVar = otelVarPrefix + otelVar | ||
otelVar = otelVarPrefix + otelVar.toLowerCase() | ||
tags.push(otelVar) | ||
@@ -138,3 +138,3 @@ } | ||
// eslint-disable-next-line max-len | ||
const defaultWafObfuscatorKeyRegex = '(?i)(?:p(?:ass)?w(?:or)?d|pass(?:[_-]?phrase)?|secret(?:[_-]?key)?|(?:(?:api|private|public|access)[_-]?)key)|(?:(?:auth|access|id|refresh)[_-]?)?token|consumer[_-]?(?:id|key|secret)|sign(?:ed|ature)|bearer|authorization|jsessionid|phpsessid|asp\\.net[_-]sessionid|sid|jwt' | ||
const defaultWafObfuscatorKeyRegex = '(?i)pass|pw(?:or)?d|secret|(?:api|private|public|access)[_-]?key|token|consumer[_-]?(?:id|key|secret)|sign(?:ed|ature)|bearer|authorization|jsessionid|phpsessid|asp\\.net[_-]sessionid|sid|jwt' | ||
// eslint-disable-next-line max-len | ||
@@ -190,3 +190,3 @@ const defaultWafObfuscatorValueRegex = '(?i)(?:p(?:ass)?w(?:or)?d|pass(?:[_-]?phrase)?|secret(?:[_-]?key)?|(?:(?:api|private|public|access)[_-]?)key(?:[_-]?id)?|(?:(?:auth|access|id|refresh)[_-]?)?token|consumer[_-]?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?|jsessionid|phpsessid|asp\\.net(?:[_-]|-)sessionid|sid|jwt)(?:\\s*=[^;]|"\\s*:\\s*"[^"]+")|bearer\\s+[a-z0-9\\._\\-]+|token:[a-z0-9]{13}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L][\\w=-]+\\.ey[I-L][\\w=-]+(?:\\.[\\w.+\\/=-]+)?|[\\-]{5}BEGIN[a-z\\s]+PRIVATE\\sKEY[\\-]{5}[^\\-]+[\\-]{5}END[a-z\\s]+PRIVATE\\sKEY|ssh-rsa\\s*[a-z0-9\\/\\.+]{100,}' | ||
// Extract by key if in object-form value | ||
if (typeof option === 'object' && !Array.isArray(option)) { | ||
if (option !== null && typeof option === 'object' && !Array.isArray(option)) { | ||
option = option[key] | ||
@@ -199,3 +199,3 @@ } | ||
// If it's not an array but not undefined there's something wrong with the input | ||
if (typeof option !== 'undefined') { | ||
if (option !== undefined) { | ||
log.warn('Unexpected input for config.tracePropagationStyle') | ||
@@ -208,3 +208,3 @@ } | ||
const envVar = coalesce(process.env[envKey], process.env.DD_TRACE_PROPAGATION_STYLE, process.env.OTEL_PROPAGATORS) | ||
if (typeof envVar !== 'undefined') { | ||
if (envVar !== undefined) { | ||
return envVar.split(',') | ||
@@ -219,30 +219,20 @@ .filter(v => v !== '') | ||
class Config { | ||
constructor (options) { | ||
options = options || {} | ||
options = this.options = { | ||
constructor (options = {}) { | ||
options = { | ||
...options, | ||
appsec: options.appsec != null ? options.appsec : options.experimental?.appsec, | ||
iastOptions: options.experimental?.iast | ||
iast: options.iast != null ? options.iast : options.experimental?.iast | ||
} | ||
checkIfBothOtelAndDdEnvVarSet() | ||
// Configure the logger first so it can be used to warn about other configs | ||
this.debug = isTrue(coalesce( | ||
process.env.DD_TRACE_DEBUG, | ||
process.env.OTEL_LOG_LEVEL && process.env.OTEL_LOG_LEVEL === 'debug', | ||
false | ||
)) | ||
this.logger = options.logger | ||
const logConfig = log.getConfig() | ||
this.debug = logConfig.enabled | ||
this.logger = coalesce(options.logger, logConfig.logger) | ||
this.logLevel = coalesce(options.logLevel, logConfig.logLevel) | ||
this.logLevel = coalesce( | ||
options.logLevel, | ||
process.env.DD_TRACE_LOG_LEVEL, | ||
process.env.OTEL_LOG_LEVEL, | ||
'debug' | ||
) | ||
log.use(this.logger) | ||
log.toggle(this.debug, this.logLevel, this) | ||
log.toggle(this.debug, this.logLevel) | ||
checkIfBothOtelAndDdEnvVarSet() | ||
const DD_TRACE_MEMCACHED_COMMAND_ENABLED = coalesce( | ||
@@ -495,3 +485,3 @@ process.env.DD_TRACE_MEMCACHED_COMMAND_ENABLED, | ||
const defaults = this._defaults = {} | ||
const defaults = setHiddenProperty(this, '_defaults', {}) | ||
@@ -503,3 +493,3 @@ this._setValue(defaults, 'appsec.blockedTemplateHtml', undefined) | ||
this._setValue(defaults, 'appsec.obfuscatorValueRegex', defaultWafObfuscatorValueRegex) | ||
this._setValue(defaults, 'appsec.rasp.enabled', false) | ||
this._setValue(defaults, 'appsec.rasp.enabled', true) | ||
this._setValue(defaults, 'appsec.rateLimit', 100) | ||
@@ -682,4 +672,4 @@ this._setValue(defaults, 'appsec.rules', undefined) | ||
const tags = {} | ||
const env = this._env = {} | ||
this._envUnprocessed = {} | ||
const env = setHiddenProperty(this, '_env', {}) | ||
setHiddenProperty(this, '_envUnprocessed', {}) | ||
@@ -824,7 +814,7 @@ tagger.add(tags, OTEL_RESOURCE_ATTRIBUTES, true) | ||
_applyOptions (options) { | ||
const opts = this._options = this._options || {} | ||
const opts = setHiddenProperty(this, '_options', this._options || {}) | ||
const tags = {} | ||
this._optsUnprocessed = {} | ||
setHiddenProperty(this, '_optsUnprocessed', {}) | ||
options = this.options = Object.assign({ ingestion: {} }, options, opts) | ||
options = setHiddenProperty(this, '_optionsArg', Object.assign({ ingestion: {} }, options, opts)) | ||
@@ -871,19 +861,19 @@ tagger.add(tags, options.tags) | ||
this._setString(opts, 'hostname', options.hostname) | ||
this._setBoolean(opts, 'iast.deduplicationEnabled', options.iastOptions && options.iastOptions.deduplicationEnabled) | ||
this._setBoolean(opts, 'iast.deduplicationEnabled', options.iast && options.iast.deduplicationEnabled) | ||
this._setBoolean(opts, 'iast.enabled', | ||
options.iastOptions && (options.iastOptions === true || options.iastOptions.enabled === true)) | ||
options.iast && (options.iast === true || options.iast.enabled === true)) | ||
this._setValue(opts, 'iast.maxConcurrentRequests', | ||
maybeInt(options.iastOptions?.maxConcurrentRequests)) | ||
this._optsUnprocessed['iast.maxConcurrentRequests'] = options.iastOptions?.maxConcurrentRequests | ||
this._setValue(opts, 'iast.maxContextOperations', maybeInt(options.iastOptions?.maxContextOperations)) | ||
this._optsUnprocessed['iast.maxContextOperations'] = options.iastOptions?.maxContextOperations | ||
this._setBoolean(opts, 'iast.redactionEnabled', options.iastOptions?.redactionEnabled) | ||
this._setString(opts, 'iast.redactionNamePattern', options.iastOptions?.redactionNamePattern) | ||
this._setString(opts, 'iast.redactionValuePattern', options.iastOptions?.redactionValuePattern) | ||
const iastRequestSampling = maybeInt(options.iastOptions?.requestSampling) | ||
maybeInt(options.iast?.maxConcurrentRequests)) | ||
this._optsUnprocessed['iast.maxConcurrentRequests'] = options.iast?.maxConcurrentRequests | ||
this._setValue(opts, 'iast.maxContextOperations', maybeInt(options.iast?.maxContextOperations)) | ||
this._optsUnprocessed['iast.maxContextOperations'] = options.iast?.maxContextOperations | ||
this._setBoolean(opts, 'iast.redactionEnabled', options.iast?.redactionEnabled) | ||
this._setString(opts, 'iast.redactionNamePattern', options.iast?.redactionNamePattern) | ||
this._setString(opts, 'iast.redactionValuePattern', options.iast?.redactionValuePattern) | ||
const iastRequestSampling = maybeInt(options.iast?.requestSampling) | ||
if (iastRequestSampling > -1 && iastRequestSampling < 101) { | ||
this._setValue(opts, 'iast.requestSampling', iastRequestSampling) | ||
this._optsUnprocessed['iast.requestSampling'] = options.iastOptions?.requestSampling | ||
this._optsUnprocessed['iast.requestSampling'] = options.iast?.requestSampling | ||
} | ||
this._setString(opts, 'iast.telemetryVerbosity', options.iastOptions && options.iastOptions.telemetryVerbosity) | ||
this._setString(opts, 'iast.telemetryVerbosity', options.iast && options.iast.telemetryVerbosity) | ||
this._setBoolean(opts, 'isCiVisibility', options.isCiVisibility) | ||
@@ -918,3 +908,3 @@ this._setBoolean(opts, 'logInjection', options.logInjection) | ||
const hasTelemetryLogsUsingFeatures = | ||
(options.iastOptions && (options.iastOptions === true || options.iastOptions?.enabled === true)) || | ||
(options.iast && (options.iast === true || options.iast?.enabled === true)) || | ||
(options.profiling && options.profiling === true) | ||
@@ -929,3 +919,3 @@ this._setBoolean(opts, 'telemetry.logCollection', hasTelemetryLogsUsingFeatures) | ||
return coalesce( | ||
this.options.isCiVisibility, | ||
this._optionsArg.isCiVisibility, | ||
this._defaults.isCiVisibility | ||
@@ -946,5 +936,5 @@ ) | ||
? new URL(DD_CIVISIBILITY_AGENTLESS_URL) | ||
: getAgentUrl(this._getTraceAgentUrl(), this.options) | ||
: getAgentUrl(this._getTraceAgentUrl(), this._optionsArg) | ||
const DD_AGENT_HOST = coalesce( | ||
this.options.hostname, | ||
this._optionsArg.hostname, | ||
process.env.DD_AGENT_HOST, | ||
@@ -960,3 +950,3 @@ process.env.DD_TRACE_AGENT_HOSTNAME, | ||
coalesce( | ||
this.options.spanAttributeSchema, | ||
this._optionsArg.spanAttributeSchema, | ||
process.env.DD_TRACE_SPAN_ATTRIBUTE_SCHEMA | ||
@@ -967,7 +957,7 @@ ) | ||
const peerServiceSet = ( | ||
this.options.hasOwnProperty('spanComputePeerService') || | ||
this._optionsArg.hasOwnProperty('spanComputePeerService') || | ||
process.env.hasOwnProperty('DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED') | ||
) | ||
const peerServiceValue = coalesce( | ||
this.options.spanComputePeerService, | ||
this._optionsArg.spanComputePeerService, | ||
process.env.DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED | ||
@@ -1003,3 +993,3 @@ ) | ||
return coalesce( | ||
this.options.stats, | ||
this._optionsArg.stats, | ||
process.env.DD_TRACE_STATS_COMPUTATION_ENABLED, | ||
@@ -1012,3 +1002,3 @@ getIsGCPFunction() || getIsAzureFunction() | ||
return coalesce( | ||
this.options.url, | ||
this._optionsArg.url, | ||
process.env.DD_TRACE_AGENT_URL, | ||
@@ -1022,3 +1012,3 @@ process.env.DD_TRACE_URL, | ||
_applyCalculated () { | ||
const calc = this._calculated = {} | ||
const calc = setHiddenProperty(this, '_calculated', {}) | ||
@@ -1032,3 +1022,3 @@ const { | ||
} else { | ||
this._setValue(calc, 'url', getAgentUrl(this._getTraceAgentUrl(), this.options)) | ||
this._setValue(calc, 'url', getAgentUrl(this._getTraceAgentUrl(), this._optionsArg)) | ||
} | ||
@@ -1049,4 +1039,4 @@ if (this._isCiVisibility()) { | ||
_applyRemote (options) { | ||
const opts = this._remote = this._remote || {} | ||
this._remoteUnprocessed = {} | ||
const opts = setHiddenProperty(this, '_remote', this._remote || {}) | ||
setHiddenProperty(this, '_remoteUnprocessed', {}) | ||
const tags = {} | ||
@@ -1067,11 +1057,14 @@ const headerTags = options.tracing_header_tags | ||
this._setBoolean(opts, 'tracing', options.tracing_enabled) | ||
// ignore tags for now since rc sampling rule tags format is not supported | ||
this._setSamplingRule(opts, 'sampler.rules', this._ignoreTags(options.tracing_sampling_rules)) | ||
this._remoteUnprocessed['sampler.rules'] = options.tracing_sampling_rules | ||
this._setSamplingRule(opts, 'sampler.rules', this._reformatTags(options.tracing_sampling_rules)) | ||
} | ||
_ignoreTags (samplingRules) { | ||
if (samplingRules) { | ||
for (const rule of samplingRules) { | ||
delete rule.tags | ||
_reformatTags (samplingRules) { | ||
for (const rule of (samplingRules || [])) { | ||
const reformattedTags = {} | ||
if (rule.tags) { | ||
for (const tag of (rule.tags || {})) { | ||
reformattedTags[tag.key] = tag.value_glob | ||
} | ||
rule.tags = reformattedTags | ||
} | ||
@@ -1218,2 +1211,11 @@ } | ||
function setHiddenProperty (obj, name, value) { | ||
Object.defineProperty(obj, name, { | ||
value, | ||
enumerable: false, | ||
writable: true | ||
}) | ||
return obj[name] | ||
} | ||
module.exports = Config |
@@ -20,7 +20,9 @@ // encoding used here is sha256 | ||
function computeHash (service, env, edgeTags, parentHash) { | ||
const key = `${service}${env}` + edgeTags.join('') + parentHash.toString() | ||
const hashableEdgeTags = edgeTags.filter(item => item !== 'manual_checkpoint:true') | ||
const key = `${service}${env}` + hashableEdgeTags.join('') + parentHash.toString() | ||
if (cache.get(key)) { | ||
return cache.get(key) | ||
} | ||
const currentHash = shaHash(`${service}${env}` + edgeTags.join('')) | ||
const currentHash = shaHash(`${service}${env}` + hashableEdgeTags.join('')) | ||
const buf = Buffer.concat([currentHash, parentHash], 16) | ||
@@ -27,0 +29,0 @@ const val = shaHash(buf.toString()) |
@@ -135,3 +135,3 @@ const os = require('os') | ||
} | ||
if (typeof obj === 'object') { | ||
if (obj !== null && typeof obj === 'object') { | ||
try { | ||
@@ -215,3 +215,4 @@ return getHeadersSize(obj) | ||
Version: this.version, | ||
Lang: 'javascript' | ||
Lang: 'javascript', | ||
Tags: Object.entries(this.tags).map(([key, value]) => `${key}:${value}`) | ||
} | ||
@@ -218,0 +219,0 @@ this.writer.flush(payload) |
'use strict' | ||
const coalesce = require('koalas') | ||
const { isTrue } = require('../util') | ||
const { debugChannel, infoChannel, warnChannel, errorChannel } = require('./channels') | ||
@@ -23,4 +25,18 @@ const logWriter = require('./writer') | ||
const config = { | ||
enabled: false, | ||
logger: undefined, | ||
logLevel: 'debug' | ||
} | ||
const log = { | ||
/** | ||
* @returns Read-only version of logging config. To modify config, call `log.use` and `log.toggle` | ||
*/ | ||
getConfig () { | ||
return { ...config } | ||
}, | ||
use (logger) { | ||
config.logger = logger | ||
logWriter.use(logger) | ||
@@ -31,2 +47,4 @@ return this | ||
toggle (enabled, logLevel) { | ||
config.enabled = enabled | ||
config.logLevel = logLevel | ||
logWriter.toggle(enabled, logLevel) | ||
@@ -81,2 +99,16 @@ return this | ||
const enabled = isTrue(coalesce( | ||
process.env.DD_TRACE_DEBUG, | ||
process.env.OTEL_LOG_LEVEL === 'debug', | ||
config.enabled | ||
)) | ||
const logLevel = coalesce( | ||
process.env.DD_TRACE_LOG_LEVEL, | ||
process.env.OTEL_LOG_LEVEL, | ||
config.logLevel | ||
) | ||
log.toggle(enabled, logLevel) | ||
module.exports = log |
@@ -5,2 +5,3 @@ 'use strict' | ||
const { trace, ROOT_CONTEXT } = require('@opentelemetry/api') | ||
const DataDogSpanContext = require('../opentracing/span_context') | ||
@@ -10,31 +11,2 @@ const SpanContext = require('./span_context') | ||
// Horrible hack to acquire the otherwise inaccessible SPAN_KEY so we can redirect it... | ||
// This is used for getting the current span context in OpenTelemetry, but the SPAN_KEY value is | ||
// not exposed as it's meant to be read-only from outside the module. We want to hijack this logic | ||
// so we can instead get the span context from the datadog context manager instead. | ||
let SPAN_KEY | ||
trace.getSpan({ | ||
getValue (key) { | ||
SPAN_KEY = key | ||
} | ||
}) | ||
// Whenever a value is acquired from the context map we should mostly delegate to the real getter, | ||
// but when accessing the current span we should hijack that access to instead provide a fake span | ||
// which we can use to get an OTel span context wrapping the datadog active scope span context. | ||
function wrappedGetValue (target) { | ||
return (key) => { | ||
if (key === SPAN_KEY) { | ||
return { | ||
spanContext () { | ||
const activeSpan = tracer.scope().active() | ||
const context = activeSpan && activeSpan.context() | ||
return new SpanContext(context) | ||
} | ||
} | ||
} | ||
return target.getValue(key) | ||
} | ||
} | ||
class ContextManager { | ||
@@ -46,9 +18,18 @@ constructor () { | ||
active () { | ||
const active = this._store.getStore() || ROOT_CONTEXT | ||
const activeSpan = tracer.scope().active() | ||
const store = this._store.getStore() | ||
const context = (activeSpan && activeSpan.context()) || store || ROOT_CONTEXT | ||
return new Proxy(active, { | ||
get (target, key) { | ||
return key === 'getValue' ? wrappedGetValue(target) : target[key] | ||
} | ||
}) | ||
if (!(context instanceof DataDogSpanContext)) { | ||
return context | ||
} | ||
if (!context._otelSpanContext) { | ||
const newSpanContext = new SpanContext(context) | ||
context._otelSpanContext = newSpanContext | ||
} | ||
if (store && trace.getSpanContext(store) === context._otelSpanContext) { | ||
return store | ||
} | ||
return trace.setSpanContext(store || ROOT_CONTEXT, context._otelSpanContext) | ||
} | ||
@@ -59,6 +40,10 @@ | ||
const ddScope = tracer.scope() | ||
return ddScope.activate(span._ddSpan, () => { | ||
const run = () => { | ||
const cb = thisArg == null ? fn : fn.bind(thisArg) | ||
return this._store.run(context, cb, ...args) | ||
}) | ||
} | ||
if (span && span._ddSpan) { | ||
return ddScope.activate(span._ddSpan, run) | ||
} | ||
return run() | ||
} | ||
@@ -73,7 +58,5 @@ | ||
// Not part of the spec but the Node.js API expects these | ||
enable () {} | ||
disable () {} | ||
} | ||
module.exports = ContextManager |
@@ -27,7 +27,7 @@ 'use strict' | ||
get traceId () { | ||
return this._ddContext._traceId.toString(16) | ||
return this._ddContext.toTraceId(true) | ||
} | ||
get spanId () { | ||
return this._ddContext._spanId.toString(16) | ||
return this._ddContext.toSpanId(true) | ||
} | ||
@@ -34,0 +34,0 @@ |
@@ -286,3 +286,3 @@ 'use strict' | ||
get ended () { | ||
return typeof this.duration !== 'undefined' | ||
return this.duration !== undefined | ||
} | ||
@@ -289,0 +289,0 @@ } |
'use strict' | ||
const { trace, context } = require('@opentelemetry/api') | ||
const { trace, context, propagation } = require('@opentelemetry/api') | ||
const { W3CTraceContextPropagator } = require('@opentelemetry/core') | ||
@@ -55,2 +56,9 @@ const tracer = require('../../') | ||
} | ||
// The default propagator used is the W3C Trace Context propagator, users should be able to pass in others | ||
// as needed | ||
if (config.propagator) { | ||
propagation.setGlobalPropagator(config.propagator) | ||
} else { | ||
propagation.setGlobalPropagator(new W3CTraceContextPropagator()) | ||
} | ||
} | ||
@@ -57,0 +65,0 @@ |
@@ -10,2 +10,3 @@ 'use strict' | ||
const SpanContext = require('./span_context') | ||
const TextMapPropagator = require('../opentracing/propagation/text_map') | ||
@@ -19,2 +20,3 @@ class Tracer { | ||
this.instrumentationLibrary = library | ||
this._spanLimits = {} | ||
} | ||
@@ -26,2 +28,20 @@ | ||
_createSpanContextFromParent (parentSpanContext) { | ||
return new SpanContext({ | ||
traceId: parentSpanContext._traceId, | ||
spanId: id(), | ||
parentId: parentSpanContext._spanId, | ||
sampling: parentSpanContext._sampling, | ||
baggageItems: Object.assign({}, parentSpanContext._baggageItems), | ||
trace: parentSpanContext._trace, | ||
tracestate: parentSpanContext._tracestate | ||
}) | ||
} | ||
// Extracted method to create span context for a new span | ||
_createSpanContextForNewSpan (context) { | ||
const { traceId, spanId, traceFlags, traceState } = context | ||
return TextMapPropagator._convertOtelContextToDatadog(traceId, spanId, traceFlags, traceState) | ||
} | ||
startSpan (name, options = {}, context = api.context.active()) { | ||
@@ -34,17 +54,7 @@ // remove span from context in case a root span is requested via options | ||
const parentSpanContext = parentSpan && parentSpan.spanContext() | ||
let spanContext | ||
// TODO: Need a way to get 128-bit trace IDs for the validity check API to work... | ||
// if (parent && api.trace.isSpanContextValid(parent)) { | ||
if (parentSpanContext && parentSpanContext.traceId) { | ||
const parent = parentSpanContext._ddContext | ||
spanContext = new SpanContext({ | ||
traceId: parent._traceId, | ||
spanId: id(), | ||
parentId: parent._spanId, | ||
sampling: parent._sampling, | ||
baggageItems: Object.assign({}, parent._baggageItems), | ||
trace: parent._trace, | ||
tracestate: parent._tracestate | ||
}) | ||
if (parentSpanContext && api.trace.isSpanContextValid(parentSpanContext)) { | ||
spanContext = parentSpanContext._ddContext | ||
? this._createSpanContextFromParent(parentSpanContext._ddContext) | ||
: this._createSpanContextForNewSpan(parentSpanContext) | ||
} else { | ||
@@ -125,4 +135,9 @@ spanContext = new SpanContext() | ||
} | ||
// not used in our codebase but needed for compatibility. See issue #1244 | ||
getSpanLimits () { | ||
return this._spanLimits | ||
} | ||
} | ||
module.exports = Tracer |
@@ -6,2 +6,3 @@ 'use strict' | ||
const DatadogSpanContext = require('../span_context') | ||
const OtelSpanContext = require('../../opentelemetry/span_context') | ||
const log = require('../../log') | ||
@@ -622,4 +623,63 @@ const TraceState = require('./tracestate') | ||
} | ||
static _convertOtelContextToDatadog (traceId, spanId, traceFlag, ts, meta = {}) { | ||
const origin = null | ||
let samplingPriority = traceFlag | ||
ts = ts?.traceparent || null | ||
if (ts) { | ||
// Use TraceState.fromString to parse the tracestate header | ||
const traceState = TraceState.fromString(ts) | ||
let ddTraceStateData = null | ||
// Extract Datadog specific trace state data | ||
traceState.forVendor('dd', (state) => { | ||
ddTraceStateData = state | ||
return state // You might need to adjust this part based on actual logic needed | ||
}) | ||
if (ddTraceStateData) { | ||
// Assuming ddTraceStateData is now a Map or similar structure containing Datadog trace state data | ||
// Extract values as needed, similar to the original logic | ||
const samplingPriorityTs = ddTraceStateData.get('s') | ||
const origin = ddTraceStateData.get('o') | ||
// Convert Map to object for meta | ||
const otherPropagatedTags = Object.fromEntries(ddTraceStateData.entries()) | ||
// Update meta and samplingPriority based on extracted values | ||
Object.assign(meta, otherPropagatedTags) | ||
samplingPriority = TextMapPropagator._getSamplingPriority(traceFlag, parseInt(samplingPriorityTs, 10), origin) | ||
} else { | ||
log.debug(`no dd list member in tracestate from incoming request: ${ts}`) | ||
} | ||
} | ||
const spanContext = new OtelSpanContext({ | ||
traceId: id(traceId, 16), spanId: id(), tags: meta, parentId: id(spanId, 16) | ||
}) | ||
spanContext._sampling = { priority: samplingPriority } | ||
spanContext._trace = { origin } | ||
return spanContext | ||
} | ||
static _getSamplingPriority (traceparentSampled, tracestateSamplingPriority, origin = null) { | ||
const fromRumWithoutPriority = !tracestateSamplingPriority && origin === 'rum' | ||
let samplingPriority | ||
if (!fromRumWithoutPriority && traceparentSampled === 0 && | ||
(!tracestateSamplingPriority || tracestateSamplingPriority >= 0)) { | ||
samplingPriority = 0 | ||
} else if (!fromRumWithoutPriority && traceparentSampled === 1 && | ||
(!tracestateSamplingPriority || tracestateSamplingPriority < 0)) { | ||
samplingPriority = 1 | ||
} else { | ||
samplingPriority = tracestateSamplingPriority | ||
} | ||
return samplingPriority | ||
} | ||
} | ||
module.exports = TextMapPropagator |
@@ -30,2 +30,3 @@ 'use strict' | ||
} | ||
this._otelSpanContext = undefined | ||
} | ||
@@ -32,0 +33,0 @@ |
@@ -8,2 +8,3 @@ 'use strict' | ||
const TextMapPropagator = require('./propagation/text_map') | ||
const DSMTextMapPropagator = require('./propagation/text_map_dsm') | ||
const HttpPropagator = require('./propagation/http') | ||
@@ -42,3 +43,4 @@ const BinaryPropagator = require('./propagation/binary') | ||
[formats.BINARY]: new BinaryPropagator(config), | ||
[formats.LOG]: new LogPropagator(config) | ||
[formats.LOG]: new LogPropagator(config), | ||
[formats.TEXT_MAP_DSM]: new DSMTextMapPropagator(config) | ||
} | ||
@@ -76,10 +78,12 @@ if (config.reportHostname) { | ||
inject (spanContext, format, carrier) { | ||
if (spanContext instanceof Span) { | ||
spanContext = spanContext.context() | ||
inject (context, format, carrier) { | ||
if (context instanceof Span) { | ||
context = context.context() | ||
} | ||
try { | ||
this._prioritySampler.sample(spanContext) | ||
this._propagators[format].inject(spanContext, carrier) | ||
if (format !== 'text_map_dsm') { | ||
this._prioritySampler.sample(context) | ||
} | ||
this._propagators[format].inject(context, carrier) | ||
} catch (e) { | ||
@@ -86,0 +90,0 @@ log.error(e) |
@@ -19,3 +19,4 @@ const { | ||
TEST_SKIPPED_BY_ITR, | ||
ITR_CORRELATION_ID | ||
ITR_CORRELATION_ID, | ||
TEST_SOURCE_FILE | ||
} = require('./util/test') | ||
@@ -211,3 +212,9 @@ const Plugin = require('./plugin') | ||
const codeOwners = getCodeOwnersForFilename(testSuite, this.codeOwnersEntries) | ||
const { [TEST_SOURCE_FILE]: testSourceFile } = extraTags | ||
// We'll try with the test source file if available (it could be different from the test suite) | ||
let codeOwners = getCodeOwnersForFilename(testSourceFile, this.codeOwnersEntries) | ||
if (!codeOwners) { | ||
codeOwners = getCodeOwnersForFilename(testSuite, this.codeOwnersEntries) | ||
} | ||
if (codeOwners) { | ||
@@ -214,0 +221,0 @@ testTags[TEST_CODE_OWNERS] = codeOwners |
@@ -6,2 +6,3 @@ 'use strict' | ||
const dc = require('dc-polyfill') | ||
const logger = require('../log') | ||
const { storage } = require('../../../datadog-core') | ||
@@ -76,3 +77,13 @@ | ||
addSub (channelName, handler) { | ||
this._subscriptions.push(new Subscription(channelName, handler)) | ||
const plugin = this | ||
const wrappedHandler = function () { | ||
try { | ||
return handler.apply(this, arguments) | ||
} catch (e) { | ||
logger.error('Error in plugin handler:', e) | ||
logger.info('Disabling plugin:', plugin.id) | ||
plugin.configure(false) | ||
} | ||
} | ||
this._subscriptions.push(new Subscription(channelName, wrappedHandler)) | ||
} | ||
@@ -79,0 +90,0 @@ |
@@ -98,2 +98,5 @@ const path = require('path') | ||
// Flaky test retries | ||
const NUM_FAILED_TEST_RETRIES = 5 | ||
module.exports = { | ||
@@ -171,3 +174,4 @@ TEST_CODE_OWNERS, | ||
TEST_BROWSER_NAME, | ||
TEST_BROWSER_VERSION | ||
TEST_BROWSER_VERSION, | ||
NUM_FAILED_TEST_RETRIES | ||
} | ||
@@ -174,0 +178,0 @@ |
@@ -72,3 +72,3 @@ 'use strict' | ||
mapper = await maybeSourceMap(config.sourceMap, SourceMapper, config.debugSourceMaps) | ||
if (config.SourceMap && config.debugSourceMaps) { | ||
if (config.sourceMap && config.debugSourceMaps) { | ||
this._logger.debug(() => { | ||
@@ -75,0 +75,0 @@ return mapper.infoMap.size === 0 |
@@ -184,2 +184,3 @@ 'use strict' | ||
this._tracer = new DatadogTracer(config, prioritySampler) | ||
this.dataStreamsCheckpointer = this._tracer.dataStreamsCheckpointer | ||
this.appsec = new AppsecSdk(this._tracer, config) | ||
@@ -186,0 +187,0 @@ this._tracingInitialized = true |
@@ -14,2 +14,3 @@ 'use strict' | ||
const DataStreamsContext = require('./data_streams_context') | ||
const { DataStreamsCheckpointer } = require('./data_streams') | ||
const { flushStartupLogs } = require('../../datadog-instrumentations/src/check_require_cache') | ||
@@ -27,2 +28,3 @@ const log = require('./log/writer') | ||
this._dataStreamsProcessor = new DataStreamsProcessor(config) | ||
this.dataStreamsCheckpointer = new DataStreamsCheckpointer(this) | ||
this._scope = new Scope() | ||
@@ -29,0 +31,0 @@ setStartupLogConfig(config) |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1901487
35
556
54890
+ Added@datadog/native-iast-rewriter@2.4.0(transitive)
+ Added@datadog/native-iast-taint-tracking@3.1.0(transitive)
- Removed@datadog/native-iast-rewriter@2.3.1(transitive)
- Removed@datadog/native-iast-taint-tracking@3.0.0(transitive)