diff --git a/docs/supported-libraries.md b/docs/supported-libraries.md
index 74bea2ded278..77bf8bee7048 100644
--- a/docs/supported-libraries.md
+++ b/docs/supported-libraries.md
@@ -31,6 +31,7 @@ These are the supported libraries and frameworks:
| [Apache HttpAsyncClient](https://hc.apache.org/index.html) | 4.1+ | N/A | [HTTP Client Spans], [HTTP Client Metrics] |
| [Apache HttpClient](https://hc.apache.org/index.html) | 2.0+ | [opentelemetry-apache-httpclient-4.3](../instrumentation/apache-httpclient/apache-httpclient-4.3/library),
[opentelemetry-apache-httpclient-5.2](../instrumentation/apache-httpclient/apache-httpclient-5.2/library) | [HTTP Client Spans], [HTTP Client Metrics] |
| [Apache ShenYu](https://shenyu.apache.org/) | 2.4+ | N/A | Provides `http.route` [2] |
+| [Apollo Configuration Center/Apollo Client](https://www.apolloconfig.com/#/en/README) | 1.0+ | N/A | none |
| [Apache Kafka Producer/Consumer API](https://kafka.apache.org/documentation/#producerapi) | 0.11+ | [opentelemetry-kafka-clients-2.6](../instrumentation/kafka/kafka-clients/kafka-clients-2.6/library) | [Messaging Spans] |
| [Apache Kafka Streams API](https://kafka.apache.org/documentation/streams/) | 0.11+ | N/A | [Messaging Spans] |
| [Apache MyFaces](https://myfaces.apache.org/) | 1.2+ (not including 3.x yet) | N/A | Provides `http.route` [2], Controller Spans [3] |
diff --git a/instrumentation/apolloconfig-apolloclient-1.0/javaagent/build.gradle.kts b/instrumentation/apolloconfig-apolloclient-1.0/javaagent/build.gradle.kts
new file mode 100644
index 000000000000..3e9c521fd3c9
--- /dev/null
+++ b/instrumentation/apolloconfig-apolloclient-1.0/javaagent/build.gradle.kts
@@ -0,0 +1,29 @@
+plugins {
+ id("otel.javaagent-instrumentation")
+}
+
+muzzle {
+ pass {
+ group.set("com.ctrip.framework.apollo")
+ module.set("apollo-client")
+ versions.set("[1.0.0,2.3.0]")
+ assertInverse.set(true)
+ }
+}
+
+dependencies {
+ compileOnly("com.google.auto.value:auto-value-annotations")
+ annotationProcessor("com.google.auto.value:auto-value")
+
+ library("com.ctrip.framework.apollo:apollo-client:1.1.0")
+
+ testImplementation(project(":testing-common"))
+
+ latestDepTestLibrary("com.ctrip.framework.apollo:apollo-client:1.1.+")
+}
+
+tasks.withType().configureEach {
+ // required on jdk17
+ jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED")
+ jvmArgs("-XX:+IgnoreUnrecognizedVMOptions")
+}
diff --git a/instrumentation/apolloconfig-apolloclient-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apolloconfig/apolloclient/v1_0/ApolloConfigInstrumentationModule.java b/instrumentation/apolloconfig-apolloclient-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apolloconfig/apolloclient/v1_0/ApolloConfigInstrumentationModule.java
new file mode 100644
index 000000000000..0456602b181c
--- /dev/null
+++ b/instrumentation/apolloconfig-apolloclient-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apolloconfig/apolloclient/v1_0/ApolloConfigInstrumentationModule.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.apolloconfig.apolloclient.v1_0;
+
+import static java.util.Collections.singletonList;
+
+import com.google.auto.service.AutoService;
+import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
+import java.util.List;
+
+@AutoService(InstrumentationModule.class)
+public class ApolloConfigInstrumentationModule extends InstrumentationModule {
+ public ApolloConfigInstrumentationModule() {
+ super("apolloconfig-apolloclient", "apolloconfig-apolloclient-1.0");
+ }
+
+ @Override
+ public List typeInstrumentations() {
+ return singletonList(new ApolloRepositoryChangeInstrumentation());
+ }
+}
diff --git a/instrumentation/apolloconfig-apolloclient-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apolloconfig/apolloclient/v1_0/ApolloConfigSingletons.java b/instrumentation/apolloconfig-apolloclient-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apolloconfig/apolloclient/v1_0/ApolloConfigSingletons.java
new file mode 100644
index 000000000000..5d514cf6402d
--- /dev/null
+++ b/instrumentation/apolloconfig-apolloclient-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apolloconfig/apolloclient/v1_0/ApolloConfigSingletons.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.apolloconfig.apolloclient.v1_0;
+
+import static io.opentelemetry.api.common.AttributeKey.stringKey;
+
+import io.opentelemetry.api.GlobalOpenTelemetry;
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.api.common.AttributesBuilder;
+import io.opentelemetry.api.trace.StatusCode;
+import io.opentelemetry.context.Context;
+import io.opentelemetry.context.ContextKey;
+import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
+import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
+import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor;
+import io.opentelemetry.instrumentation.api.instrumenter.SpanStatusExtractor;
+import javax.annotation.Nullable;
+
+public final class ApolloConfigSingletons {
+
+ private static final String NAME = "io.opentelemetry.apolloconfig-apolloclient-1.0";
+ private static final Instrumenter INSTRUMENTER;
+
+ private static final AttributeKey CONFIG_NS_ATTRIBUTE_KEY = stringKey("config.namespace");
+ public static final ContextKey REPOSITORY_CHANGE_REPEAT_CONTEXT_KEY =
+ ContextKey.named("apollo-config-repository-change-repeat");
+
+ static {
+ AttributesExtractor attributesExtractor =
+ new AttributesExtractor() {
+
+ @Override
+ public void onStart(
+ AttributesBuilder attributes, Context parentContext, String namespace) {
+ if (namespace == null) {
+ return;
+ }
+
+ attributes.put(CONFIG_NS_ATTRIBUTE_KEY, namespace);
+ }
+
+ @Override
+ public void onEnd(
+ AttributesBuilder attributes,
+ Context context,
+ String namespace,
+ @Nullable Void unused,
+ @Nullable Throwable error) {}
+ };
+
+ SpanStatusExtractor spanStatusExtractor =
+ (spanStatusBuilder, request, unused, error) -> {
+ if (error != null) {
+ spanStatusBuilder.setStatus(StatusCode.ERROR);
+ }
+ };
+
+ INSTRUMENTER =
+ Instrumenter.builder(
+ GlobalOpenTelemetry.get(), NAME, (event) -> "Apollo Config Repository Change")
+ .setSpanStatusExtractor(spanStatusExtractor)
+ .addAttributesExtractor(attributesExtractor)
+ .buildInstrumenter(SpanKindExtractor.alwaysClient());
+ }
+
+ public static Instrumenter instrumenter() {
+ return INSTRUMENTER;
+ }
+
+ private ApolloConfigSingletons() {}
+}
diff --git a/instrumentation/apolloconfig-apolloclient-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apolloconfig/apolloclient/v1_0/ApolloRepositoryChangeInstrumentation.java b/instrumentation/apolloconfig-apolloclient-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apolloconfig/apolloclient/v1_0/ApolloRepositoryChangeInstrumentation.java
new file mode 100644
index 000000000000..093dcc64240f
--- /dev/null
+++ b/instrumentation/apolloconfig-apolloclient-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apolloconfig/apolloclient/v1_0/ApolloRepositoryChangeInstrumentation.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.apolloconfig.apolloclient.v1_0;
+
+import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext;
+import static io.opentelemetry.javaagent.instrumentation.apolloconfig.apolloclient.v1_0.ApolloConfigSingletons.REPOSITORY_CHANGE_REPEAT_CONTEXT_KEY;
+import static io.opentelemetry.javaagent.instrumentation.apolloconfig.apolloclient.v1_0.ApolloConfigSingletons.instrumenter;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+
+import io.opentelemetry.context.Context;
+import io.opentelemetry.context.Scope;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+
+public class ApolloRepositoryChangeInstrumentation implements TypeInstrumentation {
+
+ @Override
+ public ElementMatcher typeMatcher() {
+ return named("com.ctrip.framework.apollo.internals.AbstractConfigRepository");
+ }
+
+ @Override
+ public void transform(TypeTransformer transformer) {
+ transformer.applyAdviceToMethod(
+ named("fireRepositoryChange"), this.getClass().getName() + "$ApolloRepositoryChangeAdvice");
+ }
+
+ @SuppressWarnings("unused")
+ public static class ApolloRepositoryChangeAdvice {
+
+ @Advice.OnMethodEnter(suppress = Throwable.class)
+ public static void onEnter(
+ @Advice.Argument(value = 0) String namespace,
+ @Advice.Local("otelContext") Context context,
+ @Advice.Local("otelScope") Scope scope) {
+ Context parentContext = currentContext();
+ String repeat = parentContext.get(REPOSITORY_CHANGE_REPEAT_CONTEXT_KEY);
+ if (repeat != null) {
+ return;
+ }
+ if (!instrumenter().shouldStart(parentContext, namespace)) {
+ return;
+ }
+
+ context = instrumenter().start(parentContext, namespace);
+ context = context.with(REPOSITORY_CHANGE_REPEAT_CONTEXT_KEY, "1");
+ scope = context.makeCurrent();
+ }
+
+ @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
+ public static void onExit(
+ @Advice.Argument(value = 0) String namespace,
+ @Advice.Thrown Throwable throwable,
+ @Advice.Local("otelContext") Context context,
+ @Advice.Local("otelScope") Scope scope) {
+ if (scope == null) {
+ return;
+ }
+ scope.close();
+ instrumenter().end(context, namespace, null, throwable);
+ }
+ }
+}
diff --git a/instrumentation/apolloconfig-apolloclient-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apolloconfig/apolloclient/v1_0/ApolloRepositoryChangeTest.java b/instrumentation/apolloconfig-apolloclient-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apolloconfig/apolloclient/v1_0/ApolloRepositoryChangeTest.java
new file mode 100644
index 000000000000..0d9d09ae3e72
--- /dev/null
+++ b/instrumentation/apolloconfig-apolloclient-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apolloconfig/apolloclient/v1_0/ApolloRepositoryChangeTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.apolloconfig.apolloclient.v1_0;
+
+import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
+
+import com.ctrip.framework.apollo.enums.ConfigSourceType;
+import com.ctrip.framework.apollo.internals.AbstractConfigRepository;
+import com.ctrip.framework.apollo.internals.ConfigRepository;
+import com.ctrip.framework.apollo.internals.RepositoryChangeListener;
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.api.trace.SpanKind;
+import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
+import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
+import io.opentelemetry.sdk.testing.assertj.AttributeAssertion;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+class ApolloRepositoryChangeTest {
+
+ @RegisterExtension
+ private static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
+
+ @Test
+ void test() {
+ String namespace = "application";
+
+ TestConfigRepository testConfigRepository = new TestConfigRepository(namespace);
+ testConfigRepository.addChangeListener(new TestRepositoryChangeListener());
+ testConfigRepository.sync();
+
+ checkRepositoryChange(namespace);
+ }
+
+ private static void checkRepositoryChange(String namespace) {
+ String spanName = "Apollo Config Repository Change";
+ List attributeAssertions = new ArrayList<>();
+ attributeAssertions.add(equalTo(AttributeKey.stringKey("config.namespace"), namespace));
+
+ testing.waitAndAssertTraces(
+ trace ->
+ trace.hasSpansSatisfyingExactly(
+ span ->
+ span.hasKind(SpanKind.CLIENT)
+ .hasName(spanName)
+ .hasAttributesSatisfyingExactly(attributeAssertions)));
+ }
+
+ static class TestConfigRepository extends AbstractConfigRepository {
+
+ final String namespace;
+
+ public TestConfigRepository(String namespace) {
+ this.namespace = namespace;
+ }
+
+ @Override
+ protected void sync() {
+ this.fireRepositoryChange(this.namespace, new Properties());
+ }
+
+ @Override
+ public Properties getConfig() {
+ return new Properties();
+ }
+
+ @Override
+ public void setUpstreamRepository(ConfigRepository upstreamConfigRepository) {}
+
+ @Override
+ public ConfigSourceType getSourceType() {
+ return ConfigSourceType.NONE;
+ }
+ }
+
+ static class TestRepositoryChangeListener implements RepositoryChangeListener {
+
+ @Override
+ public void onRepositoryChange(String namespace, Properties newProperties) {
+ new AbstractConfigRepository() {
+ @Override
+ public Properties getConfig() {
+ return newProperties;
+ }
+
+ @Override
+ public void setUpstreamRepository(ConfigRepository upstreamConfigRepository) {}
+
+ @Override
+ public ConfigSourceType getSourceType() {
+ return ConfigSourceType.NONE;
+ }
+
+ @Override
+ protected void sync() {
+ this.fireRepositoryChange(namespace, new Properties());
+ }
+ }.sync();
+ }
+ }
+}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index aa3dabeccf14..7cbc312fadd1 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -192,6 +192,7 @@ include(":instrumentation:apache-httpclient:apache-httpclient-4.3:testing")
include(":instrumentation:apache-httpclient:apache-httpclient-5.0:javaagent")
include(":instrumentation:apache-httpclient:apache-httpclient-5.2:library")
include(":instrumentation:apache-shenyu-2.4:javaagent")
+include(":instrumentation:apolloconfig-apolloclient-1.0:javaagent")
include(":instrumentation:armeria:armeria-1.3:javaagent")
include(":instrumentation:armeria:armeria-1.3:library")
include(":instrumentation:armeria:armeria-1.3:testing")