Skip to content

Commit b8401e5

Browse files
committed
refactor(config): parse and resolve defaults in getValueFromEnvSources (#8925)
getValueFromEnvSources returned the raw environment string, so every caller re-implemented the boolean/int/array parsing Config already does and those copies drifted. It now maps aliases to the canonical option, runs Config's own parser and transformer, and returns the typed value, falling back to the registered default when unset. Callers that must distinguish an unset variable from an explicitly configured one pass skipDefault and receive undefined instead of the default. 1. Resolving a value pulls in config/defaults, which transitively requires the instrumented dns module -> the dns plugin -> log -> back into config/defaults before it finished exporting. config/defaults now defers its dns require until after module.exports and log requires config/defaults eagerly again, breaking the cycle at its source. ritm reads DD_LAMBDA_HANDLER through the new getConfiguredEnvName, a presence check that never parses and so cannot re-enter the require hook. 2. skipDefault keeps the unset case meaningful where the registered default would otherwise mask it: the OTEL_TRACES_EXPORTER fallback in index.js, experimental plugin enablement in plugin_manager, the LLMObs programmatic opt-in, and the OTel SDK gate. 3. The env helpers no longer tolerate a missing table entry. Every production caller passes a registered name, so an unregistered DD_/OTEL_ name is a programming error that throws rather than silently returning a raw string. 4. log.configure() recomputes enabled and logLevel from options, env, and defaults on every call instead of retaining the previous call's values.
1 parent e0c3a2d commit b8401e5

21 files changed

Lines changed: 940 additions & 128 deletions

File tree

ci/init.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
/* eslint-disable no-console */
44
const tracer = require('../packages/dd-trace')
5-
const { isTrue, isFalse } = require('../packages/dd-trace/src/util')
65
const log = require('../packages/dd-trace/src/log')
76
const { getEnvironmentVariable, getValueFromEnvSources } = require('../packages/dd-trace/src/config/helper')
87

@@ -43,8 +42,10 @@ const baseOptions = {
4342
flushInterval: isJestWorker ? JEST_FLUSH_INTERVAL : DEFAULT_FLUSH_INTERVAL,
4443
}
4544

46-
let shouldInit = !isFalse(getValueFromEnvSources('DD_CIVISIBILITY_ENABLED'))
47-
const isAgentlessEnabled = isTrue(getValueFromEnvSources('DD_CIVISIBILITY_AGENTLESS_ENABLED'))
45+
// skipDefault: CI visibility stays on unless DD_CIVISIBILITY_ENABLED is explicitly false; the
46+
// registered default (false) would otherwise turn it off whenever the variable is unset.
47+
let shouldInit = getValueFromEnvSources('DD_CIVISIBILITY_ENABLED', true) !== false
48+
const isAgentlessEnabled = getValueFromEnvSources('DD_CIVISIBILITY_AGENTLESS_ENABLED')
4849

4950
if (!isTestWorker && isPackageManager()) {
5051
log.debug('dd-trace is not initialized in a package manager.')

packages/datadog-instrumentations/src/helpers/register.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ const Hook = require('./hook')
1313
const { isRelativeRequire } = require('./shared-utils')
1414
const rewriter = require('./rewriter')
1515

16-
const DD_TRACE_DISABLED_INSTRUMENTATIONS = getValueFromEnvSources('DD_TRACE_DISABLED_INSTRUMENTATIONS') || ''
17-
const DD_TRACE_DEBUG = getValueFromEnvSources('DD_TRACE_DEBUG') || ''
16+
const DD_TRACE_DISABLED_INSTRUMENTATIONS =
17+
getValueFromEnvSources('DD_TRACE_DISABLED_INSTRUMENTATIONS')
18+
const DD_TRACE_DEBUG = getValueFromEnvSources('DD_TRACE_DEBUG')
1819

1920
const hooks = require('./hooks')
2021
const instrumentations = require('./instrumentations')
@@ -36,7 +37,7 @@ if (!disabledInstrumentations.has('process')) {
3637
require('../process')
3738
}
3839

39-
const debugEnabled = DD_TRACE_DEBUG && DD_TRACE_DEBUG.toLowerCase() !== 'false'
40+
const debugEnabled = DD_TRACE_DEBUG
4041
checkRequireCache.checkForRequiredModules(debugEnabled)
4142
if (debugEnabled) {
4243
setImmediate(checkRequireCache.checkForPotentialConflicts)

packages/datadog-instrumentations/src/otel-sdk-trace.js

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
const shimmer = require('../../datadog-shimmer')
44
const tracer = require('../../dd-trace')
55
const { getValueFromEnvSources } = require('../../dd-trace/src/config/helper')
6-
const { isFalse, isTrue } = require('../../dd-trace/src/util')
76
const { addHook } = require('./helpers/instrument')
87

98
if (isOtelSdkEnabled()) {
@@ -21,9 +20,11 @@ if (isOtelSdkEnabled()) {
2120

2221
function isOtelSdkEnabled () {
2322
// Datadog explicit opt-out wins over every OTel signal; check it first.
24-
const ddTraceOtelEnabled = getValueFromEnvSources('DD_TRACE_OTEL_ENABLED')
25-
if (isFalse(ddTraceOtelEnabled)) return false
26-
const otelSdkDisabled = getValueFromEnvSources('OTEL_SDK_DISABLED')
27-
if (isTrue(otelSdkDisabled)) return false
28-
return isTrue(ddTraceOtelEnabled) || isFalse(otelSdkDisabled)
23+
// skipDefault: an unset option must stay undefined so the OTel signal still decides — the
24+
// registered defaults (false / true) would otherwise force-disable before that check.
25+
const ddTraceOtelEnabled = getValueFromEnvSources('DD_TRACE_OTEL_ENABLED', true)
26+
if (ddTraceOtelEnabled === false) return false
27+
const otelSdkDisabled = getValueFromEnvSources('OTEL_SDK_DISABLED', true)
28+
if (otelSdkDisabled) return false
29+
return ddTraceOtelEnabled || otelSdkDisabled === false
2930
}

packages/datadog-instrumentations/src/playwright.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ const testSuiteToTestStatuses = new Map()
5959
const testSuiteToErrors = new Map()
6060
const testsToTestStatuses = new Map()
6161

62-
const RUM_FLUSH_WAIT_TIME = Number(getValueFromEnvSources('DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS')) || 500
62+
const RUM_FLUSH_WAIT_TIME = getValueFromEnvSources('DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS')
6363
const DD_PROPERTIES_TIMEOUT = 5000
6464

6565
let applyRepeatEachIndex = null

packages/datadog-instrumentations/src/selenium.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ if (window.DD_RUM && window.DD_RUM.stopSession) {
1919
`
2020
const IS_RUM_ACTIVE_SCRIPT = 'return !!window.DD_RUM'
2121

22-
const DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS =
23-
Number(getValueFromEnvSources('DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS')) || 500
22+
const DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS = getValueFromEnvSources('DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS')
2423
const DD_CIVISIBILITY_TEST_EXECUTION_ID_COOKIE_NAME = 'datadog-ci-visibility-test-execution-id'
2524

2625
// TODO: can we increase the supported version range?

packages/datadog-instrumentations/test/otel-sdk-trace.spec.js

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@ const sinon = require('sinon')
88

99
describe('otel-sdk-trace', () => {
1010
/**
11-
* Re-load `otel-sdk-trace.js` with the supplied env values stubbed in via
12-
* `getValueFromEnvSources` and report what `addHook` saw.
11+
* Re-load `otel-sdk-trace.js` with the supplied config values stubbed in via
12+
* `getValueFromEnvSources` and report what `addHook` saw. `getValueFromEnvSources`
13+
* already parses these registered boolean options, so the gate sees `true`,
14+
* `false`, or `undefined` — never a raw string. Parsing tolerance ('1', 'maybe',
15+
* '') is exercised in the config helper's own spec.
1316
*
14-
* @param {{ ddTraceOtelEnabled?: string, otelSdkDisabled?: string }} env
17+
* @param {{ ddTraceOtelEnabled?: boolean, otelSdkDisabled?: boolean }} env
1518
* @param {{ TracerProvider?: unknown }} [tracerStub]
1619
* @returns {{ addHook: sinon.SinonSpy, wrap: sinon.SinonSpy }}
1720
*/
@@ -35,48 +38,43 @@ describe('otel-sdk-trace', () => {
3538

3639
describe('gate precedence', () => {
3740
it('disables when DD_TRACE_OTEL_ENABLED is the explicit opt-out', () => {
38-
assert.equal(loadWithEnv({ ddTraceOtelEnabled: 'false' }).addHook.called, false)
39-
assert.equal(loadWithEnv({ ddTraceOtelEnabled: '0' }).addHook.called, false)
41+
assert.equal(loadWithEnv({ ddTraceOtelEnabled: false }).addHook.called, false)
4042
})
4143

4244
it('keeps DD opt-out winning even when OTEL_SDK_DISABLED=false opts in', () => {
43-
assert.equal(loadWithEnv({ ddTraceOtelEnabled: 'false', otelSdkDisabled: 'false' }).addHook.called, false)
45+
assert.equal(loadWithEnv({ ddTraceOtelEnabled: false, otelSdkDisabled: false }).addHook.called, false)
4446
})
4547

4648
it('disables when OTEL_SDK_DISABLED is the explicit opt-out', () => {
47-
assert.equal(loadWithEnv({ otelSdkDisabled: 'true' }).addHook.called, false)
48-
assert.equal(loadWithEnv({ otelSdkDisabled: '1' }).addHook.called, false)
49+
assert.equal(loadWithEnv({ otelSdkDisabled: true }).addHook.called, false)
4950
})
5051

5152
it('keeps OTel opt-out winning even when DD_TRACE_OTEL_ENABLED=true opts in', () => {
52-
assert.equal(loadWithEnv({ ddTraceOtelEnabled: 'true', otelSdkDisabled: 'true' }).addHook.called, false)
53+
assert.equal(loadWithEnv({ ddTraceOtelEnabled: true, otelSdkDisabled: true }).addHook.called, false)
5354
})
5455

5556
it('enables when DD_TRACE_OTEL_ENABLED is the explicit opt-in', () => {
56-
assert.equal(loadWithEnv({ ddTraceOtelEnabled: 'true' }).addHook.called, true)
57-
assert.equal(loadWithEnv({ ddTraceOtelEnabled: '1' }).addHook.called, true)
58-
assert.equal(loadWithEnv({ ddTraceOtelEnabled: 'true', otelSdkDisabled: 'false' }).addHook.called, true)
57+
assert.equal(loadWithEnv({ ddTraceOtelEnabled: true }).addHook.called, true)
58+
assert.equal(loadWithEnv({ ddTraceOtelEnabled: true, otelSdkDisabled: false }).addHook.called, true)
5959
})
6060

6161
it('enables when OTEL_SDK_DISABLED=false is the OTel positive opt-in', () => {
62-
assert.equal(loadWithEnv({ otelSdkDisabled: 'false' }).addHook.called, true)
63-
assert.equal(loadWithEnv({ otelSdkDisabled: '0' }).addHook.called, true)
62+
assert.equal(loadWithEnv({ otelSdkDisabled: false }).addHook.called, true)
6463
})
6564

66-
it('stays disabled by default when neither env var is set', () => {
65+
it('stays disabled by default when neither option is set', () => {
6766
assert.equal(loadWithEnv({}).addHook.called, false)
6867
})
6968

70-
it('stays disabled for unrecognized values on either side', () => {
71-
assert.equal(loadWithEnv({ ddTraceOtelEnabled: 'maybe', otelSdkDisabled: 'sure' }).addHook.called, false)
72-
assert.equal(loadWithEnv({ ddTraceOtelEnabled: '' }).addHook.called, false)
69+
it('treats a value the helper rejected (undefined) as unset', () => {
70+
assert.equal(loadWithEnv({ ddTraceOtelEnabled: undefined, otelSdkDisabled: undefined }).addHook.called, false)
7371
})
7472
})
7573

7674
describe('hook registration', () => {
7775
it('wraps NodeTracerProvider with the dd-trace TracerProvider', () => {
7876
const tracerProvider = function FakeTracerProvider () {}
79-
const { addHook, wrap } = loadWithEnv({ ddTraceOtelEnabled: 'true' }, { TracerProvider: tracerProvider })
77+
const { addHook, wrap } = loadWithEnv({ ddTraceOtelEnabled: true }, { TracerProvider: tracerProvider })
8078

8179
sinon.assert.calledOnce(addHook)
8280
const [hookOptions, transform] = addHook.firstCall.args

packages/datadog-plugin-dd-trace-api/test/index.spec.js

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,35 @@ const {
1212
} = require('../../dd-trace/src/config/supported-configurations.json')
1313

1414
const SELF = Symbol('self')
15-
const supportedConfigurationsWithDdTraceApi = {
16-
...supportedConfigurations,
17-
DD_TRACE_DD_TRACE_API_ENABLED: [
18-
{
19-
implementation: 'A',
20-
type: 'boolean',
21-
default: 'true',
22-
},
23-
],
15+
16+
// The dd-trace-api plugin is not released yet (the `before` hook fires its load event
17+
// manually below), so its DD_TRACE_DD_TRACE_API_ENABLED flag is intentionally absent from
18+
// the shipped supported-configurations.json and must never be added there. Register it in an
19+
// in-memory copy and reload both consumers of that file — config/helper and the config/defaults
20+
// table it reads — against the copy, so plugin_manager's getEnabled() resolves the flag through
21+
// real production code instead of throwing on an unknown DD_-prefixed name. Reloading helper
22+
// alone would leave the defaults table without the entry and only pass while getEnabled keeps
23+
// short-circuiting before the default lookup.
24+
const stubbedConfigurations = {
25+
supportedConfigurations: {
26+
...supportedConfigurations,
27+
DD_TRACE_DD_TRACE_API_ENABLED: [
28+
{
29+
implementation: 'A',
30+
type: 'boolean',
31+
default: 'true',
32+
},
33+
],
34+
},
2435
}
2536

2637
const configHelperPath = require.resolve('../../dd-trace/src/config/helper')
38+
const reloadedDefaults = proxyquire.noPreserveCache()(require.resolve('../../dd-trace/src/config/defaults'), {
39+
'./supported-configurations.json': stubbedConfigurations,
40+
})
2741
const reloadedConfigHelper = proxyquire.noPreserveCache()(configHelperPath, {
28-
'./supported-configurations.json': {
29-
supportedConfigurations: supportedConfigurationsWithDdTraceApi,
30-
},
42+
'./supported-configurations.json': stubbedConfigurations,
43+
'./defaults': reloadedDefaults,
3144
})
3245
Object.assign(require(configHelperPath), reloadedConfigHelper)
3346

packages/dd-trace/src/config/defaults.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
'use strict'
22

3-
const dns = require('dns')
43
const util = require('util')
54

65
const { DD_MAJOR } = require('../../../../version')
@@ -51,7 +50,6 @@ const defaults = {
5150
isServiceUserProvided: false,
5251
plugins: true,
5352
isCiVisibility: false,
54-
lookup: dns.lookup,
5553
logger: undefined,
5654
}
5755

@@ -347,3 +345,16 @@ module.exports = {
347345

348346
generateTelemetry,
349347
}
348+
349+
// `dns` is instrumented, so requiring it pulls in the dns plugin, which loads
350+
// `log`, whose bootstrap calls back into `require('./defaults')`. Resolve the
351+
// `lookup` default last — after the exports above are fully built — so that the
352+
// re-entrant require during that cascade sees the complete module instead of a
353+
// half-initialized one.
354+
defaults.lookup = require('dns').lookup
355+
configWithOrigin.set('lookupdefault', {
356+
name: 'lookup',
357+
value: defaults.lookup,
358+
origin: 'default',
359+
seq_id: seqId++,
360+
})

0 commit comments

Comments
 (0)