Repository navigation
add: apollo opentelemetry metric. #149
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
teaho2015
wants to merge
1
commit into
apolloconfig:main
Choose a base branch
from
teaho-infra:main
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <!-- | ||
| ~ 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. | ||
| ~ | ||
| --> | ||
| <project xmlns="http://maven.apache.org/POM/4.0.0" | ||
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
| xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
| <modelVersion>4.0.0</modelVersion> | ||
| <parent> | ||
| <artifactId>apollo-plugin</artifactId> | ||
| <groupId>com.ctrip.framework.apollo</groupId> | ||
| <version>${revision}</version> | ||
| <relativePath>../pom.xml</relativePath> | ||
| </parent> | ||
|
|
||
| <artifactId>apollo-plugin-client-opentelemetry</artifactId> | ||
| <name>Apollo Plugin OpenTelemetry</name> | ||
| <packaging>jar</packaging> | ||
|
|
||
| <dependencies> | ||
| <dependency> | ||
| <groupId>com.ctrip.framework.apollo</groupId> | ||
| <artifactId>apollo-client</artifactId> | ||
| <scope>provided</scope> | ||
| </dependency> | ||
| <dependency> | ||
| <groupId>io.opentelemetry</groupId> | ||
| <artifactId>opentelemetry-api</artifactId> | ||
| <version>1.62.0</version> | ||
| </dependency> | ||
| <!-- Test dependencies --> | ||
| <dependency> | ||
| <groupId>io.opentelemetry</groupId> | ||
| <artifactId>opentelemetry-sdk-testing</artifactId> | ||
| <version>1.62.0</version> | ||
| <scope>test</scope> | ||
| </dependency> | ||
| <dependency> | ||
| <groupId>junit</groupId> | ||
| <artifactId>junit</artifactId> | ||
| <scope>test</scope> | ||
| </dependency> | ||
| <dependency> | ||
| <groupId>org.mockito</groupId> | ||
| <artifactId>mockito-inline</artifactId> | ||
| <scope>test</scope> | ||
| </dependency> | ||
| </dependencies> | ||
|
|
||
| </project> |
125 changes: 125 additions & 0 deletions
125
...ework/apollo/monitor/internal/exporter/impl/OpenTelemetryApolloClientMetricsExporter.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<String, DoubleCounter> counterMap; | ||
| private Map<String, DoubleGauge> 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<String, String> 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<String, String> 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<String, String> 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); | ||
| } | ||
| } | ||
1 change: 1 addition & 0 deletions
1
...services/com.ctrip.framework.apollo.monitor.internal.exporter.ApolloClientMetricsExporter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| com.ctrip.framework.apollo.monitor.internal.exporter.impl.OpenTelemetryApolloClientMetricsExporter |
128 changes: 128 additions & 0 deletions
128
...nitor/internal/exporter/impl/OpenTelemetryApolloClientMetricsExporterIntegrationTest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<String, String> 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<String, String> tags1 = new HashMap<>(); | ||
| tags1.put("thread_pool_name", "ConfigService"); | ||
| Map<String, String> 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<String, String> 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); | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
🔎 Supported by static analysis
🏁 Script executed:
Repository: apolloconfig/apollo-java
Length of output: 19053
🏁 Script executed:
Repository: apolloconfig/apollo-java
Length of output: 14704
🌐 Web query:
OpenTelemetry Java 1.62.0 GlobalOpenTelemetry.java get set deferred no-op registration semantics💡 Result:
🏁 Script executed:
Repository: apolloconfig/apollo-java
Length of output: 9201
Do not call
GlobalOpenTelemetry.get()before the application registers its SDK.Apollo can initialize this exporter during normal monitor startup before the application registers OpenTelemetry. In OpenTelemetry 1.62.0,
GlobalOpenTelemetry.get()attempts autoconfiguration and registersOpenTelemetry.noop()when no instance is available. The exporter then retains a no-op meter, and a laterGlobalOpenTelemetry.set(...)fails because the global has already been set.Require SDK registration before exporter initialization, or inject an explicitly supplied
OpenTelemetryinstance instead of obtaining the global instance here.🤖 Prompt for AI Agents