diff --git a/.github/workflows/serverless.yml b/.github/workflows/serverless.yml index 82068f21d7b..4b6f7286a74 100644 --- a/.github/workflows/serverless.yml +++ b/.github/workflows/serverless.yml @@ -102,8 +102,7 @@ jobs: with: version: ${{ matrix.node-version }} - uses: ./.github/actions/install - # TODO: Remove :flaky once flakiness is better - - run: yarn test:plugins:ci:flaky + - run: yarn test:plugins:ci - if: always() uses: ./.github/actions/testagent/logs with: diff --git a/integration-tests/appsec/iast-esbuild.spec.js b/integration-tests/appsec/iast-esbuild.spec.js index 1c82210a1e5..71e8de09c80 100644 --- a/integration-tests/appsec/iast-esbuild.spec.js +++ b/integration-tests/appsec/iast-esbuild.spec.js @@ -26,7 +26,7 @@ describe('esbuild support for IAST', () => { const craftedNodeModulesDir = path.join(applicationDir, 'tmp_node_modules') fs.mkdirSync(craftedNodeModulesDir) await exec('npm init -y', { cwd: craftedNodeModulesDir }) - await exec('npm install @datadog/native-iast-rewriter @datadog/native-iast-taint-tracking', { + await exec('npm install @datadog/wasm-js-rewriter @datadog/native-iast-taint-tracking', { cwd: craftedNodeModulesDir, timeout: 3e3 }) diff --git a/integration-tests/appsec/iast-esbuild/esbuild.common-config.js b/integration-tests/appsec/iast-esbuild/esbuild.common-config.js index 70b9c49924f..c7f8fc23e6b 100644 --- a/integration-tests/appsec/iast-esbuild/esbuild.common-config.js +++ b/integration-tests/appsec/iast-esbuild/esbuild.common-config.js @@ -11,7 +11,7 @@ module.exports = { target: ['node18'], external: [ '@datadog/native-iast-taint-tracking', - '@datadog/native-iast-rewriter', + '@datadog/wasm-js-rewriter', // required if you encounter graphql errors during the build step // see https://docs.datadoghq.com/tracing/trace_collection/automatic_instrumentation/dd_libraries/nodejs/#bundling diff --git a/integration-tests/cypress/cypress.spec.js b/integration-tests/cypress/cypress.spec.js index 987d8d41e99..660d736b842 100644 --- a/integration-tests/cypress/cypress.spec.js +++ b/integration-tests/cypress/cypress.spec.js @@ -2414,19 +2414,26 @@ moduleTypes.forEach(({ ...restEnvVars } = getCiVisEvpProxyConfig(receiver.port) + const specToRun = 'cypress/e2e/spec.cy.js' + childProcess = exec( - testCommand, + version === 'latest' ? testCommand : `${testCommand} --spec ${specToRun}`, { cwd, env: { ...restEnvVars, CYPRESS_BASE_URL: `http://localhost:${webAppPort}`, - DD_TEST_SESSION_NAME: 'my-test-session-name' + DD_TEST_SESSION_NAME: 'my-test-session-name', + SPEC_PATTERN: specToRun, }, stdio: 'pipe' } ) + // TODO: remove this once we have figured out flakiness + childProcess.stdout.pipe(process.stdout) + childProcess.stderr.pipe(process.stderr) + await Promise.all([ once(childProcess, 'exit'), receiverPromise @@ -2532,7 +2539,7 @@ moduleTypes.forEach(({ NUM_RETRIES_EFD ) } - }) + }, 25000) const runImpactedTest = async ( { isModified, isEfd = false, isNew = false }, @@ -2562,6 +2569,10 @@ moduleTypes.forEach(({ } ) + // TODO: remove this once we have figured out flakiness + childProcess.stdout.pipe(process.stdout) + childProcess.stderr.pipe(process.stderr) + await Promise.all([ once(childProcess, 'exit'), testAssertionsPromise diff --git a/integration-tests/esbuild/package.json b/integration-tests/esbuild/package.json index edf33a44c16..80b0352ca4e 100644 --- a/integration-tests/esbuild/package.json +++ b/integration-tests/esbuild/package.json @@ -28,6 +28,6 @@ "express": "4.21.2", "knex": "3.1.0", "koa": "3.0.3", - "openai": "6.5.0" + "openai": "6.6.0" } } diff --git a/integration-tests/helpers/fake-agent.js b/integration-tests/helpers/fake-agent.js index b73e851fc18..eaf1466e9a0 100644 --- a/integration-tests/helpers/fake-agent.js +++ b/integration-tests/helpers/fake-agent.js @@ -123,7 +123,7 @@ module.exports = class FakeAgent extends EventEmitter { // where multiple payloads are generated, and only one is expected to have the proper span (ie next.request), // but it't not guaranteed to be the last one (so, expectedMessageCount would not be helpful). // It can still fail if it takes longer than `timeout` duration or if none pass the assertions (timeout still called) - assertMessageReceived (fn, timeout, expectedMessageCount = 1, resolveAtFirstSuccess) { + assertMessageReceived (fn, timeout, expectedMessageCount = 1, resolveAtFirstSuccess = true) { timeout = timeout || 30000 let resultResolve let resultReject diff --git a/integration-tests/helpers/index.js b/integration-tests/helpers/index.js index de4029a87dc..fd54267a105 100644 --- a/integration-tests/helpers/index.js +++ b/integration-tests/helpers/index.js @@ -544,7 +544,8 @@ async function spawnPluginIntegrationTestProc (cwd, serverFile, agentPort, stdio additionalEnvArgs = additionalEnvArgs || {} let env = /** @type {Record} */ ({ NODE_OPTIONS: `--loader=${hookFile}`, - DD_TRACE_AGENT_PORT: String(agentPort) + DD_TRACE_AGENT_PORT: String(agentPort), + DD_TRACE_FLUSH_INTERVAL: '0' }) env = { ...process.env, ...env, ...additionalEnvArgs } return spawnProc(path.join(cwd, serverFile), { diff --git a/package.json b/package.json index 0cad786507b..e5a84c4ab42 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dd-trace", - "version": "5.73.0", + "version": "5.73.1", "description": "Datadog APM tracing client for JavaScript", "main": "index.js", "typings": "index.d.ts", diff --git a/packages/datadog-core/src/utils/src/set.js b/packages/datadog-core/src/utils/src/set.js index 9a46061efa5..a0301437010 100644 --- a/packages/datadog-core/src/utils/src/set.js +++ b/packages/datadog-core/src/utils/src/set.js @@ -5,7 +5,11 @@ module.exports = function set (object, path, value) { while (true) { const nextIndex = path.indexOf('.', index + 1) if (nextIndex === -1) { - object[path.slice(index + 1)] = value + if (index === -1) { + object[path] = value + } else { + object[path.slice(index + 1)] = value + } return } object = object[path.slice(index + 1, nextIndex)] ??= {} diff --git a/packages/datadog-plugin-aws-sdk/test/dynamodb.spec.js b/packages/datadog-plugin-aws-sdk/test/dynamodb.spec.js index 5f07625e4b0..5ffd22022f1 100644 --- a/packages/datadog-plugin-aws-sdk/test/dynamodb.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/dynamodb.spec.js @@ -27,6 +27,7 @@ async function resetLocalStackDynamo () { describe('Plugin', () => { describe('aws-sdk (dynamodb)', function () { setup() + this.timeout(10000) withVersions('aws-sdk', ['aws-sdk', '@aws-sdk/smithy-client'], (version, moduleName) => { let tracer diff --git a/packages/datadog-plugin-azure-event-hubs/test/integration-test/client.spec.js b/packages/datadog-plugin-azure-event-hubs/test/integration-test/client.spec.js index e76a43e3f24..7169cf7ec2a 100644 --- a/packages/datadog-plugin-azure-event-hubs/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-azure-event-hubs/test/integration-test/client.spec.js @@ -9,6 +9,8 @@ const { const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert, expect } = require('chai') +const spawnEnv = { DD_TRACE_FLUSH_INTERVAL: '2000' } + describe('esm', () => { let agent let proc @@ -16,7 +18,7 @@ describe('esm', () => { withVersions('azure-event-hubs', '@azure/event-hubs', version => { before(async function () { - this.timeout(20000) + this.timeout(60000) sandbox = await createSandbox([`'@azure/event-hubs@${version}'`], false, [ './packages/datadog-plugin-azure-event-hubs/test/integration-test/*']) }) @@ -42,7 +44,7 @@ describe('esm', () => { assert.strictEqual(checkSpansForServiceName(payload, 'azure.eventhubs.send'), true) }) - proc = await spawnPluginIntegrationTestProc(sandbox.folder, 'server.mjs', agent.port) + proc = await spawnPluginIntegrationTestProc(sandbox.folder, 'server.mjs', agent.port, spawnEnv) await res }).timeout(20000) @@ -86,7 +88,7 @@ describe('esm', () => { assert.strictEqual(parseLinks(payload[4][0]).length, 2) }) - proc = await spawnPluginIntegrationTestProc(sandbox.folder, 'server.mjs', agent.port) + proc = await spawnPluginIntegrationTestProc(sandbox.folder, 'server.mjs', agent.port, spawnEnv) await res }).timeout(60000) @@ -94,7 +96,7 @@ describe('esm', () => { const res = agent.assertMessageReceived(({ headers, payload }) => { expect(payload[2][0]).to.not.have.property('_dd.span_links') }) - const envVar = { DD_TRACE_AZURE_EVENTHUBS_BATCH_LINKS_ENABLED: false } + const envVar = { DD_TRACE_AZURE_EVENTHUBS_BATCH_LINKS_ENABLED: false, ...spawnEnv } proc = await spawnPluginIntegrationTestProc(sandbox.folder, 'server.mjs', agent.port, undefined, envVar) await res }).timeout(60000) diff --git a/packages/datadog-plugin-azure-service-bus/test/integration-test/client.spec.js b/packages/datadog-plugin-azure-service-bus/test/integration-test/client.spec.js index bb14f5041ab..e082770f0a3 100644 --- a/packages/datadog-plugin-azure-service-bus/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-azure-service-bus/test/integration-test/client.spec.js @@ -8,6 +8,8 @@ const { const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') +const spawnEnv = { DD_TRACE_FLUSH_INTERVAL: '2000' } + describe('esm', () => { let agent let proc @@ -40,7 +42,7 @@ describe('esm', () => { assert.isArray(payload) }) - proc = await spawnPluginIntegrationTestProc(sandbox.folder, 'server.mjs', agent.port) + proc = await spawnPluginIntegrationTestProc(sandbox.folder, 'server.mjs', agent.port, spawnEnv) await res }).timeout(20000) @@ -151,7 +153,7 @@ describe('esm', () => { assert.strictEqual(parseLinks(payload[22][0]).length, 2) }) - proc = await spawnPluginIntegrationTestProc(sandbox.folder, 'server.mjs', agent.port) + proc = await spawnPluginIntegrationTestProc(sandbox.folder, 'server.mjs', agent.port, spawnEnv) await res }).timeout(60000) diff --git a/packages/datadog-plugin-langchain/test/index.spec.js b/packages/datadog-plugin-langchain/test/index.spec.js index af30a270b91..a8a95188acb 100644 --- a/packages/datadog-plugin-langchain/test/index.spec.js +++ b/packages/datadog-plugin-langchain/test/index.spec.js @@ -10,6 +10,8 @@ const { withVersions } = require('../../dd-trace/test/setup/mocha') const isDdTrace = iastFilter.isDdTrace +const semifies = require('semifies') + describe('Plugin', () => { let langchainOpenai let langchainAnthropic @@ -114,9 +116,15 @@ describe('Plugin', () => { langchainTools = require(`../../../versions/@langchain/core@${version}`) .get('@langchain/core/tools') - MemoryVectorStore = require(`../../../versions/@langchain/core@${version}`) - .get('langchain/vectorstores/memory') - .MemoryVectorStore + if (semifies(realVersion, '>=1.0')) { + MemoryVectorStore = require('../../../versions/@langchain/classic@>=1.0') + .get('@langchain/classic/vectorstores/memory') + .MemoryVectorStore + } else { + MemoryVectorStore = require(`../../../versions/langchain@${version}`) + .get('langchain/vectorstores/memory') + .MemoryVectorStore + } }) describe('llm', () => { diff --git a/packages/datadog-plugin-prisma/test/integration-test/client.spec.js b/packages/datadog-plugin-prisma/test/integration-test/client.spec.js index 0aec82e64e9..d1754fa3d33 100644 --- a/packages/datadog-plugin-prisma/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-prisma/test/integration-test/client.spec.js @@ -72,7 +72,9 @@ describe('esm', () => { // TODO: Integrate the assertions into the spawn command by adding a // callback. It should end the process when the assertions are met. That // way we can remove the Promise.all and the procPromise.then(). - const procPromise = spawnPluginIntegrationTestProc(sandbox.folder, 'server.mjs', agent.port) + const procPromise = spawnPluginIntegrationTestProc(sandbox.folder, 'server.mjs', agent.port, { + DD_TRACE_FLUSH_INTERVAL: '2000' + }) await Promise.all([ procPromise.then((res) => { diff --git a/packages/dd-trace/src/config-helper.js b/packages/dd-trace/src/config-helper.js index 5fbf351b1dd..184ec96c44d 100644 --- a/packages/dd-trace/src/config-helper.js +++ b/packages/dd-trace/src/config-helper.js @@ -37,7 +37,9 @@ for (const deprecation of Object.keys(deprecations)) { module.exports = { /** * Returns the environment variables that are supported by the tracer - * (including all non-Datadog/OTEL specific environment variables) + * (including all non-Datadog/OTEL specific environment variables). + * + * This should only be called once in config.js to avoid copying the object frequently. * * @returns {TracerEnv} The environment variables */ diff --git a/packages/dd-trace/src/config.js b/packages/dd-trace/src/config.js index 7942b630691..e150576b74a 100644 --- a/packages/dd-trace/src/config.js +++ b/packages/dd-trace/src/config.js @@ -16,7 +16,7 @@ const telemetryMetrics = require('./telemetry/metrics') const { isInServerlessEnvironment, getIsGCPFunction, getIsAzureFunction } = require('./serverless') const { ORIGIN_KEY } = require('./constants') const { appendRules } = require('./payload-tagging/config') -const { getEnvironmentVariable, getEnvironmentVariables } = require('./config-helper') +const { getEnvironmentVariable: getEnv, getEnvironmentVariables } = require('./config-helper') const defaults = require('./config_defaults') const path = require('path') @@ -84,10 +84,10 @@ function getFromOtelSamplerMap (otelTracesSampler, otelTracesSamplerArg) { } function validateOtelPropagators (propagators) { - if (!getEnvironmentVariable('PROPAGATION_STYLE_EXTRACT') && - !getEnvironmentVariable('PROPAGATION_STYLE_INJECT') && - !getEnvironmentVariable('DD_TRACE_PROPAGATION_STYLE') && - getEnvironmentVariable('OTEL_PROPAGATORS')) { + if (!getEnv('PROPAGATION_STYLE_EXTRACT') && + !getEnv('PROPAGATION_STYLE_INJECT') && + !getEnv('DD_TRACE_PROPAGATION_STYLE') && + getEnv('OTEL_PROPAGATORS')) { for (const style in propagators) { if (!VALID_PROPAGATION_STYLES.has(style)) { log.warn('unexpected value for OTEL_PROPAGATORS environment variable') @@ -97,39 +97,49 @@ function validateOtelPropagators (propagators) { } } -function validateEnvVarType (envVar) { - const value = getEnvironmentVariable(envVar) +/** + * Validate the type of an environment variable + * @param {string} envVar - The name of the environment variable + * @param {string} [value] - The value of the environment variable + * @returns {boolean} - True if the value is valid, false otherwise + */ +function isInvalidOtelEnvironmentVariable (envVar, value) { + // Skip validation if the value is undefined (it was not set as environment variable) + if (value === undefined) return false + switch (envVar) { case 'OTEL_LOG_LEVEL': - return VALID_LOG_LEVELS.has(value) + return !VALID_LOG_LEVELS.has(value) case 'OTEL_PROPAGATORS': case 'OTEL_RESOURCE_ATTRIBUTES': case 'OTEL_SERVICE_NAME': - return typeof value === 'string' + return typeof value !== 'string' case 'OTEL_TRACES_SAMPLER': - return getFromOtelSamplerMap(value, getEnvironmentVariable('OTEL_TRACES_SAMPLER_ARG')) !== undefined + return getFromOtelSamplerMap(value, getEnv('OTEL_TRACES_SAMPLER_ARG')) === undefined case 'OTEL_TRACES_SAMPLER_ARG': - return !Number.isNaN(Number.parseFloat(value)) + return Number.isNaN(Number.parseFloat(value)) case 'OTEL_SDK_DISABLED': - return value.toLowerCase() === 'true' || value.toLowerCase() === 'false' + return value.toLowerCase() !== 'true' && value.toLowerCase() !== 'false' case 'OTEL_TRACES_EXPORTER': case 'OTEL_METRICS_EXPORTER': case 'OTEL_LOGS_EXPORTER': - return value.toLowerCase() === 'none' + return value.toLowerCase() !== 'none' default: - return false + return true } } function checkIfBothOtelAndDdEnvVarSet () { for (const [otelEnvVar, ddEnvVar] of Object.entries(otelDdEnvMapping)) { - if (ddEnvVar && getEnvironmentVariable(ddEnvVar) && getEnvironmentVariable(otelEnvVar)) { + const otelValue = getEnv(otelEnvVar) + + if (ddEnvVar && getEnv(ddEnvVar) && otelValue) { log.warn('both %s and %s environment variables are set', ddEnvVar, otelEnvVar) getCounter('otel.env.hiding', ddEnvVar, otelEnvVar).inc() } - if (getEnvironmentVariable(otelEnvVar) && !validateEnvVarType(otelEnvVar)) { - log.warn('unexpected value for %s environment variable', otelEnvVar) + if (isInvalidOtelEnvironmentVariable(otelEnvVar, otelValue)) { + log.warn('unexpected value %s for %s environment variable', otelValue, otelEnvVar) getCounter('otel.env.invalid', ddEnvVar, otelEnvVar).inc() } } @@ -217,9 +227,9 @@ function propagationStyle (key, option) { // Otherwise, fallback to env var parsing const envKey = `DD_TRACE_PROPAGATION_STYLE_${key.toUpperCase()}` - const envVar = getEnvironmentVariable(envKey) ?? - getEnvironmentVariable('DD_TRACE_PROPAGATION_STYLE') ?? - getEnvironmentVariable('OTEL_PROPAGATORS') + const envVar = getEnv(envKey) ?? + getEnv('DD_TRACE_PROPAGATION_STYLE') ?? + getEnv('OTEL_PROPAGATORS') if (envVar !== undefined) { return envVar.split(',') .filter(v => v !== '') @@ -237,16 +247,6 @@ function reformatSpanSamplingRules (rules) { }) } -const sourcesOrder = [ - { containerProperty: '_remote', origin: 'remote_config', unprocessedProperty: '_remoteUnprocessed' }, - { containerProperty: '_options', origin: 'code', unprocessedProperty: '_optsUnprocessed' }, - { containerProperty: '_fleetStableConfig', origin: 'fleet_stable_config' }, - { containerProperty: '_env', origin: 'env_var', unprocessedProperty: '_envUnprocessed' }, - { containerProperty: '_localStableConfig', origin: 'local_stable_config' }, - { containerProperty: '_calculated', origin: 'calculated' }, - { containerProperty: '_defaults', origin: 'default' } -] - class Config { /** * parsed DD_TAGS, usable as a standalone tag set across products @@ -254,6 +254,30 @@ class Config { */ #parsedDdTags = {} + #envUnprocessed = {} + #optsUnprocessed = {} + #remoteUnprocessed = {} + #env = {} + #options = {} + #remote = {} + #defaults = {} + #optionsArg = {} + #localStableConfig = {} + #fleetStableConfig = {} + #calculated = {} + + #getSourcesInOrder () { + return [ + { container: this.#remote, origin: 'remote_config', unprocessed: this.#remoteUnprocessed }, + { container: this.#options, origin: 'code', unprocessed: this.#optsUnprocessed }, + { container: this.#fleetStableConfig, origin: 'fleet_stable_config' }, + { container: this.#env, origin: 'env_var', unprocessed: this.#envUnprocessed }, + { container: this.#localStableConfig, origin: 'local_stable_config' }, + { container: this.#calculated, origin: 'calculated' }, + { container: this.#defaults, origin: 'default' } + ] + } + constructor (options = {}) { if (!isInServerlessEnvironment()) { // Bail out early if we're in a serverless environment, stable config isn't supported @@ -261,6 +285,8 @@ class Config { this.stableConfig = new StableConfig() } + const envs = getEnvironmentVariables() + options = { ...options, appsec: options.appsec == null ? options.experimental?.appsec : options.appsec, @@ -289,12 +315,23 @@ class Config { checkIfBothOtelAndDdEnvVarSet() - const DD_API_KEY = getEnvironmentVariable('DD_API_KEY') - const DD_APP_KEY = getEnvironmentVariable('DD_APP_KEY') + const { + DD_API_KEY, + DD_APP_KEY, + DD_INSTRUMENTATION_INSTALL_ID = null, + DD_INSTRUMENTATION_INSTALL_TIME = null, + DD_INSTRUMENTATION_INSTALL_TYPE = null, + DD_TRACE_CLOUD_PAYLOAD_TAGGING_MAX_DEPTH, + DD_TRACE_CLOUD_REQUEST_PAYLOAD_TAGGING, + DD_TRACE_CLOUD_RESPONSE_PAYLOAD_TAGGING, + DD_TRACE_PROPAGATION_STYLE_EXTRACT, + DD_TRACE_PROPAGATION_STYLE_INJECT, + DD_TRACE_PROPAGATION_STYLE, + } = envs - if (getEnvironmentVariable('DD_TRACE_PROPAGATION_STYLE') && ( - getEnvironmentVariable('DD_TRACE_PROPAGATION_STYLE_INJECT') || - getEnvironmentVariable('DD_TRACE_PROPAGATION_STYLE_EXTRACT') + if (DD_TRACE_PROPAGATION_STYLE && ( + DD_TRACE_PROPAGATION_STYLE_INJECT || + DD_TRACE_PROPAGATION_STYLE_EXTRACT )) { log.warn( // eslint-disable-next-line @stylistic/max-len @@ -320,24 +357,20 @@ class Config { } } - const DD_INSTRUMENTATION_INSTALL_ID = getEnvironmentVariable('DD_INSTRUMENTATION_INSTALL_ID') ?? null - const DD_INSTRUMENTATION_INSTALL_TIME = getEnvironmentVariable('DD_INSTRUMENTATION_INSTALL_TIME') ?? null - const DD_INSTRUMENTATION_INSTALL_TYPE = getEnvironmentVariable('DD_INSTRUMENTATION_INSTALL_TYPE') ?? null - - const DD_TRACE_CLOUD_REQUEST_PAYLOAD_TAGGING = splitJSONPathRules( - getEnvironmentVariable('DD_TRACE_CLOUD_REQUEST_PAYLOAD_TAGGING') ?? + const cloudPayloadTaggingRequestRules = splitJSONPathRules( + DD_TRACE_CLOUD_REQUEST_PAYLOAD_TAGGING ?? options.cloudPayloadTagging?.request ?? '' ) - const DD_TRACE_CLOUD_RESPONSE_PAYLOAD_TAGGING = splitJSONPathRules( - getEnvironmentVariable('DD_TRACE_CLOUD_RESPONSE_PAYLOAD_TAGGING') ?? + const cloudPayloadTaggingResponseRules = splitJSONPathRules( + DD_TRACE_CLOUD_RESPONSE_PAYLOAD_TAGGING ?? options.cloudPayloadTagging?.response ?? '' ) - const DD_TRACE_CLOUD_PAYLOAD_TAGGING_MAX_DEPTH = maybeInt( - getEnvironmentVariable('DD_TRACE_CLOUD_PAYLOAD_TAGGING_MAX_DEPTH') ?? + const cloudPayloadTaggingMaxDepth = maybeInt( + DD_TRACE_CLOUD_PAYLOAD_TAGGING_MAX_DEPTH ?? options.cloudPayloadTagging?.maxDepth ) ?? 10 @@ -353,22 +386,20 @@ class Config { } this.cloudPayloadTagging = { - requestsEnabled: !!DD_TRACE_CLOUD_REQUEST_PAYLOAD_TAGGING, - responsesEnabled: !!DD_TRACE_CLOUD_RESPONSE_PAYLOAD_TAGGING, - maxDepth: DD_TRACE_CLOUD_PAYLOAD_TAGGING_MAX_DEPTH, - rules: appendRules( - DD_TRACE_CLOUD_REQUEST_PAYLOAD_TAGGING, DD_TRACE_CLOUD_RESPONSE_PAYLOAD_TAGGING - ) + requestsEnabled: !!cloudPayloadTaggingRequestRules, + responsesEnabled: !!cloudPayloadTaggingResponseRules, + maxDepth: cloudPayloadTaggingMaxDepth, + rules: appendRules(cloudPayloadTaggingRequestRules, cloudPayloadTaggingResponseRules) } - this._applyDefaults() - this._applyLocalStableConfig() - this._applyEnvironment() - this._applyFleetStableConfig() - this._applyOptions(options) - this._applyCalculated() - this._applyRemote({}) - this._merge() + this.#defaults = defaults + this.#applyStableConfig(this.stableConfig?.localEntries ?? {}, this.#localStableConfig) + this.#applyEnvironment(envs) + this.#applyStableConfig(this.stableConfig?.fleetEntries ?? {}, this.#fleetStableConfig) + this.#applyOptions(options) + this.#applyCalculated() + this.#applyRemote({}) + this.#merge() tagger.add(this.tags, { service: this.service, @@ -384,7 +415,7 @@ class Config { } if (this.gitMetadataEnabled) { - this._loadGitMetadata() + this.#loadGitMetadata(envs) } } @@ -395,20 +426,20 @@ class Config { // Supports only a subset of options for now. configure (options, remote) { if (remote) { - this._applyRemote(options) + this.#applyRemote(options) } else { - this._applyOptions(options) + this.#applyOptions(options) } // TODO: test - this._applyCalculated() - this._merge() + this.#applyCalculated() + this.#merge() } - _getDefaultPropagationStyle (options) { + #getDefaultPropagationStyle (options) { // TODO: Remove the experimental env vars as a major? const DD_TRACE_B3_ENABLED = options.experimental?.b3 ?? - getEnvironmentVariable('DD_TRACE_EXPERIMENTAL_B3_ENABLED') + getEnv('DD_TRACE_EXPERIMENTAL_B3_ENABLED') const defaultPropagationStyle = ['datadog', 'tracecontext'] if (isTrue(DD_TRACE_B3_ENABLED)) { defaultPropagationStyle.push('b3', 'b3 single header') @@ -420,22 +451,7 @@ class Config { return isInServerlessEnvironment() } - // for _merge to work, every config value must have a default value - _applyDefaults () { - setHiddenProperty(this, '_defaults', defaults) - } - - _applyLocalStableConfig () { - const obj = setHiddenProperty(this, '_localStableConfig', {}) - this._applyStableConfig(this.stableConfig?.localEntries ?? {}, obj) - } - - _applyFleetStableConfig () { - const obj = setHiddenProperty(this, '_fleetStableConfig', {}) - this._applyStableConfig(this.stableConfig?.fleetEntries ?? {}, obj) - } - - _applyStableConfig (config, obj) { + #applyStableConfig (config, obj) { const { DD_APPSEC_ENABLED, DD_APPSEC_SCA_ENABLED, @@ -450,21 +466,21 @@ class Config { DD_VERSION } = config - this._setBoolean(obj, 'appsec.enabled', DD_APPSEC_ENABLED) - this._setBoolean(obj, 'appsec.sca.enabled', DD_APPSEC_SCA_ENABLED) - this._setBoolean(obj, 'dsmEnabled', DD_DATA_STREAMS_ENABLED) - this._setBoolean(obj, 'dynamicInstrumentation.enabled', DD_DYNAMIC_INSTRUMENTATION_ENABLED) - this._setString(obj, 'env', DD_ENV) - this._setBoolean(obj, 'iast.enabled', DD_IAST_ENABLED) - this._setBoolean(obj, 'logInjection', DD_LOGS_INJECTION) + this.#setBoolean(obj, 'appsec.enabled', DD_APPSEC_ENABLED) + this.#setBoolean(obj, 'appsec.sca.enabled', DD_APPSEC_SCA_ENABLED) + this.#setBoolean(obj, 'dsmEnabled', DD_DATA_STREAMS_ENABLED) + this.#setBoolean(obj, 'dynamicInstrumentation.enabled', DD_DYNAMIC_INSTRUMENTATION_ENABLED) + this.#setString(obj, 'env', DD_ENV) + this.#setBoolean(obj, 'iast.enabled', DD_IAST_ENABLED) + this.#setBoolean(obj, 'logInjection', DD_LOGS_INJECTION) const profilingEnabled = normalizeProfilingEnabledValue(DD_PROFILING_ENABLED) - this._setString(obj, 'profiling.enabled', profilingEnabled) - this._setBoolean(obj, 'runtimeMetrics.enabled', DD_RUNTIME_METRICS_ENABLED) - this._setString(obj, 'service', DD_SERVICE) - this._setString(obj, 'version', DD_VERSION) + this.#setString(obj, 'profiling.enabled', profilingEnabled) + this.#setBoolean(obj, 'runtimeMetrics.enabled', DD_RUNTIME_METRICS_ENABLED) + this.#setString(obj, 'service', DD_SERVICE) + this.#setString(obj, 'version', DD_VERSION) } - _applyEnvironment () { + #applyEnvironment (envs) { const { AWS_LAMBDA_FUNCTION_NAME, DD_AGENT_HOST, @@ -586,6 +602,7 @@ class Config { DD_TRACE_MIDDLEWARE_TRACING_ENABLED, DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP, DD_TRACE_PARTIAL_FLUSH_MIN_SPANS, + DD_TRACE_FLUSH_INTERVAL, DD_TRACE_PEER_SERVICE_MAPPING, DD_TRACE_PROPAGATION_EXTRACT_FIRST, DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT, @@ -629,11 +646,10 @@ class Config { OTEL_EXPORTER_OTLP_TIMEOUT, OTEL_BSP_SCHEDULE_DELAY, OTEL_BSP_MAX_EXPORT_BATCH_SIZE - } = getEnvironmentVariables() + } = envs const tags = {} - const env = setHiddenProperty(this, '_env', {}) - setHiddenProperty(this, '_envUnprocessed', {}) + const env = this.#env tagger.add(this.#parsedDdTags, parseSpaceSeparatedTags(DD_TAGS)) @@ -642,461 +658,460 @@ class Config { tagger.add(tags, DD_TRACE_TAGS) tagger.add(tags, DD_TRACE_GLOBAL_TAGS) - this._setBoolean(env, 'otelLogsEnabled', isTrue(DD_LOGS_OTEL_ENABLED)) + this.#setBoolean(env, 'otelLogsEnabled', isTrue(DD_LOGS_OTEL_ENABLED)) // Set OpenTelemetry logs configuration with specific _LOGS_ vars taking precedence over generic _EXPORTERS_ vars if (OTEL_EXPORTER_OTLP_ENDPOINT) { // Only set if there's a custom URL, otherwise let calc phase handle the default - this._setString(env, 'otelUrl', OTEL_EXPORTER_OTLP_ENDPOINT) + this.#setString(env, 'otelUrl', OTEL_EXPORTER_OTLP_ENDPOINT) } if (OTEL_EXPORTER_OTLP_ENDPOINT || OTEL_EXPORTER_OTLP_LOGS_ENDPOINT) { - this._setString(env, 'otelLogsUrl', OTEL_EXPORTER_OTLP_LOGS_ENDPOINT || env.otelUrl) + this.#setString(env, 'otelLogsUrl', OTEL_EXPORTER_OTLP_LOGS_ENDPOINT || env.otelUrl) } - this._setString(env, 'otelHeaders', OTEL_EXPORTER_OTLP_HEADERS) - this._setString(env, 'otelLogsHeaders', OTEL_EXPORTER_OTLP_LOGS_HEADERS || env.otelHeaders) - this._setString(env, 'otelProtocol', OTEL_EXPORTER_OTLP_PROTOCOL) - this._setString(env, 'otelLogsProtocol', OTEL_EXPORTER_OTLP_LOGS_PROTOCOL || env.otelProtocol) + this.#setString(env, 'otelHeaders', OTEL_EXPORTER_OTLP_HEADERS) + this.#setString(env, 'otelLogsHeaders', OTEL_EXPORTER_OTLP_LOGS_HEADERS || env.otelHeaders) + this.#setString(env, 'otelProtocol', OTEL_EXPORTER_OTLP_PROTOCOL) + this.#setString(env, 'otelLogsProtocol', OTEL_EXPORTER_OTLP_LOGS_PROTOCOL || env.otelProtocol) env.otelTimeout = maybeInt(OTEL_EXPORTER_OTLP_TIMEOUT) env.otelLogsTimeout = maybeInt(OTEL_EXPORTER_OTLP_LOGS_TIMEOUT) || env.otelTimeout env.otelLogsBatchTimeout = maybeInt(OTEL_BSP_SCHEDULE_DELAY) env.otelLogsMaxExportBatchSize = maybeInt(OTEL_BSP_MAX_EXPORT_BATCH_SIZE) - this._setBoolean( + this.#setBoolean( env, 'apmTracingEnabled', DD_APM_TRACING_ENABLED ?? (DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED && isFalse(DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED)) ) - this._setBoolean(env, 'appsec.apiSecurity.enabled', DD_API_SECURITY_ENABLED && isTrue(DD_API_SECURITY_ENABLED)) + this.#setBoolean(env, 'appsec.apiSecurity.enabled', DD_API_SECURITY_ENABLED && isTrue(DD_API_SECURITY_ENABLED)) env['appsec.apiSecurity.sampleDelay'] = maybeFloat(DD_API_SECURITY_SAMPLE_DELAY) - this._setBoolean(env, 'appsec.apiSecurity.endpointCollectionEnabled', + this.#setBoolean(env, 'appsec.apiSecurity.endpointCollectionEnabled', DD_API_SECURITY_ENDPOINT_COLLECTION_ENABLED) env['appsec.apiSecurity.endpointCollectionMessageLimit'] = maybeInt(DD_API_SECURITY_ENDPOINT_COLLECTION_MESSAGE_LIMIT) env['appsec.blockedTemplateGraphql'] = maybeFile(DD_APPSEC_GRAPHQL_BLOCKED_TEMPLATE_JSON) env['appsec.blockedTemplateHtml'] = maybeFile(DD_APPSEC_HTTP_BLOCKED_TEMPLATE_HTML) - this._envUnprocessed['appsec.blockedTemplateHtml'] = DD_APPSEC_HTTP_BLOCKED_TEMPLATE_HTML + this.#envUnprocessed['appsec.blockedTemplateHtml'] = DD_APPSEC_HTTP_BLOCKED_TEMPLATE_HTML env['appsec.blockedTemplateJson'] = maybeFile(DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON) - this._envUnprocessed['appsec.blockedTemplateJson'] = DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON - this._setBoolean(env, 'appsec.enabled', DD_APPSEC_ENABLED) - this._setString(env, 'appsec.eventTracking.mode', DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE) + this.#envUnprocessed['appsec.blockedTemplateJson'] = DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON + this.#setBoolean(env, 'appsec.enabled', DD_APPSEC_ENABLED) + this.#setString(env, 'appsec.eventTracking.mode', DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE) // TODO appsec.extendedHeadersCollection are deprecated, to delete in a major - this._setBoolean(env, 'appsec.extendedHeadersCollection.enabled', DD_APPSEC_COLLECT_ALL_HEADERS) - this._setBoolean( + this.#setBoolean(env, 'appsec.extendedHeadersCollection.enabled', DD_APPSEC_COLLECT_ALL_HEADERS) + this.#setBoolean( env, 'appsec.extendedHeadersCollection.redaction', DD_APPSEC_HEADER_COLLECTION_REDACTION_ENABLED ) env['appsec.extendedHeadersCollection.maxHeaders'] = maybeInt(DD_APPSEC_MAX_COLLECTED_HEADERS) - this._envUnprocessed['appsec.extendedHeadersCollection.maxHeaders'] = DD_APPSEC_MAX_COLLECTED_HEADERS - this._setString(env, 'appsec.obfuscatorKeyRegex', DD_APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP) - this._setString(env, 'appsec.obfuscatorValueRegex', DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP) - this._setBoolean(env, 'appsec.rasp.enabled', DD_APPSEC_RASP_ENABLED) + this.#envUnprocessed['appsec.extendedHeadersCollection.maxHeaders'] = DD_APPSEC_MAX_COLLECTED_HEADERS + this.#setString(env, 'appsec.obfuscatorKeyRegex', DD_APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP) + this.#setString(env, 'appsec.obfuscatorValueRegex', DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP) + this.#setBoolean(env, 'appsec.rasp.enabled', DD_APPSEC_RASP_ENABLED) // TODO Deprecated, to delete in a major - this._setBoolean(env, 'appsec.rasp.bodyCollection', DD_APPSEC_RASP_COLLECT_REQUEST_BODY) + this.#setBoolean(env, 'appsec.rasp.bodyCollection', DD_APPSEC_RASP_COLLECT_REQUEST_BODY) env['appsec.rateLimit'] = maybeInt(DD_APPSEC_TRACE_RATE_LIMIT) - this._envUnprocessed['appsec.rateLimit'] = DD_APPSEC_TRACE_RATE_LIMIT - this._setString(env, 'appsec.rules', DD_APPSEC_RULES) + this.#envUnprocessed['appsec.rateLimit'] = DD_APPSEC_TRACE_RATE_LIMIT + this.#setString(env, 'appsec.rules', DD_APPSEC_RULES) // DD_APPSEC_SCA_ENABLED is never used locally, but only sent to the backend - this._setBoolean(env, 'appsec.sca.enabled', DD_APPSEC_SCA_ENABLED) - this._setBoolean(env, 'appsec.stackTrace.enabled', DD_APPSEC_STACK_TRACE_ENABLED) + this.#setBoolean(env, 'appsec.sca.enabled', DD_APPSEC_SCA_ENABLED) + this.#setBoolean(env, 'appsec.stackTrace.enabled', DD_APPSEC_STACK_TRACE_ENABLED) env['appsec.stackTrace.maxDepth'] = maybeInt(DD_APPSEC_MAX_STACK_TRACE_DEPTH) - this._envUnprocessed['appsec.stackTrace.maxDepth'] = DD_APPSEC_MAX_STACK_TRACE_DEPTH + this.#envUnprocessed['appsec.stackTrace.maxDepth'] = DD_APPSEC_MAX_STACK_TRACE_DEPTH env['appsec.stackTrace.maxStackTraces'] = maybeInt(DD_APPSEC_MAX_STACK_TRACES) - this._envUnprocessed['appsec.stackTrace.maxStackTraces'] = DD_APPSEC_MAX_STACK_TRACES + this.#envUnprocessed['appsec.stackTrace.maxStackTraces'] = DD_APPSEC_MAX_STACK_TRACES env['appsec.wafTimeout'] = maybeInt(DD_APPSEC_WAF_TIMEOUT) - this._envUnprocessed['appsec.wafTimeout'] = DD_APPSEC_WAF_TIMEOUT + this.#envUnprocessed['appsec.wafTimeout'] = DD_APPSEC_WAF_TIMEOUT env.baggageMaxBytes = DD_TRACE_BAGGAGE_MAX_BYTES env.baggageMaxItems = DD_TRACE_BAGGAGE_MAX_ITEMS env.baggageTagKeys = DD_TRACE_BAGGAGE_TAG_KEYS - this._setBoolean(env, 'clientIpEnabled', DD_TRACE_CLIENT_IP_ENABLED) - this._setString(env, 'clientIpHeader', DD_TRACE_CLIENT_IP_HEADER?.toLowerCase()) - this._setBoolean(env, 'crashtracking.enabled', DD_CRASHTRACKING_ENABLED ?? !this._isInServerlessEnvironment()) - this._setBoolean(env, 'codeOriginForSpans.enabled', DD_CODE_ORIGIN_FOR_SPANS_ENABLED) - this._setBoolean( + this.#setBoolean(env, 'clientIpEnabled', DD_TRACE_CLIENT_IP_ENABLED) + this.#setString(env, 'clientIpHeader', DD_TRACE_CLIENT_IP_HEADER?.toLowerCase()) + this.#setBoolean(env, 'crashtracking.enabled', DD_CRASHTRACKING_ENABLED ?? !this._isInServerlessEnvironment()) + this.#setBoolean(env, 'codeOriginForSpans.enabled', DD_CODE_ORIGIN_FOR_SPANS_ENABLED) + this.#setBoolean( env, 'codeOriginForSpans.experimental.exit_spans.enabled', DD_CODE_ORIGIN_FOR_SPANS_EXPERIMENTAL_EXIT_SPANS_ENABLED ) - this._setString(env, 'dbmPropagationMode', DD_DBM_PROPAGATION_MODE) - this._setString(env, 'dogstatsd.hostname', DD_DOGSTATSD_HOST) - this._setString(env, 'dogstatsd.port', DD_DOGSTATSD_PORT) - this._setBoolean(env, 'dsmEnabled', DD_DATA_STREAMS_ENABLED) - this._setBoolean(env, 'dynamicInstrumentation.enabled', DD_DYNAMIC_INSTRUMENTATION_ENABLED) - this._setString(env, 'dynamicInstrumentation.probeFile', DD_DYNAMIC_INSTRUMENTATION_PROBE_FILE) - this._setArray(env, 'dynamicInstrumentation.redactedIdentifiers', DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS) - this._setArray( + this.#setString(env, 'dbmPropagationMode', DD_DBM_PROPAGATION_MODE) + this.#setString(env, 'dogstatsd.hostname', DD_DOGSTATSD_HOST) + this.#setString(env, 'dogstatsd.port', DD_DOGSTATSD_PORT) + this.#setBoolean(env, 'dsmEnabled', DD_DATA_STREAMS_ENABLED) + this.#setBoolean(env, 'dynamicInstrumentation.enabled', DD_DYNAMIC_INSTRUMENTATION_ENABLED) + this.#setString(env, 'dynamicInstrumentation.probeFile', DD_DYNAMIC_INSTRUMENTATION_PROBE_FILE) + this.#setArray(env, 'dynamicInstrumentation.redactedIdentifiers', DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS) + this.#setArray( env, 'dynamicInstrumentation.redactionExcludedIdentifiers', DD_DYNAMIC_INSTRUMENTATION_REDACTION_EXCLUDED_IDENTIFIERS ) env['dynamicInstrumentation.uploadIntervalSeconds'] = maybeFloat(DD_DYNAMIC_INSTRUMENTATION_UPLOAD_INTERVAL_SECONDS) - this._envUnprocessed['dynamicInstrumentation.uploadInterval'] = DD_DYNAMIC_INSTRUMENTATION_UPLOAD_INTERVAL_SECONDS - this._setString(env, 'env', DD_ENV || tags.env) - this._setBoolean(env, 'experimental.flaggingProvider.enabled', DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED) - this._setBoolean(env, 'traceEnabled', DD_TRACE_ENABLED) - this._setBoolean(env, 'experimental.aiguard.enabled', DD_AI_GUARD_ENABLED) - this._setString(env, 'experimental.aiguard.endpoint', DD_AI_GUARD_ENDPOINT) + this.#envUnprocessed['dynamicInstrumentation.uploadInterval'] = DD_DYNAMIC_INSTRUMENTATION_UPLOAD_INTERVAL_SECONDS + this.#setString(env, 'env', DD_ENV || tags.env) + this.#setBoolean(env, 'experimental.flaggingProvider.enabled', DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED) + this.#setBoolean(env, 'traceEnabled', DD_TRACE_ENABLED) + this.#setBoolean(env, 'experimental.aiguard.enabled', DD_AI_GUARD_ENABLED) + this.#setString(env, 'experimental.aiguard.endpoint', DD_AI_GUARD_ENDPOINT) env['experimental.aiguard.maxContentSize'] = maybeInt(DD_AI_GUARD_MAX_CONTENT_SIZE) - this._envUnprocessed['experimental.aiguard.maxContentSize'] = DD_AI_GUARD_MAX_CONTENT_SIZE + this.#envUnprocessed['experimental.aiguard.maxContentSize'] = DD_AI_GUARD_MAX_CONTENT_SIZE env['experimental.aiguard.maxMessagesLength'] = maybeInt(DD_AI_GUARD_MAX_MESSAGES_LENGTH) - this._envUnprocessed['experimental.aiguard.maxMessagesLength'] = DD_AI_GUARD_MAX_MESSAGES_LENGTH + this.#envUnprocessed['experimental.aiguard.maxMessagesLength'] = DD_AI_GUARD_MAX_MESSAGES_LENGTH env['experimental.aiguard.timeout'] = maybeInt(DD_AI_GUARD_TIMEOUT) - this._envUnprocessed['experimental.aiguard.timeout'] = DD_AI_GUARD_TIMEOUT - this._setBoolean(env, 'experimental.enableGetRumData', DD_TRACE_EXPERIMENTAL_GET_RUM_DATA_ENABLED) - this._setString(env, 'experimental.exporter', DD_TRACE_EXPERIMENTAL_EXPORTER) - if (AWS_LAMBDA_FUNCTION_NAME) env.flushInterval = 0 + this.#envUnprocessed['experimental.aiguard.timeout'] = DD_AI_GUARD_TIMEOUT + this.#setBoolean(env, 'experimental.enableGetRumData', DD_TRACE_EXPERIMENTAL_GET_RUM_DATA_ENABLED) + this.#setString(env, 'experimental.exporter', DD_TRACE_EXPERIMENTAL_EXPORTER) + env.flushInterval = AWS_LAMBDA_FUNCTION_NAME ? 0 : maybeInt(DD_TRACE_FLUSH_INTERVAL) env.flushMinSpans = maybeInt(DD_TRACE_PARTIAL_FLUSH_MIN_SPANS) - this._envUnprocessed.flushMinSpans = DD_TRACE_PARTIAL_FLUSH_MIN_SPANS - this._setBoolean(env, 'gitMetadataEnabled', DD_TRACE_GIT_METADATA_ENABLED) - this._setIntegerRangeSet(env, 'grpc.client.error.statuses', DD_GRPC_CLIENT_ERROR_STATUSES) - this._setIntegerRangeSet(env, 'grpc.server.error.statuses', DD_GRPC_SERVER_ERROR_STATUSES) - this._setArray(env, 'headerTags', DD_TRACE_HEADER_TAGS) + this.#envUnprocessed.flushMinSpans = DD_TRACE_PARTIAL_FLUSH_MIN_SPANS + this.#setBoolean(env, 'gitMetadataEnabled', DD_TRACE_GIT_METADATA_ENABLED) + this.#setIntegerRangeSet(env, 'grpc.client.error.statuses', DD_GRPC_CLIENT_ERROR_STATUSES) + this.#setIntegerRangeSet(env, 'grpc.server.error.statuses', DD_GRPC_SERVER_ERROR_STATUSES) + this.#setArray(env, 'headerTags', DD_TRACE_HEADER_TAGS) env['heapSnapshot.count'] = maybeInt(DD_HEAP_SNAPSHOT_COUNT) - this._setString(env, 'heapSnapshot.destination', DD_HEAP_SNAPSHOT_DESTINATION) + this.#setString(env, 'heapSnapshot.destination', DD_HEAP_SNAPSHOT_DESTINATION) env['heapSnapshot.interval'] = maybeInt(DD_HEAP_SNAPSHOT_INTERVAL) - this._setString(env, 'hostname', DD_AGENT_HOST) + this.#setString(env, 'hostname', DD_AGENT_HOST) env['iast.dbRowsToTaint'] = maybeInt(DD_IAST_DB_ROWS_TO_TAINT) - this._setBoolean(env, 'iast.deduplicationEnabled', DD_IAST_DEDUPLICATION_ENABLED) - this._setBoolean(env, 'iast.enabled', DD_IAST_ENABLED) + this.#setBoolean(env, 'iast.deduplicationEnabled', DD_IAST_DEDUPLICATION_ENABLED) + this.#setBoolean(env, 'iast.enabled', DD_IAST_ENABLED) env['iast.maxConcurrentRequests'] = maybeInt(DD_IAST_MAX_CONCURRENT_REQUESTS) - this._envUnprocessed['iast.maxConcurrentRequests'] = DD_IAST_MAX_CONCURRENT_REQUESTS + this.#envUnprocessed['iast.maxConcurrentRequests'] = DD_IAST_MAX_CONCURRENT_REQUESTS env['iast.maxContextOperations'] = maybeInt(DD_IAST_MAX_CONTEXT_OPERATIONS) - this._envUnprocessed['iast.maxContextOperations'] = DD_IAST_MAX_CONTEXT_OPERATIONS - this._setBoolean(env, 'iast.redactionEnabled', DD_IAST_REDACTION_ENABLED && !isFalse(DD_IAST_REDACTION_ENABLED)) - this._setString(env, 'iast.redactionNamePattern', DD_IAST_REDACTION_NAME_PATTERN) - this._setString(env, 'iast.redactionValuePattern', DD_IAST_REDACTION_VALUE_PATTERN) + this.#envUnprocessed['iast.maxContextOperations'] = DD_IAST_MAX_CONTEXT_OPERATIONS + this.#setBoolean(env, 'iast.redactionEnabled', DD_IAST_REDACTION_ENABLED && !isFalse(DD_IAST_REDACTION_ENABLED)) + this.#setString(env, 'iast.redactionNamePattern', DD_IAST_REDACTION_NAME_PATTERN) + this.#setString(env, 'iast.redactionValuePattern', DD_IAST_REDACTION_VALUE_PATTERN) const iastRequestSampling = maybeInt(DD_IAST_REQUEST_SAMPLING) - if (iastRequestSampling > -1 && iastRequestSampling < 101) { + if (iastRequestSampling !== undefined && iastRequestSampling > -1 && iastRequestSampling < 101) { env['iast.requestSampling'] = iastRequestSampling } - this._envUnprocessed['iast.requestSampling'] = DD_IAST_REQUEST_SAMPLING - this._setString(env, 'iast.securityControlsConfiguration', DD_IAST_SECURITY_CONTROLS_CONFIGURATION) - this._setString(env, 'iast.telemetryVerbosity', DD_IAST_TELEMETRY_VERBOSITY) - this._setBoolean(env, 'iast.stackTrace.enabled', DD_IAST_STACK_TRACE_ENABLED) - this._setArray(env, 'injectionEnabled', DD_INJECTION_ENABLED) - this._setString(env, 'instrumentationSource', DD_INJECTION_ENABLED ? 'ssi' : 'manual') - this._setBoolean(env, 'injectForce', DD_INJECT_FORCE) - this._setBoolean(env, 'isAzureFunction', getIsAzureFunction()) - this._setBoolean(env, 'isGCPFunction', getIsGCPFunction()) + this.#envUnprocessed['iast.requestSampling'] = DD_IAST_REQUEST_SAMPLING + this.#setString(env, 'iast.securityControlsConfiguration', DD_IAST_SECURITY_CONTROLS_CONFIGURATION) + this.#setString(env, 'iast.telemetryVerbosity', DD_IAST_TELEMETRY_VERBOSITY) + this.#setBoolean(env, 'iast.stackTrace.enabled', DD_IAST_STACK_TRACE_ENABLED) + this.#setArray(env, 'injectionEnabled', DD_INJECTION_ENABLED) + this.#setString(env, 'instrumentationSource', DD_INJECTION_ENABLED ? 'ssi' : 'manual') + this.#setBoolean(env, 'injectForce', DD_INJECT_FORCE) + this.#setBoolean(env, 'isAzureFunction', getIsAzureFunction()) + this.#setBoolean(env, 'isGCPFunction', getIsGCPFunction()) env['langchain.spanCharLimit'] = maybeInt(DD_LANGCHAIN_SPAN_CHAR_LIMIT) env['langchain.spanPromptCompletionSampleRate'] = maybeFloat(DD_LANGCHAIN_SPAN_PROMPT_COMPLETION_SAMPLE_RATE) - this._setBoolean(env, 'legacyBaggageEnabled', DD_TRACE_LEGACY_BAGGAGE_ENABLED) - this._setBoolean(env, 'llmobs.agentlessEnabled', DD_LLMOBS_AGENTLESS_ENABLED) - this._setBoolean(env, 'llmobs.enabled', DD_LLMOBS_ENABLED) - this._setString(env, 'llmobs.mlApp', DD_LLMOBS_ML_APP) - this._setBoolean(env, 'logInjection', DD_LOGS_INJECTION) + this.#setBoolean(env, 'legacyBaggageEnabled', DD_TRACE_LEGACY_BAGGAGE_ENABLED) + this.#setBoolean(env, 'llmobs.agentlessEnabled', DD_LLMOBS_AGENTLESS_ENABLED) + this.#setBoolean(env, 'llmobs.enabled', DD_LLMOBS_ENABLED) + this.#setString(env, 'llmobs.mlApp', DD_LLMOBS_ML_APP) + this.#setBoolean(env, 'logInjection', DD_LOGS_INJECTION) // Requires an accompanying DD_APM_OBFUSCATION_MEMCACHED_KEEP_COMMAND=true in the agent - this._setBoolean(env, 'memcachedCommandEnabled', DD_TRACE_MEMCACHED_COMMAND_ENABLED) - this._setBoolean(env, 'middlewareTracingEnabled', DD_TRACE_MIDDLEWARE_TRACING_ENABLED) - this._setBoolean(env, 'openAiLogsEnabled', DD_OPENAI_LOGS_ENABLED) + this.#setBoolean(env, 'memcachedCommandEnabled', DD_TRACE_MEMCACHED_COMMAND_ENABLED) + this.#setBoolean(env, 'middlewareTracingEnabled', DD_TRACE_MIDDLEWARE_TRACING_ENABLED) + this.#setBoolean(env, 'openAiLogsEnabled', DD_OPENAI_LOGS_ENABLED) env['openai.spanCharLimit'] = maybeInt(DD_OPENAI_SPAN_CHAR_LIMIT) - this._envUnprocessed.openaiSpanCharLimit = DD_OPENAI_SPAN_CHAR_LIMIT + this.#envUnprocessed.openaiSpanCharLimit = DD_OPENAI_SPAN_CHAR_LIMIT if (DD_TRACE_PEER_SERVICE_MAPPING) { env.peerServiceMapping = Object.fromEntries( DD_TRACE_PEER_SERVICE_MAPPING.split(',').map(x => x.trim().split(':')) ) - this._envUnprocessed.peerServiceMapping = DD_TRACE_PEER_SERVICE_MAPPING + this.#envUnprocessed.peerServiceMapping = DD_TRACE_PEER_SERVICE_MAPPING } - this._setString(env, 'port', DD_TRACE_AGENT_PORT) + this.#setString(env, 'port', DD_TRACE_AGENT_PORT) const profilingEnabled = normalizeProfilingEnabledValue( DD_PROFILING_ENABLED ?? (this._isInServerlessEnvironment() ? 'false' : undefined) ) - this._setString(env, 'profiling.enabled', profilingEnabled) - this._setString(env, 'profiling.exporters', DD_PROFILING_EXPORTERS) - this._setBoolean(env, 'profiling.sourceMap', DD_PROFILING_SOURCE_MAP && !isFalse(DD_PROFILING_SOURCE_MAP)) + this.#setString(env, 'profiling.enabled', profilingEnabled) + this.#setString(env, 'profiling.exporters', DD_PROFILING_EXPORTERS) + this.#setBoolean(env, 'profiling.sourceMap', DD_PROFILING_SOURCE_MAP && !isFalse(DD_PROFILING_SOURCE_MAP)) if (DD_INTERNAL_PROFILING_LONG_LIVED_THRESHOLD) { // This is only used in testing to not have to wait 30s env['profiling.longLivedThreshold'] = Number(DD_INTERNAL_PROFILING_LONG_LIVED_THRESHOLD) } - this._setString(env, 'protocolVersion', DD_TRACE_AGENT_PROTOCOL_VERSION) - this._setString(env, 'queryStringObfuscation', DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP) - this._setBoolean(env, 'remoteConfig.enabled', DD_REMOTE_CONFIGURATION_ENABLED ?? !this._isInServerlessEnvironment()) + this.#setString(env, 'protocolVersion', DD_TRACE_AGENT_PROTOCOL_VERSION) + this.#setString(env, 'queryStringObfuscation', DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP) + this.#setBoolean(env, 'remoteConfig.enabled', DD_REMOTE_CONFIGURATION_ENABLED ?? !this._isInServerlessEnvironment()) env['remoteConfig.pollInterval'] = maybeFloat(DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS) - this._envUnprocessed['remoteConfig.pollInterval'] = DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS - this._setBoolean(env, 'reportHostname', DD_TRACE_REPORT_HOSTNAME) + this.#envUnprocessed['remoteConfig.pollInterval'] = DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS + this.#setBoolean(env, 'reportHostname', DD_TRACE_REPORT_HOSTNAME) // only used to explicitly set runtimeMetrics to false const otelSetRuntimeMetrics = String(OTEL_METRICS_EXPORTER).toLowerCase() === 'none' ? false : undefined - this._setBoolean(env, 'runtimeMetrics.enabled', DD_RUNTIME_METRICS_ENABLED || + this.#setBoolean(env, 'runtimeMetrics.enabled', DD_RUNTIME_METRICS_ENABLED || otelSetRuntimeMetrics) - this._setBoolean(env, 'runtimeMetrics.eventLoop', DD_RUNTIME_METRICS_EVENT_LOOP_ENABLED) - this._setBoolean(env, 'runtimeMetrics.gc', DD_RUNTIME_METRICS_GC_ENABLED) - this._setBoolean(env, 'runtimeMetricsRuntimeId', DD_RUNTIME_METRICS_RUNTIME_ID_ENABLED) - this._setArray(env, 'sampler.spanSamplingRules', reformatSpanSamplingRules( + this.#setBoolean(env, 'runtimeMetrics.eventLoop', DD_RUNTIME_METRICS_EVENT_LOOP_ENABLED) + this.#setBoolean(env, 'runtimeMetrics.gc', DD_RUNTIME_METRICS_GC_ENABLED) + this.#setBoolean(env, 'runtimeMetricsRuntimeId', DD_RUNTIME_METRICS_RUNTIME_ID_ENABLED) + this.#setArray(env, 'sampler.spanSamplingRules', reformatSpanSamplingRules( maybeJsonFile(DD_SPAN_SAMPLING_RULES_FILE) ?? safeJsonParse(DD_SPAN_SAMPLING_RULES) )) - this._setUnit(env, 'sampleRate', DD_TRACE_SAMPLE_RATE || + this.#setUnit(env, 'sampleRate', DD_TRACE_SAMPLE_RATE || getFromOtelSamplerMap(OTEL_TRACES_SAMPLER, OTEL_TRACES_SAMPLER_ARG)) env['sampler.rateLimit'] = DD_TRACE_RATE_LIMIT - this._setSamplingRule(env, 'sampler.rules', safeJsonParse(DD_TRACE_SAMPLING_RULES)) - this._envUnprocessed['sampler.rules'] = DD_TRACE_SAMPLING_RULES - this._setString(env, 'scope', DD_TRACE_SCOPE) - this._setString(env, 'service', DD_SERVICE || tags.service || OTEL_SERVICE_NAME) + this.#setSamplingRule(env, 'sampler.rules', safeJsonParse(DD_TRACE_SAMPLING_RULES)) + this.#envUnprocessed['sampler.rules'] = DD_TRACE_SAMPLING_RULES + this.#setString(env, 'scope', DD_TRACE_SCOPE) + this.#setString(env, 'service', DD_SERVICE || tags.service || OTEL_SERVICE_NAME) if (DD_SERVICE_MAPPING) { env.serviceMapping = Object.fromEntries( DD_SERVICE_MAPPING.split(',').map(x => x.trim().split(':')) ) } - this._setString(env, 'site', DD_SITE) + this.#setString(env, 'site', DD_SITE) if (DD_TRACE_SPAN_ATTRIBUTE_SCHEMA) { - this._setString(env, 'spanAttributeSchema', validateNamingVersion(DD_TRACE_SPAN_ATTRIBUTE_SCHEMA)) - this._envUnprocessed.spanAttributeSchema = DD_TRACE_SPAN_ATTRIBUTE_SCHEMA + this.#setString(env, 'spanAttributeSchema', validateNamingVersion(DD_TRACE_SPAN_ATTRIBUTE_SCHEMA)) + this.#envUnprocessed.spanAttributeSchema = DD_TRACE_SPAN_ATTRIBUTE_SCHEMA } // 0: disabled, 1: logging, 2: garbage collection + logging env.spanLeakDebug = maybeInt(DD_TRACE_SPAN_LEAK_DEBUG) - this._setBoolean(env, 'spanRemoveIntegrationFromService', DD_TRACE_REMOVE_INTEGRATION_SERVICE_NAMES_ENABLED) - this._setBoolean(env, 'startupLogs', DD_TRACE_STARTUP_LOGS) - this._setTags(env, 'tags', tags) + this.#setBoolean(env, 'spanRemoveIntegrationFromService', DD_TRACE_REMOVE_INTEGRATION_SERVICE_NAMES_ENABLED) + this.#setBoolean(env, 'startupLogs', DD_TRACE_STARTUP_LOGS) + this.#setTags(env, 'tags', tags) env.tagsHeaderMaxLength = DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH - this._setBoolean(env, 'telemetry.enabled', DD_INSTRUMENTATION_TELEMETRY_ENABLED ?? + this.#setBoolean(env, 'telemetry.enabled', DD_INSTRUMENTATION_TELEMETRY_ENABLED ?? !(this._isInServerlessEnvironment() || JEST_WORKER_ID)) - this._setString(env, 'instrumentation_config_id', DD_INSTRUMENTATION_CONFIG_ID) - this._setBoolean(env, 'telemetry.debug', DD_TELEMETRY_DEBUG) - this._setBoolean(env, 'telemetry.dependencyCollection', DD_TELEMETRY_DEPENDENCY_COLLECTION_ENABLED) + this.#setString(env, 'instrumentation_config_id', DD_INSTRUMENTATION_CONFIG_ID) + this.#setBoolean(env, 'telemetry.debug', DD_TELEMETRY_DEBUG) + this.#setBoolean(env, 'telemetry.dependencyCollection', DD_TELEMETRY_DEPENDENCY_COLLECTION_ENABLED) env['telemetry.heartbeatInterval'] = maybeInt(Math.floor(DD_TELEMETRY_HEARTBEAT_INTERVAL * 1000)) - this._envUnprocessed['telemetry.heartbeatInterval'] = DD_TELEMETRY_HEARTBEAT_INTERVAL * 1000 - this._setBoolean(env, 'telemetry.logCollection', DD_TELEMETRY_LOG_COLLECTION_ENABLED) - this._setBoolean(env, 'telemetry.metrics', DD_TELEMETRY_METRICS_ENABLED) - this._setBoolean(env, 'traceId128BitGenerationEnabled', DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED) - this._setBoolean(env, 'traceId128BitLoggingEnabled', DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED) - this._setBoolean(env, 'tracePropagationExtractFirst', DD_TRACE_PROPAGATION_EXTRACT_FIRST) + this.#envUnprocessed['telemetry.heartbeatInterval'] = DD_TELEMETRY_HEARTBEAT_INTERVAL * 1000 + this.#setBoolean(env, 'telemetry.logCollection', DD_TELEMETRY_LOG_COLLECTION_ENABLED) + this.#setBoolean(env, 'telemetry.metrics', DD_TELEMETRY_METRICS_ENABLED) + this.#setBoolean(env, 'traceId128BitGenerationEnabled', DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED) + this.#setBoolean(env, 'traceId128BitLoggingEnabled', DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED) + this.#setBoolean(env, 'tracePropagationExtractFirst', DD_TRACE_PROPAGATION_EXTRACT_FIRST) const stringPropagationBehaviorExtract = String(DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT) env.tracePropagationBehaviorExtract = VALID_PROPAGATION_BEHAVIOR_EXTRACT.has(stringPropagationBehaviorExtract) ? stringPropagationBehaviorExtract : 'continue' - this._setBoolean(env, 'tracePropagationStyle.otelPropagators', + this.#setBoolean(env, 'tracePropagationStyle.otelPropagators', DD_TRACE_PROPAGATION_STYLE || DD_TRACE_PROPAGATION_STYLE_INJECT || DD_TRACE_PROPAGATION_STYLE_EXTRACT ? false : !!OTEL_PROPAGATORS) - this._setBoolean(env, 'traceWebsocketMessagesEnabled', DD_TRACE_WEBSOCKET_MESSAGES_ENABLED) - this._setBoolean(env, 'traceWebsocketMessagesInheritSampling', DD_TRACE_WEBSOCKET_MESSAGES_INHERIT_SAMPLING) - this._setBoolean(env, 'traceWebsocketMessagesSeparateTraces', DD_TRACE_WEBSOCKET_MESSAGES_SEPARATE_TRACES) - this._setBoolean(env, 'tracing', DD_TRACING_ENABLED) - this._setString(env, 'version', DD_VERSION || tags.version) - this._setBoolean(env, 'inferredProxyServicesEnabled', DD_TRACE_INFERRED_PROXY_SERVICES_ENABLED) - this._setBoolean(env, 'trace.aws.addSpanPointers', DD_TRACE_AWS_ADD_SPAN_POINTERS) - this._setString(env, 'trace.dynamoDb.tablePrimaryKeys', DD_TRACE_DYNAMODB_TABLE_PRIMARY_KEYS) - this._setArray(env, 'graphqlErrorExtensions', DD_TRACE_GRAPHQL_ERROR_EXTENSIONS) - this._setBoolean(env, 'trace.nativeSpanEvents', DD_TRACE_NATIVE_SPAN_EVENTS) + this.#setBoolean(env, 'traceWebsocketMessagesEnabled', DD_TRACE_WEBSOCKET_MESSAGES_ENABLED) + this.#setBoolean(env, 'traceWebsocketMessagesInheritSampling', DD_TRACE_WEBSOCKET_MESSAGES_INHERIT_SAMPLING) + this.#setBoolean(env, 'traceWebsocketMessagesSeparateTraces', DD_TRACE_WEBSOCKET_MESSAGES_SEPARATE_TRACES) + this.#setBoolean(env, 'tracing', DD_TRACING_ENABLED) + this.#setString(env, 'version', DD_VERSION || tags.version) + this.#setBoolean(env, 'inferredProxyServicesEnabled', DD_TRACE_INFERRED_PROXY_SERVICES_ENABLED) + this.#setBoolean(env, 'trace.aws.addSpanPointers', DD_TRACE_AWS_ADD_SPAN_POINTERS) + this.#setString(env, 'trace.dynamoDb.tablePrimaryKeys', DD_TRACE_DYNAMODB_TABLE_PRIMARY_KEYS) + this.#setArray(env, 'graphqlErrorExtensions', DD_TRACE_GRAPHQL_ERROR_EXTENSIONS) + this.#setBoolean(env, 'trace.nativeSpanEvents', DD_TRACE_NATIVE_SPAN_EVENTS) env['vertexai.spanPromptCompletionSampleRate'] = maybeFloat(DD_VERTEXAI_SPAN_PROMPT_COMPLETION_SAMPLE_RATE) env['vertexai.spanCharLimit'] = maybeInt(DD_VERTEXAI_SPAN_CHAR_LIMIT) } - _applyOptions (options) { - const opts = setHiddenProperty(this, '_options', this._options || {}) + #applyOptions (options) { + const opts = this.#options const tags = {} - setHiddenProperty(this, '_optsUnprocessed', {}) - options = setHiddenProperty(this, '_optionsArg', { ingestion: {}, ...options, ...opts }) + options = this.#optionsArg = { ingestion: {}, ...options, ...opts } tagger.add(tags, options.tags) - this._setBoolean(opts, 'apmTracingEnabled', options.apmTracingEnabled ?? + this.#setBoolean(opts, 'apmTracingEnabled', options.apmTracingEnabled ?? (options.experimental?.appsec?.standalone && !options.experimental.appsec.standalone.enabled) ) - this._setBoolean(opts, 'appsec.apiSecurity.enabled', options.appsec?.apiSecurity?.enabled) - this._setBoolean(opts, 'appsec.apiSecurity.endpointCollectionEnabled', + this.#setBoolean(opts, 'appsec.apiSecurity.enabled', options.appsec?.apiSecurity?.enabled) + this.#setBoolean(opts, 'appsec.apiSecurity.endpointCollectionEnabled', options.appsec?.apiSecurity?.endpointCollectionEnabled) opts['appsec.apiSecurity.endpointCollectionMessageLimit'] = maybeInt(options.appsec?.apiSecurity?.endpointCollectionMessageLimit) opts['appsec.blockedTemplateGraphql'] = maybeFile(options.appsec?.blockedTemplateGraphql) opts['appsec.blockedTemplateHtml'] = maybeFile(options.appsec?.blockedTemplateHtml) - this._optsUnprocessed['appsec.blockedTemplateHtml'] = options.appsec?.blockedTemplateHtml + this.#optsUnprocessed['appsec.blockedTemplateHtml'] = options.appsec?.blockedTemplateHtml opts['appsec.blockedTemplateJson'] = maybeFile(options.appsec?.blockedTemplateJson) - this._optsUnprocessed['appsec.blockedTemplateJson'] = options.appsec?.blockedTemplateJson - this._setBoolean(opts, 'appsec.enabled', options.appsec?.enabled) - this._setString(opts, 'appsec.eventTracking.mode', options.appsec?.eventTracking?.mode) - this._setBoolean( + this.#optsUnprocessed['appsec.blockedTemplateJson'] = options.appsec?.blockedTemplateJson + this.#setBoolean(opts, 'appsec.enabled', options.appsec?.enabled) + this.#setString(opts, 'appsec.eventTracking.mode', options.appsec?.eventTracking?.mode) + this.#setBoolean( opts, 'appsec.extendedHeadersCollection.enabled', options.appsec?.extendedHeadersCollection?.enabled ) - this._setBoolean( + this.#setBoolean( opts, 'appsec.extendedHeadersCollection.redaction', options.appsec?.extendedHeadersCollection?.redaction ) opts['appsec.extendedHeadersCollection.maxHeaders'] = options.appsec?.extendedHeadersCollection?.maxHeaders - this._setString(opts, 'appsec.obfuscatorKeyRegex', options.appsec?.obfuscatorKeyRegex) - this._setString(opts, 'appsec.obfuscatorValueRegex', options.appsec?.obfuscatorValueRegex) - this._setBoolean(opts, 'appsec.rasp.enabled', options.appsec?.rasp?.enabled) - this._setBoolean(opts, 'appsec.rasp.bodyCollection', options.appsec?.rasp?.bodyCollection) + this.#setString(opts, 'appsec.obfuscatorKeyRegex', options.appsec?.obfuscatorKeyRegex) + this.#setString(opts, 'appsec.obfuscatorValueRegex', options.appsec?.obfuscatorValueRegex) + this.#setBoolean(opts, 'appsec.rasp.enabled', options.appsec?.rasp?.enabled) + this.#setBoolean(opts, 'appsec.rasp.bodyCollection', options.appsec?.rasp?.bodyCollection) opts['appsec.rateLimit'] = maybeInt(options.appsec?.rateLimit) - this._optsUnprocessed['appsec.rateLimit'] = options.appsec?.rateLimit - this._setString(opts, 'appsec.rules', options.appsec?.rules) - this._setBoolean(opts, 'appsec.stackTrace.enabled', options.appsec?.stackTrace?.enabled) + this.#optsUnprocessed['appsec.rateLimit'] = options.appsec?.rateLimit + this.#setString(opts, 'appsec.rules', options.appsec?.rules) + this.#setBoolean(opts, 'appsec.stackTrace.enabled', options.appsec?.stackTrace?.enabled) opts['appsec.stackTrace.maxDepth'] = maybeInt(options.appsec?.stackTrace?.maxDepth) - this._optsUnprocessed['appsec.stackTrace.maxDepth'] = options.appsec?.stackTrace?.maxDepth + this.#optsUnprocessed['appsec.stackTrace.maxDepth'] = options.appsec?.stackTrace?.maxDepth opts['appsec.stackTrace.maxStackTraces'] = maybeInt(options.appsec?.stackTrace?.maxStackTraces) - this._optsUnprocessed['appsec.stackTrace.maxStackTraces'] = options.appsec?.stackTrace?.maxStackTraces + this.#optsUnprocessed['appsec.stackTrace.maxStackTraces'] = options.appsec?.stackTrace?.maxStackTraces opts['appsec.wafTimeout'] = maybeInt(options.appsec?.wafTimeout) - this._optsUnprocessed['appsec.wafTimeout'] = options.appsec?.wafTimeout - this._setBoolean(opts, 'clientIpEnabled', options.clientIpEnabled) - this._setString(opts, 'clientIpHeader', options.clientIpHeader?.toLowerCase()) + this.#optsUnprocessed['appsec.wafTimeout'] = options.appsec?.wafTimeout + this.#setBoolean(opts, 'clientIpEnabled', options.clientIpEnabled) + this.#setString(opts, 'clientIpHeader', options.clientIpHeader?.toLowerCase()) opts.baggageMaxBytes = options.baggageMaxBytes opts.baggageMaxItems = options.baggageMaxItems opts.baggageTagKeys = options.baggageTagKeys - this._setBoolean(opts, 'codeOriginForSpans.enabled', options.codeOriginForSpans?.enabled) - this._setBoolean( + this.#setBoolean(opts, 'codeOriginForSpans.enabled', options.codeOriginForSpans?.enabled) + this.#setBoolean( opts, 'codeOriginForSpans.experimental.exit_spans.enabled', options.codeOriginForSpans?.experimental?.exit_spans?.enabled ) - this._setString(opts, 'dbmPropagationMode', options.dbmPropagationMode) + this.#setString(opts, 'dbmPropagationMode', options.dbmPropagationMode) if (options.dogstatsd) { - this._setString(opts, 'dogstatsd.hostname', options.dogstatsd.hostname) - this._setString(opts, 'dogstatsd.port', options.dogstatsd.port) + this.#setString(opts, 'dogstatsd.hostname', options.dogstatsd.hostname) + this.#setString(opts, 'dogstatsd.port', options.dogstatsd.port) } - this._setBoolean(opts, 'dsmEnabled', options.dsmEnabled) - this._setBoolean(opts, 'dynamicInstrumentation.enabled', options.dynamicInstrumentation?.enabled) - this._setString(opts, 'dynamicInstrumentation.probeFile', options.dynamicInstrumentation?.probeFile) - this._setArray( + this.#setBoolean(opts, 'dsmEnabled', options.dsmEnabled) + this.#setBoolean(opts, 'dynamicInstrumentation.enabled', options.dynamicInstrumentation?.enabled) + this.#setString(opts, 'dynamicInstrumentation.probeFile', options.dynamicInstrumentation?.probeFile) + this.#setArray( opts, 'dynamicInstrumentation.redactedIdentifiers', options.dynamicInstrumentation?.redactedIdentifiers ) - this._setArray( + this.#setArray( opts, 'dynamicInstrumentation.redactionExcludedIdentifiers', options.dynamicInstrumentation?.redactionExcludedIdentifiers ) opts['dynamicInstrumentation.uploadIntervalSeconds'] = maybeFloat(options.dynamicInstrumentation?.uploadIntervalSeconds) - this._optsUnprocessed['dynamicInstrumentation.uploadIntervalSeconds'] = + this.#optsUnprocessed['dynamicInstrumentation.uploadIntervalSeconds'] = options.dynamicInstrumentation?.uploadIntervalSeconds - this._setString(opts, 'env', options.env || tags.env) - this._setBoolean(opts, 'experimental.aiguard.enabled', options.experimental?.aiguard?.enabled) - this._setString(opts, 'experimental.aiguard.endpoint', options.experimental?.aiguard?.endpoint) + this.#setString(opts, 'env', options.env || tags.env) + this.#setBoolean(opts, 'experimental.aiguard.enabled', options.experimental?.aiguard?.enabled) + this.#setString(opts, 'experimental.aiguard.endpoint', options.experimental?.aiguard?.endpoint) opts['experimental.aiguard.maxMessagesLength'] = maybeInt(options.experimental?.aiguard?.maxMessagesLength) - this._optsUnprocessed['experimental.aiguard.maxMessagesLength'] = options.experimental?.aiguard?.maxMessagesLength + this.#optsUnprocessed['experimental.aiguard.maxMessagesLength'] = options.experimental?.aiguard?.maxMessagesLength opts['experimental.aiguard.maxContentSize'] = maybeInt(options.experimental?.aiguard?.maxContentSize) - this._optsUnprocessed['experimental.aiguard.maxContentSize'] = options.experimental?.aiguard?.maxContentSize + this.#optsUnprocessed['experimental.aiguard.maxContentSize'] = options.experimental?.aiguard?.maxContentSize opts['experimental.aiguard.timeout'] = maybeInt(options.experimental?.aiguard?.timeout) - this._optsUnprocessed['experimental.aiguard.timeout'] = options.experimental?.aiguard?.timeout - this._setBoolean(opts, 'experimental.enableGetRumData', options.experimental?.enableGetRumData) - this._setString(opts, 'experimental.exporter', options.experimental?.exporter) - this._setBoolean(opts, 'experimental.flaggingProvider.enabled', options.experimental?.flaggingProvider?.enabled) + this.#optsUnprocessed['experimental.aiguard.timeout'] = options.experimental?.aiguard?.timeout + this.#setBoolean(opts, 'experimental.enableGetRumData', options.experimental?.enableGetRumData) + this.#setString(opts, 'experimental.exporter', options.experimental?.exporter) + this.#setBoolean(opts, 'experimental.flaggingProvider.enabled', options.experimental?.flaggingProvider?.enabled) opts.flushInterval = maybeInt(options.flushInterval) - this._optsUnprocessed.flushInterval = options.flushInterval + this.#optsUnprocessed.flushInterval = options.flushInterval opts.flushMinSpans = maybeInt(options.flushMinSpans) - this._optsUnprocessed.flushMinSpans = options.flushMinSpans - this._setArray(opts, 'headerTags', options.headerTags) - this._setString(opts, 'hostname', options.hostname) + this.#optsUnprocessed.flushMinSpans = options.flushMinSpans + this.#setArray(opts, 'headerTags', options.headerTags) + this.#setString(opts, 'hostname', options.hostname) opts['iast.dbRowsToTaint'] = maybeInt(options.iast?.dbRowsToTaint) - this._setBoolean(opts, 'iast.deduplicationEnabled', options.iast && options.iast.deduplicationEnabled) - this._setBoolean(opts, 'iast.enabled', + this.#setBoolean(opts, 'iast.deduplicationEnabled', options.iast && options.iast.deduplicationEnabled) + this.#setBoolean(opts, 'iast.enabled', options.iast && (options.iast === true || options.iast.enabled === true)) opts['iast.maxConcurrentRequests'] = maybeInt(options.iast?.maxConcurrentRequests) - this._optsUnprocessed['iast.maxConcurrentRequests'] = options.iast?.maxConcurrentRequests + this.#optsUnprocessed['iast.maxConcurrentRequests'] = options.iast?.maxConcurrentRequests 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) + 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) { + if (iastRequestSampling !== undefined && iastRequestSampling > -1 && iastRequestSampling < 101) { opts['iast.requestSampling'] = iastRequestSampling - this._optsUnprocessed['iast.requestSampling'] = options.iast?.requestSampling + this.#optsUnprocessed['iast.requestSampling'] = options.iast?.requestSampling } opts['iast.securityControlsConfiguration'] = options.iast?.securityControlsConfiguration - this._setBoolean(opts, 'iast.stackTrace.enabled', options.iast?.stackTrace?.enabled) - this._setString(opts, 'iast.telemetryVerbosity', options.iast && options.iast.telemetryVerbosity) - this._setBoolean(opts, 'isCiVisibility', options.isCiVisibility) - this._setBoolean(opts, 'legacyBaggageEnabled', options.legacyBaggageEnabled) - this._setBoolean(opts, 'llmobs.agentlessEnabled', options.llmobs?.agentlessEnabled) - this._setString(opts, 'llmobs.mlApp', options.llmobs?.mlApp) - this._setBoolean(opts, 'logInjection', options.logInjection) + this.#setBoolean(opts, 'iast.stackTrace.enabled', options.iast?.stackTrace?.enabled) + this.#setString(opts, 'iast.telemetryVerbosity', options.iast && options.iast.telemetryVerbosity) + this.#setBoolean(opts, 'isCiVisibility', options.isCiVisibility) + this.#setBoolean(opts, 'legacyBaggageEnabled', options.legacyBaggageEnabled) + this.#setBoolean(opts, 'llmobs.agentlessEnabled', options.llmobs?.agentlessEnabled) + this.#setString(opts, 'llmobs.mlApp', options.llmobs?.mlApp) + this.#setBoolean(opts, 'logInjection', options.logInjection) opts.lookup = options.lookup - this._setBoolean(opts, 'middlewareTracingEnabled', options.middlewareTracingEnabled) - this._setBoolean(opts, 'openAiLogsEnabled', options.openAiLogsEnabled) + this.#setBoolean(opts, 'middlewareTracingEnabled', options.middlewareTracingEnabled) + this.#setBoolean(opts, 'openAiLogsEnabled', options.openAiLogsEnabled) opts.peerServiceMapping = options.peerServiceMapping - this._setBoolean(opts, 'plugins', options.plugins) - this._setString(opts, 'port', options.port) + this.#setBoolean(opts, 'plugins', options.plugins) + this.#setString(opts, 'port', options.port) const strProfiling = String(options.profiling) if (['true', 'false', 'auto'].includes(strProfiling)) { - this._setString(opts, 'profiling.enabled', strProfiling) + this.#setString(opts, 'profiling.enabled', strProfiling) } - this._setString(opts, 'protocolVersion', options.protocolVersion) + this.#setString(opts, 'protocolVersion', options.protocolVersion) if (options.remoteConfig) { opts['remoteConfig.pollInterval'] = maybeFloat(options.remoteConfig.pollInterval) - this._optsUnprocessed['remoteConfig.pollInterval'] = options.remoteConfig.pollInterval + this.#optsUnprocessed['remoteConfig.pollInterval'] = options.remoteConfig.pollInterval } - this._setBoolean(opts, 'reportHostname', options.reportHostname) - this._setBoolean(opts, 'runtimeMetrics.enabled', options.runtimeMetrics?.enabled) - this._setBoolean(opts, 'runtimeMetrics.eventLoop', options.runtimeMetrics?.eventLoop) - this._setBoolean(opts, 'runtimeMetrics.gc', options.runtimeMetrics?.gc) - this._setBoolean(opts, 'runtimeMetricsRuntimeId', options.runtimeMetricsRuntimeId) - this._setArray(opts, 'sampler.spanSamplingRules', reformatSpanSamplingRules(options.spanSamplingRules)) - this._setUnit(opts, 'sampleRate', options.sampleRate ?? options.ingestion.sampleRate) + this.#setBoolean(opts, 'reportHostname', options.reportHostname) + this.#setBoolean(opts, 'runtimeMetrics.enabled', options.runtimeMetrics?.enabled) + this.#setBoolean(opts, 'runtimeMetrics.eventLoop', options.runtimeMetrics?.eventLoop) + this.#setBoolean(opts, 'runtimeMetrics.gc', options.runtimeMetrics?.gc) + this.#setBoolean(opts, 'runtimeMetricsRuntimeId', options.runtimeMetricsRuntimeId) + this.#setArray(opts, 'sampler.spanSamplingRules', reformatSpanSamplingRules(options.spanSamplingRules)) + this.#setUnit(opts, 'sampleRate', options.sampleRate ?? options.ingestion.sampleRate) opts['sampler.rateLimit'] = maybeInt(options.rateLimit ?? options.ingestion.rateLimit) - this._setSamplingRule(opts, 'sampler.rules', options.samplingRules) - this._setString(opts, 'service', options.service || tags.service) + this.#setSamplingRule(opts, 'sampler.rules', options.samplingRules) + this.#setString(opts, 'service', options.service || tags.service) opts.serviceMapping = options.serviceMapping - this._setString(opts, 'site', options.site) + this.#setString(opts, 'site', options.site) if (options.spanAttributeSchema) { - this._setString(opts, 'spanAttributeSchema', validateNamingVersion(options.spanAttributeSchema)) - this._optsUnprocessed.spanAttributeSchema = options.spanAttributeSchema + this.#setString(opts, 'spanAttributeSchema', validateNamingVersion(options.spanAttributeSchema)) + this.#optsUnprocessed.spanAttributeSchema = options.spanAttributeSchema } - this._setBoolean(opts, 'spanRemoveIntegrationFromService', options.spanRemoveIntegrationFromService) - this._setBoolean(opts, 'startupLogs', options.startupLogs) - this._setTags(opts, 'tags', tags) - this._setBoolean(opts, 'traceId128BitGenerationEnabled', options.traceId128BitGenerationEnabled) - this._setBoolean(opts, 'traceId128BitLoggingEnabled', options.traceId128BitLoggingEnabled) - this._setBoolean(opts, 'traceWebsocketMessagesEnabled', options.traceWebsocketMessagesEnabled) - this._setBoolean(opts, 'traceWebsocketMessagesInheritSampling', options.traceWebsocketMessagesInheritSampling) - this._setBoolean(opts, 'traceWebsocketMessagesSeparateTraces', options.traceWebsocketMessagesSeparateTraces) - this._setString(opts, 'version', options.version || tags.version) - this._setBoolean(opts, 'inferredProxyServicesEnabled', options.inferredProxyServicesEnabled) - this._setBoolean(opts, 'graphqlErrorExtensions', options.graphqlErrorExtensions) - this._setBoolean(opts, 'trace.nativeSpanEvents', options.trace?.nativeSpanEvents) + this.#setBoolean(opts, 'spanRemoveIntegrationFromService', options.spanRemoveIntegrationFromService) + this.#setBoolean(opts, 'startupLogs', options.startupLogs) + this.#setTags(opts, 'tags', tags) + this.#setBoolean(opts, 'traceId128BitGenerationEnabled', options.traceId128BitGenerationEnabled) + this.#setBoolean(opts, 'traceId128BitLoggingEnabled', options.traceId128BitLoggingEnabled) + this.#setBoolean(opts, 'traceWebsocketMessagesEnabled', options.traceWebsocketMessagesEnabled) + this.#setBoolean(opts, 'traceWebsocketMessagesInheritSampling', options.traceWebsocketMessagesInheritSampling) + this.#setBoolean(opts, 'traceWebsocketMessagesSeparateTraces', options.traceWebsocketMessagesSeparateTraces) + this.#setString(opts, 'version', options.version || tags.version) + this.#setBoolean(opts, 'inferredProxyServicesEnabled', options.inferredProxyServicesEnabled) + this.#setBoolean(opts, 'graphqlErrorExtensions', options.graphqlErrorExtensions) + this.#setBoolean(opts, 'trace.nativeSpanEvents', options.trace?.nativeSpanEvents) // For LLMObs, we want the environment variable to take precedence over the options. // This is reliant on environment config being set before options. // This is to make sure the origins of each value are tracked appropriately for telemetry. // We'll only set `llmobs.enabled` on the opts when it's not set on the environment, and options.llmobs is provided. - const llmobsEnabledEnv = this._env['llmobs.enabled'] + const llmobsEnabledEnv = this.#env['llmobs.enabled'] if (llmobsEnabledEnv == null && options.llmobs) { - this._setBoolean(opts, 'llmobs.enabled', !!options.llmobs) + this.#setBoolean(opts, 'llmobs.enabled', !!options.llmobs) } } - _isCiVisibility () { - return this._optionsArg.isCiVisibility ?? this._defaults.isCiVisibility + #isCiVisibility () { + return this.#optionsArg.isCiVisibility ?? this.#defaults.isCiVisibility } - _isCiVisibilityItrEnabled () { - return getEnvironmentVariable('DD_CIVISIBILITY_ITR_ENABLED') ?? true + #isCiVisibilityItrEnabled () { + return getEnv('DD_CIVISIBILITY_ITR_ENABLED') ?? true } - _getHostname () { - const DD_CIVISIBILITY_AGENTLESS_URL = getEnvironmentVariable('DD_CIVISIBILITY_AGENTLESS_URL') + #getHostname () { + const DD_CIVISIBILITY_AGENTLESS_URL = getEnv('DD_CIVISIBILITY_AGENTLESS_URL') const url = DD_CIVISIBILITY_AGENTLESS_URL ? new URL(DD_CIVISIBILITY_AGENTLESS_URL) - : getAgentUrl(this._getTraceAgentUrl(), this._optionsArg) - const DD_AGENT_HOST = this._optionsArg.hostname ?? - getEnvironmentVariable('DD_AGENT_HOST') ?? + : getAgentUrl(this._getTraceAgentUrl(), this.#optionsArg) + const DD_AGENT_HOST = this.#optionsArg.hostname ?? + getEnv('DD_AGENT_HOST') ?? defaults.hostname return DD_AGENT_HOST || url?.hostname } - _getSpanComputePeerService () { + #getSpanComputePeerService () { const DD_TRACE_SPAN_ATTRIBUTE_SCHEMA = validateNamingVersion( - this._optionsArg.spanAttributeSchema ?? - getEnvironmentVariable('DD_TRACE_SPAN_ATTRIBUTE_SCHEMA') + this.#optionsArg.spanAttributeSchema ?? + getEnv('DD_TRACE_SPAN_ATTRIBUTE_SCHEMA') ) const peerServiceSet = ( - this._optionsArg.hasOwnProperty('spanComputePeerService') || - getEnvironmentVariables().hasOwnProperty('DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED') + this.#optionsArg.hasOwnProperty('spanComputePeerService') || + getEnv('DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED') !== undefined ) - const peerServiceValue = this._optionsArg.spanComputePeerService ?? - getEnvironmentVariable('DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED') + const peerServiceValue = this.#optionsArg.spanComputePeerService ?? + getEnv('DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED') const spanComputePeerService = ( DD_TRACE_SPAN_ATTRIBUTE_SCHEMA === 'v0' @@ -1109,91 +1124,78 @@ class Config { return spanComputePeerService } - _isCiVisibilityGitUploadEnabled () { - return getEnvironmentVariable('DD_CIVISIBILITY_GIT_UPLOAD_ENABLED') ?? true - } - - _isCiVisibilityManualApiEnabled () { - return getEnvironmentVariable('DD_CIVISIBILITY_MANUAL_API_ENABLED') ?? true - } - - _isTraceStatsComputationEnabled () { - const apmTracingEnabled = this._options.apmTracingEnabled !== false && - this._env.apmTracingEnabled !== false + #isTraceStatsComputationEnabled () { + const apmTracingEnabled = this.#options.apmTracingEnabled !== false && + this.#env.apmTracingEnabled !== false return apmTracingEnabled && ( - this._optionsArg.stats ?? - getEnvironmentVariable('DD_TRACE_STATS_COMPUTATION_ENABLED') ?? + this.#optionsArg.stats ?? + getEnv('DD_TRACE_STATS_COMPUTATION_ENABLED') ?? (getIsGCPFunction() || getIsAzureFunction()) ) } _getTraceAgentUrl () { - return this._optionsArg.url ?? - getEnvironmentVariable('DD_TRACE_AGENT_URL') ?? + return this.#optionsArg.url ?? + getEnv('DD_TRACE_AGENT_URL') ?? null } // handles values calculated from a mixture of options and env vars - _applyCalculated () { - const calc = setHiddenProperty(this, '_calculated', {}) + #applyCalculated () { + const calc = this.#calculated - const { - DD_CIVISIBILITY_AGENTLESS_URL, - DD_CIVISIBILITY_EARLY_FLAKE_DETECTION_ENABLED, - DD_CIVISIBILITY_FLAKY_RETRY_ENABLED, - DD_CIVISIBILITY_FLAKY_RETRY_COUNT, - DD_TEST_SESSION_NAME, - DD_AGENTLESS_LOG_SUBMISSION_ENABLED, - DD_TEST_FAILED_TEST_REPLAY_ENABLED, - DD_TEST_MANAGEMENT_ENABLED, - DD_TEST_MANAGEMENT_ATTEMPT_TO_FIX_RETRIES, - DD_CIVISIBILITY_IMPACTED_TESTS_DETECTION_ENABLED - } = getEnvironmentVariables() + const DD_CIVISIBILITY_AGENTLESS_URL = getEnv('DD_CIVISIBILITY_AGENTLESS_URL') calc.url = DD_CIVISIBILITY_AGENTLESS_URL ? new URL(DD_CIVISIBILITY_AGENTLESS_URL) - : getAgentUrl(this._getTraceAgentUrl(), this._optionsArg) - if (this._isCiVisibility()) { - this._setBoolean(calc, 'isEarlyFlakeDetectionEnabled', DD_CIVISIBILITY_EARLY_FLAKE_DETECTION_ENABLED ?? true) - this._setBoolean(calc, 'isFlakyTestRetriesEnabled', DD_CIVISIBILITY_FLAKY_RETRY_ENABLED ?? true) - calc.flakyTestRetriesCount = maybeInt(DD_CIVISIBILITY_FLAKY_RETRY_COUNT) ?? 5 - this._setBoolean(calc, 'isIntelligentTestRunnerEnabled', isTrue(this._isCiVisibilityItrEnabled())) - this._setBoolean(calc, 'isManualApiEnabled', !isFalse(this._isCiVisibilityManualApiEnabled())) - this._setString(calc, 'ciVisibilityTestSessionName', DD_TEST_SESSION_NAME) - this._setBoolean(calc, 'ciVisAgentlessLogSubmissionEnabled', isTrue(DD_AGENTLESS_LOG_SUBMISSION_ENABLED)) - this._setBoolean(calc, 'isTestDynamicInstrumentationEnabled', !isFalse(DD_TEST_FAILED_TEST_REPLAY_ENABLED)) - this._setBoolean(calc, 'isServiceUserProvided', !!this._env.service) - this._setBoolean(calc, 'isTestManagementEnabled', !isFalse(DD_TEST_MANAGEMENT_ENABLED)) - calc.testManagementAttemptToFixRetries = maybeInt(DD_TEST_MANAGEMENT_ATTEMPT_TO_FIX_RETRIES) ?? 20 - this._setBoolean(calc, 'isImpactedTestsEnabled', !isFalse(DD_CIVISIBILITY_IMPACTED_TESTS_DETECTION_ENABLED)) + : getAgentUrl(this._getTraceAgentUrl(), this.#optionsArg) + + if (this.#isCiVisibility()) { + this.#setBoolean(calc, 'isEarlyFlakeDetectionEnabled', + getEnv('DD_CIVISIBILITY_EARLY_FLAKE_DETECTION_ENABLED') ?? true) + this.#setBoolean(calc, 'isFlakyTestRetriesEnabled', getEnv('DD_CIVISIBILITY_FLAKY_RETRY_ENABLED') ?? true) + calc.flakyTestRetriesCount = maybeInt(getEnv('DD_CIVISIBILITY_FLAKY_RETRY_COUNT')) ?? 5 + this.#setBoolean(calc, 'isIntelligentTestRunnerEnabled', isTrue(this.#isCiVisibilityItrEnabled())) + this.#setBoolean(calc, 'isManualApiEnabled', !isFalse(getEnv('DD_CIVISIBILITY_MANUAL_API_ENABLED'))) + this.#setString(calc, 'ciVisibilityTestSessionName', getEnv('DD_TEST_SESSION_NAME')) + this.#setBoolean(calc, 'ciVisAgentlessLogSubmissionEnabled', + isTrue(getEnv('DD_AGENTLESS_LOG_SUBMISSION_ENABLED'))) + this.#setBoolean(calc, 'isTestDynamicInstrumentationEnabled', + !isFalse(getEnv('DD_TEST_FAILED_TEST_REPLAY_ENABLED'))) + this.#setBoolean(calc, 'isServiceUserProvided', !!this.#env.service) + this.#setBoolean(calc, 'isTestManagementEnabled', !isFalse(getEnv('DD_TEST_MANAGEMENT_ENABLED'))) + calc.testManagementAttemptToFixRetries = maybeInt(getEnv('DD_TEST_MANAGEMENT_ATTEMPT_TO_FIX_RETRIES')) ?? 20 + this.#setBoolean(calc, 'isImpactedTestsEnabled', + !isFalse(getEnv('DD_CIVISIBILITY_IMPACTED_TESTS_DETECTION_ENABLED'))) } // Disable log injection when OTEL logs are enabled // OTEL logs and DD log injection are mutually exclusive - if (this._env.otelLogsEnabled) { - this._setBoolean(calc, 'logInjection', false) + if (this.#env.otelLogsEnabled) { + this.#setBoolean(calc, 'logInjection', false) } - calc['dogstatsd.hostname'] = this._getHostname() + calc['dogstatsd.hostname'] = this.#getHostname() // Compute OTLP logs URL to send payloads to the active Datadog Agent - const agentHostname = this._getHostname() + const agentHostname = this.#getHostname() calc.otelLogsUrl = `http://${agentHostname}:${DEFAULT_OTLP_PORT}` calc.otelUrl = `http://${agentHostname}:${DEFAULT_OTLP_PORT}` - this._setBoolean(calc, 'isGitUploadEnabled', - calc.isIntelligentTestRunnerEnabled && !isFalse(this._isCiVisibilityGitUploadEnabled())) - this._setBoolean(calc, 'spanComputePeerService', this._getSpanComputePeerService()) - this._setBoolean(calc, 'stats.enabled', this._isTraceStatsComputationEnabled()) - const defaultPropagationStyle = this._getDefaultPropagationStyle(this._optionsArg) + this.#setBoolean(calc, 'isGitUploadEnabled', + calc.isIntelligentTestRunnerEnabled && !isFalse(getEnv('DD_CIVISIBILITY_GIT_UPLOAD_ENABLED'))) + + this.#setBoolean(calc, 'spanComputePeerService', this.#getSpanComputePeerService()) + this.#setBoolean(calc, 'stats.enabled', this.#isTraceStatsComputationEnabled()) + const defaultPropagationStyle = this.#getDefaultPropagationStyle(this.#optionsArg) calc['tracePropagationStyle.inject'] = propagationStyle( 'inject', - this._optionsArg.tracePropagationStyle + this.#optionsArg.tracePropagationStyle ) calc['tracePropagationStyle.extract'] = propagationStyle( 'extract', - this._optionsArg.tracePropagationStyle + this.#optionsArg.tracePropagationStyle ) if (defaultPropagationStyle.length > 2) { calc['tracePropagationStyle.inject'] = calc['tracePropagationStyle.inject'] || defaultPropagationStyle @@ -1201,9 +1203,8 @@ class Config { } } - _applyRemote (options) { - const opts = setHiddenProperty(this, '_remote', this._remote || {}) - setHiddenProperty(this, '_remoteUnprocessed', {}) + #applyRemote (options) { + const opts = this.#remote const tags = {} const headerTags = options.tracing_header_tags ? options.tracing_header_tags.map(tag => { @@ -1214,20 +1215,20 @@ class Config { tagger.add(tags, options.tracing_tags) if (Object.keys(tags).length) tags['runtime-id'] = runtimeId - this._setUnit(opts, 'sampleRate', options.tracing_sampling_rate) - this._setBoolean(opts, 'logInjection', options.log_injection_enabled) + this.#setUnit(opts, 'sampleRate', options.tracing_sampling_rate) + this.#setBoolean(opts, 'logInjection', options.log_injection_enabled) opts.headerTags = headerTags - this._setTags(opts, 'tags', tags) - this._setBoolean(opts, 'tracing', options.tracing_enabled) - this._remoteUnprocessed['sampler.rules'] = options.tracing_sampling_rules - this._setSamplingRule(opts, 'sampler.rules', this._reformatTags(options.tracing_sampling_rules)) + this.#setTags(opts, 'tags', tags) + this.#setBoolean(opts, 'tracing', options.tracing_enabled) + this.#remoteUnprocessed['sampler.rules'] = options.tracing_sampling_rules + this.#setSamplingRule(opts, 'sampler.rules', this.#reformatTags(options.tracing_sampling_rules)) } - _reformatTags (samplingRules) { + #reformatTags (samplingRules) { for (const rule of (samplingRules || [])) { const reformattedTags = {} if (rule.tags) { - for (const tag of (rule.tags || {})) { + for (const tag of rule.tags) { reformattedTags[tag.key] = tag.value_glob } rule.tags = reformattedTags @@ -1236,7 +1237,7 @@ class Config { return samplingRules } - _setBoolean (obj, name, value) { + #setBoolean (obj, name, value) { if (value === undefined || value === null) { obj[name] = value } else if (isTrue(value)) { @@ -1246,7 +1247,7 @@ class Config { } } - _setUnit (obj, name, value) { + #setUnit (obj, name, value) { if (value === null || value === undefined) { obj[name] = value return @@ -1260,7 +1261,7 @@ class Config { } } - _setArray (obj, name, value) { + #setArray (obj, name, value) { if (value == null) { obj[name] = null return @@ -1279,7 +1280,7 @@ class Config { } } - _setIntegerRangeSet (obj, name, value) { + #setIntegerRangeSet (obj, name, value) { if (value == null) { obj[name] = null return @@ -1300,7 +1301,7 @@ class Config { obj[name] = result } - _setSamplingRule (obj, name, value) { + #setSamplingRule (obj, name, value) { if (value == null) { obj[name] = null return @@ -1320,11 +1321,11 @@ class Config { } } - _setString (obj, name, value) { + #setString (obj, name, value) { obj[name] = value ? String(value) : undefined // unset for empty strings } - _setTags (obj, name, value) { + #setTags (obj, name, value) { if (!value || Object.keys(value).length === 0) { obj[name] = null return @@ -1333,7 +1334,7 @@ class Config { obj[name] = value } - _setAndTrackChange ({ name, value, origin, unprocessedValue, changes }) { + #setAndTrackChange ({ name, value, origin, unprocessedValue, changes }) { set(this, name, value) if (!changeTracker[name]) { @@ -1358,21 +1359,21 @@ class Config { // TODO: Move change tracking to telemetry. // for telemetry reporting, `name`s in `containers` need to be keys from: // https://github.com/DataDog/dd-go/blob/prod/trace/apps/tracer-telemetry-intake/telemetry-payload/static/config_norm_rules.json - _merge () { + #merge () { const changes = [] + const sources = this.#getSourcesInOrder() - for (const name in this._defaults) { + for (const name of Object.keys(this.#defaults)) { // Use reverse order for merge (lowest priority first) - for (let i = sourcesOrder.length - 1; i >= 0; i--) { - const { containerProperty, origin, unprocessedProperty } = sourcesOrder[i] - const container = this[containerProperty] + for (let i = sources.length - 1; i >= 0; i--) { + const { container, origin, unprocessed } = sources[i] const value = container[name] - if (value != null || container === this._defaults) { - this._setAndTrackChange({ + if (value != null || container === this.#defaults) { + this.#setAndTrackChange({ name, value, origin, - unprocessedValue: unprocessedProperty === undefined ? undefined : this[unprocessedProperty][name], + unprocessedValue: unprocessed?.[name], changes }) } @@ -1383,34 +1384,33 @@ class Config { } getOrigin (name) { - for (const { containerProperty, origin } of sourcesOrder) { - const container = this[containerProperty] + for (const { container, origin } of this.#getSourcesInOrder()) { const value = container[name] - if (value != null || container === this._defaults) { + if (value != null || container === this.#defaults) { return origin } } } - _loadGitMetadata () { + #loadGitMetadata (envs) { // try to read Git metadata from the environment variables this.repositoryUrl = removeUserSensitiveInfo( - getEnvironmentVariable('DD_GIT_REPOSITORY_URL') ?? + envs.DD_GIT_REPOSITORY_URL ?? this.tags[GIT_REPOSITORY_URL] ) - this.commitSHA = getEnvironmentVariable('DD_GIT_COMMIT_SHA') ?? + this.commitSHA = envs.DD_GIT_COMMIT_SHA ?? this.tags[GIT_COMMIT_SHA] // otherwise, try to read Git metadata from the git.properties file if (!this.repositoryUrl || !this.commitSHA) { - const DD_GIT_PROPERTIES_FILE = getEnvironmentVariable('DD_GIT_PROPERTIES_FILE') ?? + const DD_GIT_PROPERTIES_FILE = envs.DD_GIT_PROPERTIES_FILE ?? `${process.cwd()}/git.properties` let gitPropertiesString try { gitPropertiesString = fs.readFileSync(DD_GIT_PROPERTIES_FILE, 'utf8') } catch (e) { // Only log error if the user has set a git.properties path - if (getEnvironmentVariable('DD_GIT_PROPERTIES_FILE')) { + if (envs.DD_GIT_PROPERTIES_FILE) { log.error('Error reading DD_GIT_PROPERTIES_FILE: %s', DD_GIT_PROPERTIES_FILE, e) } } @@ -1422,7 +1422,7 @@ class Config { } // otherwise, try to read Git metadata from the .git/ folder if (!this.repositoryUrl || !this.commitSHA) { - const DD_GIT_FOLDER_PATH = getEnvironmentVariable('DD_GIT_FOLDER_PATH') ?? + const DD_GIT_FOLDER_PATH = envs.DD_GIT_FOLDER_PATH ?? path.join(process.cwd(), '.git') if (!this.repositoryUrl) { // try to read git config (repository URL) @@ -1434,7 +1434,7 @@ class Config { } } catch (e) { // Only log error if the user has set a .git/ path - if (getEnvironmentVariable('DD_GIT_FOLDER_PATH')) { + if (envs.DD_GIT_FOLDER_PATH) { log.error('Error reading git config: %s', gitConfigPath, e) } } @@ -1483,21 +1483,12 @@ function getAgentUrl (url, options) { if ( !options.hostname && !options.port && - !getEnvironmentVariable('DD_AGENT_HOST') && - !getEnvironmentVariable('DD_TRACE_AGENT_PORT') && + !getEnv('DD_AGENT_HOST') && + !getEnv('DD_TRACE_AGENT_PORT') && fs.existsSync('/var/run/datadog/apm.socket') ) { return new URL('unix:///var/run/datadog/apm.socket') } } -function setHiddenProperty (obj, name, value) { - Object.defineProperty(obj, name, { - value, - enumerable: false, - writable: true - }) - return obj[name] -} - module.exports = Config diff --git a/packages/dd-trace/src/config_defaults.js b/packages/dd-trace/src/config_defaults.js index 79740b44d37..996aa27ec4a 100644 --- a/packages/dd-trace/src/config_defaults.js +++ b/packages/dd-trace/src/config_defaults.js @@ -2,7 +2,7 @@ const pkg = require('./pkg') const { GRPC_CLIENT_ERROR_STATUSES, GRPC_SERVER_ERROR_STATUSES } = require('./constants') -const { getEnvironmentVariables } = require('./config-helper') +const { getEnvironmentVariable: getEnv } = require('./config-helper') // eslint-disable-next-line @stylistic/max-len const qsRegex = String.raw`(?: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)?)(?:(?:\s|%20)*(?:=|%3D)[^&]+|(?:"|%22)(?:\s|%20)*(?::|%3A)(?:\s|%20)*(?:"|%22)(?:%2[^2]|%[^2]|[^"%])+(?:"|%22))|bearer(?:\s|%20)+[a-z0-9\._\-]+|token(?::|%3A)[a-z0-9]{13}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L](?:[\w=-]|%3D)+\.ey[I-L](?:[\w=-]|%3D)+(?:\.(?:[\w.+\/=-]|%3D|%2F|%2B)+)?|[\-]{5}BEGIN(?:[a-z\s]|%20)+PRIVATE(?:\s|%20)KEY[\-]{5}[^\-]+[\-]{5}END(?:[a-z\s]|%20)+PRIVATE(?:\s|%20)KEY|ssh-rsa(?:\s|%20)*(?:[a-z0-9\/\.+]|%2F|%5C|%2B){100,}` @@ -11,17 +11,10 @@ const defaultWafObfuscatorKeyRegex = String.raw`(?i)pass|pw(?:or)?d|secret|(?:ap // eslint-disable-next-line @stylistic/max-len const defaultWafObfuscatorValueRegex = String.raw`(?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*("[^"]+"|\d+))|bearer\s+([a-z0-9\._\-]+)|token\s*:\s*([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,})` -const { - AWS_LAMBDA_FUNCTION_NAME, - FUNCTION_NAME, - K_SERVICE, - WEBSITE_SITE_NAME -} = getEnvironmentVariables() - -const service = AWS_LAMBDA_FUNCTION_NAME || - FUNCTION_NAME || // Google Cloud Function Name set by deprecated runtimes - K_SERVICE || // Google Cloud Function Name set by newer runtimes - WEBSITE_SITE_NAME || // set by Azure Functions +const service = getEnv('AWS_LAMBDA_FUNCTION_NAME') || + getEnv('FUNCTION_NAME') || // Google Cloud Function Name set by deprecated runtimes + getEnv('K_SERVICE') || // Google Cloud Function Name set by newer runtimes + getEnv('WEBSITE_SITE_NAME') || // set by Azure Functions pkg.name || 'node' diff --git a/packages/dd-trace/src/plugins/util/ci.js b/packages/dd-trace/src/plugins/util/ci.js index 8c35a7f7b89..814bd4fd4d9 100644 --- a/packages/dd-trace/src/plugins/util/ci.js +++ b/packages/dd-trace/src/plugins/util/ci.js @@ -95,10 +95,11 @@ function normalizeNumber (number) { } function getGitHubEventPayload () { - if (!getEnvironmentVariable('GITHUB_EVENT_PATH')) { + const path = getEnvironmentVariable('GITHUB_EVENT_PATH') + if (!path) { return } - return JSON.parse(readFileSync(getEnvironmentVariable('GITHUB_EVENT_PATH'), 'utf8')) + return JSON.parse(readFileSync(path, 'utf8')) } module.exports = { diff --git a/packages/dd-trace/src/supported-configurations.json b/packages/dd-trace/src/supported-configurations.json index eedd5534c41..22e5aaa28c5 100644 --- a/packages/dd-trace/src/supported-configurations.json +++ b/packages/dd-trace/src/supported-configurations.json @@ -282,6 +282,7 @@ "DD_TRACE_FASTIFY_ENABLED": ["A"], "DD_TRACE_FETCH_ENABLED": ["A"], "DD_TRACE_FIND_MY_WAY_ENABLED": ["A"], + "DD_TRACE_FLUSH_INTERVAL": ["A"], "DD_TRACE_FS_ENABLED": ["A"], "DD_TRACE_GENERIC_POOL_ENABLED": ["A"], "DD_TRACE_GIT_METADATA_ENABLED": ["A"], diff --git a/packages/dd-trace/src/telemetry/index.js b/packages/dd-trace/src/telemetry/index.js index cd05b23de2c..31f4d138612 100644 --- a/packages/dd-trace/src/telemetry/index.js +++ b/packages/dd-trace/src/telemetry/index.js @@ -1,23 +1,26 @@ 'use strict' -const activate = () => { - const active = require('./telemetry') +let telemetry - return Object.setPrototypeOf(module.exports, active) -} - -const inactive = { +// Lazy load the telemetry module to avoid the performance impact of loading it unconditionally +module.exports = { start (config, ...args) { - return config?.telemetry?.enabled && activate().start(config, ...args) + telemetry ??= require('./telemetry') + telemetry.start(config, ...args) + }, + stop () { + telemetry?.stop() }, - stop () {}, // This might be called before `start` so we have to trigger loading the // underlying module here as well. updateConfig (changes, config, ...args) { - return config?.telemetry?.enabled && activate().updateConfig(changes, config, ...args) + telemetry ??= require('./telemetry') + telemetry.updateConfig(changes, config, ...args) }, - updateIntegrations () {}, - appClosing () {} + updateIntegrations () { + telemetry?.updateIntegrations() + }, + appClosing () { + telemetry?.appClosing() + } } - -module.exports = Object.setPrototypeOf({}, inactive) diff --git a/packages/dd-trace/test/config.spec.js b/packages/dd-trace/test/config.spec.js index d03f44217c7..37019a8ff75 100644 --- a/packages/dd-trace/test/config.spec.js +++ b/packages/dd-trace/test/config.spec.js @@ -1503,6 +1503,7 @@ describe('Config', () => { process.env.DD_TRACE_GLOBAL_TAGS = 'foo:bar,baz:qux' process.env.DD_TRACE_MIDDLEWARE_TRACING_ENABLED = 'false' process.env.DD_TRACE_PARTIAL_FLUSH_MIN_SPANS = '2000' + process.env.DD_TRACE_FLUSH_INTERVAL = '2000' process.env.DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED = 'false' process.env.DD_TRACE_PEER_SERVICE_MAPPING = 'c:cc' process.env.DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT = 'restart' @@ -1583,6 +1584,7 @@ describe('Config', () => { enableGetRumData: false }, flushMinSpans: 500, + flushInterval: 500, hostname: 'server', iast: { dbRowsToTaint: 3, @@ -1673,6 +1675,7 @@ describe('Config', () => { expect(config).to.have.nested.property('experimental.enableGetRumData', false) expect(config).to.have.nested.property('experimental.exporter', 'agent') expect(config).to.have.property('flushMinSpans', 500) + expect(config).to.have.property('flushInterval', 500) expect(config).to.have.nested.property('iast.dbRowsToTaint', 3) expect(config).to.have.nested.property('iast.deduplicationEnabled', true) expect(config).to.have.nested.property('iast.enabled', true) diff --git a/packages/dd-trace/test/llmobs/cassettes/anthropic/anthropic_v1_messages_post_08bab2b6.yaml b/packages/dd-trace/test/llmobs/cassettes/anthropic/anthropic_v1_messages_post_08bab2b6.yaml new file mode 100644 index 00000000000..1a6cfa16396 --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/anthropic/anthropic_v1_messages_post_08bab2b6.yaml @@ -0,0 +1,120 @@ +interactions: +- request: + body: '{"model":"claude-3-5-sonnet-20241022","stream":false,"max_tokens":4096,"thinking":{"type":"disabled"},"messages":[{"role":"user","content":"Hello!"}]}' + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip, deflate + ? !!python/object/apply:multidict._multidict.istr + - Accept-Language + : - '*' + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '150' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - Anthropic/JS 0.65.0 + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Arch + : - arm64 + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Lang + : - js + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-OS + : - MacOS + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Package-Version + : - 0.65.0 + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Retry-Count + : - '0' + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Runtime + : - node + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Runtime-Version + : - v22.17.0 + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Timeout + : - '600' + ? !!python/object/apply:multidict._multidict.istr + - anthropic-dangerous-direct-browser-access + : - 'true' + ? !!python/object/apply:multidict._multidict.istr + - anthropic-version + : - '2023-06-01' + ? !!python/object/apply:multidict._multidict.istr + - sec-fetch-mode + : - cors + method: POST + uri: https://api.anthropic.com/v1/messages + response: + body: + string: '{"model":"claude-3-5-sonnet-20241022","id":"msg_01LWSczDEYTJkFxkvGr4q1kc","type":"message","role":"assistant","content":[{"type":"text","text":"Hi + there! How can I help you today?"}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":9,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":13,"service_tier":"standard"}}' + headers: + CF-RAY: + - 9922ea6108b5d684-IAD + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 21 Oct 2025 18:46:04 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Robots-Tag: + - none + anthropic-organization-id: + - 4257e925-ee99-4ee8-9c62-8e53716d5203 + anthropic-ratelimit-input-tokens-limit: + - '5000000' + anthropic-ratelimit-input-tokens-remaining: + - '5000000' + anthropic-ratelimit-input-tokens-reset: + - '2025-10-21T18:46:04Z' + anthropic-ratelimit-output-tokens-limit: + - '1000000' + anthropic-ratelimit-output-tokens-remaining: + - '1000000' + anthropic-ratelimit-output-tokens-reset: + - '2025-10-21T18:46:04Z' + anthropic-ratelimit-requests-limit: + - '10000' + anthropic-ratelimit-requests-remaining: + - '9999' + anthropic-ratelimit-requests-reset: + - '2025-10-21T18:46:03Z' + anthropic-ratelimit-tokens-limit: + - '6000000' + anthropic-ratelimit-tokens-remaining: + - '6000000' + anthropic-ratelimit-tokens-reset: + - '2025-10-21T18:46:04Z' + cf-cache-status: + - DYNAMIC + request-id: + - req_011CULqgdUBEuBvMTzJvKKFM + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + via: + - 1.1 google + x-envoy-upstream-service-time: + - '1000' + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/plugins/langchain/index.spec.js b/packages/dd-trace/test/llmobs/plugins/langchain/index.spec.js index 87daad754e7..5fd8fc80ac5 100644 --- a/packages/dd-trace/test/llmobs/plugins/langchain/index.spec.js +++ b/packages/dd-trace/test/llmobs/plugins/langchain/index.spec.js @@ -17,6 +17,8 @@ const { } = require('../../util') const chai = require('chai') +const semifies = require('semifies') + chai.Assertion.addMethod('deepEqualWithMockValues', deepEqualWithMockValues) const isDdTrace = iastFilter.isDdTrace @@ -95,7 +97,7 @@ describe('integrations', () => { iastFilter.isDdTrace = isDdTrace }) - withVersions('langchain', ['@langchain/core'], version => { + withVersions('langchain', ['@langchain/core'], (version, _, realVersion) => { describe('langchain', () => { beforeEach(() => { langchainOpenai = require(`../../../../../../versions/langchain@${version}`) @@ -117,9 +119,15 @@ describe('integrations', () => { .get('@langchain/core/tools') .tool - MemoryVectorStore = require(`../../../../../../versions/@langchain/core@${version}`) - .get('langchain/vectorstores/memory') - .MemoryVectorStore + if (semifies(realVersion, '>=1.0')) { + MemoryVectorStore = require('../../../../../../versions/@langchain/classic@>=1.0') + .get('@langchain/classic/vectorstores/memory') + .MemoryVectorStore + } else { + MemoryVectorStore = require(`../../../../../../versions/langchain@${version}`) + .get('langchain/vectorstores/memory') + .MemoryVectorStore + } }) describe('llm', () => { @@ -621,7 +629,8 @@ describe('integrations', () => { expect(secondLLMSpanEvent).to.deepEqualWithMockValues(expectedSecondLLM) }) - it('submits workflow and llm spans for a batched chain', async () => { + // flaky test, skipping for now and will follow up in a different PR + it.skip('submits workflow and llm spans for a batched chain', async () => { const prompt = langchainPrompts.ChatPromptTemplate.fromTemplate( 'Tell me a joke about {topic}' ) diff --git a/packages/dd-trace/test/plugins/externals.json b/packages/dd-trace/test/plugins/externals.json index 0baf5410237..e8e4adeaa06 100644 --- a/packages/dd-trace/test/plugins/externals.json +++ b/packages/dd-trace/test/plugins/externals.json @@ -347,6 +347,10 @@ { "name": "langchain", "versions": [">=0.1"] + }, + { + "name": "@langchain/classic", + "versions": [">=1.0"] } ], "ldapjs": [ diff --git a/packages/dd-trace/test/plugins/versions/package.json b/packages/dd-trace/test/plugins/versions/package.json index d29e5b00033..c1b5ae77843 100644 --- a/packages/dd-trace/test/plugins/versions/package.json +++ b/packages/dd-trace/test/plugins/versions/package.json @@ -48,6 +48,7 @@ "@jest/transform": "30.2.0", "@koa/router": "14.0.0", "@langchain/anthropic": "0.3.32", + "@langchain/classic": "1.0.0", "@langchain/cohere": "0.3.4", "@langchain/core": "0.3.78", "@langchain/google-genai": "0.2.18", diff --git a/packages/dd-trace/test/telemetry/index.spec.js b/packages/dd-trace/test/telemetry/index.spec.js index 78d2d52f3be..63a5b2d6085 100644 --- a/packages/dd-trace/test/telemetry/index.spec.js +++ b/packages/dd-trace/test/telemetry/index.spec.js @@ -35,20 +35,6 @@ describe('telemetry (proxy)', () => { }) }) - it('should be noop when disabled', () => { - proxy.start() - proxy.updateIntegrations() - proxy.updateConfig([]) - proxy.appClosing() - proxy.stop() - - expect(telemetry.start).to.not.have.been.called - expect(telemetry.updateIntegrations).to.not.have.been.called - expect(telemetry.updateConfig).to.not.have.been.called - expect(telemetry.appClosing).to.not.have.been.called - expect(telemetry.stop).to.not.have.been.called - }) - it('should proxy when enabled', () => { const config = { telemetry: { enabled: true } } @@ -117,7 +103,13 @@ describe('telemetry', () => { foo2: { _enabled: true }, bar2: { _enabled: false } } - + /** + * @type {Object} CircularObject + * @property {string} field + * @property {Object} child + * @property {string} child.field + * @property {CircularObject | null} child.parent + */ const circularObject = { child: { parent: null, field: 'child_value' }, field: 'parent_value' diff --git a/scripts/flakiness.mjs b/scripts/flakiness.mjs index f39f82ad18f..90d52b91f76 100644 --- a/scripts/flakiness.mjs +++ b/scripts/flakiness.mjs @@ -39,6 +39,7 @@ let totalCount = 0 let flakeCount = 0 async function checkWorkflowRuns (id, page = 1) { + // This only gets the last attempt of every run. const response = await octokit.rest.actions.listWorkflowRuns({ owner: 'DataDog', repo: 'dd-trace-js', @@ -68,7 +69,7 @@ async function checkWorkflowRuns (id, page = 1) { flakeCount++ - promises.push(checkWorkflowJobs(run.id)) + promises.push(checkWorkflowJobs(run.id, run.run_attempt - 1)) } promises.push(checkWorkflowRuns(id, page + 1)) @@ -76,9 +77,11 @@ async function checkWorkflowRuns (id, page = 1) { return Promise.all(promises) } -async function checkWorkflowJobs (id, page = 1) { +async function checkWorkflowJobs (id, attempt, page = 1) { + if (attempt < 1) return + const response = await octokit.rest.actions.listJobsForWorkflowRunAttempt({ - attempt_number: 1, // ignore other attempts to keep things simple + attempt_number: attempt, owner: 'DataDog', repo: 'dd-trace-js', run_id: id, @@ -88,9 +91,6 @@ async function checkWorkflowJobs (id, page = 1) { const { jobs } = response.data - // We've reached the last page and there are no more results. - if (jobs.length === 0) return - for (const job of jobs) { if (job.conclusion !== 'failure') continue @@ -107,7 +107,13 @@ async function checkWorkflowJobs (id, page = 1) { } } - return checkWorkflowJobs(id, page + 1) + // We've reached the last page and there are no more results. + if (jobs.length < 100) { + // Check previous attempt to include successive failures. + return checkWorkflowJobs(id, attempt - 1) + } + + return checkWorkflowJobs(id, attempt, page + 1) } await Promise.all(workflows.map(w => checkWorkflowRuns(w))) diff --git a/scripts/rerun.js b/scripts/rerun.js new file mode 100644 index 00000000000..08771d65bc1 --- /dev/null +++ b/scripts/rerun.js @@ -0,0 +1,27 @@ +'use strict' + +// Example to rerun the Serverless workflow 30 times on the current branch: +// GITHUB_TOKEN=$(ddtool auth github token) WORKFLOW=Serverless node scripts/rerun + +const { execSync } = require('child_process') + +const { + ATTEMPTS = 30, + BRANCH, + INTERVAL = 600, + WORKFLOW +} = process.env + +function rerun (current = 1) { + const branch = BRANCH || execSync('git rev-parse --abbrev-ref HEAD').toString().trim() + const result = execSync(`gh run ls -b ${branch} -w ${WORKFLOW}`).toString() + const id = result.match(/\d{11}/)[0] + + execSync(`gh run rerun ${id} --repo DataDog/dd-trace-js || exit 0`) + + if (current >= ATTEMPTS) return + + setTimeout(() => rerun(current + 1), INTERVAL * 1000) +} + +rerun()