From 6e631f746c926421fd737ebcfd0be48c647eacde Mon Sep 17 00:00:00 2001 From: teaho2015 Date: Sun, 18 Oct 2026 00:14:41 +0800 Subject: [PATCH] feat: export Apollo client metrics through OpenTelemetry --- CHANGES.md | 1 + .../pom.xml | 63 +++++++ ...nTelemetryApolloClientMetricsExporter.java | 125 +++++++++++++ ...ernal.exporter.ApolloClientMetricsExporter | 1 + ...oClientMetricsExporterIntegrationTest.java | 128 +++++++++++++ ...emetryApolloClientMetricsExporterTest.java | 176 ++++++++++++++++++ ...emetryApolloClientMetricsExporterTest.java | 114 ++++++++++++ apollo-plugin/pom.xml | 1 + 8 files changed, 609 insertions(+) create mode 100644 apollo-plugin/apollo-plugin-client-opentelemetry/pom.xml create mode 100644 apollo-plugin/apollo-plugin-client-opentelemetry/src/main/java/com/ctrip/framework/apollo/monitor/internal/exporter/impl/OpenTelemetryApolloClientMetricsExporter.java create mode 100644 apollo-plugin/apollo-plugin-client-opentelemetry/src/main/resources/META-INF/services/com.ctrip.framework.apollo.monitor.internal.exporter.ApolloClientMetricsExporter create mode 100644 apollo-plugin/apollo-plugin-client-opentelemetry/src/test/java/com/ctrip/framework/apollo/monitor/internal/exporter/impl/OpenTelemetryApolloClientMetricsExporterIntegrationTest.java create mode 100644 apollo-plugin/apollo-plugin-client-opentelemetry/src/test/java/com/ctrip/framework/apollo/monitor/internal/exporter/impl/OpenTelemetryApolloClientMetricsExporterTest.java create mode 100644 apollo-plugin/apollo-plugin-client-opentelemetry/src/test/java/com/ctrip/framework/apollo/monitor/internal/exporter/impl/SimpleOpenTelemetryApolloClientMetricsExporterTest.java diff --git a/CHANGES.md b/CHANGES.md index c18c8d75..c7c1753e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,7 @@ Apollo Java 2.6.0 * [Fix Apollo client local cache fallback for Spring Boot 3 executable JARs](https://github.com/apolloconfig/apollo-java/pull/136) * [Support `extendInfo`/`parentAppId` in OpenAPI `getNamespace(s)` client calls](https://github.com/apolloconfig/apollo-java/pull/144) +* [Export Apollo client metrics through OpenTelemetry](https://github.com/apolloconfig/apollo-java/pull/149) ------------------ All issues and pull requests are [here](https://github.com/apolloconfig/apollo-java/milestone/6?closed=1) diff --git a/apollo-plugin/apollo-plugin-client-opentelemetry/pom.xml b/apollo-plugin/apollo-plugin-client-opentelemetry/pom.xml new file mode 100644 index 00000000..5a67228d --- /dev/null +++ b/apollo-plugin/apollo-plugin-client-opentelemetry/pom.xml @@ -0,0 +1,63 @@ + + + + 4.0.0 + + apollo-plugin + com.ctrip.framework.apollo + ${revision} + ../pom.xml + + + apollo-plugin-client-opentelemetry + Apollo Plugin OpenTelemetry + jar + + + + com.ctrip.framework.apollo + apollo-client + provided + + + io.opentelemetry + opentelemetry-api + 1.62.0 + + + + io.opentelemetry + opentelemetry-sdk-testing + 1.62.0 + test + + + junit + junit + test + + + org.mockito + mockito-inline + test + + + + \ No newline at end of file diff --git a/apollo-plugin/apollo-plugin-client-opentelemetry/src/main/java/com/ctrip/framework/apollo/monitor/internal/exporter/impl/OpenTelemetryApolloClientMetricsExporter.java b/apollo-plugin/apollo-plugin-client-opentelemetry/src/main/java/com/ctrip/framework/apollo/monitor/internal/exporter/impl/OpenTelemetryApolloClientMetricsExporter.java new file mode 100644 index 00000000..8c7d97f0 --- /dev/null +++ b/apollo-plugin/apollo-plugin-client-opentelemetry/src/main/java/com/ctrip/framework/apollo/monitor/internal/exporter/impl/OpenTelemetryApolloClientMetricsExporter.java @@ -0,0 +1,125 @@ +/* + * Copyright 2026 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.monitor.internal.exporter.impl; + +import com.ctrip.framework.apollo.core.utils.DeferredLoggerFactory; +import com.ctrip.framework.apollo.monitor.internal.exporter.AbstractApolloClientMetricsExporter; +import com.ctrip.framework.apollo.monitor.internal.exporter.ApolloClientMetricsExporter; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.api.metrics.*; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import org.slf4j.Logger; + +/** + * OpenTelemetry implementation of Apollo client metrics exporter. + * Only uses OpenTelemetry API layer (no SDK dependency). + * + * @author leon.he@walmart.com + */ +public class OpenTelemetryApolloClientMetricsExporter extends AbstractApolloClientMetricsExporter + implements ApolloClientMetricsExporter { + + private static final String OPENTELEMETRY = "opentelemetry"; + private static final String METER_NAME = "apollo-client"; + private static final String COUNTER_UNIT = "1"; + private static final String GAUGE_UNIT = "1"; + + private final Logger logger = + DeferredLoggerFactory.getLogger(OpenTelemetryApolloClientMetricsExporter.class); + + private Meter meter; + private Map counterMap; + private Map gaugeMap; + + @Override + public void doInit() { + // Get meter from global OpenTelemetry + meter = GlobalOpenTelemetry.get().getMeter(METER_NAME); + // Initialize maps + counterMap = new ConcurrentHashMap<>(); + gaugeMap = new ConcurrentHashMap<>(); + logger.info("OpenTelemetry metrics exporter initialized with meter: {}", METER_NAME); + } + + @Override + public boolean isSupport(String form) { + return OPENTELEMETRY.equals(form); + } + + @Override + public void registerOrUpdateCounterSample(String name, Map tags, + double incrValue) { + if (meter == null) { + logger.warn("OpenTelemetry meter not initialized, skipping counter registration for '{}'", + name); + return; + } + DoubleCounter counter = counterMap.computeIfAbsent(name, key -> meter.counterBuilder(name) + .setDescription("Apollo counter metrics").setUnit(COUNTER_UNIT).ofDoubles().build()); + Attributes attributes = getOrCreateAttributes(tags); + counter.add(incrValue, attributes); + + if (logger.isDebugEnabled()) { + logger.debug("Updated OpenTelemetry counter '{}' with value: {}, tags: {}", name, incrValue, + tags); + } + } + + @Override + public void registerOrUpdateGaugeSample(String name, Map tags, double value) { + if (meter == null) { + logger.warn("OpenTelemetry meter not initialized, skipping gauge registration for '{}'", + name); + return; + } + Attributes attributes = getOrCreateAttributes(tags); + // Register gauge if not already registered + DoubleGauge gauge = gaugeMap.computeIfAbsent(name, key -> meter.gaugeBuilder(name) + .setDescription("Apollo gauge metrics").setUnit(GAUGE_UNIT).build()); + gauge.set(value, attributes); + + if (logger.isDebugEnabled()) { + logger.debug("Updated OpenTelemetry gauge '{}' with value: {}, tags: {}", name, value, tags); + } + } + + private Attributes getOrCreateAttributes(Map tags) { + if (tags == null || tags.isEmpty()) { + return Attributes.empty(); + } + AttributesBuilder builder = Attributes.builder(); + tags.forEach(builder::put); + return builder.build(); + } + + + @Override + public String response() { + // Return simple status information since we're only using API layer + int counterCount = counterMap != null ? counterMap.size() : 0; + int gaugeCount = gaugeMap != null ? gaugeMap.size() : 0; + + String meterStatus = (meter != null) ? METER_NAME : "not initialized"; + return String.format( + "OpenTelemetry metrics exporter status - Counters: %d, Gauges: %d, Meter: %s", counterCount, + gaugeCount, meterStatus); + } +} diff --git a/apollo-plugin/apollo-plugin-client-opentelemetry/src/main/resources/META-INF/services/com.ctrip.framework.apollo.monitor.internal.exporter.ApolloClientMetricsExporter b/apollo-plugin/apollo-plugin-client-opentelemetry/src/main/resources/META-INF/services/com.ctrip.framework.apollo.monitor.internal.exporter.ApolloClientMetricsExporter new file mode 100644 index 00000000..8e064550 --- /dev/null +++ b/apollo-plugin/apollo-plugin-client-opentelemetry/src/main/resources/META-INF/services/com.ctrip.framework.apollo.monitor.internal.exporter.ApolloClientMetricsExporter @@ -0,0 +1 @@ +com.ctrip.framework.apollo.monitor.internal.exporter.impl.OpenTelemetryApolloClientMetricsExporter \ No newline at end of file diff --git a/apollo-plugin/apollo-plugin-client-opentelemetry/src/test/java/com/ctrip/framework/apollo/monitor/internal/exporter/impl/OpenTelemetryApolloClientMetricsExporterIntegrationTest.java b/apollo-plugin/apollo-plugin-client-opentelemetry/src/test/java/com/ctrip/framework/apollo/monitor/internal/exporter/impl/OpenTelemetryApolloClientMetricsExporterIntegrationTest.java new file mode 100644 index 00000000..2a3ebc08 --- /dev/null +++ b/apollo-plugin/apollo-plugin-client-opentelemetry/src/test/java/com/ctrip/framework/apollo/monitor/internal/exporter/impl/OpenTelemetryApolloClientMetricsExporterIntegrationTest.java @@ -0,0 +1,128 @@ +/* + * Copyright 2022 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.monitor.internal.exporter.impl; + +import static org.junit.Assert.assertEquals; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; +import java.util.HashMap; +import java.util.Map; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** + * Integration tests using a real OpenTelemetry SDK to verify exported metric values. + */ +public class OpenTelemetryApolloClientMetricsExporterIntegrationTest { + + private SdkMeterProvider meterProvider; + private InMemoryMetricReader reader; + private OpenTelemetryApolloClientMetricsExporter exporter; + + @Before + public void setUp() { + reader = InMemoryMetricReader.create(); + meterProvider = SdkMeterProvider.builder().registerMetricReader(reader).build(); + OpenTelemetrySdk sdk = OpenTelemetrySdk.builder().setMeterProvider(meterProvider).build(); + GlobalOpenTelemetry.set(sdk); + + exporter = new OpenTelemetryApolloClientMetricsExporter(); + exporter.doInit(); + } + + @After + public void tearDown() { + GlobalOpenTelemetry.resetForTest(); + meterProvider.close(); + } + + private double latestGaugeValue(String metricName, String tagValue) { + for (MetricData md : reader.collectAllMetrics()) { + if (!metricName.equals(md.getName())) { + continue; + } + return md.getDoubleGaugeData().getPoints().stream() + .filter(p -> tagValue.equals(p.getAttributes() + .get(io.opentelemetry.api.common.AttributeKey.stringKey("thread_pool_name")))) + .findFirst() + .orElseThrow(() -> new AssertionError("No gauge point found for metric '" + metricName + + "' with tag value '" + tagValue + "', all metrics: " + reader.collectAllMetrics())) + .getValue(); + } + throw new AssertionError("Metric not found: " + metricName); + } + + private double latestCounterValue(String metricName) { + for (MetricData md : reader.collectAllMetrics()) { + if (metricName.equals(md.getName())) { + return md.getDoubleSumData().getPoints().stream().findFirst() + .orElseThrow(() -> new AssertionError("No counter point for " + metricName)).getValue(); + } + } + throw new AssertionError("Metric not found: " + metricName); + } + + @Test + public void gaugeShouldReflectLatestValueAfterUpdates() { + Map tags = new HashMap<>(); + tags.put("thread_pool_name", "ConfigService"); + + exporter.registerOrUpdateGaugeSample("apollo_client_thread_pool_queue_size", tags, 5.0); + // force collection so subsequent update is a new delta + reader.collectAllMetrics(); + exporter.registerOrUpdateGaugeSample("apollo_client_thread_pool_queue_size", tags, 10.0); + reader.collectAllMetrics(); + exporter.registerOrUpdateGaugeSample("apollo_client_thread_pool_queue_size", tags, 3.0); + + assertEquals(3.0, latestGaugeValue("apollo_client_thread_pool_queue_size", "ConfigService"), + 0.0001); + } + + @Test + public void gaugeWithDifferentTagsShouldRecordSeparateSeries() { + Map tags1 = new HashMap<>(); + tags1.put("thread_pool_name", "ConfigService"); + Map tags2 = new HashMap<>(); + tags2.put("thread_pool_name", "RemoteConfigLongPollService"); + + exporter.registerOrUpdateGaugeSample("apollo_client_thread_pool_active_task_count", tags1, 1.0); + exporter.registerOrUpdateGaugeSample("apollo_client_thread_pool_active_task_count", tags2, 7.0); + + assertEquals(1.0, + latestGaugeValue("apollo_client_thread_pool_active_task_count", "ConfigService"), 0.0001); + assertEquals(7.0, latestGaugeValue("apollo_client_thread_pool_active_task_count", + "RemoteConfigLongPollService"), 0.0001); + } + + @Test + public void counterShouldAccumulateAcrossUpdates() { + Map tags = new HashMap<>(); + tags.put("namespace", "application"); + + reader.collectAllMetrics(); + exporter.registerOrUpdateCounterSample("apollo_client_exception_num", tags, 2.0); + reader.collectAllMetrics(); + exporter.registerOrUpdateCounterSample("apollo_client_exception_num", tags, 3.0); + + assertEquals(5.0, latestCounterValue("apollo_client_exception_num"), 0.0001); + } +} diff --git a/apollo-plugin/apollo-plugin-client-opentelemetry/src/test/java/com/ctrip/framework/apollo/monitor/internal/exporter/impl/OpenTelemetryApolloClientMetricsExporterTest.java b/apollo-plugin/apollo-plugin-client-opentelemetry/src/test/java/com/ctrip/framework/apollo/monitor/internal/exporter/impl/OpenTelemetryApolloClientMetricsExporterTest.java new file mode 100644 index 00000000..cbe28c86 --- /dev/null +++ b/apollo-plugin/apollo-plugin-client-opentelemetry/src/test/java/com/ctrip/framework/apollo/monitor/internal/exporter/impl/OpenTelemetryApolloClientMetricsExporterTest.java @@ -0,0 +1,176 @@ +/* + * Copyright 2022 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.monitor.internal.exporter.impl; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.metrics.Meter; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.MockedStatic; + +import java.util.HashMap; +import java.util.Map; + +public class OpenTelemetryApolloClientMetricsExporterTest { + + private OpenTelemetryApolloClientMetricsExporter exporter; + private MockedStatic globalOpenTelemetryMock; + private OpenTelemetry openTelemetry; + private Meter meter; + + @Before + public void setUp() { + // Mock GlobalOpenTelemetry + globalOpenTelemetryMock = mockStatic(GlobalOpenTelemetry.class); + openTelemetry = mock(OpenTelemetry.class); + meter = mock(Meter.class); + + when(GlobalOpenTelemetry.get()).thenReturn(openTelemetry); + when(openTelemetry.getMeter("apollo-client")).thenReturn(meter); + + exporter = new OpenTelemetryApolloClientMetricsExporter(); + exporter.doInit(); + } + + @After + public void tearDown() { + if (globalOpenTelemetryMock != null) { + globalOpenTelemetryMock.close(); + } + } + + @Test + public void testIsSupport() { + assertTrue(exporter.isSupport("opentelemetry")); + assertFalse(exporter.isSupport("prometheus")); + assertFalse(exporter.isSupport("other")); + } + + @Test + public void testDoInit() { + // Verify that meter was obtained from GlobalOpenTelemetry + globalOpenTelemetryMock.verify(() -> GlobalOpenTelemetry.get()); + verify(openTelemetry).getMeter("apollo-client"); + } + + @Test + public void testRegisterOrUpdateCounterSample() { + String name = "test_counter"; + Map tags = new HashMap<>(); + tags.put("namespace", "application"); + tags.put("cluster", "default"); + + // Mock counter builder + io.opentelemetry.api.metrics.LongCounterBuilder counterBuilder = + mock(io.opentelemetry.api.metrics.LongCounterBuilder.class); + io.opentelemetry.api.metrics.DoubleCounterBuilder doubleCounterBuilder = + mock(io.opentelemetry.api.metrics.DoubleCounterBuilder.class); + io.opentelemetry.api.metrics.DoubleCounter counter = + mock(io.opentelemetry.api.metrics.DoubleCounter.class); + + when(meter.counterBuilder(name)).thenReturn(counterBuilder); + when(counterBuilder.setDescription("Apollo counter metrics")).thenReturn(counterBuilder); + when(counterBuilder.setUnit("1")).thenReturn(counterBuilder); + when(counterBuilder.ofDoubles()).thenReturn(doubleCounterBuilder); + when(doubleCounterBuilder.build()).thenReturn(counter); + + // This will create the counter on first call + exporter.registerOrUpdateCounterSample(name, tags, 1.0); + + // Verify counter was created and value recorded + verify(meter).counterBuilder(name); + verify(counter).add(eq(1.0), any()); + } + + @Test + public void testRegisterOrUpdateGaugeSample() { + String name = "test_gauge"; + Map tags = new HashMap<>(); + tags.put("namespace", "application"); + tags.put("cluster", "default"); + + // Mock gauge builder (synchronous double gauge) + io.opentelemetry.api.metrics.DoubleGaugeBuilder gaugeBuilder = + mock(io.opentelemetry.api.metrics.DoubleGaugeBuilder.class); + io.opentelemetry.api.metrics.DoubleGauge gauge = + mock(io.opentelemetry.api.metrics.DoubleGauge.class); + + when(meter.gaugeBuilder(name)).thenReturn(gaugeBuilder); + when(gaugeBuilder.setDescription("Apollo gauge metrics")).thenReturn(gaugeBuilder); + when(gaugeBuilder.setUnit("1")).thenReturn(gaugeBuilder); + when(gaugeBuilder.build()).thenReturn(gauge); + + // This will create the gauge on first call + exporter.registerOrUpdateGaugeSample(name, tags, 3.14); + + // Verify gauge was created and value set + verify(meter).gaugeBuilder(name); + verify(gauge).set(eq(3.14), any()); + } + + @Test + public void testResponse() { + String response = exporter.response(); + assertNotNull(response); + assertTrue(response.contains("OpenTelemetry metrics exporter status")); + assertTrue(response.contains("Counters: 0")); + assertTrue(response.contains("Gauges: 0")); + assertTrue(response.contains("Meter: apollo-client")); + } + + @Test + public void testResponseWithMetrics() { + // Register some metrics first + Map tags = new HashMap<>(); + tags.put("test", "value"); + + // Mock counter creation + io.opentelemetry.api.metrics.LongCounterBuilder counterBuilder = + mock(io.opentelemetry.api.metrics.LongCounterBuilder.class); + io.opentelemetry.api.metrics.DoubleCounterBuilder doubleCounterBuilder = + mock(io.opentelemetry.api.metrics.DoubleCounterBuilder.class); + io.opentelemetry.api.metrics.DoubleCounter counter = + mock(io.opentelemetry.api.metrics.DoubleCounter.class); + when(meter.counterBuilder("test_counter")).thenReturn(counterBuilder); + when(counterBuilder.setDescription(anyString())).thenReturn(counterBuilder); + when(counterBuilder.setUnit(anyString())).thenReturn(counterBuilder); + when(counterBuilder.ofDoubles()).thenReturn(doubleCounterBuilder); + when(doubleCounterBuilder.build()).thenReturn(counter); + + // Mock gauge creation (synchronous double gauge) + io.opentelemetry.api.metrics.DoubleGaugeBuilder gaugeBuilder = + mock(io.opentelemetry.api.metrics.DoubleGaugeBuilder.class); + io.opentelemetry.api.metrics.DoubleGauge gauge = + mock(io.opentelemetry.api.metrics.DoubleGauge.class); + when(meter.gaugeBuilder("test_gauge")).thenReturn(gaugeBuilder); + when(gaugeBuilder.setDescription(anyString())).thenReturn(gaugeBuilder); + when(gaugeBuilder.setUnit(anyString())).thenReturn(gaugeBuilder); + when(gaugeBuilder.build()).thenReturn(gauge); + + exporter.registerOrUpdateCounterSample("test_counter", tags, 1.0); + exporter.registerOrUpdateGaugeSample("test_gauge", tags, 2.0); + + String response = exporter.response(); + assertTrue(response.contains("Counters: 1")); + assertTrue(response.contains("Gauges: 1")); + } +} diff --git a/apollo-plugin/apollo-plugin-client-opentelemetry/src/test/java/com/ctrip/framework/apollo/monitor/internal/exporter/impl/SimpleOpenTelemetryApolloClientMetricsExporterTest.java b/apollo-plugin/apollo-plugin-client-opentelemetry/src/test/java/com/ctrip/framework/apollo/monitor/internal/exporter/impl/SimpleOpenTelemetryApolloClientMetricsExporterTest.java new file mode 100644 index 00000000..3a715d4c --- /dev/null +++ b/apollo-plugin/apollo-plugin-client-opentelemetry/src/test/java/com/ctrip/framework/apollo/monitor/internal/exporter/impl/SimpleOpenTelemetryApolloClientMetricsExporterTest.java @@ -0,0 +1,114 @@ +/* + * Copyright 2022 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.monitor.internal.exporter.impl; + +import static org.junit.Assert.*; + +import org.junit.Before; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +public class SimpleOpenTelemetryApolloClientMetricsExporterTest { + + private OpenTelemetryApolloClientMetricsExporter exporter; + + @Before + public void setUp() { + exporter = new OpenTelemetryApolloClientMetricsExporter(); + // Note: We don't call doInit() because it requires GlobalOpenTelemetry + // In a real test environment, you would need to setup OpenTelemetry first + } + + @Test + public void testIsSupport() { + assertTrue(exporter.isSupport("opentelemetry")); + assertFalse(exporter.isSupport("prometheus")); + assertFalse(exporter.isSupport("other")); + assertFalse(exporter.isSupport(null)); + } + + @Test + public void testResponseWithoutInit() { + // Even without init, response should return a status message + String response = exporter.response(); + assertNotNull(response); + assertTrue(response.contains("OpenTelemetry metrics exporter status")); + } + + @Test + public void testRegisterOrUpdateCounterSampleWithoutInit() { + // This should not throw exception even without init + String name = "test_counter"; + Map tags = new HashMap<>(); + tags.put("namespace", "application"); + + try { + exporter.registerOrUpdateCounterSample(name, tags, 1.0); + // If we get here, it means no exception was thrown + assertTrue(true); + } catch (Exception e) { + // It's okay if it throws exception when not initialized + // This depends on OpenTelemetry API behavior + } + } + + @Test + public void testRegisterOrUpdateGaugeSampleWithoutInit() { + // This should not throw exception even without init + String name = "test_gauge"; + Map tags = new HashMap<>(); + tags.put("namespace", "application"); + + try { + exporter.registerOrUpdateGaugeSample(name, tags, 3.14); + // If we get here, it means no exception was thrown + assertTrue(true); + } catch (Exception e) { + // It's okay if it throws exception when not initialized + // This depends on OpenTelemetry API behavior + } + } + + @Test + public void testEmptyTags() { + String name = "test_metric"; + Map emptyTags = new HashMap<>(); + + try { + exporter.registerOrUpdateCounterSample(name, emptyTags, 1.0); + exporter.registerOrUpdateGaugeSample(name, emptyTags, 2.0); + assertTrue(true); + } catch (Exception e) { + // Acceptable if it throws exception + } + } + + @Test + public void testNullTags() { + String name = "test_metric"; + + try { + exporter.registerOrUpdateCounterSample(name, null, 1.0); + exporter.registerOrUpdateGaugeSample(name, null, 2.0); + assertTrue(true); + } catch (Exception e) { + // Acceptable if it throws exception for null tags + } + } +} diff --git a/apollo-plugin/pom.xml b/apollo-plugin/pom.xml index f0d1b00f..b97e09f4 100644 --- a/apollo-plugin/pom.xml +++ b/apollo-plugin/pom.xml @@ -32,6 +32,7 @@ apollo-plugin-log4j2 apollo-plugin-client-prometheus + apollo-plugin-client-opentelemetry