Skip to content

Commit 776f68f

Browse files
BaptisteFoysabrenner
authored andcommitted
Support Hand-Off Config (#5126)
1 parent b90979a commit 776f68f

File tree

7 files changed

+400
-35
lines changed

7 files changed

+400
-35
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@
8282
"node": ">=18"
8383
},
8484
"dependencies": {
85-
"@datadog/libdatadog": "^0.4.0",
85+
"@datadog/libdatadog": "^0.5.0",
8686
"@datadog/native-appsec": "8.4.0",
8787
"@datadog/native-iast-rewriter": "2.8.0",
8888
"@datadog/native-iast-taint-tracking": "3.3.0",

packages/dd-trace/src/config.js

+95-16
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const tagger = require('./tagger')
1111
const get = require('../../datadog-core/src/utils/src/get')
1212
const has = require('../../datadog-core/src/utils/src/has')
1313
const set = require('../../datadog-core/src/utils/src/set')
14-
const { isTrue, isFalse } = require('./util')
14+
const { isTrue, isFalse, normalizeProfilingEnabledValue } = require('./util')
1515
const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('./plugins/util/tags')
1616
const { getGitMetadataFromGitProperties, removeUserSensitiveInfo } = require('./git_properties')
1717
const { updateConfig } = require('./telemetry')
@@ -236,6 +236,12 @@ function reformatSpanSamplingRules (rules) {
236236

237237
class Config {
238238
constructor (options = {}) {
239+
if (!isInServerlessEnvironment()) {
240+
// Bail out early if we're in a serverless environment, stable config isn't supported
241+
const StableConfig = require('./config_stable')
242+
this.stableConfig = new StableConfig()
243+
}
244+
239245
options = {
240246
...options,
241247
appsec: options.appsec != null ? options.appsec : options.experimental?.appsec,
@@ -244,13 +250,24 @@ class Config {
244250

245251
// Configure the logger first so it can be used to warn about other configs
246252
const logConfig = log.getConfig()
247-
this.debug = logConfig.enabled
253+
this.debug = log.isEnabled(
254+
this.stableConfig?.fleetEntries?.DD_TRACE_DEBUG,
255+
this.stableConfig?.localEntries?.DD_TRACE_DEBUG
256+
)
248257
this.logger = coalesce(options.logger, logConfig.logger)
249-
this.logLevel = coalesce(options.logLevel, logConfig.logLevel)
250-
258+
this.logLevel = log.getLogLevel(
259+
options.logLevel,
260+
this.stableConfig?.fleetEntries?.DD_TRACE_LOG_LEVEL,
261+
this.stableConfig?.localEntries?.DD_TRACE_LOG_LEVEL
262+
)
251263
log.use(this.logger)
252264
log.toggle(this.debug, this.logLevel)
253265

266+
// Process stable config warnings, if any
267+
for (const warning of this.stableConfig?.warnings ?? []) {
268+
log.warn(warning)
269+
}
270+
254271
checkIfBothOtelAndDdEnvVarSet()
255272

256273
const DD_API_KEY = coalesce(
@@ -337,7 +354,9 @@ class Config {
337354
}
338355

339356
this._applyDefaults()
357+
this._applyLocalStableConfig()
340358
this._applyEnvironment()
359+
this._applyFleetStableConfig()
341360
this._applyOptions(options)
342361
this._applyCalculated()
343362
this._applyRemote({})
@@ -576,6 +595,45 @@ class Config {
576595
this._setValue(defaults, 'trace.dynamoDb.tablePrimaryKeys', undefined)
577596
}
578597

598+
_applyLocalStableConfig () {
599+
const obj = setHiddenProperty(this, '_localStableConfig', {})
600+
this._applyStableConfig(this.stableConfig?.localEntries ?? {}, obj)
601+
}
602+
603+
_applyFleetStableConfig () {
604+
const obj = setHiddenProperty(this, '_fleetStableConfig', {})
605+
this._applyStableConfig(this.stableConfig?.fleetEntries ?? {}, obj)
606+
}
607+
608+
_applyStableConfig (config, obj) {
609+
const {
610+
DD_APPSEC_ENABLED,
611+
DD_APPSEC_SCA_ENABLED,
612+
DD_DATA_STREAMS_ENABLED,
613+
DD_DYNAMIC_INSTRUMENTATION_ENABLED,
614+
DD_ENV,
615+
DD_IAST_ENABLED,
616+
DD_LOGS_INJECTION,
617+
DD_PROFILING_ENABLED,
618+
DD_RUNTIME_METRICS_ENABLED,
619+
DD_SERVICE,
620+
DD_VERSION
621+
} = config
622+
623+
this._setBoolean(obj, 'appsec.enabled', DD_APPSEC_ENABLED)
624+
this._setBoolean(obj, 'appsec.sca.enabled', DD_APPSEC_SCA_ENABLED)
625+
this._setBoolean(obj, 'dsmEnabled', DD_DATA_STREAMS_ENABLED)
626+
this._setBoolean(obj, 'dynamicInstrumentation.enabled', DD_DYNAMIC_INSTRUMENTATION_ENABLED)
627+
this._setString(obj, 'env', DD_ENV)
628+
this._setBoolean(obj, 'iast.enabled', DD_IAST_ENABLED)
629+
this._setBoolean(obj, 'logInjection', DD_LOGS_INJECTION)
630+
const profilingEnabled = normalizeProfilingEnabledValue(DD_PROFILING_ENABLED)
631+
this._setString(obj, 'profiling.enabled', profilingEnabled)
632+
this._setBoolean(obj, 'runtimeMetrics', DD_RUNTIME_METRICS_ENABLED)
633+
this._setString(obj, 'service', DD_SERVICE)
634+
this._setString(obj, 'version', DD_VERSION)
635+
}
636+
579637
_applyEnvironment () {
580638
const {
581639
AWS_LAMBDA_FUNCTION_NAME,
@@ -831,16 +889,13 @@ class Config {
831889
this._envUnprocessed.peerServiceMapping = DD_TRACE_PEER_SERVICE_MAPPING
832890
}
833891
this._setString(env, 'port', DD_TRACE_AGENT_PORT)
834-
const profilingEnabledEnv = coalesce(
835-
DD_EXPERIMENTAL_PROFILING_ENABLED,
836-
DD_PROFILING_ENABLED,
837-
this._isInServerlessEnvironment() ? 'false' : undefined
892+
const profilingEnabled = normalizeProfilingEnabledValue(
893+
coalesce(
894+
DD_EXPERIMENTAL_PROFILING_ENABLED,
895+
DD_PROFILING_ENABLED,
896+
this._isInServerlessEnvironment() ? 'false' : undefined
897+
)
838898
)
839-
const profilingEnabled = isTrue(profilingEnabledEnv)
840-
? 'true'
841-
: isFalse(profilingEnabledEnv)
842-
? 'false'
843-
: profilingEnabledEnv === 'auto' ? 'auto' : undefined
844899
this._setString(env, 'profiling.enabled', profilingEnabled)
845900
this._setString(env, 'profiling.exporters', DD_PROFILING_EXPORTERS)
846901
this._setBoolean(env, 'profiling.sourceMap', DD_PROFILING_SOURCE_MAP && !isFalse(DD_PROFILING_SOURCE_MAP))
@@ -1347,9 +1402,33 @@ class Config {
13471402
// eslint-disable-next-line @stylistic/js/max-len
13481403
// https://github.com/DataDog/dd-go/blob/prod/trace/apps/tracer-telemetry-intake/telemetry-payload/static/config_norm_rules.json
13491404
_merge () {
1350-
const containers = [this._remote, this._options, this._env, this._calculated, this._defaults]
1351-
const origins = ['remote_config', 'code', 'env_var', 'calculated', 'default']
1352-
const unprocessedValues = [this._remoteUnprocessed, this._optsUnprocessed, this._envUnprocessed, {}, {}]
1405+
const containers = [
1406+
this._remote,
1407+
this._options,
1408+
this._fleetStableConfig,
1409+
this._env,
1410+
this._localStableConfig,
1411+
this._calculated,
1412+
this._defaults
1413+
]
1414+
const origins = [
1415+
'remote_config',
1416+
'code',
1417+
'fleet_stable_config',
1418+
'env_var',
1419+
'local_stable_config',
1420+
'calculated',
1421+
'default'
1422+
]
1423+
const unprocessedValues = [
1424+
this._remoteUnprocessed,
1425+
this._optsUnprocessed,
1426+
{},
1427+
this._envUnprocessed,
1428+
{},
1429+
{},
1430+
{}
1431+
]
13531432
const changes = []
13541433

13551434
for (const name in this._defaults) {
+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
const os = require('os')
2+
const fs = require('fs')
3+
4+
class StableConfig {
5+
constructor () {
6+
this.warnings = [] // Logger hasn't been initialized yet, so we can't use log.warn
7+
this.localEntries = {}
8+
this.fleetEntries = {}
9+
this.wasm_loaded = false
10+
11+
const { localConfigPath, fleetConfigPath } = this._getStableConfigPaths()
12+
if (!fs.existsSync(localConfigPath) && !fs.existsSync(fleetConfigPath)) {
13+
// Bail out early if files don't exist to avoid unnecessary library loading
14+
return
15+
}
16+
17+
const localConfig = this._readConfigFromPath(localConfigPath)
18+
const fleetConfig = this._readConfigFromPath(fleetConfigPath)
19+
if (!localConfig && !fleetConfig) {
20+
// Bail out early if files are empty or we can't read them to avoid unnecessary library loading
21+
return
22+
}
23+
24+
// Note: we don't enforce loading because there may be cases where the library is not available and we
25+
// want to avoid breaking the application. In those cases, we will not have the file-based configuration.
26+
let libdatadog
27+
try {
28+
libdatadog = require('@datadog/libdatadog')
29+
this.wasm_loaded = true
30+
} catch (e) {
31+
this.warnings.push('Can\'t load libdatadog library')
32+
return
33+
}
34+
35+
const libconfig = libdatadog.maybeLoad('library_config')
36+
if (libconfig === undefined) {
37+
this.warnings.push('Can\'t load library_config library')
38+
return
39+
}
40+
41+
try {
42+
const configurator = new libconfig.JsConfigurator()
43+
configurator.set_envp(Object.entries(process.env).map(([key, value]) => `${key}=${value}`))
44+
configurator.set_args(process.argv)
45+
configurator.get_configuration(localConfig.toString(), fleetConfig.toString()).forEach((entry) => {
46+
if (entry.source === 'local_stable_config') {
47+
this.localEntries[entry.name] = entry.value
48+
} else if (entry.source === 'fleet_stable_config') {
49+
this.fleetEntries[entry.name] = entry.value
50+
}
51+
})
52+
} catch (e) {
53+
this.warnings.push(`Error parsing configuration from file: ${e.message}`)
54+
}
55+
}
56+
57+
_readConfigFromPath (path) {
58+
try {
59+
return fs.readFileSync(path, 'utf8')
60+
} catch (err) {
61+
if (err.code !== 'ENOENT') {
62+
this.warnings.push(`Error reading config file at ${path}. ${err.code}: ${err.message}`)
63+
}
64+
return '' // Always return a string to avoid undefined.toString() errors
65+
}
66+
}
67+
68+
_getStableConfigPaths () {
69+
let localConfigPath = ''
70+
let fleetConfigPath = ''
71+
switch (os.type().toLowerCase()) {
72+
case 'linux':
73+
localConfigPath = '/etc/datadog-agent/application_monitoring.yaml'
74+
fleetConfigPath = '/etc/datadog-agent/managed/datadog-agent/stable/application_monitoring.yaml'
75+
break
76+
case 'darwin':
77+
localConfigPath = '/opt/datadog-agent/etc/application_monitoring.yaml'
78+
fleetConfigPath = '/opt/datadog-agent/etc/managed/datadog-agent/stable/application_monitoring.yaml'
79+
break
80+
case 'win32':
81+
localConfigPath = 'C:\\ProgramData\\Datadog\\application_monitoring.yaml'
82+
fleetConfigPath = 'C:\\ProgramData\\Datadog\\managed\\datadog-agent\\stable\\application_monitoring.yaml'
83+
break
84+
default:
85+
break
86+
}
87+
88+
// Allow overriding the paths for testing
89+
if (process.env.DD_TEST_LOCAL_CONFIG_PATH !== undefined) {
90+
localConfigPath = process.env.DD_TEST_LOCAL_CONFIG_PATH
91+
}
92+
if (process.env.DD_TEST_FLEET_CONFIG_PATH !== undefined) {
93+
fleetConfigPath = process.env.DD_TEST_FLEET_CONFIG_PATH
94+
}
95+
96+
return { localConfigPath, fleetConfigPath }
97+
}
98+
}
99+
100+
module.exports = StableConfig

packages/dd-trace/src/log/index.js

+26-13
Original file line numberDiff line numberDiff line change
@@ -105,23 +105,36 @@ const log = {
105105

106106
deprecate (code, message) {
107107
return this._deprecate(code, message)
108+
},
109+
110+
isEnabled (fleetStableConfigValue = undefined, localStableConfigValue = undefined) {
111+
return isTrue(coalesce(
112+
fleetStableConfigValue,
113+
process.env?.DD_TRACE_DEBUG,
114+
process.env?.OTEL_LOG_LEVEL === 'debug' || undefined,
115+
localStableConfigValue,
116+
config.enabled
117+
))
118+
},
119+
120+
getLogLevel (
121+
optionsValue = undefined,
122+
fleetStableConfigValue = undefined,
123+
localStableConfigValue = undefined
124+
) {
125+
return coalesce(
126+
optionsValue,
127+
fleetStableConfigValue,
128+
process.env?.DD_TRACE_LOG_LEVEL,
129+
process.env?.OTEL_LOG_LEVEL,
130+
localStableConfigValue,
131+
config.logLevel
132+
)
108133
}
109134
}
110135

111136
log.reset()
112137

113-
const enabled = isTrue(coalesce(
114-
process.env.DD_TRACE_DEBUG,
115-
process.env.OTEL_LOG_LEVEL === 'debug',
116-
config.enabled
117-
))
118-
119-
const logLevel = coalesce(
120-
process.env.DD_TRACE_LOG_LEVEL,
121-
process.env.OTEL_LOG_LEVEL,
122-
config.logLevel
123-
)
124-
125-
log.toggle(enabled, logLevel)
138+
log.toggle(log.isEnabled(), log.getLogLevel())
126139

127140
module.exports = log

packages/dd-trace/src/util.js

+10-1
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,20 @@ function hasOwn (object, prop) {
7979
return Object.prototype.hasOwnProperty.call(object, prop)
8080
}
8181

82+
function normalizeProfilingEnabledValue (configValue) {
83+
return isTrue(configValue)
84+
? 'true'
85+
: isFalse(configValue)
86+
? 'false'
87+
: configValue === 'auto' ? 'auto' : undefined
88+
}
89+
8290
module.exports = {
8391
isTrue,
8492
isFalse,
8593
isError,
8694
globMatch,
8795
calculateDDBasePath,
88-
hasOwn
96+
hasOwn,
97+
normalizeProfilingEnabledValue
8998
}

0 commit comments

Comments
 (0)