diff --git a/buildSrc/.kotlin/errors/errors-1754401818862.log b/buildSrc/.kotlin/errors/errors-1754401818862.log new file mode 100644 index 00000000000..1219b509f09 --- /dev/null +++ b/buildSrc/.kotlin/errors/errors-1754401818862.log @@ -0,0 +1,4 @@ +kotlin version: 2.0.21 +error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output: + 1. Kotlin compile daemon is ready + diff --git a/internal-api/src/main/java/datadog/trace/api/telemetry/ConfigInversionMetricCollector.java b/internal-api/src/main/java/datadog/trace/api/telemetry/ConfigInversionMetricCollector.java new file mode 100644 index 00000000000..cdd781c78d6 --- /dev/null +++ b/internal-api/src/main/java/datadog/trace/api/telemetry/ConfigInversionMetricCollector.java @@ -0,0 +1,70 @@ +package datadog.trace.api.telemetry; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ConfigInversionMetricCollector + implements MetricCollector { + private static final Logger log = LoggerFactory.getLogger(ConfigInversionMetricCollector.class); + private static final String CONFIG_INVERSION_KEY_TAG = "config_name:"; + private static final String CONFIG_INVERSION_METRIC_NAME = "untracked.config.detected"; + private static final String NAMESPACE = "tracers"; + private static final ConfigInversionMetricCollector INSTANCE = + new ConfigInversionMetricCollector(); + + private final BlockingQueue metricsQueue; + + private ConfigInversionMetricCollector() { + this.metricsQueue = new ArrayBlockingQueue<>(RAW_QUEUE_SIZE); + } + + public static ConfigInversionMetricCollector getInstance() { + return INSTANCE; + } + + public void setUndocumentedEnvVarMetric(String configName) { + setMetricConfigInversionMetric(CONFIG_INVERSION_KEY_TAG + configName); + } + + private void setMetricConfigInversionMetric(final String... tags) { + if (!metricsQueue.offer( + new ConfigInversionMetricCollector.ConfigInversionMetric( + NAMESPACE, true, CONFIG_INVERSION_METRIC_NAME, "count", 1, tags))) { + log.debug("Unable to add telemetry metric {} for {}", CONFIG_INVERSION_METRIC_NAME, tags[0]); + } + } + + @Override + public void prepareMetrics() { + // Nothing to do here + } + + @Override + public Collection drain() { + if (this.metricsQueue.isEmpty()) { + return Collections.emptyList(); + } + List drained = + new ArrayList<>(this.metricsQueue.size()); + this.metricsQueue.drainTo(drained); + return drained; + } + + public static class ConfigInversionMetric extends MetricCollector.Metric { + public ConfigInversionMetric( + String namespace, + boolean common, + String metricName, + String type, + Number value, + final String... tags) { + super(namespace, common, metricName, type, value, tags); + } + } +} diff --git a/internal-api/src/test/groovy/datadog/trace/api/telemetry/ConfigInversionMetricCollectorTest.groovy b/internal-api/src/test/groovy/datadog/trace/api/telemetry/ConfigInversionMetricCollectorTest.groovy new file mode 100644 index 00000000000..7f63048eb27 --- /dev/null +++ b/internal-api/src/test/groovy/datadog/trace/api/telemetry/ConfigInversionMetricCollectorTest.groovy @@ -0,0 +1,41 @@ +package datadog.trace.api.telemetry + +import datadog.trace.test.util.DDSpecification + +import static datadog.trace.api.telemetry.ConfigInversionMetricCollector.CONFIG_INVERSION_METRIC_NAME + +class ConfigInversionMetricCollectorTest extends DDSpecification { + + def "should emit metric when unsupported env var is used"() { + setup: + def collector = ConfigInversionMetricCollector.getInstance() + + when: + ConfigInversionMetricCollectorTestHelper.checkAndEmitUnsupported("DD_UNKNOWN_FEATURE") + collector.prepareMetrics() + def metrics = collector.drain() + + then: + metrics.size() == 1 + def metric = metrics[0] + metric.type == 'count' + metric.value == 1 + metric.namespace == 'tracers' + metric.metricName == CONFIG_INVERSION_METRIC_NAME + metric.tags.size() == 1 + metric.tags[0] == 'config_name:DD_UNKNOWN_FEATURE' + } + + def "should not emit metric when supported env var is used"() { + setup: + def collector = ConfigInversionMetricCollector.getInstance() + + when: + ConfigInversionMetricCollectorTestHelper.checkAndEmitUnsupported("DD_ENV") + collector.prepareMetrics() + def metrics = collector.drain() + + then: + metrics.isEmpty() + } +} diff --git a/internal-api/src/test/groovy/datadog/trace/api/telemetry/ConfigInversionMetricCollectorTestHelper.java b/internal-api/src/test/groovy/datadog/trace/api/telemetry/ConfigInversionMetricCollectorTestHelper.java new file mode 100644 index 00000000000..9cb70dd68b5 --- /dev/null +++ b/internal-api/src/test/groovy/datadog/trace/api/telemetry/ConfigInversionMetricCollectorTestHelper.java @@ -0,0 +1,21 @@ +package datadog.trace.api.telemetry; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +// Lightweight helper class to simulate Config Inversion ConfigHelper scenario where telemetry +// metrics are emitted for "unknown" environment variables. +public class ConfigInversionMetricCollectorTestHelper { + private static final Set SUPPORTED_ENV_VARS = + new HashSet<>(Arrays.asList("DD_ENV", "DD_SERVICE")); + + private static final ConfigInversionMetricCollector configInversionMetricCollector = + ConfigInversionMetricCollector.getInstance(); + + public static void checkAndEmitUnsupported(String envVarName) { + if (!SUPPORTED_ENV_VARS.contains(envVarName)) { + configInversionMetricCollector.setUndocumentedEnvVarMetric(envVarName); + } + } +} diff --git a/telemetry/src/main/java/datadog/telemetry/TelemetrySystem.java b/telemetry/src/main/java/datadog/telemetry/TelemetrySystem.java index 8a1c3af38de..8edac4935e2 100644 --- a/telemetry/src/main/java/datadog/telemetry/TelemetrySystem.java +++ b/telemetry/src/main/java/datadog/telemetry/TelemetrySystem.java @@ -10,6 +10,7 @@ import datadog.telemetry.integration.IntegrationPeriodicAction; import datadog.telemetry.log.LogPeriodicAction; import datadog.telemetry.metric.CiVisibilityMetricPeriodicAction; +import datadog.telemetry.metric.ConfigInversionMetricPeriodicAction; import datadog.telemetry.metric.CoreMetricsPeriodicAction; import datadog.telemetry.metric.IastMetricPeriodicAction; import datadog.telemetry.metric.OtelEnvMetricPeriodicAction; @@ -51,6 +52,7 @@ static Thread createTelemetryRunnable( if (telemetryMetricsEnabled) { actions.add(new CoreMetricsPeriodicAction()); actions.add(new OtelEnvMetricPeriodicAction()); + actions.add(new ConfigInversionMetricPeriodicAction()); actions.add(new IntegrationPeriodicAction()); actions.add(new WafMetricPeriodicAction()); if (Verbosity.OFF != Config.get().getIastTelemetryVerbosity()) { diff --git a/telemetry/src/main/java/datadog/telemetry/metric/ConfigInversionMetricPeriodicAction.java b/telemetry/src/main/java/datadog/telemetry/metric/ConfigInversionMetricPeriodicAction.java new file mode 100644 index 00000000000..e7eb7dfab48 --- /dev/null +++ b/telemetry/src/main/java/datadog/telemetry/metric/ConfigInversionMetricPeriodicAction.java @@ -0,0 +1,13 @@ +package datadog.telemetry.metric; + +import datadog.trace.api.telemetry.ConfigInversionMetricCollector; +import datadog.trace.api.telemetry.MetricCollector; +import edu.umd.cs.findbugs.annotations.NonNull; + +public class ConfigInversionMetricPeriodicAction extends MetricPeriodicAction { + @Override + @NonNull + public MetricCollector collector() { + return ConfigInversionMetricCollector.getInstance(); + } +} diff --git a/telemetry/src/test/groovy/datadog/telemetry/metric/ConfigInversionMetricPeriodicActionTest.groovy b/telemetry/src/test/groovy/datadog/telemetry/metric/ConfigInversionMetricPeriodicActionTest.groovy new file mode 100644 index 00000000000..073c1e3e68f --- /dev/null +++ b/telemetry/src/test/groovy/datadog/telemetry/metric/ConfigInversionMetricPeriodicActionTest.groovy @@ -0,0 +1,29 @@ +package datadog.telemetry.metric + +import datadog.telemetry.TelemetryService +import datadog.telemetry.api.Metric +import spock.lang.Specification + +class ConfigInversionMetricPeriodicActionTest extends Specification{ + + void 'test undocumented env var metric'() { + setup: + final telemetryService = Mock(TelemetryService) + final action = new ConfigInversionMetricPeriodicAction() + + when: + action.collector().setUndocumentedEnvVarMetric("DD_ENV_VAR") + action.collector().prepareMetrics() + action.doIteration(telemetryService) + + then: + 1 * telemetryService.addMetric({ Metric metric -> + metric.namespace == 'tracers' && + metric.metric == 'untracked.config.detected' && + metric.points[0][1] == 1 && + metric.tags == ['config_name:DD_ENV_VAR'] && + metric.type == Metric.TypeEnum.COUNT + }) + 0 * _._ + } +}