diff --git a/instrumentation/apolloconfig/apolloconfig-2.0.0/javaagent/build.gradle.kts b/instrumentation/apolloconfig/apolloconfig-2.0.0/javaagent/build.gradle.kts new file mode 100644 index 000000000000..7855afbe7207 --- /dev/null +++ b/instrumentation/apolloconfig/apolloconfig-2.0.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("[2.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:2.0.0") + + testImplementation(project(":instrumentation:apolloconfig:apolloconfig-2.0.0:testing")) + + latestDepTestLibrary("com.ctrip.framework.apollo:apollo-client:2.0.+") +} + +tasks.withType().configureEach { + // required on jdk17 + jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED") + jvmArgs("-XX:+IgnoreUnrecognizedVMOptions") +} diff --git a/instrumentation/apolloconfig/apolloconfig-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apolloconfig/v2_0_0/ApolloConfigInstrumentationModule.java b/instrumentation/apolloconfig/apolloconfig-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apolloconfig/v2_0_0/ApolloConfigInstrumentationModule.java new file mode 100644 index 000000000000..128b789d34a0 --- /dev/null +++ b/instrumentation/apolloconfig/apolloconfig-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apolloconfig/v2_0_0/ApolloConfigInstrumentationModule.java @@ -0,0 +1,24 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apolloconfig.v2_0_0; + +import com.google.auto.service.AutoService; +import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import java.util.Collections; +import java.util.List; + +@AutoService(InstrumentationModule.class) +public class ApolloConfigInstrumentationModule extends InstrumentationModule { + public ApolloConfigInstrumentationModule() { + super("apolloconfig", "apolloconfig-2.0.0"); + } + + @Override + public List typeInstrumentations() { + return Collections.singletonList(new ApolloRepositoryChangeInstrumentation()); + } +} diff --git a/instrumentation/apolloconfig/apolloconfig-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apolloconfig/v2_0_0/ApolloConfigSingletons.java b/instrumentation/apolloconfig/apolloconfig-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apolloconfig/v2_0_0/ApolloConfigSingletons.java new file mode 100644 index 000000000000..e14eeeabe3e6 --- /dev/null +++ b/instrumentation/apolloconfig/apolloconfig-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apolloconfig/v2_0_0/ApolloConfigSingletons.java @@ -0,0 +1,74 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apolloconfig.v2_0_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 class ApolloConfigSingletons { + + private static final String NAME = "io.opentelemetry.javaagent.apolloconfig-2.0.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/apolloconfig-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apolloconfig/v2_0_0/ApolloRepositoryChangeInstrumentation.java b/instrumentation/apolloconfig/apolloconfig-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apolloconfig/v2_0_0/ApolloRepositoryChangeInstrumentation.java new file mode 100644 index 000000000000..14656f343630 --- /dev/null +++ b/instrumentation/apolloconfig/apolloconfig-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apolloconfig/v2_0_0/ApolloRepositoryChangeInstrumentation.java @@ -0,0 +1,69 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apolloconfig.v2_0_0; + +import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext; +import static io.opentelemetry.javaagent.instrumentation.apolloconfig.v2_0_0.ApolloConfigSingletons.REPOSITORY_CHANGE_REPEAT_CONTEXT_KEY; +import static io.opentelemetry.javaagent.instrumentation.apolloconfig.v2_0_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) { + String adviceName = this.getClass().getName() + "$ApolloRepositoryChangeAdvice"; + transformer.applyAdviceToMethod(named("fireRepositoryChange"), adviceName); + } + + @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/apolloconfig-2.0.0/testing/src/main/java/io/opentelemetry/instrumentation/apolloconfig/v2_0_0/ApolloRepositoryChangeTest.java b/instrumentation/apolloconfig/apolloconfig-2.0.0/testing/src/main/java/io/opentelemetry/instrumentation/apolloconfig/v2_0_0/ApolloRepositoryChangeTest.java new file mode 100644 index 000000000000..306dbef9eee3 --- /dev/null +++ b/instrumentation/apolloconfig/apolloconfig-2.0.0/testing/src/main/java/io/opentelemetry/instrumentation/apolloconfig/v2_0_0/ApolloRepositoryChangeTest.java @@ -0,0 +1,84 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.apolloconfig.v2_0_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 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; + +public abstract class ApolloRepositoryChangeTest { + + @RegisterExtension + private static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); + + @Test + void test() { + String namespace = "application"; + TestConfigRepository testConfigRepository = new TestConfigRepository(namespace, true); + 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; + final Boolean hasNext; + + public TestConfigRepository(String namespace, Boolean hasNext) { + this.namespace = namespace; + this.hasNext = Boolean.TRUE; + } + + @Override + protected void sync() { + this.fireRepositoryChange(this.namespace, new Properties()); + if (hasNext) { + TestConfigRepository sub = new TestConfigRepository(namespace, false); + sub.sync(); + } + } + + @Override + public Properties getConfig() { + return new Properties(); + } + + @Override + public void setUpstreamRepository(ConfigRepository upstreamConfigRepository) {} + + @Override + public ConfigSourceType getSourceType() { + return ConfigSourceType.NONE; + } + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index aa3dabeccf14..881b5a1829ac 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -192,6 +192,8 @@ 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:apolloconfig-2.0.0:javaagent") +include(":instrumentation:apolloconfig:apolloconfig-2.0.0:testing") include(":instrumentation:armeria:armeria-1.3:javaagent") include(":instrumentation:armeria:armeria-1.3:library") include(":instrumentation:armeria:armeria-1.3:testing")