diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 0d5eaf21ca5..60f6303719c 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -210,6 +210,8 @@
# Language Platform
/* @DataDog/lang-platform-js
+/eslint-rules/ @DataDog/lang-platform-js
+
/integration-tests/bun/ @DataDog/lang-platform-js
/integration-tests/init.spec.js @DataDog/lang-platform-js
/integration-tests/package-guardrails.spec.js @DataDog/lang-platform-js
diff --git a/.github/workflows/project.yml b/.github/workflows/project.yml
index a348fb73a8a..471e22c1e67 100644
--- a/.github/workflows/project.yml
+++ b/.github/workflows/project.yml
@@ -52,6 +52,14 @@ jobs:
- uses: ./.github/actions/install
- run: npm run verify-exercised-tests
+ generated-config-types:
+ runs-on: ubuntu-latest
+ name: Generated config types
+ steps:
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ - uses: ./.github/actions/node/latest
+ - run: npm run verify:config:types
+
workflow-job-names:
runs-on: ubuntu-latest
name: Workflow job names (unique)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 182a99016f0..a3a03f620a2 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -466,3 +466,244 @@ $ yarn bench
```
[1]: https://docs.datadoghq.com/help
+
+## Working with Configurations
+
+`packages/dd-trace/src/config/supported-configurations.json` is the source of truth for tracer configuration metadata.
+
+When you add a new configuration here, the config system can usually derive:
+
+- default values
+- env var parsing
+- `tracer.init({...})` option mapping
+- generated config types
+- config telemetry
+
+## What A Developer Needs To Know
+
+Each entry defines:
+
+- the canonical env var name
+- the runtime type
+- the default value
+- the programmatic option path
+- optional aliases, validation, and transforms
+
+Minimal example:
+
+```json
+"DD_AGENT_HOST": [{
+ "implementation": "E",
+ "type": "string",
+ "configurationNames": ["hostname"],
+ "default": "127.0.0.1",
+ "aliases": ["DD_TRACE_AGENT_HOSTNAME"]
+}]
+```
+
+Important fields:
+
+- `type`: parser to use for environment variables. Common values are `string`, `boolean`, `integer`, `decimal`, `array`, `map`, `json`.
+- `default`: parsed into the runtime type. `null` means `undefined` at runtime.
+- `configurationNames`: programmatic option names. The first entry becomes the main internal property path.
+- `internalPropertyName`: use this instead of `configurationNames` when the runtime property path should differ from the public option name.
+- `transform`: extra conversion after parsing. This applies to both env vars and programmatic options.
+- `allowed`: whitelist of accepted values.
+- `aliases`: old or alternate env var names.
+- `deprecated`: emits a deprecation warning when used.
+- `description`: developer-facing note in the JSON.
+- `implementation`: metadata only in the current flow.
+
+## Runtime Flow
+
+```mermaid
+flowchart LR
+ A["supported-configurations.json"] --> B["defaults.js
build defaults + lookup tables"]
+ A --> C["helper.js
aliases + deprecations"]
+ A --> D["generate-config-types.js
generated-config-types.d.ts"]
+ B --> E["config/index.js"]
+ C --> E
+ E --> F["Config singleton"]
+ E --> G["Config telemetry"]
+ H["remote_config.js"] --> E
+```
+
+Load order in `config/index.js`:
+
+1. defaults
+2. local stable config
+3. env vars
+4. fleet stable config
+5. `tracer.init({...})` options
+6. calculated values
+
+## Examples That Matter
+
+### Simple boolean
+
+```json
+"DD_RUNTIME_METRICS_ENABLED": [{
+ "type": "boolean",
+ "configurationNames": ["runtimeMetrics.enabled", "runtimeMetrics"],
+ "default": "false"
+}]
+```
+
+Both of these work:
+
+```js
+tracer.init({ runtimeMetrics: true })
+```
+
+```js
+tracer.init({
+ runtimeMetrics: {
+ enabled: true
+ }
+})
+```
+
+Result:
+
+```js
+config.runtimeMetrics.enabled === true
+```
+
+### Decimal with transform
+
+```json
+"DD_TRACE_SAMPLE_RATE": [{
+ "type": "decimal",
+ "configurationNames": ["sampleRate", "ingestion.sampleRate"],
+ "default": null,
+ "transform": "sampleRate"
+}]
+```
+
+The `sampleRate` transform validates and clamps the value to the supported `0..1` range.
+
+### Array with transform
+
+```json
+"DD_TRACE_HEADER_TAGS": [{
+ "type": "array",
+ "configurationNames": ["headerTags"],
+ "default": "",
+ "transform": "stripColonWhitespace"
+}]
+```
+
+This matters because the transform is reused for both input styles:
+
+```bash
+DD_TRACE_HEADER_TAGS="x-user-id : user.id, x-team : team"
+```
+
+```js
+tracer.init({
+ headerTags: ['x-user-id : user.id', 'x-team : team']
+})
+```
+
+Both become:
+
+```js
+config.headerTags
+// ['x-user-id:user.id', 'x-team:team']
+```
+
+### JSON with nested output
+
+```json
+"DD_TRACE_SAMPLING_RULES": [{
+ "type": "json",
+ "configurationNames": ["samplingRules"],
+ "default": "[]",
+ "transform": "toCamelCase"
+}]
+```
+
+```bash
+DD_TRACE_SAMPLING_RULES='[{"sample_rate":0.5,"service":"api"}]'
+```
+
+Result:
+
+```js
+config.samplingRules
+// [{ sampleRate: 0.5, service: 'api' }]
+```
+
+### Internal property path
+
+```json
+"DD_API_KEY": [{
+ "type": "string",
+ "default": null,
+ "internalPropertyName": "apiKey"
+}]
+```
+
+Result:
+
+```js
+config.apiKey
+```
+
+## Nested Properties
+
+Dot notation creates nested objects on the config singleton.
+
+```json
+"DD_API_SECURITY_ENABLED": [{
+ "type": "boolean",
+ "configurationNames": [
+ "appsec.apiSecurity.enabled",
+ "experimental.appsec.apiSecurity.enabled"
+ ],
+ "default": "true"
+}]
+```
+
+```js
+tracer.init({
+ appsec: {
+ apiSecurity: {
+ enabled: true
+ }
+ }
+})
+```
+
+Result:
+
+```js
+config.appsec.apiSecurity.enabled === true
+```
+
+## Telemetry And Remote Config
+
+Config telemetry is handled automatically by the standard config flow.
+
+If your config is defined in `supported-configurations.json` and goes through the normal parsing/application path, telemetry usually works without extra code. Telemetry records the canonical name, the normalized value, and the origin such as `default`, `env_var`, `code`, `remote_config`, or `calculated`.
+
+Remote config is not a separate system. `packages/dd-trace/src/config/remote_config.js` translates remote field names into local option names and then applies them through `config.setRemoteConfig(...)`. After that, the normal pipeline runs again: apply options, recompute calculated values, and update telemetry.
+
+## Adding A New Configuration
+
+Use this checklist:
+
+1. Add the new entry to `packages/dd-trace/src/config/supported-configurations.json`.
+2. Pick the correct `type` and `default`.
+3. Add `configurationNames` if the setting should be exposed via `tracer.init({...})`. Add the documentation to `index.d.ts`.
+4. Use `internalPropertyName` if the runtime property path should differ.
+5. Add `transform` or `allowed` only if the raw parsed value is not enough.
+6. Add `aliases` or `deprecated` only for compatibility.
+7. Regenerate types if needed.
+8. Add tests for env vars, programmatic options, and edge cases.
+
+## Mental Model
+
+Think of `supported-configurations.json` as the schema for one config singleton.
+
+You describe the input shape once, and the runtime uses that to build defaults, parse env vars, map programmatic options, generate types, and emit telemetry.
diff --git a/benchmark/sirun/exporting-pipeline/index.js b/benchmark/sirun/exporting-pipeline/index.js
index b8588c62973..f3395667f00 100644
--- a/benchmark/sirun/exporting-pipeline/index.js
+++ b/benchmark/sirun/exporting-pipeline/index.js
@@ -7,7 +7,7 @@ const SpanProcessor = require('../../../packages/dd-trace/src/span_processor')
const Exporter = require('../../../packages/dd-trace/src/exporters/agent/index')
const PrioritySampler = require('../../../packages/dd-trace/src/priority_sampler')
const id = require('../../../packages/dd-trace/src/id')
-const defaults = require('../../../packages/dd-trace/src/config/defaults')
+const { defaults } = require('../../../packages/dd-trace/src/config/defaults')
const config = {
url: `http://${defaults.hostname}:${defaults.port}`,
diff --git a/benchmark/sirun/statsd.js b/benchmark/sirun/statsd.js
index 462889874f1..dc71e6d71a3 100644
--- a/benchmark/sirun/statsd.js
+++ b/benchmark/sirun/statsd.js
@@ -1,7 +1,7 @@
'use strict'
const dgram = require('dgram')
-const defaults = require('../../packages/dd-trace/src/config/defaults')
+const { defaults } = require('../../packages/dd-trace/src/config/defaults')
const port = process.env.SIRUN_STATSD_PORT || defaults['dogstatsd.port']
class StatsD {
diff --git a/eslint-rules/eslint-config-names-sync.mjs b/eslint-rules/eslint-config-names-sync.mjs
new file mode 100644
index 00000000000..fd3cffb0997
--- /dev/null
+++ b/eslint-rules/eslint-config-names-sync.mjs
@@ -0,0 +1,766 @@
+import fs from 'node:fs'
+import path from 'node:path'
+
+import ts from 'typescript'
+
+const IGNORED_CONFIGURATION_NAMES = new Set([
+ 'tracePropagationStyle',
+ 'tracing',
+])
+const UNSUPPORTED_CONFIGURATION_ROOTS = new Set([
+ 'isCiVisibility',
+ 'logger',
+ 'lookup',
+ 'plugins',
+])
+
+const supportedConfigurationInfoCache = new Map()
+const indexDtsConfigurationNamesCache = new Map()
+const envTagNamesCache = new WeakMap()
+const interfacePropertiesCache = new WeakMap()
+
+/**
+ * @typedef {{
+ * node: import('typescript').InterfaceDeclaration | import('typescript').TypeAliasDeclaration
+ * namespaceKey: string
+ * key: string
+ * }} DeclarationEntry
+ */
+
+/**
+ * @typedef {{
+ * hasEnvDescendant: boolean
+ * hasBooleanBranch: boolean
+ * hasObjectBranch: boolean
+ * }} TypeInspectionResult
+ */
+
+/**
+ * @typedef {{
+ * names: Set
+ * envTargets: Map>
+ * }} SupportedConfigurationInfo
+ */
+
+/**
+ * @returns {TypeInspectionResult}
+ */
+function createEmptyInspectionResult () {
+ return {
+ hasEnvDescendant: false,
+ hasBooleanBranch: false,
+ hasObjectBranch: false,
+ }
+}
+
+/**
+ * @returns {TypeInspectionResult}
+ */
+function createRecursiveInspectionResult () {
+ return {
+ hasEnvDescendant: false,
+ hasBooleanBranch: false,
+ hasObjectBranch: true,
+ }
+}
+
+/**
+ * @returns {TypeInspectionResult}
+ */
+function createObjectInspectionResult () {
+ return {
+ hasEnvDescendant: false,
+ hasBooleanBranch: false,
+ hasObjectBranch: true,
+ }
+}
+
+/**
+ * @returns {TypeInspectionResult}
+ */
+function createBooleanInspectionResult () {
+ return {
+ hasEnvDescendant: false,
+ hasBooleanBranch: true,
+ hasObjectBranch: false,
+ }
+}
+
+/**
+ * @param {TypeInspectionResult} target
+ * @param {TypeInspectionResult} source
+ * @returns {TypeInspectionResult}
+ */
+function mergeInspectionResult (target, source) {
+ target.hasEnvDescendant ||= source.hasEnvDescendant
+ target.hasBooleanBranch ||= source.hasBooleanBranch
+ target.hasObjectBranch ||= source.hasObjectBranch
+ return target
+}
+
+/**
+ * @param {string} namespaceKey
+ * @param {string} name
+ * @returns {string}
+ */
+function qualifyName (namespaceKey, name) {
+ return namespaceKey ? `${namespaceKey}.${name}` : name
+}
+
+/**
+ * @param {string} pathPrefix
+ * @param {string} name
+ * @returns {string}
+ */
+function appendPath (pathPrefix, name) {
+ return pathPrefix ? `${pathPrefix}.${name}` : name
+}
+
+/**
+ * @param {string} fullPath
+ * @returns {string}
+ */
+function getRootPathSegment (fullPath) {
+ const separatorIndex = fullPath.indexOf('.')
+ return separatorIndex === -1 ? fullPath : fullPath.slice(0, separatorIndex)
+}
+
+/**
+ * @param {string} filePath
+ * @returns {string}
+ */
+function readFile (filePath) {
+ return fs.readFileSync(filePath, 'utf8')
+}
+
+/**
+ * @param {string} filePath
+ * @returns {SupportedConfigurationInfo}
+ */
+function getSupportedConfigurationInfo (filePath) {
+ const cachedInfo = supportedConfigurationInfoCache.get(filePath)
+ if (cachedInfo) return cachedInfo
+
+ const parsed = JSON.parse(readFile(filePath))
+ const supportedConfigurations = parsed?.supportedConfigurations
+ if (
+ !supportedConfigurations ||
+ typeof supportedConfigurations !== 'object' ||
+ Array.isArray(supportedConfigurations)
+ ) {
+ throw new Error('Expected a supportedConfigurations object.')
+ }
+
+ const names = new Set()
+ const envTargets = new Map()
+
+ /**
+ * @param {string} envName
+ * @param {Set} targets
+ */
+ function addEnvTargets (envName, targets) {
+ let existingTargets = envTargets.get(envName)
+ if (!existingTargets) {
+ existingTargets = new Set()
+ envTargets.set(envName, existingTargets)
+ }
+
+ for (const target of targets) {
+ existingTargets.add(target)
+ }
+ }
+
+ for (const [envName, entries] of Object.entries(supportedConfigurations)) {
+ if (!Array.isArray(entries)) continue
+
+ /** @type {Set} */
+ const targets = new Set()
+
+ for (const entry of entries) {
+ if (!entry || typeof entry !== 'object') continue
+
+ if (typeof entry.internalPropertyName === 'string') {
+ targets.add(entry.internalPropertyName)
+ }
+
+ if (!Array.isArray(entry.configurationNames)) continue
+
+ for (const name of entry.configurationNames) {
+ if (typeof name === 'string' && !IGNORED_CONFIGURATION_NAMES.has(name)) {
+ names.add(name)
+ targets.add(name)
+ }
+ }
+
+ if (Array.isArray(entry.aliases)) {
+ for (const alias of entry.aliases) {
+ if (typeof alias === 'string') {
+ addEnvTargets(alias, targets)
+ }
+ }
+ }
+ }
+
+ addEnvTargets(envName, targets)
+ }
+
+ const info = { names, envTargets }
+ supportedConfigurationInfoCache.set(filePath, info)
+ return info
+}
+
+/**
+ * @param {import('typescript').EntityName} entityName
+ * @returns {string}
+ */
+function getEntityNameText (entityName) {
+ if (ts.isIdentifier(entityName)) {
+ return entityName.text
+ }
+
+ return `${getEntityNameText(entityName.left)}.${entityName.right.text}`
+}
+
+/**
+ * @param {import('typescript').SourceFile} sourceFile
+ * @returns {Map}
+ */
+function getDeclarationRegistry (sourceFile) {
+ const declarations = new Map()
+
+ /**
+ * @param {readonly import('typescript').Statement[]} statements
+ * @param {string} namespaceKey
+ */
+ function visitStatements (statements, namespaceKey) {
+ for (const statement of statements) {
+ if (ts.isModuleDeclaration(statement)) {
+ visitModuleDeclaration(statement, namespaceKey)
+ continue
+ }
+
+ if (!ts.isInterfaceDeclaration(statement) && !ts.isTypeAliasDeclaration(statement)) continue
+
+ const key = qualifyName(namespaceKey, statement.name.text)
+ declarations.set(key, {
+ node: statement,
+ namespaceKey,
+ key,
+ })
+ }
+ }
+
+ /**
+ * @param {import('typescript').ModuleDeclaration} declaration
+ * @param {string} namespaceKey
+ */
+ function visitModuleDeclaration (declaration, namespaceKey) {
+ const nextNamespaceKey = qualifyName(namespaceKey, declaration.name.text)
+
+ if (!declaration.body) return
+
+ if (ts.isModuleBlock(declaration.body)) {
+ visitStatements(declaration.body.statements, nextNamespaceKey)
+ return
+ }
+
+ visitModuleDeclaration(
+ /** @type {import('typescript').ModuleDeclaration} */ (declaration.body),
+ nextNamespaceKey
+ )
+ }
+
+ visitStatements(sourceFile.statements, '')
+
+ return declarations
+}
+
+/**
+ * @param {Map} declarations
+ * @param {string} typeName
+ * @param {string} namespaceKey
+ * @returns {DeclarationEntry | undefined}
+ */
+function resolveDeclaration (declarations, typeName, namespaceKey) {
+ let currentNamespaceKey = namespaceKey
+
+ while (true) {
+ const declaration = declarations.get(qualifyName(currentNamespaceKey, typeName))
+ if (declaration) {
+ return declaration
+ }
+
+ if (!currentNamespaceKey) {
+ return undefined
+ }
+
+ const lastSeparatorIndex = currentNamespaceKey.lastIndexOf('.')
+ currentNamespaceKey = lastSeparatorIndex === -1
+ ? ''
+ : currentNamespaceKey.slice(0, lastSeparatorIndex)
+ }
+}
+
+/**
+ * @param {import('typescript').PropertyName} propertyName
+ * @returns {string | undefined}
+ */
+function getPropertyName (propertyName) {
+ if (ts.isIdentifier(propertyName) || ts.isStringLiteral(propertyName)) {
+ return propertyName.text
+ }
+}
+
+/**
+ * @param {import('typescript').Node} node
+ * @returns {Set}
+ */
+function getEnvTagNames (node) {
+ const cachedNames = envTagNamesCache.get(node)
+ if (cachedNames) return cachedNames
+
+ const envTagNames = new Set()
+ for (const tag of ts.getJSDocTags(node)) {
+ if (tag.tagName.text !== 'env' || typeof tag.comment !== 'string') continue
+
+ for (const match of tag.comment.matchAll(/\b(?:DD|OTEL)_[A-Z0-9_]+\b/g)) {
+ envTagNames.add(match[0])
+ }
+ }
+
+ envTagNamesCache.set(node, envTagNames)
+ return envTagNames
+}
+
+/**
+ * @param {string} fullPath
+ * @param {Set} envTagNames
+ * @param {Map>} supportedEnvTargets
+ * @returns {boolean}
+ */
+function shouldAddDirectConfigurationName (fullPath, envTagNames, supportedEnvTargets) {
+ let hasSupportedEnvTarget = false
+
+ for (const envName of envTagNames) {
+ const targets = supportedEnvTargets.get(envName)
+ if (!targets) {
+ return true
+ }
+
+ hasSupportedEnvTarget = true
+ if (targets.has(fullPath)) {
+ return true
+ }
+ }
+
+ return !hasSupportedEnvTarget
+}
+
+/**
+ * @param {string} fullPath
+ * @returns {boolean}
+ */
+function isUnsupportedConfigurationPath (fullPath) {
+ return UNSUPPORTED_CONFIGURATION_ROOTS.has(getRootPathSegment(fullPath))
+}
+
+/**
+ * @param {import('typescript').InterfaceDeclaration} declaration
+ * @param {string} propertyName
+ * @returns {import('typescript').PropertySignature | undefined}
+ */
+function getInterfaceProperty (declaration, propertyName) {
+ let properties = interfacePropertiesCache.get(declaration)
+
+ if (!properties) {
+ properties = new Map()
+
+ for (const member of declaration.members) {
+ if (!ts.isPropertySignature(member) || !member.type) continue
+
+ const memberName = getPropertyName(member.name)
+ if (memberName) {
+ properties.set(memberName, member)
+ }
+ }
+
+ interfacePropertiesCache.set(declaration, properties)
+ }
+
+ return properties.get(propertyName)
+}
+
+/**
+ * @param {readonly import('typescript').TypeElement[]} members
+ * @param {string} namespaceKey
+ * @param {string} pathPrefix
+ * @param {Map} declarations
+ * @param {Map>} supportedEnvTargets
+ * @param {Set} names
+ * @param {Set} visitedDeclarations
+ * @returns {TypeInspectionResult}
+ */
+function inspectMembers (
+ members,
+ namespaceKey,
+ pathPrefix,
+ declarations,
+ supportedEnvTargets,
+ names,
+ visitedDeclarations,
+) {
+ const result = createObjectInspectionResult()
+
+ for (const member of members) {
+ if (!ts.isPropertySignature(member) || !member.type) continue
+
+ const propertyName = getPropertyName(member.name)
+ if (!propertyName) continue
+
+ const propertyResult = inspectProperty(
+ member,
+ namespaceKey,
+ appendPath(pathPrefix, propertyName),
+ declarations,
+ supportedEnvTargets,
+ names,
+ visitedDeclarations
+ )
+ result.hasEnvDescendant ||= propertyResult.hasEnvDescendant
+ }
+
+ return result
+}
+
+/**
+ * @param {import('typescript').PropertySignature} property
+ * @param {string} namespaceKey
+ * @param {string} fullPath
+ * @param {Map} declarations
+ * @param {Map>} supportedEnvTargets
+ * @param {Set} names
+ * @param {Set} visitedDeclarations
+ * @returns {TypeInspectionResult}
+ */
+function inspectProperty (
+ property,
+ namespaceKey,
+ fullPath,
+ declarations,
+ supportedEnvTargets,
+ names,
+ visitedDeclarations,
+) {
+ if (isUnsupportedConfigurationPath(fullPath)) {
+ return createEmptyInspectionResult()
+ }
+
+ const result = inspectTypeNode(
+ property.type,
+ namespaceKey,
+ fullPath,
+ declarations,
+ supportedEnvTargets,
+ names,
+ visitedDeclarations
+ )
+ const envTagNames = getEnvTagNames(property)
+ const hasOwnEnvTag = envTagNames.size > 0
+ const isLeafConfiguration = !result.hasObjectBranch
+ const isBooleanAlias =
+ result.hasBooleanBranch &&
+ result.hasObjectBranch &&
+ result.hasEnvDescendant
+
+ if (
+ (hasOwnEnvTag && shouldAddDirectConfigurationName(fullPath, envTagNames, supportedEnvTargets)) ||
+ isLeafConfiguration ||
+ isBooleanAlias
+ ) {
+ names.add(fullPath)
+ }
+
+ if (hasOwnEnvTag) {
+ result.hasEnvDescendant = true
+ }
+
+ return result
+}
+
+/**
+ * @param {DeclarationEntry} declaration
+ * @param {string} fullPath
+ * @param {Map} declarations
+ * @param {Map>} supportedEnvTargets
+ * @param {Set} names
+ * @param {Set} visitedDeclarations
+ * @returns {TypeInspectionResult}
+ */
+function inspectDeclaration (
+ declaration,
+ fullPath,
+ declarations,
+ supportedEnvTargets,
+ names,
+ visitedDeclarations,
+) {
+ if (visitedDeclarations.has(declaration.key)) {
+ return createRecursiveInspectionResult()
+ }
+
+ visitedDeclarations.add(declaration.key)
+
+ const result = ts.isInterfaceDeclaration(declaration.node)
+ ? inspectMembers(
+ declaration.node.members,
+ declaration.namespaceKey,
+ fullPath,
+ declarations,
+ supportedEnvTargets,
+ names,
+ visitedDeclarations
+ )
+ : inspectTypeNode(
+ declaration.node.type,
+ declaration.namespaceKey,
+ fullPath,
+ declarations,
+ supportedEnvTargets,
+ names,
+ visitedDeclarations
+ )
+
+ visitedDeclarations.delete(declaration.key)
+ return result
+}
+
+/**
+ * @param {import('typescript').TypeNode | undefined} typeNode
+ * @param {string} namespaceKey
+ * @param {string} fullPath
+ * @param {Map} declarations
+ * @param {Map>} supportedEnvTargets
+ * @param {Set} names
+ * @param {Set} visitedDeclarations
+ * @returns {TypeInspectionResult}
+ */
+function inspectTypeNode (
+ typeNode,
+ namespaceKey,
+ fullPath,
+ declarations,
+ supportedEnvTargets,
+ names,
+ visitedDeclarations,
+) {
+ if (!typeNode) {
+ return createEmptyInspectionResult()
+ }
+
+ if (ts.isParenthesizedTypeNode(typeNode)) {
+ return inspectTypeNode(
+ typeNode.type,
+ namespaceKey,
+ fullPath,
+ declarations,
+ supportedEnvTargets,
+ names,
+ visitedDeclarations
+ )
+ }
+
+ if (typeNode.kind === ts.SyntaxKind.BooleanKeyword) {
+ return createBooleanInspectionResult()
+ }
+
+ if (ts.isTypeLiteralNode(typeNode)) {
+ return inspectMembers(
+ typeNode.members,
+ namespaceKey,
+ fullPath,
+ declarations,
+ supportedEnvTargets,
+ names,
+ visitedDeclarations
+ )
+ }
+
+ if (ts.isUnionTypeNode(typeNode) || ts.isIntersectionTypeNode(typeNode)) {
+ const result = createEmptyInspectionResult()
+
+ for (const part of typeNode.types) {
+ mergeInspectionResult(
+ result,
+ inspectTypeNode(
+ part,
+ namespaceKey,
+ fullPath,
+ declarations,
+ supportedEnvTargets,
+ names,
+ visitedDeclarations
+ )
+ )
+ }
+
+ return result
+ }
+
+ if (ts.isTypeReferenceNode(typeNode)) {
+ const declaration = resolveDeclaration(declarations, getEntityNameText(typeNode.typeName), namespaceKey)
+ return declaration
+ ? inspectDeclaration(declaration, fullPath, declarations, supportedEnvTargets, names, visitedDeclarations)
+ : createEmptyInspectionResult()
+ }
+
+ if (
+ ts.isIndexedAccessTypeNode(typeNode) &&
+ ts.isLiteralTypeNode(typeNode.indexType) &&
+ ts.isStringLiteral(typeNode.indexType.literal) &&
+ ts.isTypeReferenceNode(typeNode.objectType)
+ ) {
+ const declaration = resolveDeclaration(
+ declarations,
+ getEntityNameText(typeNode.objectType.typeName),
+ namespaceKey
+ )
+
+ if (!declaration || !ts.isInterfaceDeclaration(declaration.node)) {
+ return createEmptyInspectionResult()
+ }
+
+ const property = getInterfaceProperty(declaration.node, typeNode.indexType.literal.text)
+ return property
+ ? inspectProperty(
+ property,
+ declaration.namespaceKey,
+ fullPath,
+ declarations,
+ supportedEnvTargets,
+ names,
+ visitedDeclarations
+ )
+ : createEmptyInspectionResult()
+ }
+
+ return createEmptyInspectionResult()
+}
+
+/**
+ * @param {string} filePath
+ * @param {SupportedConfigurationInfo} supportedConfigurationInfo
+ * @returns {Set}
+ */
+function getIndexDtsConfigurationNames (filePath, supportedConfigurationInfo) {
+ const cacheKey = `${filePath}::${JSON.stringify([...supportedConfigurationInfo.envTargets.keys()].sort())}`
+ const cachedNames = indexDtsConfigurationNamesCache.get(cacheKey)
+ if (cachedNames) return cachedNames
+
+ const sourceFile = ts.createSourceFile(filePath, readFile(filePath), ts.ScriptTarget.Latest, true, ts.ScriptKind.TS)
+ const declarations = getDeclarationRegistry(sourceFile)
+ const tracerOptions = declarations.get('tracer.TracerOptions')
+
+ if (!tracerOptions || !ts.isInterfaceDeclaration(tracerOptions.node)) {
+ throw new Error('Could not resolve tracer.TracerOptions.')
+ }
+
+ const names = new Set()
+ inspectMembers(
+ tracerOptions.node.members,
+ tracerOptions.namespaceKey,
+ '',
+ declarations,
+ supportedConfigurationInfo.envTargets,
+ names,
+ new Set()
+ )
+
+ for (const ignoredConfigurationName of IGNORED_CONFIGURATION_NAMES) {
+ names.delete(ignoredConfigurationName)
+ }
+
+ indexDtsConfigurationNamesCache.set(cacheKey, names)
+ return names
+}
+
+/** @type {import('eslint').Rule.RuleModule} */
+export default {
+ meta: {
+ type: 'problem',
+ docs: {
+ description: 'Ensure supported configuration names stay in sync with index.d.ts',
+ },
+ schema: [{
+ type: 'object',
+ properties: {
+ indexDtsPath: {
+ type: 'string',
+ },
+ supportedConfigurationsPath: {
+ type: 'string',
+ },
+ },
+ additionalProperties: false,
+ }],
+ messages: {
+ configurationMissingInIndexDts:
+ "Configuration name '{{configurationName}}' exists in supported-configurations.json but not in index.d.ts.",
+ configurationMissingInSupportedConfigurations:
+ "Configuration name '{{configurationName}}' exists in index.d.ts but not in supported-configurations.json.",
+ readFailure:
+ 'Unable to compare supported configuration names: {{reason}}',
+ },
+ },
+ create (context) {
+ const options = context.options[0] || {}
+ const indexDtsPath = path.resolve(context.cwd, options.indexDtsPath || 'index.d.ts')
+ const supportedConfigurationsPath = path.resolve(
+ context.cwd,
+ options.supportedConfigurationsPath || 'packages/dd-trace/src/config/supported-configurations.json'
+ )
+
+ return {
+ Program (node) {
+ let indexDtsNames
+ let supportedConfigurationInfo
+
+ try {
+ supportedConfigurationInfo = getSupportedConfigurationInfo(supportedConfigurationsPath)
+ indexDtsNames = getIndexDtsConfigurationNames(indexDtsPath, supportedConfigurationInfo)
+ } catch (error) {
+ context.report({
+ node,
+ messageId: 'readFailure',
+ data: {
+ reason: error instanceof Error ? error.message : String(error),
+ },
+ })
+ return
+ }
+
+ const missingInIndexDts = [...supportedConfigurationInfo.names]
+ .filter(name => !indexDtsNames.has(name))
+ .sort()
+ const missingInSupportedConfigurations = [...indexDtsNames]
+ .filter(name => !supportedConfigurationInfo.names.has(name))
+ .sort()
+
+ for (const configurationName of missingInIndexDts) {
+ context.report({
+ node,
+ messageId: 'configurationMissingInIndexDts',
+ data: { configurationName },
+ })
+ }
+
+ for (const configurationName of missingInSupportedConfigurations) {
+ context.report({
+ node,
+ messageId: 'configurationMissingInSupportedConfigurations',
+ data: { configurationName },
+ })
+ }
+ },
+ }
+ },
+}
diff --git a/eslint-rules/eslint-config-names-sync.test.mjs b/eslint-rules/eslint-config-names-sync.test.mjs
new file mode 100644
index 00000000000..457dce1c475
--- /dev/null
+++ b/eslint-rules/eslint-config-names-sync.test.mjs
@@ -0,0 +1,85 @@
+import path from 'node:path'
+
+import { RuleTester } from 'eslint'
+
+import rule from './eslint-config-names-sync.mjs'
+
+const ruleTester = new RuleTester({
+ languageOptions: {
+ ecmaVersion: 2022,
+ sourceType: 'script',
+ },
+})
+
+const fixturesDirectory = path.join(process.cwd(), 'eslint-rules/fixtures/config-names-sync')
+
+/**
+ * @param {string} fixtureName
+ * @returns {{ indexDtsPath: string, supportedConfigurationsPath: string }}
+ */
+function getFixtureOptions (fixtureName) {
+ const fixtureDirectory = path.join(fixturesDirectory, fixtureName)
+
+ return {
+ indexDtsPath: path.relative(process.cwd(), path.join(fixtureDirectory, 'index.d.ts')),
+ supportedConfigurationsPath: path.relative(
+ process.cwd(),
+ path.join(fixtureDirectory, 'supported-configurations.json')
+ ),
+ }
+}
+
+ruleTester.run('eslint-config-names-sync', rule, {
+ valid: [
+ {
+ filename: path.join(fixturesDirectory, 'valid', 'lint-anchor.js'),
+ code: '',
+ options: [getFixtureOptions('valid')],
+ },
+ {
+ filename: path.join(fixturesDirectory, 'trace-propagation-style-exception', 'lint-anchor.js'),
+ code: '',
+ options: [getFixtureOptions('trace-propagation-style-exception')],
+ },
+ {
+ filename: path.join(fixturesDirectory, 'internal-env-and-ignored-names', 'lint-anchor.js'),
+ code: '',
+ options: [getFixtureOptions('internal-env-and-ignored-names')],
+ },
+ ],
+ invalid: [
+ {
+ filename: path.join(fixturesDirectory, 'missing-in-index-dts', 'lint-anchor.js'),
+ code: '',
+ options: [getFixtureOptions('missing-in-index-dts')],
+ errors: [{
+ messageId: 'configurationMissingInIndexDts',
+ data: {
+ configurationName: 'missingFromTypes',
+ },
+ }],
+ },
+ {
+ filename: path.join(fixturesDirectory, 'missing-in-supported-configurations', 'lint-anchor.js'),
+ code: '',
+ options: [getFixtureOptions('missing-in-supported-configurations')],
+ errors: [{
+ messageId: 'configurationMissingInSupportedConfigurations',
+ data: {
+ configurationName: 'missingFromJson',
+ },
+ }],
+ },
+ {
+ filename: path.join(fixturesDirectory, 'missing-nested-leaf-in-supported-configurations', 'lint-anchor.js'),
+ code: '',
+ options: [getFixtureOptions('missing-nested-leaf-in-supported-configurations')],
+ errors: [{
+ messageId: 'configurationMissingInSupportedConfigurations',
+ data: {
+ configurationName: 'llmobs.agentlessEnabledasd',
+ },
+ }],
+ },
+ ],
+})
diff --git a/eslint-rules/fixtures/config-names-sync/internal-env-and-ignored-names/index.d.ts b/eslint-rules/fixtures/config-names-sync/internal-env-and-ignored-names/index.d.ts
new file mode 100644
index 00000000000..e46ff0b872c
--- /dev/null
+++ b/eslint-rules/fixtures/config-names-sync/internal-env-and-ignored-names/index.d.ts
@@ -0,0 +1,14 @@
+declare namespace tracer {
+ export interface TracerOptions {
+ /**
+ * @env DD_LLMOBS_ENABLED
+ * The environment variable listed above takes precedence over programmatic configuration.
+ */
+ llmobs?: {
+ /**
+ * @env DD_LLMOBS_ML_APP
+ */
+ mlApp?: string
+ }
+ }
+}
diff --git a/eslint-rules/fixtures/config-names-sync/internal-env-and-ignored-names/supported-configurations.json b/eslint-rules/fixtures/config-names-sync/internal-env-and-ignored-names/supported-configurations.json
new file mode 100644
index 00000000000..5e989725d9c
--- /dev/null
+++ b/eslint-rules/fixtures/config-names-sync/internal-env-and-ignored-names/supported-configurations.json
@@ -0,0 +1,23 @@
+{
+ "supportedConfigurations": {
+ "DD_LLMOBS_ENABLED": [
+ {
+ "internalPropertyName": "llmobs.enabled"
+ }
+ ],
+ "DD_LLMOBS_ML_APP": [
+ {
+ "configurationNames": [
+ "llmobs.mlApp"
+ ]
+ }
+ ],
+ "DD_TRACE_ENABLED": [
+ {
+ "configurationNames": [
+ "tracing"
+ ]
+ }
+ ]
+ }
+}
diff --git a/eslint-rules/fixtures/config-names-sync/missing-in-index-dts/index.d.ts b/eslint-rules/fixtures/config-names-sync/missing-in-index-dts/index.d.ts
new file mode 100644
index 00000000000..8d70784430f
--- /dev/null
+++ b/eslint-rules/fixtures/config-names-sync/missing-in-index-dts/index.d.ts
@@ -0,0 +1,8 @@
+declare namespace tracer {
+ export interface TracerOptions {
+ /**
+ * @env DD_SIMPLE
+ */
+ simple?: string
+ }
+}
diff --git a/eslint-rules/fixtures/config-names-sync/missing-in-index-dts/supported-configurations.json b/eslint-rules/fixtures/config-names-sync/missing-in-index-dts/supported-configurations.json
new file mode 100644
index 00000000000..807ee5dc464
--- /dev/null
+++ b/eslint-rules/fixtures/config-names-sync/missing-in-index-dts/supported-configurations.json
@@ -0,0 +1,18 @@
+{
+ "supportedConfigurations": {
+ "DD_SIMPLE": [
+ {
+ "configurationNames": [
+ "simple"
+ ]
+ }
+ ],
+ "DD_MISSING_FROM_TYPES": [
+ {
+ "configurationNames": [
+ "missingFromTypes"
+ ]
+ }
+ ]
+ }
+}
diff --git a/eslint-rules/fixtures/config-names-sync/missing-in-supported-configurations/index.d.ts b/eslint-rules/fixtures/config-names-sync/missing-in-supported-configurations/index.d.ts
new file mode 100644
index 00000000000..491702f278b
--- /dev/null
+++ b/eslint-rules/fixtures/config-names-sync/missing-in-supported-configurations/index.d.ts
@@ -0,0 +1,13 @@
+declare namespace tracer {
+ export interface TracerOptions {
+ /**
+ * @env DD_SIMPLE
+ */
+ simple?: string
+
+ /**
+ * @env DD_MISSING_FROM_JSON
+ */
+ missingFromJson?: boolean
+ }
+}
diff --git a/eslint-rules/fixtures/config-names-sync/missing-in-supported-configurations/supported-configurations.json b/eslint-rules/fixtures/config-names-sync/missing-in-supported-configurations/supported-configurations.json
new file mode 100644
index 00000000000..90553c533af
--- /dev/null
+++ b/eslint-rules/fixtures/config-names-sync/missing-in-supported-configurations/supported-configurations.json
@@ -0,0 +1,11 @@
+{
+ "supportedConfigurations": {
+ "DD_SIMPLE": [
+ {
+ "configurationNames": [
+ "simple"
+ ]
+ }
+ ]
+ }
+}
diff --git a/eslint-rules/fixtures/config-names-sync/missing-nested-leaf-in-supported-configurations/index.d.ts b/eslint-rules/fixtures/config-names-sync/missing-nested-leaf-in-supported-configurations/index.d.ts
new file mode 100644
index 00000000000..597e4028452
--- /dev/null
+++ b/eslint-rules/fixtures/config-names-sync/missing-nested-leaf-in-supported-configurations/index.d.ts
@@ -0,0 +1,16 @@
+declare namespace tracer {
+ export interface TracerOptions {
+ /**
+ * @env DD_LLMOBS_ENABLED
+ * The environment variable listed above takes precedence over programmatic configuration.
+ */
+ llmobs?: {
+ /**
+ * @env DD_LLMOBS_ML_APP
+ */
+ mlApp?: string
+
+ agentlessEnabledasd?: string
+ }
+ }
+}
diff --git a/eslint-rules/fixtures/config-names-sync/missing-nested-leaf-in-supported-configurations/supported-configurations.json b/eslint-rules/fixtures/config-names-sync/missing-nested-leaf-in-supported-configurations/supported-configurations.json
new file mode 100644
index 00000000000..8cbeba651ac
--- /dev/null
+++ b/eslint-rules/fixtures/config-names-sync/missing-nested-leaf-in-supported-configurations/supported-configurations.json
@@ -0,0 +1,16 @@
+{
+ "supportedConfigurations": {
+ "DD_LLMOBS_ENABLED": [
+ {
+ "internalPropertyName": "llmobs.enabled"
+ }
+ ],
+ "DD_LLMOBS_ML_APP": [
+ {
+ "configurationNames": [
+ "llmobs.mlApp"
+ ]
+ }
+ ]
+ }
+}
diff --git a/eslint-rules/fixtures/config-names-sync/trace-propagation-style-exception/index.d.ts b/eslint-rules/fixtures/config-names-sync/trace-propagation-style-exception/index.d.ts
new file mode 100644
index 00000000000..87aa771ca5d
--- /dev/null
+++ b/eslint-rules/fixtures/config-names-sync/trace-propagation-style-exception/index.d.ts
@@ -0,0 +1,20 @@
+declare namespace tracer {
+ export interface PropagationStyle {
+ /**
+ * @env DD_TRACE_PROPAGATION_STYLE, DD_TRACE_PROPAGATION_STYLE_INJECT
+ */
+ inject: string[]
+
+ /**
+ * @env DD_TRACE_PROPAGATION_STYLE, DD_TRACE_PROPAGATION_STYLE_EXTRACT
+ */
+ extract: string[]
+ }
+
+ export interface TracerOptions {
+ /**
+ * @env DD_TRACE_PROPAGATION_STYLE, DD_TRACE_PROPAGATION_STYLE_INJECT, DD_TRACE_PROPAGATION_STYLE_EXTRACT
+ */
+ tracePropagationStyle?: string[] | PropagationStyle
+ }
+}
diff --git a/eslint-rules/fixtures/config-names-sync/trace-propagation-style-exception/supported-configurations.json b/eslint-rules/fixtures/config-names-sync/trace-propagation-style-exception/supported-configurations.json
new file mode 100644
index 00000000000..5816e540058
--- /dev/null
+++ b/eslint-rules/fixtures/config-names-sync/trace-propagation-style-exception/supported-configurations.json
@@ -0,0 +1,23 @@
+{
+ "supportedConfigurations": {
+ "DD_TRACE_PROPAGATION_STYLE": [
+ {
+ "type": "array"
+ }
+ ],
+ "DD_TRACE_PROPAGATION_STYLE_INJECT": [
+ {
+ "configurationNames": [
+ "tracePropagationStyle.inject"
+ ]
+ }
+ ],
+ "DD_TRACE_PROPAGATION_STYLE_EXTRACT": [
+ {
+ "configurationNames": [
+ "tracePropagationStyle.extract"
+ ]
+ }
+ ]
+ }
+}
diff --git a/eslint-rules/fixtures/config-names-sync/valid/index.d.ts b/eslint-rules/fixtures/config-names-sync/valid/index.d.ts
new file mode 100644
index 00000000000..1a3b57c3edf
--- /dev/null
+++ b/eslint-rules/fixtures/config-names-sync/valid/index.d.ts
@@ -0,0 +1,35 @@
+declare namespace tracer {
+ export interface TracerOptions {
+ /**
+ * @env DD_SIMPLE
+ */
+ simple?: string
+
+ objectOnly?: {
+ /**
+ * @env DD_OBJECT_ONLY_ENABLED
+ */
+ enabled?: boolean
+ }
+
+ appsec?: boolean | {
+ /**
+ * @env DD_APPSEC_ENABLED
+ */
+ enabled?: boolean
+ }
+
+ experimental?: {
+ appsec?: boolean | TracerOptions['appsec']
+
+ iast?: boolean | IastOptions
+ }
+ }
+
+ interface IastOptions {
+ /**
+ * @env DD_IAST_ENABLED
+ */
+ enabled?: boolean
+ }
+}
diff --git a/eslint-rules/fixtures/config-names-sync/valid/supported-configurations.json b/eslint-rules/fixtures/config-names-sync/valid/supported-configurations.json
new file mode 100644
index 00000000000..17238484371
--- /dev/null
+++ b/eslint-rules/fixtures/config-names-sync/valid/supported-configurations.json
@@ -0,0 +1,36 @@
+{
+ "supportedConfigurations": {
+ "DD_SIMPLE": [
+ {
+ "configurationNames": [
+ "simple"
+ ]
+ }
+ ],
+ "DD_OBJECT_ONLY_ENABLED": [
+ {
+ "configurationNames": [
+ "objectOnly.enabled"
+ ]
+ }
+ ],
+ "DD_APPSEC_ENABLED": [
+ {
+ "configurationNames": [
+ "appsec.enabled",
+ "appsec",
+ "experimental.appsec.enabled",
+ "experimental.appsec"
+ ]
+ }
+ ],
+ "DD_IAST_ENABLED": [
+ {
+ "configurationNames": [
+ "experimental.iast.enabled",
+ "experimental.iast"
+ ]
+ }
+ ]
+ }
+}
diff --git a/eslint.config.mjs b/eslint.config.mjs
index 3a39c81ebaa..5661efc36d3 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -14,11 +14,12 @@ import eslintPluginPromise from 'eslint-plugin-promise'
import eslintPluginUnicorn from 'eslint-plugin-unicorn'
import globals from 'globals'
-import eslintProcessEnv from './eslint-rules/eslint-process-env.mjs'
+import eslintConfigNamesSync from './eslint-rules/eslint-config-names-sync.mjs'
import eslintEnvAliases from './eslint-rules/eslint-env-aliases.mjs'
-import eslintSafeTypeOfObject from './eslint-rules/eslint-safe-typeof-object.mjs'
import eslintLogPrintfStyle from './eslint-rules/eslint-log-printf-style.mjs'
+import eslintProcessEnv from './eslint-rules/eslint-process-env.mjs'
import eslintRequireExportExists from './eslint-rules/eslint-require-export-exists.mjs'
+import eslintSafeTypeOfObject from './eslint-rules/eslint-safe-typeof-object.mjs'
const { dependencies } = JSON.parse(readFileSync('./vendor/package.json', 'utf8'))
@@ -375,6 +376,7 @@ export default [
rules: {
'eslint-process-env': eslintProcessEnv,
'eslint-env-aliases': eslintEnvAliases,
+ 'eslint-config-names-sync': eslintConfigNamesSync,
'eslint-safe-typeof-object': eslintSafeTypeOfObject,
'eslint-log-printf-style': eslintLogPrintfStyle,
'eslint-require-export-exists': eslintRequireExportExists,
@@ -514,6 +516,15 @@ export default [
'unicorn/switch-case-braces': 'off', // Questionable benefit
},
},
+ {
+ name: 'dd-trace/config-sync',
+ files: [
+ 'eslint.config.mjs',
+ ],
+ rules: {
+ 'eslint-rules/eslint-config-names-sync': 'error',
+ },
+ },
{
name: 'dd-trace/scripts',
files: [
diff --git a/integration-tests/aiguard/index.spec.js b/integration-tests/aiguard/index.spec.js
index 9f1ddbed4fa..cec03ec0488 100644
--- a/integration-tests/aiguard/index.spec.js
+++ b/integration-tests/aiguard/index.spec.js
@@ -32,8 +32,8 @@ describe('AIGuard SDK integration tests', () => {
env: {
DD_SERVICE: 'ai_guard_integration_test',
DD_ENV: 'test',
- DD_TRACING_ENABLED: 'true',
- DD_TRACE_AGENT_PORT: agent.port,
+ DD_TRACE_ENABLED: 'true',
+ DD_TRACE_AGENT_PORT: String(agent.port),
DD_AI_GUARD_ENABLED: 'true',
DD_AI_GUARD_ENDPOINT: `http://localhost:${api.address().port}`,
DD_API_KEY: 'DD_API_KEY',
diff --git a/integration-tests/debugger/tracing-integration.spec.js b/integration-tests/debugger/tracing-integration.spec.js
index c654c88f382..048ba645b76 100644
--- a/integration-tests/debugger/tracing-integration.spec.js
+++ b/integration-tests/debugger/tracing-integration.spec.js
@@ -4,6 +4,45 @@ const assert = require('assert')
const { setup, testBasicInput, testBasicInputWithoutDD } = require('./utils')
describe('Dynamic Instrumentation', function () {
+ describe('DD_TRACE_ENABLED=true, DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED=true', function () {
+ const t = setup({
+ testApp: 'target-app/basic.js',
+ env: { DD_TRACE_ENABLED: 'true', DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED: true },
+ dependencies: ['fastify'],
+ })
+
+ describe('input messages', function () {
+ it('should capture and send expected payload when a log line probe is triggered', testBasicInput.bind(null, t))
+ })
+ })
+
+ describe('DD_TRACE_ENABLED=true, DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED=false', function () {
+ const t = setup({
+ testApp: 'target-app/basic.js',
+ env: { DD_TRACE_ENABLED: 'true', DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED: false },
+ dependencies: ['fastify'],
+ })
+
+ describe('input messages', function () {
+ it('should capture and send expected payload when a log line probe is triggered', testBasicInput.bind(null, t))
+ })
+ })
+
+ describe('DD_TRACE_ENABLED=false', function () {
+ const t = setup({
+ testApp: 'target-app/basic.js',
+ env: { DD_TRACE_ENABLED: 'false' },
+ dependencies: ['fastify'],
+ })
+
+ describe('input messages', function () {
+ it(
+ 'should capture and send expected payload when a log line probe is triggered',
+ testBasicInputWithoutDD.bind(null, t)
+ )
+ })
+ })
+
describe('DD_TRACING_ENABLED=true, DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED=true', function () {
const t = setup({
testApp: 'target-app/basic.js',
diff --git a/integration-tests/debugger/utils.js b/integration-tests/debugger/utils.js
index 0074f88943d..1af0b399653 100644
--- a/integration-tests/debugger/utils.js
+++ b/integration-tests/debugger/utils.js
@@ -304,14 +304,15 @@ function setupAssertionListeners (t, done, probe) {
let traceId, spanId, dd
const messageListener = ({ payload }) => {
- const span = payload.find((arr) => arr[0].name === 'fastify.request')?.[0]
+ const span = payload
+ .flat()
+ .find((span) => span.name === 'fastify.request' && (!dd || span.span_id.toString() === dd.span_id))
+
if (!span) return
traceId = span.trace_id.toString()
spanId = span.span_id.toString()
- t.agent.removeListener('message', messageListener)
-
assertDD()
}
@@ -336,6 +337,7 @@ function setupAssertionListeners (t, done, probe) {
if (!traceId || !spanId || !dd) return
assert.strictEqual(dd.trace_id, traceId)
assert.strictEqual(dd.span_id, spanId)
+ t.agent.removeListener('message', messageListener)
done()
}
}
diff --git a/integration-tests/opentelemetry.spec.js b/integration-tests/opentelemetry.spec.js
index 2e4e93e1f84..b0feff6328e 100644
--- a/integration-tests/opentelemetry.spec.js
+++ b/integration-tests/opentelemetry.spec.js
@@ -50,10 +50,12 @@ function nearNow (ts, now = Date.now(), range = 1000) {
return delta < range && delta >= 0
}
-describe('opentelemetry', () => {
- let agent
+describe('opentelemetry', function () {
+ this.timeout(20000)
+
+ let agent = /** @type {FakeAgent | null} */ (null)
let proc
- let cwd
+ let cwd = /** @type {string} */ ('')
const timeout = 5000
const dependencies = [
'@opentelemetry/api@1.8.0',
@@ -75,14 +77,14 @@ describe('opentelemetry', () => {
after(async () => {
await stopProc(proc)
- await agent.stop()
+ await agent?.stop()
})
it("should not capture telemetry DD and OTEL vars don't conflict", async () => {
proc = fork(join(cwd, 'opentelemetry/basic.js'), {
cwd,
env: {
- DD_TRACE_AGENT_PORT: agent.port,
+ DD_TRACE_AGENT_PORT: agent?.port,
DD_TRACE_OTEL_ENABLED: '1',
DD_TELEMETRY_HEARTBEAT_INTERVAL: '1',
TIMEOUT: '1500',
@@ -114,7 +116,7 @@ describe('opentelemetry', () => {
proc = fork(join(cwd, 'opentelemetry/basic.js'), {
cwd,
env: {
- DD_TRACE_AGENT_PORT: agent.port,
+ DD_TRACE_AGENT_PORT: agent?.port,
DD_TRACE_OTEL_ENABLED: '1',
DD_TELEMETRY_HEARTBEAT_INTERVAL: '1',
TIMEOUT: '1500',
@@ -147,42 +149,20 @@ describe('opentelemetry', () => {
const otelHiding = metrics.series.filter(({ metric }) => metric === 'otel.env.hiding')
const otelInvalid = metrics.series.filter(({ metric }) => metric === 'otel.env.invalid')
- assert.strictEqual(otelHiding.length, 9)
- assert.strictEqual(otelInvalid.length, 0)
-
- assert.deepStrictEqual(otelHiding[0].tags, [
- 'config_datadog:dd_trace_log_level', 'config_opentelemetry:otel_log_level',
- ])
- assert.deepStrictEqual(otelHiding[1].tags, [
- 'config_datadog:dd_trace_propagation_style', 'config_opentelemetry:otel_propagators',
- ])
- assert.deepStrictEqual(otelHiding[2].tags, [
- 'config_datadog:dd_service', 'config_opentelemetry:otel_service_name',
- ])
-
- assert.deepStrictEqual(otelHiding[3].tags, [
- 'config_datadog:dd_trace_sample_rate', 'config_opentelemetry:otel_traces_sampler',
- ])
-
- assert.deepStrictEqual(otelHiding[4].tags, [
- 'config_datadog:dd_trace_sample_rate', 'config_opentelemetry:otel_traces_sampler_arg',
- ])
-
- assert.deepStrictEqual(otelHiding[5].tags, [
- 'config_datadog:dd_trace_enabled', 'config_opentelemetry:otel_traces_exporter',
- ])
-
- assert.deepStrictEqual(otelHiding[6].tags, [
- 'config_datadog:dd_runtime_metrics_enabled', 'config_opentelemetry:otel_metrics_exporter',
- ])
- assert.deepStrictEqual(otelHiding[7].tags, [
- 'config_datadog:dd_tags', 'config_opentelemetry:otel_resource_attributes',
- ])
+ assert.deepStrictEqual(sortMetricTags(otelHiding), sortMetricTags([
+ ['config_datadog:dd_trace_log_level', 'config_opentelemetry:otel_log_level'],
+ ['config_datadog:dd_trace_propagation_style', 'config_opentelemetry:otel_propagators'],
+ ['config_datadog:dd_service', 'config_opentelemetry:otel_service_name'],
+ ['config_datadog:dd_trace_sample_rate', 'config_opentelemetry:otel_traces_sampler'],
+ ['config_datadog:dd_trace_sample_rate', 'config_opentelemetry:otel_traces_sampler_arg'],
+ ['config_datadog:dd_trace_enabled', 'config_opentelemetry:otel_traces_exporter'],
+ ['config_datadog:dd_runtime_metrics_enabled', 'config_opentelemetry:otel_metrics_exporter'],
+ ['config_datadog:dd_tags', 'config_opentelemetry:otel_resource_attributes'],
+ ['config_datadog:dd_trace_otel_enabled', 'config_opentelemetry:otel_sdk_disabled'],
+ ]))
- assert.deepStrictEqual(otelHiding[8].tags, [
- 'config_datadog:dd_trace_otel_enabled', 'config_opentelemetry:otel_sdk_disabled',
- ])
+ assert.deepStrictEqual(sortMetricTags(otelInvalid), [])
for (const metric of otelHiding) {
assert.strictEqual(metric.points[0][1], 1)
@@ -194,7 +174,7 @@ describe('opentelemetry', () => {
proc = fork(join(cwd, 'opentelemetry/basic.js'), {
cwd,
env: {
- DD_TRACE_AGENT_PORT: agent.port,
+ DD_TRACE_AGENT_PORT: agent?.port,
DD_TRACE_OTEL_ENABLED: '1',
DD_TELEMETRY_HEARTBEAT_INTERVAL: '1',
TIMEOUT: '1500',
@@ -221,47 +201,20 @@ describe('opentelemetry', () => {
const otelHiding = metrics.series.filter(({ metric }) => metric === 'otel.env.hiding')
const otelInvalid = metrics.series.filter(({ metric }) => metric === 'otel.env.invalid')
- assert.strictEqual(otelHiding.length, 1)
- assert.strictEqual(otelInvalid.length, 8)
-
- assert.deepStrictEqual(otelHiding[0].tags, [
- 'config_datadog:dd_trace_otel_enabled', 'config_opentelemetry:otel_sdk_disabled',
- ])
-
- assert.deepStrictEqual(otelInvalid[0].tags, [
- 'config_datadog:dd_trace_log_level', 'config_opentelemetry:otel_log_level',
- ])
-
- assert.deepStrictEqual(otelInvalid[1].tags, [
- 'config_datadog:dd_trace_sample_rate',
- 'config_opentelemetry:otel_traces_sampler',
- ])
-
- assert.deepStrictEqual(otelInvalid[2].tags, [
- 'config_datadog:dd_trace_sample_rate',
- 'config_opentelemetry:otel_traces_sampler_arg',
- ])
- assert.deepStrictEqual(otelInvalid[3].tags, [
- 'config_datadog:dd_trace_enabled', 'config_opentelemetry:otel_traces_exporter',
- ])
-
- assert.deepStrictEqual(otelInvalid[4].tags, [
- 'config_datadog:dd_runtime_metrics_enabled',
- 'config_opentelemetry:otel_metrics_exporter',
- ])
-
- assert.deepStrictEqual(otelInvalid[5].tags, [
- 'config_datadog:dd_trace_otel_enabled', 'config_opentelemetry:otel_sdk_disabled',
- ])
-
- assert.deepStrictEqual(otelInvalid[6].tags, [
- 'config_opentelemetry:otel_logs_exporter',
- ])
-
- assert.deepStrictEqual(otelInvalid[7].tags, [
- 'config_datadog:dd_trace_propagation_style',
- 'config_opentelemetry:otel_propagators',
- ])
+ assert.deepStrictEqual(sortMetricTags(otelHiding), sortMetricTags([
+ ['config_datadog:dd_trace_otel_enabled', 'config_opentelemetry:otel_sdk_disabled'],
+ ]))
+
+ assert.deepStrictEqual(sortMetricTags(otelInvalid), sortMetricTags([
+ ['config_datadog:dd_trace_log_level', 'config_opentelemetry:otel_log_level'],
+ ['config_datadog:dd_trace_propagation_style', 'config_opentelemetry:otel_propagators'],
+ ['config_opentelemetry:otel_logs_exporter'],
+ ['config_datadog:dd_trace_sample_rate', 'config_opentelemetry:otel_traces_sampler'],
+ ['config_datadog:dd_trace_sample_rate', 'config_opentelemetry:otel_traces_sampler_arg'],
+ ['config_datadog:dd_trace_enabled', 'config_opentelemetry:otel_traces_exporter'],
+ ['config_datadog:dd_runtime_metrics_enabled', 'config_opentelemetry:otel_metrics_exporter'],
+ ['config_datadog:dd_trace_otel_enabled', 'config_opentelemetry:otel_sdk_disabled'],
+ ]))
for (const metric of otelInvalid) {
assert.strictEqual(metric.points[0][1], 1)
@@ -273,7 +226,7 @@ describe('opentelemetry', () => {
proc = fork(join(cwd, 'opentelemetry/basic.js'), {
cwd,
env: {
- DD_TRACE_AGENT_PORT: agent.port,
+ DD_TRACE_AGENT_PORT: agent?.port,
},
})
await check(agent, proc, timeout, ({ payload }) => {
@@ -292,7 +245,7 @@ describe('opentelemetry', () => {
proc = fork(join(cwd, 'opentelemetry/basic.js'), {
cwd,
env: {
- DD_TRACE_AGENT_PORT: agent.port,
+ DD_TRACE_AGENT_PORT: agent?.port,
DD_TRACE_OTEL_ENABLED: '1',
DD_TELEMETRY_HEARTBEAT_INTERVAL: '1',
TIMEOUT: '1500',
@@ -334,7 +287,7 @@ describe('opentelemetry', () => {
proc = fork(join(cwd, 'opentelemetry/auto-instrumentation.js'), {
cwd,
env: {
- DD_TRACE_AGENT_PORT: agent.port,
+ DD_TRACE_AGENT_PORT: agent?.port,
DD_TRACE_OTEL_ENABLED: '1',
SERVER_PORT,
DD_TRACE_DISABLED_INSTRUMENTATIONS: 'http,dns,express,net',
@@ -378,7 +331,7 @@ describe('opentelemetry', () => {
proc = fork(join(cwd, 'opentelemetry/server.js'), {
cwd,
env: {
- DD_TRACE_AGENT_PORT: agent.port,
+ DD_TRACE_AGENT_PORT: agent?.port,
},
})
await check(agent, proc, timeout, ({ payload }) => {
@@ -407,7 +360,7 @@ describe('opentelemetry', () => {
proc = fork(join(cwd, 'opentelemetry/auto-instrumentation.js'), {
cwd,
env: {
- DD_TRACE_AGENT_PORT: agent.port,
+ DD_TRACE_AGENT_PORT: agent?.port,
DD_TRACE_OTEL_ENABLED: '1',
SERVER_PORT,
DD_TRACE_DISABLED_INSTRUMENTATIONS: 'http,dns,express,net',
@@ -456,18 +409,12 @@ describe('opentelemetry', () => {
proc = fork(join(cwd, 'opentelemetry/env-var.js'), {
cwd,
env: {
- DD_TRACE_AGENT_PORT: agent.port,
+ DD_TRACE_AGENT_PORT: agent?.port,
},
})
await check(agent, proc, timeout, ({ payload }) => {
- // Should have a single trace with a single span
- assert.strictEqual(payload.length, 1)
- const [trace] = payload
- assert.strictEqual(trace.length, 1)
- const [span] = trace
-
- // Should be the expected otel span
- assert.strictEqual(span.name, 'otel-sub')
+ const trace = payload.find(trace => trace.length === 1 && trace[0].name === 'otel-sub')
+ assert.ok(trace)
})
})
})
@@ -477,3 +424,9 @@ function isChildOf (childSpan, parentSpan) {
assert.notStrictEqual(childSpan.span_id.toString(), parentSpan.span_id.toString())
assert.strictEqual(childSpan.parent_id.toString(), parentSpan.span_id.toString())
}
+
+function sortMetricTags (metrics) {
+ return metrics
+ .map(metric => Array.isArray(metric) ? metric : metric.tags)
+ .sort((a, b) => a.join(',').localeCompare(b.join(',')))
+}
diff --git a/integration-tests/package-guardrails.spec.js b/integration-tests/package-guardrails.spec.js
index 7b8ec191930..db2db8b621e 100644
--- a/integration-tests/package-guardrails.spec.js
+++ b/integration-tests/package-guardrails.spec.js
@@ -13,7 +13,7 @@ const {
const NODE_OPTIONS = '--require dd-trace/init.js'
const DD_TRACE_DEBUG = 'true'
const DD_INJECTION_ENABLED = 'tracing'
-const DD_LOG_LEVEL = 'error'
+const DD_LOG_LEVEL = 'info'
const NODE_MAJOR = Number(process.versions.node.split('.')[0])
const FASTIFY_DEP = NODE_MAJOR < 20 ? 'fastify@4' : 'fastify'
diff --git a/integration-tests/telemetry.spec.js b/integration-tests/telemetry.spec.js
index 462b36bc777..617d45c3486 100644
--- a/integration-tests/telemetry.spec.js
+++ b/integration-tests/telemetry.spec.js
@@ -26,7 +26,7 @@ describe('telemetry', () => {
proc = await spawnProc(startupTestFile, {
cwd,
env: {
- AGENT_PORT: agent.port,
+ AGENT_PORT: String(agent.port),
DD_LOGS_INJECTION: 'true',
},
})
@@ -66,9 +66,9 @@ describe('telemetry', () => {
await agent.assertTelemetryReceived(msg => {
const { configuration } = msg.payload.payload
assertObjectContains(configuration, [
- { name: 'DD_LOG_INJECTION', value: true, origin: 'default' },
- { name: 'DD_LOG_INJECTION', value: true, origin: 'env_var' },
- { name: 'DD_LOG_INJECTION', value: false, origin: 'code' },
+ { name: 'DD_LOGS_INJECTION', value: true, origin: 'default' },
+ { name: 'DD_LOGS_INJECTION', value: true, origin: 'env_var' },
+ { name: 'DD_LOGS_INJECTION', value: false, origin: 'code' },
])
}, 'app-started', 5_000, 1)
})
diff --git a/package.json b/package.json
index 1bd4a579b65..710eb105403 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,8 @@
"bench": "node benchmark/index.js",
"bench:e2e:test-optimization": "node benchmark/e2e-test-optimization/benchmark-run.js",
"dependencies:dedupe": "yarn-deduplicate yarn.lock",
+ "generate:config:types": "node scripts/generate-config-types.js",
+ "verify:config:types": "node scripts/generate-config-types.js --check",
"type:check": "tsc --noEmit -p tsconfig.dev.json",
"type:doc:build": "cd docs && yarn && yarn build",
"type:doc:test": "cd docs && yarn && yarn test",
diff --git a/packages/datadog-plugin-aws-sdk/src/base.js b/packages/datadog-plugin-aws-sdk/src/base.js
index da8e40a56f1..b349ba99a80 100644
--- a/packages/datadog-plugin-aws-sdk/src/base.js
+++ b/packages/datadog-plugin-aws-sdk/src/base.js
@@ -23,12 +23,13 @@ class BaseAwsSdkPlugin extends ClientPlugin {
return id
}
+ /** @type {import('../../dd-trace/src/config/config-types').ConfigProperties['cloudPayloadTagging']} */
get cloudTaggingConfig () {
return this._tracerConfig.cloudPayloadTagging
}
get payloadTaggingRules () {
- return this.cloudTaggingConfig.rules.aws?.[this.constructor.id]
+ return this.cloudTaggingConfig.rules?.aws?.[this.constructor.id]
}
constructor (...args) {
@@ -78,7 +79,7 @@ class BaseAwsSdkPlugin extends ClientPlugin {
this.requestInject(span, request)
})
- if (this.constructor.isPayloadReporter && this.cloudTaggingConfig.requestsEnabled) {
+ if (this.constructor.isPayloadReporter && this.cloudTaggingConfig.request) {
const maxDepth = this.cloudTaggingConfig.maxDepth
const requestTags = tagsFromRequest(this.payloadTaggingRules, request.params, { maxDepth })
span.addTags(requestTags)
@@ -215,7 +216,7 @@ class BaseAwsSdkPlugin extends ClientPlugin {
span.addTags(tags)
- if (this.constructor.isPayloadReporter && this.cloudTaggingConfig.responsesEnabled) {
+ if (this.constructor.isPayloadReporter && this.cloudTaggingConfig.response) {
const maxDepth = this.cloudTaggingConfig.maxDepth
const responseBody = this.extractResponseBody(response)
const responseTags = tagsFromResponse(this.payloadTaggingRules, responseBody, { maxDepth })
diff --git a/packages/datadog-plugin-grpc/test/client.spec.js b/packages/datadog-plugin-grpc/test/client.spec.js
index 6c1eef00667..bad1443d9f4 100644
--- a/packages/datadog-plugin-grpc/test/client.spec.js
+++ b/packages/datadog-plugin-grpc/test/client.spec.js
@@ -12,7 +12,7 @@ const loader = require('../../../versions/@grpc/proto-loader').get()
const { withNamingSchema, withPeerService, withVersions } = require('../../dd-trace/test/setup/mocha')
const agent = require('../../dd-trace/test/plugins/agent')
const { ERROR_MESSAGE, ERROR_TYPE, ERROR_STACK } = require('../../dd-trace/src/constants')
-const defaults = require('../../dd-trace/src/config/defaults')
+const { defaults } = require('../../dd-trace/src/config/defaults')
const { NODE_MAJOR } = require('../../../version')
const getService = require('./service')
diff --git a/packages/datadog-plugin-grpc/test/server.spec.js b/packages/datadog-plugin-grpc/test/server.spec.js
index 1c75183879d..f7c638d22fb 100644
--- a/packages/datadog-plugin-grpc/test/server.spec.js
+++ b/packages/datadog-plugin-grpc/test/server.spec.js
@@ -11,7 +11,7 @@ const { assertObjectContains } = require('../../../integration-tests/helpers')
const { withNamingSchema, withVersions } = require('../../dd-trace/test/setup/mocha')
const agent = require('../../dd-trace/test/plugins/agent')
const { ERROR_MESSAGE, ERROR_TYPE, ERROR_STACK } = require('../../dd-trace/src/constants')
-const defaults = require('../../dd-trace/src/config/defaults')
+const { defaults } = require('../../dd-trace/src/config/defaults')
const { NODE_MAJOR } = require('../../../version')
const GRPC_SERVER_ERROR_STATUSES = defaults['grpc.server.error.statuses']
diff --git a/packages/datadog-plugin-openai/src/services.js b/packages/datadog-plugin-openai/src/services.js
index f3ca56911af..917aa260544 100644
--- a/packages/datadog-plugin-openai/src/services.js
+++ b/packages/datadog-plugin-openai/src/services.js
@@ -20,6 +20,7 @@ module.exports.init = function (tracerConfig) {
`env:${tracerConfig.tags.env}`,
`version:${tracerConfig.tags.version}`,
],
+ lookup: tracerConfig.lookup,
})
: new NoopDogStatsDClient()
diff --git a/packages/datadog-plugin-openai/test/services.spec.js b/packages/datadog-plugin-openai/test/services.spec.js
index 99673240f49..cd984d320e9 100644
--- a/packages/datadog-plugin-openai/test/services.spec.js
+++ b/packages/datadog-plugin-openai/test/services.spec.js
@@ -1,15 +1,60 @@
'use strict'
+const sinon = require('sinon')
+const proxyquire = require('proxyquire')
+
const services = require('../src/services')
const { getConfigFresh } = require('../../dd-trace/test/helpers/config')
describe('Plugin', () => {
describe('openai services', () => {
- describe('when unconfigured', () => {
- afterEach(() => {
- services.shutdown()
+ afterEach(() => {
+ services.shutdown()
+ })
+
+ it('should initialize DogStatsDClient with explicit config values', () => {
+ const flush = sinon.stub()
+ const DogStatsDClient = sinon.stub().returns({
+ flush,
+ })
+ const ExternalLogger = sinon.stub().returns({
+ log: sinon.stub(),
+ })
+ const NoopDogStatsDClient = sinon.stub()
+ const NoopExternalLogger = sinon.stub()
+ const proxiedServices = proxyquire('../src/services', {
+ '../../dd-trace/src/dogstatsd': { DogStatsDClient },
+ '../../dd-trace/src/noop/dogstatsd': NoopDogStatsDClient,
+ '../../dd-trace/src/external-logger/src': {
+ ExternalLogger,
+ NoopExternalLogger,
+ },
+ })
+ const config = getConfigFresh({
+ env: 'prod',
+ hostname: 'foo',
+ service: 'bar',
+ version: '1.2.3',
})
+ proxiedServices.init(config)
+
+ sinon.assert.calledOnceWithExactly(DogStatsDClient, {
+ host: config.dogstatsd.hostname,
+ lookup: config.lookup,
+ port: config.dogstatsd.port,
+ tags: [
+ 'service:bar',
+ 'env:prod',
+ 'version:1.2.3',
+ ],
+ })
+ sinon.assert.notCalled(NoopDogStatsDClient)
+
+ proxiedServices.shutdown()
+ })
+
+ describe('when unconfigured', () => {
it('dogstatsd does not throw when missing .dogstatsd', () => {
const service = services.init(getConfigFresh({
hostname: 'foo',
diff --git a/packages/dd-trace/src/agent/url.js b/packages/dd-trace/src/agent/url.js
index 82f734d9a9a..f2460ce24d6 100644
--- a/packages/dd-trace/src/agent/url.js
+++ b/packages/dd-trace/src/agent/url.js
@@ -1,7 +1,7 @@
'use strict'
const { URL, format } = require('url')
-const defaults = require('../config/defaults')
+const { defaults } = require('../config/defaults')
module.exports = { getAgentUrl }
@@ -12,7 +12,7 @@ module.exports = { getAgentUrl }
/**
* Gets the agent URL from config, constructing it from hostname/port if needed
- * @param {ReturnType} config - Tracer configuration object
+ * @param {Partial} config - Tracer configuration object
* @returns {URL} The agent URL
*/
function getAgentUrl (config) {
diff --git a/packages/dd-trace/src/aiguard/sdk.js b/packages/dd-trace/src/aiguard/sdk.js
index 64886ba092a..cbd3a486199 100644
--- a/packages/dd-trace/src/aiguard/sdk.js
+++ b/packages/dd-trace/src/aiguard/sdk.js
@@ -57,6 +57,10 @@ class AIGuard extends NoopAIGuard {
#maxContentSize
#meta
+ /**
+ * @param {import('../tracer')} tracer - Tracer instance
+ * @param {import('../config/config-base')} config - Tracer configuration
+ */
constructor (tracer, config) {
super()
diff --git a/packages/dd-trace/src/appsec/blocking.js b/packages/dd-trace/src/appsec/blocking.js
index 3615e7ef2dc..a21aab0b76d 100644
--- a/packages/dd-trace/src/appsec/blocking.js
+++ b/packages/dd-trace/src/appsec/blocking.js
@@ -164,6 +164,9 @@ function getBlockingAction (actions) {
return actions?.redirect_request || actions?.block_request
}
+/**
+ * @param {import('../config/config-base')} config - Tracer configuration
+ */
function setTemplates (config) {
templateHtml = config.appsec.blockedTemplateHtml || blockedTemplates.html
diff --git a/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js b/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js
index 5039f2bb544..1964e333d98 100644
--- a/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js
+++ b/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js
@@ -3,7 +3,7 @@
const log = require('../../../../log')
const vulnerabilities = require('../../vulnerabilities')
-const defaults = require('../../../../config/defaults')
+const { defaults } = require('../../../../config/defaults')
const { contains, intersects, remove } = require('./range-utils')
diff --git a/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js b/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js
index 6e1c483a967..f9628c2673b 100644
--- a/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js
+++ b/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js
@@ -2,7 +2,7 @@
const crypto = require('crypto')
-const defaults = require('../../../config/defaults')
+const { defaults } = require('../../../config/defaults')
const STRINGIFY_RANGE_KEY = 'DD_' + crypto.randomBytes(20).toString('hex')
const STRINGIFY_SENSITIVE_KEY = STRINGIFY_RANGE_KEY + 'SENSITIVE'
diff --git a/packages/dd-trace/src/appsec/remote_config.js b/packages/dd-trace/src/appsec/remote_config.js
index a56465bd3a8..5db2d30f8d8 100644
--- a/packages/dd-trace/src/appsec/remote_config.js
+++ b/packages/dd-trace/src/appsec/remote_config.js
@@ -76,6 +76,7 @@ function enableOrDisableAppsec (action, rcConfig, config, appsec) {
appsec.disable()
}
+ // TODO: Use configWithOrigin /generateTelemetry instead of manually constructing the change.
updateConfig([
{
name: 'appsec.enabled',
diff --git a/packages/dd-trace/src/appsec/sdk/index.js b/packages/dd-trace/src/appsec/sdk/index.js
index 1b07e25c902..499079c2b4f 100644
--- a/packages/dd-trace/src/appsec/sdk/index.js
+++ b/packages/dd-trace/src/appsec/sdk/index.js
@@ -26,6 +26,10 @@ class EventTrackingV2 {
}
class AppsecSdk {
+ /**
+ * @param {import('../../tracer')} tracer - Tracer instance
+ * @param {import('../../config/config-base')} config - Tracer configuration
+ */
constructor (tracer, config) {
this._tracer = tracer
if (config) {
diff --git a/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js b/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js
index 273d9091b98..5df5b9c8b59 100644
--- a/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js
+++ b/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js
@@ -11,6 +11,9 @@ const probeIdToResolveBreakpointSet = new Map()
const probeIdToResolveBreakpointRemove = new Map()
class TestVisDynamicInstrumentation {
+ /**
+ * @param {import('../../config/config-base')} config - Tracer configuration
+ */
constructor (config) {
this._config = config
this.worker = null
@@ -83,7 +86,6 @@ class TestVisDynamicInstrumentation {
DD_TRACE_ENABLED: 'false',
DD_TEST_FAILED_TEST_REPLAY_ENABLED: 'false',
DD_CIVISIBILITY_MANUAL_API_ENABLED: 'false',
- DD_TRACING_ENABLED: 'false',
DD_INSTRUMENTATION_TELEMETRY_ENABLED: 'false',
},
workerData: {
@@ -150,6 +152,9 @@ class TestVisDynamicInstrumentation {
let dynamicInstrumentation
+/**
+ * @param {import('../../config/config-base')} config - Tracer configuration
+ */
module.exports = function createAndGetTestVisDynamicInstrumentation (config) {
if (dynamicInstrumentation) {
return dynamicInstrumentation
diff --git a/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js b/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js
index 90602ba0a1a..c6883eb0c8d 100644
--- a/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js
+++ b/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js
@@ -54,6 +54,10 @@ class TestApiManualPlugin extends CiPlugin {
})
}
+ /**
+ * @param {import('../../config/config-base')} config - Tracer configuration
+ * @param {boolean} shouldGetEnvironmentData - Whether to get environment data
+ */
configure (config, shouldGetEnvironmentData) {
this._config = config
super.configure(config, shouldGetEnvironmentData)
diff --git a/packages/dd-trace/src/config/config-base.d.ts b/packages/dd-trace/src/config/config-base.d.ts
new file mode 100644
index 00000000000..7d9e05d09c8
--- /dev/null
+++ b/packages/dd-trace/src/config/config-base.d.ts
@@ -0,0 +1,7 @@
+import type { ConfigProperties } from './config-types'
+
+declare class ConfigBase {}
+
+interface ConfigBase extends ConfigProperties {}
+
+export = ConfigBase
diff --git a/packages/dd-trace/src/config/config-base.js b/packages/dd-trace/src/config/config-base.js
new file mode 100644
index 00000000000..3c21f87998f
--- /dev/null
+++ b/packages/dd-trace/src/config/config-base.js
@@ -0,0 +1,5 @@
+'use strict'
+
+class ConfigBase {}
+
+module.exports = ConfigBase
diff --git a/packages/dd-trace/src/config/config-types.d.ts b/packages/dd-trace/src/config/config-types.d.ts
new file mode 100644
index 00000000000..6a83baf4630
--- /dev/null
+++ b/packages/dd-trace/src/config/config-types.d.ts
@@ -0,0 +1,78 @@
+import type { GeneratedConfig } from './generated-config-types'
+
+type PayloadTaggingRules = ReturnType | []
+
+export interface ConfigProperties extends GeneratedConfig {
+ cloudPayloadTagging: GeneratedConfig['cloudPayloadTagging'] & {
+ requestsEnabled: boolean
+ responsesEnabled: boolean
+ rules: PayloadTaggingRules
+ }
+ commitSHA: string | undefined
+ debug: boolean
+ gcpPubSubPushSubscriptionEnabled: boolean
+ instrumentationSource: 'manual' | 'ssi'
+ isAzureFunction: boolean
+ isCiVisibility: boolean
+ isGCPFunction: boolean
+ isServiceNameInferred: boolean
+ isServiceUserProvided: boolean
+ logger: import('../../../../index').TracerOptions['logger'] | undefined
+ lookup: NonNullable
+ readonly parsedDdTags: Record
+ plugins: boolean
+ repositoryUrl: string | undefined
+ rules: import('../../../../index').SamplingRule[]
+ sampler: {
+ rateLimit: number
+ rules: import('../../../../index').SamplingRule[]
+ sampleRate: number | undefined
+ spanSamplingRules: import('../../../../index').SpanSamplingRule[] | undefined
+ }
+ stableConfig: {
+ fleetEntries: Record
+ localEntries: Record
+ warnings: string[] | undefined
+ }
+ tracePropagationStyle: GeneratedConfig['tracePropagationStyle']
+}
+
+type Primitive = bigint | boolean | null | number | string | symbol | undefined
+type Terminal = Date | Function | Primitive | RegExp | URL
+
+type KnownStringKeys = Extract<{
+ [K in keyof T]:
+ K extends string
+ ? string extends K
+ ? never
+ : K
+ : never
+}[keyof T], string>
+
+type NestedConfigPath = [NonNullable] extends [Terminal]
+ ? never
+ : [NonNullable] extends [readonly unknown[]]
+ ? never
+ : [NonNullable] extends [object]
+ ? ConfigPathFor>
+ : never
+
+type ConfigPathFor = {
+ [K in KnownStringKeys]:
+ | K
+ | (NestedConfigPath extends never ? never : `${K}.${NestedConfigPath}`)
+}[KnownStringKeys]
+
+type ConfigPathValueFor =
+ TPath extends `${infer TKey}.${infer TRest}`
+ ? TKey extends KnownStringKeys
+ ? ConfigPathValueFor, TRest>
+ : never
+ : TPath extends KnownStringKeys
+ ? T[TPath]
+ : never
+
+export type ConfigKey = KnownStringKeys
+export type ConfigPath = ConfigPathFor
+export type ConfigPathValue = ConfigPathValueFor
+export type ConfigDefaults = Partial<{ [TPath in ConfigPath]: ConfigPathValue }>
diff --git a/packages/dd-trace/src/config/defaults.js b/packages/dd-trace/src/config/defaults.js
index 4c2af2c9bc2..c441e0e37bf 100644
--- a/packages/dd-trace/src/config/defaults.js
+++ b/packages/dd-trace/src/config/defaults.js
@@ -1,177 +1,333 @@
'use strict'
-const pkg = require('../pkg')
-const { isFalse, isTrue } = require('../util')
-const { DD_MAJOR } = require('../../../../version')
-const { getEnvironmentVariable: getEnv } = require('./helper')
+const dns = require('dns')
+const util = require('util')
+const { DD_MAJOR } = require('../../../../version')
+const { parsers, transformers, telemetryTransformers, setWarnInvalidValue } = require('./parsers')
const {
supportedConfigurations,
} = /** @type {import('./helper').SupportedConfigurationsJson} */ (require('./supported-configurations.json'))
-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'
+let log
+let seqId = 0
+const configWithOrigin = new Map()
+const parseErrors = new Map()
+
+if (DD_MAJOR >= 6) {
+ // Programmatic configuration of DD_IAST_SECURITY_CONTROLS_CONFIGURATION is not supported
+ // in newer major versions. This is special handled here until a better solution is found.
+ // TODO: Remove the programmatic configuration from supported-configurations.json once v5 is not supported anymore.
+ supportedConfigurations.DD_IAST_SECURITY_CONTROLS_CONFIGURATION[0].internalPropertyName =
+ supportedConfigurations.DD_IAST_SECURITY_CONTROLS_CONFIGURATION[0].configurationNames?.[0]
+ delete supportedConfigurations.DD_IAST_SECURITY_CONTROLS_CONFIGURATION[0].configurationNames
+} else {
+ // Default value for DD_TRACE_STARTUP_LOGS is 'false' in older major versions.
+ // This is special handled here until a better solution is found.
+ // TODO: Remove this here once v5 is not supported anymore.
+ supportedConfigurations.DD_TRACE_STARTUP_LOGS[0].default = 'false'
+}
/**
- * @param {string|null} raw
- * @param {string} type
- * @returns {string|number|boolean|Record|unknown[]|undefined}
+ * Warns about an invalid value for an option and adds the error to the last telemetry entry if it is not already set.
+ * Logging happens only if the error is not already set or the option name is different from the last telemetry entry.
+ *
+ * @param {unknown} value - The value that is invalid.
+ * @param {string} optionName - The name of the option.
+ * @param {string} source - The source of the value.
+ * @param {string} baseMessage - The base message to use for the warning.
+ * @param {Error} [error] - An error that was thrown while parsing the value.
*/
-function parseDefaultByType (raw, type) {
- if (raw === null) {
- return
+function warnInvalidValue (value, optionName, source, baseMessage, error) {
+ const canonicalName = (optionsTable[optionName]?.canonicalName ?? optionName) + source
+ // Lazy load log module to avoid circular dependency
+ if (!parseErrors.has(canonicalName)) {
+ // TODO: Rephrase: It will fallback to former source (or default if not set)
+ let message = `${baseMessage}: ${util.inspect(value)} for ${optionName} (source: ${source}), picked default`
+ if (error) {
+ error.stack = error.toString()
+ message += `\n\n${util.inspect(error)}`
+ }
+ parseErrors.set(canonicalName, { message })
+ log ??= require('../log')
+ const logLevel = error ? 'error' : 'warn'
+ log[logLevel](message)
}
+}
+setWarnInvalidValue(warnInvalidValue)
+
+/** @type {import('./config-types').ConfigDefaults} */
+const defaults = {
+ instrumentationSource: 'manual',
+ isServiceUserProvided: false,
+ isServiceNameInferred: true,
+ plugins: true,
+ isCiVisibility: false,
+ lookup: dns.lookup,
+ logger: undefined,
+}
+
+for (const [name, value] of Object.entries(defaults)) {
+ configWithOrigin.set(`${name}default`, {
+ name,
+ value: value ?? null,
+ origin: 'default',
+ seq_id: seqId++,
+ })
+}
- switch (type) {
- case 'boolean':
- if (isTrue(raw)) return true
- if (isFalse(raw)) return false
- // TODO: What should we do with these?
- return
- case 'int':
- case 'decimal': {
- return Number(raw)
+/**
+ * @param {unknown} value
+ * @param {string} origin
+ * @param {string} optionName
+ */
+function generateTelemetry (value = null, origin, optionName) {
+ const { type, canonicalName = optionName } = configurationsTable[optionName] ?? { type: typeof value }
+ // TODO: Consider adding a preParser hook to the parsers object.
+ if (canonicalName === 'OTEL_RESOURCE_ATTRIBUTES') {
+ value = telemetryTransformers.MAP(value)
+ }
+ // TODO: Should we not send defaults to telemetry to reduce size?
+ // TODO: How to handle aliases/actual names in the future? Optional fields? Normalize the name at intake?
+ // TODO: Validate that space separated tags are parsed by the backend. Optimizations would be possible with that.
+ // TODO: How to handle telemetry reporting for aliases?
+ if (value !== null) {
+ if (telemetryTransformers[type]) {
+ value = telemetryTransformers[type](value)
+ } else if (typeof value === 'object' && value !== null) {
+ value = value instanceof URL
+ ? String(value)
+ : JSON.stringify(value)
+ } else if (typeof value === 'function') {
+ value = value.name || 'function'
}
- case 'array': {
- if (!raw || raw.length === 0) return []
- // TODO: Make the parsing a helper that is reused.
- return raw.split(',').map(item => {
- const colonIndex = item.indexOf(':')
- if (colonIndex === -1) {
- return item.trim()
+ }
+ const telemetryEntry = {
+ name: canonicalName,
+ value,
+ origin,
+ seq_id: seqId++,
+ }
+ const error = parseErrors.get(`${canonicalName}${origin}`)
+ if (error) {
+ parseErrors.delete(`${canonicalName}${origin}`)
+ telemetryEntry.error = error
+ }
+ configWithOrigin.set(`${canonicalName}${origin}`, telemetryEntry)
+}
+
+// Iterate over the object and always handle the leaf properties as lookup.
+// Example entries:
+//
+// cloudPayloadTagging: {
+// nestedProperties: [
+// 'rules',
+// 'requestsEnabled',
+// 'responses',
+// ],
+// option: {
+// property: 'rules',
+// parser: parsers.JSON,
+// canonicalName: 'DD_TRACE_CLOUD_REQUEST_PAYLOAD_TAGGING',
+// transformer: transformers.toCamelCase,
+// },
+// },
+// 'cloudPayloadTagging.responses': {
+// nestedProperties: [
+// 'enabled',
+// ],
+// },
+// 'cloudPayloadTagging.rules': {},
+// 'cloudPayloadTagging.requestsEnabled': {},
+// 'cloudPayloadTagging.responses.enabled': {}
+const optionsTable = {
+ // Additional properties that are not supported by the supported-configurations.json file.
+ lookup: {
+ transformer (value) {
+ if (typeof value === 'function') {
+ return value
+ }
+ },
+ property: 'lookup',
+ },
+ logger: {
+ transformer (object) {
+ if (typeof object === 'object' &&
+ object !== null &&
+ Object.values(object).some(value => typeof value === 'function')) {
+ return object
+ }
+ },
+ property: 'logger',
+ },
+ isCiVisibility: {
+ property: 'isCiVisibility',
+ },
+ plugins: {
+ property: 'plugins',
+ },
+}
+
+const parser = (value, optionName, source) => {
+ const { type, canonicalName = optionName } = configurationsTable[optionName]
+ const parsed = parsers[type](value, canonicalName)
+ if (parsed === undefined) {
+ warnInvalidValue(value, optionName, source, `Invalid ${type} input`)
+ }
+ return parsed
+}
+
+/**
+ * @template {import('./config-types').ConfigPath} TPath
+ * @type {Partial unknown,
+ * canonicalName?: string,
+ * transformer?: (value: unknown, optionName: string, source: string) => unknown,
+ * telemetryTransformer?: (value: unknown) => unknown
+ * }>>} ConfigurationsTable
+ */
+const configurationsTable = {}
+
+// One way aliases. Must be applied in apply calculated entries.
+const fallbackConfigurations = new Map()
+
+const regExps = {}
+
+for (const [canonicalName, entries] of Object.entries(supportedConfigurations)) {
+ if (entries.length !== 1) {
+ // TODO: Determine if we really want to support multiple entries for a canonical name.
+ // This would be needed to show official support for multiple diverging implementations
+ // at a time with by checking for another configuration that is not the canonical name.
+ throw new Error(
+ `Multiple entries found for canonical name: ${canonicalName}. ` +
+ 'This is currently not supported and must be implemented, if needed.'
+ )
+ }
+ for (const entry of entries) {
+ const configurationNames = entry.internalPropertyName ? [entry.internalPropertyName] : entry.configurationNames
+ const fullPropertyName = configurationNames?.[0] ?? canonicalName
+ const type = entry.type.toUpperCase()
+
+ let transformer = transformers[entry.transform]
+ if (entry.allowed) {
+ regExps[entry.allowed] ??= new RegExp(`^(${entry.allowed})$`, 'i')
+ const allowed = regExps[entry.allowed]
+ const originalTransform = transformer
+ transformer = (value, optionName, source) => {
+ if (!allowed.test(value)) {
+ warnInvalidValue(value, optionName, source, 'Invalid value')
+ return
}
- const key = item.slice(0, colonIndex).trim()
- const value = item.slice(colonIndex + 1).trim()
- return `${key}:${value}`
- })
+ if (originalTransform) {
+ value = originalTransform(value)
+ }
+ return value
+ }
}
- case 'map': {
- if (!raw || raw.length === 0) return {}
- // TODO: Make the parsing a helper that is reused.
- /** @type {Record} */
- const entries = {}
- for (const item of raw.split(',')) {
- const colonIndex = item.indexOf(':')
- if (colonIndex === -1) {
- const key = item.trim()
- if (key.length > 0) {
- entries[key] = ''
- }
+
+ const option = { parser, type }
+
+ if (fullPropertyName !== canonicalName) {
+ option.property = fullPropertyName
+ option.canonicalName = canonicalName
+ configurationsTable[fullPropertyName] = option
+ }
+ if (transformer) {
+ option.transformer = transformer
+ }
+ if (entry.configurationNames) {
+ addOption(option, type, entry.configurationNames)
+ }
+ configurationsTable[canonicalName] = option
+
+ if (entry.default === null) {
+ defaults[fullPropertyName] = undefined
+ } else {
+ let parsedDefault = parser(entry.default, fullPropertyName, 'default')
+ if (entry.transform) {
+ parsedDefault = transformer(parsedDefault, fullPropertyName, 'default')
+ }
+ defaults[fullPropertyName] = parsedDefault
+ }
+ generateTelemetry(defaults[fullPropertyName], 'default', fullPropertyName)
+
+ if (entry.aliases) {
+ for (const alias of entry.aliases) {
+ if (!supportedConfigurations[alias]) {
+ // An actual alias has no matching entry
continue
}
- const key = item.slice(0, colonIndex).trim()
- const value = item.slice(colonIndex + 1).trim()
- if (key.length > 0) {
- entries[key] = value
+ if (!supportedConfigurations[alias].aliases?.includes(canonicalName)) {
+ // Alias will be replaced with the full property name of the alias, if it exists.
+ fallbackConfigurations.set(fullPropertyName, alias)
}
}
- return entries
}
- default:
- return raw
}
}
-/** @type {Record} */
-const metadataDefaults = {}
-for (const entries of Object.values(supportedConfigurations)) {
- for (const entry of entries) {
- // TODO: Replace $dynamic with method names that would be called and that
- // are also called when the user passes through the value. That way the
- // handling is unified and methods can be declared as default.
- // The name of that method should be expressive for users.
- // TODO: Add handling for all environment variable names. They should not
- // need a configuration name for being listed with their default.
- if (!Array.isArray(entry.configurationNames)) {
- continue
- }
+// Replace the alias with the canonical property name.
+for (const [fullPropertyName, alias] of fallbackConfigurations) {
+ if (configurationsTable[alias].property) {
+ fallbackConfigurations.set(fullPropertyName, configurationsTable[alias].property)
+ }
+}
+
+function addOption (option, type, configurationNames) {
+ for (const name of configurationNames) {
+ let index = -1
+ let lastNestedProperties
+ while (true) {
+ const nextIndex = name.indexOf('.', index + 1)
+ const intermediateName = nextIndex === -1 ? name : name.slice(0, nextIndex)
+ if (lastNestedProperties) {
+ lastNestedProperties.add(intermediateName.slice(index + 1))
+ }
- const parsedValue = parseDefaultByType(entry.default, entry.type)
- for (const configurationName of entry.configurationNames) {
- metadataDefaults[configurationName] = entry.default === null ? undefined : parsedValue
+ if (nextIndex === -1) {
+ if (optionsTable[name]) {
+ if (optionsTable[name].nestedProperties && !optionsTable[name].option) {
+ optionsTable[name].option = option
+ break
+ }
+ throw new Error(`Duplicate configuration name: ${name}`)
+ }
+ optionsTable[name] = option
+ break
+ }
+
+ lastNestedProperties = new Set()
+ index = nextIndex
+
+ if (!optionsTable[intermediateName]) {
+ optionsTable[intermediateName] = {
+ nestedProperties: lastNestedProperties,
+ }
+ } else if (optionsTable[intermediateName].nestedProperties) {
+ lastNestedProperties = optionsTable[intermediateName].nestedProperties
+ } else {
+ optionsTable[intermediateName] = {
+ nestedProperties: lastNestedProperties,
+ option: optionsTable[intermediateName],
+ }
+ }
}
}
}
-// Defaults required by JS config merge/applyCalculated that are not represented in supported-configurations.
-const defaultsWithoutSupportedConfigurationEntry = {
- 'cloudPayloadTagging.rules': [],
- 'cloudPayloadTagging.requestsEnabled': false,
- 'cloudPayloadTagging.responsesEnabled': false,
- isAzureFunction: false,
- isCiVisibility: false,
- isGCPFunction: false,
- instrumentationSource: 'manual',
- isServiceUserProvided: false,
- isServiceNameInferred: true,
- lookup: undefined,
- plugins: true,
-}
+module.exports = {
+ configurationsTable,
-// These values are documented in supported-configurations as CI Visibility
-// defaults. Keep startup baseline false and let #applyCalculated() switch them
-// when CI Visibility is active.
-// TODO: These entries should be removed. They are off by default
-// because they rely on other configs.
-const defaultsWithConditionalRuntimeBehavior = {
- startupLogs: DD_MAJOR >= 6,
- isGitUploadEnabled: false,
- isImpactedTestsEnabled: false,
- isIntelligentTestRunnerEnabled: false,
- isManualApiEnabled: false,
- isTestManagementEnabled: false,
- // TODO: These are not conditional, they would just be of type number.
- 'dogstatsd.port': '8125',
- port: '8126',
- // Override due to expecting numbers, not strings. TODO: Replace later.
- 'grpc.client.error.statuses': [
- 1,
- 2,
- 3,
- 4,
- 5,
- 6,
- 7,
- 8,
- 9,
- 10,
- 11,
- 12,
- 13,
- 14,
- 15,
- 16,
- ],
- 'grpc.server.error.statuses': [
- 2,
- 3,
- 4,
- 5,
- 6,
- 7,
- 8,
- 9,
- 10,
- 11,
- 12,
- 13,
- 14,
- 15,
- 16,
- ],
-}
+ defaults,
-/** @type {Record} */
-const defaults = {
- ...defaultsWithoutSupportedConfigurationEntry,
- ...metadataDefaults,
- ...defaultsWithConditionalRuntimeBehavior,
- service,
- version: pkg.version,
-}
+ fallbackConfigurations,
-module.exports = defaults
+ optionsTable,
+
+ configWithOrigin,
+
+ parseErrors,
+
+ generateTelemetry,
+}
diff --git a/packages/dd-trace/src/config/generated-config-types.d.ts b/packages/dd-trace/src/config/generated-config-types.d.ts
new file mode 100644
index 00000000000..71394bbd911
--- /dev/null
+++ b/packages/dd-trace/src/config/generated-config-types.d.ts
@@ -0,0 +1,582 @@
+// This file is generated from packages/dd-trace/src/config/supported-configurations.json
+// by scripts/generate-config-types.js. Do not edit this file directly.
+
+export interface GeneratedConfig {
+ _DD_APM_TRACING_AGENTLESS_ENABLED: boolean;
+ apiKey: string | undefined;
+ apmTracingEnabled: boolean;
+ appKey: string | undefined;
+ appsec: {
+ apiSecurity: {
+ downstreamBodyAnalysisSampleRate: number;
+ enabled: boolean;
+ endpointCollectionEnabled: boolean;
+ endpointCollectionMessageLimit: number;
+ maxDownstreamRequestBodyAnalysis: number;
+ sampleDelay: number;
+ };
+ blockedTemplateGraphql: string | undefined;
+ blockedTemplateHtml: string | undefined;
+ blockedTemplateJson: string | undefined;
+ enabled: boolean | undefined;
+ eventTracking: {
+ mode: string;
+ };
+ extendedHeadersCollection: {
+ enabled: boolean;
+ maxHeaders: number;
+ redaction: boolean;
+ };
+ obfuscatorKeyRegex: string;
+ obfuscatorValueRegex: string;
+ rasp: {
+ bodyCollection: boolean;
+ enabled: boolean;
+ };
+ rateLimit: number;
+ rules: string | undefined;
+ sca: {
+ enabled: boolean | undefined;
+ };
+ stackTrace: {
+ enabled: boolean;
+ maxDepth: number;
+ maxStackTraces: number;
+ };
+ wafTimeout: number;
+ };
+ baggageMaxBytes: number;
+ baggageMaxItems: number;
+ baggageTagKeys: string[];
+ ciVisAgentlessLogSubmissionEnabled: boolean;
+ ciVisibilityTestSessionName: string | undefined;
+ clientIpEnabled: boolean;
+ clientIpHeader: string | undefined;
+ cloudPayloadTagging: {
+ maxDepth: number;
+ request: string[] | undefined;
+ response: string[] | undefined;
+ };
+ codeOriginForSpans: {
+ enabled: boolean;
+ experimental: {
+ exit_spans: {
+ enabled: boolean;
+ };
+ };
+ };
+ crashtracking: {
+ enabled: boolean;
+ };
+ dbm: {
+ injectSqlBaseHash: boolean;
+ };
+ dbmPropagationMode: string;
+ DD_ACTION_EXECUTION_ID: string | undefined;
+ DD_AGENTLESS_LOG_SUBMISSION_URL: string | undefined;
+ DD_APM_FLUSH_DEADLINE_MILLISECONDS: number;
+ DD_AZURE_RESOURCE_GROUP: string | undefined;
+ DD_CIVISIBILITY_AGENTLESS_ENABLED: boolean;
+ DD_CIVISIBILITY_AGENTLESS_URL: string | undefined;
+ DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER: boolean;
+ DD_CIVISIBILITY_DANGEROUSLY_FORCE_COVERAGE: boolean;
+ DD_CIVISIBILITY_DANGEROUSLY_FORCE_TEST_SKIPPING: boolean;
+ DD_CIVISIBILITY_ENABLED: boolean;
+ DD_CIVISIBILITY_GIT_UNSHALLOW_ENABLED: boolean;
+ DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS: number;
+ DD_CIVISIBILITY_TEST_COMMAND: string | undefined;
+ DD_CIVISIBILITY_TEST_MODULE_ID: string | undefined;
+ DD_CIVISIBILITY_TEST_SESSION_ID: string | undefined;
+ DD_CUSTOM_TRACE_ID: string | undefined;
+ DD_ENABLE_NX_SERVICE_NAME: boolean;
+ DD_EXPERIMENTAL_TEST_OPT_GIT_CACHE_DIR: string;
+ DD_EXPERIMENTAL_TEST_OPT_GIT_CACHE_ENABLED: boolean;
+ DD_EXPERIMENTAL_TEST_OPT_SETTINGS_CACHE: string;
+ DD_EXTERNAL_ENV: string | undefined;
+ DD_GIT_BRANCH: string | undefined;
+ DD_GIT_COMMIT_AUTHOR_DATE: string | undefined;
+ DD_GIT_COMMIT_AUTHOR_EMAIL: string | undefined;
+ DD_GIT_COMMIT_AUTHOR_NAME: string | undefined;
+ DD_GIT_COMMIT_COMMITTER_DATE: string | undefined;
+ DD_GIT_COMMIT_COMMITTER_EMAIL: string | undefined;
+ DD_GIT_COMMIT_COMMITTER_NAME: string | undefined;
+ DD_GIT_COMMIT_HEAD_SHA: string | undefined;
+ DD_GIT_COMMIT_MESSAGE: string | undefined;
+ DD_GIT_COMMIT_SHA: string | undefined;
+ DD_GIT_FOLDER_PATH: string | undefined;
+ DD_GIT_PROPERTIES_FILE: string | undefined;
+ DD_GIT_PULL_REQUEST_BASE_BRANCH: string | undefined;
+ DD_GIT_PULL_REQUEST_BASE_BRANCH_SHA: string | undefined;
+ DD_GIT_REPOSITORY_URL: string | undefined;
+ DD_GIT_TAG: string | undefined;
+ DD_INTERNAL_PROFILING_TIMELINE_SAMPLING_ENABLED: boolean;
+ DD_LAMBDA_HANDLER: string | undefined;
+ DD_MINI_AGENT_PATH: string | undefined;
+ DD_PIPELINE_EXECUTION_ID: string | undefined;
+ DD_PLAYWRIGHT_WORKER: string | undefined;
+ DD_PROFILING_ASYNC_CONTEXT_FRAME_ENABLED: boolean;
+ DD_PROFILING_CODEHOTSPOTS_ENABLED: boolean;
+ DD_PROFILING_CPU_ENABLED: boolean;
+ DD_PROFILING_DEBUG_SOURCE_MAPS: boolean;
+ DD_PROFILING_DEBUG_UPLOAD_COMPRESSION: string;
+ DD_PROFILING_ENDPOINT_COLLECTION_ENABLED: boolean;
+ DD_PROFILING_EXPERIMENTAL_CODEHOTSPOTS_ENABLED: boolean;
+ DD_PROFILING_EXPERIMENTAL_CPU_ENABLED: boolean;
+ DD_PROFILING_EXPERIMENTAL_ENDPOINT_COLLECTION_ENABLED: boolean;
+ DD_PROFILING_EXPERIMENTAL_OOM_EXPORT_STRATEGIES: string[];
+ DD_PROFILING_EXPERIMENTAL_OOM_HEAP_LIMIT_EXTENSION_SIZE: number;
+ DD_PROFILING_EXPERIMENTAL_OOM_MAX_HEAP_EXTENSION_COUNT: number;
+ DD_PROFILING_EXPERIMENTAL_OOM_MONITORING_ENABLED: boolean;
+ DD_PROFILING_EXPERIMENTAL_TIMELINE_ENABLED: boolean;
+ DD_PROFILING_EXPORTERS: string[];
+ DD_PROFILING_HEAP_ENABLED: boolean | undefined;
+ DD_PROFILING_HEAP_SAMPLING_INTERVAL: number;
+ DD_PROFILING_PPROF_PREFIX: string;
+ DD_PROFILING_PROFILERS: string[];
+ DD_PROFILING_SOURCE_MAP: boolean;
+ DD_PROFILING_TIMELINE_ENABLED: boolean;
+ DD_PROFILING_UPLOAD_PERIOD: number;
+ DD_PROFILING_UPLOAD_TIMEOUT: number;
+ DD_PROFILING_V8_PROFILER_BUG_WORKAROUND: boolean;
+ DD_PROFILING_WALLTIME_ENABLED: boolean | undefined;
+ DD_ROOT_JS_SESSION_ID: string | undefined;
+ DD_RUNTIME_METRICS_FLUSH_INTERVAL: number;
+ DD_SPAN_SAMPLING_RULES_FILE: string | undefined;
+ DD_TELEMETRY_FORWARDER_PATH: string | undefined;
+ DD_TEST_FLEET_CONFIG_PATH: string | undefined;
+ DD_TEST_LOCAL_CONFIG_PATH: string | undefined;
+ DD_TRACE_AEROSPIKE_ENABLED: boolean;
+ DD_TRACE_AI_ENABLED: boolean;
+ DD_TRACE_AMQP10_ENABLED: boolean;
+ DD_TRACE_AMQPLIB_ENABLED: boolean;
+ DD_TRACE_ANTHROPIC_ENABLED: boolean;
+ DD_TRACE_APOLLO_ENABLED: boolean;
+ DD_TRACE_APOLLO_GATEWAY_ENABLED: boolean;
+ DD_TRACE_APOLLO_SERVER_CORE_ENABLED: boolean;
+ DD_TRACE_APOLLO_SERVER_ENABLED: boolean;
+ DD_TRACE_APOLLO_SERVER_EXPRESS_ENABLED: boolean;
+ DD_TRACE_APOLLO_SERVER_FASTIFY_ENABLED: boolean;
+ DD_TRACE_APOLLO_SUBGRAPH_ENABLED: boolean;
+ DD_TRACE_AVSC_ENABLED: boolean;
+ DD_TRACE_AWS_SDK_AWS_BATCH_PROPAGATION_ENABLED: boolean;
+ DD_TRACE_AWS_SDK_AWS_ENABLED: boolean;
+ DD_TRACE_AWS_SDK_BATCH_PROPAGATION_ENABLED: boolean;
+ DD_TRACE_AWS_SDK_BEDROCKRUNTIME_BATCH_PROPAGATION_ENABLED: boolean;
+ DD_TRACE_AWS_SDK_BEDROCKRUNTIME_ENABLED: boolean;
+ DD_TRACE_AWS_SDK_CLOUDWATCHLOGS_BATCH_PROPAGATION_ENABLED: boolean;
+ DD_TRACE_AWS_SDK_CLOUDWATCHLOGS_ENABLED: boolean;
+ DD_TRACE_AWS_SDK_DYNAMODB_BATCH_PROPAGATION_ENABLED: boolean;
+ DD_TRACE_AWS_SDK_DYNAMODB_ENABLED: boolean;
+ DD_TRACE_AWS_SDK_ENABLED: boolean;
+ DD_TRACE_AWS_SDK_EVENTBRIDGE_BATCH_PROPAGATION_ENABLED: boolean;
+ DD_TRACE_AWS_SDK_EVENTBRIDGE_ENABLED: boolean;
+ DD_TRACE_AWS_SDK_KINESIS_BATCH_PROPAGATION_ENABLED: boolean;
+ DD_TRACE_AWS_SDK_KINESIS_ENABLED: boolean;
+ DD_TRACE_AWS_SDK_LAMBDA_BATCH_PROPAGATION_ENABLED: boolean;
+ DD_TRACE_AWS_SDK_LAMBDA_ENABLED: boolean;
+ DD_TRACE_AWS_SDK_NODE_HTTP_HANDLER_ENABLED: boolean;
+ DD_TRACE_AWS_SDK_REDSHIFT_BATCH_PROPAGATION_ENABLED: boolean;
+ DD_TRACE_AWS_SDK_REDSHIFT_ENABLED: boolean;
+ DD_TRACE_AWS_SDK_S3_BATCH_PROPAGATION_ENABLED: boolean;
+ DD_TRACE_AWS_SDK_S3_ENABLED: boolean;
+ DD_TRACE_AWS_SDK_SFN_BATCH_PROPAGATION_ENABLED: boolean;
+ DD_TRACE_AWS_SDK_SFN_CLIENT_ENABLED: boolean;
+ DD_TRACE_AWS_SDK_SFN_ENABLED: boolean;
+ DD_TRACE_AWS_SDK_SMITHY_CLIENT_ENABLED: boolean;
+ DD_TRACE_AWS_SDK_SNS_BATCH_PROPAGATION_ENABLED: boolean;
+ DD_TRACE_AWS_SDK_SNS_ENABLED: boolean;
+ DD_TRACE_AWS_SDK_SQS_BATCH_PROPAGATION_ENABLED: boolean;
+ DD_TRACE_AWS_SDK_SQS_ENABLED: boolean;
+ DD_TRACE_AWS_SDK_STATES_BATCH_PROPAGATION_ENABLED: boolean;
+ DD_TRACE_AWS_SDK_STATES_ENABLED: boolean;
+ DD_TRACE_AWS_SDK_STEPFUNCTIONS_BATCH_PROPAGATION_ENABLED: boolean;
+ DD_TRACE_AWS_SDK_STEPFUNCTIONS_ENABLED: boolean;
+ DD_TRACE_AXIOS_ENABLED: boolean;
+ DD_TRACE_AZURE_DURABLE_FUNCTIONS_ENABLED: boolean;
+ DD_TRACE_AZURE_EVENT_HUBS_ENABLED: boolean;
+ DD_TRACE_AZURE_EVENTHUBS_BATCH_LINKS_ENABLED: boolean;
+ DD_TRACE_AZURE_FUNCTIONS_ENABLED: boolean;
+ DD_TRACE_AZURE_SERVICE_BUS_ENABLED: boolean;
+ DD_TRACE_AZURE_SERVICEBUS_BATCH_LINKS_ENABLED: boolean;
+ DD_TRACE_BEAUTIFUL_LOGS: boolean;
+ DD_TRACE_BLUEBIRD_ENABLED: boolean;
+ DD_TRACE_BODY_PARSER_ENABLED: boolean;
+ DD_TRACE_BSON_ENABLED: boolean;
+ DD_TRACE_BULLMQ_ENABLED: boolean;
+ DD_TRACE_BUNYAN_ENABLED: boolean;
+ DD_TRACE_CASSANDRA_DRIVER_ENABLED: boolean;
+ DD_TRACE_CHILD_PROCESS_ENABLED: boolean;
+ DD_TRACE_COLLECTIONS_ENABLED: boolean;
+ DD_TRACE_COMMONPLUGIN_ENABLED: boolean;
+ DD_TRACE_CONFLUENTINC_KAFKA_JAVASCRIPT_ENABLED: boolean;
+ DD_TRACE_CONNECT_ENABLED: boolean;
+ DD_TRACE_COOKIE_ENABLED: boolean;
+ DD_TRACE_COOKIE_PARSER_ENABLED: boolean;
+ DD_TRACE_COUCHBASE_ENABLED: boolean;
+ DD_TRACE_CRYPTO_ENABLED: boolean;
+ DD_TRACE_CUCUMBER_CUCUMBER_ENABLED: boolean;
+ DD_TRACE_CUCUMBER_ENABLED: boolean;
+ DD_TRACE_CYPRESS_ENABLED: boolean;
+ DD_TRACE_DEBUG: boolean;
+ DD_TRACE_DISABLED_INSTRUMENTATIONS: string;
+ DD_TRACE_DISABLED_PLUGINS: string | undefined;
+ DD_TRACE_DNS_ENABLED: boolean;
+ DD_TRACE_ELASTIC_ELASTICSEARCH_ENABLED: boolean;
+ DD_TRACE_ELASTIC_TRANSPORT_ENABLED: boolean;
+ DD_TRACE_ELASTICSEARCH_ENABLED: boolean;
+ DD_TRACE_ENCODING_DEBUG: boolean;
+ DD_TRACE_EXPERIMENTAL_RUNTIME_ID_ENABLED: boolean;
+ DD_TRACE_EXPERIMENTAL_SPAN_COUNTS: boolean;
+ DD_TRACE_EXPERIMENTAL_STATE_TRACKING: boolean;
+ DD_TRACE_EXPRESS_ENABLED: boolean;
+ DD_TRACE_EXPRESS_MONGO_SANITIZE_ENABLED: boolean;
+ DD_TRACE_EXPRESS_SESSION_ENABLED: boolean;
+ DD_TRACE_FASTIFY_ENABLED: boolean;
+ DD_TRACE_FETCH_ENABLED: boolean;
+ DD_TRACE_FIND_MY_WAY_ENABLED: boolean;
+ DD_TRACE_FS_ENABLED: boolean;
+ DD_TRACE_GCP_PUBSUB_PUSH_ENABLED: boolean;
+ DD_TRACE_GENERIC_POOL_ENABLED: boolean;
+ DD_TRACE_GOOGLE_CLOUD_PUBSUB_ENABLED: boolean;
+ DD_TRACE_GOOGLE_CLOUD_VERTEXAI_ENABLED: boolean;
+ DD_TRACE_GOOGLE_GAX_ENABLED: boolean;
+ DD_TRACE_GOOGLE_GENAI_ENABLED: boolean;
+ DD_TRACE_GRAPHQL_ENABLED: boolean;
+ DD_TRACE_GRAPHQL_TAG_ENABLED: boolean;
+ DD_TRACE_GRAPHQL_TOOLS_ENABLED: boolean;
+ DD_TRACE_GRAPHQL_TOOLS_EXECUTOR_ENABLED: boolean;
+ DD_TRACE_GRAPHQL_YOGA_ENABLED: boolean;
+ DD_TRACE_GRPC_ENABLED: boolean;
+ DD_TRACE_GRPC_GRPC_JS_ENABLED: boolean;
+ DD_TRACE_GRPC_PROTO_LOADER_ENABLED: boolean;
+ DD_TRACE_HANDLEBARS_ENABLED: boolean;
+ DD_TRACE_HAPI_BOOM_ENABLED: boolean;
+ DD_TRACE_HAPI_ENABLED: boolean;
+ DD_TRACE_HAPI_HAPI_ENABLED: boolean;
+ DD_TRACE_HONO_ENABLED: boolean;
+ DD_TRACE_HTTP_ENABLED: boolean;
+ DD_TRACE_HTTP2_ENABLED: boolean;
+ DD_TRACE_HTTPS_ENABLED: boolean;
+ DD_TRACE_IOREDIS_ENABLED: boolean;
+ DD_TRACE_IOVALKEY_ENABLED: boolean;
+ DD_TRACE_JEST_CIRCUS_ENABLED: boolean;
+ DD_TRACE_JEST_CONFIG_ENABLED: boolean;
+ DD_TRACE_JEST_CORE_ENABLED: boolean;
+ DD_TRACE_JEST_ENABLED: boolean;
+ DD_TRACE_JEST_ENVIRONMENT_JSDOM_ENABLED: boolean;
+ DD_TRACE_JEST_ENVIRONMENT_NODE_ENABLED: boolean;
+ DD_TRACE_JEST_GLOBALS_ENABLED: boolean;
+ DD_TRACE_JEST_REPORTERS_ENABLED: boolean;
+ DD_TRACE_JEST_RUNTIME_ENABLED: boolean;
+ DD_TRACE_JEST_TEST_SEQUENCER_ENABLED: boolean;
+ DD_TRACE_JEST_TRANSFORM_ENABLED: boolean;
+ DD_TRACE_JEST_WORKER_ENABLED: boolean;
+ DD_TRACE_KAFKAJS_ENABLED: boolean;
+ DD_TRACE_KNEX_ENABLED: boolean;
+ DD_TRACE_KOA_ENABLED: boolean;
+ DD_TRACE_KOA_ROUTE_ENABLED: boolean;
+ DD_TRACE_KOA_ROUTER_ENABLED: boolean;
+ DD_TRACE_KOA_WEBSOCKET_ENABLED: boolean;
+ DD_TRACE_LANGCHAIN_ANTHROPIC_ENABLED: boolean;
+ DD_TRACE_LANGCHAIN_COHERE_ENABLED: boolean;
+ DD_TRACE_LANGCHAIN_CORE_ENABLED: boolean;
+ DD_TRACE_LANGCHAIN_ENABLED: boolean;
+ DD_TRACE_LANGCHAIN_GOOGLE_GENAI_ENABLED: boolean;
+ DD_TRACE_LANGCHAIN_OPENAI_ENABLED: boolean;
+ DD_TRACE_LANGGRAPH_ENABLED: boolean;
+ DD_TRACE_LDAPJS_ENABLED: boolean;
+ DD_TRACE_LDAPJS_PROMISE_ENABLED: boolean;
+ DD_TRACE_LIMITD_CLIENT_ENABLED: boolean;
+ DD_TRACE_LODASH_ENABLED: boolean;
+ DD_TRACE_LOOPBACK_ENABLED: boolean;
+ DD_TRACE_MARIADB_ENABLED: boolean;
+ DD_TRACE_MEMCACHED_ENABLED: boolean;
+ DD_TRACE_MICROGATEWAY_CORE_ENABLED: boolean;
+ DD_TRACE_MIDDIE_ENABLED: boolean;
+ DD_TRACE_MOCHA_EACH_ENABLED: boolean;
+ DD_TRACE_MOCHA_ENABLED: boolean;
+ DD_TRACE_MOLECULER_ENABLED: boolean;
+ DD_TRACE_MONGODB_CORE_ENABLED: boolean;
+ DD_TRACE_MONGODB_ENABLED: boolean;
+ DD_TRACE_MONGODB_HEARTBEAT_ENABLED: boolean;
+ DD_TRACE_MONGOOSE_ENABLED: boolean;
+ DD_TRACE_MQUERY_ENABLED: boolean;
+ DD_TRACE_MULTER_ENABLED: boolean;
+ DD_TRACE_MYSQL_ENABLED: boolean;
+ DD_TRACE_MYSQL2_ENABLED: boolean;
+ DD_TRACE_NET_ENABLED: boolean;
+ DD_TRACE_NEXT_ENABLED: boolean;
+ DD_TRACE_NODE_CHILD_PROCESS_ENABLED: boolean;
+ DD_TRACE_NODE_REDIS_CLIENT_ENABLED: boolean;
+ DD_TRACE_NODE_SERIALIZE_ENABLED: boolean;
+ DD_TRACE_NYC_ENABLED: boolean;
+ DD_TRACE_OPENAI_ENABLED: boolean;
+ DD_TRACE_OPENSEARCH_ENABLED: boolean;
+ DD_TRACE_OPENSEARCH_PROJECT_OPENSEARCH_ENABLED: boolean;
+ DD_TRACE_OPENTELEMETRY_SDK_TRACE_NODE_ENABLED: boolean;
+ DD_TRACE_ORACLEDB_ENABLED: boolean;
+ DD_TRACE_OTEL_ENABLED: boolean;
+ DD_TRACE_PASSPORT_ENABLED: boolean;
+ DD_TRACE_PASSPORT_HTTP_ENABLED: boolean;
+ DD_TRACE_PASSPORT_LOCAL_ENABLED: boolean;
+ DD_TRACE_PG_CURSOR_ENABLED: boolean;
+ DD_TRACE_PG_ENABLED: boolean;
+ DD_TRACE_PG_NATIVE_ENABLED: boolean;
+ DD_TRACE_PG_QUERY_STREAM_ENABLED: boolean;
+ DD_TRACE_PINO_ENABLED: boolean;
+ DD_TRACE_PINO_PRETTY_ENABLED: boolean;
+ DD_TRACE_PLAYWRIGHT_CORE_ENABLED: boolean;
+ DD_TRACE_PLAYWRIGHT_ENABLED: boolean;
+ DD_TRACE_PLAYWRIGHT_TEST_ENABLED: boolean;
+ DD_TRACE_PRISMA_ENABLED: boolean;
+ DD_TRACE_PROCESS_ENABLED: boolean;
+ DD_TRACE_PROMISE_ENABLED: boolean;
+ DD_TRACE_PROMISE_JS_ENABLED: boolean;
+ DD_TRACE_PROPAGATION_STYLE: string[];
+ DD_TRACE_PROTOBUFJS_ENABLED: boolean;
+ DD_TRACE_PUG_ENABLED: boolean;
+ DD_TRACE_Q_ENABLED: boolean;
+ DD_TRACE_REACT_DOM_ENABLED: boolean;
+ DD_TRACE_REACT_ENABLED: boolean;
+ DD_TRACE_REDIS_CLIENT_ENABLED: boolean;
+ DD_TRACE_REDIS_ENABLED: boolean;
+ DD_TRACE_REQUEST_ENABLED: boolean;
+ DD_TRACE_RESTIFY_ENABLED: boolean;
+ DD_TRACE_RHEA_ENABLED: boolean;
+ DD_TRACE_ROUTER_ENABLED: boolean;
+ DD_TRACE_SELENIUM_ENABLED: boolean;
+ DD_TRACE_SELENIUM_WEBDRIVER_ENABLED: boolean;
+ DD_TRACE_SEQUELIZE_ENABLED: boolean;
+ DD_TRACE_SHAREDB_ENABLED: boolean;
+ DD_TRACE_SMITHY_SMITHY_CLIENT_ENABLED: boolean;
+ DD_TRACE_SQLITE3_ENABLED: boolean;
+ DD_TRACE_SUFFIXPLUGIN_ENABLED: boolean;
+ DD_TRACE_TAGS: Record | undefined;
+ DD_TRACE_TEDIOUS_ENABLED: boolean;
+ DD_TRACE_UNDICI_ENABLED: boolean;
+ DD_TRACE_URL_ENABLED: boolean;
+ DD_TRACE_VITEST_ENABLED: boolean;
+ DD_TRACE_VITEST_RUNNER_ENABLED: boolean;
+ DD_TRACE_VM_ENABLED: boolean;
+ DD_TRACE_WHEN_ENABLED: boolean;
+ DD_TRACE_WINSTON_ENABLED: boolean;
+ DD_TRACE_WORKERPOOL_ENABLED: boolean;
+ DD_TRACE_WS_ENABLED: boolean;
+ DD_VITEST_WORKER: string | undefined;
+ dogstatsd: {
+ hostname: string;
+ port: string | number;
+ };
+ dsmEnabled: boolean;
+ dynamicInstrumentation: {
+ captureTimeoutMs: number;
+ enabled: boolean;
+ probeFile: string | undefined;
+ redactedIdentifiers: string[];
+ redactionExcludedIdentifiers: string[];
+ uploadIntervalSeconds: number;
+ };
+ env: string | undefined;
+ experimental: {
+ aiguard: {
+ enabled: boolean;
+ endpoint: string | undefined;
+ maxContentSize: number;
+ maxMessagesLength: number;
+ timeout: number;
+ };
+ appsec: {
+ standalone: {
+ enabled: boolean;
+ };
+ };
+ b3: boolean;
+ enableGetRumData: boolean;
+ exporter: string;
+ flaggingProvider: {
+ enabled: boolean;
+ initializationTimeoutMs: number;
+ };
+ };
+ flakyTestRetriesCount: number;
+ flushInterval: number;
+ flushMinSpans: number;
+ gitMetadataEnabled: boolean;
+ graphqlErrorExtensions: string[];
+ grpc: {
+ client: {
+ error: {
+ statuses: number[];
+ };
+ };
+ server: {
+ error: {
+ statuses: number[];
+ };
+ };
+ };
+ headerTags: string[];
+ heapSnapshot: {
+ count: number;
+ destination: string;
+ interval: number;
+ };
+ hostname: string;
+ iast: {
+ dbRowsToTaint: number;
+ deduplicationEnabled: boolean;
+ enabled: boolean;
+ maxConcurrentRequests: number;
+ maxContextOperations: number;
+ redactionEnabled: boolean;
+ redactionNamePattern: string;
+ redactionValuePattern: string;
+ requestSampling: number;
+ securityControlsConfiguration: string | undefined;
+ stackTrace: {
+ enabled: boolean;
+ };
+ telemetryVerbosity: string;
+ };
+ inferredProxyServicesEnabled: boolean;
+ injectForce: boolean;
+ injectionEnabled: string | undefined;
+ installSignature: {
+ id: string | undefined;
+ time: string | undefined;
+ type: string | undefined;
+ };
+ instrumentation_config_id: string | undefined;
+ isEarlyFlakeDetectionEnabled: boolean;
+ isFlakyTestRetriesEnabled: boolean;
+ isGitUploadEnabled: boolean;
+ isImpactedTestsEnabled: boolean;
+ isIntelligentTestRunnerEnabled: boolean;
+ isKeepingCoverageConfiguration: boolean;
+ isManualApiEnabled: boolean;
+ isTestDynamicInstrumentationEnabled: boolean;
+ isTestManagementEnabled: boolean;
+ langchain: {
+ spanCharLimit: number;
+ spanPromptCompletionSampleRate: number;
+ };
+ legacyBaggageEnabled: boolean;
+ llmobs: {
+ agentlessEnabled: boolean | undefined;
+ enabled: boolean;
+ mlApp: string | undefined;
+ };
+ logInjection: boolean;
+ logLevel: "debug" | "info" | "warn" | "error";
+ memcachedCommandEnabled: boolean;
+ middlewareTracingEnabled: boolean;
+ openai: {
+ spanCharLimit: number;
+ };
+ openAiLogsEnabled: boolean;
+ OTEL_EXPORTER_OTLP_ENDPOINT: string | undefined;
+ OTEL_LOG_LEVEL: "debug" | "info" | "warn" | "error" | undefined;
+ OTEL_LOGS_EXPORTER: "none" | "otlp" | undefined;
+ OTEL_METRICS_EXPORTER: "none" | "otlp" | undefined;
+ OTEL_RESOURCE_ATTRIBUTES: Record;
+ OTEL_SDK_DISABLED: boolean;
+ OTEL_TRACES_EXPORTER: "none" | "otlp";
+ OTEL_TRACES_SAMPLER: "always_on" | "always_off" | "traceidratio" | "parentbased_always_on" | "parentbased_always_off" | "parentbased_traceidratio";
+ OTEL_TRACES_SAMPLER_ARG: number | undefined;
+ otelBatchTimeout: number;
+ otelHeaders: string | undefined;
+ otelLogsEnabled: boolean;
+ otelLogsHeaders: string | undefined;
+ otelLogsProtocol: string;
+ otelLogsTimeout: number;
+ otelLogsUrl: string | undefined;
+ otelMaxExportBatchSize: number;
+ otelMaxQueueSize: number;
+ otelMetricsEnabled: boolean;
+ otelMetricsExportInterval: number;
+ otelMetricsExportTimeout: number;
+ otelMetricsHeaders: string | undefined;
+ otelMetricsProtocol: string;
+ otelMetricsTemporalityPreference: "DELTA" | "CUMULATIVE" | "LOWMEMORY";
+ otelMetricsTimeout: number;
+ otelMetricsUrl: string | undefined;
+ otelProtocol: string;
+ otelTimeout: number;
+ peerServiceMapping: Record;
+ port: string | number;
+ profiling: {
+ enabled: 'true' | 'false' | 'auto';
+ longLivedThreshold: number;
+ };
+ propagateProcessTags: {
+ enabled: boolean;
+ };
+ protocolVersion: string;
+ queryStringObfuscation: string;
+ rateLimit: number;
+ remoteConfig: {
+ enabled: boolean;
+ pollInterval: number;
+ };
+ reportHostname: boolean;
+ resourceRenamingEnabled: boolean;
+ runtimeMetrics: {
+ enabled: boolean;
+ eventLoop: boolean;
+ gc: boolean;
+ };
+ runtimeMetricsRuntimeId: boolean;
+ sampleRate: number | undefined;
+ samplingRules: import('../../../../index').SamplingRule[];
+ scope: string | undefined;
+ service: string | undefined;
+ serviceMapping: Record;
+ site: string;
+ spanAttributeSchema: "v0" | "v1";
+ spanComputePeerService: boolean;
+ spanLeakDebug: number;
+ spanRemoveIntegrationFromService: boolean;
+ spanSamplingRules: import('../../../../index').SpanSamplingRule[] | undefined;
+ startupLogs: boolean;
+ stats: {
+ enabled: boolean;
+ };
+ tags: Record;
+ tagsHeaderMaxLength: number;
+ telemetry: {
+ debug: boolean;
+ dependencyCollection: boolean;
+ enabled: boolean;
+ heartbeatInterval: number;
+ logCollection: boolean;
+ metrics: boolean;
+ };
+ testManagementAttemptToFixRetries: number;
+ trace: {
+ aws: {
+ addSpanPointers: boolean;
+ };
+ dynamoDb: {
+ tablePrimaryKeys: string | undefined;
+ };
+ nativeSpanEvents: boolean;
+ };
+ traceId128BitGenerationEnabled: boolean;
+ traceId128BitLoggingEnabled: boolean;
+ tracePropagationBehaviorExtract: "continue" | "restart" | "ignore";
+ tracePropagationExtractFirst: boolean;
+ tracePropagationStyle: {
+ extract: string[];
+ inject: string[];
+ };
+ traceWebsocketMessagesEnabled: boolean;
+ traceWebsocketMessagesInheritSampling: boolean;
+ traceWebsocketMessagesSeparateTraces: boolean;
+ tracing: boolean;
+ url: string | URL;
+ version: string | undefined;
+ vertexai: {
+ spanCharLimit: number;
+ spanPromptCompletionSampleRate: number;
+ };
+}
diff --git a/packages/dd-trace/src/config/helper.js b/packages/dd-trace/src/config/helper.js
index 0ba7b197758..011fa0caa1a 100644
--- a/packages/dd-trace/src/config/helper.js
+++ b/packages/dd-trace/src/config/helper.js
@@ -9,6 +9,9 @@
* @property {string|number|boolean|null|object|unknown[]} default
* @property {string[]} [aliases]
* @property {string[]} [configurationNames]
+ * @property {string} [internalPropertyName]
+ * @property {string} [transform]
+ * @property {string} [allowed]
* @property {string|boolean} [deprecated]
*/
@@ -57,6 +60,13 @@ for (const [canonical, configuration] of Object.entries(supportedConfigurations)
const aliasToCanonical = {}
for (const canonical of Object.keys(aliases)) {
for (const alias of aliases[canonical]) {
+ if (supportedConfigurations[alias]) {
+ // Allow 'fallback' aliases to be used for other configurations.
+ // This is used to handle the case where an alias could be used for multiple configurations.
+ // For example, OTEL_EXPORTER_OTLP_ENDPOINT is used for OTEL_EXPORTER_OTLP_LOGS_ENDPOINT
+ // and OTEL_EXPORTER_OTLP_METRICS_ENDPOINT.
+ continue
+ }
if (aliasToCanonical[alias]) {
throw new Error(`The alias ${alias} is already used for ${aliasToCanonical[alias]}.`)
}
@@ -99,22 +109,37 @@ function loadStableConfig () {
}
function getValueFromSource (name, source) {
- const value = source[name]
+ if (source[name] !== undefined) {
+ return source[name]
+ }
- if (value === undefined && aliases[name]) {
+ if (aliases[name]) {
for (const alias of aliases[name]) {
if (source[alias] !== undefined) {
return source[alias]
}
}
}
+}
- return value
+function getEnvNameFromSource (name, source) {
+ if (source[name] !== undefined) {
+ return name
+ }
+
+ if (aliases[name]) {
+ for (const alias of aliases[name]) {
+ if (source[alias] !== undefined) {
+ return alias
+ }
+ }
+ }
}
function validateAccess (name) {
- if ((name.startsWith('DD_') || name.startsWith('OTEL_') || aliasToCanonical[name]) &&
- !supportedConfigurations[name]) {
+ if ((name.startsWith('DD_') || name.startsWith('OTEL_')) &&
+ !supportedConfigurations[name] &&
+ !aliasToCanonical[name]) {
throw new Error(`Missing ${name} env/configuration in "supported-configurations.json" file.`)
}
}
@@ -144,10 +169,9 @@ module.exports = {
*
* @returns {TracerEnv} The environment variables
*/
- getEnvironmentVariables () {
+ getEnvironmentVariables (source = process.env, internalOnly = false) {
const configs = {}
- for (const [key, value] of Object.entries(process.env)) {
- // TODO(BridgeAR): Handle telemetry reporting for aliases.
+ for (const [key, value] of Object.entries(source)) {
if (key.startsWith('DD_') || key.startsWith('OTEL_') || aliasToCanonical[key]) {
if (supportedConfigurations[key]) {
configs[key] = value
@@ -155,7 +179,7 @@ module.exports = {
// The alias should only be used if the actual configuration is not set
// In case that more than a single alias exist, use the one defined first in our own order
for (const alias of aliases[aliasToCanonical[key]]) {
- if (process.env[alias] !== undefined) {
+ if (source[alias] !== undefined) {
configs[aliasToCanonical[key]] = value
break
}
@@ -165,9 +189,10 @@ module.exports = {
// debug(
// `Missing configuration ${env} in supported-configurations file. The environment variable is ignored.`
// )
+ // This could be moved inside the main config logic.
}
deprecationMethods[key]?.()
- } else {
+ } else if (!internalOnly) {
configs[key] = value
}
}
@@ -211,4 +236,28 @@ module.exports = {
return getValueFromSource(name, localStableConfig)
}
},
+
+ /**
+ * Returns the actual environment variable name used for a supported configuration
+ * from a specific environment-based source.
+ *
+ * @param {string} name Environment variable name
+ * @returns {string|undefined}
+ */
+ getConfiguredEnvName (name) {
+ validateAccess(name)
+
+ if (!stableConfigLoaded) {
+ loadStableConfig()
+ }
+
+ for (const source of [fleetStableConfig, process.env, localStableConfig]) {
+ if (source !== undefined) {
+ const fromSource = getEnvNameFromSource(name, source)
+ if (fromSource !== undefined) {
+ return fromSource
+ }
+ }
+ }
+ },
}
diff --git a/packages/dd-trace/src/config/index.js b/packages/dd-trace/src/config/index.js
index 797d983e644..2c6222e5622 100644
--- a/packages/dd-trace/src/config/index.js
+++ b/packages/dd-trace/src/config/index.js
@@ -5,1682 +5,745 @@ const os = require('node:os')
const { URL } = require('node:url')
const path = require('node:path')
+const rfdc = require('../../../../vendor/dist/rfdc')({ proto: false, circles: false })
const uuid = require('../../../../vendor/dist/crypto-randomuuid') // we need to keep the old uuid dep because of cypress
-
const set = require('../../../datadog-core/src/utils/src/set')
const { DD_MAJOR } = require('../../../../version')
const log = require('../log')
-const tagger = require('../tagger')
-const { isTrue, isFalse, normalizeProfilingEnabledValue } = require('../util')
+const pkg = require('../pkg')
+const { isTrue } = require('../util')
const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('../plugins/util/tags')
-const { updateConfig } = require('../telemetry')
+const telemetry = require('../telemetry')
const telemetryMetrics = require('../telemetry/metrics')
const {
IS_SERVERLESS,
getIsGCPFunction,
getIsAzureFunction,
- enableGCPPubSubPushSubscription,
} = require('../serverless')
const { ORIGIN_KEY } = require('../constants')
const { appendRules } = require('../payload-tagging/config')
const { getGitMetadataFromGitProperties, removeUserSensitiveInfo, getRemoteOriginURL, resolveGitHeadSHA } =
require('./git_properties')
-const { getEnvironmentVariable: getEnv, getEnvironmentVariables, getStableConfigSources } = require('./helper')
-const defaults = require('./defaults')
-
-const TELEMETRY_COUNTERS = new Map([
- ['otel.env.hiding', {}],
- ['otel.env.invalid', {}],
-])
-const OTEL_DD_ENV_MAPPING = new Map([
- ['OTEL_LOG_LEVEL', 'DD_TRACE_LOG_LEVEL'],
- ['OTEL_PROPAGATORS', 'DD_TRACE_PROPAGATION_STYLE'],
- ['OTEL_SERVICE_NAME', 'DD_SERVICE'],
- ['OTEL_TRACES_SAMPLER', 'DD_TRACE_SAMPLE_RATE'],
- ['OTEL_TRACES_SAMPLER_ARG', 'DD_TRACE_SAMPLE_RATE'],
- ['OTEL_TRACES_EXPORTER', 'DD_TRACE_ENABLED'],
- ['OTEL_METRICS_EXPORTER', 'DD_RUNTIME_METRICS_ENABLED'],
- ['OTEL_RESOURCE_ATTRIBUTES', 'DD_TAGS'],
- ['OTEL_SDK_DISABLED', 'DD_TRACE_OTEL_ENABLED'],
- ['OTEL_LOGS_EXPORTER', undefined],
-])
-const VALID_PROPAGATION_STYLES = new Set(['datadog', 'tracecontext', 'b3', 'b3 single header', 'none'])
-const VALID_PROPAGATION_BEHAVIOR_EXTRACT = new Set(['continue', 'restart', 'ignore'])
-const VALID_LOG_LEVELS = new Set(['debug', 'info', 'warn', 'error'])
-const DEFAULT_OTLP_PORT = 4318
+const ConfigBase = require('./config-base')
+const {
+ getEnvironmentVariable,
+ getEnvironmentVariables,
+ getStableConfigSources,
+ getValueFromEnvSources,
+} = require('./helper')
+const {
+ defaults,
+ fallbackConfigurations,
+ configurationsTable,
+ optionsTable,
+ configWithOrigin,
+ parseErrors,
+ generateTelemetry,
+} = require('./defaults')
+const { transformers } = require('./parsers')
+
const RUNTIME_ID = uuid()
-// eslint-disable-next-line eslint-rules/eslint-process-env -- internal propagation, not user config
-const ROOT_SESSION_ID = process.env.DD_ROOT_JS_SESSION_ID || RUNTIME_ID
-const NAMING_VERSIONS = new Set(['v0', 'v1'])
-const DEFAULT_NAMING_VERSION = 'v0'
const tracerMetrics = telemetryMetrics.manager.namespace('tracers')
-const changeTracker = {}
+/**
+ * @typedef {'default'
+ * | 'code'
+ * | 'remote_config'
+ * | 'calculated'
+ * | 'env_var'
+ * | 'local_stable_config'
+ * | 'fleet_stable_config'} TelemetrySource
+ * @typedef {'remote_config' | 'calculated'} RevertibleTelemetrySource
+ * @typedef {import('../../../../index').TracerOptions} TracerOptions
+ * @typedef {import('./config-types').ConfigKey} ConfigKey
+ * @typedef {import('./config-types').ConfigPath} ConfigPath
+ * @typedef {{
+ * value: import('./config-types').ConfigPathValue,
+ * source: TelemetrySource
+ * }} TrackedConfigEntry
+ * @typedef {{
+ * baseValuesByPath: Partial>,
+ * remote_config: Set,
+ * calculated: Set,
+ * }} ChangeTracker
+ */
+
+/** @type {Config | null} */
let configInstance = null
+// An entry that is undefined means it is the default value.
+/** @type {Map} */
+const trackedConfigOrigins = new Map()
+
+// ChangeTracker tracks the changes to the config up to programmatic options (code).
+/** @type {ChangeTracker} */
+const changeTracker = {
+ baseValuesByPath: {},
+ remote_config: new Set(),
+ calculated: new Set(),
+}
+
+/**
+ * @param {Config} config
+ * @param {RevertibleTelemetrySource} source
+ */
+function undo (config, source) {
+ for (const name of changeTracker[source]) {
+ const entry = changeTracker.baseValuesByPath[name] ?? { source: 'default', value: defaults[name] }
+ setAndTrack(config, name, entry.value, undefined, entry.source)
+ }
+}
+
+function get (object, path) {
+ // Fast path for simple property access.
+ if (object[path] !== undefined) {
+ return object[path]
+ }
+ let index = 0
+ while (true) {
+ const nextIndex = path.indexOf('.', index)
+ if (nextIndex === -1) {
+ return object[path.slice(index)]
+ }
+ object = object[path.slice(index, nextIndex)]
+ index = nextIndex + 1
+ }
+}
+
+/**
+ * @param {Config} config
+ * @template {ConfigPath} TPath
+ * @param {TPath} name
+ * @param {import('./config-types').ConfigPathValue} value
+ * @param {unknown} [rawValue]
+ * @param {TelemetrySource} [source]
+ */
+function setAndTrack (config, name, value, rawValue = value, source = 'calculated') {
+ // envs can not be undefined
+ if (value == null) {
+ // TODO: This works as before while ignoring undefined programmatic options is not ideal.
+ if (source !== 'default') {
+ return
+ }
+ } else if (source === 'calculated' || source === 'remote_config') {
+ if (source === 'calculated' && value === get(config, name)) {
+ return
+ }
+ changeTracker[source].add(name)
+ } else {
+ const copy = typeof value === 'object' && value !== null ? rfdc(value) : value
+ changeTracker.baseValuesByPath[name] = { value: copy, source }
+ }
+ set(config, name, value)
+
+ generateTelemetry(rawValue, source, name)
+ if (source === 'default') {
+ trackedConfigOrigins.delete(name)
+ } else {
+ trackedConfigOrigins.set(name, source)
+ }
+}
+
module.exports = getConfig
-class Config {
+// We extend from ConfigBase to make our types work
+class Config extends ConfigBase {
/**
* parsed DD_TAGS, usable as a standalone tag set across products
* @type {Record}
*/
- #parsedDdTags = {}
- #envUnprocessed = {}
- #optsUnprocessed = {}
- #remoteUnprocessed = {}
- #env = {}
- #options = {}
- #remote = {}
- #defaults = {}
- #optionsArg = {}
- #localStableConfig = {}
- #fleetStableConfig = {}
- #calculated = {}
+ #parsedDdTags
+ /**
+ * @type {Record}
+ */
+ get parsedDdTags () {
+ return this.#parsedDdTags
+ }
+
+ /**
+ * @param {TracerOptions} [options={}]
+ */
constructor (options = {}) {
- if (!IS_SERVERLESS) {
- const configEnvSources = getStableConfigSources()
- this.stableConfig = {
- fleetEntries: configEnvSources.fleetStableConfig,
- localEntries: configEnvSources.localStableConfig,
- warnings: configEnvSources.stableConfigWarnings,
- }
- }
+ super()
- options = {
- ...options,
- // TODO(BridgeAR): Remove the experimental prefix once we have a major version.
- // That also applies to index.d.ts
- appsec: options.appsec == null ? options.experimental?.appsec : options.appsec,
- iast: options.iast == null ? options.experimental?.iast : options.iast,
+ const configEnvSources = getStableConfigSources()
+ this.stableConfig = {
+ fleetEntries: configEnvSources.fleetStableConfig ?? {},
+ localEntries: configEnvSources.localStableConfig ?? {},
+ warnings: configEnvSources.stableConfigWarnings,
}
// Configure the logger first so it can be used to warn about other configs
- const logConfig = log.getConfig()
- this.debug = log.isEnabled(
- this.stableConfig?.fleetEntries?.DD_TRACE_DEBUG,
- this.stableConfig?.localEntries?.DD_TRACE_DEBUG
- )
- this.logger = options.logger ?? logConfig.logger
- this.logLevel = log.getLogLevel(
- options.logLevel,
- this.stableConfig?.fleetEntries?.DD_TRACE_LOG_LEVEL,
- this.stableConfig?.localEntries?.DD_TRACE_LOG_LEVEL
- )
- log.use(this.logger)
- log.toggle(this.debug, this.logLevel)
+ // TODO: Implement auto buffering of inside of log module before first
+ // configure call. That way the logger is always available and the
+ // application doesn't need to configure it first and the configuration
+ // happens inside of config instead of inside of log module. If the logger
+ // is not deactivated, the buffered logs would be discarded. That way stable
+ // config warnings can also be logged directly and do not need special
+ // handling.
+ this.debug = log.configure(options)
// Process stable config warnings, if any
for (const warning of this.stableConfig?.warnings ?? []) {
log.warn(warning)
}
- checkIfBothOtelAndDdEnvVarSet()
-
- if (typeof options.appsec === 'boolean') {
- options.appsec = {
- enabled: options.appsec,
- }
- }
-
- if (typeof options.runtimeMetrics === 'boolean') {
- options.runtimeMetrics = {
- enabled: options.runtimeMetrics,
- }
- }
-
- this.#defaults = defaults
this.#applyDefaults()
- this.#applyStableConfig(this.stableConfig?.localEntries ?? {}, this.#localStableConfig)
- this.#applyEnvironment()
- this.#applyStableConfig(this.stableConfig?.fleetEntries ?? {}, this.#fleetStableConfig)
- this.#applyOptions(options)
+ // TODO: Update origin documentation to list all valid sources. Add local_stable_config and fleet_stable_config.
+ this.#applyEnvs(getEnvironmentVariables(this.stableConfig.localEntries, true), 'local_stable_config')
+ this.#applyEnvs(getEnvironmentVariables(undefined, true), 'env_var')
+ this.#applyEnvs(getEnvironmentVariables(this.stableConfig.fleetEntries, true), 'fleet_stable_config')
+
+ // Experimental options are applied first, so they can be overridden by non-experimental options.
+ // TODO: When using programmatic options, check if there is a higher
+ // priority name in the same options object. Use the highest priority name.
+ const { experimental, ...rest } = options
+ if (experimental) {
+ // @ts-expect-error - Difficult to type this correctly.
+ this.#applyOptions(experimental, 'code', 'experimental')
+ }
+ this.#applyOptions(rest, 'code')
this.#applyCalculated()
- this.#merge()
- tagger.add(this.tags, {
- service: this.service,
- env: this.env,
- version: this.version,
- 'runtime-id': RUNTIME_ID,
- })
+ warnWrongOtelSettings()
+
+ if (this.gitMetadataEnabled) {
+ this.#loadGitMetadata()
+ }
- this.rootSessionId = ROOT_SESSION_ID
+ parseErrors.clear()
+ }
- if (this.isCiVisibility) {
- tagger.add(this.tags, {
- [ORIGIN_KEY]: 'ciapp-test',
- })
+ #applyDefaults () {
+ for (const [name, value] of Object.entries(defaults)) {
+ set(this, name, value)
}
+ }
- if (this.gitMetadataEnabled) {
- this.#loadGitMetadata()
+ /**
+ * @param {import('./helper').TracerEnv} envs
+ * @param {'env_var' | 'local_stable_config' | 'fleet_stable_config'} source
+ */
+ #applyEnvs (envs, source) {
+ for (const [name, value] of Object.entries(envs)) {
+ const entry = configurationsTable[name]
+ // TracePropagationStyle is a special case. It is a single option that is used to set both inject and extract.
+ // TODO: Consider what to do with this later
+ if (name === 'DD_TRACE_PROPAGATION_STYLE') {
+ if (
+ getValueFromEnvSources('DD_TRACE_PROPAGATION_STYLE_INJECT') !== undefined ||
+ getValueFromEnvSources('DD_TRACE_PROPAGATION_STYLE_EXTRACT') !== undefined
+ ) {
+ log.warn(
+ // eslint-disable-next-line @stylistic/max-len
+ 'Use either DD_TRACE_PROPAGATION_STYLE or separate DD_TRACE_PROPAGATION_STYLE_INJECT and DD_TRACE_PROPAGATION_STYLE_EXTRACT environment variables'
+ )
+ continue
+ }
+ this.#applyEnvs({ DD_TRACE_PROPAGATION_STYLE_INJECT: value, DD_TRACE_PROPAGATION_STYLE_EXTRACT: value }, source)
+ continue
+ }
+ const parsed = entry.parser(value, name, source)
+ const transformed = parsed !== undefined && entry.transformer ? entry.transformer(parsed, name, source) : parsed
+ const rawValue = transformed !== null && typeof transformed === 'object' ? value : parsed
+ setAndTrack(this, entry.property ?? name, transformed, rawValue, source)
}
}
- get parsedDdTags () {
- return this.#parsedDdTags
+ /**
+ * @param {TracerOptions} options
+ * @param {'code' | 'remote_config'} source
+ * @param {string} [root]
+ */
+ #applyOptions (options, source, root = '') {
+ for (const [name, value] of Object.entries(options)) {
+ const fullName = root ? `${root}.${name}` : name
+ let entry = optionsTable[fullName]
+ if (!entry) {
+ // TODO: Fix this by by changing remote config to use env styles.
+ if (name !== 'tracing' || source !== 'remote_config') {
+ log.warn('Unknown option %s with value %o', fullName, value)
+ continue
+ }
+ // @ts-expect-error - The entry is defined in the configurationsTable.
+ entry = configurationsTable.tracing
+ }
+
+ if (entry.nestedProperties) {
+ let matched = false
+ if (typeof value === 'object' && value !== null) {
+ for (const nestedProperty of entry.nestedProperties) {
+ // WARNING: if the property name might be part of the value we look at, this could conflict!
+ // Defining an option that receives an object as value may not contain a property that is also
+ // potentially a nested property!
+ if (Object.hasOwn(value, nestedProperty)) {
+ this.#applyOptions(value, source, fullName)
+ matched = true
+ break
+ }
+ }
+ }
+ if (matched) {
+ continue
+ }
+ if (entry.option) {
+ entry = entry.option
+ } else {
+ if (fullName === 'tracePropagationStyle') {
+ // TracePropagationStyle is special. It is a single option that is used to set both inject and extract.
+ // @ts-expect-error - Difficult to type this correctly.
+ this.#applyOptions({ inject: value, extract: value }, source, 'tracePropagationStyle')
+ } else {
+ log.warn('Unknown option %s with value %o', fullName, value)
+ }
+ continue
+ }
+ }
+ // TODO: Coerce mismatched types to the expected type, if possible. E.g., strings <> numbers
+ const transformed = value !== undefined && entry.transformer ? entry.transformer(value, fullName, source) : value
+ setAndTrack(this, entry.property, transformed, value, source)
+ }
}
/**
* Set the configuration with remote config settings.
* Applies remote configuration, recalculates derived values, and merges all configuration sources.
*
- * @param {import('./remote_config').RemoteConfigOptions|null} options - Configurations received via Remote
+ * @param {TracerOptions|null} options - Configurations received via Remote
* Config or null to reset all remote configuration
*/
setRemoteConfig (options) {
// Clear all RC-managed fields to ensure previous values don't persist.
// State is instead managed by the `RCClientLibConfigManager` class
- this.#remote = {}
- this.#remoteUnprocessed = {}
+ undo(this, 'remote_config')
// Special case: if options is null, nothing to apply
// This happens when all remote configs are removed
if (options !== null) {
- this.#applyRemoteConfig(options)
+ this.#applyOptions(options, 'remote_config')
}
this.#applyCalculated()
- this.#merge()
}
- // TODO: Remove the `updateOptions` method. We don't want to support updating the config this way
/**
- * Updates the configuration with new programmatic options.
- *
- * @deprecated This method should not be used and will be removed in a future version.
- * @param {object} options - Configuration options to apply (same format as tracer init options)
+ * @param {ConfigPath} name
*/
- updateOptions (options) {
- this.#applyOptions(options)
- this.#applyCalculated()
- this.#merge()
- }
-
getOrigin (name) {
- for (const { container, origin } of this.#getSourcesInOrder()) {
- const value = container[name]
- if (value != null || container === this.#defaults) {
- return origin
- }
- }
- }
-
- #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' },
- ]
- }
-
- #applyStableConfig (config, obj) {
- this.#applyConfigValues(config, obj, {})
+ return trackedConfigOrigins.get(name) ?? 'default'
}
- // Set environment-dependent defaults that can be overridden by users
- #applyDefaults () {
- const defaults = this.#defaults
-
- if (IS_SERVERLESS) {
- setBoolean(defaults, 'crashtracking.enabled', false)
- setString(defaults, 'profiling.enabled', 'false')
- setBoolean(defaults, 'telemetry.enabled', false)
- setBoolean(defaults, 'remoteConfig.enabled', false)
- } else {
- setBoolean(defaults, 'crashtracking.enabled', true)
+ // Handles values calculated from a mixture of options and env vars
+ #applyCalculated () {
+ undo(this, 'calculated')
+
+ if (this.DD_CIVISIBILITY_AGENTLESS_URL ||
+ this.url ||
+ os.type() !== 'Windows_NT' &&
+ !trackedConfigOrigins.has('hostname') &&
+ !trackedConfigOrigins.has('port') &&
+ !this.DD_CIVISIBILITY_AGENTLESS_ENABLED &&
+ fs.existsSync('/var/run/datadog/apm.socket')) {
+ setAndTrack(
+ this,
+ 'url',
+ new URL(this.DD_CIVISIBILITY_AGENTLESS_URL || this.url || 'unix:///var/run/datadog/apm.socket')
+ )
}
- if (getEnv('JEST_WORKER_ID')) {
- setBoolean(defaults, 'telemetry.enabled', false)
+ if (this.isCiVisibility) {
+ setAndTrack(this, 'isServiceUserProvided', trackedConfigOrigins.has('service'))
+ this.tags[ORIGIN_KEY] = 'ciapp-test'
}
- }
-
- #applyEnvironment () {
- this.#applyConfigValues(getEnvironmentVariables(), this.#env, this.#envUnprocessed)
- }
+ // Compute OTLP logs and metrics URLs to send payloads to the active Datadog Agent
+ const agentHostname = this.hostname || /** @type {URL} */ (this.url).hostname
- #applyConfigValues (source, target, unprocessedTarget) {
- const {
- AWS_LAMBDA_FUNCTION_NAME,
- DD_AGENT_HOST,
- DD_AI_GUARD_ENABLED,
- DD_AI_GUARD_ENDPOINT,
- DD_AI_GUARD_MAX_CONTENT_SIZE,
- DD_AI_GUARD_MAX_MESSAGES_LENGTH,
- DD_AI_GUARD_TIMEOUT,
- DD_API_KEY,
- DD_API_SECURITY_ENABLED,
- DD_API_SECURITY_SAMPLE_DELAY,
- DD_API_SECURITY_ENDPOINT_COLLECTION_ENABLED,
- DD_API_SECURITY_ENDPOINT_COLLECTION_MESSAGE_LIMIT,
- DD_API_SECURITY_DOWNSTREAM_BODY_ANALYSIS_SAMPLE_RATE,
- DD_API_SECURITY_MAX_DOWNSTREAM_REQUEST_BODY_ANALYSIS,
- DD_APM_TRACING_ENABLED,
- DD_APP_KEY,
- DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE,
- DD_APPSEC_COLLECT_ALL_HEADERS,
- DD_APPSEC_ENABLED,
- DD_APPSEC_GRAPHQL_BLOCKED_TEMPLATE_JSON,
- DD_APPSEC_HEADER_COLLECTION_REDACTION_ENABLED,
- DD_APPSEC_HTTP_BLOCKED_TEMPLATE_HTML,
- DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON,
- DD_APPSEC_MAX_COLLECTED_HEADERS,
- DD_APPSEC_MAX_STACK_TRACES,
- DD_APPSEC_MAX_STACK_TRACE_DEPTH,
- DD_APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP,
- DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP,
- DD_APPSEC_RULES,
- DD_APPSEC_SCA_ENABLED,
- DD_APPSEC_STACK_TRACE_ENABLED,
- DD_APPSEC_RASP_ENABLED,
- DD_APPSEC_RASP_COLLECT_REQUEST_BODY,
- DD_APPSEC_TRACE_RATE_LIMIT,
- DD_APPSEC_WAF_TIMEOUT,
- DD_CRASHTRACKING_ENABLED,
- DD_CODE_ORIGIN_FOR_SPANS_ENABLED,
- DD_CODE_ORIGIN_FOR_SPANS_EXPERIMENTAL_EXIT_SPANS_ENABLED,
- DD_DATA_STREAMS_ENABLED,
- DD_DBM_PROPAGATION_MODE,
- DD_DBM_INJECT_SQL_BASEHASH,
- DD_DOGSTATSD_HOST,
- DD_DOGSTATSD_PORT,
- DD_DYNAMIC_INSTRUMENTATION_CAPTURE_TIMEOUT_MS,
- DD_DYNAMIC_INSTRUMENTATION_ENABLED,
- DD_DYNAMIC_INSTRUMENTATION_PROBE_FILE,
- DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS,
- DD_DYNAMIC_INSTRUMENTATION_REDACTION_EXCLUDED_IDENTIFIERS,
- DD_DYNAMIC_INSTRUMENTATION_UPLOAD_INTERVAL_SECONDS,
- DD_ENV,
- DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED,
- DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED,
- DD_PROFILING_ENABLED,
- DD_GRPC_CLIENT_ERROR_STATUSES,
- DD_GRPC_SERVER_ERROR_STATUSES,
- DD_HEAP_SNAPSHOT_COUNT,
- DD_HEAP_SNAPSHOT_DESTINATION,
- DD_HEAP_SNAPSHOT_INTERVAL,
- DD_IAST_DB_ROWS_TO_TAINT,
- DD_IAST_DEDUPLICATION_ENABLED,
- DD_IAST_ENABLED,
- DD_IAST_MAX_CONCURRENT_REQUESTS,
- DD_IAST_MAX_CONTEXT_OPERATIONS,
- DD_IAST_REDACTION_ENABLED,
- DD_IAST_REDACTION_NAME_PATTERN,
- DD_IAST_REDACTION_VALUE_PATTERN,
- DD_IAST_REQUEST_SAMPLING,
- DD_IAST_SECURITY_CONTROLS_CONFIGURATION,
- DD_IAST_TELEMETRY_VERBOSITY,
- DD_IAST_STACK_TRACE_ENABLED,
- DD_INJECTION_ENABLED,
- DD_INJECT_FORCE,
- DD_ENABLE_NX_SERVICE_NAME,
- DD_INSTRUMENTATION_TELEMETRY_ENABLED,
- DD_INSTRUMENTATION_CONFIG_ID,
- DD_LOGS_INJECTION,
- DD_LOGS_OTEL_ENABLED,
- DD_METRICS_OTEL_ENABLED,
- DD_LANGCHAIN_SPAN_CHAR_LIMIT,
- DD_LANGCHAIN_SPAN_PROMPT_COMPLETION_SAMPLE_RATE,
- DD_LLMOBS_AGENTLESS_ENABLED,
- DD_LLMOBS_ENABLED,
- DD_LLMOBS_ML_APP,
- DD_OPENAI_LOGS_ENABLED,
- DD_OPENAI_SPAN_CHAR_LIMIT,
- DD_PROFILING_EXPORTERS,
- DD_PROFILING_SOURCE_MAP,
- DD_INTERNAL_PROFILING_LONG_LIVED_THRESHOLD,
- DD_INSTRUMENTATION_INSTALL_ID,
- DD_INSTRUMENTATION_INSTALL_TIME,
- DD_INSTRUMENTATION_INSTALL_TYPE,
- DD_REMOTE_CONFIGURATION_ENABLED,
- DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS,
- DD_RUNTIME_METRICS_ENABLED,
- DD_RUNTIME_METRICS_EVENT_LOOP_ENABLED,
- DD_RUNTIME_METRICS_GC_ENABLED,
- DD_SERVICE,
- DD_SERVICE_MAPPING,
- DD_SITE,
- DD_SPAN_SAMPLING_RULES,
- DD_SPAN_SAMPLING_RULES_FILE,
- DD_TAGS,
- DD_TELEMETRY_DEBUG,
- DD_TELEMETRY_DEPENDENCY_COLLECTION_ENABLED,
- DD_TELEMETRY_HEARTBEAT_INTERVAL,
- DD_TELEMETRY_LOG_COLLECTION_ENABLED,
- DD_TELEMETRY_METRICS_ENABLED,
- DD_TEST_TIA_KEEP_COV_CONFIG,
- DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED,
- DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED,
- DD_TRACE_AGENT_PORT,
- DD_TRACE_AGENT_PROTOCOL_VERSION,
- DD_TRACE_AWS_ADD_SPAN_POINTERS,
- DD_TRACE_BAGGAGE_MAX_BYTES,
- DD_TRACE_BAGGAGE_MAX_ITEMS,
- DD_TRACE_BAGGAGE_TAG_KEYS,
- DD_TRACE_CLIENT_IP_ENABLED,
- DD_TRACE_CLIENT_IP_HEADER,
- DD_TRACE_CLOUD_REQUEST_PAYLOAD_TAGGING,
- DD_TRACE_CLOUD_RESPONSE_PAYLOAD_TAGGING,
- DD_TRACE_CLOUD_PAYLOAD_TAGGING_MAX_DEPTH,
- DD_TRACE_DYNAMODB_TABLE_PRIMARY_KEYS,
- DD_TRACE_ENABLED,
- DD_TRACE_EXPERIMENTAL_EXPORTER,
- DD_TRACE_EXPERIMENTAL_GET_RUM_DATA_ENABLED,
- DD_RUNTIME_METRICS_RUNTIME_ID_ENABLED,
- DD_TRACE_GIT_METADATA_ENABLED,
- DD_TRACE_GRAPHQL_ERROR_EXTENSIONS,
- DD_TRACE_HEADER_TAGS,
- DD_TRACE_LEGACY_BAGGAGE_ENABLED,
- DD_TRACE_MEMCACHED_COMMAND_ENABLED,
- 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,
- DD_TRACE_PROPAGATION_STYLE,
- DD_TRACE_PROPAGATION_STYLE_INJECT,
- DD_TRACE_PROPAGATION_STYLE_EXTRACT,
- DD_TRACE_RATE_LIMIT,
- DD_TRACE_REMOVE_INTEGRATION_SERVICE_NAMES_ENABLED,
- DD_TRACE_REPORT_HOSTNAME,
- DD_TRACE_RESOURCE_RENAMING_ENABLED,
- DD_TRACE_SAMPLE_RATE,
- DD_TRACE_SAMPLING_RULES,
- DD_TRACE_SCOPE,
- DD_TRACE_SPAN_ATTRIBUTE_SCHEMA,
- DD_TRACE_SPAN_LEAK_DEBUG,
- DD_TRACE_STARTUP_LOGS,
- DD_TRACE_TAGS,
- DD_TRACE_WEBSOCKET_MESSAGES_ENABLED,
- DD_TRACE_WEBSOCKET_MESSAGES_INHERIT_SAMPLING,
- DD_TRACE_WEBSOCKET_MESSAGES_SEPARATE_TRACES,
- DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH,
- DD_TRACING_ENABLED,
- DD_VERSION,
- DD_VERTEXAI_SPAN_PROMPT_COMPLETION_SAMPLE_RATE,
- DD_VERTEXAI_SPAN_CHAR_LIMIT,
- DD_TRACE_INFERRED_PROXY_SERVICES_ENABLED,
- DD_TRACE_NATIVE_SPAN_EVENTS,
- OTEL_METRICS_EXPORTER,
- OTEL_PROPAGATORS,
- OTEL_RESOURCE_ATTRIBUTES,
- OTEL_SERVICE_NAME,
- OTEL_TRACES_SAMPLER,
- OTEL_TRACES_SAMPLER_ARG,
- DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED,
- DD_EXPERIMENTAL_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS,
- OTEL_EXPORTER_OTLP_LOGS_ENDPOINT,
- OTEL_EXPORTER_OTLP_LOGS_HEADERS,
- OTEL_EXPORTER_OTLP_LOGS_PROTOCOL,
- OTEL_EXPORTER_OTLP_LOGS_TIMEOUT,
- OTEL_EXPORTER_OTLP_METRICS_ENDPOINT,
- OTEL_EXPORTER_OTLP_METRICS_HEADERS,
- OTEL_EXPORTER_OTLP_METRICS_PROTOCOL,
- OTEL_EXPORTER_OTLP_METRICS_TIMEOUT,
- OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE,
- OTEL_METRIC_EXPORT_TIMEOUT,
- OTEL_EXPORTER_OTLP_PROTOCOL,
- OTEL_EXPORTER_OTLP_ENDPOINT,
- OTEL_EXPORTER_OTLP_HEADERS,
- OTEL_EXPORTER_OTLP_TIMEOUT,
- OTEL_BSP_SCHEDULE_DELAY,
- OTEL_BSP_MAX_EXPORT_BATCH_SIZE,
- OTEL_BSP_MAX_QUEUE_SIZE,
- OTEL_METRIC_EXPORT_INTERVAL,
- NX_TASK_TARGET_PROJECT,
- } = source
-
- const tags = {}
-
- tagger.add(tags, parseSpaceSeparatedTags(handleOtel(OTEL_RESOURCE_ATTRIBUTES)))
- tagger.add(tags, parseSpaceSeparatedTags(DD_TAGS))
- tagger.add(tags, DD_TRACE_TAGS)
-
- Object.assign(this.#parsedDdTags, tags)
-
- setString(target, 'apiKey', DD_API_KEY)
- setBoolean(target, 'otelLogsEnabled', 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
- setString(target, 'otelUrl', OTEL_EXPORTER_OTLP_ENDPOINT)
+ if (!trackedConfigOrigins.has('dogstatsd.hostname')) {
+ setAndTrack(this, 'dogstatsd.hostname', agentHostname)
}
- if (OTEL_EXPORTER_OTLP_ENDPOINT || OTEL_EXPORTER_OTLP_LOGS_ENDPOINT) {
- setString(target, 'otelLogsUrl', OTEL_EXPORTER_OTLP_LOGS_ENDPOINT || target.otelUrl)
+ // Disable log injection when OTEL logs are enabled
+ // OTEL logs and DD log injection are mutually exclusive
+ if (this.otelLogsEnabled) {
+ setAndTrack(this, 'logInjection', false)
}
- setString(target, 'otelHeaders', OTEL_EXPORTER_OTLP_HEADERS)
- setString(target, 'otelLogsHeaders', OTEL_EXPORTER_OTLP_LOGS_HEADERS || target.otelHeaders)
- setString(target, 'otelProtocol', OTEL_EXPORTER_OTLP_PROTOCOL)
- setString(target, 'otelLogsProtocol', OTEL_EXPORTER_OTLP_LOGS_PROTOCOL || target.otelProtocol)
- const otelTimeout = nonNegInt(OTEL_EXPORTER_OTLP_TIMEOUT, 'OTEL_EXPORTER_OTLP_TIMEOUT')
- if (otelTimeout !== undefined) {
- target.otelTimeout = otelTimeout
+ if (this.otelMetricsEnabled &&
+ trackedConfigOrigins.has('OTEL_METRICS_EXPORTER') &&
+ this.OTEL_METRICS_EXPORTER === 'none') {
+ setAndTrack(this, 'otelMetricsEnabled', false)
}
- const otelLogsTimeout = nonNegInt(OTEL_EXPORTER_OTLP_LOGS_TIMEOUT, 'OTEL_EXPORTER_OTLP_LOGS_TIMEOUT')
- target.otelLogsTimeout = otelLogsTimeout === undefined ? target.otelTimeout : otelLogsTimeout
- const otelBatchTimeout = nonNegInt(OTEL_BSP_SCHEDULE_DELAY, 'OTEL_BSP_SCHEDULE_DELAY', false)
- if (otelBatchTimeout !== undefined) {
- target.otelBatchTimeout = otelBatchTimeout
+
+ if (this.telemetry.heartbeatInterval) {
+ setAndTrack(this, 'telemetry.heartbeatInterval', Math.floor(this.telemetry.heartbeatInterval * 1000))
}
- target.otelMaxExportBatchSize = nonNegInt(OTEL_BSP_MAX_EXPORT_BATCH_SIZE, 'OTEL_BSP_MAX_EXPORT_BATCH_SIZE', false)
- target.otelMaxQueueSize = nonNegInt(OTEL_BSP_MAX_QUEUE_SIZE, 'OTEL_BSP_MAX_QUEUE_SIZE', false)
-
- const otelMetricsExporterEnabled = OTEL_METRICS_EXPORTER?.toLowerCase() !== 'none'
- setBoolean(
- target,
- 'otelMetricsEnabled',
- DD_METRICS_OTEL_ENABLED && isTrue(DD_METRICS_OTEL_ENABLED) && otelMetricsExporterEnabled
- )
- // Set OpenTelemetry metrics configuration with specific _METRICS_ vars
- // taking precedence over generic _EXPORTERS_ vars
- if (OTEL_EXPORTER_OTLP_ENDPOINT || OTEL_EXPORTER_OTLP_METRICS_ENDPOINT) {
- setString(target, 'otelMetricsUrl', OTEL_EXPORTER_OTLP_METRICS_ENDPOINT || target.otelUrl)
+
+ // Enable resourceRenamingEnabled when appsec is enabled and only
+ // if DD_TRACE_RESOURCE_RENAMING_ENABLED is not explicitly set
+ if (!trackedConfigOrigins.has('resourceRenamingEnabled')) {
+ setAndTrack(this, 'resourceRenamingEnabled', this.appsec.enabled ?? false)
}
- setString(target, 'otelMetricsHeaders', OTEL_EXPORTER_OTLP_METRICS_HEADERS || target.otelHeaders)
- setString(target, 'otelMetricsProtocol', OTEL_EXPORTER_OTLP_METRICS_PROTOCOL || target.otelProtocol)
- const otelMetricsTimeout = nonNegInt(OTEL_EXPORTER_OTLP_METRICS_TIMEOUT, 'OTEL_EXPORTER_OTLP_METRICS_TIMEOUT')
- target.otelMetricsTimeout = otelMetricsTimeout === undefined ? target.otelTimeout : otelMetricsTimeout
- target.otelMetricsExportTimeout = nonNegInt(OTEL_METRIC_EXPORT_TIMEOUT, 'OTEL_METRIC_EXPORT_TIMEOUT')
- target.otelMetricsExportInterval = nonNegInt(OTEL_METRIC_EXPORT_INTERVAL, 'OTEL_METRIC_EXPORT_INTERVAL', false)
-
- // Parse temporality preference (default to DELTA for Datadog)
- if (OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE) {
- const temporalityPref = OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE.toUpperCase()
- if (['DELTA', 'CUMULATIVE', 'LOWMEMORY'].includes(temporalityPref)) {
- setString(target, 'otelMetricsTemporalityPreference', temporalityPref)
- }
+
+ if (!trackedConfigOrigins.has('spanComputePeerService') && this.spanAttributeSchema !== 'v0') {
+ setAndTrack(this, 'spanComputePeerService', true)
+ }
+
+ if (!this.apmTracingEnabled) {
+ setAndTrack(this, 'stats.enabled', false)
+ } else if (!trackedConfigOrigins.has('stats.enabled')) {
+ setAndTrack(this, 'stats.enabled', getIsGCPFunction() || getIsAzureFunction())
}
- setBoolean(
- target,
- 'apmTracingEnabled',
- DD_APM_TRACING_ENABLED ??
- (DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED && isFalse(DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED))
- )
- setBoolean(target, 'propagateProcessTags.enabled', DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED)
- setString(target, 'appKey', DD_APP_KEY)
- setBoolean(target, 'appsec.apiSecurity.enabled', DD_API_SECURITY_ENABLED && isTrue(DD_API_SECURITY_ENABLED))
- target['appsec.apiSecurity.sampleDelay'] = maybeFloat(DD_API_SECURITY_SAMPLE_DELAY)
- setBoolean(target, 'appsec.apiSecurity.endpointCollectionEnabled',
- DD_API_SECURITY_ENDPOINT_COLLECTION_ENABLED)
- target['appsec.apiSecurity.endpointCollectionMessageLimit'] =
- maybeInt(DD_API_SECURITY_ENDPOINT_COLLECTION_MESSAGE_LIMIT)
- target['appsec.blockedTemplateGraphql'] = maybeFile(DD_APPSEC_GRAPHQL_BLOCKED_TEMPLATE_JSON)
- target['appsec.blockedTemplateHtml'] = maybeFile(DD_APPSEC_HTTP_BLOCKED_TEMPLATE_HTML)
- unprocessedTarget['appsec.blockedTemplateHtml'] = DD_APPSEC_HTTP_BLOCKED_TEMPLATE_HTML
- target['appsec.blockedTemplateJson'] = maybeFile(DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON)
- unprocessedTarget['appsec.blockedTemplateJson'] = DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON
- setBoolean(target, 'appsec.enabled', DD_APPSEC_ENABLED)
- setString(target, 'appsec.eventTracking.mode', DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE)
- // TODO appsec.extendedHeadersCollection are deprecated, to delete in a major
- setBoolean(target, 'appsec.extendedHeadersCollection.enabled', DD_APPSEC_COLLECT_ALL_HEADERS)
- setBoolean(
- target,
- 'appsec.extendedHeadersCollection.redaction',
- DD_APPSEC_HEADER_COLLECTION_REDACTION_ENABLED
- )
- target['appsec.extendedHeadersCollection.maxHeaders'] = maybeInt(DD_APPSEC_MAX_COLLECTED_HEADERS)
- unprocessedTarget['appsec.extendedHeadersCollection.maxHeaders'] = DD_APPSEC_MAX_COLLECTED_HEADERS
- setString(target, 'appsec.obfuscatorKeyRegex', DD_APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP)
- setString(target, 'appsec.obfuscatorValueRegex', DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP)
- setBoolean(target, 'appsec.rasp.enabled', DD_APPSEC_RASP_ENABLED)
- // TODO Deprecated, to delete in a major
- setBoolean(target, 'appsec.rasp.bodyCollection', DD_APPSEC_RASP_COLLECT_REQUEST_BODY)
- target['appsec.rateLimit'] = maybeInt(DD_APPSEC_TRACE_RATE_LIMIT)
- unprocessedTarget['appsec.rateLimit'] = DD_APPSEC_TRACE_RATE_LIMIT
- setString(target, 'appsec.rules', DD_APPSEC_RULES)
- // DD_APPSEC_SCA_ENABLED is never used locally, but only sent to the backend
- setBoolean(target, 'appsec.sca.enabled', DD_APPSEC_SCA_ENABLED)
- setBoolean(target, 'appsec.stackTrace.enabled', DD_APPSEC_STACK_TRACE_ENABLED)
- target['appsec.stackTrace.maxDepth'] = maybeInt(DD_APPSEC_MAX_STACK_TRACE_DEPTH)
- unprocessedTarget['appsec.stackTrace.maxDepth'] = DD_APPSEC_MAX_STACK_TRACE_DEPTH
- target['appsec.stackTrace.maxStackTraces'] = maybeInt(DD_APPSEC_MAX_STACK_TRACES)
- unprocessedTarget['appsec.stackTrace.maxStackTraces'] = DD_APPSEC_MAX_STACK_TRACES
- target['appsec.wafTimeout'] = maybeInt(DD_APPSEC_WAF_TIMEOUT)
- unprocessedTarget['appsec.wafTimeout'] = DD_APPSEC_WAF_TIMEOUT
- target['appsec.apiSecurity.downstreamBodyAnalysisSampleRate'] =
- maybeFloat(DD_API_SECURITY_DOWNSTREAM_BODY_ANALYSIS_SAMPLE_RATE)
- target['appsec.apiSecurity.maxDownstreamRequestBodyAnalysis'] =
- maybeInt(DD_API_SECURITY_MAX_DOWNSTREAM_REQUEST_BODY_ANALYSIS)
- target.baggageMaxBytes = DD_TRACE_BAGGAGE_MAX_BYTES
- target.baggageMaxItems = DD_TRACE_BAGGAGE_MAX_ITEMS
- setArray(target, 'baggageTagKeys', DD_TRACE_BAGGAGE_TAG_KEYS)
- setBoolean(target, 'clientIpEnabled', DD_TRACE_CLIENT_IP_ENABLED)
- setString(target, 'clientIpHeader', DD_TRACE_CLIENT_IP_HEADER?.toLowerCase())
- if (DD_TRACE_CLOUD_REQUEST_PAYLOAD_TAGGING || DD_TRACE_CLOUD_RESPONSE_PAYLOAD_TAGGING) {
- if (DD_TRACE_CLOUD_REQUEST_PAYLOAD_TAGGING) {
- setBoolean(target, 'cloudPayloadTagging.requestsEnabled', true)
+
+ // TODO: Remove the experimental env vars as a major or deprecate the option?
+ if (this.experimental?.b3) {
+ if (!this.tracePropagationStyle.inject.includes('b3')) {
+ this.tracePropagationStyle.inject.push('b3')
}
- if (DD_TRACE_CLOUD_RESPONSE_PAYLOAD_TAGGING) {
- setBoolean(target, 'cloudPayloadTagging.responsesEnabled', true)
+ if (!this.tracePropagationStyle.extract.includes('b3')) {
+ this.tracePropagationStyle.extract.push('b3')
}
- target['cloudPayloadTagging.rules'] = appendRules(
- splitJSONPathRules(DD_TRACE_CLOUD_REQUEST_PAYLOAD_TAGGING),
- splitJSONPathRules(DD_TRACE_CLOUD_RESPONSE_PAYLOAD_TAGGING)
- )
- }
- if (DD_TRACE_CLOUD_PAYLOAD_TAGGING_MAX_DEPTH) {
- target['cloudPayloadTagging.maxDepth'] = maybeInt(DD_TRACE_CLOUD_PAYLOAD_TAGGING_MAX_DEPTH)
- }
- setBoolean(target, 'crashtracking.enabled', DD_CRASHTRACKING_ENABLED)
- setBoolean(target, 'codeOriginForSpans.enabled', DD_CODE_ORIGIN_FOR_SPANS_ENABLED)
- setBoolean(
- target,
- 'codeOriginForSpans.experimental.exit_spans.enabled',
- DD_CODE_ORIGIN_FOR_SPANS_EXPERIMENTAL_EXIT_SPANS_ENABLED
- )
- setString(target, 'dbmPropagationMode', DD_DBM_PROPAGATION_MODE)
- setBoolean(target, 'dbm.injectSqlBaseHash', DD_DBM_INJECT_SQL_BASEHASH)
- setString(target, 'dogstatsd.hostname', DD_DOGSTATSD_HOST)
- setString(target, 'dogstatsd.port', DD_DOGSTATSD_PORT)
- setBoolean(target, 'dsmEnabled', DD_DATA_STREAMS_ENABLED)
- target['dynamicInstrumentation.captureTimeoutMs'] = maybeInt(DD_DYNAMIC_INSTRUMENTATION_CAPTURE_TIMEOUT_MS)
- unprocessedTarget['dynamicInstrumentation.captureTimeoutMs'] = DD_DYNAMIC_INSTRUMENTATION_CAPTURE_TIMEOUT_MS
- setBoolean(target, 'dynamicInstrumentation.enabled', DD_DYNAMIC_INSTRUMENTATION_ENABLED)
- setString(target, 'dynamicInstrumentation.probeFile', DD_DYNAMIC_INSTRUMENTATION_PROBE_FILE)
- setArray(target, 'dynamicInstrumentation.redactedIdentifiers',
- DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS)
- setArray(
- target,
- 'dynamicInstrumentation.redactionExcludedIdentifiers',
- DD_DYNAMIC_INSTRUMENTATION_REDACTION_EXCLUDED_IDENTIFIERS
- )
- target['dynamicInstrumentation.uploadIntervalSeconds'] =
- maybeFloat(DD_DYNAMIC_INSTRUMENTATION_UPLOAD_INTERVAL_SECONDS)
- unprocessedTarget['dynamicInstrumentation.uploadInterval'] = DD_DYNAMIC_INSTRUMENTATION_UPLOAD_INTERVAL_SECONDS
- setString(target, 'env', DD_ENV || tags.env)
- setBoolean(
- target,
- 'experimental.flaggingProvider.enabled',
- DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED
- )
- if (DD_EXPERIMENTAL_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS != null) {
- target['experimental.flaggingProvider.initializationTimeoutMs'] =
- maybeInt(DD_EXPERIMENTAL_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS)
+ if (!this.tracePropagationStyle.inject.includes('b3 single header')) {
+ this.tracePropagationStyle.inject.push('b3 single header')
+ }
+ if (!this.tracePropagationStyle.extract.includes('b3 single header')) {
+ this.tracePropagationStyle.extract.push('b3 single header')
+ }
+ setAndTrack(this, 'tracePropagationStyle.inject', this.tracePropagationStyle.inject)
+ setAndTrack(this, 'tracePropagationStyle.extract', this.tracePropagationStyle.extract)
}
- setBoolean(target, 'traceEnabled', DD_TRACE_ENABLED)
- setBoolean(target, 'experimental.aiguard.enabled', DD_AI_GUARD_ENABLED)
- setString(target, 'experimental.aiguard.endpoint', DD_AI_GUARD_ENDPOINT)
- target['experimental.aiguard.maxContentSize'] = maybeInt(DD_AI_GUARD_MAX_CONTENT_SIZE)
- unprocessedTarget['experimental.aiguard.maxContentSize'] = DD_AI_GUARD_MAX_CONTENT_SIZE
- target['experimental.aiguard.maxMessagesLength'] = maybeInt(DD_AI_GUARD_MAX_MESSAGES_LENGTH)
- unprocessedTarget['experimental.aiguard.maxMessagesLength'] = DD_AI_GUARD_MAX_MESSAGES_LENGTH
- target['experimental.aiguard.timeout'] = maybeInt(DD_AI_GUARD_TIMEOUT)
- unprocessedTarget['experimental.aiguard.timeout'] = DD_AI_GUARD_TIMEOUT
- setBoolean(target, 'experimental.enableGetRumData', DD_TRACE_EXPERIMENTAL_GET_RUM_DATA_ENABLED)
- setString(target, 'experimental.exporter', DD_TRACE_EXPERIMENTAL_EXPORTER)
- if (AWS_LAMBDA_FUNCTION_NAME) {
- target.flushInterval = 0
- } else if (DD_TRACE_FLUSH_INTERVAL) {
- target.flushInterval = maybeInt(DD_TRACE_FLUSH_INTERVAL)
+
+ if (getEnvironmentVariable('AWS_LAMBDA_FUNCTION_NAME')) {
+ setAndTrack(this, 'flushInterval', 0)
}
- target.flushMinSpans = maybeInt(DD_TRACE_PARTIAL_FLUSH_MIN_SPANS)
- unprocessedTarget.flushMinSpans = DD_TRACE_PARTIAL_FLUSH_MIN_SPANS
- setBoolean(target, 'gitMetadataEnabled', DD_TRACE_GIT_METADATA_ENABLED)
- setIntegerRangeSet(target, 'grpc.client.error.statuses', DD_GRPC_CLIENT_ERROR_STATUSES)
- setIntegerRangeSet(target, 'grpc.server.error.statuses', DD_GRPC_SERVER_ERROR_STATUSES)
- setArray(target, 'headerTags', DD_TRACE_HEADER_TAGS)
- target['heapSnapshot.count'] = maybeInt(DD_HEAP_SNAPSHOT_COUNT)
- setString(target, 'heapSnapshot.destination', DD_HEAP_SNAPSHOT_DESTINATION)
- target['heapSnapshot.interval'] = maybeInt(DD_HEAP_SNAPSHOT_INTERVAL)
- setString(target, 'hostname', DD_AGENT_HOST)
- target['iast.dbRowsToTaint'] = maybeInt(DD_IAST_DB_ROWS_TO_TAINT)
- setBoolean(target, 'iast.deduplicationEnabled', DD_IAST_DEDUPLICATION_ENABLED)
- setBoolean(target, 'iast.enabled', DD_IAST_ENABLED)
- target['iast.maxConcurrentRequests'] = maybeInt(DD_IAST_MAX_CONCURRENT_REQUESTS)
- unprocessedTarget['iast.maxConcurrentRequests'] = DD_IAST_MAX_CONCURRENT_REQUESTS
- target['iast.maxContextOperations'] = maybeInt(DD_IAST_MAX_CONTEXT_OPERATIONS)
- unprocessedTarget['iast.maxContextOperations'] = DD_IAST_MAX_CONTEXT_OPERATIONS
- setBoolean(target, 'iast.redactionEnabled', DD_IAST_REDACTION_ENABLED && !isFalse(DD_IAST_REDACTION_ENABLED))
- setString(target, 'iast.redactionNamePattern', DD_IAST_REDACTION_NAME_PATTERN)
- setString(target, 'iast.redactionValuePattern', DD_IAST_REDACTION_VALUE_PATTERN)
- const iastRequestSampling = maybeInt(DD_IAST_REQUEST_SAMPLING)
- if (iastRequestSampling !== undefined && iastRequestSampling > -1 && iastRequestSampling < 101) {
- target['iast.requestSampling'] = iastRequestSampling
+
+ if (!trackedConfigOrigins.has('apmTracingEnabled') &&
+ trackedConfigOrigins.has('experimental.appsec.standalone.enabled')) {
+ setAndTrack(this, 'apmTracingEnabled', !this.experimental.appsec.standalone.enabled)
}
- unprocessedTarget['iast.requestSampling'] = DD_IAST_REQUEST_SAMPLING
- setString(target, 'iast.securityControlsConfiguration', DD_IAST_SECURITY_CONTROLS_CONFIGURATION)
- setString(target, 'iast.telemetryVerbosity', DD_IAST_TELEMETRY_VERBOSITY)
- setBoolean(target, 'iast.stackTrace.enabled', DD_IAST_STACK_TRACE_ENABLED)
- setString(target, 'installSignature.id', DD_INSTRUMENTATION_INSTALL_ID)
- setString(target, 'installSignature.time', DD_INSTRUMENTATION_INSTALL_TIME)
- setString(target, 'installSignature.type', DD_INSTRUMENTATION_INSTALL_TYPE)
- // TODO: Why is DD_INJECTION_ENABLED a comma separated list?
- setArray(target, 'injectionEnabled', DD_INJECTION_ENABLED)
- if (DD_INJECTION_ENABLED !== undefined) {
- setString(target, 'instrumentationSource', DD_INJECTION_ENABLED ? 'ssi' : 'manual')
+
+ if (this.cloudPayloadTagging?.request || this.cloudPayloadTagging?.response) {
+ setAndTrack(this, 'cloudPayloadTagging.rules', appendRules(
+ this.cloudPayloadTagging.request,
+ this.cloudPayloadTagging.response
+ ))
}
- setBoolean(target, 'injectForce', DD_INJECT_FORCE)
- setBoolean(target, 'isAzureFunction', getIsAzureFunction())
- setBoolean(target, 'isGCPFunction', getIsGCPFunction())
- setBoolean(target, 'gcpPubSubPushSubscriptionEnabled', enableGCPPubSubPushSubscription())
- target['langchain.spanCharLimit'] = maybeInt(DD_LANGCHAIN_SPAN_CHAR_LIMIT)
- target['langchain.spanPromptCompletionSampleRate'] = maybeFloat(DD_LANGCHAIN_SPAN_PROMPT_COMPLETION_SAMPLE_RATE)
- setBoolean(target, 'legacyBaggageEnabled', DD_TRACE_LEGACY_BAGGAGE_ENABLED)
- setBoolean(target, 'llmobs.agentlessEnabled', DD_LLMOBS_AGENTLESS_ENABLED)
- setBoolean(target, 'llmobs.enabled', DD_LLMOBS_ENABLED)
- setString(target, 'llmobs.mlApp', DD_LLMOBS_ML_APP)
- setBoolean(target, 'logInjection', DD_LOGS_INJECTION)
- // Requires an accompanying DD_APM_OBFUSCATION_MEMCACHED_KEEP_COMMAND=true in the agent
- setBoolean(target, 'memcachedCommandEnabled', DD_TRACE_MEMCACHED_COMMAND_ENABLED)
- setBoolean(target, 'middlewareTracingEnabled', DD_TRACE_MIDDLEWARE_TRACING_ENABLED)
- setBoolean(target, 'openAiLogsEnabled', DD_OPENAI_LOGS_ENABLED)
- target['openai.spanCharLimit'] = maybeInt(DD_OPENAI_SPAN_CHAR_LIMIT)
- unprocessedTarget.openaiSpanCharLimit = DD_OPENAI_SPAN_CHAR_LIMIT
- if (DD_TRACE_PEER_SERVICE_MAPPING) {
- target.peerServiceMapping = Object.fromEntries(
- DD_TRACE_PEER_SERVICE_MAPPING.split(',').map(x => x.trim().split(':'))
- )
- unprocessedTarget.peerServiceMapping = DD_TRACE_PEER_SERVICE_MAPPING
+
+ if (this.injectionEnabled) {
+ setAndTrack(this, 'instrumentationSource', 'ssi')
}
- setString(target, 'port', DD_TRACE_AGENT_PORT)
- const profilingEnabled = normalizeProfilingEnabledValue(DD_PROFILING_ENABLED)
- setString(target, 'profiling.enabled', profilingEnabled)
- setString(target, 'profiling.exporters', DD_PROFILING_EXPORTERS)
- setBoolean(target, '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
- target['profiling.longLivedThreshold'] = Number(DD_INTERNAL_PROFILING_LONG_LIVED_THRESHOLD)
+
+ if (!trackedConfigOrigins.has('runtimeMetrics.enabled') && this.OTEL_METRICS_EXPORTER === 'none') {
+ setAndTrack(this, 'runtimeMetrics.enabled', false)
}
- setString(target, 'protocolVersion', DD_TRACE_AGENT_PROTOCOL_VERSION)
- setString(target, 'queryStringObfuscation', DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP)
- setBoolean(target, 'remoteConfig.enabled', DD_REMOTE_CONFIGURATION_ENABLED)
- target['remoteConfig.pollInterval'] = maybeFloat(DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS)
- unprocessedTarget['remoteConfig.pollInterval'] = DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS
- setBoolean(target, 'reportHostname', DD_TRACE_REPORT_HOSTNAME)
- if (DD_TRACE_RESOURCE_RENAMING_ENABLED !== undefined) {
- setBoolean(target, 'resourceRenamingEnabled', DD_TRACE_RESOURCE_RENAMING_ENABLED)
+ if (!trackedConfigOrigins.has('sampleRate') && trackedConfigOrigins.has('OTEL_TRACES_SAMPLER')) {
+ setAndTrack(this, 'sampleRate', getFromOtelSamplerMap(this.OTEL_TRACES_SAMPLER, this.OTEL_TRACES_SAMPLER_ARG))
}
- // only used to explicitly set runtimeMetrics to false
- const otelSetRuntimeMetrics = String(OTEL_METRICS_EXPORTER).toLowerCase() === 'none'
- ? false
- : undefined
- setBoolean(target, 'runtimeMetrics.enabled', DD_RUNTIME_METRICS_ENABLED ||
- otelSetRuntimeMetrics)
- setBoolean(target, 'runtimeMetrics.eventLoop', DD_RUNTIME_METRICS_EVENT_LOOP_ENABLED)
- setBoolean(target, 'runtimeMetrics.gc', DD_RUNTIME_METRICS_GC_ENABLED)
- setBoolean(target, 'runtimeMetricsRuntimeId', DD_RUNTIME_METRICS_RUNTIME_ID_ENABLED)
- setArray(target, 'sampler.spanSamplingRules', reformatSpanSamplingRules(
- maybeJsonFile(DD_SPAN_SAMPLING_RULES_FILE) ??
- safeJsonParse(DD_SPAN_SAMPLING_RULES)
- ))
- setUnit(
- target,
- 'sampleRate',
- DD_TRACE_SAMPLE_RATE || getFromOtelSamplerMap(OTEL_TRACES_SAMPLER, OTEL_TRACES_SAMPLER_ARG)
- )
- target['sampler.rateLimit'] = DD_TRACE_RATE_LIMIT
- setSamplingRule(target, 'sampler.rules', safeJsonParse(DD_TRACE_SAMPLING_RULES))
- unprocessedTarget['sampler.rules'] = DD_TRACE_SAMPLING_RULES
- setString(target, 'scope', DD_TRACE_SCOPE)
- // Priority:
- // DD_SERVICE > tags.service > OTEL_SERVICE_NAME > NX_TASK_TARGET_PROJECT (if DD_ENABLE_NX_SERVICE_NAME) > default
- let serviceName = DD_SERVICE || tags.service || OTEL_SERVICE_NAME
- let isServiceNameInferred
- if (!serviceName && NX_TASK_TARGET_PROJECT) {
- if (isTrue(DD_ENABLE_NX_SERVICE_NAME)) {
- isServiceNameInferred = true
- serviceName = NX_TASK_TARGET_PROJECT
- } else if (DD_MAJOR < 6) {
- // Warn about v6 behavior change for Nx projects
- log.warn(
- // eslint-disable-next-line @stylistic/max-len
- 'NX_TASK_TARGET_PROJECT is set but no service name was configured. In v6, NX_TASK_TARGET_PROJECT will be used as the default service name. Set DD_ENABLE_NX_SERVICE_NAME=true to opt-in to this behavior now, or set a service name explicitly.'
- )
+
+ if (this.DD_SPAN_SAMPLING_RULES_FILE) {
+ try {
+ // TODO: Should we log a warning in case this is defined next to spanSamplingRules?
+ setAndTrack(this, 'spanSamplingRules', transformers.toCamelCase(JSON.parse(this.DD_SPAN_SAMPLING_RULES_FILE)))
+ } catch (error) {
+ log.warn('Error reading span sampling rules file %s; %o', this.DD_SPAN_SAMPLING_RULES_FILE, error)
}
}
- setString(target, 'service', serviceName)
- if (serviceName) setBoolean(target, 'isServiceNameInferred', isServiceNameInferred ?? false)
- if (DD_SERVICE_MAPPING) {
- target.serviceMapping = Object.fromEntries(
- DD_SERVICE_MAPPING.split(',').map(x => x.trim().split(':'))
- )
- }
- setString(target, 'site', DD_SITE)
- if (DD_TRACE_SPAN_ATTRIBUTE_SCHEMA) {
- setString(target, 'spanAttributeSchema', validateNamingVersion(DD_TRACE_SPAN_ATTRIBUTE_SCHEMA))
- unprocessedTarget.spanAttributeSchema = DD_TRACE_SPAN_ATTRIBUTE_SCHEMA
- }
- // 0: disabled, 1: logging, 2: garbage collection + logging
- target.spanLeakDebug = maybeInt(DD_TRACE_SPAN_LEAK_DEBUG)
- setBoolean(target, 'spanRemoveIntegrationFromService', DD_TRACE_REMOVE_INTEGRATION_SERVICE_NAMES_ENABLED)
- setBoolean(target, 'startupLogs', DD_TRACE_STARTUP_LOGS)
- setTags(target, 'tags', tags)
- target.tagsHeaderMaxLength = DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH
- setBoolean(target, 'telemetry.enabled', DD_INSTRUMENTATION_TELEMETRY_ENABLED)
- setString(target, 'instrumentation_config_id', DD_INSTRUMENTATION_CONFIG_ID)
- setBoolean(target, 'telemetry.debug', DD_TELEMETRY_DEBUG)
- setBoolean(target, 'telemetry.dependencyCollection', DD_TELEMETRY_DEPENDENCY_COLLECTION_ENABLED)
- target['telemetry.heartbeatInterval'] = maybeInt(Math.floor(DD_TELEMETRY_HEARTBEAT_INTERVAL * 1000))
- unprocessedTarget['telemetry.heartbeatInterval'] = DD_TELEMETRY_HEARTBEAT_INTERVAL
- setBoolean(target, 'telemetry.logCollection', DD_TELEMETRY_LOG_COLLECTION_ENABLED)
- setBoolean(target, 'telemetry.metrics', DD_TELEMETRY_METRICS_ENABLED)
- setBoolean(target, 'isKeepingCoverageConfiguration', DD_TEST_TIA_KEEP_COV_CONFIG)
- setBoolean(target, 'traceId128BitGenerationEnabled', DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED)
- setBoolean(target, 'traceId128BitLoggingEnabled', DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED)
- warnIfPropagationStyleConflict(
- DD_TRACE_PROPAGATION_STYLE,
- DD_TRACE_PROPAGATION_STYLE_INJECT,
- DD_TRACE_PROPAGATION_STYLE_EXTRACT
- )
- if (DD_TRACE_PROPAGATION_STYLE !== undefined) {
- setArray(target, 'tracePropagationStyle.inject', normalizePropagationStyle(DD_TRACE_PROPAGATION_STYLE))
- setArray(target, 'tracePropagationStyle.extract', normalizePropagationStyle(DD_TRACE_PROPAGATION_STYLE))
- }
- if (DD_TRACE_PROPAGATION_STYLE_INJECT !== undefined) {
- setArray(target, 'tracePropagationStyle.inject',
- normalizePropagationStyle(DD_TRACE_PROPAGATION_STYLE_INJECT))
+
+ // All sampler options are tracked as individual values. No need to track the sampler object as a whole.
+ this.sampler = {
+ rules: this.samplingRules,
+ rateLimit: this.rateLimit,
+ sampleRate: this.sampleRate,
+ spanSamplingRules: this.spanSamplingRules,
}
- if (DD_TRACE_PROPAGATION_STYLE_EXTRACT !== undefined) {
- setArray(target, 'tracePropagationStyle.extract',
- normalizePropagationStyle(DD_TRACE_PROPAGATION_STYLE_EXTRACT))
+
+ // For LLMObs, we want to auto enable it when other llmobs options are defined.
+ if (!this.llmobs.enabled &&
+ !trackedConfigOrigins.has('llmobs.enabled') &&
+ (trackedConfigOrigins.has('llmobs.agentlessEnabled') ||
+ trackedConfigOrigins.has('llmobs.mlApp'))) {
+ setAndTrack(this, 'llmobs.enabled', true)
}
- setBoolean(target, 'tracePropagationExtractFirst', DD_TRACE_PROPAGATION_EXTRACT_FIRST)
- if (DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT !== undefined) {
- const stringPropagationBehaviorExtract = String(DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT)
- target.tracePropagationBehaviorExtract =
- VALID_PROPAGATION_BEHAVIOR_EXTRACT.has(stringPropagationBehaviorExtract)
- ? stringPropagationBehaviorExtract
- : 'continue'
+
+ if (this.OTEL_RESOURCE_ATTRIBUTES) {
+ for (const [key, value] of Object.entries(this.OTEL_RESOURCE_ATTRIBUTES)) {
+ // Not replacing existing tags keeps the order of the tags as before.
+ if (!this.tags[key]) {
+ this.tags[key] = value
+ }
+ }
}
- if (DD_TRACE_PROPAGATION_STYLE !== undefined ||
- DD_TRACE_PROPAGATION_STYLE_INJECT !== undefined ||
- DD_TRACE_PROPAGATION_STYLE_EXTRACT !== undefined ||
- OTEL_PROPAGATORS !== undefined) {
- // At least one var is defined, calculate value using truthy logic
- const useDdStyle = DD_TRACE_PROPAGATION_STYLE ||
- DD_TRACE_PROPAGATION_STYLE_INJECT ||
- DD_TRACE_PROPAGATION_STYLE_EXTRACT
- setBoolean(target, 'tracePropagationStyle.otelPropagators',
- useDdStyle ? false : !!OTEL_PROPAGATORS)
-
- // Use OTEL_PROPAGATORS if no DD-specific vars are set
- if (!useDdStyle && OTEL_PROPAGATORS) {
- const otelStyles = normalizePropagationStyle(OTEL_PROPAGATORS)
- // Validate OTEL propagators
- for (const style of otelStyles || []) {
- if (!VALID_PROPAGATION_STYLES.has(style)) {
- log.warn('unexpected value %s for OTEL_PROPAGATORS environment variable', style)
- getCounter('otel.env.invalid', 'DD_TRACE_PROPAGATION_STYLE', 'OTEL_PROPAGATORS').inc()
+ if (this.DD_TRACE_TAGS) {
+ // TODO: This is a hack to keep the order of the tags as before.
+ // That hack is not sufficient, since it does not handle other cases where the tags are set by the user.
+ if (trackedConfigOrigins.get('tags') === 'code') {
+ for (const [key, value] of Object.entries(this.DD_TRACE_TAGS)) {
+ // Not replacing existing tags keeps the order of the tags as before.
+ if (!this.tags[key]) {
+ this.tags[key] = value
}
}
- // Set inject/extract from OTEL_PROPAGATORS
- if (otelStyles) {
- setArray(target, 'tracePropagationStyle.inject', otelStyles)
- setArray(target, 'tracePropagationStyle.extract', otelStyles)
- }
+ } else {
+ Object.assign(this.tags, this.DD_TRACE_TAGS)
}
}
- setBoolean(target, 'traceWebsocketMessagesEnabled', DD_TRACE_WEBSOCKET_MESSAGES_ENABLED)
- setBoolean(target, 'traceWebsocketMessagesInheritSampling', DD_TRACE_WEBSOCKET_MESSAGES_INHERIT_SAMPLING)
- setBoolean(target, 'traceWebsocketMessagesSeparateTraces', DD_TRACE_WEBSOCKET_MESSAGES_SEPARATE_TRACES)
- setBoolean(target, 'tracing', DD_TRACING_ENABLED)
- setString(target, 'version', DD_VERSION || tags.version)
- setBoolean(target, 'inferredProxyServicesEnabled', DD_TRACE_INFERRED_PROXY_SERVICES_ENABLED)
- setBoolean(target, 'trace.aws.addSpanPointers', DD_TRACE_AWS_ADD_SPAN_POINTERS)
- setString(target, 'trace.dynamoDb.tablePrimaryKeys', DD_TRACE_DYNAMODB_TABLE_PRIMARY_KEYS)
- setArray(target, 'graphqlErrorExtensions', DD_TRACE_GRAPHQL_ERROR_EXTENSIONS)
- setBoolean(target, 'trace.nativeSpanEvents', DD_TRACE_NATIVE_SPAN_EVENTS)
- target['vertexai.spanPromptCompletionSampleRate'] = maybeFloat(DD_VERTEXAI_SPAN_PROMPT_COMPLETION_SAMPLE_RATE)
- target['vertexai.spanCharLimit'] = maybeInt(DD_VERTEXAI_SPAN_CHAR_LIMIT)
- }
- #applyOptions (options) {
- const opts = this.#options
- const tags = {}
-
- options = this.#optionsArg = { ingestion: {}, ...options, ...opts }
-
- tagger.add(tags, options.tags)
-
- setBoolean(opts, 'apmTracingEnabled', options.apmTracingEnabled ??
- (options.experimental?.appsec?.standalone && !options.experimental.appsec.standalone.enabled)
- )
- setBoolean(opts, 'appsec.apiSecurity.enabled', options.appsec?.apiSecurity?.enabled)
- 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
- opts['appsec.blockedTemplateJson'] = maybeFile(options.appsec?.blockedTemplateJson)
- this.#optsUnprocessed['appsec.blockedTemplateJson'] = options.appsec?.blockedTemplateJson
- setBoolean(opts, 'appsec.enabled', options.appsec?.enabled)
- setString(opts, 'appsec.eventTracking.mode', options.appsec?.eventTracking?.mode)
- setBoolean(
- opts,
- 'appsec.extendedHeadersCollection.enabled',
- options.appsec?.extendedHeadersCollection?.enabled
- )
- setBoolean(
- opts,
- 'appsec.extendedHeadersCollection.redaction',
- options.appsec?.extendedHeadersCollection?.redaction
- )
- opts['appsec.extendedHeadersCollection.maxHeaders'] = options.appsec?.extendedHeadersCollection?.maxHeaders
- setString(opts, 'appsec.obfuscatorKeyRegex', options.appsec?.obfuscatorKeyRegex)
- setString(opts, 'appsec.obfuscatorValueRegex', options.appsec?.obfuscatorValueRegex)
- setBoolean(opts, 'appsec.rasp.enabled', options.appsec?.rasp?.enabled)
- setBoolean(opts, 'appsec.rasp.bodyCollection', options.appsec?.rasp?.bodyCollection)
- opts['appsec.rateLimit'] = maybeInt(options.appsec?.rateLimit)
- this.#optsUnprocessed['appsec.rateLimit'] = options.appsec?.rateLimit
- setString(opts, 'appsec.rules', options.appsec?.rules)
- 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
- opts['appsec.stackTrace.maxStackTraces'] = maybeInt(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
- setBoolean(opts, 'clientIpEnabled', options.clientIpEnabled)
- setString(opts, 'clientIpHeader', options.clientIpHeader?.toLowerCase())
- if (options.cloudPayloadTagging?.request || options.cloudPayloadTagging?.response) {
- if (options.cloudPayloadTagging.request) {
- setBoolean(opts, 'cloudPayloadTagging.requestsEnabled', true)
- }
- if (options.cloudPayloadTagging.response) {
- setBoolean(opts, 'cloudPayloadTagging.responsesEnabled', true)
- }
- opts['cloudPayloadTagging.rules'] = appendRules(
- splitJSONPathRules(options.cloudPayloadTagging.request),
- splitJSONPathRules(options.cloudPayloadTagging.response)
- )
- }
- if (options.cloudPayloadTagging?.requestsEnabled !== undefined) {
- setBoolean(opts, 'cloudPayloadTagging.requestsEnabled', options.cloudPayloadTagging.requestsEnabled)
- }
- if (options.cloudPayloadTagging?.responsesEnabled !== undefined) {
- setBoolean(opts, 'cloudPayloadTagging.responsesEnabled', options.cloudPayloadTagging.responsesEnabled)
+ if (!this.#parsedDdTags) {
+ this.#parsedDdTags = rfdc(this.tags)
}
- opts['cloudPayloadTagging.maxDepth'] = maybeInt(options.cloudPayloadTagging?.maxDepth)
- opts.baggageMaxBytes = options.baggageMaxBytes
- opts.baggageMaxItems = options.baggageMaxItems
- setArray(opts, 'baggageTagKeys', options.baggageTagKeys)
- setBoolean(opts, 'codeOriginForSpans.enabled', options.codeOriginForSpans?.enabled)
- setBoolean(
- opts,
- 'codeOriginForSpans.experimental.exit_spans.enabled',
- options.codeOriginForSpans?.experimental?.exit_spans?.enabled
- )
- setString(opts, 'dbmPropagationMode', options.dbmPropagationMode)
- setBoolean(opts, 'dbm.injectSqlBaseHash', options.dbm?.injectSqlBaseHash)
- if (options.dogstatsd) {
- setString(opts, 'dogstatsd.hostname', options.dogstatsd.hostname)
- setString(opts, 'dogstatsd.port', options.dogstatsd.port)
- }
- setBoolean(opts, 'dsmEnabled', options.dsmEnabled)
- opts['dynamicInstrumentation.captureTimeoutMs'] = maybeInt(options.dynamicInstrumentation?.captureTimeoutMs)
- this.#optsUnprocessed['dynamicInstrumentation.captureTimeoutMs'] = options.dynamicInstrumentation?.captureTimeoutMs
- setBoolean(opts, 'dynamicInstrumentation.enabled', options.dynamicInstrumentation?.enabled)
- setString(opts, 'dynamicInstrumentation.probeFile', options.dynamicInstrumentation?.probeFile)
- setArray(
- opts,
- 'dynamicInstrumentation.redactedIdentifiers',
- options.dynamicInstrumentation?.redactedIdentifiers
- )
- setArray(
- opts,
- 'dynamicInstrumentation.redactionExcludedIdentifiers',
- options.dynamicInstrumentation?.redactionExcludedIdentifiers
- )
- opts['dynamicInstrumentation.uploadIntervalSeconds'] =
- maybeFloat(options.dynamicInstrumentation?.uploadIntervalSeconds)
- this.#optsUnprocessed['dynamicInstrumentation.uploadIntervalSeconds'] =
- options.dynamicInstrumentation?.uploadIntervalSeconds
- setString(opts, 'env', options.env || tags.env)
- setBoolean(opts, 'experimental.aiguard.enabled', options.experimental?.aiguard?.enabled)
- 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
- opts['experimental.aiguard.maxContentSize'] = maybeInt(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
- setBoolean(opts, 'experimental.enableGetRumData', options.experimental?.enableGetRumData)
- setString(opts, 'experimental.exporter', options.experimental?.exporter)
- setBoolean(opts, 'experimental.flaggingProvider.enabled', options.experimental?.flaggingProvider?.enabled)
- opts['experimental.flaggingProvider.initializationTimeoutMs'] = maybeInt(
- options.experimental?.flaggingProvider?.initializationTimeoutMs
- )
- this.#optsUnprocessed['experimental.flaggingProvider.initializationTimeoutMs'] =
- options.experimental?.flaggingProvider?.initializationTimeoutMs
- opts.flushInterval = maybeInt(options.flushInterval)
- this.#optsUnprocessed.flushInterval = options.flushInterval
- opts.flushMinSpans = maybeInt(options.flushMinSpans)
- this.#optsUnprocessed.flushMinSpans = options.flushMinSpans
- setArray(opts, 'headerTags', options.headerTags)
- setString(opts, 'hostname', options.hostname)
- opts['iast.dbRowsToTaint'] = maybeInt(options.iast?.dbRowsToTaint)
- setBoolean(opts, 'iast.deduplicationEnabled', options.iast && options.iast.deduplicationEnabled)
- 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
- opts['iast.maxContextOperations'] = maybeInt(options.iast?.maxContextOperations)
- this.#optsUnprocessed['iast.maxContextOperations'] = options.iast?.maxContextOperations
- setBoolean(opts, 'iast.redactionEnabled', options.iast?.redactionEnabled)
- setString(opts, 'iast.redactionNamePattern', options.iast?.redactionNamePattern)
- setString(opts, 'iast.redactionValuePattern', options.iast?.redactionValuePattern)
- const iastRequestSampling = maybeInt(options.iast?.requestSampling)
- if (iastRequestSampling !== undefined && iastRequestSampling > -1 && iastRequestSampling < 101) {
- opts['iast.requestSampling'] = iastRequestSampling
- this.#optsUnprocessed['iast.requestSampling'] = options.iast?.requestSampling
- }
- if (DD_MAJOR < 6) {
- opts['iast.securityControlsConfiguration'] = options.iast?.securityControlsConfiguration
+
+ if (!this.env && this.tags.env !== undefined) {
+ setAndTrack(this, 'env', this.tags.env)
}
- setBoolean(opts, 'iast.stackTrace.enabled', options.iast?.stackTrace?.enabled)
- setString(opts, 'iast.telemetryVerbosity', options.iast && options.iast.telemetryVerbosity)
- setBoolean(opts, 'isCiVisibility', options.isCiVisibility)
- setBoolean(opts, 'legacyBaggageEnabled', options.legacyBaggageEnabled)
- setBoolean(opts, 'llmobs.agentlessEnabled', options.llmobs?.agentlessEnabled)
- setString(opts, 'llmobs.mlApp', options.llmobs?.mlApp)
- setBoolean(opts, 'logInjection', options.logInjection)
- opts.lookup = options.lookup
- setBoolean(opts, 'middlewareTracingEnabled', options.middlewareTracingEnabled)
- setBoolean(opts, 'openAiLogsEnabled', options.openAiLogsEnabled)
- opts.peerServiceMapping = options.peerServiceMapping
- setBoolean(opts, 'plugins', options.plugins)
- setString(opts, 'port', options.port)
- const strProfiling = String(options.profiling)
- if (['true', 'false', 'auto'].includes(strProfiling)) {
- setString(opts, 'profiling.enabled', strProfiling)
+
+ if (!this.version) {
+ setAndTrack(this, 'version', this.tags.version || pkg.version)
+ this.tags.version ??= pkg.version
}
- setString(opts, 'protocolVersion', options.protocolVersion)
- if (options.remoteConfig) {
- opts['remoteConfig.pollInterval'] = maybeFloat(options.remoteConfig.pollInterval)
- this.#optsUnprocessed['remoteConfig.pollInterval'] = options.remoteConfig.pollInterval
+
+ let isServiceNameInferred = false
+ if (!trackedConfigOrigins.has('service')) {
+ if (this.tags.service) {
+ setAndTrack(this, 'service', this.tags.service)
+ } else {
+ const NX_TASK_TARGET_PROJECT = getEnvironmentVariable('NX_TASK_TARGET_PROJECT')
+ if (NX_TASK_TARGET_PROJECT) {
+ if (this.DD_ENABLE_NX_SERVICE_NAME) {
+ setAndTrack(this, 'service', NX_TASK_TARGET_PROJECT)
+ isServiceNameInferred = true
+ } else if (DD_MAJOR < 6) {
+ log.warn(
+ // eslint-disable-next-line eslint-rules/eslint-log-printf-style
+ 'NX_TASK_TARGET_PROJECT is set but no service name was configured. In v6, NX_TASK_TARGET_PROJECT will ' +
+ 'be used as the default service name. Set DD_ENABLE_NX_SERVICE_NAME=true to opt-in to this behavior ' +
+ 'now, or set a service name explicitly.'
+ )
+ }
+ }
+ }
+
+ if (!this.service) {
+ const serverlessName = IS_SERVERLESS
+ ? (
+ getEnvironmentVariable('AWS_LAMBDA_FUNCTION_NAME') ||
+ getEnvironmentVariable('FUNCTION_NAME') || // Google Cloud Function Name set by deprecated runtimes
+ getEnvironmentVariable('K_SERVICE') || // Google Cloud Function Name set by newer runtimes
+ getEnvironmentVariable('WEBSITE_SITE_NAME') // set by Azure Functions
+ )
+ : undefined
+
+ setAndTrack(this, 'service', serverlessName || pkg.name || 'node')
+ this.tags.service ??= /** @type {string} */ (this.service)
+ isServiceNameInferred = true
+ }
}
- setBoolean(opts, 'reportHostname', options.reportHostname)
- setBoolean(opts, 'runtimeMetrics.enabled', options.runtimeMetrics?.enabled)
- setBoolean(opts, 'runtimeMetrics.eventLoop', options.runtimeMetrics?.eventLoop)
- setBoolean(opts, 'runtimeMetrics.gc', options.runtimeMetrics?.gc)
- setBoolean(opts, 'runtimeMetricsRuntimeId', options.runtimeMetricsRuntimeId)
- setArray(opts, 'sampler.spanSamplingRules', reformatSpanSamplingRules(options.spanSamplingRules))
- setUnit(opts, 'sampleRate', options.sampleRate ?? options.ingestion.sampleRate)
- opts['sampler.rateLimit'] = maybeInt(options.rateLimit ?? options.ingestion.rateLimit)
- setSamplingRule(opts, 'sampler.rules', options.samplingRules)
- const optService = options.service || tags.service
- setString(opts, 'service', optService)
- if (optService) {
- setBoolean(opts, 'isServiceNameInferred', false)
+ setAndTrack(this, 'isServiceNameInferred', isServiceNameInferred)
+
+ // Add missing tags, in case they are defined otherwise.
+ if (this.service) {
+ this.tags.service = this.service
}
- opts.serviceMapping = options.serviceMapping
- setString(opts, 'site', options.site)
- if (options.spanAttributeSchema) {
- setString(opts, 'spanAttributeSchema', validateNamingVersion(options.spanAttributeSchema))
- this.#optsUnprocessed.spanAttributeSchema = options.spanAttributeSchema
+ if (this.env) {
+ this.tags.env = this.env
}
- setBoolean(opts, 'spanRemoveIntegrationFromService', options.spanRemoveIntegrationFromService)
- setBoolean(opts, 'startupLogs', options.startupLogs)
- setTags(opts, 'tags', tags)
- setBoolean(opts, 'traceId128BitGenerationEnabled', options.traceId128BitGenerationEnabled)
- setBoolean(opts, 'traceId128BitLoggingEnabled', options.traceId128BitLoggingEnabled)
- setBoolean(opts, 'traceWebsocketMessagesEnabled', options.traceWebsocketMessagesEnabled)
- setBoolean(opts, 'traceWebsocketMessagesInheritSampling', options.traceWebsocketMessagesInheritSampling)
- setBoolean(opts, 'traceWebsocketMessagesSeparateTraces', options.traceWebsocketMessagesSeparateTraces)
- setString(opts, 'version', options.version || tags.version)
- setBoolean(opts, 'inferredProxyServicesEnabled', options.inferredProxyServicesEnabled)
- setBoolean(opts, 'graphqlErrorExtensions', options.graphqlErrorExtensions)
- setBoolean(opts, 'trace.nativeSpanEvents', options.trace?.nativeSpanEvents)
- if (options.tracePropagationStyle) {
- setArray(opts, 'tracePropagationStyle.inject',
- normalizePropagationStyle(options.tracePropagationStyle.inject ?? options.tracePropagationStyle))
- setArray(opts, 'tracePropagationStyle.extract',
- normalizePropagationStyle(options.tracePropagationStyle.extract ?? options.tracePropagationStyle))
+ if (this.version) {
+ this.tags.version = this.version
}
+ this.tags['runtime-id'] = RUNTIME_ID
- // 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.
- if (this.#env['llmobs.enabled'] == null && options.llmobs) {
- setBoolean(opts, 'llmobs.enabled', true)
+ if (IS_SERVERLESS) {
+ setAndTrack(this, 'telemetry.enabled', false)
+ setAndTrack(this, 'crashtracking.enabled', false)
+ setAndTrack(this, 'remoteConfig.enabled', false)
}
- }
-
- #isCiVisibility () {
- return this.#optionsArg.isCiVisibility ?? this.#defaults.isCiVisibility
- }
-
- #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 ??
- getEnv('DD_AGENT_HOST') ??
- defaults.hostname
- return DD_AGENT_HOST || url?.hostname
- }
-
- #getSpanComputePeerService () {
- const DD_TRACE_SPAN_ATTRIBUTE_SCHEMA = validateNamingVersion(
- this.#optionsArg.spanAttributeSchema ??
- getEnv('DD_TRACE_SPAN_ATTRIBUTE_SCHEMA')
- )
-
- const peerServiceSet = (
- this.#optionsArg.hasOwnProperty('spanComputePeerService') ||
- getEnv('DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED') !== undefined
- )
- const peerServiceValue = this.#optionsArg.spanComputePeerService ??
- getEnv('DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED')
-
- const spanComputePeerService = (
- DD_TRACE_SPAN_ATTRIBUTE_SCHEMA === 'v0'
- // In v0, peer service is computed only if it is explicitly set to true
- ? peerServiceSet && isTrue(peerServiceValue)
- // In >v0, peer service is false only if it is explicitly set to false
- : (peerServiceSet ? !isFalse(peerServiceValue) : true)
- )
-
- return spanComputePeerService
- }
-
- #isTraceStatsComputationEnabled () {
- const apmTracingEnabled = this.#options.apmTracingEnabled !== false &&
- this.#env.apmTracingEnabled !== false
-
- return apmTracingEnabled && (
- this.#optionsArg.stats ??
- getEnv('DD_TRACE_STATS_COMPUTATION_ENABLED') ??
- (getIsGCPFunction() || getIsAzureFunction())
- )
- }
-
- #getTraceAgentUrl () {
- return this.#optionsArg.url ??
- getEnv('DD_TRACE_AGENT_URL') ??
- null
- }
-
- // handles values calculated from a mixture of options and env vars
- #applyCalculated () {
- const calc = this.#calculated
- 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)
+ // TODO: Should this unconditionally be disabled?
+ if (getEnvironmentVariable('JEST_WORKER_ID') && !trackedConfigOrigins.has('telemetry.enabled')) {
+ setAndTrack(this, 'telemetry.enabled', false)
+ }
// Experimental agentless APM span intake
// When enabled, sends spans directly to Datadog intake without an agent
- const agentlessEnabled = isTrue(getEnv('_DD_APM_TRACING_AGENTLESS_ENABLED'))
+ // TODO: Replace this with a proper configuration
+ const agentlessEnabled = isTrue(getEnvironmentVariable('_DD_APM_TRACING_AGENTLESS_ENABLED'))
if (agentlessEnabled) {
- setString(calc, 'experimental.exporter', 'agentless')
- // Disable rate limiting - server-side sampling will be used
- calc['sampler.rateLimit'] = -1
+ setAndTrack(this, 'experimental.exporter', 'agentless')
// Disable client-side stats computation
- setBoolean(calc, 'stats.enabled', false)
+ setAndTrack(this, 'stats.enabled', false)
// Enable hostname reporting
- setBoolean(calc, 'reportHostname', true)
+ setAndTrack(this, 'reportHostname', true)
+ // Disable rate limiting - server-side sampling will be used
+ setAndTrack(this, 'sampler.rateLimit', -1)
// Clear sampling rules - server-side sampling handles this
- calc['sampler.rules'] = []
+ setAndTrack(this, 'sampler.rules', [])
// Agentless intake only accepts 64-bit trace IDs; disable 128-bit generation
- setBoolean(calc, 'traceId128BitGenerationEnabled', false)
- }
-
- if (this.#isCiVisibility()) {
- setBoolean(calc, 'isEarlyFlakeDetectionEnabled',
- getEnv('DD_CIVISIBILITY_EARLY_FLAKE_DETECTION_ENABLED') ?? true)
- setBoolean(calc, 'isFlakyTestRetriesEnabled', getEnv('DD_CIVISIBILITY_FLAKY_RETRY_ENABLED') ?? true)
- calc.flakyTestRetriesCount = maybeInt(getEnv('DD_CIVISIBILITY_FLAKY_RETRY_COUNT')) ?? 5
- setBoolean(calc, 'isIntelligentTestRunnerEnabled', isTrue(isCiVisibilityItrEnabled()))
- setBoolean(calc, 'isManualApiEnabled', !isFalse(getEnv('DD_CIVISIBILITY_MANUAL_API_ENABLED')))
- setString(calc, 'ciVisibilityTestSessionName', getEnv('DD_TEST_SESSION_NAME'))
- setBoolean(calc, 'ciVisAgentlessLogSubmissionEnabled',
- isTrue(getEnv('DD_AGENTLESS_LOG_SUBMISSION_ENABLED')))
- setBoolean(calc, 'isTestDynamicInstrumentationEnabled',
- !isFalse(getEnv('DD_TEST_FAILED_TEST_REPLAY_ENABLED')))
- setBoolean(calc, 'isServiceUserProvided', !!this.#env.service)
- setBoolean(calc, 'isTestManagementEnabled', !isFalse(getEnv('DD_TEST_MANAGEMENT_ENABLED')))
- calc.testManagementAttemptToFixRetries = maybeInt(getEnv('DD_TEST_MANAGEMENT_ATTEMPT_TO_FIX_RETRIES')) ?? 20
- 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) {
- setBoolean(calc, 'logInjection', false)
+ if (!trackedConfigOrigins.has('traceId128BitGenerationEnabled')) {
+ setAndTrack(this, 'traceId128BitGenerationEnabled', false)
+ }
}
- calc['dogstatsd.hostname'] = this.#getHostname()
-
- // Compute OTLP logs and metrics URLs to send payloads to the active Datadog Agent
- const agentHostname = this.#getHostname()
- calc.otelLogsUrl = `http://${agentHostname}:${DEFAULT_OTLP_PORT}`
- calc.otelMetricsUrl = `http://${agentHostname}:${DEFAULT_OTLP_PORT}/v1/metrics`
- calc.otelUrl = `http://${agentHostname}:${DEFAULT_OTLP_PORT}`
- calc['telemetry.heartbeatInterval'] = maybeInt(Math.floor(this.#defaults['telemetry.heartbeatInterval'] * 1000))
-
- setBoolean(calc, 'isGitUploadEnabled',
- calc.isIntelligentTestRunnerEnabled && !isFalse(getEnv('DD_CIVISIBILITY_GIT_UPLOAD_ENABLED')))
-
- // Enable resourceRenamingEnabled when appsec is enabled and only
- // if DD_TRACE_RESOURCE_RENAMING_ENABLED is not explicitly set
- if (this.#env.resourceRenamingEnabled === undefined) {
- const appsecEnabled = this.#options['appsec.enabled'] ?? this.#env['appsec.enabled']
- if (appsecEnabled) {
- setBoolean(calc, 'resourceRenamingEnabled', true)
+ // Apply all fallbacks to the calculated config.
+ for (const [configName, alias] of fallbackConfigurations) {
+ if (!trackedConfigOrigins.has(configName) && trackedConfigOrigins.has(alias)) {
+ setAndTrack(this, configName, this[alias])
}
}
- setBoolean(calc, 'spanComputePeerService', this.#getSpanComputePeerService())
- setBoolean(calc, 'stats.enabled', this.#isTraceStatsComputationEnabled())
- const defaultPropagationStyle = getDefaultPropagationStyle(this.#optionsArg)
- if (defaultPropagationStyle.length > 2) {
- // b3 was added, so update defaults to include it
- // This will only be used if no other source (options, env, stable config) set the value
- calc['tracePropagationStyle.inject'] = defaultPropagationStyle
- calc['tracePropagationStyle.extract'] = defaultPropagationStyle
+ const DEFAULT_OTLP_PORT = '4318'
+ if (!this.otelLogsUrl) {
+ setAndTrack(this, 'otelLogsUrl', `http://${agentHostname}:${DEFAULT_OTLP_PORT}`)
}
- }
-
- /**
- * Applies remote configuration options from APM_TRACING configs.
- *
- * @param {import('./remote_config').RemoteConfigOptions} options - Configurations received via Remote Config
- */
- #applyRemoteConfig (options) {
- const opts = this.#remote
-
- setBoolean(opts, 'dynamicInstrumentation.enabled', options.dynamic_instrumentation_enabled)
- setBoolean(opts, 'codeOriginForSpans.enabled', options.code_origin_enabled)
- setUnit(opts, 'sampleRate', options.tracing_sampling_rate)
- setBoolean(opts, 'logInjection', options.log_injection_enabled)
- setBoolean(opts, 'tracing', options.tracing_enabled)
- this.#remoteUnprocessed['sampler.rules'] = options.tracing_sampling_rules
- setSamplingRule(opts, 'sampler.rules', reformatTagsFromRC(options.tracing_sampling_rules))
-
- opts.headerTags = options.tracing_header_tags?.map(tag => {
- return tag.tag_name ? `${tag.header}:${tag.tag_name}` : tag.header
- })
-
- const tags = {}
- tagger.add(tags, options.tracing_tags)
- if (Object.keys(tags).length) {
- tags['runtime-id'] = RUNTIME_ID
+ if (!this.otelMetricsUrl) {
+ setAndTrack(this, 'otelMetricsUrl', `http://${agentHostname}:${DEFAULT_OTLP_PORT}/v1/metrics`)
}
- setTags(opts, 'tags', tags)
- }
-
- #setAndTrackChange ({ name, value, origin, unprocessedValue, changes }) {
- set(this, name, value)
- if (!changeTracker[name]) {
- changeTracker[name] = {}
+ if (process.platform === 'win32') {
+ // OOM monitoring does not work properly on Windows, so it will be disabled.
+ deactivateIfEnabledAndWarnOnWindows(this, 'DD_PROFILING_EXPERIMENTAL_OOM_MONITORING_ENABLED')
+ // Profiler sampling contexts are not available on Windows, so features
+ // depending on those (code hotspots and endpoint collection) need to be disabled on Windows.
+ deactivateIfEnabledAndWarnOnWindows(this, 'DD_PROFILING_CODEHOTSPOTS_ENABLED')
+ deactivateIfEnabledAndWarnOnWindows(this, 'DD_PROFILING_ENDPOINT_COLLECTION_ENABLED')
+ deactivateIfEnabledAndWarnOnWindows(this, 'DD_PROFILING_CPU_ENABLED')
+ deactivateIfEnabledAndWarnOnWindows(this, 'DD_PROFILING_TIMELINE_ENABLED')
+ deactivateIfEnabledAndWarnOnWindows(this, 'DD_PROFILING_ASYNC_CONTEXT_FRAME_ENABLED')
}
- const originExists = origin in changeTracker[name]
- const oldValue = changeTracker[name][origin]
+ // Single tags update is tracked as a calculated value.
+ setAndTrack(this, 'tags', this.tags)
- if (!originExists || oldValue !== value) {
- changeTracker[name][origin] = value
- changes.push({
- name,
- value: unprocessedValue || value,
- origin,
- })
- }
- }
-
- // TODO: Report origin changes and errors to telemetry.
- // TODO: Deeply merge configurations.
- // 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 () {
- const changes = []
- const sources = this.#getSourcesInOrder()
-
- for (const name of Object.keys(this.#defaults)) {
- // Use reverse order for merge (lowest priority first)
- 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({
- name,
- value,
- origin,
- unprocessedValue: unprocessed?.[name],
- changes,
- })
- }
- }
- }
- this.sampler.sampleRate = this.sampleRate
- updateConfig(changes, this)
+ telemetry.updateConfig([...configWithOrigin.values()], this)
}
+ // TODO: Move outside of config. This is unrelated to the config system.
#loadGitMetadata () {
- // try to read Git metadata from the environment variables
- this.repositoryUrl = removeUserSensitiveInfo(
- getEnv('DD_GIT_REPOSITORY_URL') ?? this.tags[GIT_REPOSITORY_URL]
- )
- this.commitSHA = getEnv('DD_GIT_COMMIT_SHA') ?? this.tags[GIT_COMMIT_SHA]
+ // Try to read Git metadata from the environment variables
+ this.repositoryUrl = removeUserSensitiveInfo(this.DD_GIT_REPOSITORY_URL ?? this.tags[GIT_REPOSITORY_URL])
+ this.commitSHA = this.DD_GIT_COMMIT_SHA ?? this.tags[GIT_COMMIT_SHA]
- // otherwise, try to read Git metadata from the git.properties file
+ // Otherwise, try to read Git metadata from the git.properties file
if (!this.repositoryUrl || !this.commitSHA) {
- const DD_GIT_PROPERTIES_FILE = getEnv('DD_GIT_PROPERTIES_FILE')
+ const DD_GIT_PROPERTIES_FILE = this.DD_GIT_PROPERTIES_FILE
const gitPropertiesFile = DD_GIT_PROPERTIES_FILE ?? `${process.cwd()}/git.properties`
- let gitPropertiesString
try {
- gitPropertiesString = fs.readFileSync(gitPropertiesFile, 'utf8')
- } catch (e) {
+ const gitPropertiesString = fs.readFileSync(gitPropertiesFile, 'utf8')
+ const { commitSHA, repositoryUrl } = getGitMetadataFromGitProperties(gitPropertiesString)
+ this.commitSHA ??= commitSHA
+ this.repositoryUrl ??= repositoryUrl
+ } catch (error) {
// Only log error if the user has set a git.properties path
if (DD_GIT_PROPERTIES_FILE) {
- log.error('Error reading DD_GIT_PROPERTIES_FILE: %s', gitPropertiesFile, e)
+ log.error('Error reading DD_GIT_PROPERTIES_FILE: %s', gitPropertiesFile, error)
}
}
- if (gitPropertiesString) {
- const { commitSHA, repositoryUrl } = getGitMetadataFromGitProperties(gitPropertiesString)
- this.commitSHA = this.commitSHA || commitSHA
- this.repositoryUrl = this.repositoryUrl || repositoryUrl
- }
}
- // otherwise, try to read Git metadata from the .git/ folder
- if (!this.repositoryUrl || !this.commitSHA) {
- const DD_GIT_FOLDER_PATH = getEnv('DD_GIT_FOLDER_PATH')
- const gitFolderPath = DD_GIT_FOLDER_PATH ?? path.join(process.cwd(), '.git')
- if (!this.repositoryUrl) {
- // try to read git config (repository URL)
- const gitConfigPath = path.join(gitFolderPath, 'config')
- try {
- const gitConfigContent = fs.readFileSync(gitConfigPath, 'utf8')
- if (gitConfigContent) {
- this.repositoryUrl = getRemoteOriginURL(gitConfigContent)
- }
- } catch (e) {
- // Only log error if the user has set a .git/ path
- if (DD_GIT_FOLDER_PATH) {
- log.error('Error reading git config: %s', gitConfigPath, e)
- }
+
+ // Otherwise, try to read Git metadata from the .git/ folder
+ const DD_GIT_FOLDER_PATH = this.DD_GIT_FOLDER_PATH
+ const gitFolderPath = DD_GIT_FOLDER_PATH ?? path.join(process.cwd(), '.git')
+
+ if (!this.repositoryUrl) {
+ // Try to read git config (repository URL)
+ const gitConfigPath = path.join(gitFolderPath, 'config')
+ try {
+ const gitConfigContent = fs.readFileSync(gitConfigPath, 'utf8')
+ if (gitConfigContent) {
+ this.repositoryUrl = getRemoteOriginURL(gitConfigContent)
}
- }
- if (!this.commitSHA) {
- // try to read git HEAD (commit SHA)
- const gitHeadSha = resolveGitHeadSHA(gitFolderPath)
- if (gitHeadSha) {
- this.commitSHA = gitHeadSha
+ } catch (error) {
+ // Only log error if the user has set a .git/ path
+ if (DD_GIT_FOLDER_PATH) {
+ log.error('Error reading git config: %s', gitConfigPath, error)
}
}
}
+ // Try to read git HEAD (commit SHA)
+ this.commitSHA ??= resolveGitHeadSHA(gitFolderPath)
}
}
-function getCounter (event, ddVar, otelVar) {
- const counters = TELEMETRY_COUNTERS.get(event)
- const tags = []
- const ddVarPrefix = 'config_datadog:'
- const otelVarPrefix = 'config_opentelemetry:'
- if (ddVar) {
- ddVar = ddVarPrefix + ddVar.toLowerCase()
- tags.push(ddVar)
- }
- if (otelVar) {
- otelVar = otelVarPrefix + otelVar.toLowerCase()
- tags.push(otelVar)
- }
-
- if (!(otelVar in counters)) counters[otelVar] = {}
-
- const counter = tracerMetrics.count(event, tags)
- counters[otelVar][ddVar] = counter
- return counter
-}
-
-function getFromOtelSamplerMap (otelTracesSampler, otelTracesSamplerArg) {
- const OTEL_TRACES_SAMPLER_MAPPING = {
- always_on: '1.0',
- always_off: '0.0',
- traceidratio: otelTracesSamplerArg,
- parentbased_always_on: '1.0',
- parentbased_always_off: '0.0',
- parentbased_traceidratio: otelTracesSamplerArg,
- }
- return OTEL_TRACES_SAMPLER_MAPPING[otelTracesSampler]
-}
-
/**
- * 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
+ * @param {Config} config
+ * @param {ConfigKey} envVar
*/
-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)
- case 'OTEL_PROPAGATORS':
- case 'OTEL_RESOURCE_ATTRIBUTES':
- case 'OTEL_SERVICE_NAME':
- return typeof value !== 'string'
- case 'OTEL_TRACES_SAMPLER':
- return getFromOtelSamplerMap(value, getEnv('OTEL_TRACES_SAMPLER_ARG')) === undefined
- case 'OTEL_TRACES_SAMPLER_ARG':
- return Number.isNaN(Number.parseFloat(value))
- case 'OTEL_SDK_DISABLED':
- return value.toLowerCase() !== 'true' && value.toLowerCase() !== 'false'
- case 'OTEL_TRACES_EXPORTER':
- case 'OTEL_METRICS_EXPORTER':
- case 'OTEL_LOGS_EXPORTER':
- return value.toLowerCase() !== 'none'
- default:
- return true
- }
-}
-
-function checkIfBothOtelAndDdEnvVarSet () {
- for (const [otelEnvVar, ddEnvVar] of OTEL_DD_ENV_MAPPING) {
- 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 (isInvalidOtelEnvironmentVariable(otelEnvVar, otelValue)) {
- log.warn('unexpected value %s for %s environment variable', otelValue, otelEnvVar)
- getCounter('otel.env.invalid', ddEnvVar, otelEnvVar).inc()
+function deactivateIfEnabledAndWarnOnWindows (config, envVar) {
+ if (config[envVar]) {
+ const source = trackedConfigOrigins.get(envVar)
+ setAndTrack(config, envVar, false)
+ // TODO: Should we log even for default values?
+ if (source) {
+ log.warn('%s is not supported on Windows. Deactivating. (source: %s)', envVar, source)
}
}
}
-function maybeFile (filepath) {
- if (!filepath) return
- try {
- return fs.readFileSync(filepath, 'utf8')
- } catch (e) {
- log.error('Error reading file %s', filepath, e)
- }
-}
-
-function maybeJsonFile (filepath) {
- const file = maybeFile(filepath)
- if (!file) return
- try {
- return JSON.parse(file)
- } catch (e) {
- log.error('Error parsing JSON file %s', filepath, e)
- }
-}
-
-function safeJsonParse (input) {
- try {
- return JSON.parse(input)
- } catch {}
-}
-
-function validateNamingVersion (versionString) {
- if (!versionString) {
- return DEFAULT_NAMING_VERSION
- }
- if (!NAMING_VERSIONS.has(versionString)) {
- log.warn('Unexpected input for config.spanAttributeSchema, picked default', DEFAULT_NAMING_VERSION)
- return DEFAULT_NAMING_VERSION
- }
- return versionString
-}
-
-/**
- * Given a string of comma-separated paths, return the array of paths.
- * If a blank path is provided a null is returned to signal that the feature is disabled.
- * An empty array means the feature is enabled but that no rules need to be applied.
- *
- * @param {string | string[]} input
- */
-function splitJSONPathRules (input) {
- if (!input || input === '$') return
- if (Array.isArray(input)) return input
- if (input === 'all') return []
- return input.split(',')
-}
-
-// Shallow clone with property name remapping
-function remapify (input, mappings) {
- if (!input) return
- const output = {}
- for (const [key, value] of Object.entries(input)) {
- output[key in mappings ? mappings[key] : key] = value
+function increaseCounter (event, ddVar, otelVar) {
+ const tags = []
+ if (ddVar) {
+ tags.push(`config_datadog:${ddVar.toLowerCase()}`)
}
- return output
+ tags.push(`config_opentelemetry:${otelVar.toLowerCase()}`)
+ tracerMetrics.count(event, tags).inc()
}
-/**
- * Normalizes propagation style values to a lowercase array.
- * Handles both string (comma-separated) and array inputs.
- */
-function normalizePropagationStyle (value) {
- if (Array.isArray(value)) {
- return value.map(v => v.toLowerCase())
- }
- if (typeof value === 'string') {
- return value.split(',')
- .filter(v => v !== '')
- .map(v => v.trim().toLowerCase())
- }
- if (value !== undefined) {
- log.warn('Unexpected input for config.tracePropagationStyle')
+function getFromOtelSamplerMap (otelTracesSampler, otelTracesSamplerArg) {
+ const OTEL_TRACES_SAMPLER_MAPPING = {
+ always_on: 1,
+ always_off: 0,
+ parentbased_always_on: 1,
+ parentbased_always_off: 0,
}
-}
-/**
- * Warns if both DD_TRACE_PROPAGATION_STYLE and specific inject/extract vars are set.
- */
-function warnIfPropagationStyleConflict (general, inject, extract) {
- if (general && (inject || extract)) {
- log.warn(
- // eslint-disable-next-line @stylistic/max-len
- 'Use either the DD_TRACE_PROPAGATION_STYLE environment variable or separate DD_TRACE_PROPAGATION_STYLE_INJECT and DD_TRACE_PROPAGATION_STYLE_EXTRACT environment variables'
- )
+ const result = OTEL_TRACES_SAMPLER_MAPPING[otelTracesSampler] ?? otelTracesSamplerArg
+ if (result === undefined) {
+ increaseCounter('otel.env.invalid', 'DD_TRACE_SAMPLE_RATE', 'OTEL_TRACES_SAMPLER')
}
+ return result
}
-function reformatSpanSamplingRules (rules) {
- if (!rules) return rules
- return rules.map(rule => {
- return remapify(rule, {
- sample_rate: 'sampleRate',
- max_per_second: 'maxPerSecond',
- })
- })
-}
-
-function getDefaultPropagationStyle (options) {
- // TODO: Remove the experimental env vars as a major?
- const DD_TRACE_B3_ENABLED = options.experimental?.b3 ??
- getEnv('DD_TRACE_EXPERIMENTAL_B3_ENABLED')
- const defaultPropagationStyle = ['datadog', 'tracecontext']
- if (isTrue(DD_TRACE_B3_ENABLED)) {
- defaultPropagationStyle.push('b3', 'b3 single header')
- }
- return defaultPropagationStyle
-}
-
-function isCiVisibilityItrEnabled () {
- return getEnv('DD_CIVISIBILITY_ITR_ENABLED') ?? true
-}
-
-function reformatTagsFromRC (samplingRules) {
- for (const rule of (samplingRules || [])) {
- if (rule.tags) {
- const reformattedTags = {}
- for (const tag of rule.tags) {
- reformattedTags[tag.key] = tag.value_glob
+function warnWrongOtelSettings () {
+ // This mostly works for non-aliased environment variables only.
+ // TODO: Adjust this to work across all sources.
+ for (const [otelEnvVar, ddEnvVar, key] of [
+ // eslint-disable-next-line eslint-rules/eslint-env-aliases
+ ['OTEL_LOG_LEVEL', 'DD_TRACE_LOG_LEVEL'],
+ // eslint-disable-next-line eslint-rules/eslint-env-aliases
+ ['OTEL_PROPAGATORS', 'DD_TRACE_PROPAGATION_STYLE'],
+ // eslint-disable-next-line eslint-rules/eslint-env-aliases
+ ['OTEL_SERVICE_NAME', 'DD_SERVICE', 'service'],
+ ['OTEL_TRACES_SAMPLER', 'DD_TRACE_SAMPLE_RATE'],
+ ['OTEL_TRACES_SAMPLER_ARG', 'DD_TRACE_SAMPLE_RATE'],
+ ['OTEL_TRACES_EXPORTER', 'DD_TRACE_ENABLED'],
+ ['OTEL_METRICS_EXPORTER', 'DD_RUNTIME_METRICS_ENABLED'],
+ ['OTEL_RESOURCE_ATTRIBUTES', 'DD_TAGS'],
+ ['OTEL_SDK_DISABLED', 'DD_TRACE_OTEL_ENABLED'],
+ ['OTEL_LOGS_EXPORTER'],
+ ]) {
+ // eslint-disable-next-line eslint-rules/eslint-process-env
+ const envs = process.env
+ const otelSource = trackedConfigOrigins.get(/** @type {ConfigPath} */ (key ?? otelEnvVar))
+ const otelEnvValue = envs[otelEnvVar]
+ if (otelEnvValue) {
+ if (envs[ddEnvVar]) {
+ log.warn('Conflicting %s and %s environment variables are set for %s', ddEnvVar, otelEnvVar, otelSource)
+ increaseCounter('otel.env.hiding', ddEnvVar, otelEnvVar)
}
- rule.tags = reformattedTags
- }
- }
- return samplingRules
-}
-
-function setBoolean (obj, name, value) {
- if (value === undefined || value === null) {
- obj[name] = value
- } else if (isTrue(value)) {
- obj[name] = true
- } else if (isFalse(value)) {
- obj[name] = false
- }
-}
-
-function setUnit (obj, name, value) {
- if (value === null || value === undefined) {
- obj[name] = value
- return
- }
-
- value = Number.parseFloat(value)
-
- if (!Number.isNaN(value)) {
- // TODO: Ignore out of range values instead of normalizing them.
- obj[name] = Math.min(Math.max(value, 0), 1)
- }
-}
-
-function setArray (obj, name, value) {
- if (value == null) {
- obj[name] = null
- return
- }
-
- if (typeof value === 'string') {
- value = value.split(',').map(item => {
- // Trim each item and remove whitespace around the colon
- const [key, val] = item.split(':').map(part => part.trim())
- return val === undefined ? key : `${key}:${val}`
- })
- }
- if (Array.isArray(value)) {
- obj[name] = value
- }
-}
-
-function setIntegerRangeSet (obj, name, value) {
- if (value == null) {
- obj[name] = null
- return
- }
- value = value.split(',')
- const result = []
-
- for (const val of value) {
- if (val.includes('-')) {
- const [start, end] = val.split('-').map(Number)
- for (let i = start; i <= end; i++) {
- result.push(i)
+ // eslint-disable-next-line eslint-rules/eslint-env-aliases
+ const invalidOtelValue = otelEnvVar === 'OTEL_PROPAGATORS'
+ ? trackedConfigOrigins.get(/** @type {ConfigPath} */ ('tracePropagationStyle.inject')) !== otelSource &&
+ !envs[ddEnvVar]
+ : !otelSource
+ if (invalidOtelValue) {
+ increaseCounter('otel.env.invalid', ddEnvVar, otelEnvVar)
}
- } else {
- result.push(Number(val))
}
}
- obj[name] = result
-}
-
-function setSamplingRule (obj, name, value) {
- if (value == null) {
- obj[name] = null
- return
- }
-
- if (typeof value === 'string') {
- value = value.split(',')
- }
-
- if (Array.isArray(value)) {
- value = value.map(rule => {
- return remapify(rule, {
- sample_rate: 'sampleRate',
- })
- })
- obj[name] = value
- }
-}
-
-function setString (obj, name, value) {
- obj[name] = value ? String(value) : undefined // unset for empty strings
-}
-
-function setTags (obj, name, value) {
- if (!value || Object.keys(value).length === 0) {
- obj[name] = null
- return
- }
-
- obj[name] = value
-}
-
-function handleOtel (tagString) {
- return tagString
- ?.replace(/(^|,)deployment\.environment=/, '$1env:')
- .replace(/(^|,)service\.name=/, '$1service:')
- .replace(/(^|,)service\.version=/, '$1version:')
- .replaceAll('=', ':')
-}
-
-function parseSpaceSeparatedTags (tagString) {
- if (tagString && !tagString.includes(',')) {
- tagString = tagString.replaceAll(/\s+/g, ',')
- }
- return tagString
-}
-
-function maybeInt (number) {
- const parsed = Number.parseInt(number)
- return Number.isNaN(parsed) ? undefined : parsed
-}
-
-function maybeFloat (number) {
- const parsed = Number.parseFloat(number)
- return Number.isNaN(parsed) ? undefined : parsed
-}
-
-function nonNegInt (value, envVarName, allowZero = true) {
- if (value === undefined) return
- const parsed = Number.parseInt(value)
- if (Number.isNaN(parsed) || parsed < 0 || (parsed === 0 && !allowZero)) {
- log.warn('Invalid value %d for %s. Using default value.', parsed, envVarName)
- return
- }
- return parsed
-}
-
-function getAgentUrl (url, options) {
- if (url) return new URL(url)
-
- if (os.type() === 'Windows_NT') return
-
- if (
- !options.hostname &&
- !options.port &&
- !getEnv('DD_AGENT_HOST') &&
- !getEnv('DD_TRACE_AGENT_PORT') &&
- !isTrue(getEnv('DD_CIVISIBILITY_AGENTLESS_ENABLED')) &&
- fs.existsSync('/var/run/datadog/apm.socket')
- ) {
- return new URL('unix:///var/run/datadog/apm.socket')
- }
}
+/**
+ * @param {TracerOptions} [options]
+ */
function getConfig (options) {
if (!configInstance) {
configInstance = new Config(options)
diff --git a/packages/dd-trace/src/config/parsers.js b/packages/dd-trace/src/config/parsers.js
new file mode 100644
index 00000000000..fbaa78accdd
--- /dev/null
+++ b/packages/dd-trace/src/config/parsers.js
@@ -0,0 +1,254 @@
+'use strict'
+
+const fs = require('fs')
+
+const tagger = require('../tagger')
+
+let warnInvalidValue
+function setWarnInvalidValue (fn) {
+ warnInvalidValue = fn
+}
+
+const VALID_PROPAGATION_STYLES = new Set([
+ 'datadog', 'tracecontext', 'b3', 'b3 single header', 'b3multi', 'baggage', 'none',
+])
+
+function toCase (value, methodName) {
+ if (Array.isArray(value)) {
+ return value.map(item => {
+ return transformers[methodName](item)
+ })
+ }
+ return value[methodName]()
+}
+
+const transformers = {
+ setIntegerRangeSet (value) {
+ if (value == null) {
+ return
+ }
+ value = value.split(',')
+ const result = []
+
+ for (const val of value) {
+ if (val.includes('-')) {
+ const [start, end] = val.split('-').map(Number)
+ for (let i = start; i <= end; i++) {
+ result.push(i)
+ }
+ } else {
+ result.push(Number(val))
+ }
+ }
+ return result
+ },
+ toLowerCase (value) {
+ return toCase(value, 'toLowerCase')
+ },
+ toUpperCase (value) {
+ return toCase(value, 'toUpperCase')
+ },
+ toCamelCase (value) {
+ if (Array.isArray(value)) {
+ return value.map(item => {
+ return transformers.toCamelCase(item)
+ })
+ }
+ if (typeof value === 'object' && value !== null) {
+ const result = {}
+ for (const [key, innerValue] of Object.entries(value)) {
+ const camelCaseKey = key.replaceAll(/_(\w)/g, (_, letter) => letter.toUpperCase())
+ result[camelCaseKey] = transformers.toCamelCase(innerValue)
+ }
+ return result
+ }
+ return value
+ },
+ parseOtelTags (value, optionName) {
+ return parsers.MAP(value
+ ?.replace(/(^|,)deployment\.environment=/, '$1env:')
+ .replace(/(^|,)service\.name=/, '$1service:')
+ .replace(/(^|,)service\.version=/, '$1version:')
+ .replaceAll('=', ':'), optionName)
+ },
+ normalizeProfilingEnabled (configValue) {
+ if (configValue == null) {
+ return
+ }
+ if (configValue === 'true' || configValue === '1') {
+ return 'true'
+ }
+ if (configValue === 'false' || configValue === '0') {
+ return 'false'
+ }
+ const lowercased = String(configValue).toLowerCase()
+ if (lowercased !== configValue) {
+ return transformers.normalizeProfilingEnabled(lowercased)
+ }
+ return configValue
+ },
+ sampleRate (value, optionName, source) {
+ const number = Number(value)
+ if (Number.isNaN(number) || value === null) {
+ warnInvalidValue(value, optionName, source, 'Sample rate invalid')
+ return
+ }
+ const clamped = Math.min(Math.max(number, 0), 1)
+ if (clamped !== number) {
+ warnInvalidValue(value, optionName, source, 'Sample rate out of range between 0 and 1')
+ return clamped
+ }
+ return number
+ },
+ readFilePath (raw, optionName, source) {
+ const { stackTraceLimit } = Error
+ Error.stackTraceLimit = 0
+ try {
+ return fs.readFileSync(raw, 'utf8')
+ } catch (error) {
+ warnInvalidValue(raw, optionName, source, 'Error reading path', error)
+ } finally {
+ Error.stackTraceLimit = stackTraceLimit
+ }
+ },
+ /**
+ * Given a string of comma-separated paths, return the array of paths.
+ * If a blank path is provided a null is returned to signal that the feature is disabled.
+ * An empty array means the feature is enabled but that no rules need to be applied.
+ *
+ * @param {string | string[]} input
+ */
+ splitJSONPathRules (input) {
+ if (!input || input === '$') return
+ if (Array.isArray(input)) return input
+ if (input === 'all') return []
+ return input.split(',')
+ },
+ stripColonWhitespace (value) {
+ if (Array.isArray(value)) {
+ return value.map(item => {
+ return transformers.stripColonWhitespace(item)
+ })
+ }
+ return value.replaceAll(/\s*:\s*/g, ':')
+ },
+ validatePropagationStyles (value, optionName) {
+ value = transformers.toLowerCase(value)
+ for (const propagator of value) {
+ if (!VALID_PROPAGATION_STYLES.has(propagator)) {
+ warnInvalidValue(propagator, optionName, optionName, 'Invalid propagator')
+ return
+ }
+ }
+ return value
+ },
+}
+
+const telemetryTransformers = {
+ JSON (object) {
+ return (typeof object !== 'object' || object === null) ? object : JSON.stringify(object)
+ },
+ MAP (object) {
+ if (typeof object !== 'object' || object === null) {
+ return object
+ }
+ let result = ''
+ for (const [key, value] of Object.entries(object)) {
+ result += `${key}:${value},`
+ }
+ return result.slice(0, -1)
+ },
+ ARRAY (array) {
+ return Array.isArray(array) ? array.join(',') : array
+ },
+}
+
+const parsers = {
+ BOOLEAN (raw) {
+ if (raw === 'true' || raw === '1') {
+ return true
+ }
+ if (raw === 'false' || raw === '0') {
+ return false
+ }
+ const lowercased = raw.toLowerCase()
+ if (lowercased !== raw) {
+ return parsers.BOOLEAN(lowercased)
+ }
+ },
+ INT (raw) {
+ const parsed = Math.trunc(raw)
+ if (Number.isNaN(parsed)) {
+ return
+ }
+ return parsed
+ },
+ DECIMAL (raw) {
+ const parsed = Number(raw)
+ if (Number.isNaN(parsed)) {
+ return
+ }
+ return parsed
+ },
+ ARRAY (raw) {
+ // TODO: Make the parsing a helper that is reused everywhere.
+ const result = []
+ if (!raw) {
+ return result
+ }
+ let valueStart = 0
+ for (let i = 0; i < raw.length; i++) {
+ const char = raw[i]
+ if (char === ',') {
+ const value = raw.slice(valueStart, i).trim()
+ // Auto filter empty entries.
+ if (value.length > 0) {
+ result.push(value)
+ }
+ valueStart = i + 1
+ }
+ }
+ if (valueStart < raw.length) {
+ const value = raw.slice(valueStart).trim()
+ // Auto filter empty entries.
+ if (value.length > 0) {
+ result.push(value)
+ }
+ }
+ return result
+ },
+ MAP (raw, optionName) {
+ /** @type {Record} */
+ const entries = {}
+ if (!raw) {
+ return entries
+ }
+ // DD_TAGS is a special case. It may be a map of key-value pairs separated by spaces.
+ if (optionName === 'DD_TAGS' && !raw.includes(',')) {
+ raw = raw.replaceAll(/\s+/g, ',')
+ }
+ tagger.add(entries, raw)
+ return entries
+ },
+ JSON (raw) {
+ const { stackTraceLimit } = Error
+ Error.stackTraceLimit = 0
+ try {
+ return JSON.parse(raw)
+ } catch {
+ // ignore
+ } finally {
+ Error.stackTraceLimit = stackTraceLimit
+ }
+ },
+ STRING (raw) {
+ return raw
+ },
+}
+
+module.exports = {
+ parsers,
+ transformers,
+ telemetryTransformers,
+ setWarnInvalidValue,
+}
diff --git a/packages/dd-trace/src/config/remote_config.js b/packages/dd-trace/src/config/remote_config.js
index 54f4b3067b4..f981dd37fef 100644
--- a/packages/dd-trace/src/config/remote_config.js
+++ b/packages/dd-trace/src/config/remote_config.js
@@ -2,6 +2,7 @@
const RemoteConfigCapabilities = require('../remote_config/capabilities')
const log = require('../log')
+const tagger = require('../tagger')
module.exports = {
enable,
@@ -194,10 +195,66 @@ function enable (rc, config, onConfigUpdated) {
transaction.ack(item.path)
}
- // Get merged config and apply it
- const mergedLibConfig = rcClientLibConfigManager.getMergedLibConfig()
+ /** @type {import('../config').TracerOptions|null|RemoteConfigOptions} */
+ let mergedLibConfig = rcClientLibConfigManager.getMergedLibConfig()
+
+ if (mergedLibConfig) {
+ mergedLibConfig = transformRemoteConfigToLocalOption(mergedLibConfig)
+ }
+
config.setRemoteConfig(mergedLibConfig)
onConfigUpdated()
})
}
+
+/**
+ * @param {RemoteConfigOptions} libConfig
+ * @returns {import('../config').TracerOptions}
+ */
+function transformRemoteConfigToLocalOption (libConfig) {
+ const normalizedConfig = {}
+ for (const [name, value] of Object.entries(libConfig)) {
+ if (value !== null) {
+ normalizedConfig[optionLookupTable[name] ?? name] = transformers[name]?.(value) ?? value
+ }
+ }
+ return normalizedConfig
+}
+
+// This is intermediate solution until remote config is reworked to handle all known entries with proper names
+const optionLookupTable = {
+ dynamic_instrumentation_enabled: 'dynamicInstrumentation.enabled',
+ code_origin_enabled: 'codeOriginForSpans.enabled',
+ tracing_sampling_rate: 'sampleRate',
+ log_injection_enabled: 'logInjection',
+ tracing_enabled: 'tracing',
+ tracing_sampling_rules: 'samplingRules',
+ tracing_header_tags: 'headerTags',
+ tracing_tags: 'tags',
+}
+
+const transformers = {
+ tracing_sampling_rules (samplingRules) {
+ for (const rule of (samplingRules || [])) {
+ if (rule.tags) {
+ const reformattedTags = {}
+ for (const tag of rule.tags) {
+ reformattedTags[tag.key] = tag.value_glob
+ }
+ rule.tags = reformattedTags
+ }
+ }
+ return samplingRules
+ },
+ tracing_header_tags (headerTags) {
+ return headerTags?.map(tag => {
+ return tag.tag_name ? `${tag.header}:${tag.tag_name}` : tag.header
+ })
+ },
+ tracing_tags (tags) {
+ const normalizedTags = {}
+ tagger.add(normalizedTags, tags)
+ return normalizedTags
+ },
+}
diff --git a/packages/dd-trace/src/config/supported-configurations.json b/packages/dd-trace/src/config/supported-configurations.json
index 5bca7736dc9..e20da7b9703 100644
--- a/packages/dd-trace/src/config/supported-configurations.json
+++ b/packages/dd-trace/src/config/supported-configurations.json
@@ -13,9 +13,7 @@
"implementation": "A",
"type": "boolean",
"default": "false",
- "configurationNames": [
- "ciVisAgentlessLogSubmissionEnabled"
- ]
+ "internalPropertyName": "ciVisAgentlessLogSubmissionEnabled"
}
],
"DD_AGENTLESS_LOG_SUBMISSION_URL": [
@@ -104,9 +102,7 @@
"aliases": [
"DATADOG_API_KEY"
],
- "configurationNames": [
- "apiKey"
- ]
+ "internalPropertyName": "apiKey"
}
],
"DD_API_SECURITY_ENABLED": [
@@ -114,7 +110,8 @@
"implementation": "A",
"type": "boolean",
"configurationNames": [
- "appsec.apiSecurity.enabled"
+ "appsec.apiSecurity.enabled",
+ "experimental.appsec.apiSecurity.enabled"
],
"default": "true",
"aliases": [
@@ -127,7 +124,8 @@
"implementation": "A",
"type": "boolean",
"configurationNames": [
- "appsec.apiSecurity.endpointCollectionEnabled"
+ "appsec.apiSecurity.endpointCollectionEnabled",
+ "experimental.appsec.apiSecurity.endpointCollectionEnabled"
],
"default": "true"
}
@@ -137,7 +135,8 @@
"implementation": "A",
"type": "int",
"configurationNames": [
- "appsec.apiSecurity.endpointCollectionMessageLimit"
+ "appsec.apiSecurity.endpointCollectionMessageLimit",
+ "experimental.appsec.apiSecurity.endpointCollectionMessageLimit"
],
"default": "300"
}
@@ -146,9 +145,7 @@
{
"implementation": "A",
"type": "decimal",
- "configurationNames": [
- "appsec.apiSecurity.downstreamBodyAnalysisSampleRate"
- ],
+ "internalPropertyName": "appsec.apiSecurity.downstreamBodyAnalysisSampleRate",
"default": "0.5"
}
],
@@ -156,9 +153,7 @@
{
"implementation": "A",
"type": "int",
- "configurationNames": [
- "appsec.apiSecurity.maxDownstreamRequestBodyAnalysis"
- ],
+ "internalPropertyName": "appsec.apiSecurity.maxDownstreamRequestBodyAnalysis",
"default": "1"
}
],
@@ -167,9 +162,7 @@
"implementation": "A",
"type": "decimal",
"default": "30",
- "configurationNames": [
- "appsec.apiSecurity.sampleDelay"
- ]
+ "internalPropertyName": "appsec.apiSecurity.sampleDelay"
}
],
"DD_APM_FLUSH_DEADLINE_MILLISECONDS": [
@@ -194,7 +187,8 @@
"implementation": "E",
"type": "string",
"configurationNames": [
- "appsec.eventTracking.mode"
+ "appsec.eventTracking.mode",
+ "experimental.appsec.eventTracking.mode"
],
"default": "identification",
"aliases": [
@@ -207,7 +201,8 @@
"implementation": "A",
"type": "boolean",
"configurationNames": [
- "appsec.extendedHeadersCollection.enabled"
+ "appsec.extendedHeadersCollection.enabled",
+ "experimental.appsec.extendedHeadersCollection.enabled"
],
"default": "false",
"deprecated": true
@@ -218,7 +213,10 @@
"implementation": "C",
"type": "boolean",
"configurationNames": [
- "appsec.enabled"
+ "appsec.enabled",
+ "appsec",
+ "experimental.appsec.enabled",
+ "experimental.appsec"
],
"default": null
}
@@ -228,9 +226,11 @@
"implementation": "A",
"type": "string",
"configurationNames": [
- "appsec.blockedTemplateGraphql"
+ "appsec.blockedTemplateGraphql",
+ "experimental.appsec.blockedTemplateGraphql"
],
- "default": null
+ "default": null,
+ "transform": "readFilePath"
}
],
"DD_APPSEC_HEADER_COLLECTION_REDACTION_ENABLED": [
@@ -238,7 +238,8 @@
"implementation": "A",
"type": "boolean",
"configurationNames": [
- "appsec.extendedHeadersCollection.redaction"
+ "appsec.extendedHeadersCollection.redaction",
+ "experimental.appsec.extendedHeadersCollection.redaction"
],
"default": "true"
}
@@ -248,9 +249,11 @@
"implementation": "B",
"type": "string",
"configurationNames": [
- "appsec.blockedTemplateHtml"
+ "appsec.blockedTemplateHtml",
+ "experimental.appsec.blockedTemplateHtml"
],
- "default": null
+ "default": null,
+ "transform": "readFilePath"
}
],
"DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON": [
@@ -258,9 +261,11 @@
"implementation": "B",
"type": "string",
"configurationNames": [
- "appsec.blockedTemplateJson"
+ "appsec.blockedTemplateJson",
+ "experimental.appsec.blockedTemplateJson"
],
- "default": null
+ "default": null,
+ "transform": "readFilePath"
}
],
"DD_APPSEC_MAX_COLLECTED_HEADERS": [
@@ -268,7 +273,8 @@
"implementation": "A",
"type": "int",
"configurationNames": [
- "appsec.extendedHeadersCollection.maxHeaders"
+ "appsec.extendedHeadersCollection.maxHeaders",
+ "experimental.appsec.extendedHeadersCollection.maxHeaders"
],
"default": "50"
}
@@ -278,7 +284,11 @@
"implementation": "A",
"type": "int",
"configurationNames": [
- "appsec.stackTrace.maxStackTraces"
+ "appsec.stackTrace.maxStackTraces",
+ "experimental.appsec.stackTrace.maxStackTraces"
+ ],
+ "aliases": [
+ "DD_APPSEC_MAX_STACKTRACES"
],
"default": "2"
}
@@ -288,7 +298,11 @@
"implementation": "A",
"type": "int",
"configurationNames": [
- "appsec.stackTrace.maxDepth"
+ "appsec.stackTrace.maxDepth",
+ "experimental.appsec.stackTrace.maxDepth"
+ ],
+ "aliases": [
+ "DD_APPSEC_MAX_STACKTRACE_DEPTH"
],
"default": "32"
}
@@ -298,7 +312,8 @@
"implementation": "B",
"type": "string",
"configurationNames": [
- "appsec.obfuscatorKeyRegex"
+ "appsec.obfuscatorKeyRegex",
+ "experimental.appsec.obfuscatorKeyRegex"
],
"default": "(?i)pass|pw(?:or)?d|secret|(?:api|private|public|access)[_-]?key|token|consumer[_-]?(?:id|key|secret)|sign(?:ed|ature)|bearer|authorization|jsessionid|phpsessid|asp\\.net[_-]sessionid|sid|jwt"
}
@@ -308,7 +323,8 @@
"implementation": "G",
"type": "string",
"configurationNames": [
- "appsec.obfuscatorValueRegex"
+ "appsec.obfuscatorValueRegex",
+ "experimental.appsec.obfuscatorValueRegex"
],
"default": "(?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,})"
}
@@ -318,7 +334,8 @@
"implementation": "A",
"type": "boolean",
"configurationNames": [
- "appsec.rasp.bodyCollection"
+ "appsec.rasp.bodyCollection",
+ "experimental.appsec.rasp.bodyCollection"
],
"default": "false",
"deprecated": true
@@ -329,7 +346,8 @@
"implementation": "A",
"type": "boolean",
"configurationNames": [
- "appsec.rasp.enabled"
+ "appsec.rasp.enabled",
+ "experimental.appsec.rasp.enabled"
],
"default": "true"
}
@@ -339,7 +357,8 @@
"implementation": "B",
"type": "string",
"configurationNames": [
- "appsec.rules"
+ "appsec.rules",
+ "experimental.appsec.rules"
],
"default": null
}
@@ -349,9 +368,7 @@
"implementation": "B",
"type": "boolean",
"default": null,
- "configurationNames": [
- "appsec.sca.enabled"
- ]
+ "internalPropertyName": "appsec.sca.enabled"
}
],
"DD_APPSEC_STACK_TRACE_ENABLED": [
@@ -359,7 +376,8 @@
"implementation": "A",
"type": "boolean",
"configurationNames": [
- "appsec.stackTrace.enabled"
+ "appsec.stackTrace.enabled",
+ "experimental.appsec.stackTrace.enabled"
],
"default": "true"
}
@@ -369,7 +387,8 @@
"implementation": "A",
"type": "int",
"configurationNames": [
- "appsec.rateLimit"
+ "appsec.rateLimit",
+ "experimental.appsec.rateLimit"
],
"default": "100"
}
@@ -379,7 +398,8 @@
"implementation": "E",
"type": "int",
"configurationNames": [
- "appsec.wafTimeout"
+ "appsec.wafTimeout",
+ "experimental.appsec.wafTimeout"
],
"default": "5000"
}
@@ -389,9 +409,7 @@
"implementation": "A",
"type": "string",
"default": null,
- "configurationNames": [
- "appKey"
- ]
+ "internalPropertyName": "appKey"
}
],
"DD_AZURE_RESOURCE_GROUP": [
@@ -441,9 +459,7 @@
"implementation": "B",
"type": "boolean",
"default": "true",
- "configurationNames": [
- "isEarlyFlakeDetectionEnabled"
- ]
+ "internalPropertyName": "isEarlyFlakeDetectionEnabled"
}
],
"DD_CIVISIBILITY_ENABLED": [
@@ -458,9 +474,7 @@
"implementation": "A",
"type": "int",
"default": "5",
- "configurationNames": [
- "flakyTestRetriesCount"
- ]
+ "internalPropertyName": "flakyTestRetriesCount"
}
],
"DD_CIVISIBILITY_FLAKY_RETRY_ENABLED": [
@@ -468,9 +482,7 @@
"implementation": "A",
"type": "boolean",
"default": "true",
- "configurationNames": [
- "isFlakyTestRetriesEnabled"
- ]
+ "internalPropertyName": "isFlakyTestRetriesEnabled"
}
],
"DD_CIVISIBILITY_GIT_UNSHALLOW_ENABLED": [
@@ -485,9 +497,7 @@
"implementation": "A",
"type": "boolean",
"default": "true",
- "configurationNames": [
- "isGitUploadEnabled"
- ]
+ "internalPropertyName": "isGitUploadEnabled"
}
],
"DD_CIVISIBILITY_IMPACTED_TESTS_DETECTION_ENABLED": [
@@ -495,9 +505,7 @@
"implementation": "A",
"type": "boolean",
"default": "true",
- "configurationNames": [
- "isImpactedTestsEnabled"
- ]
+ "internalPropertyName": "isImpactedTestsEnabled"
}
],
"DD_CIVISIBILITY_ITR_ENABLED": [
@@ -505,9 +513,7 @@
"implementation": "A",
"type": "boolean",
"default": "true",
- "configurationNames": [
- "isIntelligentTestRunnerEnabled"
- ]
+ "internalPropertyName": "isIntelligentTestRunnerEnabled"
}
],
"DD_CIVISIBILITY_MANUAL_API_ENABLED": [
@@ -515,9 +521,7 @@
"implementation": "A",
"type": "boolean",
"default": "true",
- "configurationNames": [
- "isManualApiEnabled"
- ]
+ "internalPropertyName": "isManualApiEnabled"
}
],
"DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS": [
@@ -573,9 +577,7 @@
"implementation": "A",
"type": "boolean",
"default": "true",
- "configurationNames": [
- "crashtracking.enabled"
- ]
+ "internalPropertyName": "crashtracking.enabled"
}
],
"DD_CUSTOM_TRACE_ID": [
@@ -690,19 +692,19 @@
],
"DD_DYNAMIC_INSTRUMENTATION_UPLOAD_INTERVAL_SECONDS": [
{
- "implementation": "A",
- "type": "int",
+ "implementation": "C",
+ "type": "decimal",
"configurationNames": [
"dynamicInstrumentation.uploadIntervalSeconds"
],
- "default": "1"
+ "default": "1.0"
}
],
"DD_ENABLE_NX_SERVICE_NAME": [
{
- "implementation": "A",
- "type": "string",
- "default": null
+ "implementation": "B",
+ "type": "boolean",
+ "default": "false"
}
],
"DD_ENV": [
@@ -717,9 +719,12 @@
],
"DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED": [
{
- "implementation": "A",
+ "implementation": "B",
"type": "boolean",
- "default": "true"
+ "default": "false",
+ "configurationNames": [
+ "experimental.appsec.standalone.enabled"
+ ]
}
],
"DD_EXPERIMENTAL_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS": [
@@ -747,9 +752,7 @@
"implementation": "B",
"type": "boolean",
"default": "true",
- "configurationNames": [
- "propagateProcessTags.enabled"
- ]
+ "internalPropertyName": "propagateProcessTags.enabled"
}
],
"DD_EXPERIMENTAL_TEST_OPT_SETTINGS_CACHE": [
@@ -894,23 +897,20 @@
],
"DD_GRPC_CLIENT_ERROR_STATUSES": [
{
- "implementation": "A",
- "type": "array",
- "default": "1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16",
- "configurationNames": [
- "grpc.client.error.statuses"
- ],
- "handler": "GRPC_HANDLER"
+ "implementation": "C",
+ "type": "string",
+ "default": "1-16",
+ "internalPropertyName": "grpc.client.error.statuses",
+ "transform": "setIntegerRangeSet"
}
],
"DD_GRPC_SERVER_ERROR_STATUSES": [
{
- "implementation": "A",
- "type": "array",
- "default": "2,3,4,5,6,7,8,9,10,11,12,13,14,15,16",
- "configurationNames": [
- "grpc.server.error.statuses"
- ]
+ "implementation": "C",
+ "type": "string",
+ "default": "2-16",
+ "internalPropertyName": "grpc.server.error.statuses",
+ "transform": "setIntegerRangeSet"
}
],
"DD_HEAP_SNAPSHOT_COUNT": [
@@ -918,9 +918,7 @@
"implementation": "A",
"type": "int",
"default": "0",
- "configurationNames": [
- "heapSnapshot.count"
- ]
+ "internalPropertyName": "heapSnapshot.count"
}
],
"DD_HEAP_SNAPSHOT_DESTINATION": [
@@ -928,9 +926,7 @@
"implementation": "A",
"type": "string",
"default": "",
- "configurationNames": [
- "heapSnapshot.destination"
- ]
+ "internalPropertyName": "heapSnapshot.destination"
}
],
"DD_HEAP_SNAPSHOT_INTERVAL": [
@@ -938,9 +934,7 @@
"implementation": "A",
"type": "int",
"default": "3600",
- "configurationNames": [
- "heapSnapshot.interval"
- ]
+ "internalPropertyName": "heapSnapshot.interval"
}
],
"DD_IAST_DB_ROWS_TO_TAINT": [
@@ -948,7 +942,8 @@
"implementation": "A",
"type": "int",
"configurationNames": [
- "iast.dbRowsToTaint"
+ "iast.dbRowsToTaint",
+ "experimental.iast.dbRowsToTaint"
],
"default": "1"
}
@@ -958,7 +953,8 @@
"implementation": "A",
"type": "boolean",
"configurationNames": [
- "iast.deduplicationEnabled"
+ "iast.deduplicationEnabled",
+ "experimental.iast.deduplicationEnabled"
],
"default": "true"
}
@@ -968,7 +964,10 @@
"implementation": "B",
"type": "boolean",
"configurationNames": [
- "iast.enabled"
+ "iast.enabled",
+ "iast",
+ "experimental.iast.enabled",
+ "experimental.iast"
],
"default": "false"
}
@@ -978,7 +977,8 @@
"implementation": "A",
"type": "int",
"configurationNames": [
- "iast.maxConcurrentRequests"
+ "iast.maxConcurrentRequests",
+ "experimental.iast.maxConcurrentRequests"
],
"default": "2"
}
@@ -988,7 +988,8 @@
"implementation": "A",
"type": "int",
"configurationNames": [
- "iast.maxContextOperations"
+ "iast.maxContextOperations",
+ "experimental.iast.maxContextOperations"
],
"default": "2"
}
@@ -998,7 +999,8 @@
"implementation": "A",
"type": "boolean",
"configurationNames": [
- "iast.redactionEnabled"
+ "iast.redactionEnabled",
+ "experimental.iast.redactionEnabled"
],
"default": "true"
}
@@ -1008,7 +1010,8 @@
"implementation": "A",
"type": "string",
"configurationNames": [
- "iast.redactionNamePattern"
+ "iast.redactionNamePattern",
+ "experimental.iast.redactionNamePattern"
],
"default": "(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?|access_?|secret_?)key(?:_?id)?|token|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?|(?:sur|last)name|user(?:name)?|address|e?mail)"
}
@@ -1018,7 +1021,8 @@
"implementation": "A",
"type": "string",
"configurationNames": [
- "iast.redactionValuePattern"
+ "iast.redactionValuePattern",
+ "experimental.iast.redactionValuePattern"
],
"default": "(?:bearer\\s+[a-z0-9\\._\\-]+|glpat-[\\w\\-]{20}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L][\\w=\\-]+\\.ey[I-L][\\w=\\-]+(?:\\.[\\w.+/=\\-]+)?|(?:[\\-]{5}BEGIN[a-z\\s]+PRIVATE\\sKEY[\\-]{5}[^\\-]+[\\-]{5}END[a-z\\s]+PRIVATE\\sKEY[\\-]{5}|ssh-rsa\\s*[a-z0-9/\\.+]{100,})|[\\w\\.-]+@[a-zA-Z\\d\\.-]+\\.[a-zA-Z]{2,})"
}
@@ -1028,9 +1032,12 @@
"implementation": "A",
"type": "int",
"configurationNames": [
- "iast.requestSampling"
+ "iast.requestSampling",
+ "experimental.iast.requestSampling"
],
- "default": "30"
+ "default": "30",
+ "allowed": "100|[1-9]?\\d",
+ "transform": "iastRequestSampling"
}
],
"DD_IAST_SECURITY_CONTROLS_CONFIGURATION": [
@@ -1038,7 +1045,8 @@
"implementation": "B",
"type": "string",
"configurationNames": [
- "iast.securityControlsConfiguration"
+ "iast.securityControlsConfiguration",
+ "experimental.iast.securityControlsConfiguration"
],
"default": null
}
@@ -1048,7 +1056,8 @@
"implementation": "B",
"type": "boolean",
"configurationNames": [
- "iast.stackTrace.enabled"
+ "iast.stackTrace.enabled",
+ "experimental.iast.stackTrace.enabled"
],
"default": "true"
}
@@ -1058,19 +1067,18 @@
"implementation": "B",
"type": "string",
"configurationNames": [
- "iast.telemetryVerbosity"
+ "iast.telemetryVerbosity",
+ "experimental.iast.telemetryVerbosity"
],
"default": "INFORMATION"
}
],
"DD_INJECTION_ENABLED": [
{
- "implementation": "A",
- "type": "array",
- "default": "",
- "configurationNames": [
- "injectionEnabled"
- ]
+ "implementation": "C",
+ "type": "string",
+ "default": null,
+ "internalPropertyName": "injectionEnabled"
}
],
"DD_INJECT_FORCE": [
@@ -1078,9 +1086,7 @@
"implementation": "A",
"type": "boolean",
"default": "false",
- "configurationNames": [
- "injectForce"
- ]
+ "internalPropertyName": "injectForce"
}
],
"DD_INSTRUMENTATION_CONFIG_ID": [
@@ -1088,9 +1094,7 @@
"implementation": "A",
"type": "string",
"default": null,
- "configurationNames": [
- "instrumentation_config_id"
- ]
+ "internalPropertyName": "instrumentation_config_id"
}
],
"DD_INSTRUMENTATION_INSTALL_ID": [
@@ -1098,9 +1102,7 @@
"implementation": "A",
"type": "string",
"default": null,
- "configurationNames": [
- "installSignature.id"
- ]
+ "internalPropertyName": "installSignature.id"
}
],
"DD_INSTRUMENTATION_INSTALL_TIME": [
@@ -1108,9 +1110,7 @@
"implementation": "A",
"type": "string",
"default": null,
- "configurationNames": [
- "installSignature.time"
- ]
+ "internalPropertyName": "installSignature.time"
}
],
"DD_INSTRUMENTATION_INSTALL_TYPE": [
@@ -1118,9 +1118,7 @@
"implementation": "A",
"type": "string",
"default": null,
- "configurationNames": [
- "installSignature.type"
- ]
+ "internalPropertyName": "installSignature.type"
}
],
"DD_INSTRUMENTATION_TELEMETRY_ENABLED": [
@@ -1131,9 +1129,7 @@
"aliases": [
"DD_TRACE_TELEMETRY_ENABLED"
],
- "configurationNames": [
- "telemetry.enabled"
- ]
+ "internalPropertyName": "telemetry.enabled"
}
],
"DD_INTERNAL_PROFILING_LONG_LIVED_THRESHOLD": [
@@ -1141,9 +1137,7 @@
"implementation": "A",
"type": "int",
"default": "30000",
- "configurationNames": [
- "profiling.longLivedThreshold"
- ]
+ "internalPropertyName": "profiling.longLivedThreshold"
}
],
"DD_INTERNAL_PROFILING_TIMELINE_SAMPLING_ENABLED": [
@@ -1165,9 +1159,7 @@
"implementation": "A",
"type": "int",
"default": "128",
- "configurationNames": [
- "langchain.spanCharLimit"
- ]
+ "internalPropertyName": "langchain.spanCharLimit"
}
],
"DD_LANGCHAIN_SPAN_PROMPT_COMPLETION_SAMPLE_RATE": [
@@ -1175,9 +1167,7 @@
"implementation": "A",
"type": "decimal",
"default": "1",
- "configurationNames": [
- "langchain.spanPromptCompletionSampleRate"
- ]
+ "internalPropertyName": "langchain.spanPromptCompletionSampleRate"
}
],
"DD_LLMOBS_AGENTLESS_ENABLED": [
@@ -1195,9 +1185,7 @@
"implementation": "A",
"type": "boolean",
"default": "false",
- "configurationNames": [
- "llmobs.enabled"
- ]
+ "internalPropertyName": "llmobs.enabled"
}
],
"DD_LLMOBS_ML_APP": [
@@ -1225,16 +1213,22 @@
"implementation": "A",
"type": "boolean",
"default": "false",
- "configurationNames": [
- "otelLogsEnabled"
- ]
+ "internalPropertyName": "otelLogsEnabled"
}
],
- "DD_LOG_LEVEL": [
+ "DD_TRACE_LOG_LEVEL": [
{
- "implementation": "B",
+ "implementation": "C",
"type": "string",
- "default": null
+ "default": "debug",
+ "configurationNames": [
+ "logLevel"
+ ],
+ "aliases": [
+ "DD_LOG_LEVEL",
+ "OTEL_LOG_LEVEL"
+ ],
+ "allowed": "debug|info|warn|error"
}
],
"DD_METRICS_OTEL_ENABLED": [
@@ -1242,9 +1236,7 @@
"implementation": "A",
"type": "boolean",
"default": "false",
- "configurationNames": [
- "otelMetricsEnabled"
- ]
+ "internalPropertyName": "otelMetricsEnabled"
}
],
"DD_MINI_AGENT_PATH": [
@@ -1269,9 +1261,7 @@
"implementation": "A",
"type": "int",
"default": "128",
- "configurationNames": [
- "openai.spanCharLimit"
- ]
+ "internalPropertyName": "openai.spanCharLimit"
}
],
"DD_PIPELINE_EXECUTION_ID": [
@@ -1346,38 +1336,30 @@
],
"DD_PROFILING_DEBUG_UPLOAD_COMPRESSION": [
{
- "implementation": "A",
+ "implementation": "B",
"type": "string",
- "default": "zstd"
+ "default": "on",
+ "allowed": "on|off|(gzip|zstd)(-[1-9][0-9]?)?",
+ "transform": "toLowerCase"
}
],
"DD_PROFILING_ENABLED": [
{
- "implementation": "A",
- "type": "boolean",
+ "implementation": "B",
+ "type": "string",
+ "internalPropertyName": "profiling.enabled",
"configurationNames": [
- "profiling.enabled"
+ "profiling"
],
+ "allowed": "false|true|auto|1|0",
+ "transform": "normalizeProfilingEnabled",
"default": "false",
+ "__TODO__": "The alias is deprecated and should log. This needs an re-implementation.",
"aliases": [
"DD_EXPERIMENTAL_PROFILING_ENABLED"
]
}
],
- "DD_EXPERIMENTAL_PROFILING_ENABLED": [
- {
- "implementation": "A",
- "type": "boolean",
- "configurationNames": [
- "profiling.enabled"
- ],
- "default": "false",
- "aliases": [
- "DD_PROFILING_ENABLED"
- ],
- "deprecated": true
- }
- ],
"DD_PROFILING_ENDPOINT_COLLECTION_ENABLED": [
{
"implementation": "A",
@@ -1401,8 +1383,8 @@
],
"DD_PROFILING_EXPERIMENTAL_OOM_EXPORT_STRATEGIES": [
{
- "implementation": "A",
- "type": "string",
+ "implementation": "B",
+ "type": "array",
"default": "process"
}
],
@@ -1429,19 +1411,16 @@
],
"DD_PROFILING_EXPORTERS": [
{
- "implementation": "A",
- "type": "string",
- "default": "agent",
- "configurationNames": [
- "profiling.exporters"
- ]
+ "implementation": "B",
+ "type": "array",
+ "default": "agent"
}
],
"DD_PROFILING_HEAP_ENABLED": [
{
- "implementation": "A",
+ "implementation": "B",
"type": "boolean",
- "default": "false"
+ "default": null
}
],
"DD_PROFILING_HEAP_SAMPLING_INTERVAL": [
@@ -1460,8 +1439,8 @@
],
"DD_PROFILING_PROFILERS": [
{
- "implementation": "A",
- "type": "string",
+ "implementation": "B",
+ "type": "array",
"default": "space,wall"
}
],
@@ -1469,10 +1448,7 @@
{
"implementation": "A",
"type": "boolean",
- "default": "true",
- "configurationNames": [
- "profiling.sourceMap"
- ]
+ "default": "true"
}
],
"DD_PROFILING_TIMELINE_ENABLED": [
@@ -1519,9 +1495,9 @@
],
"DD_PROFILING_WALLTIME_ENABLED": [
{
- "implementation": "B",
+ "implementation": "A",
"type": "boolean",
- "default": "true"
+ "default": null
}
],
"DD_REMOTE_CONFIGURATION_ENABLED": [
@@ -1532,9 +1508,7 @@
"aliases": [
"DD_REMOTE_CONFIG_ENABLED"
],
- "configurationNames": [
- "remoteConfig.enabled"
- ]
+ "internalPropertyName": "remoteConfig.enabled"
}
],
"DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS": [
@@ -1552,7 +1526,8 @@
"implementation": "A",
"type": "boolean",
"configurationNames": [
- "runtimeMetrics.enabled"
+ "runtimeMetrics.enabled",
+ "runtimeMetrics"
],
"default": "false"
}
@@ -1597,6 +1572,14 @@
]
}
],
+ "DD_ROOT_JS_SESSION_ID": [
+ {
+ "implementation": "A",
+ "type": "string",
+ "default": null,
+ "internal": true
+ }
+ ],
"DD_TRACE_EXPERIMENTAL_RUNTIME_ID_ENABLED": [
{
"implementation": "B",
@@ -1617,8 +1600,10 @@
],
"default": null,
"aliases": [
- "DD_SERVICE_NAME"
- ]
+ "DD_SERVICE_NAME",
+ "OTEL_SERVICE_NAME"
+ ],
+ "allowed": ".+"
}
],
"DD_SERVICE_MAPPING": [
@@ -1643,20 +1628,21 @@
],
"DD_SPAN_SAMPLING_RULES": [
{
- "implementation": "C",
- "type": "array",
+ "implementation": "D",
+ "type": "json",
"configurationNames": [
- "spanSamplingRules",
- "sampler.spanSamplingRules"
+ "spanSamplingRules"
],
- "default": null
+ "default": null,
+ "transform": "toCamelCase"
}
],
"DD_SPAN_SAMPLING_RULES_FILE": [
{
- "implementation": "B",
+ "implementation": "A",
"type": "string",
- "default": ""
+ "default": null,
+ "transform": "readFilePath"
}
],
"DD_TAGS": [
@@ -1674,9 +1660,7 @@
"implementation": "A",
"type": "boolean",
"default": "false",
- "configurationNames": [
- "telemetry.debug"
- ]
+ "internalPropertyName": "telemetry.debug"
}
],
"DD_TELEMETRY_DEPENDENCY_COLLECTION_ENABLED": [
@@ -1684,9 +1668,7 @@
"implementation": "A",
"type": "boolean",
"default": "true",
- "configurationNames": [
- "telemetry.dependencyCollection"
- ]
+ "internalPropertyName": "telemetry.dependencyCollection"
}
],
"DD_TELEMETRY_FORWARDER_PATH": [
@@ -1701,9 +1683,7 @@
"implementation": "B",
"type": "decimal",
"default": "60.0",
- "configurationNames": [
- "telemetry.heartbeatInterval"
- ]
+ "internalPropertyName": "telemetry.heartbeatInterval"
}
],
"DD_TELEMETRY_LOG_COLLECTION_ENABLED": [
@@ -1711,9 +1691,7 @@
"implementation": "A",
"type": "boolean",
"default": "true",
- "configurationNames": [
- "telemetry.logCollection"
- ]
+ "internalPropertyName": "telemetry.logCollection"
}
],
"DD_TELEMETRY_METRICS_ENABLED": [
@@ -1721,9 +1699,7 @@
"implementation": "A",
"type": "boolean",
"default": "true",
- "configurationNames": [
- "telemetry.metrics"
- ]
+ "internalPropertyName": "telemetry.metrics"
}
],
"DD_TEST_FAILED_TEST_REPLAY_ENABLED": [
@@ -1731,9 +1707,7 @@
"implementation": "A",
"type": "boolean",
"default": "true",
- "configurationNames": [
- "isTestDynamicInstrumentationEnabled"
- ]
+ "internalPropertyName": "isTestDynamicInstrumentationEnabled"
}
],
"DD_TEST_FLEET_CONFIG_PATH": [
@@ -1755,9 +1729,7 @@
"implementation": "C",
"type": "int",
"default": "20",
- "configurationNames": [
- "testManagementAttemptToFixRetries"
- ]
+ "internalPropertyName": "testManagementAttemptToFixRetries"
}
],
"DD_TEST_MANAGEMENT_ENABLED": [
@@ -1765,9 +1737,7 @@
"implementation": "A",
"type": "boolean",
"default": "true",
- "configurationNames": [
- "isTestManagementEnabled"
- ]
+ "internalPropertyName": "isTestManagementEnabled"
}
],
"DD_TEST_TIA_KEEP_COV_CONFIG": [
@@ -1775,9 +1745,7 @@
"implementation": "A",
"type": "boolean",
"default": "false",
- "configurationNames": [
- "isKeepingCoverageConfiguration"
- ]
+ "internalPropertyName": "isKeepingCoverageConfiguration"
}
],
"DD_TEST_SESSION_NAME": [
@@ -1785,9 +1753,7 @@
"implementation": "A",
"type": "string",
"default": null,
- "configurationNames": [
- "ciVisibilityTestSessionName"
- ]
+ "internalPropertyName": "ciVisibilityTestSessionName"
}
],
"DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED": [
@@ -1939,9 +1905,7 @@
"implementation": "A",
"type": "boolean",
"default": "true",
- "configurationNames": [
- "trace.aws.addSpanPointers"
- ]
+ "internalPropertyName": "trace.aws.addSpanPointers"
}
],
"DD_TRACE_AWS_SDK_AWS_BATCH_PROPAGATION_ENABLED": [
@@ -2327,7 +2291,8 @@
"configurationNames": [
"clientIpHeader"
],
- "default": null
+ "default": null,
+ "transform": "toLowerCase"
}
],
"DD_TRACE_CLOUD_PAYLOAD_TAGGING_MAX_DEPTH": [
@@ -2337,27 +2302,30 @@
"configurationNames": [
"cloudPayloadTagging.maxDepth"
],
- "default": "10"
+ "default": "10",
+ "allowed": "\\d+"
}
],
"DD_TRACE_CLOUD_REQUEST_PAYLOAD_TAGGING": [
{
- "implementation": "A",
- "type": "array",
+ "implementation": "B",
+ "type": "string",
"configurationNames": [
"cloudPayloadTagging.request"
],
- "default": null
+ "default": null,
+ "transform": "splitJSONPathRules"
}
],
"DD_TRACE_CLOUD_RESPONSE_PAYLOAD_TAGGING": [
{
- "implementation": "A",
- "type": "array",
+ "implementation": "B",
+ "type": "string",
"configurationNames": [
"cloudPayloadTagging.response"
],
- "default": null
+ "default": null,
+ "transform": "splitJSONPathRules"
}
],
"DD_TRACE_COLLECTIONS_ENABLED": [
@@ -2470,9 +2438,7 @@
"implementation": "A",
"type": "string",
"default": null,
- "configurationNames": [
- "trace.dynamoDb.tablePrimaryKeys"
- ]
+ "internalPropertyName": "trace.dynamoDb.tablePrimaryKeys"
}
],
"DD_TRACE_ELASTICSEARCH_ENABLED": [
@@ -2501,8 +2467,9 @@
"implementation": "A",
"type": "boolean",
"default": "true",
- "configurationNames": [
- "traceEnabled"
+ "internalPropertyName": "tracing",
+ "aliases": [
+ "DD_TRACING_ENABLED"
]
}
],
@@ -2620,10 +2587,7 @@
{
"implementation": "A",
"type": "boolean",
- "default": "true",
- "configurationNames": [
- "isGCPPubSubPushSubscriptionEnabled"
- ]
+ "default": "true"
}
],
"DD_TRACE_GENERIC_POOL_ENABLED": [
@@ -2638,9 +2602,7 @@
"implementation": "A",
"type": "boolean",
"default": "true",
- "configurationNames": [
- "gitMetadataEnabled"
- ]
+ "internalPropertyName": "gitMetadataEnabled"
}
],
"DD_TRACE_GOOGLE_CLOUD_PUBSUB_ENABLED": [
@@ -2683,9 +2645,7 @@
"implementation": "A",
"type": "array",
"default": "",
- "configurationNames": [
- "graphqlErrorExtensions"
- ]
+ "internalPropertyName": "graphqlErrorExtensions"
}
],
"DD_TRACE_GRAPHQL_TAG_ENABLED": [
@@ -2772,7 +2732,8 @@
"default": "",
"configurationNames": [
"headerTags"
- ]
+ ],
+ "transform": "stripColonWhitespace"
}
],
"DD_TRACE_HONO_ENABLED": [
@@ -3040,13 +3001,6 @@
"default": "true"
}
],
- "DD_TRACE_LOG_LEVEL": [
- {
- "implementation": "C",
- "type": "string",
- "default": "debug"
- }
- ],
"DD_TRACE_LOOPBACK_ENABLED": [
{
"implementation": "A",
@@ -3066,9 +3020,7 @@
"implementation": "A",
"type": "boolean",
"default": "false",
- "configurationNames": [
- "memcachedCommandEnabled"
- ]
+ "internalPropertyName": "memcachedCommandEnabled"
}
],
"DD_TRACE_MEMCACHED_ENABLED": [
@@ -3184,9 +3136,7 @@
"implementation": "A",
"type": "boolean",
"default": "false",
- "configurationNames": [
- "trace.nativeSpanEvents"
- ]
+ "internalPropertyName": "trace.nativeSpanEvents"
}
],
"DD_TRACE_NET_ENABLED": [
@@ -3236,9 +3186,7 @@
"implementation": "F",
"type": "string",
"default": "(?: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,}",
- "configurationNames": [
- "queryStringObfuscation"
- ]
+ "internalPropertyName": "queryStringObfuscation"
}
],
"DD_TRACE_OPENAI_ENABLED": [
@@ -3429,10 +3377,10 @@
{
"implementation": "B",
"type": "string",
+ "allowed": "continue|restart|ignore",
+ "transform": "toLowerCase",
"default": "continue",
- "configurationNames": [
- "tracePropagationBehaviorExtract"
- ]
+ "internalPropertyName": "tracePropagationBehaviorExtract"
}
],
"DD_TRACE_PROPAGATION_EXTRACT_FIRST": [
@@ -3440,19 +3388,18 @@
"implementation": "A",
"type": "boolean",
"default": "false",
- "configurationNames": [
- "tracePropagationExtractFirst"
- ]
+ "internalPropertyName": "tracePropagationExtractFirst"
}
],
"DD_TRACE_PROPAGATION_STYLE": [
{
"implementation": "D",
"type": "array",
- "configurationNames": [
- "tracePropagationStyle"
- ],
- "default": "datadog,tracecontext,baggage"
+ "default": "datadog,tracecontext,baggage",
+ "transform": "validatePropagationStyles",
+ "aliases": [
+ "OTEL_PROPAGATORS"
+ ]
}
],
"DD_TRACE_PROPAGATION_STYLE_EXTRACT": [
@@ -3462,7 +3409,8 @@
"configurationNames": [
"tracePropagationStyle.extract"
],
- "default": "datadog, tracecontext, baggage"
+ "default": "datadog, tracecontext, baggage",
+ "transform": "toLowerCase"
}
],
"DD_TRACE_PROPAGATION_STYLE_INJECT": [
@@ -3472,7 +3420,8 @@
"configurationNames": [
"tracePropagationStyle.inject"
],
- "default": "datadog, tracecontext, baggage"
+ "default": "datadog, tracecontext, baggage",
+ "transform": "toLowerCase"
}
],
"DD_TRACE_PROTOBUFJS_ENABLED": [
@@ -3501,8 +3450,8 @@
"implementation": "A",
"type": "int",
"configurationNames": [
- "ingestion.rateLimit",
- "sampler.rateLimit"
+ "rateLimit",
+ "ingestion.rateLimit"
],
"default": "100"
}
@@ -3567,9 +3516,7 @@
"implementation": "A",
"type": "boolean",
"default": "false",
- "configurationNames": [
- "resourceRenamingEnabled"
- ]
+ "internalPropertyName": "resourceRenamingEnabled"
}
],
"DD_TRACE_RESTIFY_ENABLED": [
@@ -3598,20 +3545,22 @@
"implementation": "B",
"type": "decimal",
"configurationNames": [
+ "sampleRate",
"ingestion.sampleRate"
],
- "default": null
+ "default": null,
+ "transform": "sampleRate"
}
],
"DD_TRACE_SAMPLING_RULES": [
{
- "implementation": "A",
- "type": "array",
+ "implementation": "E",
+ "type": "json",
"configurationNames": [
- "samplingRules",
- "sampler.rules"
+ "samplingRules"
],
- "default": ""
+ "default": "[]",
+ "transform": "toCamelCase"
}
],
"DD_TRACE_SCOPE": [
@@ -3619,9 +3568,7 @@
"implementation": "A",
"type": "string",
"default": null,
- "configurationNames": [
- "scope"
- ]
+ "internalPropertyName": "scope"
}
],
"DD_TRACE_SELENIUM_ENABLED": [
@@ -3663,6 +3610,8 @@
{
"implementation": "B",
"type": "string",
+ "allowed": "v0|v1",
+ "transform": "toLowerCase",
"configurationNames": [
"spanAttributeSchema"
],
@@ -3673,9 +3622,7 @@
{
"implementation": "A",
"type": "int",
- "configurationNames": [
- "spanLeakDebug"
- ],
+ "internalPropertyName": "spanLeakDebug",
"default": "0"
}
],
@@ -3688,20 +3635,21 @@
],
"DD_TRACE_STARTUP_LOGS": [
{
- "implementation": "D",
+ "implementation": "C",
"type": "boolean",
"configurationNames": [
"startupLogs"
],
- "default": "false"
+ "default": "true"
}
],
"DD_TRACE_STATS_COMPUTATION_ENABLED": [
{
"implementation": "A",
"type": "boolean",
+ "internalPropertyName": "stats.enabled",
"configurationNames": [
- "stats.enabled"
+ "stats"
],
"default": "false"
}
@@ -3828,19 +3776,7 @@
"implementation": "A",
"type": "int",
"default": "512",
- "configurationNames": [
- "tagsHeaderMaxLength"
- ]
- }
- ],
- "DD_TRACING_ENABLED": [
- {
- "implementation": "A",
- "type": "boolean",
- "default": "true",
- "configurationNames": [
- "tracing"
- ]
+ "internalPropertyName": "tagsHeaderMaxLength"
}
],
"DD_VERSION": [
@@ -3858,9 +3794,7 @@
"implementation": "A",
"type": "int",
"default": "128",
- "configurationNames": [
- "vertexai.spanCharLimit"
- ]
+ "internalPropertyName": "vertexai.spanCharLimit"
}
],
"DD_VERTEXAI_SPAN_PROMPT_COMPLETION_SAMPLE_RATE": [
@@ -3868,9 +3802,7 @@
"implementation": "A",
"type": "decimal",
"default": "1",
- "configurationNames": [
- "vertexai.spanPromptCompletionSampleRate"
- ]
+ "internalPropertyName": "vertexai.spanPromptCompletionSampleRate"
}
],
"DD_VITEST_WORKER": [
@@ -3884,50 +3816,42 @@
{
"implementation": "A",
"type": "int",
- "configurationNames": [
- "otelMaxExportBatchSize"
- ],
- "default": "512"
+ "internalPropertyName": "otelMaxExportBatchSize",
+ "default": "512",
+ "allowed": "[1-9]\\d*"
}
],
"OTEL_BSP_MAX_QUEUE_SIZE": [
{
"implementation": "A",
"type": "int",
- "configurationNames": [
- "otelMaxQueueSize"
- ],
- "default": "2048"
+ "internalPropertyName": "otelMaxQueueSize",
+ "default": "2048",
+ "allowed": "[1-9]\\d*"
}
],
"OTEL_BSP_SCHEDULE_DELAY": [
{
"implementation": "A",
"type": "int",
- "configurationNames": [
- "otelBatchTimeout"
- ],
- "default": "5000"
+ "internalPropertyName": "otelBatchTimeout",
+ "default": "5000",
+ "allowed": "[1-9]\\d*"
}
],
"OTEL_EXPORTER_OTLP_ENDPOINT": [
{
"implementation": "A",
"type": "string",
- "default": null,
- "configurationNames": [
- "otelUrl"
- ]
+ "default": null
}
],
"OTEL_EXPORTER_OTLP_HEADERS": [
{
- "implementation": "B",
- "type": "map",
+ "implementation": "C",
+ "type": "string",
"default": null,
- "configurationNames": [
- "otelHeaders"
- ]
+ "internalPropertyName": "otelHeaders"
}
],
"OTEL_EXPORTER_OTLP_LOGS_ENDPOINT": [
@@ -3935,18 +3859,20 @@
"implementation": "A",
"type": "string",
"default": null,
- "configurationNames": [
- "otelLogsUrl"
+ "internalPropertyName": "otelLogsUrl",
+ "aliases": [
+ "OTEL_EXPORTER_OTLP_ENDPOINT"
]
}
],
"OTEL_EXPORTER_OTLP_LOGS_HEADERS": [
{
- "implementation": "B",
- "type": "map",
+ "implementation": "A",
+ "type": "string",
"default": null,
- "configurationNames": [
- "otelLogsHeaders"
+ "internalPropertyName": "otelLogsHeaders",
+ "aliases": [
+ "OTEL_EXPORTER_OTLP_HEADERS"
]
}
],
@@ -3955,8 +3881,9 @@
"implementation": "D",
"type": "string",
"default": "http/protobuf",
- "configurationNames": [
- "otelLogsProtocol"
+ "internalPropertyName": "otelLogsProtocol",
+ "aliases": [
+ "OTEL_EXPORTER_OTLP_PROTOCOL"
]
}
],
@@ -3964,10 +3891,12 @@
{
"implementation": "A",
"type": "int",
- "configurationNames": [
- "otelLogsTimeout"
- ],
- "default": "10000"
+ "internalPropertyName": "otelLogsTimeout",
+ "default": "10000",
+ "allowed": "[1-9]\\d*",
+ "aliases": [
+ "OTEL_EXPORTER_OTLP_TIMEOUT"
+ ]
}
],
"OTEL_EXPORTER_OTLP_METRICS_ENDPOINT": [
@@ -3975,18 +3904,20 @@
"implementation": "A",
"type": "string",
"default": null,
- "configurationNames": [
- "otelMetricsUrl"
+ "internalPropertyName": "otelMetricsUrl",
+ "aliases": [
+ "OTEL_EXPORTER_OTLP_ENDPOINT"
]
}
],
"OTEL_EXPORTER_OTLP_METRICS_HEADERS": [
{
- "implementation": "A",
- "type": "map",
+ "implementation": "B",
+ "type": "string",
"default": null,
- "configurationNames": [
- "otelMetricsHeaders"
+ "internalPropertyName": "otelMetricsHeaders",
+ "aliases": [
+ "OTEL_EXPORTER_OTLP_HEADERS"
]
}
],
@@ -3995,8 +3926,9 @@
"implementation": "B",
"type": "string",
"default": "http/protobuf",
- "configurationNames": [
- "otelMetricsProtocol"
+ "internalPropertyName": "otelMetricsProtocol",
+ "aliases": [
+ "OTEL_EXPORTER_OTLP_PROTOCOL"
]
}
],
@@ -4004,9 +3936,9 @@
{
"implementation": "A",
"type": "string",
- "configurationNames": [
- "otelMetricsTemporalityPreference"
- ],
+ "allowed": "Delta|Cumulative|LowMemory",
+ "transform": "toUpperCase",
+ "internalPropertyName": "otelMetricsTemporalityPreference",
"default": "delta"
}
],
@@ -4014,10 +3946,12 @@
{
"implementation": "B",
"type": "int",
- "configurationNames": [
- "otelMetricsTimeout"
- ],
- "default": "10000"
+ "allowed": "[1-9]\\d*",
+ "internalPropertyName": "otelMetricsTimeout",
+ "default": "10000",
+ "aliases": [
+ "OTEL_EXPORTER_OTLP_TIMEOUT"
+ ]
}
],
"OTEL_EXPORTER_OTLP_PROTOCOL": [
@@ -4025,18 +3959,15 @@
"implementation": "A",
"type": "string",
"default": "http/protobuf",
- "configurationNames": [
- "otelProtocol"
- ]
+ "internalPropertyName": "otelProtocol"
}
],
"OTEL_EXPORTER_OTLP_TIMEOUT": [
{
"implementation": "A",
"type": "int",
- "configurationNames": [
- "otelTimeout"
- ],
+ "allowed": "[1-9]\\d*",
+ "internalPropertyName": "otelTimeout",
"default": "10000"
}
],
@@ -4044,30 +3975,34 @@
{
"implementation": "A",
"type": "string",
- "default": null
+ "default": null,
+ "allowed": "none|otlp",
+ "transform": "toLowerCase"
}
],
"OTEL_LOG_LEVEL": [
{
"implementation": "C",
"type": "string",
- "default": null
+ "default": null,
+ "allowed": "debug|info|warn|error"
}
],
"OTEL_METRICS_EXPORTER": [
{
"implementation": "C",
"type": "string",
- "default": null
+ "default": null,
+ "allowed": "none|otlp",
+ "transform": "toLowerCase"
}
],
"OTEL_METRIC_EXPORT_INTERVAL": [
{
"implementation": "A",
"type": "int",
- "configurationNames": [
- "otelMetricsExportInterval"
- ],
+ "allowed": "[1-9]\\d*",
+ "internalPropertyName": "otelMetricsExportInterval",
"default": "10000"
}
],
@@ -4075,27 +4010,17 @@
{
"implementation": "A",
"type": "int",
- "configurationNames": [
- "otelMetricsExportTimeout"
- ],
+ "allowed": "[1-9]\\d*",
+ "internalPropertyName": "otelMetricsExportTimeout",
"default": "7500"
}
],
- "OTEL_PROPAGATORS": [
- {
- "implementation": "A",
- "type": "array",
- "default": "",
- "configurationNames": [
- "tracePropagationStyle.otelPropagators"
- ]
- }
- ],
"OTEL_RESOURCE_ATTRIBUTES": [
{
"implementation": "B",
"type": "string",
- "default": ""
+ "default": "",
+ "transform": "parseOtelTags"
}
],
"OTEL_SDK_DISABLED": [
@@ -4105,38 +4030,30 @@
"default": "true"
}
],
- "OTEL_SERVICE_NAME": [
- {
- "implementation": "B",
- "type": "string",
- "configurationNames": [
- "service"
- ],
- "default": null
- }
- ],
"OTEL_TRACES_EXPORTER": [
{
"implementation": "F",
"type": "string",
- "default": "otlp"
+ "default": "otlp",
+ "allowed": "none|otlp",
+ "transform": "toLowerCase"
}
],
"OTEL_TRACES_SAMPLER": [
{
"implementation": "E",
"type": "string",
- "default": "parentbased_always_on"
+ "default": "parentbased_always_on",
+ "allowed": "always_on|always_off|traceidratio|parentbased_always_on|parentbased_always_off|parentbased_traceidratio",
+ "transform": "toLowerCase"
}
],
"OTEL_TRACES_SAMPLER_ARG": [
{
"implementation": "D",
"type": "decimal",
- "configurationNames": [
- "sampleRate"
- ],
- "default": null
+ "default": null,
+ "allowed": "\\d+(\\.\\d+)?"
}
]
}
diff --git a/packages/dd-trace/src/crashtracking/crashtracker.js b/packages/dd-trace/src/crashtracking/crashtracker.js
index 1fd2a822fb6..10b02988dc2 100644
--- a/packages/dd-trace/src/crashtracking/crashtracker.js
+++ b/packages/dd-trace/src/crashtracking/crashtracker.js
@@ -23,6 +23,9 @@ class Crashtracker {
}
}
+ /**
+ * @param {import('../config/config-base')} config - Tracer configuration
+ */
start (config) {
if (this.#started) return this.configure(config)
@@ -35,7 +38,7 @@ class Crashtracker {
this.#getMetadata(config)
)
} catch (e) {
- log.error('Error initialising crashtracker', e)
+ log.error('Error initializing crashtracker', e)
}
}
@@ -49,6 +52,9 @@ class Crashtracker {
}
// TODO: Send only configured values when defaults are fixed.
+ /**
+ * @param {import('../config/config-base')} config - Tracer configuration
+ */
#getConfig (config) {
const url = getAgentUrl(config)
diff --git a/packages/dd-trace/src/debugger/index.js b/packages/dd-trace/src/debugger/index.js
index fe70f9dc5c2..9f3e750702e 100644
--- a/packages/dd-trace/src/debugger/index.js
+++ b/packages/dd-trace/src/debugger/index.js
@@ -147,7 +147,7 @@ function start (config, rcInstance) {
* Sends the new configuration to the worker thread via the config channel.
* Does nothing if the worker is not started.
*
- * @param {Config} config - The updated tracer configuration object
+ * @param {import('../config/config-base')} config - The updated tracer configuration object
*/
function configure (config) {
if (configChannel === null) return
diff --git a/packages/dd-trace/src/dogstatsd.js b/packages/dd-trace/src/dogstatsd.js
index b9f1491febc..f3853203cb2 100644
--- a/packages/dd-trace/src/dogstatsd.js
+++ b/packages/dd-trace/src/dogstatsd.js
@@ -1,13 +1,11 @@
'use strict'
-const lookup = require('dns').lookup // cache to avoid instrumentation
const dgram = require('dgram')
const isIP = require('net').isIP
const request = require('./exporters/common/request')
const log = require('./log')
const Histogram = require('./histogram')
-const defaults = require('./config/defaults')
const { getAgentUrl } = require('./agent/url')
const { entityId } = require('./exporters/common/docker')
@@ -23,7 +21,9 @@ const TYPE_HISTOGRAM = 'h'
* @implements {DogStatsD}
*/
class DogStatsDClient {
- constructor (options = {}) {
+ #lookup
+ constructor (options) {
+ this.#lookup = options.lookup
if (options.metricsProxyUrl) {
this._httpOptions = {
method: 'POST',
@@ -32,11 +32,10 @@ class DogStatsDClient {
}
}
- this._host = options.host || defaults['dogstatsd.hostname']
+ this._host = options.host
this._family = isIP(this._host)
- this._port = options.port || defaults['dogstatsd.port']
- this._prefix = options.prefix || ''
- this._tags = options.tags || []
+ this._port = options.port
+ this._tags = options.tags
this._queue = []
this._buffer = ''
this._offset = 0
@@ -99,7 +98,7 @@ class DogStatsDClient {
_sendUdp (queue) {
if (this._family === 0) {
- lookup(this._host, (err, address, family) => {
+ this.#lookup(this._host, (err, address, family) => {
if (err) return log.error('DogStatsDClient: Host not found', err)
this._sendUdpFromQueue(queue, address, family)
})
@@ -118,7 +117,7 @@ class DogStatsDClient {
}
_add (stat, value, type, tags) {
- let message = `${this._prefix + stat}:${value}|${type}`
+ let message = `${stat}:${value}|${type}`
// Don't manipulate this._tags as it is still used
tags = tags ? [...this._tags, ...tags] : this._tags
@@ -164,6 +163,9 @@ class DogStatsDClient {
return socket
}
+ /**
+ * @param {import('./config/config-base')} config - Tracer configuration
+ */
static generateClientConfig (config) {
const tags = []
@@ -183,6 +185,7 @@ class DogStatsDClient {
host: config.dogstatsd.hostname,
port: config.dogstatsd.port,
tags,
+ lookup: config.lookup,
}
if (config.url || config.port) {
diff --git a/packages/dd-trace/src/heap_snapshots.js b/packages/dd-trace/src/heap_snapshots.js
index 35360d892c0..19ef1206a69 100644
--- a/packages/dd-trace/src/heap_snapshots.js
+++ b/packages/dd-trace/src/heap_snapshots.js
@@ -45,6 +45,9 @@ function getName (destination) {
}
module.exports = {
+ /**
+ * @param {import('./config/config-base')} config - Tracer configuration
+ */
async start (config) {
const destination = config.heapSnapshot.destination
diff --git a/packages/dd-trace/src/index.js b/packages/dd-trace/src/index.js
index 582511ab6a9..0366f023e4c 100644
--- a/packages/dd-trace/src/index.js
+++ b/packages/dd-trace/src/index.js
@@ -1,7 +1,7 @@
'use strict'
const { getValueFromEnvSources } = require('./config/helper')
-const { isFalse } = require('./util')
+const { isFalse, isTrue } = require('./util')
// Global `jest` is only present in Jest workers.
const inJestWorker = typeof jest !== 'undefined'
@@ -9,7 +9,10 @@ const inJestWorker = typeof jest !== 'undefined'
const ddTraceDisabled = getValueFromEnvSources('DD_TRACE_ENABLED')
? isFalse(getValueFromEnvSources('DD_TRACE_ENABLED'))
: String(getValueFromEnvSources('OTEL_TRACES_EXPORTER')).toLowerCase() === 'none'
+const shouldUseProxyWhenTracingDisabled =
+ isTrue(getValueFromEnvSources('DD_DYNAMIC_INSTRUMENTATION_ENABLED')) ||
+ isTrue(getValueFromEnvSources('DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED'))
-module.exports = ddTraceDisabled || inJestWorker
+module.exports = (ddTraceDisabled && !shouldUseProxyWhenTracingDisabled) || inJestWorker
? require('./noop/proxy')
: require('./proxy')
diff --git a/packages/dd-trace/src/llmobs/sdk.js b/packages/dd-trace/src/llmobs/sdk.js
index 6e06027953c..3429a95a27e 100644
--- a/packages/dd-trace/src/llmobs/sdk.js
+++ b/packages/dd-trace/src/llmobs/sdk.js
@@ -29,6 +29,11 @@ class LLMObs extends NoopLLMObs {
*/
#hasUserSpanProcessor = false
+ /**
+ * @param {import('../tracer')} tracer - Tracer instance
+ * @param {import('./index')} llmobsModule - LLMObs module instance
+ * @param {import('../config/config-base')} config - Tracer configuration
+ */
constructor (tracer, llmobsModule, config) {
super(tracer)
@@ -38,7 +43,7 @@ class LLMObs extends NoopLLMObs {
}
get enabled () {
- return this._config.llmobs.enabled
+ return this._config.llmobs.enabled ?? false
}
enable (options = {}) {
@@ -56,13 +61,10 @@ class LLMObs extends NoopLLMObs {
return
}
- const llmobs = {
- mlApp: options.mlApp,
- agentlessEnabled: options.agentlessEnabled,
- }
- // TODO: This will update config telemetry with the origin 'code', which is not ideal when `enable()` is called
- // based on `APM_TRACING` RC product updates.
- this._config.updateOptions({ llmobs })
+ // TODO: These configs should be passed through directly at construction time instead.
+ this._config.llmobs.enabled = true
+ this._config.llmobs.mlApp = options.mlApp
+ this._config.llmobs.agentlessEnabled = options.agentlessEnabled
// configure writers and channel subscribers
this._llmobsModule.enable(this._config)
diff --git a/packages/dd-trace/src/log/index.js b/packages/dd-trace/src/log/index.js
index 7ff2a82fe99..a237325d899 100644
--- a/packages/dd-trace/src/log/index.js
+++ b/packages/dd-trace/src/log/index.js
@@ -1,5 +1,8 @@
'use strict'
+
const { inspect } = require('util')
+
+const { defaults } = require('../config/defaults')
const { isTrue } = require('../util')
const { getValueFromEnvSources } = require('../config/helper')
const { traceChannel, debugChannel, infoChannel, warnChannel, errorChannel } = require('./channels')
@@ -8,12 +11,17 @@ const { Log, LogConfig, NoTransmitError } = require('./log')
const { memoize } = require('./utils')
const config = {
- enabled: false,
+ enabled: defaults.DD_TRACE_DEBUG,
logger: undefined,
- logLevel: 'debug',
+ logLevel: defaults.logLevel,
}
-// in most places where we know we want to mute a log we use log.error() directly
+const deprecate = memoize((code, message) => {
+ publishFormatted(errorChannel, null, message)
+ return true
+})
+
+// In most places where we know we want to mute a log we use log.error() directly
const NO_TRANSMIT = new LogConfig(false)
const log = {
@@ -21,36 +29,6 @@ const log = {
NO_TRANSMIT,
NoTransmitError,
- /**
- * @returns Read-only version of logging config. To modify config, call `log.use` and `log.toggle`
- */
- getConfig () {
- return { ...config }
- },
-
- use (logger) {
- config.logger = logger
- logWriter.use(logger)
- return log
- },
-
- toggle (enabled, logLevel) {
- config.enabled = enabled
- config.logLevel = logLevel
- logWriter.toggle(enabled, logLevel)
- return log
- },
-
- reset () {
- logWriter.reset()
- log._deprecate = memoize((code, message) => {
- publishFormatted(errorChannel, null, message)
- return true
- })
-
- return log
- },
-
trace (...args) {
if (traceChannel.hasSubscribers) {
const logRecord = {}
@@ -66,6 +44,8 @@ const log = {
publishFormatted(traceChannel, null, stack.join('\n'))
}
+ // TODO: Why do we allow chaining here? This is likely not used anywhere.
+ // If it is used, that seems like a mistake.
return log
},
@@ -103,30 +83,23 @@ const log = {
},
deprecate (code, message) {
- return log._deprecate(code, message)
+ return deprecate(code, message)
},
- isEnabled (fleetStableConfigValue, localStableConfigValue) {
- return isTrue(
- fleetStableConfigValue ??
+ configure (options) {
+ config.logger = options.logger
+ config.logLevel = options.logLevel ??
+ getValueFromEnvSources('DD_TRACE_LOG_LEVEL') ??
+ config.logLevel
+ config.enabled = isTrue(
getValueFromEnvSources('DD_TRACE_DEBUG') ??
- (getValueFromEnvSources('OTEL_LOG_LEVEL') === 'debug' || undefined) ??
- localStableConfigValue ??
- config.enabled
+ // TODO: Handle this by adding a log buffer so that configure may be called with the actual configurations.
+ // eslint-disable-next-line eslint-rules/eslint-process-env
+ (process.env.OTEL_LOG_LEVEL === 'debug' || config.enabled)
)
- },
+ logWriter.configure(config.enabled, config.logLevel, options.logger)
- getLogLevel (
- optionsValue,
- fleetStableConfigValue,
- localStableConfigValue
- ) {
- return optionsValue ??
- fleetStableConfigValue ??
- getValueFromEnvSources('DD_TRACE_LOG_LEVEL') ??
- getValueFromEnvSources('OTEL_LOG_LEVEL') ??
- localStableConfigValue ??
- config.logLevel
+ return config.enabled
},
}
@@ -150,8 +123,6 @@ function getErrorLog (err) {
return err
}
-log.reset()
-
-log.toggle(log.isEnabled(), log.getLogLevel())
+log.configure({})
module.exports = log
diff --git a/packages/dd-trace/src/log/writer.js b/packages/dd-trace/src/log/writer.js
index 13ce84d92d7..358a3b680fe 100644
--- a/packages/dd-trace/src/log/writer.js
+++ b/packages/dd-trace/src/log/writer.js
@@ -2,6 +2,7 @@
const { storage } = require('../../../datadog-core')
const { LogChannel } = require('./channels')
+
const defaultLogger = {
debug: msg => console.debug(msg), /* eslint-disable-line no-console */
info: msg => console.info(msg), /* eslint-disable-line no-console */
@@ -17,12 +18,8 @@ function withNoop (fn) {
storage('legacy').run({ noop: true }, fn)
}
-function unsubscribeAll () {
- logChannel.unsubscribe({ trace, debug, info, warn, error })
-}
-
function toggleSubscription (enable, level) {
- unsubscribeAll()
+ logChannel.unsubscribe({ trace, debug, info, warn, error })
if (enable) {
logChannel = new LogChannel(level)
@@ -30,23 +27,14 @@ function toggleSubscription (enable, level) {
}
}
-function toggle (enable, level) {
+function configure (enable, level, newLogger) {
enabled = enable
+ logger = typeof newLogger?.debug === 'function' && typeof newLogger.error === 'function'
+ ? newLogger
+ : defaultLogger
toggleSubscription(enabled, level)
}
-function use (newLogger) {
- if (typeof newLogger?.debug === 'function' && typeof newLogger.error === 'function') {
- logger = newLogger
- }
-}
-
-function reset () {
- logger = defaultLogger
- enabled = false
- toggleSubscription(false)
-}
-
function error (err) {
withNoop(() => logger.error(err))
}
@@ -69,4 +57,4 @@ function trace (log) {
withNoop(() => logger.debug(log))
}
-module.exports = { use, toggle, reset, error, warn, info, debug, trace }
+module.exports = { configure, error, warn, info, debug, trace }
diff --git a/packages/dd-trace/src/opentelemetry/logs/index.js b/packages/dd-trace/src/opentelemetry/logs/index.js
index 2d9ec8c71d7..a36446d7dbe 100644
--- a/packages/dd-trace/src/opentelemetry/logs/index.js
+++ b/packages/dd-trace/src/opentelemetry/logs/index.js
@@ -33,7 +33,7 @@ const OtlpHttpLogExporter = require('./otlp_http_log_exporter')
/**
* Initializes OpenTelemetry Logs support
- * @param {Config} config - Tracer configuration instance
+ * @param {import('../../config/config-base')} config - Tracer configuration instance
*/
function initializeOpenTelemetryLogs (config) {
// Build resource attributes
diff --git a/packages/dd-trace/src/opentelemetry/metrics/index.js b/packages/dd-trace/src/opentelemetry/metrics/index.js
index c0d116e2075..914baeee330 100644
--- a/packages/dd-trace/src/opentelemetry/metrics/index.js
+++ b/packages/dd-trace/src/opentelemetry/metrics/index.js
@@ -35,7 +35,7 @@ const OtlpHttpMetricExporter = require('./otlp_http_metric_exporter')
/**
* Initializes OpenTelemetry Metrics support
- * @param {Config} config - Tracer configuration instance
+ * @param {import('../../config/config-base')} config - Tracer configuration instance
*/
function initializeOpenTelemetryMetrics (config) {
const resourceAttributes = {
diff --git a/packages/dd-trace/src/opentracing/propagation/text_map.js b/packages/dd-trace/src/opentracing/propagation/text_map.js
index 3c7b65eefb0..50efb42c9e0 100644
--- a/packages/dd-trace/src/opentracing/propagation/text_map.js
+++ b/packages/dd-trace/src/opentracing/propagation/text_map.js
@@ -6,6 +6,7 @@ const id = require('../../id')
const DatadogSpanContext = require('../span_context')
const log = require('../../log')
const tags = require('../../../../../ext/tags')
+const { getConfiguredEnvName } = require('../../config/helper')
const { setBaggageItem, getAllBaggageItems, removeAllBaggageItems } = require('../../baggage')
const telemetryMetrics = require('../../telemetry/metrics')
@@ -65,8 +66,15 @@ const zeroTraceId = '0000000000000000'
const hex16 = /^[0-9A-Fa-f]{16}$/
class TextMapPropagator {
+ #extractB3Context
+
constructor (config) {
this._config = config
+
+ // TODO: should match "b3 single header" in next major
+ const envName = getConfiguredEnvName('DD_TRACE_PROPAGATION_STYLE')
+ // eslint-disable-next-line eslint-rules/eslint-env-aliases
+ this.#extractB3Context = envName === 'OTEL_PROPAGATORS' ? this._extractB3SingleContext : this._extractB3MultiContext
}
inject (spanContext, carrier) {
@@ -363,10 +371,7 @@ class TextMapPropagator {
extractedContext = this._extractB3SingleContext(carrier)
break
case 'b3':
- extractedContext = this._config.tracePropagationStyle.otelPropagators
- // TODO: should match "b3 single header" in next major
- ? this._extractB3SingleContext(carrier)
- : this._extractB3MultiContext(carrier)
+ extractedContext = this.#extractB3Context(carrier)
break
case 'b3multi':
extractedContext = this._extractB3MultiContext(carrier)
diff --git a/packages/dd-trace/src/payload-tagging/config/index.js b/packages/dd-trace/src/payload-tagging/config/index.js
index 1f91dd9d6e7..c103349ca8b 100644
--- a/packages/dd-trace/src/payload-tagging/config/index.js
+++ b/packages/dd-trace/src/payload-tagging/config/index.js
@@ -3,16 +3,17 @@
const aws = require('./aws.json')
const sdks = { aws }
+/** @typedef {Record} SDKRules */
/**
* Builds rules per service for a given SDK, appending user-provided rules.
*
- * @param {Record} sdk
+ * @param {SDKRules} sdk
* @param {string[]} requestInput
* @param {string[]} responseInput
- * @returns {Record}
+ * @returns {SDKRules}
*/
function getSDKRules (sdk, requestInput, responseInput) {
- const sdkServiceRules = {}
+ const sdkServiceRules = /** @type {SDKRules} */ ({})
for (const [service, serviceRules] of Object.entries(sdk)) {
sdkServiceRules[service] = {
// Make a copy. Otherwise calling the function multiple times would append
@@ -31,10 +32,10 @@ function getSDKRules (sdk, requestInput, responseInput) {
*
* @param {string[]} [requestInput=[]]
* @param {string[]} [responseInput=[]]
- * @returns {Record>}
+ * @returns {Record}
*/
function appendRules (requestInput = [], responseInput = []) {
- const sdkRules = {}
+ const sdkRules = /** @type {Record} */ ({})
for (const [name, sdk] of Object.entries(sdks)) {
sdkRules[name] = getSDKRules(sdk, requestInput, responseInput)
}
diff --git a/packages/dd-trace/src/plugin_manager.js b/packages/dd-trace/src/plugin_manager.js
index 2bf92b390c6..5b78fb048f5 100644
--- a/packages/dd-trace/src/plugin_manager.js
+++ b/packages/dd-trace/src/plugin_manager.js
@@ -67,7 +67,7 @@ function getEnabled (Plugin) {
module.exports = class PluginManager {
constructor (tracer) {
this._tracer = tracer
- this._tracerConfig = null
+ this._tracerConfig = /** @type {import('./config/config-base')} */ (null)
this._pluginsByName = {}
this._configsByName = {}
@@ -121,8 +121,11 @@ module.exports = class PluginManager {
this.loadPlugin(name)
}
- // like instrumenter.enable()
- configure (config = {}) {
+ /**
+ * Like instrumenter.enable()
+ * @param {import('./config/config-base')} config - Tracer configuration
+ */
+ configure (config) {
this._tracerConfig = config
this._tracer._nomenclature.configure(config)
diff --git a/packages/dd-trace/src/plugins/ci_plugin.js b/packages/dd-trace/src/plugins/ci_plugin.js
index 9008186c107..d318174bf61 100644
--- a/packages/dd-trace/src/plugins/ci_plugin.js
+++ b/packages/dd-trace/src/plugins/ci_plugin.js
@@ -469,6 +469,10 @@ module.exports = class CiPlugin extends Plugin {
return getSessionRequestErrorTags(this.testSessionSpan)
}
+ /**
+ * @param {import('../config/config-base')} config - Tracer configuration
+ * @param {boolean} shouldGetEnvironmentData - Whether to get environment data
+ */
configure (config, shouldGetEnvironmentData = true) {
super.configure(config)
diff --git a/packages/dd-trace/src/plugins/plugin.js b/packages/dd-trace/src/plugins/plugin.js
index 0f12da1d81c..7b6a565288d 100644
--- a/packages/dd-trace/src/plugins/plugin.js
+++ b/packages/dd-trace/src/plugins/plugin.js
@@ -163,9 +163,10 @@ module.exports = class Plugin {
/**
* Enable or disable the plugin and (re)apply its configuration.
*
- * @param {boolean|object} config Either a boolean to enable/disable or a configuration object
- * containing at least `{ enabled: boolean }`.
- * @returns {void}
+ * TODO: Remove the overloading with `enabled` and use the config object directly.
+ *
+ * @param {boolean|import('../config/config-base')} config Either a boolean to enable/disable
+ * or a configuration object containing at least `{ enabled: boolean }`.
*/
configure (config) {
if (typeof config === 'boolean') {
diff --git a/packages/dd-trace/src/process-tags/index.js b/packages/dd-trace/src/process-tags/index.js
index 6fe87b848cb..98f7cf3a2aa 100644
--- a/packages/dd-trace/src/process-tags/index.js
+++ b/packages/dd-trace/src/process-tags/index.js
@@ -72,6 +72,9 @@ function buildProcessTags (config) {
// Singleton with constant defaults so pre-init reads don't blow up
const processTags = module.exports = {
+ /**
+ * @param {import('../config/config-base')} config
+ */
initialize (config) {
// check if one of the properties added during build exist and if so return
if (processTags.tags) return
diff --git a/packages/dd-trace/src/profiler.js b/packages/dd-trace/src/profiler.js
index 4990212fe92..36e30a0c80a 100644
--- a/packages/dd-trace/src/profiler.js
+++ b/packages/dd-trace/src/profiler.js
@@ -5,13 +5,16 @@ const { profiler } = require('./profiling')
globalThis[Symbol.for('dd-trace')].beforeExitHandlers.add(() => { profiler.stop() })
module.exports = {
- start: config => {
+ /**
+ * @param {import('./config/config-base')} config - Tracer configuration
+ */
+ start (config) {
// Forward the full tracer config to the profiling layer.
// Profiling code is responsible for deriving the specific options it needs.
return profiler.start(config)
},
- stop: () => {
+ stop () {
profiler.stop()
},
}
diff --git a/packages/dd-trace/src/profiling/config.js b/packages/dd-trace/src/profiling/config.js
index 040618c5b58..f34135284af 100644
--- a/packages/dd-trace/src/profiling/config.js
+++ b/packages/dd-trace/src/profiling/config.js
@@ -6,9 +6,8 @@ const { pathToFileURL } = require('url')
const satisfies = require('../../../../vendor/dist/semifies')
const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('../plugins/util/tags')
const { getIsAzureFunction } = require('../serverless')
-const { isFalse, isTrue } = require('../util')
const { getAzureTagsFromMetadata, getAzureAppMetadata, getAzureFunctionMetadata } = require('../azure_metadata')
-const { getEnvironmentVariable, getValueFromEnvSources } = require('../config/helper')
+const { getEnvironmentVariable } = require('../config/helper')
const { getAgentUrl } = require('../agent/url')
const { isACFActive } = require('../../../datadog-core/src/storage')
@@ -22,59 +21,22 @@ const { oomExportStrategies, snapshotKinds } = require('./constants')
const { tagger } = require('./tagger')
class Config {
- constructor (options = {}) {
- // TODO: Remove entries that were already resolved in config.
- // For the others, move them over to config.
+ constructor (options) {
const AWS_LAMBDA_FUNCTION_NAME = getEnvironmentVariable('AWS_LAMBDA_FUNCTION_NAME')
- // TODO: Move initialization of these values to packages/dd-trace/src/config/index.js, and just read from config
- const {
- DD_INTERNAL_PROFILING_TIMELINE_SAMPLING_ENABLED,
- DD_PROFILING_ASYNC_CONTEXT_FRAME_ENABLED,
- DD_PROFILING_CODEHOTSPOTS_ENABLED,
- DD_PROFILING_CPU_ENABLED,
- DD_PROFILING_DEBUG_SOURCE_MAPS,
- DD_PROFILING_DEBUG_UPLOAD_COMPRESSION,
- DD_PROFILING_ENDPOINT_COLLECTION_ENABLED,
- DD_PROFILING_EXPERIMENTAL_OOM_EXPORT_STRATEGIES,
- DD_PROFILING_EXPERIMENTAL_OOM_HEAP_LIMIT_EXTENSION_SIZE,
- DD_PROFILING_EXPERIMENTAL_OOM_MAX_HEAP_EXTENSION_COUNT,
- DD_PROFILING_EXPERIMENTAL_OOM_MONITORING_ENABLED,
- DD_PROFILING_HEAP_ENABLED,
- DD_PROFILING_HEAP_SAMPLING_INTERVAL,
- DD_PROFILING_PPROF_PREFIX,
- DD_PROFILING_PROFILERS,
- DD_PROFILING_TIMELINE_ENABLED,
- DD_PROFILING_UPLOAD_PERIOD,
- DD_PROFILING_UPLOAD_TIMEOUT,
- DD_PROFILING_V8_PROFILER_BUG_WORKAROUND,
- DD_PROFILING_WALLTIME_ENABLED,
- DD_TAGS,
- } = getProfilingEnvValues()
-
- // Must be longer than one minute so pad with five seconds
- const flushInterval = options.interval ?? (Number(DD_PROFILING_UPLOAD_PERIOD) * 1000 || 65 * 1000)
- const uploadTimeout = options.uploadTimeout ?? (Number(DD_PROFILING_UPLOAD_TIMEOUT) || 60 * 1000)
- const pprofPrefix = options.pprofPrefix ?? DD_PROFILING_PPROF_PREFIX ?? ''
-
- // TODO: Remove the fallback. Just use the value from the config.
- this.service = options.service || 'node'
+ this.version = options.version
+ this.service = options.service
this.env = options.env
this.functionname = AWS_LAMBDA_FUNCTION_NAME
- this.version = options.version
- this.tags = Object.assign(
- tagger.parse(DD_TAGS),
- tagger.parse(options.tags),
- tagger.parse({
- env: options.env,
+ this.tags = {
+ ...options.tags,
+ ...tagger.parse({
host: options.reportHostname ? require('os').hostname() : undefined,
- service: this.service,
- version: this.version,
functionname: AWS_LAMBDA_FUNCTION_NAME,
}),
- getAzureTagsFromMetadata(getIsAzureFunction() ? getAzureFunctionMetadata() : getAzureAppMetadata())
- )
+ ...getAzureTagsFromMetadata(getIsAzureFunction() ? getAzureFunctionMetadata() : getAzureAppMetadata()),
+ }
// Add source code integration tags if available
if (options.repositoryUrl && options.commitSHA) {
@@ -82,58 +44,35 @@ class Config {
this.tags[GIT_COMMIT_SHA] = options.commitSHA
}
- this.logger = ensureLogger(options.logger)
- // Profiler sampling contexts are not available on Windows, so features
- // depending on those (code hotspots and endpoint collection) need to default
- // to false on Windows.
- const samplingContextsAvailable = process.platform !== 'win32'
- function checkOptionAllowed (option, description, condition) {
- if (option && !condition) {
- // injection hardening: all of these can only happen if user explicitly
- // sets an environment variable to its non-default value on the platform.
- // In practical terms, it'd require someone explicitly turning on OOM
- // monitoring, code hotspots, endpoint profiling, or CPU profiling on
- // Windows, where it is not supported.
- throw new Error(`${description} not supported on ${process.platform}.`)
- }
- }
- function checkOptionWithSamplingContextAllowed (option, description) {
- checkOptionAllowed(option, description, samplingContextsAvailable)
- }
+ // Normalize from seconds to milliseconds. Default must be longer than a minute.
+ this.flushInterval = options.DD_PROFILING_UPLOAD_PERIOD * 1000
+ this.uploadTimeout = options.DD_PROFILING_UPLOAD_TIMEOUT
+ this.sourceMap = options.DD_PROFILING_SOURCE_MAP
+ this.debugSourceMaps = options.DD_PROFILING_DEBUG_SOURCE_MAPS
+ this.endpointCollectionEnabled = options.DD_PROFILING_ENDPOINT_COLLECTION_ENABLED
+ this.pprofPrefix = options.DD_PROFILING_PPROF_PREFIX
+ this.v8ProfilerBugWorkaroundEnabled = options.DD_PROFILING_V8_PROFILER_BUG_WORKAROUND
- this.flushInterval = flushInterval
- this.uploadTimeout = uploadTimeout
- this.sourceMap = options.sourceMap
- this.debugSourceMaps = isTrue(options.debugSourceMaps ?? DD_PROFILING_DEBUG_SOURCE_MAPS)
- this.endpointCollectionEnabled = isTrue(options.endpointCollection ??
- DD_PROFILING_ENDPOINT_COLLECTION_ENABLED ?? samplingContextsAvailable)
- checkOptionWithSamplingContextAllowed(this.endpointCollectionEnabled, 'Endpoint collection')
-
- this.pprofPrefix = pprofPrefix
- this.v8ProfilerBugWorkaroundEnabled = isTrue(options.v8ProfilerBugWorkaround ??
- DD_PROFILING_V8_PROFILER_BUG_WORKAROUND ?? true)
+ this.logger = ensureLogger(options.logger)
this.url = getAgentUrl(options)
- this.libraryInjected = options.libraryInjected
- this.activation = options.activation
- this.exporters = ensureExporters(options.exporters || [
- new AgentExporter(this),
- ], this)
+ this.libraryInjected = !!options.DD_INJECTION_ENABLED
- // OOM monitoring does not work well on Windows, so it is disabled by default.
- const oomMonitoringSupported = process.platform !== 'win32'
+ let activation
+ if (options.profiling.enabled === 'auto') {
+ activation = 'auto'
+ } else if (options.profiling.enabled === 'true') {
+ activation = 'manual'
+ } // else activation = undefined
- const oomMonitoringEnabled = isTrue(options.oomMonitoring ??
- DD_PROFILING_EXPERIMENTAL_OOM_MONITORING_ENABLED ?? oomMonitoringSupported)
- checkOptionAllowed(oomMonitoringEnabled, 'OOM monitoring', oomMonitoringSupported)
+ this.activation = activation
+ this.exporters = ensureExporters(options.DD_PROFILING_EXPORTERS, this)
- const heapLimitExtensionSize = options.oomHeapLimitExtensionSize ??
- (Number(DD_PROFILING_EXPERIMENTAL_OOM_HEAP_LIMIT_EXTENSION_SIZE) || 0)
- const maxHeapExtensionCount = options.oomMaxHeapExtensionCount ??
- (Number(DD_PROFILING_EXPERIMENTAL_OOM_MAX_HEAP_EXTENSION_COUNT) || 0)
+ const oomMonitoringEnabled = options.DD_PROFILING_EXPERIMENTAL_OOM_MONITORING_ENABLED
+ const heapLimitExtensionSize = options.DD_PROFILING_EXPERIMENTAL_OOM_HEAP_LIMIT_EXTENSION_SIZE
+ const maxHeapExtensionCount = options.DD_PROFILING_EXPERIMENTAL_OOM_MAX_HEAP_EXTENSION_COUNT
const exportStrategies = oomMonitoringEnabled
- ? ensureOOMExportStrategies(options.oomExportStrategies ?? DD_PROFILING_EXPERIMENTAL_OOM_EXPORT_STRATEGIES ??
- [oomExportStrategies.PROCESS], this)
+ ? ensureOOMExportStrategies(options.DD_PROFILING_EXPERIMENTAL_OOM_EXPORT_STRATEGIES, this)
: []
const exportCommand = oomMonitoringEnabled ? buildExportCommand(this) : undefined
this.oomMonitoring = {
@@ -144,61 +83,26 @@ class Config {
exportCommand,
}
- const profilers = options.profilers || getProfilers({
- DD_PROFILING_HEAP_ENABLED,
- DD_PROFILING_WALLTIME_ENABLED,
- DD_PROFILING_PROFILERS,
- })
+ const profilers = getProfilers(options)
- this.timelineEnabled = isTrue(
- options.timelineEnabled ?? DD_PROFILING_TIMELINE_ENABLED ?? samplingContextsAvailable
- )
- checkOptionWithSamplingContextAllowed(this.timelineEnabled, 'Timeline view')
- this.timelineSamplingEnabled = isTrue(
- options.timelineSamplingEnabled ?? DD_INTERNAL_PROFILING_TIMELINE_SAMPLING_ENABLED ?? true
- )
+ this.timelineEnabled = options.DD_PROFILING_TIMELINE_ENABLED
+ this.timelineSamplingEnabled = options.DD_INTERNAL_PROFILING_TIMELINE_SAMPLING_ENABLED
+ this.codeHotspotsEnabled = options.DD_PROFILING_CODEHOTSPOTS_ENABLED
+ this.cpuProfilingEnabled = options.DD_PROFILING_CPU_ENABLED
+ this.heapSamplingInterval = options.DD_PROFILING_HEAP_SAMPLING_INTERVAL
- this.codeHotspotsEnabled = isTrue(
- options.codeHotspotsEnabled ?? DD_PROFILING_CODEHOTSPOTS_ENABLED ?? samplingContextsAvailable
- )
- checkOptionWithSamplingContextAllowed(this.codeHotspotsEnabled, 'Code hotspots')
-
- this.cpuProfilingEnabled = isTrue(
- options.cpuProfilingEnabled ?? DD_PROFILING_CPU_ENABLED ?? samplingContextsAvailable
- )
- checkOptionWithSamplingContextAllowed(this.cpuProfilingEnabled, 'CPU profiling')
-
- this.samplingInterval = options.samplingInterval || 1e3 / 99 // 99hz in millis
-
- this.heapSamplingInterval = options.heapSamplingInterval ??
- (Number(DD_PROFILING_HEAP_SAMPLING_INTERVAL) || 512 * 1024)
+ this.samplingInterval = 1e3 / 99 // 99hz in milliseconds
const isAtLeast24 = satisfies(process.versions.node, '>=24.0.0')
- const uploadCompression0 = options.uploadCompression ?? DD_PROFILING_DEBUG_UPLOAD_COMPRESSION ?? 'on'
+ const uploadCompression0 = options.DD_PROFILING_DEBUG_UPLOAD_COMPRESSION
let [uploadCompression, level0] = uploadCompression0.split('-')
- if (!['on', 'off', 'gzip', 'zstd'].includes(uploadCompression)) {
- this.logger.warn(`Invalid profile upload compression method "${uploadCompression0}". Will use "on".`)
- uploadCompression = 'on'
- }
let level = level0 ? Number.parseInt(level0, 10) : undefined
if (level !== undefined) {
- if (['on', 'off'].includes(uploadCompression)) {
- this.logger.warn(`Compression levels are not supported for "${uploadCompression}".`)
- level = undefined
- } else if (Number.isNaN(level)) {
- this.logger.warn(
- `Invalid compression level "${level0}". Will use default level.`)
- level = undefined
- } else if (level < 1) {
- this.logger.warn(`Invalid compression level ${level}. Will use 1.`)
- level = 1
- } else {
- const maxLevel = { gzip: 9, zstd: 22 }[uploadCompression]
- if (level > maxLevel) {
- this.logger.warn(`Invalid compression level ${level}. Will use ${maxLevel}.`)
- level = maxLevel
- }
+ const maxLevel = { gzip: 9, zstd: 22 }[uploadCompression]
+ if (level > maxLevel) {
+ this.logger.warn(`Invalid compression level ${level}. Will use ${maxLevel}.`)
+ level = maxLevel
}
}
@@ -219,13 +123,9 @@ class Config {
that.asyncContextFrameEnabled = false
}
- const canUseAsyncContextFrame = samplingContextsAvailable && isACFActive
-
- this.asyncContextFrameEnabled = isTrue(DD_PROFILING_ASYNC_CONTEXT_FRAME_ENABLED ?? canUseAsyncContextFrame)
- if (this.asyncContextFrameEnabled && !canUseAsyncContextFrame) {
- if (!samplingContextsAvailable) {
- turnOffAsyncContextFrame(`on ${process.platform}`)
- } else if (isAtLeast24) {
+ this.asyncContextFrameEnabled = options.DD_PROFILING_ASYNC_CONTEXT_FRAME_ENABLED ?? isACFActive
+ if (this.asyncContextFrameEnabled && !isACFActive) {
+ if (isAtLeast24) {
turnOffAsyncContextFrame('with --no-async-context-frame')
} else if (satisfies(process.versions.node, '>=22.9.0')) {
turnOffAsyncContextFrame('without --experimental-async-context-frame')
@@ -234,7 +134,7 @@ class Config {
}
}
- this.heartbeatInterval = options.heartbeatInterval || 60 * 1000 // 1 minute
+ this.heartbeatInterval = options.telemetry.heartbeatInterval
this.profilers = ensureProfilers(profilers, this)
}
@@ -248,7 +148,7 @@ class Config {
endpointCollectionEnabled: this.endpointCollectionEnabled,
heapSamplingInterval: this.heapSamplingInterval,
oomMonitoring: { ...this.oomMonitoring },
- profilerTypes: this.profilers.map(p => p.type),
+ profilerTypes: this.profilers.map(profiler => profiler.type),
sourceMap: this.sourceMap,
timelineEnabled: this.timelineEnabled,
timelineSamplingEnabled: this.timelineSamplingEnabled,
@@ -263,7 +163,9 @@ class Config {
module.exports = { Config }
function getProfilers ({
- DD_PROFILING_HEAP_ENABLED, DD_PROFILING_WALLTIME_ENABLED, DD_PROFILING_PROFILERS,
+ DD_PROFILING_HEAP_ENABLED,
+ DD_PROFILING_WALLTIME_ENABLED,
+ DD_PROFILING_PROFILERS,
}) {
// First consider "legacy" DD_PROFILING_PROFILERS env variable, defaulting to space + wall
// Use a Set to avoid duplicates
@@ -272,26 +174,26 @@ function getProfilers ({
// snapshots the space profile won't include memory taken by profiles created
// before it in the sequence. That memory is ultimately transient and will be
// released when all profiles are subsequently encoded.
- const profilers = new Set((DD_PROFILING_PROFILERS ?? 'space,wall').split(','))
+ const profilers = new Set(DD_PROFILING_PROFILERS)
let spaceExplicitlyEnabled = false
// Add/remove space depending on the value of DD_PROFILING_HEAP_ENABLED
- if (DD_PROFILING_HEAP_ENABLED != null) {
- if (isTrue(DD_PROFILING_HEAP_ENABLED)) {
+ if (DD_PROFILING_HEAP_ENABLED !== undefined) {
+ if (DD_PROFILING_HEAP_ENABLED) {
if (!profilers.has('space')) {
profilers.add('space')
spaceExplicitlyEnabled = true
}
- } else if (isFalse(DD_PROFILING_HEAP_ENABLED)) {
+ } else {
profilers.delete('space')
}
}
// Add/remove wall depending on the value of DD_PROFILING_WALLTIME_ENABLED
- if (DD_PROFILING_WALLTIME_ENABLED != null) {
- if (isTrue(DD_PROFILING_WALLTIME_ENABLED)) {
+ if (DD_PROFILING_WALLTIME_ENABLED !== undefined) {
+ if (DD_PROFILING_WALLTIME_ENABLED) {
profilers.add('wall')
- } else if (isFalse(DD_PROFILING_WALLTIME_ENABLED)) {
+ } else {
profilers.delete('wall')
profilers.delete('cpu') // remove alias too
}
@@ -321,22 +223,12 @@ function getExportStrategy (name, options) {
}
function ensureOOMExportStrategies (strategies, options) {
- if (!strategies) {
- return []
+ const set = new Set()
+ for (const strategy of strategies) {
+ set.add(getExportStrategy(strategy, options))
}
- if (typeof strategies === 'string') {
- strategies = strategies.split(',')
- }
-
- for (let i = 0; i < strategies.length; i++) {
- const strategy = strategies[i]
- if (typeof strategy === 'string') {
- strategies[i] = getExportStrategy(strategy, options)
- }
- }
-
- return [...new Set(strategies)]
+ return [...set]
}
function getExporter (name, options) {
@@ -345,22 +237,13 @@ function getExporter (name, options) {
return new AgentExporter(options)
case 'file':
return new FileExporter(options)
+ default:
+ options.logger.error(`Unknown exporter "${name}"`)
}
}
function ensureExporters (exporters, options) {
- if (typeof exporters === 'string') {
- exporters = exporters.split(',')
- }
-
- for (let i = 0; i < exporters.length; i++) {
- const exporter = exporters[i]
- if (typeof exporter === 'string') {
- exporters[i] = getExporter(exporter, options)
- }
- }
-
- return exporters
+ return exporters.map((exporter) => getExporter(exporter, options))
}
function getProfiler (name, options) {
@@ -376,30 +259,26 @@ function getProfiler (name, options) {
}
function ensureProfilers (profilers, options) {
- if (typeof profilers === 'string') {
- profilers = profilers.split(',')
- }
+ const filteredProfilers = []
for (let i = 0; i < profilers.length; i++) {
- const profiler = profilers[i]
- if (typeof profiler === 'string') {
- profilers[i] = getProfiler(profiler, options)
+ const profiler = getProfiler(profilers[i], options)
+ if (profiler !== undefined) {
+ filteredProfilers.push(profiler)
}
}
// Events profiler is a profiler that produces timeline events. It is only
// added if timeline is enabled and there's a wall profiler.
- if (options.timelineEnabled && profilers.some(p => p instanceof WallProfiler)) {
- profilers.push(new EventsProfiler(options))
+ if (options.timelineEnabled && filteredProfilers.some(profiler => profiler instanceof WallProfiler)) {
+ filteredProfilers.push(new EventsProfiler(options))
}
- // Filter out any invalid profilers
- return profilers.filter(Boolean)
+ return filteredProfilers
}
function ensureLogger (logger) {
- if (typeof logger !== 'object' ||
- typeof logger.debug !== 'function' ||
+ if (typeof logger?.debug !== 'function' ||
typeof logger.info !== 'function' ||
typeof logger.warn !== 'function' ||
typeof logger.error !== 'function') {
@@ -424,50 +303,3 @@ function buildExportCommand (options) {
path.join(__dirname, 'exporter_cli.js'),
urls.join(','), tags, 'space']
}
-
-function getProfilingEnvValues () {
- return {
- DD_INTERNAL_PROFILING_TIMELINE_SAMPLING_ENABLED:
- getValueFromEnvSources('DD_INTERNAL_PROFILING_TIMELINE_SAMPLING_ENABLED'),
- DD_PROFILING_ASYNC_CONTEXT_FRAME_ENABLED:
- getValueFromEnvSources('DD_PROFILING_ASYNC_CONTEXT_FRAME_ENABLED'),
- DD_PROFILING_CODEHOTSPOTS_ENABLED:
- getValueFromEnvSources('DD_PROFILING_CODEHOTSPOTS_ENABLED'),
- DD_PROFILING_CPU_ENABLED:
- getValueFromEnvSources('DD_PROFILING_CPU_ENABLED'),
- DD_PROFILING_DEBUG_SOURCE_MAPS:
- getValueFromEnvSources('DD_PROFILING_DEBUG_SOURCE_MAPS'),
- DD_PROFILING_DEBUG_UPLOAD_COMPRESSION:
- getValueFromEnvSources('DD_PROFILING_DEBUG_UPLOAD_COMPRESSION'),
- DD_PROFILING_ENDPOINT_COLLECTION_ENABLED:
- getValueFromEnvSources('DD_PROFILING_ENDPOINT_COLLECTION_ENABLED'),
- DD_PROFILING_EXPERIMENTAL_OOM_EXPORT_STRATEGIES:
- getValueFromEnvSources('DD_PROFILING_EXPERIMENTAL_OOM_EXPORT_STRATEGIES'),
- DD_PROFILING_EXPERIMENTAL_OOM_HEAP_LIMIT_EXTENSION_SIZE:
- getValueFromEnvSources('DD_PROFILING_EXPERIMENTAL_OOM_HEAP_LIMIT_EXTENSION_SIZE'),
- DD_PROFILING_EXPERIMENTAL_OOM_MAX_HEAP_EXTENSION_COUNT:
- getValueFromEnvSources('DD_PROFILING_EXPERIMENTAL_OOM_MAX_HEAP_EXTENSION_COUNT'),
- DD_PROFILING_EXPERIMENTAL_OOM_MONITORING_ENABLED:
- getValueFromEnvSources('DD_PROFILING_EXPERIMENTAL_OOM_MONITORING_ENABLED'),
- DD_PROFILING_HEAP_ENABLED:
- getValueFromEnvSources('DD_PROFILING_HEAP_ENABLED'),
- DD_PROFILING_HEAP_SAMPLING_INTERVAL:
- getValueFromEnvSources('DD_PROFILING_HEAP_SAMPLING_INTERVAL'),
- DD_PROFILING_PPROF_PREFIX:
- getValueFromEnvSources('DD_PROFILING_PPROF_PREFIX'),
- DD_PROFILING_PROFILERS:
- getValueFromEnvSources('DD_PROFILING_PROFILERS'),
- DD_PROFILING_TIMELINE_ENABLED:
- getValueFromEnvSources('DD_PROFILING_TIMELINE_ENABLED'),
- DD_PROFILING_UPLOAD_PERIOD:
- getValueFromEnvSources('DD_PROFILING_UPLOAD_PERIOD'),
- DD_PROFILING_UPLOAD_TIMEOUT:
- getValueFromEnvSources('DD_PROFILING_UPLOAD_TIMEOUT'),
- DD_PROFILING_V8_PROFILER_BUG_WORKAROUND:
- getValueFromEnvSources('DD_PROFILING_V8_PROFILER_BUG_WORKAROUND'),
- DD_PROFILING_WALLTIME_ENABLED:
- getValueFromEnvSources('DD_PROFILING_WALLTIME_ENABLED'),
- DD_TAGS:
- getValueFromEnvSources('DD_TAGS'),
- }
-}
diff --git a/packages/dd-trace/src/profiling/exporter_cli.js b/packages/dd-trace/src/profiling/exporter_cli.js
index cba3d6349b1..a122a334664 100644
--- a/packages/dd-trace/src/profiling/exporter_cli.js
+++ b/packages/dd-trace/src/profiling/exporter_cli.js
@@ -17,9 +17,6 @@ function exporterFromURL (url) {
if (url.protocol === 'file:') {
return new FileExporter({ pprofPrefix: fileURLToPath(url) })
}
- // TODO: Why is DD_INJECTION_ENABLED a comma separated list?
- const injectionEnabled = (getValueFromEnvSources('DD_INJECTION_ENABLED') ?? '').split(',')
- const libraryInjected = injectionEnabled.length > 0
const profilingEnabled = (getValueFromEnvSources('DD_PROFILING_ENABLED') ?? '').toLowerCase()
const activation = ['true', '1'].includes(profilingEnabled)
? 'manual'
@@ -30,7 +27,7 @@ function exporterFromURL (url) {
url,
logger,
uploadTimeout: timeoutMs,
- libraryInjected,
+ libraryInjected: !!getValueFromEnvSources('DD_INJECTION_ENABLED'),
activation,
})
}
diff --git a/packages/dd-trace/src/profiling/exporters/event_serializer.js b/packages/dd-trace/src/profiling/exporters/event_serializer.js
index a7bd652f9e9..586c91409b1 100644
--- a/packages/dd-trace/src/profiling/exporters/event_serializer.js
+++ b/packages/dd-trace/src/profiling/exporters/event_serializer.js
@@ -14,7 +14,7 @@ class EventSerializer {
this._host = host
this._service = service
this._appVersion = version
- this._libraryInjected = !!libraryInjected
+ this._libraryInjected = libraryInjected
this._activation = activation || 'unknown'
}
diff --git a/packages/dd-trace/src/profiling/profiler.js b/packages/dd-trace/src/profiling/profiler.js
index c107fc82750..6ecffadabfb 100644
--- a/packages/dd-trace/src/profiling/profiler.js
+++ b/packages/dd-trace/src/profiling/profiler.js
@@ -70,56 +70,22 @@ class Profiler extends EventEmitter {
return this.#config?.flushInterval
}
+ /**
+ * @param {import('../config/config-base')} config - Tracer configuration
+ */
start (config) {
- const {
- service,
- version,
- env,
- url,
- hostname,
- port,
- tags,
- repositoryUrl,
- commitSHA,
- injectionEnabled,
- reportHostname,
- } = config
- const { enabled, sourceMap, exporters } = config.profiling
- const { heartbeatInterval } = config.telemetry
-
// TODO: Unify with main logger and rewrite template strings to use printf formatting.
const logger = {
- debug (message) { log.debug(message) },
- info (message) { log.info(message) },
- warn (message) { log.warn(message) },
- error (...args) { log.error(...args) },
+ debug: log.debug.bind(log),
+ info: log.info.bind(log),
+ warn: log.warn.bind(log),
+ error: log.error.bind(log),
}
- const libraryInjected = injectionEnabled.length > 0
- let activation
- if (enabled === 'auto') {
- activation = 'auto'
- } else if (enabled === 'true') {
- activation = 'manual'
- } // else activation = undefined
-
+ // TODO: Rewrite this to not need to copy the config.
const options = {
- service,
- version,
- env,
+ ...config,
logger,
- sourceMap,
- exporters,
- url,
- hostname,
- port,
- tags,
- repositoryUrl,
- commitSHA,
- libraryInjected,
- activation,
- heartbeatInterval,
- reportHostname,
}
try {
@@ -182,6 +148,9 @@ class Profiler extends EventEmitter {
return this.#compressionFn
}
+ /**
+ * @param {import('../config/config-base')} options - Tracer configuration
+ */
_start (options) {
if (this.enabled) return true
diff --git a/packages/dd-trace/src/profiling/profilers/events.js b/packages/dd-trace/src/profiling/profilers/events.js
index c2e5aa02fd9..eddeef7fab1 100644
--- a/packages/dd-trace/src/profiling/profilers/events.js
+++ b/packages/dd-trace/src/profiling/profilers/events.js
@@ -51,8 +51,7 @@ function labelFromStrStr (stringTable, keyStr, valStr) {
}
function getMaxSamples (options) {
- const flushInterval = options.flushInterval || 65 * 1e3 // 65 seconds
- const maxCpuSamples = flushInterval / options.samplingInterval
+ const maxCpuSamples = options.flushInterval / options.samplingInterval
// The lesser of max parallelism and libuv thread pool size, plus one so we can detect
// oversubscription on libuv thread pool, plus another one for GC.
@@ -403,7 +402,7 @@ class EventsProfiler {
get type () { return 'events' }
- constructor (options = {}) {
+ constructor (options) {
this.#maxSamples = getMaxSamples(options)
this.#timelineSamplingEnabled = !!options.timelineSamplingEnabled
this.#eventSerializer = new EventSerializer(this.#maxSamples)
diff --git a/packages/dd-trace/src/profiling/ssi-heuristics.js b/packages/dd-trace/src/profiling/ssi-heuristics.js
index 994cf7d6a46..e83ba71b18f 100644
--- a/packages/dd-trace/src/profiling/ssi-heuristics.js
+++ b/packages/dd-trace/src/profiling/ssi-heuristics.js
@@ -1,6 +1,6 @@
'use strict'
-const dc = require('dc-polyfill')
+const dc = /** @type {typeof import('diagnostics_channel')} */ (require('dc-polyfill'))
const log = require('../log')
// If the process lives for at least 30 seconds, it's considered long-lived
@@ -10,6 +10,9 @@ const DEFAULT_LONG_LIVED_THRESHOLD = 30_000
* This class embodies the SSI profiler-triggering heuristics under SSI.
*/
class SSIHeuristics {
+ /**
+ * @param {import('../config/config-base')} config - Tracer configuration
+ */
constructor (config) {
const longLivedThreshold = config.profiling.longLivedThreshold || DEFAULT_LONG_LIVED_THRESHOLD
if (typeof longLivedThreshold !== 'number' || longLivedThreshold <= 0) {
diff --git a/packages/dd-trace/src/propagation-hash/index.js b/packages/dd-trace/src/propagation-hash/index.js
index 74d61f3938b..29cb6069809 100644
--- a/packages/dd-trace/src/propagation-hash/index.js
+++ b/packages/dd-trace/src/propagation-hash/index.js
@@ -17,11 +17,12 @@ class PropagationHashManager {
_cachedHash = null
_cachedHashString = null
_cachedHashBase64 = null
+ /** @type {import('../config/config-base') | null} */
_config = null
/**
* Configure the propagation hash manager with tracer config
- * @param {object} config - Tracer configuration
+ * @param {import('../config/config-base')} config - Tracer configuration
*/
configure (config) {
this._config = config
diff --git a/packages/dd-trace/src/proxy.js b/packages/dd-trace/src/proxy.js
index 015e91f78ca..6ac2b7dc4f9 100644
--- a/packages/dd-trace/src/proxy.js
+++ b/packages/dd-trace/src/proxy.js
@@ -27,9 +27,12 @@ class LazyModule {
this.provider = provider
}
- enable (...args) {
+ /**
+ * @param {import('./config/config-base')} config - Tracer configuration
+ */
+ enable (config, ...args) {
this.module = this.provider()
- this.module.enable(...args)
+ this.module.enable(config, ...args)
}
disable () {
@@ -237,12 +240,16 @@ class Tracer extends NoopProxy {
getDynamicInstrumentationClient(config)
}
} catch (e) {
- log.error('Error initialising tracer', e)
+ log.error('Error initializing tracer', e)
+ // TODO: Should we stop everything started so far?
}
return this
}
+ /**
+ * @param {import('./config/config-base')} config - Tracer configuration
+ */
_startProfiler (config) {
// do not stop tracer initialization if the profiler fails to be imported
try {
@@ -256,6 +263,9 @@ class Tracer extends NoopProxy {
}
}
+ /**
+ * @param {import('./config/config-base')} config - Tracer configuration
+ */
#updateTracing (config) {
if (config.tracing !== false) {
if (config.appsec.enabled) {
diff --git a/packages/dd-trace/src/remote_config/index.js b/packages/dd-trace/src/remote_config/index.js
index d4451234938..83a3b016e15 100644
--- a/packages/dd-trace/src/remote_config/index.js
+++ b/packages/dd-trace/src/remote_config/index.js
@@ -25,6 +25,9 @@ class RemoteConfig {
#products = new Set()
#batchHandlers = new Map()
+ /**
+ * @param {import('../config/config-base')} config - Tracer configuration
+ */
constructor (config) {
const pollInterval = Math.floor(config.remoteConfig.pollInterval * 1000)
diff --git a/packages/dd-trace/src/runtime_metrics/index.js b/packages/dd-trace/src/runtime_metrics/index.js
index 9b2602844e7..72f51dae1fb 100644
--- a/packages/dd-trace/src/runtime_metrics/index.js
+++ b/packages/dd-trace/src/runtime_metrics/index.js
@@ -14,6 +14,9 @@ const noop = runtimeMetrics = {
}
module.exports = {
+ /**
+ * @param {import('../config/config-base')} config - Tracer configuration
+ */
start (config) {
if (!config?.runtimeMetrics.enabled) return
diff --git a/packages/dd-trace/src/runtime_metrics/runtime_metrics.js b/packages/dd-trace/src/runtime_metrics/runtime_metrics.js
index 5e042b8484b..7fd0ccdd7c1 100644
--- a/packages/dd-trace/src/runtime_metrics/runtime_metrics.js
+++ b/packages/dd-trace/src/runtime_metrics/runtime_metrics.js
@@ -35,6 +35,9 @@ let eventLoopDelayObserver = null
// https://github.com/DataDog/dogweb/blob/prod/integration/node/node_metadata.csv
module.exports = {
+ /**
+ * @param {import('../config/config-base')} config - Tracer configuration
+ */
start (config) {
this.stop()
const clientConfig = DogStatsDClient.generateClientConfig(config)
diff --git a/packages/dd-trace/src/sampler.js b/packages/dd-trace/src/sampler.js
index b023c55b6de..df9eadb1dec 100644
--- a/packages/dd-trace/src/sampler.js
+++ b/packages/dd-trace/src/sampler.js
@@ -42,7 +42,7 @@ class Sampler {
/**
* Determines whether a trace/span should be sampled based on the configured sampling rate.
*
- * @param {Span|SpanContext} span - The span or span context to evaluate.
+ * @param {import("../../..").Span|import("../../..").SpanContext} span - The span or span context to evaluate.
* @returns {boolean} `true` if the trace/span should be sampled, otherwise `false`.
*/
isSampled (span) {
diff --git a/packages/dd-trace/src/standalone/index.js b/packages/dd-trace/src/standalone/index.js
index eb43ee87d4d..699e48c220c 100644
--- a/packages/dd-trace/src/standalone/index.js
+++ b/packages/dd-trace/src/standalone/index.js
@@ -11,6 +11,9 @@ const startCh = channel('dd-trace:span:start')
const injectCh = channel('dd-trace:span:inject')
const extractCh = channel('dd-trace:span:extract')
+/**
+ * @param {import('../config/config-base')} config - Tracer configuration
+ */
function configure (config) {
if (startCh.hasSubscribers) startCh.unsubscribe(onSpanStart)
if (injectCh.hasSubscribers) injectCh.unsubscribe(onSpanInject)
diff --git a/packages/dd-trace/src/telemetry/index.js b/packages/dd-trace/src/telemetry/index.js
index 05a7ec8b96b..43e884b026f 100644
--- a/packages/dd-trace/src/telemetry/index.js
+++ b/packages/dd-trace/src/telemetry/index.js
@@ -5,15 +5,14 @@ let telemetry
// Lazy load the telemetry module to avoid the performance impact of loading it unconditionally
module.exports = {
start (config, ...args) {
+ if (!config.telemetry.enabled) return
telemetry ??= require('./telemetry')
telemetry.start(config, ...args)
},
- stop () {
- telemetry?.stop()
- },
// This might be called before `start` so we have to trigger loading the
// underlying module here as well.
updateConfig (changes, config, ...args) {
+ if (!config.telemetry.enabled) return
telemetry ??= require('./telemetry')
telemetry.updateConfig(changes, config, ...args)
},
diff --git a/packages/dd-trace/src/telemetry/send-data.js b/packages/dd-trace/src/telemetry/send-data.js
index fb7af48e64d..ef0d86634df 100644
--- a/packages/dd-trace/src/telemetry/send-data.js
+++ b/packages/dd-trace/src/telemetry/send-data.js
@@ -62,19 +62,6 @@ const { getValueFromEnvSources } = require('../config/helper')
* kernel_name?: string
* } & Record} TelemetryHost
*/
-/**
- * @typedef {{
- * hostname?: string,
- * port?: string | number,
- * url?: string | URL,
- * site?: string,
- * apiKey?: string,
- * isCiVisibility?: boolean,
- * spanAttributeSchema?: string,
- * tags: Record,
- * telemetry?: { debug?: boolean }
- * }} TelemetryConfig
- */
/**
* @callback SendDataCallback
* @param {Error | null | undefined} error
@@ -85,23 +72,22 @@ const { getValueFromEnvSources } = require('../config/helper')
let agentTelemetry = true
/**
- * @param {TelemetryConfig} config
+ * @param {import('../config/config-base')} config
* @param {TelemetryApplication} application
* @param {TelemetryRequestType} reqType
* @returns {Record}
*/
function getHeaders (config, application, reqType) {
- const sessionId = config.tags['runtime-id']
const headers = {
'content-type': 'application/json',
'dd-telemetry-api-version': 'v2',
'dd-telemetry-request-type': reqType,
'dd-client-library-language': application.language_name,
'dd-client-library-version': application.tracer_version,
- 'dd-session-id': sessionId,
+ 'dd-session-id': config.tags['runtime-id'],
}
- if (config.rootSessionId && config.rootSessionId !== sessionId) {
- headers['dd-root-session-id'] = config.rootSessionId
+ if (config.DD_ROOT_JS_SESSION_ID) {
+ headers['dd-root-session-id'] = config.DD_ROOT_JS_SESSION_ID
}
const debug = config.telemetry && config.telemetry.debug
if (debug) {
@@ -141,7 +127,7 @@ function getPayload (payload) {
// TODO(BridgeAR): Simplify this code. A lot does not need to be recalculated on every call.
/**
- * @param {TelemetryConfig} config
+ * @param {import('../config/config-base')} config
* @param {TelemetryApplication} application
* @param {TelemetryHost} host
* @param {TelemetryRequestType} reqType
diff --git a/packages/dd-trace/src/telemetry/session-propagation.js b/packages/dd-trace/src/telemetry/session-propagation.js
index 0af4968db52..7f191f02d7a 100644
--- a/packages/dd-trace/src/telemetry/session-propagation.js
+++ b/packages/dd-trace/src/telemetry/session-propagation.js
@@ -1,53 +1,37 @@
'use strict'
-const dc = require('dc-polyfill')
-
+const dc = /** @type {typeof import('diagnostics_channel')} */ (require('dc-polyfill'))
const childProcessChannel = dc.tracingChannel('datadog:child_process:execution')
let subscribed = false
-let rootSessionId
let runtimeId
-function injectSessionEnv (existingEnv) {
- // eslint-disable-next-line eslint-rules/eslint-process-env -- not in supported-configurations.json
- const base = existingEnv == null ? process.env : existingEnv
- return {
- ...base,
- DD_ROOT_JS_SESSION_ID: rootSessionId,
- DD_PARENT_JS_SESSION_ID: runtimeId,
- }
+function isOptionsObject (value) {
+ return value != null && typeof value === 'object' && !Array.isArray(value) && value
}
-function findOptionsIndex (args, shell) {
- if (Array.isArray(args[1])) {
- return { index: 2, exists: args[2] != null && typeof args[2] === 'object' }
- }
- if (args[1] != null && typeof args[1] === 'object') {
- return { index: 1, exists: true }
- }
- if (!shell && args[2] != null && typeof args[2] === 'object') {
- return { index: 2, exists: true }
- }
- return { index: shell ? 1 : 2, exists: false }
+function getEnvWithRuntimeId (env) {
+ // eslint-disable-next-line eslint-rules/eslint-process-env
+ return { ...(env ?? process.env), DD_ROOT_JS_SESSION_ID: runtimeId }
}
function onChildProcessStart (context) {
- if (!context.callArgs) return
-
const args = context.callArgs
- const { index, exists } = findOptionsIndex(args, context.shell)
+ if (!args) return
- if (exists) {
- args[index] = { ...args[index], env: injectSessionEnv(args[index].env) }
+ const index = Array.isArray(args[1]) || (!context.shell && !isOptionsObject(args[1])) ? 2 : 1
+ const options = isOptionsObject(args[index]) ? args[index] : undefined
+
+ if (options) {
+ args[index] = { ...options, env: getEnvWithRuntimeId(options.env) }
return
}
- const opts = { env: injectSessionEnv(null) }
-
- if (!context.shell && !Array.isArray(args[1])) {
+ if (index === 2 && !Array.isArray(args[1])) {
args.splice(1, 0, [])
}
+ const opts = { env: getEnvWithRuntimeId() }
if (typeof args[index] === 'function') {
args.splice(index, 0, opts)
} else {
@@ -55,24 +39,15 @@ function onChildProcessStart (context) {
}
}
-const handler = { start: onChildProcessStart }
-
function start (config) {
if (!config.telemetry?.enabled || subscribed) return
subscribed = true
- rootSessionId = config.rootSessionId
- runtimeId = config.tags['runtime-id']
-
- childProcessChannel.subscribe(handler)
-}
+ runtimeId = config.DD_ROOT_JS_SESSION_ID || config.tags['runtime-id']
-function stop () {
- if (!subscribed) return
- childProcessChannel.unsubscribe(handler)
- subscribed = false
- rootSessionId = undefined
- runtimeId = undefined
+ childProcessChannel.subscribe(
+ /** @type {import('diagnostics_channel').TracingChannelSubscribers