From 0e437e7e8939b13d1ceec08239d7bb7d0066d2e3 Mon Sep 17 00:00:00 2001 From: freshchen <961011595@qq.com> Date: Tue, 26 Nov 2024 12:07:48 +0800 Subject: [PATCH] Add Apolloconfig Inst https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/12787 --- docs/supported-libraries.md | 1 + .../javaagent/build.gradle.kts | 29 +++++ .../ApolloConfigInstrumentationModule.java | 25 ++++ .../v1_1/ApolloConfigSingletons.java | 74 ++++++++++++ ...ApolloRepositoryChangeInstrumentation.java | 69 +++++++++++ .../v1_1/ApolloRepositoryChangeTest.java | 107 ++++++++++++++++++ settings.gradle.kts | 1 + 7 files changed, 306 insertions(+) create mode 100644 instrumentation/apolloconfig-apolloclient/apolloconfig-apolloclient-1.1/javaagent/build.gradle.kts create mode 100644 instrumentation/apolloconfig-apolloclient/apolloconfig-apolloclient-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apolloconfig/apolloclient/v1_1/ApolloConfigInstrumentationModule.java create mode 100644 instrumentation/apolloconfig-apolloclient/apolloconfig-apolloclient-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apolloconfig/apolloclient/v1_1/ApolloConfigSingletons.java create mode 100644 instrumentation/apolloconfig-apolloclient/apolloconfig-apolloclient-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apolloconfig/apolloclient/v1_1/ApolloRepositoryChangeInstrumentation.java create mode 100644 instrumentation/apolloconfig-apolloclient/apolloconfig-apolloclient-1.1/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apolloconfig/apolloclient/v1_1/ApolloRepositoryChangeTest.java 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/apolloconfig-apolloclient-1.1/javaagent/build.gradle.kts b/instrumentation/apolloconfig-apolloclient/apolloconfig-apolloclient-1.1/javaagent/build.gradle.kts new file mode 100644 index 000000000000..3e9c521fd3c9 --- /dev/null +++ b/instrumentation/apolloconfig-apolloclient/apolloconfig-apolloclient-1.1/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/apolloconfig-apolloclient-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apolloconfig/apolloclient/v1_1/ApolloConfigInstrumentationModule.java b/instrumentation/apolloconfig-apolloclient/apolloconfig-apolloclient-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apolloconfig/apolloclient/v1_1/ApolloConfigInstrumentationModule.java new file mode 100644 index 000000000000..f0e216519cbd --- /dev/null +++ b/instrumentation/apolloconfig-apolloclient/apolloconfig-apolloclient-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apolloconfig/apolloclient/v1_1/ApolloConfigInstrumentationModule.java @@ -0,0 +1,25 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apolloconfig.apolloclient.v1_1; + +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.1"); + } + + @Override + public List typeInstrumentations() { + return singletonList(new ApolloRepositoryChangeInstrumentation()); + } +} diff --git a/instrumentation/apolloconfig-apolloclient/apolloconfig-apolloclient-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apolloconfig/apolloclient/v1_1/ApolloConfigSingletons.java b/instrumentation/apolloconfig-apolloclient/apolloconfig-apolloclient-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apolloconfig/apolloclient/v1_1/ApolloConfigSingletons.java new file mode 100644 index 000000000000..7e7d3711c2d4 --- /dev/null +++ b/instrumentation/apolloconfig-apolloclient/apolloconfig-apolloclient-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apolloconfig/apolloclient/v1_1/ApolloConfigSingletons.java @@ -0,0 +1,74 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apolloconfig.apolloclient.v1_1; + +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.1"; + 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/apolloconfig-apolloclient-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apolloconfig/apolloclient/v1_1/ApolloRepositoryChangeInstrumentation.java b/instrumentation/apolloconfig-apolloclient/apolloconfig-apolloclient-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apolloconfig/apolloclient/v1_1/ApolloRepositoryChangeInstrumentation.java new file mode 100644 index 000000000000..873b30280725 --- /dev/null +++ b/instrumentation/apolloconfig-apolloclient/apolloconfig-apolloclient-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apolloconfig/apolloclient/v1_1/ApolloRepositoryChangeInstrumentation.java @@ -0,0 +1,69 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apolloconfig.apolloclient.v1_1; + +import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext; +import static io.opentelemetry.javaagent.instrumentation.apolloconfig.apolloclient.v1_1.ApolloConfigSingletons.REPOSITORY_CHANGE_REPEAT_CONTEXT_KEY; +import static io.opentelemetry.javaagent.instrumentation.apolloconfig.apolloclient.v1_1.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/apolloconfig-apolloclient-1.1/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apolloconfig/apolloclient/v1_1/ApolloRepositoryChangeTest.java b/instrumentation/apolloconfig-apolloclient/apolloconfig-apolloclient-1.1/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apolloconfig/apolloclient/v1_1/ApolloRepositoryChangeTest.java new file mode 100644 index 000000000000..63604764ecef --- /dev/null +++ b/instrumentation/apolloconfig-apolloclient/apolloconfig-apolloclient-1.1/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apolloconfig/apolloclient/v1_1/ApolloRepositoryChangeTest.java @@ -0,0 +1,107 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apolloconfig.apolloclient.v1_1; + +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..95ccd0935c96 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:apolloconfig-apolloclient-1.1:javaagent") include(":instrumentation:armeria:armeria-1.3:javaagent") include(":instrumentation:armeria:armeria-1.3:library") include(":instrumentation:armeria:armeria-1.3:testing")