From 8e759b9230abe0c8ba91d0c5f4b019ca9e98e6f8 Mon Sep 17 00:00:00 2001 From: Sean Li Date: Mon, 5 Jan 2026 15:04:49 -0800 Subject: [PATCH 01/15] Add azure_monitor to metrics exporter for AKS --- .../internal/init/AiConfigCustomizer.java | 23 ++++++++++++++++--- .../smoketest/SmokeTestExtension.java | 8 ++++++- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java index 5d17bb98b6..f70017b64a 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java @@ -5,6 +5,7 @@ import static java.util.concurrent.TimeUnit.SECONDS; +import com.azure.monitor.opentelemetry.autoconfigure.implementation.AzureMonitorExporterProviderKeys; import com.microsoft.applicationinsights.agent.internal.configuration.Configuration; import com.microsoft.applicationinsights.agent.internal.legacyheaders.DelegatingPropagatorProvider; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; @@ -115,9 +116,25 @@ public Map apply(ConfigProperties otelConfig) { } String metricsExporter = otelConfig.getString("otel.metrics.exporter"); - if (metricsExporter == null) { - // this overrides the default "otlp" so the exporter can be configured later - properties.put("otel.metrics.exporter", "none"); + String azureMonitorName = AzureMonitorExporterProviderKeys.EXPORTER_NAME; + boolean metricsToLogAnalyticsEnabled = + otelConfig.getBoolean("applicationinsights.metrics.to.loganalytics.enabled", false); + + if (metricsToLogAnalyticsEnabled) { + if (metricsExporter == null + || metricsExporter.isEmpty() + || metricsExporter.equalsIgnoreCase("none")) { + // enable Azure Monitor metrics exporter by default when flag is enabled + properties.put("otel.metrics.exporter", azureMonitorName); + } else if (!metricsExporter.contains(azureMonitorName)) { + // ensure Azure Monitor exporter is included when flag is enabled + properties.put("otel.metrics.exporter", metricsExporter + "," + azureMonitorName); + } + } else { + if (metricsExporter == null) { + // preserve previous behavior: override default "otlp" so exporter can be configured later + properties.put("otel.metrics.exporter", "none"); + } } String logsExporter = otelConfig.getString("otel.logs.exporter"); diff --git a/smoke-tests/framework/src/main/java/com/microsoft/applicationinsights/smoketest/SmokeTestExtension.java b/smoke-tests/framework/src/main/java/com/microsoft/applicationinsights/smoketest/SmokeTestExtension.java index ad7245cf5a..d02cedd041 100644 --- a/smoke-tests/framework/src/main/java/com/microsoft/applicationinsights/smoketest/SmokeTestExtension.java +++ b/smoke-tests/framework/src/main/java/com/microsoft/applicationinsights/smoketest/SmokeTestExtension.java @@ -116,7 +116,13 @@ public class SmokeTestExtension private final boolean useOtlpEndpoint; public static SmokeTestExtension create() { - return builder().build(); + SmokeTestExtensionBuilder b = builder(); + String metricsToLogAnalytics = + System.getenv("APPLICATIONINSIGHTS_METRICS_TO_LOGANALYTICS_ENABLED"); + if (metricsToLogAnalytics != null && metricsToLogAnalytics.equalsIgnoreCase("true")) { + b.setEnvVar("APPLICATIONINSIGHTS_METRICS_TO_LOGANALYTICS_ENABLED", "true"); + } + return b.build(); } public static SmokeTestExtensionBuilder builder() { From dac5b9e29831b23e07744156ad44480afcf07164 Mon Sep 17 00:00:00 2001 From: Sean Li Date: Wed, 21 Jan 2026 10:33:22 -0800 Subject: [PATCH 02/15] Check AKS_ARM_NAMESPACE_ID env var in AiConfigCustomizer --- .../agent/internal/init/AiConfigCustomizer.java | 11 ++++++++++- .../smoketest/SmokeTestExtension.java | 8 +------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java index f70017b64a..f68d5831fb 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java @@ -6,6 +6,7 @@ import static java.util.concurrent.TimeUnit.SECONDS; import com.azure.monitor.opentelemetry.autoconfigure.implementation.AzureMonitorExporterProviderKeys; +import com.azure.monitor.opentelemetry.autoconfigure.implementation.utils.Strings; import com.microsoft.applicationinsights.agent.internal.configuration.Configuration; import com.microsoft.applicationinsights.agent.internal.legacyheaders.DelegatingPropagatorProvider; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; @@ -120,7 +121,15 @@ public Map apply(ConfigProperties otelConfig) { boolean metricsToLogAnalyticsEnabled = otelConfig.getBoolean("applicationinsights.metrics.to.loganalytics.enabled", false); - if (metricsToLogAnalyticsEnabled) { + boolean isAks = false; + try { + String aksNamespaceId = System.getenv("AKS_ARM_NAMESPACE_ID"); + isAks = !Strings.isNullOrEmpty(aksNamespaceId); + } catch (SecurityException ignored) { + // if env is not accessible, assume not AKS + } + + if (metricsToLogAnalyticsEnabled && isAks) { if (metricsExporter == null || metricsExporter.isEmpty() || metricsExporter.equalsIgnoreCase("none")) { diff --git a/smoke-tests/framework/src/main/java/com/microsoft/applicationinsights/smoketest/SmokeTestExtension.java b/smoke-tests/framework/src/main/java/com/microsoft/applicationinsights/smoketest/SmokeTestExtension.java index d02cedd041..ad7245cf5a 100644 --- a/smoke-tests/framework/src/main/java/com/microsoft/applicationinsights/smoketest/SmokeTestExtension.java +++ b/smoke-tests/framework/src/main/java/com/microsoft/applicationinsights/smoketest/SmokeTestExtension.java @@ -116,13 +116,7 @@ public class SmokeTestExtension private final boolean useOtlpEndpoint; public static SmokeTestExtension create() { - SmokeTestExtensionBuilder b = builder(); - String metricsToLogAnalytics = - System.getenv("APPLICATIONINSIGHTS_METRICS_TO_LOGANALYTICS_ENABLED"); - if (metricsToLogAnalytics != null && metricsToLogAnalytics.equalsIgnoreCase("true")) { - b.setEnvVar("APPLICATIONINSIGHTS_METRICS_TO_LOGANALYTICS_ENABLED", "true"); - } - return b.build(); + return builder().build(); } public static SmokeTestExtensionBuilder builder() { From 4b90df6865a3b5afadbf7d4a8e8fc33133223b4b Mon Sep 17 00:00:00 2001 From: Sean Li Date: Wed, 21 Jan 2026 23:49:20 -0800 Subject: [PATCH 03/15] modify the AMLE condition to activate on both unset and true --- .../agent/internal/init/AiConfigCustomizer.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java index f68d5831fb..72fffbe5b1 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java @@ -118,8 +118,10 @@ public Map apply(ConfigProperties otelConfig) { String metricsExporter = otelConfig.getString("otel.metrics.exporter"); String azureMonitorName = AzureMonitorExporterProviderKeys.EXPORTER_NAME; - boolean metricsToLogAnalyticsEnabled = - otelConfig.getBoolean("applicationinsights.metrics.to.loganalytics.enabled", false); + String metricsToLogAnalyticsEnabledEnvVar = + otelConfig.getString("applicationinsights.metrics.to.loganalytics.enabled"); + boolean metricsToLogAnalyticsActivated = + metricsToLogAnalyticsEnabledEnvVar == null || Boolean.parseBoolean(metricsToLogAnalyticsEnabledEnvVar); boolean isAks = false; try { @@ -129,7 +131,7 @@ public Map apply(ConfigProperties otelConfig) { // if env is not accessible, assume not AKS } - if (metricsToLogAnalyticsEnabled && isAks) { + if (metricsToLogAnalyticsActivated && isAks) { if (metricsExporter == null || metricsExporter.isEmpty() || metricsExporter.equalsIgnoreCase("none")) { From ef4951b9b7eab30bcfda4dab9117283b33389eec Mon Sep 17 00:00:00 2001 From: Sean Li Date: Wed, 21 Jan 2026 23:53:06 -0800 Subject: [PATCH 04/15] Make a copy of OtlpTest.java file --- .../smoketest/OtlpLogAnalyticsOnAksTest.java | 184 ++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 smoke-tests/apps/OtlpMetrics/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/OtlpLogAnalyticsOnAksTest.java diff --git a/smoke-tests/apps/OtlpMetrics/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/OtlpLogAnalyticsOnAksTest.java b/smoke-tests/apps/OtlpMetrics/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/OtlpLogAnalyticsOnAksTest.java new file mode 100644 index 0000000000..b66c1509d4 --- /dev/null +++ b/smoke-tests/apps/OtlpMetrics/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/OtlpLogAnalyticsOnAksTest.java @@ -0,0 +1,184 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.applicationinsights.smoketest; + +import static com.microsoft.applicationinsights.smoketest.EnvironmentValue.TOMCAT_8_JAVA_8; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; +import static org.mockserver.model.HttpRequest.request; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +import com.microsoft.applicationinsights.smoketest.schemav2.Data; +import com.microsoft.applicationinsights.smoketest.schemav2.Envelope; +import com.microsoft.applicationinsights.smoketest.schemav2.MetricData; +import com.microsoft.applicationinsights.smoketest.schemav2.RequestData; +import io.opentelemetry.proto.metrics.v1.Metric; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mockserver.model.HttpRequest; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest( + classes = {OtlpApplication.class}, + webEnvironment = RANDOM_PORT) +@UseAgent +abstract class OtlpTest { + + @RegisterExtension + static final SmokeTestExtension testing = SmokeTestExtension.builder().useOtlpEndpoint().build(); + + @Test + @TargetUri("/ping") + public void testOtlpTelemetry() throws Exception { + // verify request sent to breeze endpoint + List rdList = testing.mockedIngestion.waitForItems("RequestData", 1); + Envelope rdEnvelope = rdList.get(0); + RequestData rd = (RequestData) ((Data) rdEnvelope.getData()).getBaseData(); + assertThat(rd.getName()).isEqualTo("GET /OtlpMetrics/ping"); + + // verify custom histogram metric sent to Application Insights endpoint + List metricList = + testing.mockedIngestion.waitForItems("MetricData", OtlpTest::isHistogramMetric, 1); + Envelope metricEnvelope = metricList.get(0); + MetricData metricData = (MetricData) ((Data) metricEnvelope.getData()).getBaseData(); + assertThat(metricData.getMetrics().get(0).getName()).isEqualTo("histogram-test-otlp-exporter"); + + // verify stable otel metric sent to Application Insights endpoint + List stableOtelMetrics = + testing.mockedIngestion.waitForItems("MetricData", OtlpTest::isStableOtelMetric, 1); + Envelope stableOtelMetricEnvelope = stableOtelMetrics.get(0); + assertThat( + ((MetricData) ((Data) stableOtelMetricEnvelope.getData()).getBaseData()) + .getMetrics() + .get(0) + .getName()) + .isEqualTo("http.server.request.duration"); + + // verify pre-aggregated standard metric sent to Application Insights endpoint + List standardMetrics = + testing.mockedIngestion.waitForStandardMetricItems("requests/duration", 1); + Envelope standardMetricEnvelope = standardMetrics.get(0); + MetricData standardMetricData = + (MetricData) ((Data) standardMetricEnvelope.getData()).getBaseData(); + assertThat(standardMetricData.getMetrics().get(0).getName()) + .isEqualTo("http.server.request.duration"); + assertThat(standardMetricData.getProperties().get("_MS.IsAutocollected")).isEqualTo("True"); + + // verify Statsbeat sent to the breeze endpoint + verifyStatsbeatSentToBreezeEndpoint(); + + // verify custom histogram metric 'histogram-test-otlp-exporter' and otel metric + // 'http.server.request.duration' sent to OTLP endpoint + // verify Statsbeat doesn't get sent to OTLP endpoint + verifyMetricsSentToOtlpEndpoint(); + } + + @SuppressWarnings("PreferJavaTimeOverload") // legacy time API required for backward compatibility + private void verifyMetricsSentToOtlpEndpoint() { + await() + .atMost(60, SECONDS) + .untilAsserted( + () -> { + HttpRequest[] requests = + testing + .mockedOtlpIngestion + .getCollectorServer() + .retrieveRecordedRequests(request()); + + // verify metrics + List metrics = + testing.mockedOtlpIngestion.extractMetricsFromRequests(requests); + assertThat(metrics) + .extracting(Metric::getName) + .contains("histogram-test-otlp-exporter", "http.server.request.duration") + .doesNotContain("Attach", "Feature"); // statsbeat + }); + } + + private static boolean isHistogramMetric(Envelope envelope) { + if (envelope.getData().getBaseType().equals("MetricData")) { + MetricData data = (MetricData) ((Data) envelope.getData()).getBaseData(); + return data.getMetrics().get(0).getName().equals("histogram-test-otlp-exporter"); + } + return false; + } + + private static boolean isStableOtelMetric(Envelope envelope) { + if (envelope.getData().getBaseType().equals("MetricData")) { + MetricData data = (MetricData) ((Data) envelope.getData()).getBaseData(); + return data.getMetrics().get(0).getName().equals("http.server.request.duration") + && data.getProperties().get("http.response.status_code") != null; + } + return false; + } + + private void verifyStatsbeatSentToBreezeEndpoint() throws Exception { + List statsbeatMetricList = + testing.mockedIngestion.waitForItems("MetricData", OtlpTest::isAttachStatsbeat, 1); + Envelope statsbeatEnvelope = statsbeatMetricList.get(0); + MetricData statsbeatMetricData = + (MetricData) ((Data) statsbeatEnvelope.getData()).getBaseData(); + assertThat(statsbeatMetricData.getMetrics().get(0).getName()).isEqualTo("Attach"); + assertThat(statsbeatMetricData.getProperties().get("rp")).isNotNull(); + assertThat(statsbeatMetricData.getProperties().get("attach")).isEqualTo("StandaloneAuto"); + + List features = + testing.mockedIngestion.waitForItems("MetricData", OtlpTest::isFeatureStatsbeat, 2); + Envelope featureEnvelope = features.get(0); + MetricData featureMetricData = (MetricData) ((Data) featureEnvelope.getData()).getBaseData(); + assertThat(featureMetricData.getMetrics().get(0).getName()).isEqualTo("Feature"); + assertThat(featureMetricData.getProperties().get("type")).isNotEmpty(); + + List requestSuccessCounts = + testing.mockedIngestion.waitForItems("MetricData", OtlpTest::isRequestSuccessCount, 1); + Envelope rscEnvelope = requestSuccessCounts.get(0); + MetricData rscMetricData = (MetricData) ((Data) rscEnvelope.getData()).getBaseData(); + assertThat(rscMetricData.getMetrics().get(0).getName()).isEqualTo("Request_Success_Count"); + assertThat(rscMetricData.getProperties().get("endpoint")).isEqualTo("breeze"); + + List requestDurations = + testing.mockedIngestion.waitForItems("MetricData", OtlpTest::isRequestDuration, 1); + Envelope rdEnvelope = requestDurations.get(0); + MetricData rdMetricData = (MetricData) ((Data) rdEnvelope.getData()).getBaseData(); + assertThat(rdMetricData.getMetrics().get(0).getName()).isEqualTo("Request_Duration"); + assertThat(rdMetricData.getProperties().get("endpoint")).isEqualTo("breeze"); + } + + private static boolean isAttachStatsbeat(Envelope envelope) { + if (envelope.getData().getBaseType().equals("MetricData")) { + MetricData data = (MetricData) ((Data) envelope.getData()).getBaseData(); + return data.getMetrics().get(0).getName().equals("Attach"); + } + return false; + } + + private static boolean isFeatureStatsbeat(Envelope envelope) { + if (envelope.getData().getBaseType().equals("MetricData")) { + MetricData data = (MetricData) ((Data) envelope.getData()).getBaseData(); + return data.getMetrics().get(0).getName().equals("Feature"); + } + return false; + } + + private static boolean isRequestSuccessCount(Envelope envelope) { + if (envelope.getData().getBaseType().equals("MetricData")) { + MetricData data = (MetricData) ((Data) envelope.getData()).getBaseData(); + return data.getMetrics().get(0).getName().equals("Request_Success_Count"); + } + return false; + } + + private static boolean isRequestDuration(Envelope envelope) { + if (envelope.getData().getBaseType().equals("MetricData")) { + MetricData data = (MetricData) ((Data) envelope.getData()).getBaseData(); + return data.getMetrics().get(0).getName().equals("Request_Duration"); + } + return false; + } + + @Environment(TOMCAT_8_JAVA_8) + static class Tomcat8Java8Test extends OtlpTest {} +} From bf6a1b1d1794533cf8088b7d1ffbfb3da2265976 Mon Sep 17 00:00:00 2001 From: Sean Li Date: Thu, 22 Jan 2026 00:00:23 -0800 Subject: [PATCH 05/15] Modify original OtlpTest to test APPLICATIONINSIGHTS_METRICS_TO_LOGANALYTICS_ENABLED env var on AKS --- .../smoketest/OtlpLogAnalyticsOnAksTest.java | 22 +++++++++++-------- .../smoketest/SmokeTestExtension.java | 22 ++++++++++++++++--- .../smoketest/SmokeTestExtensionBuilder.java | 10 ++++++++- 3 files changed, 41 insertions(+), 13 deletions(-) diff --git a/smoke-tests/apps/OtlpMetrics/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/OtlpLogAnalyticsOnAksTest.java b/smoke-tests/apps/OtlpMetrics/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/OtlpLogAnalyticsOnAksTest.java index b66c1509d4..90fe915de6 100644 --- a/smoke-tests/apps/OtlpMetrics/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/OtlpLogAnalyticsOnAksTest.java +++ b/smoke-tests/apps/OtlpMetrics/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/OtlpLogAnalyticsOnAksTest.java @@ -25,10 +25,14 @@ classes = {OtlpApplication.class}, webEnvironment = RANDOM_PORT) @UseAgent -abstract class OtlpTest { +abstract class OtlpLogAnalyticsOnAksTest { @RegisterExtension - static final SmokeTestExtension testing = SmokeTestExtension.builder().useOtlpEndpoint().build(); + static final SmokeTestExtension testing = SmokeTestExtension.builder() + .setEnvVar("APPLICATIONINSIGHTS_METRICS_TO_LOGANALYTICS_ENABLED", "true") + .setEnvVar("AKS_ARM_NAMESPACE_ID", "dummy-aks-namespace") + .useOtlpEndpointOnly() + .build(); @Test @TargetUri("/ping") @@ -41,14 +45,14 @@ public void testOtlpTelemetry() throws Exception { // verify custom histogram metric sent to Application Insights endpoint List metricList = - testing.mockedIngestion.waitForItems("MetricData", OtlpTest::isHistogramMetric, 1); + testing.mockedIngestion.waitForItems("MetricData", OtlpLogAnalyticsOnAksTest::isHistogramMetric, 1); Envelope metricEnvelope = metricList.get(0); MetricData metricData = (MetricData) ((Data) metricEnvelope.getData()).getBaseData(); assertThat(metricData.getMetrics().get(0).getName()).isEqualTo("histogram-test-otlp-exporter"); // verify stable otel metric sent to Application Insights endpoint List stableOtelMetrics = - testing.mockedIngestion.waitForItems("MetricData", OtlpTest::isStableOtelMetric, 1); + testing.mockedIngestion.waitForItems("MetricData", OtlpLogAnalyticsOnAksTest::isStableOtelMetric, 1); Envelope stableOtelMetricEnvelope = stableOtelMetrics.get(0); assertThat( ((MetricData) ((Data) stableOtelMetricEnvelope.getData()).getBaseData()) @@ -117,7 +121,7 @@ private static boolean isStableOtelMetric(Envelope envelope) { private void verifyStatsbeatSentToBreezeEndpoint() throws Exception { List statsbeatMetricList = - testing.mockedIngestion.waitForItems("MetricData", OtlpTest::isAttachStatsbeat, 1); + testing.mockedIngestion.waitForItems("MetricData", OtlpLogAnalyticsOnAksTest::isAttachStatsbeat, 1); Envelope statsbeatEnvelope = statsbeatMetricList.get(0); MetricData statsbeatMetricData = (MetricData) ((Data) statsbeatEnvelope.getData()).getBaseData(); @@ -126,21 +130,21 @@ private void verifyStatsbeatSentToBreezeEndpoint() throws Exception { assertThat(statsbeatMetricData.getProperties().get("attach")).isEqualTo("StandaloneAuto"); List features = - testing.mockedIngestion.waitForItems("MetricData", OtlpTest::isFeatureStatsbeat, 2); + testing.mockedIngestion.waitForItems("MetricData", OtlpLogAnalyticsOnAksTest::isFeatureStatsbeat, 2); Envelope featureEnvelope = features.get(0); MetricData featureMetricData = (MetricData) ((Data) featureEnvelope.getData()).getBaseData(); assertThat(featureMetricData.getMetrics().get(0).getName()).isEqualTo("Feature"); assertThat(featureMetricData.getProperties().get("type")).isNotEmpty(); List requestSuccessCounts = - testing.mockedIngestion.waitForItems("MetricData", OtlpTest::isRequestSuccessCount, 1); + testing.mockedIngestion.waitForItems("MetricData", OtlpLogAnalyticsOnAksTest::isRequestSuccessCount, 1); Envelope rscEnvelope = requestSuccessCounts.get(0); MetricData rscMetricData = (MetricData) ((Data) rscEnvelope.getData()).getBaseData(); assertThat(rscMetricData.getMetrics().get(0).getName()).isEqualTo("Request_Success_Count"); assertThat(rscMetricData.getProperties().get("endpoint")).isEqualTo("breeze"); List requestDurations = - testing.mockedIngestion.waitForItems("MetricData", OtlpTest::isRequestDuration, 1); + testing.mockedIngestion.waitForItems("MetricData", OtlpLogAnalyticsOnAksTest::isRequestDuration, 1); Envelope rdEnvelope = requestDurations.get(0); MetricData rdMetricData = (MetricData) ((Data) rdEnvelope.getData()).getBaseData(); assertThat(rdMetricData.getMetrics().get(0).getName()).isEqualTo("Request_Duration"); @@ -180,5 +184,5 @@ private static boolean isRequestDuration(Envelope envelope) { } @Environment(TOMCAT_8_JAVA_8) - static class Tomcat8Java8Test extends OtlpTest {} + static class Tomcat8Java8Test extends OtlpLogAnalyticsOnAksTest {} } diff --git a/smoke-tests/framework/src/main/java/com/microsoft/applicationinsights/smoketest/SmokeTestExtension.java b/smoke-tests/framework/src/main/java/com/microsoft/applicationinsights/smoketest/SmokeTestExtension.java index ad7245cf5a..c3337724ea 100644 --- a/smoke-tests/framework/src/main/java/com/microsoft/applicationinsights/smoketest/SmokeTestExtension.java +++ b/smoke-tests/framework/src/main/java/com/microsoft/applicationinsights/smoketest/SmokeTestExtension.java @@ -114,6 +114,7 @@ public class SmokeTestExtension private final List jvmArgs; private final boolean useDefaultHttpPort; private final boolean useOtlpEndpoint; + private final boolean useOtlpEndpointOnly; public static SmokeTestExtension create() { return builder().build(); @@ -139,7 +140,8 @@ public static SmokeTestExtensionBuilder builder() { Map envVars, List jvmArgs, boolean useDefaultHttpPort, - boolean useOtlpEndpoint) { + boolean useOtlpEndpoint, + boolean useOtlpEndpointOnly) { this.skipHealthCheck = skipHealthCheck; this.readOnly = readOnly; this.dependencyContainer = dependencyContainer; @@ -169,6 +171,7 @@ public static SmokeTestExtensionBuilder builder() { this.jvmArgs = jvmArgs; this.useDefaultHttpPort = useDefaultHttpPort; this.useOtlpEndpoint = useOtlpEndpoint; + this.useOtlpEndpointOnly = useOtlpEndpointOnly; mockedIngestion = new MockedAppInsightsIngestionServer(useOld3xAgent); } @@ -220,7 +223,7 @@ private void prepareEnvironment(Environment environment) throws Exception { mockedIngestion.startServer(); mockedIngestion.setRequestLoggingEnabled(true); mockedIngestion.setQuickPulseRequestLoggingEnabled(true); - if (useOtlpEndpoint) { + if (useOtlpEndpoint || useOtlpEndpointOnly) { mockedOtlpIngestion.startServer(); } network = Network.newNetwork(); @@ -430,6 +433,12 @@ private void startTestApplicationContainer() throws Exception { envVars.put("OTEL_EXPORTER_OTLP_PROTOCOL", "http/protobuf"); } + if (useOtlpEndpointOnly) { + envVars.put("OTEL_METRICS_EXPORTER", "otlp"); + envVars.put("OTEL_EXPORTER_OTLP_METRICS_ENDPOINT", FAKE_OTLP_INGESTION_ENDPOINT); + envVars.put("OTEL_EXPORTER_OTLP_PROTOCOL", "http/protobuf"); + } + GenericContainer container; if (REMOTE_DEBUG || useDefaultHttpPort) { FixedHostPortGenericContainer fixedPortContainer = @@ -479,6 +488,13 @@ private void startTestApplicationContainer() throws Exception { javaToolOptions.add("-Dotel.exporter.otlp.metrics.endpoint=" + FAKE_OTLP_INGESTION_ENDPOINT); javaToolOptions.add("-Dotel.exporter.otlp.protocol=http/protobuf"); } + if (useOtlpEndpointOnly) { + // TODO (trask) don't use azure_monitor exporter for smoke test health check + javaToolOptions.add("-Dotel.metrics.exporter=otlp"); + javaToolOptions.add("-Dotel.exporter.otlp.metrics.endpoint=" + FAKE_OTLP_INGESTION_ENDPOINT); + javaToolOptions.add("-Dotel.exporter.otlp.protocol=http/protobuf"); + } + if (REMOTE_DEBUG) { javaToolOptions.add( "-agentlib:jdwp=transport=dt_socket,address=0.0.0.0:5005,server=y,suspend=y"); @@ -579,7 +595,7 @@ public void afterAll(ExtensionContext context) throws Exception { mockedIngestion.stopServer(); mockedIngestion.setRequestLoggingEnabled(false); mockedIngestion.setQuickPulseRequestLoggingEnabled(false); - if (useOtlpEndpoint) { + if (useOtlpEndpoint || useOtlpEndpointOnly) { mockedOtlpIngestion.stopServer(); } } diff --git a/smoke-tests/framework/src/main/java/com/microsoft/applicationinsights/smoketest/SmokeTestExtensionBuilder.java b/smoke-tests/framework/src/main/java/com/microsoft/applicationinsights/smoketest/SmokeTestExtensionBuilder.java index 610141bb78..922c80135e 100644 --- a/smoke-tests/framework/src/main/java/com/microsoft/applicationinsights/smoketest/SmokeTestExtensionBuilder.java +++ b/smoke-tests/framework/src/main/java/com/microsoft/applicationinsights/smoketest/SmokeTestExtensionBuilder.java @@ -29,6 +29,7 @@ public class SmokeTestExtensionBuilder { private final List jvmArgs = new ArrayList<>(); private boolean useDefaultHttpPort; private boolean useOtlpEndpoint; + private boolean useOtlpEndpointOnly; public SmokeTestExtensionBuilder setDependencyContainer( String envVarName, GenericContainer container) { @@ -108,6 +109,12 @@ public SmokeTestExtensionBuilder useOtlpEndpoint() { return this; } + + public SmokeTestExtensionBuilder useOtlpEndpointOnly() { + this.useOtlpEndpointOnly = true; + return this; + } + public SmokeTestExtension build() { return new SmokeTestExtension( dependencyContainer, @@ -125,6 +132,7 @@ public SmokeTestExtension build() { envVars, jvmArgs, useDefaultHttpPort, - useOtlpEndpoint); + useOtlpEndpoint, + useOtlpEndpointOnly); } } From 43175329ab06a7bfb9368a701c0698ab143dd731 Mon Sep 17 00:00:00 2001 From: Sean Li Date: Thu, 22 Jan 2026 00:25:31 -0800 Subject: [PATCH 06/15] Add additional JDK+server combos --- .../smoketest/OtlpLogAnalyticsOnAksTest.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/smoke-tests/apps/OtlpMetrics/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/OtlpLogAnalyticsOnAksTest.java b/smoke-tests/apps/OtlpMetrics/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/OtlpLogAnalyticsOnAksTest.java index 90fe915de6..eb33f4d85d 100644 --- a/smoke-tests/apps/OtlpMetrics/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/OtlpLogAnalyticsOnAksTest.java +++ b/smoke-tests/apps/OtlpMetrics/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/OtlpLogAnalyticsOnAksTest.java @@ -3,7 +3,16 @@ package com.microsoft.applicationinsights.smoketest; +import static com.microsoft.applicationinsights.smoketest.EnvironmentValue.TOMCAT_8_JAVA_11; +import static com.microsoft.applicationinsights.smoketest.EnvironmentValue.TOMCAT_8_JAVA_11_OPENJ9; +import static com.microsoft.applicationinsights.smoketest.EnvironmentValue.TOMCAT_8_JAVA_17; +import static com.microsoft.applicationinsights.smoketest.EnvironmentValue.TOMCAT_8_JAVA_17_OPENJ9; +import static com.microsoft.applicationinsights.smoketest.EnvironmentValue.TOMCAT_8_JAVA_21; +import static com.microsoft.applicationinsights.smoketest.EnvironmentValue.TOMCAT_8_JAVA_21_OPENJ9; +import static com.microsoft.applicationinsights.smoketest.EnvironmentValue.TOMCAT_8_JAVA_25; +import static com.microsoft.applicationinsights.smoketest.EnvironmentValue.TOMCAT_8_JAVA_25_OPENJ9; import static com.microsoft.applicationinsights.smoketest.EnvironmentValue.TOMCAT_8_JAVA_8; +import static com.microsoft.applicationinsights.smoketest.EnvironmentValue.TOMCAT_8_JAVA_8_OPENJ9; import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; @@ -185,4 +194,31 @@ private static boolean isRequestDuration(Envelope envelope) { @Environment(TOMCAT_8_JAVA_8) static class Tomcat8Java8Test extends OtlpLogAnalyticsOnAksTest {} + + @Environment(TOMCAT_8_JAVA_8_OPENJ9) + static class Tomcat8Java8OpenJ9Test extends OtlpLogAnalyticsOnAksTest {} + + @Environment(TOMCAT_8_JAVA_11) + static class Tomcat8Java11Test extends OtlpLogAnalyticsOnAksTest {} + + @Environment(TOMCAT_8_JAVA_11_OPENJ9) + static class Tomcat8Java11OpenJ9Test extends OtlpLogAnalyticsOnAksTest {} + + @Environment(TOMCAT_8_JAVA_17) + static class Tomcat8Java17Test extends OtlpLogAnalyticsOnAksTest {} + + @Environment(TOMCAT_8_JAVA_17_OPENJ9) + static class Tomcat8Java17OpenJ9Test extends OtlpLogAnalyticsOnAksTest {} + + @Environment(TOMCAT_8_JAVA_21) + static class Tomcat8Java21Test extends OtlpLogAnalyticsOnAksTest {} + + @Environment(TOMCAT_8_JAVA_21_OPENJ9) + static class Tomcat8Java21OpenJ9Test extends OtlpLogAnalyticsOnAksTest {} + + @Environment(TOMCAT_8_JAVA_25) + static class Tomcat8Java23Test extends OtlpLogAnalyticsOnAksTest {} + + @Environment(TOMCAT_8_JAVA_25_OPENJ9) + static class Tomcat8Java23OpenJ9Test extends OtlpLogAnalyticsOnAksTest {} } From f1cef228ef00c3ae78094e59b828cde43a36812b Mon Sep 17 00:00:00 2001 From: Sean Li Date: Thu, 22 Jan 2026 00:48:44 -0800 Subject: [PATCH 07/15] `./gradlew spotlessApply` --- .../applicationinsights/smoketest/SmokeTestExtensionBuilder.java | 1 - 1 file changed, 1 deletion(-) diff --git a/smoke-tests/framework/src/main/java/com/microsoft/applicationinsights/smoketest/SmokeTestExtensionBuilder.java b/smoke-tests/framework/src/main/java/com/microsoft/applicationinsights/smoketest/SmokeTestExtensionBuilder.java index 922c80135e..e223c64aa0 100644 --- a/smoke-tests/framework/src/main/java/com/microsoft/applicationinsights/smoketest/SmokeTestExtensionBuilder.java +++ b/smoke-tests/framework/src/main/java/com/microsoft/applicationinsights/smoketest/SmokeTestExtensionBuilder.java @@ -109,7 +109,6 @@ public SmokeTestExtensionBuilder useOtlpEndpoint() { return this; } - public SmokeTestExtensionBuilder useOtlpEndpointOnly() { this.useOtlpEndpointOnly = true; return this; From a2f062adfb2bbdefb001d3165867195722b1ce3b Mon Sep 17 00:00:00 2001 From: Sean Li Date: Thu, 22 Jan 2026 00:52:27 -0800 Subject: [PATCH 08/15] more of `./gradlew spotlessApply` --- .../internal/init/AiConfigCustomizer.java | 3 +- .../smoketest/OtlpLogAnalyticsOnAksTest.java | 29 ++++++++++++------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java index 72fffbe5b1..b982c4d0b0 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java @@ -121,7 +121,8 @@ public Map apply(ConfigProperties otelConfig) { String metricsToLogAnalyticsEnabledEnvVar = otelConfig.getString("applicationinsights.metrics.to.loganalytics.enabled"); boolean metricsToLogAnalyticsActivated = - metricsToLogAnalyticsEnabledEnvVar == null || Boolean.parseBoolean(metricsToLogAnalyticsEnabledEnvVar); + metricsToLogAnalyticsEnabledEnvVar == null + || Boolean.parseBoolean(metricsToLogAnalyticsEnabledEnvVar); boolean isAks = false; try { diff --git a/smoke-tests/apps/OtlpMetrics/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/OtlpLogAnalyticsOnAksTest.java b/smoke-tests/apps/OtlpMetrics/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/OtlpLogAnalyticsOnAksTest.java index eb33f4d85d..246cbfb687 100644 --- a/smoke-tests/apps/OtlpMetrics/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/OtlpLogAnalyticsOnAksTest.java +++ b/smoke-tests/apps/OtlpMetrics/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/OtlpLogAnalyticsOnAksTest.java @@ -37,11 +37,12 @@ abstract class OtlpLogAnalyticsOnAksTest { @RegisterExtension - static final SmokeTestExtension testing = SmokeTestExtension.builder() - .setEnvVar("APPLICATIONINSIGHTS_METRICS_TO_LOGANALYTICS_ENABLED", "true") - .setEnvVar("AKS_ARM_NAMESPACE_ID", "dummy-aks-namespace") - .useOtlpEndpointOnly() - .build(); + static final SmokeTestExtension testing = + SmokeTestExtension.builder() + .setEnvVar("APPLICATIONINSIGHTS_METRICS_TO_LOGANALYTICS_ENABLED", "true") + .setEnvVar("AKS_ARM_NAMESPACE_ID", "dummy-aks-namespace") + .useOtlpEndpointOnly() + .build(); @Test @TargetUri("/ping") @@ -54,14 +55,16 @@ public void testOtlpTelemetry() throws Exception { // verify custom histogram metric sent to Application Insights endpoint List metricList = - testing.mockedIngestion.waitForItems("MetricData", OtlpLogAnalyticsOnAksTest::isHistogramMetric, 1); + testing.mockedIngestion.waitForItems( + "MetricData", OtlpLogAnalyticsOnAksTest::isHistogramMetric, 1); Envelope metricEnvelope = metricList.get(0); MetricData metricData = (MetricData) ((Data) metricEnvelope.getData()).getBaseData(); assertThat(metricData.getMetrics().get(0).getName()).isEqualTo("histogram-test-otlp-exporter"); // verify stable otel metric sent to Application Insights endpoint List stableOtelMetrics = - testing.mockedIngestion.waitForItems("MetricData", OtlpLogAnalyticsOnAksTest::isStableOtelMetric, 1); + testing.mockedIngestion.waitForItems( + "MetricData", OtlpLogAnalyticsOnAksTest::isStableOtelMetric, 1); Envelope stableOtelMetricEnvelope = stableOtelMetrics.get(0); assertThat( ((MetricData) ((Data) stableOtelMetricEnvelope.getData()).getBaseData()) @@ -130,7 +133,8 @@ private static boolean isStableOtelMetric(Envelope envelope) { private void verifyStatsbeatSentToBreezeEndpoint() throws Exception { List statsbeatMetricList = - testing.mockedIngestion.waitForItems("MetricData", OtlpLogAnalyticsOnAksTest::isAttachStatsbeat, 1); + testing.mockedIngestion.waitForItems( + "MetricData", OtlpLogAnalyticsOnAksTest::isAttachStatsbeat, 1); Envelope statsbeatEnvelope = statsbeatMetricList.get(0); MetricData statsbeatMetricData = (MetricData) ((Data) statsbeatEnvelope.getData()).getBaseData(); @@ -139,21 +143,24 @@ private void verifyStatsbeatSentToBreezeEndpoint() throws Exception { assertThat(statsbeatMetricData.getProperties().get("attach")).isEqualTo("StandaloneAuto"); List features = - testing.mockedIngestion.waitForItems("MetricData", OtlpLogAnalyticsOnAksTest::isFeatureStatsbeat, 2); + testing.mockedIngestion.waitForItems( + "MetricData", OtlpLogAnalyticsOnAksTest::isFeatureStatsbeat, 2); Envelope featureEnvelope = features.get(0); MetricData featureMetricData = (MetricData) ((Data) featureEnvelope.getData()).getBaseData(); assertThat(featureMetricData.getMetrics().get(0).getName()).isEqualTo("Feature"); assertThat(featureMetricData.getProperties().get("type")).isNotEmpty(); List requestSuccessCounts = - testing.mockedIngestion.waitForItems("MetricData", OtlpLogAnalyticsOnAksTest::isRequestSuccessCount, 1); + testing.mockedIngestion.waitForItems( + "MetricData", OtlpLogAnalyticsOnAksTest::isRequestSuccessCount, 1); Envelope rscEnvelope = requestSuccessCounts.get(0); MetricData rscMetricData = (MetricData) ((Data) rscEnvelope.getData()).getBaseData(); assertThat(rscMetricData.getMetrics().get(0).getName()).isEqualTo("Request_Success_Count"); assertThat(rscMetricData.getProperties().get("endpoint")).isEqualTo("breeze"); List requestDurations = - testing.mockedIngestion.waitForItems("MetricData", OtlpLogAnalyticsOnAksTest::isRequestDuration, 1); + testing.mockedIngestion.waitForItems( + "MetricData", OtlpLogAnalyticsOnAksTest::isRequestDuration, 1); Envelope rdEnvelope = requestDurations.get(0); MetricData rdMetricData = (MetricData) ((Data) rdEnvelope.getData()).getBaseData(); assertThat(rdMetricData.getMetrics().get(0).getName()).isEqualTo("Request_Duration"); From 1412085ff6bd128920b136ec3639cab77fa8a3dd Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Thu, 22 Jan 2026 14:32:05 -0800 Subject: [PATCH 09/15] extract a couple of methods --- .../internal/init/AiConfigCustomizer.java | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java index b982c4d0b0..fc0270b53a 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java @@ -117,36 +117,11 @@ public Map apply(ConfigProperties otelConfig) { } String metricsExporter = otelConfig.getString("otel.metrics.exporter"); - String azureMonitorName = AzureMonitorExporterProviderKeys.EXPORTER_NAME; - String metricsToLogAnalyticsEnabledEnvVar = - otelConfig.getString("applicationinsights.metrics.to.loganalytics.enabled"); - boolean metricsToLogAnalyticsActivated = - metricsToLogAnalyticsEnabledEnvVar == null - || Boolean.parseBoolean(metricsToLogAnalyticsEnabledEnvVar); - - boolean isAks = false; - try { - String aksNamespaceId = System.getenv("AKS_ARM_NAMESPACE_ID"); - isAks = !Strings.isNullOrEmpty(aksNamespaceId); - } catch (SecurityException ignored) { - // if env is not accessible, assume not AKS - } - - if (metricsToLogAnalyticsActivated && isAks) { - if (metricsExporter == null - || metricsExporter.isEmpty() - || metricsExporter.equalsIgnoreCase("none")) { - // enable Azure Monitor metrics exporter by default when flag is enabled - properties.put("otel.metrics.exporter", azureMonitorName); - } else if (!metricsExporter.contains(azureMonitorName)) { - // ensure Azure Monitor exporter is included when flag is enabled - properties.put("otel.metrics.exporter", metricsExporter + "," + azureMonitorName); - } - } else { - if (metricsExporter == null) { - // preserve previous behavior: override default "otlp" so exporter can be configured later - properties.put("otel.metrics.exporter", "none"); - } + if (isAksIntegratedAttach(otelConfig)) { + properties.put("otel.metrics.exporter", addAzureMonitorIfNotPresent(metricsExporter)); + } else if (metricsExporter == null) { + // this overrides the default "otlp" so the exporter can be configured later + properties.put("otel.metrics.exporter", "none"); } String logsExporter = otelConfig.getString("otel.logs.exporter"); @@ -355,6 +330,31 @@ private static void enableInstrumentations( } } + private static boolean isAksIntegratedAttach(ConfigProperties otelConfig) { + String envVar = otelConfig.getString("applicationinsights.metrics.to.loganalytics.enabled"); + boolean metricsToLogAnalyticsEnabled = envVar == null || Boolean.parseBoolean(envVar); + + boolean isAks = false; + try { + String aksNamespaceId = System.getenv("AKS_ARM_NAMESPACE_ID"); + isAks = !Strings.isNullOrEmpty(aksNamespaceId); + } catch (SecurityException ignored) { + // if env is not accessible, assume not AKS + } + + return metricsToLogAnalyticsEnabled && isAks; + } + + private static String addAzureMonitorIfNotPresent(String metricsExporter) { + String azureMonitorName = AzureMonitorExporterProviderKeys.EXPORTER_NAME; + if (metricsExporter == null || metricsExporter.isEmpty() || metricsExporter.equals("none")) { + return azureMonitorName; + } else if (!metricsExporter.contains(azureMonitorName)) { + return metricsExporter + "," + azureMonitorName; + } + return metricsExporter; + } + private static void setHttpHeaderConfiguration( Map properties, String propertyName, List headers) { if (!headers.isEmpty()) { From 71c731a184aa8b18e7cc5dbb4f2355935ab0bd88 Mon Sep 17 00:00:00 2001 From: Sean Li Date: Thu, 22 Jan 2026 15:15:19 -0800 Subject: [PATCH 10/15] don't catch the permission exception --- .../agent/internal/init/AiConfigCustomizer.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java index fc0270b53a..91c5243041 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java @@ -334,13 +334,8 @@ private static boolean isAksIntegratedAttach(ConfigProperties otelConfig) { String envVar = otelConfig.getString("applicationinsights.metrics.to.loganalytics.enabled"); boolean metricsToLogAnalyticsEnabled = envVar == null || Boolean.parseBoolean(envVar); - boolean isAks = false; - try { - String aksNamespaceId = System.getenv("AKS_ARM_NAMESPACE_ID"); - isAks = !Strings.isNullOrEmpty(aksNamespaceId); - } catch (SecurityException ignored) { - // if env is not accessible, assume not AKS - } + String aksNamespaceId = System.getenv("AKS_ARM_NAMESPACE_ID"); + boolean isAks = !Strings.isNullOrEmpty(aksNamespaceId); return metricsToLogAnalyticsEnabled && isAks; } From 8c6c78b56b0b11e22e39d393ff047a2df072c792 Mon Sep 17 00:00:00 2001 From: Sean Li Date: Thu, 22 Jan 2026 15:18:59 -0800 Subject: [PATCH 11/15] rename --- .../agent/internal/init/AiConfigCustomizer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java index 91c5243041..f098082f8c 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java @@ -117,7 +117,7 @@ public Map apply(ConfigProperties otelConfig) { } String metricsExporter = otelConfig.getString("otel.metrics.exporter"); - if (isAksIntegratedAttach(otelConfig)) { + if (isAksAttach(otelConfig)) { properties.put("otel.metrics.exporter", addAzureMonitorIfNotPresent(metricsExporter)); } else if (metricsExporter == null) { // this overrides the default "otlp" so the exporter can be configured later @@ -330,7 +330,7 @@ private static void enableInstrumentations( } } - private static boolean isAksIntegratedAttach(ConfigProperties otelConfig) { + private static boolean isAksAttach(ConfigProperties otelConfig) { String envVar = otelConfig.getString("applicationinsights.metrics.to.loganalytics.enabled"); boolean metricsToLogAnalyticsEnabled = envVar == null || Boolean.parseBoolean(envVar); From a19c8350b56731ac691e3b7f60c8a94d20489127 Mon Sep 17 00:00:00 2001 From: Sean Li Date: Thu, 22 Jan 2026 17:04:05 -0800 Subject: [PATCH 12/15] refactor conditions and add unit tests --- .../internal/init/AiConfigCustomizer.java | 72 ++++++++++----- .../internal/init/AiConfigCustomizerTest.java | 91 +++++++++++++++++++ 2 files changed, 141 insertions(+), 22 deletions(-) create mode 100644 agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizerTest.java diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java index f098082f8c..c7d8d5e225 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java @@ -117,8 +117,10 @@ public Map apply(ConfigProperties otelConfig) { } String metricsExporter = otelConfig.getString("otel.metrics.exporter"); - if (isAksAttach(otelConfig)) { - properties.put("otel.metrics.exporter", addAzureMonitorIfNotPresent(metricsExporter)); + String aksNamespaceId = System.getenv("AKS_ARM_NAMESPACE_ID"); + String metricsToLogAnalyticsEnabled = otelConfig.getString("applicationinsights.metrics.to.loganalytics.enabled"); + if (isAksAttach(aksNamespaceId)) { + properties.put("otel.metrics.exporter", updateMetricsExporter(metricsExporter, metricsToLogAnalyticsEnabled)); } else if (metricsExporter == null) { // this overrides the default "otlp" so the exporter can be configured later properties.put("otel.metrics.exporter", "none"); @@ -330,26 +332,6 @@ private static void enableInstrumentations( } } - private static boolean isAksAttach(ConfigProperties otelConfig) { - String envVar = otelConfig.getString("applicationinsights.metrics.to.loganalytics.enabled"); - boolean metricsToLogAnalyticsEnabled = envVar == null || Boolean.parseBoolean(envVar); - - String aksNamespaceId = System.getenv("AKS_ARM_NAMESPACE_ID"); - boolean isAks = !Strings.isNullOrEmpty(aksNamespaceId); - - return metricsToLogAnalyticsEnabled && isAks; - } - - private static String addAzureMonitorIfNotPresent(String metricsExporter) { - String azureMonitorName = AzureMonitorExporterProviderKeys.EXPORTER_NAME; - if (metricsExporter == null || metricsExporter.isEmpty() || metricsExporter.equals("none")) { - return azureMonitorName; - } else if (!metricsExporter.contains(azureMonitorName)) { - return metricsExporter + "," + azureMonitorName; - } - return metricsExporter; - } - private static void setHttpHeaderConfiguration( Map properties, String propertyName, List headers) { if (!headers.isEmpty()) { @@ -367,4 +349,50 @@ private static String join(List values, char separator) { } return sb.toString(); } + + // visible for tests + static boolean isAksAttach(String aksNamespaceId) { + return !Strings.isNullOrEmpty(aksNamespaceId); + } + + static String updateMetricsExporter(String metricsExporter, String metricsToLogAnalyticsEnabled) { + String azureMonitorName = AzureMonitorExporterProviderKeys.EXPORTER_NAME; + if (metricsExporter == null || metricsExporter.isEmpty()) { + if (metricsToLogAnalyticsEnabled == null || Boolean.parseBoolean(metricsToLogAnalyticsEnabled)) { + return azureMonitorName + ",otlp"; + } else { + return azureMonitorName; + } + } + + if (metricsToLogAnalyticsEnabled == null || metricsToLogAnalyticsEnabled.isEmpty()) { + if (metricsExporter.contains(azureMonitorName) && !metricsExporter.contains("otlp")) { + return metricsExporter + ",otlp"; + } + return metricsExporter; + } + + // If AMLE is true, make sure both otlp and azure monitor exporters are present + if (Boolean.parseBoolean(metricsToLogAnalyticsEnabled)) { + if (metricsExporter == null || metricsExporter.isEmpty() || metricsExporter.equals("none")) { + return azureMonitorName + ",otlp"; + } + if (!metricsExporter.contains(azureMonitorName)) { + metricsExporter += "," + azureMonitorName; + } + if (!metricsExporter.contains("otlp")) { + metricsExporter += ",otlp"; + } + return metricsExporter; + } else { + // If AMLE is false, make sure only azure monitor exporter is present + if (metricsExporter == null || metricsExporter.isEmpty()) { + return azureMonitorName; + } + if (metricsExporter.contains(azureMonitorName) && metricsExporter.contains("otlp")) { + return metricsExporter.replace(",otlp", "").replace("otlp,", ""); + } + return metricsExporter; + } + } } diff --git a/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizerTest.java b/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizerTest.java new file mode 100644 index 0000000000..322a7b0452 --- /dev/null +++ b/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizerTest.java @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.applicationinsights.agent.internal.init; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class AiConfigCustomizerTest { + + @Test + void isAksAttach() { + assertThat(AiConfigCustomizer.isAksAttach("dummy-aks-namespace")).isTrue(); + + assertThat(AiConfigCustomizer.isAksAttach(null)).isFalse(); + assertThat(AiConfigCustomizer.isAksAttach("")).isFalse(); + } + + @Test + void updateMetricsExporter_ExporterUnset() { + + assertThat(AiConfigCustomizer.updateMetricsExporter(null, null)) + .isEqualTo("azure_monitor,otlp"); + + assertThat(AiConfigCustomizer.updateMetricsExporter("", null)) + .isEqualTo("azure_monitor,otlp"); + + assertThat(AiConfigCustomizer.updateMetricsExporter(null, "true")) + .isEqualTo("azure_monitor,otlp"); + + assertThat(AiConfigCustomizer.updateMetricsExporter(null, "True")) + .isEqualTo("azure_monitor,otlp"); + } + + static Stream stringPairs() { + return Stream.of( + Arguments.of("none", "none"), + Arguments.of("azure_monitor", "azure_monitor,otlp"), + Arguments.of("azure_monitor,otlp", "azure_monitor,otlp"), + Arguments.of("otlp", "otlp")); + } + + @ParameterizedTest + @MethodSource("stringPairs") + void updateMetricsExporter_ExporterSet_AMLE_Unset(String metricsExporter, String expectAzureMonitor) { + assertThat(AiConfigCustomizer.updateMetricsExporter(metricsExporter, null)) + .isEqualTo(expectAzureMonitor); + assertThat(AiConfigCustomizer.updateMetricsExporter(metricsExporter, "")) + .isEqualTo(expectAzureMonitor); + } + + static Stream stringPairs2() { + return Stream.of( + Arguments.of("none", "azure_monitor,otlp"), + Arguments.of("azure_monitor", "azure_monitor,otlp"), + Arguments.of("azure_monitor,otlp", "azure_monitor,otlp"), + Arguments.of("otlp", "otlp,azure_monitor")); + } + + @ParameterizedTest + @MethodSource("stringPairs2") + void updateMetricsExporter_ExporterSet_AMLE_True(String metricsExporter, String expectAzureMonitor) { + assertThat(AiConfigCustomizer.updateMetricsExporter(metricsExporter, "true")) + .isEqualTo(expectAzureMonitor); + assertThat(AiConfigCustomizer.updateMetricsExporter(metricsExporter, "True")) + .isEqualTo(expectAzureMonitor); + } + + static Stream stringPairs3() { + return Stream.of( + Arguments.of("none", "none"), + Arguments.of("azure_monitor", "azure_monitor"), + Arguments.of("azure_monitor,otlp", "azure_monitor"), + Arguments.of("otlp", "otlp")); + } + + @ParameterizedTest + @MethodSource("stringPairs3") + void updateMetricsExporter_ExporterSet_AMLE_False(String metricsExporter, String expectAzureMonitor) { + assertThat(AiConfigCustomizer.updateMetricsExporter(metricsExporter, "false")) + .isEqualTo(expectAzureMonitor); + assertThat(AiConfigCustomizer.updateMetricsExporter(metricsExporter, "False")) + .isEqualTo(expectAzureMonitor); + } +} From 1d5a4198e726c74b6ac8e7aa3e53ab1f0ce0a261 Mon Sep 17 00:00:00 2001 From: Sean Li Date: Thu, 22 Jan 2026 17:07:07 -0800 Subject: [PATCH 13/15] ./gradlew spotlessApply --- .../internal/init/AiConfigCustomizer.java | 10 ++-- .../internal/init/AiConfigCustomizerTest.java | 53 ++++++++++--------- 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java index c7d8d5e225..c31c6e0915 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java @@ -118,9 +118,12 @@ public Map apply(ConfigProperties otelConfig) { String metricsExporter = otelConfig.getString("otel.metrics.exporter"); String aksNamespaceId = System.getenv("AKS_ARM_NAMESPACE_ID"); - String metricsToLogAnalyticsEnabled = otelConfig.getString("applicationinsights.metrics.to.loganalytics.enabled"); + String metricsToLogAnalyticsEnabled = + otelConfig.getString("applicationinsights.metrics.to.loganalytics.enabled"); if (isAksAttach(aksNamespaceId)) { - properties.put("otel.metrics.exporter", updateMetricsExporter(metricsExporter, metricsToLogAnalyticsEnabled)); + properties.put( + "otel.metrics.exporter", + updateMetricsExporter(metricsExporter, metricsToLogAnalyticsEnabled)); } else if (metricsExporter == null) { // this overrides the default "otlp" so the exporter can be configured later properties.put("otel.metrics.exporter", "none"); @@ -358,7 +361,8 @@ static boolean isAksAttach(String aksNamespaceId) { static String updateMetricsExporter(String metricsExporter, String metricsToLogAnalyticsEnabled) { String azureMonitorName = AzureMonitorExporterProviderKeys.EXPORTER_NAME; if (metricsExporter == null || metricsExporter.isEmpty()) { - if (metricsToLogAnalyticsEnabled == null || Boolean.parseBoolean(metricsToLogAnalyticsEnabled)) { + if (metricsToLogAnalyticsEnabled == null + || Boolean.parseBoolean(metricsToLogAnalyticsEnabled)) { return azureMonitorName + ",otlp"; } else { return azureMonitorName; diff --git a/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizerTest.java b/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizerTest.java index 322a7b0452..253c96ba62 100644 --- a/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizerTest.java +++ b/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizerTest.java @@ -6,7 +6,6 @@ import static org.assertj.core.api.Assertions.assertThat; import java.util.stream.Stream; - import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -28,44 +27,45 @@ void updateMetricsExporter_ExporterUnset() { assertThat(AiConfigCustomizer.updateMetricsExporter(null, null)) .isEqualTo("azure_monitor,otlp"); - assertThat(AiConfigCustomizer.updateMetricsExporter("", null)) - .isEqualTo("azure_monitor,otlp"); - + assertThat(AiConfigCustomizer.updateMetricsExporter("", null)).isEqualTo("azure_monitor,otlp"); + assertThat(AiConfigCustomizer.updateMetricsExporter(null, "true")) .isEqualTo("azure_monitor,otlp"); - + assertThat(AiConfigCustomizer.updateMetricsExporter(null, "True")) .isEqualTo("azure_monitor,otlp"); } static Stream stringPairs() { return Stream.of( - Arguments.of("none", "none"), - Arguments.of("azure_monitor", "azure_monitor,otlp"), + Arguments.of("none", "none"), + Arguments.of("azure_monitor", "azure_monitor,otlp"), Arguments.of("azure_monitor,otlp", "azure_monitor,otlp"), - Arguments.of("otlp", "otlp")); - } - + Arguments.of("otlp", "otlp")); + } + @ParameterizedTest @MethodSource("stringPairs") - void updateMetricsExporter_ExporterSet_AMLE_Unset(String metricsExporter, String expectAzureMonitor) { + void updateMetricsExporter_ExporterSet_AMLE_Unset( + String metricsExporter, String expectAzureMonitor) { assertThat(AiConfigCustomizer.updateMetricsExporter(metricsExporter, null)) .isEqualTo(expectAzureMonitor); assertThat(AiConfigCustomizer.updateMetricsExporter(metricsExporter, "")) .isEqualTo(expectAzureMonitor); } - + static Stream stringPairs2() { return Stream.of( - Arguments.of("none", "azure_monitor,otlp"), - Arguments.of("azure_monitor", "azure_monitor,otlp"), - Arguments.of("azure_monitor,otlp", "azure_monitor,otlp"), - Arguments.of("otlp", "otlp,azure_monitor")); - } - + Arguments.of("none", "azure_monitor,otlp"), + Arguments.of("azure_monitor", "azure_monitor,otlp"), + Arguments.of("azure_monitor,otlp", "azure_monitor,otlp"), + Arguments.of("otlp", "otlp,azure_monitor")); + } + @ParameterizedTest @MethodSource("stringPairs2") - void updateMetricsExporter_ExporterSet_AMLE_True(String metricsExporter, String expectAzureMonitor) { + void updateMetricsExporter_ExporterSet_AMLE_True( + String metricsExporter, String expectAzureMonitor) { assertThat(AiConfigCustomizer.updateMetricsExporter(metricsExporter, "true")) .isEqualTo(expectAzureMonitor); assertThat(AiConfigCustomizer.updateMetricsExporter(metricsExporter, "True")) @@ -74,15 +74,16 @@ void updateMetricsExporter_ExporterSet_AMLE_True(String metricsExporter, String static Stream stringPairs3() { return Stream.of( - Arguments.of("none", "none"), - Arguments.of("azure_monitor", "azure_monitor"), - Arguments.of("azure_monitor,otlp", "azure_monitor"), - Arguments.of("otlp", "otlp")); - } - + Arguments.of("none", "none"), + Arguments.of("azure_monitor", "azure_monitor"), + Arguments.of("azure_monitor,otlp", "azure_monitor"), + Arguments.of("otlp", "otlp")); + } + @ParameterizedTest @MethodSource("stringPairs3") - void updateMetricsExporter_ExporterSet_AMLE_False(String metricsExporter, String expectAzureMonitor) { + void updateMetricsExporter_ExporterSet_AMLE_False( + String metricsExporter, String expectAzureMonitor) { assertThat(AiConfigCustomizer.updateMetricsExporter(metricsExporter, "false")) .isEqualTo(expectAzureMonitor); assertThat(AiConfigCustomizer.updateMetricsExporter(metricsExporter, "False")) From 68dcbc99c6bd72018c8fec7205a9b8d06ac8b042 Mon Sep 17 00:00:00 2001 From: Sean Li Date: Thu, 22 Jan 2026 17:46:32 -0800 Subject: [PATCH 14/15] refine with more clear rules --- .../internal/init/AiConfigCustomizer.java | 46 +++++++------------ .../internal/init/AiConfigCustomizerTest.java | 2 +- 2 files changed, 17 insertions(+), 31 deletions(-) diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java index c31c6e0915..d79bf74869 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java @@ -360,43 +360,29 @@ static boolean isAksAttach(String aksNamespaceId) { static String updateMetricsExporter(String metricsExporter, String metricsToLogAnalyticsEnabled) { String azureMonitorName = AzureMonitorExporterProviderKeys.EXPORTER_NAME; - if (metricsExporter == null || metricsExporter.isEmpty()) { - if (metricsToLogAnalyticsEnabled == null - || Boolean.parseBoolean(metricsToLogAnalyticsEnabled)) { - return azureMonitorName + ",otlp"; - } else { - return azureMonitorName; - } + // If AMLE is true, configure both otlp and azure monitor exporters + if (Boolean.parseBoolean(metricsToLogAnalyticsEnabled)) { + return azureMonitorName + ",otlp"; } + // If AMLE is unset: if (metricsToLogAnalyticsEnabled == null || metricsToLogAnalyticsEnabled.isEmpty()) { - if (metricsExporter.contains(azureMonitorName) && !metricsExporter.contains("otlp")) { - return metricsExporter + ",otlp"; - } - return metricsExporter; - } - - // If AMLE is true, make sure both otlp and azure monitor exporters are present - if (Boolean.parseBoolean(metricsToLogAnalyticsEnabled)) { - if (metricsExporter == null || metricsExporter.isEmpty() || metricsExporter.equals("none")) { + if (metricsExporter == null || metricsExporter.isEmpty()) { + // default is "azure_monitor,otlp" return azureMonitorName + ",otlp"; - } - if (!metricsExporter.contains(azureMonitorName)) { - metricsExporter += "," + azureMonitorName; - } - if (!metricsExporter.contains("otlp")) { + } else if (metricsExporter.contains(azureMonitorName) && !metricsExporter.contains("otlp")) { + // if azure monitor is already present and otlp is not, add otlp metricsExporter += ",otlp"; } return metricsExporter; - } else { - // If AMLE is false, make sure only azure monitor exporter is present - if (metricsExporter == null || metricsExporter.isEmpty()) { - return azureMonitorName; - } - if (metricsExporter.contains(azureMonitorName) && metricsExporter.contains("otlp")) { - return metricsExporter.replace(",otlp", "").replace("otlp,", ""); - } - return metricsExporter; } + + // If AMLE is false, configure only azure monitor exporter. + // 1. Default is azure monitor + // 2. If otlp is set, cancel it and replace with azure monitor (AMLE false has higher priority than otlp setting) + if (metricsExporter == null || metricsExporter.isEmpty() || metricsExporter.contains(azureMonitorName)) { + return azureMonitorName; + } + return metricsExporter; } } diff --git a/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizerTest.java b/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizerTest.java index 253c96ba62..119cb1aec9 100644 --- a/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizerTest.java +++ b/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizerTest.java @@ -59,7 +59,7 @@ static Stream stringPairs2() { Arguments.of("none", "azure_monitor,otlp"), Arguments.of("azure_monitor", "azure_monitor,otlp"), Arguments.of("azure_monitor,otlp", "azure_monitor,otlp"), - Arguments.of("otlp", "otlp,azure_monitor")); + Arguments.of("otlp", "azure_monitor,otlp")); } @ParameterizedTest From 730b25d5435883223987eccfda23615af50898ab Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Thu, 22 Jan 2026 20:48:48 -0800 Subject: [PATCH 15/15] simplify --- .../internal/init/AiConfigCustomizer.java | 44 +-------- .../agent/internal/init/SecondEntryPoint.java | 55 +++++++++++ .../internal/init/AiConfigCustomizerTest.java | 92 ------------------- .../internal/init/SecondEntryPointTest.java | 48 ++++++++++ 4 files changed, 104 insertions(+), 135 deletions(-) delete mode 100644 agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizerTest.java create mode 100644 agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/init/SecondEntryPointTest.java diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java index d79bf74869..5d17bb98b6 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java @@ -5,8 +5,6 @@ import static java.util.concurrent.TimeUnit.SECONDS; -import com.azure.monitor.opentelemetry.autoconfigure.implementation.AzureMonitorExporterProviderKeys; -import com.azure.monitor.opentelemetry.autoconfigure.implementation.utils.Strings; import com.microsoft.applicationinsights.agent.internal.configuration.Configuration; import com.microsoft.applicationinsights.agent.internal.legacyheaders.DelegatingPropagatorProvider; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; @@ -117,14 +115,7 @@ public Map apply(ConfigProperties otelConfig) { } String metricsExporter = otelConfig.getString("otel.metrics.exporter"); - String aksNamespaceId = System.getenv("AKS_ARM_NAMESPACE_ID"); - String metricsToLogAnalyticsEnabled = - otelConfig.getString("applicationinsights.metrics.to.loganalytics.enabled"); - if (isAksAttach(aksNamespaceId)) { - properties.put( - "otel.metrics.exporter", - updateMetricsExporter(metricsExporter, metricsToLogAnalyticsEnabled)); - } else if (metricsExporter == null) { + if (metricsExporter == null) { // this overrides the default "otlp" so the exporter can be configured later properties.put("otel.metrics.exporter", "none"); } @@ -352,37 +343,4 @@ private static String join(List values, char separator) { } return sb.toString(); } - - // visible for tests - static boolean isAksAttach(String aksNamespaceId) { - return !Strings.isNullOrEmpty(aksNamespaceId); - } - - static String updateMetricsExporter(String metricsExporter, String metricsToLogAnalyticsEnabled) { - String azureMonitorName = AzureMonitorExporterProviderKeys.EXPORTER_NAME; - // If AMLE is true, configure both otlp and azure monitor exporters - if (Boolean.parseBoolean(metricsToLogAnalyticsEnabled)) { - return azureMonitorName + ",otlp"; - } - - // If AMLE is unset: - if (metricsToLogAnalyticsEnabled == null || metricsToLogAnalyticsEnabled.isEmpty()) { - if (metricsExporter == null || metricsExporter.isEmpty()) { - // default is "azure_monitor,otlp" - return azureMonitorName + ",otlp"; - } else if (metricsExporter.contains(azureMonitorName) && !metricsExporter.contains("otlp")) { - // if azure monitor is already present and otlp is not, add otlp - metricsExporter += ",otlp"; - } - return metricsExporter; - } - - // If AMLE is false, configure only azure monitor exporter. - // 1. Default is azure monitor - // 2. If otlp is set, cancel it and replace with azure monitor (AMLE false has higher priority than otlp setting) - if (metricsExporter == null || metricsExporter.isEmpty() || metricsExporter.contains(azureMonitorName)) { - return azureMonitorName; - } - return metricsExporter; - } } diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/SecondEntryPoint.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/SecondEntryPoint.java index ac2fe85bf7..6cc887b40a 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/SecondEntryPoint.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/SecondEntryPoint.java @@ -264,6 +264,19 @@ public void customize(AutoConfigurationCustomizer autoConfiguration) { return props; }) .addPropertiesCustomizer(new AiConfigCustomizer()) + .addPropertiesCustomizer( + otelConfig -> { + Map props = new HashMap<>(); + if (isAksAttach()) { + String metricsExporter = otelConfig.getString("otel.metrics.exporter"); + String amle = + otelConfig.getString("applicationinsights.metrics.to.loganalytics.enabled"); + props.put( + "otel.metrics.exporter", + conditionallyAddAzureMonitorExporter(metricsExporter, amle)); + } + return props; + }) .addSpanExporterCustomizer( (spanExporter, configProperties) -> { if (spanExporter instanceof AzureMonitorSpanExporterProvider.MarkerSpanExporter) { @@ -798,4 +811,46 @@ private static CompletableResultCode flushAll( }); return overallResult; } + + private static boolean isAksAttach() { + return !Strings.isNullOrEmpty(System.getenv("AKS_ARM_NAMESPACE_ID")); + } + + // visible for tests + // Per spec: when amle=true, ensure azure_monitor is included; otherwise respect user's setting + // https://github.com/aep-health-and-standards/Telemetry-Collection-Spec/blob/main/ApplicationInsights/AutoAttach_Env_Vars.md#metrics-exporter + static String conditionallyAddAzureMonitorExporter(String metricsExporter, String amle) { + + // Default to azure_monitor when not set + if (Strings.isNullOrEmpty(metricsExporter)) { + // Note: this won't really happen since we default otel.metrics.exporter + // already in the PropertiesSupplier above which runs before this + return AzureMonitorExporterProviderKeys.EXPORTER_NAME; + } + + // When amle=true, ensure azure_monitor is included + if ("true".equals(amle)) { + if ("none".equals(metricsExporter)) { + return AzureMonitorExporterProviderKeys.EXPORTER_NAME; + } + if (!containsAzureMonitor(metricsExporter)) { + return metricsExporter + "," + AzureMonitorExporterProviderKeys.EXPORTER_NAME; + } + } + + return metricsExporter; + } + + // visible for tests + static boolean containsAzureMonitor(String metricsExporter) { + if (metricsExporter == null) { + return false; + } + for (String exporter : metricsExporter.split(",")) { + if (AzureMonitorExporterProviderKeys.EXPORTER_NAME.equals(exporter.trim())) { + return true; + } + } + return false; + } } diff --git a/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizerTest.java b/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizerTest.java deleted file mode 100644 index 119cb1aec9..0000000000 --- a/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizerTest.java +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.applicationinsights.agent.internal.init; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.stream.Stream; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -class AiConfigCustomizerTest { - - @Test - void isAksAttach() { - assertThat(AiConfigCustomizer.isAksAttach("dummy-aks-namespace")).isTrue(); - - assertThat(AiConfigCustomizer.isAksAttach(null)).isFalse(); - assertThat(AiConfigCustomizer.isAksAttach("")).isFalse(); - } - - @Test - void updateMetricsExporter_ExporterUnset() { - - assertThat(AiConfigCustomizer.updateMetricsExporter(null, null)) - .isEqualTo("azure_monitor,otlp"); - - assertThat(AiConfigCustomizer.updateMetricsExporter("", null)).isEqualTo("azure_monitor,otlp"); - - assertThat(AiConfigCustomizer.updateMetricsExporter(null, "true")) - .isEqualTo("azure_monitor,otlp"); - - assertThat(AiConfigCustomizer.updateMetricsExporter(null, "True")) - .isEqualTo("azure_monitor,otlp"); - } - - static Stream stringPairs() { - return Stream.of( - Arguments.of("none", "none"), - Arguments.of("azure_monitor", "azure_monitor,otlp"), - Arguments.of("azure_monitor,otlp", "azure_monitor,otlp"), - Arguments.of("otlp", "otlp")); - } - - @ParameterizedTest - @MethodSource("stringPairs") - void updateMetricsExporter_ExporterSet_AMLE_Unset( - String metricsExporter, String expectAzureMonitor) { - assertThat(AiConfigCustomizer.updateMetricsExporter(metricsExporter, null)) - .isEqualTo(expectAzureMonitor); - assertThat(AiConfigCustomizer.updateMetricsExporter(metricsExporter, "")) - .isEqualTo(expectAzureMonitor); - } - - static Stream stringPairs2() { - return Stream.of( - Arguments.of("none", "azure_monitor,otlp"), - Arguments.of("azure_monitor", "azure_monitor,otlp"), - Arguments.of("azure_monitor,otlp", "azure_monitor,otlp"), - Arguments.of("otlp", "azure_monitor,otlp")); - } - - @ParameterizedTest - @MethodSource("stringPairs2") - void updateMetricsExporter_ExporterSet_AMLE_True( - String metricsExporter, String expectAzureMonitor) { - assertThat(AiConfigCustomizer.updateMetricsExporter(metricsExporter, "true")) - .isEqualTo(expectAzureMonitor); - assertThat(AiConfigCustomizer.updateMetricsExporter(metricsExporter, "True")) - .isEqualTo(expectAzureMonitor); - } - - static Stream stringPairs3() { - return Stream.of( - Arguments.of("none", "none"), - Arguments.of("azure_monitor", "azure_monitor"), - Arguments.of("azure_monitor,otlp", "azure_monitor"), - Arguments.of("otlp", "otlp")); - } - - @ParameterizedTest - @MethodSource("stringPairs3") - void updateMetricsExporter_ExporterSet_AMLE_False( - String metricsExporter, String expectAzureMonitor) { - assertThat(AiConfigCustomizer.updateMetricsExporter(metricsExporter, "false")) - .isEqualTo(expectAzureMonitor); - assertThat(AiConfigCustomizer.updateMetricsExporter(metricsExporter, "False")) - .isEqualTo(expectAzureMonitor); - } -} diff --git a/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/init/SecondEntryPointTest.java b/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/init/SecondEntryPointTest.java new file mode 100644 index 0000000000..b47774fe5a --- /dev/null +++ b/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/init/SecondEntryPointTest.java @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.applicationinsights.agent.internal.init; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class SecondEntryPointTest { + + // Test cases matching the spec table in + // https://github.com/aep-health-and-standards/Telemetry-Collection-Spec/blob/main/ApplicationInsights/AutoAttach_Env_Vars.md#metrics-exporter + // + // OTEL_METRICS_EXPORTER | AMLE | azure_monitor included + // ----------------------|--------|------------------------ + static Stream metricsExporterSpecTable() { + return Stream.of( + // AMLE unset + Arguments.of(null, null, true), + Arguments.of("none", null, false), + Arguments.of("azure_monitor", null, true), + Arguments.of("otlp,azure_monitor", null, true), + Arguments.of("otlp", null, false), + // AMLE=true (always include azure_monitor) + Arguments.of(null, "true", true), + Arguments.of("none", "true", true), + Arguments.of("azure_monitor", "true", true), + Arguments.of("otlp,azure_monitor", "true", true), + Arguments.of("otlp", "true", true), + // AMLE=false (same as unset) + Arguments.of(null, "false", true), + Arguments.of("none", "false", false), + Arguments.of("azure_monitor", "false", true), + Arguments.of("otlp,azure_monitor", "false", true), + Arguments.of("otlp", "false", false)); + } + + @ParameterizedTest(name = "exporter={0}, amle={1} -> included={2}") + @MethodSource("metricsExporterSpecTable") + void testUpdateMetricsExporter(String exporter, String amle, boolean expectAzureMonitor) { + String result = SecondEntryPoint.conditionallyAddAzureMonitorExporter(exporter, amle); + assertThat(SecondEntryPoint.containsAzureMonitor(result)).isEqualTo(expectAzureMonitor); + } +}