From d550f175ed6c92cd8c0c8ac801aeb846eda1c86b Mon Sep 17 00:00:00 2001 From: Matthew Li Date: Tue, 7 Apr 2026 14:55:23 -0400 Subject: [PATCH 1/6] adding logback env var to supported-configurations.json --- metadata/supported-configurations.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/metadata/supported-configurations.json b/metadata/supported-configurations.json index 8f2eec5b73d..92de4676295 100644 --- a/metadata/supported-configurations.json +++ b/metadata/supported-configurations.json @@ -7761,6 +7761,14 @@ "aliases": ["DD_TRACE_INTEGRATION_LOGS_INTAKE_LOG4J_2_ENABLED", "DD_INTEGRATION_LOGS_INTAKE_LOG4J_2_ENABLED"] } ], + "DD_TRACE_LOGS_INTAKE_LOGBACK_ENABLED": [ + { + "version": "A", + "type": "boolean", + "default": "true", + "aliases": ["DD_TRACE_INTEGRATION_LOGS_INTAKE_LOGBACK_ENABLED", "DD_INTEGRATION_LOGS_INTAKE_LOGBACK_ENABLED"] + } + ], "DD_TRACE_MAVEN_ENABLED": [ { "version": "A", From c44baa36b5afd875619b66ba2039aad9f0b4aaeb Mon Sep 17 00:00:00 2001 From: Matthew Li Date: Wed, 8 Apr 2026 12:52:15 -0400 Subject: [PATCH 2/6] adding spring-messaging-kotlin --- metadata/supported-configurations.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/metadata/supported-configurations.json b/metadata/supported-configurations.json index 92de4676295..48e8ea945d9 100644 --- a/metadata/supported-configurations.json +++ b/metadata/supported-configurations.json @@ -10129,6 +10129,14 @@ "aliases": ["DD_TRACE_INTEGRATION_SPRING_MESSAGING_ENABLED", "DD_INTEGRATION_SPRING_MESSAGING_ENABLED"] } ], + "DD_TRACE_SPRING_MESSAGING_KOTLIN_ENABLED": [ + { + "version": "A", + "type": "boolean", + "default": "true", + "aliases": ["DD_TRACE_INTEGRATION_SPRING_MESSAGING_KOTLIN_ENABLED", "DD_INTEGRATION_SPRING_MESSAGING_KOTLIN_ENABLED"] + } + ], "DD_TRACE_SPRING_PATH_FILTER_ENABLED": [ { "version": "A", From f8b124ad2ce8e47db6b923343a6319327ab97abd Mon Sep 17 00:00:00 2001 From: Matthew Li Date: Wed, 8 Apr 2026 14:01:46 -0400 Subject: [PATCH 3/6] adding profiling config --- metadata/supported-configurations.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/metadata/supported-configurations.json b/metadata/supported-configurations.json index 48e8ea945d9..ac7935039e3 100644 --- a/metadata/supported-configurations.json +++ b/metadata/supported-configurations.json @@ -2577,6 +2577,14 @@ "aliases": [] } ], + "DD_PROFILING_ASYNC_LIVEHEAP_TRACK_SIZE_ENABLED": [ + { + "version": "A", + "type": "boolean", + "default": "true", + "aliases": [] + } + ], "DD_PROFILING_ASYNC_LOGLEVEL": [ { "version": "A", From b6f0dab5c26837e0c6a709571267423639ff0dd8 Mon Sep 17 00:00:00 2001 From: Matthew Li Date: Thu, 9 Apr 2026 13:52:34 -0400 Subject: [PATCH 4/6] init --- .../plugin/config/ConfigInversionLinter.kt | 191 +++++++++++++++++- 1 file changed, 187 insertions(+), 4 deletions(-) diff --git a/buildSrc/src/main/kotlin/datadog/gradle/plugin/config/ConfigInversionLinter.kt b/buildSrc/src/main/kotlin/datadog/gradle/plugin/config/ConfigInversionLinter.kt index 74c184a47ae..8561a670494 100644 --- a/buildSrc/src/main/kotlin/datadog/gradle/plugin/config/ConfigInversionLinter.kt +++ b/buildSrc/src/main/kotlin/datadog/gradle/plugin/config/ConfigInversionLinter.kt @@ -4,10 +4,11 @@ import com.github.javaparser.ParserConfiguration import com.github.javaparser.StaticJavaParser import com.github.javaparser.ast.CompilationUnit import com.github.javaparser.ast.Modifier -import com.github.javaparser.ast.body.FieldDeclaration -import com.github.javaparser.ast.body.VariableDeclarator +import com.github.javaparser.ast.body.* import com.github.javaparser.ast.expr.StringLiteralExpr import com.github.javaparser.ast.nodeTypes.NodeWithModifiers +import com.github.javaparser.ast.stmt.ExplicitConstructorInvocationStmt +import com.github.javaparser.ast.stmt.ReturnStmt import org.gradle.api.GradleException import org.gradle.api.Plugin import org.gradle.api.Project @@ -23,13 +24,16 @@ class ConfigInversionLinter : Plugin { registerLogEnvVarUsages(target, extension) registerCheckEnvironmentVariablesUsage(target) registerCheckConfigStringsTask(target, extension) + registerCheckInstrumenterModuleConfigurations(target, extension) + registerCheckDecoratorAnalyticsConfigurations(target, extension) } } // Data class for fields from generated class private data class LoadedConfigFields( val supported: Set, - val aliasMapping: Map = emptyMap() + val aliasMapping: Map = emptyMap(), + val aliases: Map> = emptyMap() ) // Cache for fields from generated class @@ -55,7 +59,9 @@ private fun loadConfigFields( @Suppress("UNCHECKED_CAST") val aliasMappingMap = clazz.getField("ALIAS_MAPPING").get(null) as Map - LoadedConfigFields(supportedSet, aliasMappingMap) + @Suppress("UNCHECKED_CAST") + val aliasesMap = clazz.getField("ALIASES").get(null) as Map> + LoadedConfigFields(supportedSet, aliasMappingMap, aliasesMap) }.also { cachedConfigFields = it } } } @@ -248,3 +254,180 @@ private fun registerCheckConfigStringsTask(project: Project, extension: Supporte } } } + +private val INSTRUMENTER_MODULE_TYPES = setOf( + "InstrumenterModule", + "InstrumenterModule.Tracing", + "InstrumenterModule.Profiling", + "InstrumenterModule.AppSec", + "InstrumenterModule.Iast", + "InstrumenterModule.Usm", + "InstrumenterModule.CiVisibility", + "InstrumenterModule.ContextTracking" +) + +/** Checks that [key] exists in [supported] and [aliases], and that all [expectedAliases] are values of that alias entry. */ +private fun MutableList.checkKeyAndAliases( + key: String, + expectedAliases: List, + supported: Set, + aliases: Map>, + location: String, + context: String +) { + if (key !in supported) { + add("$location -> $context: '$key' is missing from SUPPORTED") + } + if (key !in aliases) { + add("$location -> $context: '$key' is missing from ALIASES") + } else { + val aliasValues = aliases[key] ?: emptyList() + for (expected in expectedAliases) { + if (expected !in aliasValues) { + add("$location -> $context: '$expected' is missing from ALIASES['$key']") + } + } + } +} + +/** + * Shared setup for tasks that scan instrumentation source files against the generated config class. + * Registers a task that parses Java files in dd-java-agent/instrumentation/ and calls [checker] + * for each parsed CompilationUnit to collect violations. + */ +private fun registerInstrumentationCheckTask( + project: Project, + extension: SupportedTracerConfigurations, + taskName: String, + taskDescription: String, + errorHeader: String, + errorMessage: String, + successMessage: String, + checker: MutableList.(LoadedConfigFields, String, CompilationUnit) -> Unit +) { + val ownerPath = extension.configOwnerPath + val generatedFile = extension.className + + project.tasks.register(taskName) { + group = "verification" + description = taskDescription + + val mainSourceSetOutput = ownerPath.map { + project.project(it) + .extensions.getByType() + .named(SourceSet.MAIN_SOURCE_SET_NAME) + .map { main -> main.output } + } + + val instrumentationFiles = project.fileTree(project.rootProject.projectDir) { + include("dd-java-agent/instrumentation/**/src/main/java/**/*.java") + } + doLast { + val configFields = loadConfigFields(mainSourceSetOutput.get().get(), generatedFile.get()) + + val parserConfig = ParserConfiguration() + parserConfig.setLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_8) + StaticJavaParser.setConfiguration(parserConfig) + + val repoRoot = project.rootProject.projectDir.toPath() + val violations = buildList { + instrumentationFiles.files.forEach { file -> + val rel = repoRoot.relativize(file.toPath()).toString() + val cu: CompilationUnit = try { + StaticJavaParser.parse(file) + } catch (_: Exception) { + return@forEach + } + checker(configFields, rel, cu) + } + } + + if (violations.isNotEmpty()) { + logger.error(errorHeader) + violations.forEach { logger.lifecycle(it) } + throw GradleException(errorMessage) + } else { + logger.info(successMessage) + } + } + } +} + +/** Registers `checkInstrumenterModuleConfigurations` to verify each InstrumenterModule's integration name has proper entries in SUPPORTED and ALIASES. */ +private fun registerCheckInstrumenterModuleConfigurations(project: Project, extension: SupportedTracerConfigurations) { + registerInstrumentationCheckTask( + project, extension, + taskName = "checkInstrumenterModuleConfigurations", + taskDescription = "Validates that InstrumenterModule integration names have corresponding entries in SUPPORTED and ALIASES", + errorHeader = "\nFound InstrumenterModule integration names with missing SUPPORTED/ALIASES entries:", + errorMessage = "InstrumenterModule integration names are missing from SUPPORTED or ALIASES in '${extension.jsonFile.get()}'.", + successMessage = "All InstrumenterModule integration names have proper SUPPORTED and ALIASES entries." + ) { configFields, rel, cu -> + cu.findAll(ClassOrInterfaceDeclaration::class.java).forEach classLoop@{ classDecl -> + // Only examine classes extending InstrumenterModule.* + val extendsModule = classDecl.extendedTypes.any { it.toString() in INSTRUMENTER_MODULE_TYPES } + if (!extendsModule) return@classLoop + + classDecl.findAll(ExplicitConstructorInvocationStmt::class.java) + .filter { !it.isThis } + .forEach { superCall -> + val names = superCall.arguments + .filterIsInstance() + .map { it.value } + val line = superCall.range.map { it.begin.line }.orElse(1) + + for (name in names) { + val normalized = name.uppercase().replace("-", "_").replace(".", "_") + val enabledKey = "DD_TRACE_${normalized}_ENABLED" + val context = "Integration '$name' (super arg)" + val location = "$rel:$line" + + checkKeyAndAliases( + enabledKey, + listOf("DD_TRACE_INTEGRATION_${normalized}_ENABLED", "DD_INTEGRATION_${normalized}_ENABLED"), + configFields.supported, configFields.aliases, location, context + ) + } + } + } + } +} + +/** Registers `checkDecoratorAnalyticsConfigurations` to verify each BaseDecorator subclass's instrumentationNames have proper analytics entries in SUPPORTED and ALIASES. */ +private fun registerCheckDecoratorAnalyticsConfigurations(project: Project, extension: SupportedTracerConfigurations) { + registerInstrumentationCheckTask( + project, extension, + taskName = "checkDecoratorAnalyticsConfigurations", + taskDescription = "Validates that Decorator instrumentationNames have corresponding analytics entries in SUPPORTED and ALIASES", + errorHeader = "\nFound Decorator instrumentationNames with missing analytics SUPPORTED/ALIASES entries:", + errorMessage = "Decorator instrumentationNames are missing analytics entries from SUPPORTED or ALIASES in '${extension.jsonFile.get()}'.", + successMessage = "All Decorator instrumentationNames have proper analytics SUPPORTED and ALIASES entries." + ) { configFields, rel, cu -> + cu.findAll(MethodDeclaration::class.java) + .filter { it.nameAsString == "instrumentationNames" && it.parameters.isEmpty() } + .forEach { method -> + val names = method.findAll(ReturnStmt::class.java).flatMap { ret -> + ret.expression.map { it.findAll(StringLiteralExpr::class.java).map { s -> s.value } } + .orElse(emptyList()) + } + val line = method.range.map { it.begin.line }.orElse(1) + + for (name in names) { + val normalized = name.uppercase().replace("-", "_").replace(".", "_") + val context = "Decorator instrumentationName '$name'" + val location = "$rel:$line" + + checkKeyAndAliases( + "DD_TRACE_${normalized}_ANALYTICS_ENABLED", + listOf("DD_${normalized}_ANALYTICS_ENABLED"), + configFields.supported, configFields.aliases, location, context + ) + checkKeyAndAliases( + "DD_TRACE_${normalized}_ANALYTICS_SAMPLE_RATE", + listOf("DD_${normalized}_ANALYTICS_SAMPLE_RATE"), + configFields.supported, configFields.aliases, location, context + ) + } + } + } +} From 08dd521e632cfdf781888a4334159081a71057a6 Mon Sep 17 00:00:00 2001 From: Matthew Li Date: Thu, 9 Apr 2026 13:57:36 -0400 Subject: [PATCH 5/6] adding tasks to gitlab job --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 939a9025109..2bfeb85049d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -402,7 +402,7 @@ config-inversion-linter: needs: [] script: - ./gradlew --version - - ./gradlew logEnvVarUsages checkEnvironmentVariablesUsage checkConfigStrings + - ./gradlew logEnvVarUsages checkEnvironmentVariablesUsage checkConfigStrings checkInstrumenterModuleConfigurations checkDecoratorAnalyticsConfigurations test_published_artifacts: extends: .gradle_build From 01ad1308cf78f46c12e64e86c32759453dfa85dd Mon Sep 17 00:00:00 2001 From: Matthew Li Date: Thu, 9 Apr 2026 14:08:31 -0400 Subject: [PATCH 6/6] ensure GeneratedSupportedConfigurations is generated before the task runs --- .../kotlin/datadog/gradle/plugin/config/ConfigInversionLinter.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/buildSrc/src/main/kotlin/datadog/gradle/plugin/config/ConfigInversionLinter.kt b/buildSrc/src/main/kotlin/datadog/gradle/plugin/config/ConfigInversionLinter.kt index 8561a670494..d55a879feb2 100644 --- a/buildSrc/src/main/kotlin/datadog/gradle/plugin/config/ConfigInversionLinter.kt +++ b/buildSrc/src/main/kotlin/datadog/gradle/plugin/config/ConfigInversionLinter.kt @@ -318,6 +318,7 @@ private fun registerInstrumentationCheckTask( .named(SourceSet.MAIN_SOURCE_SET_NAME) .map { main -> main.output } } + inputs.files(mainSourceSetOutput) val instrumentationFiles = project.fileTree(project.rootProject.projectDir) { include("dd-java-agent/instrumentation/**/src/main/java/**/*.java")