diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c37d89c2f..fcd93f209 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,7 +32,7 @@ jobs: fail-fast: false matrix: os: [ ubuntu-latest, windows-latest ] - java-version: [ 8, 11, 16 ] + java-version: [ 8, 11, 16, 17 ] java-distribution: [ adopt, adopt-openj9 ] # Steps represent a sequence of tasks that will be executed as part of the job @@ -53,4 +53,4 @@ jobs: # Runs a single command using the runners shell - name: Build with Maven - run: mvn clean package + run: mvn clean package diff --git a/build/pom.xml b/build/pom.xml index 33094c42a..e9bd28627 100644 --- a/build/pom.xml +++ b/build/pom.xml @@ -21,7 +21,7 @@ easeagent com.megaease.easeagent - 2.2.9 + 2.3.0 4.0.0 @@ -38,23 +38,6 @@ javax.servlet-api provided - - org.springframework.boot - spring-boot-starter-web - ${version.spring-boot} - provided - - - org.springframework.cloud - spring-cloud-starter-openfeign - provided - - - org.springframework.cloud - spring-cloud-starter-gateway - provided - - com.megaease.easeagent metrics @@ -209,6 +192,7 @@ com.megaease.easeagent.Main com.megaease.easeagent.StartBootstrap log4j.configurationFile + ${version} diff --git a/build/src/main/resources/agent.properties b/build/src/main/resources/agent.properties index 0d3d66126..aeacbf7a2 100644 --- a/build/src/main/resources/agent.properties +++ b/build/src/main/resources/agent.properties @@ -8,6 +8,7 @@ easeagent.server.enabled=true easeagent.server.port=9900 # Enable health/readiness easeagent.health.readiness.enabled=true +plugin.integrability.global.healthReady.enabled=true # forwarded headers page # Pass-through headers from the root process all the way to the end # format: easeagent.progress.forwarded.headers={headerName} @@ -36,6 +37,7 @@ observability.tracings.tag.response.headers.eg.1=X-EG-Retryer observability.tracings.tag.response.headers.eg.2=X-EG-Rate-Limiter observability.tracings.tag.response.headers.eg.3=X-EG-Time-Limiter # -------------------- plugin global config --------------------- +plugin.observability.global.init.enabled=true plugin.observability.global.tracing.enabled=true plugin.observability.global.metric.enabled=true plugin.observability.global.metric.interval=30 @@ -198,7 +200,7 @@ plugin.observability.redis.metric.url=/platform-metrics # plugin.observability.springGateway.metric.appendType=kafka # # -------------------- request --------------------- -## httpclient tracing:httpclient and httpclient5 +## httpclient tracing\uFF1Ahttpclient and httpclient5 # plugin.observability.httpclient.tracing.enabled=true ## okHttp tracing # plugin.observability.okHttp.tracing.enabled=true @@ -352,3 +354,6 @@ reporter.tracing.output.messageTimeout=1000 ## final output url: http://127.0.0.10:9090/metric #reporter.metric.sender.url=/metrics +## support spring boot 3.5.3: jdk17+3.5.3 +#runtime.code.version.points.jdk=jdk17 +#runtime.code.version.points.spring-boot=3.x.x diff --git a/config/pom.xml b/config/pom.xml index 6472761e8..342fec114 100644 --- a/config/pom.xml +++ b/config/pom.xml @@ -22,7 +22,7 @@ easeagent com.megaease.easeagent - 2.2.9 + 2.3.0 4.0.0 diff --git a/config/src/main/java/com/megaease/easeagent/config/ConfigUtils.java b/config/src/main/java/com/megaease/easeagent/config/ConfigUtils.java index 7168dcbfc..6f6d6e3b8 100644 --- a/config/src/main/java/com/megaease/easeagent/config/ConfigUtils.java +++ b/config/src/main/java/com/megaease/easeagent/config/ConfigUtils.java @@ -32,7 +32,8 @@ import static com.megaease.easeagent.plugin.api.config.ConfigConst.*; public class ConfigUtils { - private ConfigUtils() {} + private ConfigUtils() { + } public static void bindProp(String name, Config configs, BiFunction func, Consumer consumer, R def) { Runnable process = () -> { @@ -134,12 +135,16 @@ public static String buildPluginProperty(String domain, String namespace, String return String.format(PLUGIN_FORMAT, domain, namespace, id, property); } + public static String buildCodeVersionKey(String key) { + return RUNTIME_CODE_VERSION_POINTS_PREFIX + key; + } + /** * extract config item with a fromPrefix to and convert the prefix to 'toPrefix' for configuration Compatibility * - * @param cfg config source map + * @param cfg config source map * @param fromPrefix from - * @param toPrefix to + * @param toPrefix to * @return Extracted and converted KV map */ public static Map extractAndConvertPrefix(Map cfg, String fromPrefix, String toPrefix) { @@ -162,8 +167,9 @@ public static Map extractAndConvertPrefix(Map cf /** * Extract config items from config by prefix - * @param config config - * @param prefix prefix + * + * @param config config + * @param prefix prefix * @return Extracted KV */ public static Map extractByPrefix(Config config, String prefix) { diff --git a/context/pom.xml b/context/pom.xml index 1248c803c..51569befd 100644 --- a/context/pom.xml +++ b/context/pom.xml @@ -22,7 +22,7 @@ easeagent com.megaease.easeagent - 2.2.9 + 2.3.0 4.0.0 diff --git a/core/pom.xml b/core/pom.xml index 667050ee9..377c99617 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -21,7 +21,7 @@ easeagent com.megaease.easeagent - 2.2.9 + 2.3.0 4.0.0 @@ -105,12 +105,6 @@ reactor-core provided - - org.springframework - spring-web - ${version.spring} - test - net.bytebuddy byte-buddy-agent diff --git a/core/src/main/java/com/megaease/easeagent/core/Bootstrap.java b/core/src/main/java/com/megaease/easeagent/core/Bootstrap.java index 74dcbb9b1..03baf8222 100644 --- a/core/src/main/java/com/megaease/easeagent/core/Bootstrap.java +++ b/core/src/main/java/com/megaease/easeagent/core/Bootstrap.java @@ -200,7 +200,7 @@ public static AgentBuilder getAgentBuilder(Configs config, boolean test) { .with(AgentBuilder.LocationStrategy.ForClassLoader.STRONG .withFallbackTo(ClassFileLocator.ForClassLoader.ofSystemLoader())); AgentBuilder.Ignored ignore = builder.ignore(isSynthetic()) - .or(nameStartsWith("sun.")) + .or(nameStartsWith("sun.").and(not(nameStartsWith("sun.net.www.protocol.http")) )) .or(nameStartsWith("com.sun.")) .or(nameStartsWith("brave.")) .or(nameStartsWith("zipkin2.")) diff --git a/core/src/main/java/com/megaease/easeagent/core/plugin/PluginLoader.java b/core/src/main/java/com/megaease/easeagent/core/plugin/PluginLoader.java index ceda39f23..7e0a3c65c 100644 --- a/core/src/main/java/com/megaease/easeagent/core/plugin/PluginLoader.java +++ b/core/src/main/java/com/megaease/easeagent/core/plugin/PluginLoader.java @@ -17,6 +17,7 @@ package com.megaease.easeagent.core.plugin; +import com.megaease.easeagent.config.ConfigUtils; import com.megaease.easeagent.config.Configs; import com.megaease.easeagent.core.plugin.matcher.ClassTransformation; import com.megaease.easeagent.core.plugin.matcher.MethodTransformation; @@ -24,13 +25,16 @@ import com.megaease.easeagent.core.plugin.transformer.CompoundPluginTransformer; import com.megaease.easeagent.core.plugin.transformer.DynamicFieldTransformer; import com.megaease.easeagent.core.plugin.transformer.ForAdviceTransformer; +import com.megaease.easeagent.core.plugin.transformer.TypeFieldTransformer; import com.megaease.easeagent.log4j2.Logger; import com.megaease.easeagent.log4j2.LoggerFactory; import com.megaease.easeagent.plugin.AgentPlugin; -import com.megaease.easeagent.plugin.interceptor.InterceptorProvider; +import com.megaease.easeagent.plugin.CodeVersion; import com.megaease.easeagent.plugin.Ordered; import com.megaease.easeagent.plugin.Points; import com.megaease.easeagent.plugin.field.AgentDynamicFieldAccessor; +import com.megaease.easeagent.plugin.interceptor.InterceptorProvider; +import com.megaease.easeagent.plugin.utils.common.StringUtils; import net.bytebuddy.agent.builder.AgentBuilder; import java.util.*; @@ -46,19 +50,27 @@ private PluginLoader() { public static AgentBuilder load(AgentBuilder ab, Configs conf) { pluginLoad(); + pointsLoad(conf); providerLoad(); - Set sortedTransformations = pointsLoad(); + Set sortedTransformations = classTransformationLoad(); for (ClassTransformation transformation : sortedTransformations) { ab = ab.type(transformation.getClassMatcher(), transformation.getClassloaderMatcher()) - .transform(compound(transformation.isHasDynamicField(), transformation.getMethodTransformations())); + .transform(compound(transformation.isHasDynamicField(), transformation.getMethodTransformations(), transformation.getTypeFieldAccessor())); } return ab; } public static void providerLoad() { for (InterceptorProvider provider : BaseLoader.load(InterceptorProvider.class)) { - log.debug("loading provider:{}", provider.getClass().getName()); + String pointsClassName = PluginRegistry.getPointsClassName(provider.getAdviceTo()); + Points points = PluginRegistry.getPoints(pointsClassName); + if (points == null) { + log.debug("Unload provider:{}, can not found Points<{}>", provider.getClass().getName(), pointsClassName); + continue; + } else { + log.debug("Loading provider:{}", provider.getClass().getName()); + } try { log.debug("provider for:{} at {}", @@ -73,14 +85,14 @@ public static void providerLoad() { } } - public static Set pointsLoad() { - List points = BaseLoader.load(Points.class); + public static Set classTransformationLoad() { + Collection points = PluginRegistry.getPoints(); return points.stream().map(point -> { try { - return PluginRegistry.register(point); + return PluginRegistry.registerClassTransformation(point); } catch (Exception e) { log.error( - "Unable to load points in [class {}]", + "Unable to load classTransformation in [class {}]", point.getClass().getName(), e); return null; @@ -111,12 +123,54 @@ public static void pluginLoad() { } } + public static void pointsLoad(Configs conf) { + for (Points points : BaseLoader.load(Points.class)) { + if (!isCodeVersion(points, conf)) { + continue; + } else { + log.info("Loading points [class Points<{}>]", points.getClass().getName()); + } + + try { + PluginRegistry.register(points); + } catch (Exception | LinkageError e) { + log.error( + "Unable to load extension [class {}]", + points.getClass().getName(), + e); + } + } + } + + public static boolean isCodeVersion(Points points, Configs conf) { + CodeVersion codeVersion = points.codeVersions(); + if (codeVersion.isEmpty()) { + return true; + } + String versionKey = ConfigUtils.buildCodeVersionKey(codeVersion.getKey()); + Set versions = new HashSet<>(conf.getStringList(versionKey)); + if (versions.isEmpty()) { + versions = Points.DEFAULT_VERSIONS; + } + Set pointVersions = codeVersion.getVersions(); + for (String version : versions) { + if (pointVersions.contains(version)) { + return true; + } + } + log.info("Unload points [class Points<{}>], the config [{}={}] is not in Points.codeVersions()=[{}:{}]", + points.getClass().getCanonicalName(), versionKey, String.join(",", versions), + codeVersion.getKey(), String.join(",", codeVersion.getVersions())); + return false; + } + + /** * @param methodTransformations method matchers under a special classMatcher * @return transform */ public static AgentBuilder.Transformer compound(boolean hasDynamicField, - Iterable methodTransformations) { + Iterable methodTransformations, String typeFieldAccessor) { List agentTransformers = StreamSupport .stream(methodTransformations.spliterator(), false) .map(ForAdviceTransformer::new) @@ -126,6 +180,10 @@ public static AgentBuilder.Transformer compound(boolean hasDynamicField, agentTransformers.add(new DynamicFieldTransformer(AgentDynamicFieldAccessor.DYNAMIC_FIELD_NAME)); } + if (StringUtils.hasText(typeFieldAccessor)) { + agentTransformers.add(new TypeFieldTransformer(typeFieldAccessor)); + } + return new CompoundPluginTransformer(agentTransformers); } } diff --git a/core/src/main/java/com/megaease/easeagent/core/plugin/matcher/ClassTransformation.java b/core/src/main/java/com/megaease/easeagent/core/plugin/matcher/ClassTransformation.java index 81a4cc646..959fa589c 100644 --- a/core/src/main/java/com/megaease/easeagent/core/plugin/matcher/ClassTransformation.java +++ b/core/src/main/java/com/megaease/easeagent/core/plugin/matcher/ClassTransformation.java @@ -32,14 +32,16 @@ public class ClassTransformation implements Ordered { private int order; private Junction classMatcher; private ElementMatcher classloaderMatcher; - private Set methodTransformations; + private Set methodTransformations; private boolean hasDynamicField; + private String typeFieldAccessor; public ClassTransformation(int order, ElementMatcher classloaderMatcher, Junction classMatcher, Set methodTransformations, - boolean hasDynamicField) { + boolean hasDynamicField, + String typeFieldAccessor) { this.order = order; if (classloaderMatcher == null) { this.classloaderMatcher = any(); @@ -49,6 +51,7 @@ public ClassTransformation(int order, this.classMatcher = classMatcher; this.methodTransformations = methodTransformations; this.hasDynamicField = hasDynamicField; + this.typeFieldAccessor = typeFieldAccessor; } public static Builder builder() { @@ -67,6 +70,8 @@ public static class Builder { private Set methodTransformations; private boolean hasDynamicField; + private String typeFieldAccessor; + Builder() { } @@ -95,9 +100,14 @@ public Builder hasDynamicField(boolean hasDynamicField) { return this; } + public Builder typeFieldAccessor(String typeFieldAccessor) { + this.typeFieldAccessor = typeFieldAccessor; + return this; + } + public ClassTransformation build() { return new ClassTransformation(order, classloaderMatcher, classMatcher, - methodTransformations, hasDynamicField); + methodTransformations, hasDynamicField, typeFieldAccessor); } public String toString() { diff --git a/core/src/main/java/com/megaease/easeagent/core/plugin/registry/PluginRegistry.java b/core/src/main/java/com/megaease/easeagent/core/plugin/registry/PluginRegistry.java index 9bec3d287..8b9e23594 100644 --- a/core/src/main/java/com/megaease/easeagent/core/plugin/registry/PluginRegistry.java +++ b/core/src/main/java/com/megaease/easeagent/core/plugin/registry/PluginRegistry.java @@ -35,8 +35,7 @@ import net.bytebuddy.matcher.ElementMatcher; import net.bytebuddy.matcher.ElementMatcher.Junction; -import java.util.Objects; -import java.util.Set; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; @@ -46,22 +45,36 @@ public class PluginRegistry { static final ConcurrentHashMap QUALIFIER_TO_PLUGIN = new ConcurrentHashMap<>(); static final ConcurrentHashMap POINTS_TO_PLUGIN = new ConcurrentHashMap<>(); static final ConcurrentHashMap PLUGIN_CLASSNAME_TO_PLUGIN = new ConcurrentHashMap<>(); + static final ConcurrentHashMap POINTS_CLASSNAME_TO_POINTS = new ConcurrentHashMap<>(); static final ConcurrentHashMap QUALIFIER_TO_INDEX = new ConcurrentHashMap<>(); static final ConcurrentHashMap INDEX_TO_METHOD_TRANSFORMATION = new ConcurrentHashMap<>(); static final AgentArray INTERCEPTOR_PROVIDERS = new AgentArray<>(); - private PluginRegistry() {} + private PluginRegistry() { + } public static void register(AgentPlugin plugin) { PLUGIN_CLASSNAME_TO_PLUGIN.putIfAbsent(plugin.getClass().getCanonicalName(), plugin); } + public static void register(Points points) { + POINTS_CLASSNAME_TO_POINTS.putIfAbsent(points.getClass().getCanonicalName(), points); + } + + public static Collection getPoints() { + return POINTS_CLASSNAME_TO_POINTS.values(); + } + + public static Points getPoints(String pointsClassName) { + return POINTS_CLASSNAME_TO_POINTS.get(pointsClassName); + } + private static String getMethodQualifier(String classname, String qualifier) { return classname + ":" + qualifier; } - public static ClassTransformation register(Points points) { + public static ClassTransformation registerClassTransformation(Points points) { String pointsClassName = points.getClass().getCanonicalName(); IClassMatcher classMatcher = points.getClassMatcher(); boolean hasDynamicField = points.isAddDynamicField(); @@ -81,6 +94,9 @@ public static ClassTransformation register(Points points) { return null; } Builder providerBuilder = INTERCEPTOR_PROVIDERS.get(index); + if (providerBuilder == null) { + return null; + } MethodTransformation mt = new MethodTransformation(index, bMethodMatcher, providerBuilder); if (INDEX_TO_METHOD_TRANSFORMATION.putIfAbsent(index, mt) != null) { log.error("There are duplicate qualifier in Points:{}!", qualifier); @@ -90,11 +106,11 @@ public static ClassTransformation register(Points points) { AgentPlugin plugin = POINTS_TO_PLUGIN.get(pointsClassName); int order = plugin.order(); - return ClassTransformation.builder().classMatcher(innerClassMatcher) .hasDynamicField(hasDynamicField) .methodTransformations(mInfo) .classloaderMatcher(loaderMatcher) + .typeFieldAccessor(points.getTypeFieldAccessor()) .order(order).build(); } @@ -107,7 +123,6 @@ public static int register(InterceptorProvider provider) { // code autogenerate issues that are unlikely to occur! throw new RuntimeException(); } - QUALIFIER_TO_PLUGIN.putIfAbsent(qualifier, plugin); POINTS_TO_PLUGIN.putIfAbsent(getPointsClassName(qualifier), plugin); @@ -128,7 +143,7 @@ public static int register(InterceptorProvider provider) { return index; } - static String getPointsClassName(String name) { + public static String getPointsClassName(String name) { int index; if (Strings.isNullOrEmpty(name)) { return "unknown"; @@ -147,4 +162,5 @@ public static MethodTransformation getMethodTransformation(int pointcutIndex) { public static void addMethodTransformation(int pointcutIndex, MethodTransformation info) { INDEX_TO_METHOD_TRANSFORMATION.putIfAbsent(pointcutIndex, info); } + } diff --git a/core/src/main/java/com/megaease/easeagent/core/plugin/transformer/DynamicFieldTransformer.java b/core/src/main/java/com/megaease/easeagent/core/plugin/transformer/DynamicFieldTransformer.java index 7e4b6099f..95b78a389 100644 --- a/core/src/main/java/com/megaease/easeagent/core/plugin/transformer/DynamicFieldTransformer.java +++ b/core/src/main/java/com/megaease/easeagent/core/plugin/transformer/DynamicFieldTransformer.java @@ -85,6 +85,9 @@ private static boolean check(TypeDescription td, Class accessor, ClassLoader Cache checkCache = FIELD_MAP.get(key); if (checkCache == null) { Cache cache = CacheBuilder.newBuilder().weakKeys().build(); + if (cl == null) { + cl = Thread.currentThread().getContextClassLoader(); + } cache.put(cl, true); checkCache = FIELD_MAP.putIfAbsent(key, cache); if (checkCache == null) { diff --git a/core/src/main/java/com/megaease/easeagent/core/plugin/transformer/TypeFieldTransformer.java b/core/src/main/java/com/megaease/easeagent/core/plugin/transformer/TypeFieldTransformer.java new file mode 100644 index 000000000..59a52b099 --- /dev/null +++ b/core/src/main/java/com/megaease/easeagent/core/plugin/transformer/TypeFieldTransformer.java @@ -0,0 +1,73 @@ +package com.megaease.easeagent.core.plugin.transformer; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.megaease.easeagent.log4j2.Logger; +import com.megaease.easeagent.log4j2.LoggerFactory; +import com.megaease.easeagent.plugin.field.TypeFieldGetter; +import net.bytebuddy.agent.builder.AgentBuilder; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.dynamic.DynamicType; +import net.bytebuddy.implementation.FieldAccessor; +import net.bytebuddy.utility.JavaModule; + +import java.util.concurrent.ConcurrentHashMap; + +public class TypeFieldTransformer implements AgentBuilder.Transformer { + private static final Logger log = LoggerFactory.getLogger(TypeFieldTransformer.class); + + private static final ConcurrentHashMap> FIELD_MAP = new ConcurrentHashMap<>(); + private final String fieldName; + private final Class accessor; + private final AgentBuilder.Transformer.ForAdvice transformer; + public TypeFieldTransformer(String fieldName) { + this.fieldName = fieldName; + this.accessor = TypeFieldGetter.class; + this.transformer = new AgentBuilder.Transformer + .ForAdvice(Advice.withCustomMapping()) + .include(getClass().getClassLoader()); + } + + @Override + public DynamicType.Builder transform(DynamicType.Builder b, + TypeDescription td, ClassLoader cl, JavaModule m) { + if (check(td, this.accessor, cl) && this.fieldName != null) { + try { + b = b.implement(this.accessor) + .intercept(FieldAccessor.ofField(this.fieldName)); + } catch (Exception e) { + log.debug("Type:{} add extend field again!", td.getName()); + } + return transformer.transform(b, td, cl, m); + } + return b; + } + + /** + * Avoiding add a accessor interface to a class repeatedly + * + * @param td represent the class to be enhanced + * @param accessor access interface class + * @param cl current classloader + * @return return true when it is the first time + */ + private static boolean check(TypeDescription td, Class accessor, ClassLoader cl) { + String key = td.getCanonicalName() + accessor.getCanonicalName(); + + Cache checkCache = FIELD_MAP.get(key); + if (checkCache == null) { + Cache cache = CacheBuilder.newBuilder().weakKeys().build(); + if (cl == null) { + cl = Thread.currentThread().getContextClassLoader(); + } + cache.put(cl, true); + checkCache = FIELD_MAP.putIfAbsent(key, cache); + if (checkCache == null) { + return true; + } + } + + return checkCache.getIfPresent(cl) == null; + } +} diff --git a/core/src/test/java/com/megaease/easeagent/core/BootstrapTest.java b/core/src/test/java/com/megaease/easeagent/core/BootstrapTest.java index c3cce9134..538601d7e 100644 --- a/core/src/test/java/com/megaease/easeagent/core/BootstrapTest.java +++ b/core/src/test/java/com/megaease/easeagent/core/BootstrapTest.java @@ -21,7 +21,6 @@ import com.j256.simplejmx.client.JmxClient; import com.j256.simplejmx.common.IoUtils; import com.j256.simplejmx.server.JmxServer; -import com.megaease.easeagent.config.Configs; import com.megaease.easeagent.config.GlobalConfigs; import lombok.SneakyThrows; import org.hamcrest.CoreMatchers; diff --git a/core/src/test/java/com/megaease/easeagent/core/instrument/ClinitMethodTransformTest.java b/core/src/test/java/com/megaease/easeagent/core/instrument/ClinitMethodTransformTest.java index 98eb97b99..4b3bc9e93 100644 --- a/core/src/test/java/com/megaease/easeagent/core/instrument/ClinitMethodTransformTest.java +++ b/core/src/test/java/com/megaease/easeagent/core/instrument/ClinitMethodTransformTest.java @@ -95,12 +95,12 @@ public void testTypeInitialAdviceTransformer() throws Exception { ClassFileTransformer classFileTransformer = builder .type(ElementMatchers.is(Foo.class), ElementMatchers.is(classLoader)) - .transform(PluginLoader.compound(false, transformations)) + .transform(PluginLoader.compound(false, transformations, null)) .installOnByteBuddyAgent(); try { Class type = classLoader.loadClass(Foo.class.getName()); - AgentFieldReflectAccessor.setStaticFieldValue(type, "clazzInitString", BAR+QUX); + AgentFieldReflectAccessor.setStaticFieldValue(type, "clazzInitString", BAR + QUX); testString = AgentFieldReflectAccessor.getStaticFieldValue(type, "clazzInitString"); assertEquals(BAR + QUX, testString); // wait to finish diff --git a/core/src/test/java/com/megaease/easeagent/core/instrument/NewInstanceMethodTransformTest.java b/core/src/test/java/com/megaease/easeagent/core/instrument/NewInstanceMethodTransformTest.java index 63acd7ae5..f27eaca0e 100644 --- a/core/src/test/java/com/megaease/easeagent/core/instrument/NewInstanceMethodTransformTest.java +++ b/core/src/test/java/com/megaease/easeagent/core/instrument/NewInstanceMethodTransformTest.java @@ -95,7 +95,7 @@ public void testClassInstanceTransformer() throws Exception { ClassFileTransformer classFileTransformer = builder .type(hasSuperType(named(FooBase.class.getName())), ElementMatchers.is(classLoader)) - .transform(PluginLoader.compound(true, transformations)) + .transform(PluginLoader.compound(true, transformations, null)) .installOnByteBuddyAgent(); try { Class type = classLoader.loadClass(Foo.class.getName()); diff --git a/core/src/test/java/com/megaease/easeagent/core/instrument/NonStaticMethodTransformTest.java b/core/src/test/java/com/megaease/easeagent/core/instrument/NonStaticMethodTransformTest.java index ec6c55dad..85212c40a 100644 --- a/core/src/test/java/com/megaease/easeagent/core/instrument/NonStaticMethodTransformTest.java +++ b/core/src/test/java/com/megaease/easeagent/core/instrument/NonStaticMethodTransformTest.java @@ -87,7 +87,7 @@ public void testAdviceTransformer() throws Exception { ClassFileTransformer classFileTransformer = builder .type(hasSuperType(named(FooBase.class.getName())), ElementMatchers.is(classLoader)) - .transform(PluginLoader.compound(true, transformations)) + .transform(PluginLoader.compound(true, transformations, null)) .installOnByteBuddyAgent(); try { Class type = classLoader.loadClass(Foo.class.getName()); diff --git a/core/src/test/java/com/megaease/easeagent/core/instrument/OrchestrationTransformTest.java b/core/src/test/java/com/megaease/easeagent/core/instrument/OrchestrationTransformTest.java index 4591ab733..2aa192b63 100644 --- a/core/src/test/java/com/megaease/easeagent/core/instrument/OrchestrationTransformTest.java +++ b/core/src/test/java/com/megaease/easeagent/core/instrument/OrchestrationTransformTest.java @@ -89,8 +89,8 @@ public void testOrchestration() { ClassFileTransformer classFileTransformer = builder .type(hasSuperType(named(FooBase.class.getName())), ElementMatchers.is(classLoader)) - .transform(PluginLoader.compound(true, transformations)) - .transform(PluginLoader.compound(true, secTransformations)) + .transform(PluginLoader.compound(true, transformations, null)) + .transform(PluginLoader.compound(true, secTransformations, null)) .installOnByteBuddyAgent(); try { Class type = classLoader.loadClass(Foo.class.getName()); diff --git a/core/src/test/java/com/megaease/easeagent/core/instrument/StaticMethodTransformTest.java b/core/src/test/java/com/megaease/easeagent/core/instrument/StaticMethodTransformTest.java index dd402c4a6..40c6d7923 100644 --- a/core/src/test/java/com/megaease/easeagent/core/instrument/StaticMethodTransformTest.java +++ b/core/src/test/java/com/megaease/easeagent/core/instrument/StaticMethodTransformTest.java @@ -89,7 +89,7 @@ public void testStaticAdviceTransformer() throws Exception { ClassFileTransformer classFileTransformer = builder .type(ElementMatchers.is(Foo.class), ElementMatchers.is(classLoader)) - .transform(PluginLoader.compound(false, transformations)) + .transform(PluginLoader.compound(false, transformations, null)) .installOnByteBuddyAgent(); try { @@ -117,7 +117,7 @@ public void testTypeInitialAdviceTransformer() throws Exception { ClassFileTransformer classFileTransformer = builder .type(ElementMatchers.is(Foo.class), ElementMatchers.is(classLoader)) - .transform(PluginLoader.compound(false, transformations)) + .transform(PluginLoader.compound(false, transformations, null)) .installOnByteBuddyAgent(); try { @@ -132,9 +132,11 @@ public void testTypeInitialAdviceTransformer() throws Exception { @SuppressWarnings("unused") public static class Foo { static String clazzInitString = FOO; + public static String fooStatic(String a) { return a; } + public String foo(String a) { return a; } diff --git a/core/src/test/java/com/megaease/easeagent/core/plugin/PluginLoaderTest.java b/core/src/test/java/com/megaease/easeagent/core/plugin/PluginLoaderTest.java new file mode 100644 index 000000000..cd2898958 --- /dev/null +++ b/core/src/test/java/com/megaease/easeagent/core/plugin/PluginLoaderTest.java @@ -0,0 +1,87 @@ +package com.megaease.easeagent.core.plugin; + +import com.megaease.easeagent.config.Configs; +import com.megaease.easeagent.core.plugin.registry.PluginRegistry; +import com.megaease.easeagent.plugin.AgentPlugin; +import com.megaease.easeagent.plugin.CodeVersion; +import com.megaease.easeagent.plugin.Points; +import com.megaease.easeagent.plugin.interceptor.Interceptor; +import com.megaease.easeagent.plugin.interceptor.InterceptorProvider; +import com.megaease.easeagent.plugin.matcher.IClassMatcher; +import com.megaease.easeagent.plugin.matcher.IMethodMatcher; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Supplier; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class PluginLoaderTest { + + @Test + public void isVersion() { + AgentPlugin plugin = new TestAgentPlugin(); + PluginRegistry.register(plugin); + TestPoints points = new TestPoints(); + assertTrue(PluginLoader.isCodeVersion(points, new Configs(Collections.emptyMap()))); + Configs configs = new Configs(Collections.singletonMap("runtime.code.version.points.sprint-boot", "spring_boot_2_x")); + //empty codeVersion always true + assertTrue(PluginLoader.isCodeVersion(points, configs)); + + points.versions = CodeVersion.builder().key("sprint-boot").add(Points.DEFAULT_VERSION).add("spring_boot_2_x").build(); + assertTrue(PluginLoader.isCodeVersion(points, configs)); + + points.versions = CodeVersion.builder().key("sprint-boot").add("spring_boot_2_x").build(); + assertTrue(PluginLoader.isCodeVersion(points, configs)); + + points.versions = CodeVersion.builder().key("sprint-boot").add("spring_boot_3_x").build(); + assertFalse(PluginLoader.isCodeVersion(points, configs)); + + configs = new Configs(Collections.singletonMap("runtime.code.version.points.sprint-boot", "spring_boot_3_x")); + points.versions = CodeVersion.builder().key("sprint-boot").add(Points.DEFAULT_VERSION).add("spring_boot_2_x").build(); + assertFalse(PluginLoader.isCodeVersion(points, configs)); + + configs = new Configs(Collections.emptyMap()); + assertTrue(PluginLoader.isCodeVersion(points, configs)); + + } + + class TestAgentPlugin implements AgentPlugin { + @Override + public String getNamespace() { + return "test_namespace"; + } + + @Override + public String getDomain() { + return "test_domain"; + } + } + + class TestPoints implements Points { + CodeVersion versions; + + @Override + public CodeVersion codeVersions() { + if (versions == null) { + return Points.super.codeVersions(); + } else { + return versions; + } + } + + @Override + public IClassMatcher getClassMatcher() { + return null; + } + + @Override + public Set getMethodMatcher() { + return null; + } + } +} diff --git a/doc/development-guide.md b/doc/development-guide.md index ccb55b7e2..ab32c993d 100644 --- a/doc/development-guide.md +++ b/doc/development-guide.md @@ -4,6 +4,7 @@ - [Architecture](#architecture) - [Plugin Structure](#plugin-structure) - [Points](#points) + - [Code Version](#code-version) - [Interceptor](#interceptor) - [Plugin Orchestration](#plugin-orchestration) - [AdviceTo Annotation](#adviceto-annotation) @@ -73,6 +74,23 @@ Decoupled from **ByteBuddy**, the EaseAgent "ClassMatcher" and "MethodMatcher" w The DSL of `ClassMatcher` and `MethodMatcher` is described in [Matcher DSL](./matcher-DSL.md) ```java public interface Points { + CodeVersion EMPTY_VERSION = CodeVersion.builder().build(); + + + /** + * eg. + * versions=CodeVersion.builder().key("jdk").add("default").add("jdk8").build() + * do not set or set the following value to load: runtime.code.version.points.jdk=jdk8 + *

+ * when set for not load: runtime.code.version.points.jdk=jdk17 + * but load from Points: versions=CodeVersion.builder().key("jdk").add("jdk17").build() + * @see com.megaease.easeagent.plugin.CodeVersion + * @return CodeVersion code of versions for control whether to load, If EMPTY_VERSIONS is returned, it means it will load forever + */ + default CodeVersion codeVersions() { + return EMPTY_VERSION; + } + /** * return the defined class matcher matching a class or a group of classes * eg. @@ -122,9 +140,38 @@ public interface Points { default boolean isAddDynamicField() { return false; } + + /** + * When a non-null string is returned, the converter will add an accessor to get the member variables inside the class. + * Get method: value = TypeFieldGetter.get(instance) + * @see com.megaease.easeagent.plugin.field.TypeFieldGetter#get(Object) + * @return String field name + */ + default String getTypeFieldAccessor() { + return null; + } } ``` +#### code-version +The same function may be implemented differently in different versions of the runtime environment, which may cause errors in the Interceptor runtime, such as reporting ClassNotFoundException. + +In this case, different versions of Interceptor should use different `Points`. + +At this time, just implement the codeVersions() method of `Points`, return different version numbers, and specify a specific version number in the configuration at runtime. + +The configuration rules are as follows: + +* If `CodeVersion.builder().build()` is returned, it means it will load forever. +* The configuration format is as follows: `runtime.code.version.points.{key}={version}` +* There is a special string `default` in version, please do not occupy it. + * When the returned version has this `default`, it means that if there is no configuration for this key, Points will be used by default. +* eg. + * CodeVersion.builder().key("jdk").add("default").build(), it means it will load by default. but not load by specified like `runtime.code.version.points.jdk=jdk10` + * CodeVersion.builder().key("jdk").add("jdk10").add("jdk11").build() + * When multiple versions are specified, it means that it can be loaded by multiple versions: + * `runtime.code.version.points.jdk=jdk10` or `runtime.code.version.points.jdk=jdk11` + ### Interceptor `Interceptor` is the core of implementing specific enhancements. diff --git a/doc/spring-boot-3.x.x-demo.md b/doc/spring-boot-3.x.x-demo.md new file mode 100644 index 000000000..d89fd4bec --- /dev/null +++ b/doc/spring-boot-3.x.x-demo.md @@ -0,0 +1,98 @@ +# Spring Boot 3.5.3 Demo + +You need Java 17+, Maven 3.9.10 and easeagent: + +If you don't already have easeagent, get it and set $EASE_AGENT_PATH: [EaseAgent](../README.md#get-and-set-environment-variable) + +## 1. Building the application. +There is a [demo](https://github.com/megaease/easeagent-spring-boot-demo-3.5.3) which is spring web and client +``` +$ git clone https://github.com/megaease/easeagent-spring-boot-demo-3.5.3.git +$ cd easeagent-spring-boot-demo-3.5.3 +$ mvn clean package -Dmaven.test.skip +``` +## 2. Add code version to config + +add two configurations for the `${EASE_AGENT_PATH}/agent.properties` to take effect: +```properties +runtime.code.version.points.jdk=jdk17 +runtime.code.version.points.spring-boot=3.x.x +``` + +## 3. Run the demo application with EaseAgent. +``` +# Open another console +$ export EASE_AGENT_PATH=[Replace with agent path] +$ java "-javaagent:${EASE_AGENT_PATH}/easeagent-dep.jar" -Deaseagent.config.path=${EASE_AGENT_PATH}/agent.properties -Deaseagent.server.port=9900 -jar spring-web/target/spring-web-0.0.1-SNAPSHOT.jar + +``` + +Open another console, run curl to access the test url for several times. + +``` +$ for i in {1..1000}; do curl -v http://127.0.0.1:18889/web_client;sleep 0.1; done +``` + +## 4. How to verify it? + +### * Tracing + +If the tracing data is send to console, there would be some tracing log in console like this: +``` +[{"traceId":"5a8800b902703307","parentId":"84c4cba42fb92788","id":"fd00a1705c88cbb2","kind":"SERVER","name":"get","timestamp":1639493283759877,"duration":217545,"shared":true,"localEndpoint":{"serviceName":"demo-service","ipv4":"192.168.0.102"},"remoteEndpoint":{"ipv4":"127.0.0.1","port":55809},"tags":{"http.method":"GET","http.path":"/hello","http.route":"/hello","i":"ServerName.local"},"type":"log-tracing","service":"demo-service","system":"demo-system"},{"traceId":"5a8800b902703307","id":"5a8800b902703307","kind":"SERVER","name":"get","timestamp":1639493283753466,"duration":228827,"localEndpoint":{"serviceName":"demo-service","ipv4":"192.168.0.102"},"remoteEndpoint":{"ipv4":"127.0.0.1","port":55851},"tags":{"http.method":"GET","http.path":"/web_client","i":"ServerName.local"},"type":"log-tracing","service":"demo-service","system":"demo-system"}] +... +``` + +### * Metric + +#### Integrate with Prometheus + +Adding the following configuration in `prometheus.yml` +```yaml + - job_name: 'spring-web-service' + static_configs: + - targets: ['localhost:9900'] + metrics_path: "/prometheus/metrics" +``` + +Start Prometheus +```bash +$ ./prometheus --config.file=prometheus.yml +``` + +Open Browser to visit [http://localhost:9090](http://localhost:9090). + +Prometheus Metric Schedule: [Prometheus Metric](./prometheus-metric-schedule.md) + +search `application_http_request_m1{url="GET /web_client"}`. You will see as following. + +![image](./images/prometheus-demo.jpg) + +## Configuration + +* Modify service name, default configuration is `demo-service`. +``` +name=[app-name] +``` +* Modify kafka server config, default configuration is `127.0.0.1:9092`. + Both `tracing` data and `metric` data will be send to kafka server by default configuration. +``` +#reporter.outputServer.bootstrapServer=[ip:port] +``` + +* Modify output configuration, if you want to watch log information in console. +``` +# metric output +plugin.observability.global.metric.appendType=console + +# tracings output to console +reporter.tracing.sender.appendType=console +``` + +* Sending tracing data to zipkin server +``` +# [zipkin]: send data to zipkin server +# [system]: send data to kafka +reporter.tracing.sender.appendType=http +reporter.tracing.sender.url=http://localhost:9411/api/v2/spans +``` diff --git a/doc/user-manual.md b/doc/user-manual.md index bea1fe01e..ab8e0a841 100644 --- a/doc/user-manual.md +++ b/doc/user-manual.md @@ -5,6 +5,10 @@ - [Configuration](#configuration) - [Getting the configuration file](#getting-the-configuration-file) - [Global Configuration](#global-configuration) + - [Agent Configuration](#agent-configuration) + - [Runtime Code Version Configuration](#runtime-code-version-configuration) + - [Jdk](#jdk) + - [Spring Boot](#spring-boot) - [Internal HTTP Server](#internal-http-server) - [Output Data Server: Kafka and HTTP/Zipkin Server](#output-data-server-kafka-and-httpzipkin-server) - [Progress Configuration](#progress-configuration) @@ -98,6 +102,42 @@ $ java "-javaagent:${EASE_AGENT_PATH}/easeagent.jar" -jar user-app.jar | `easeagent.name` | `EASEAGENT_NAME` | `name` | Specify logical service name. | | `easeagent.system` | `EASEAGENT_SYSTEM` | `system` | Specify logical service system. | +#### Runtime Code Version Configuration + +##### jdk + +* `System property`: `runtime.code.version.points.jdk` +* `version`: `jdk8`,`jdk17` +* `description`: specify the JDK version of the running environment +* `plugins`: + * httpurlconnection: `default`,`jdk8` + * httpurlconnection-jdk17: `jdk17` + * tomcat-jdk17: `jdk17` +* eg. `runtime.code.version.points.jdk=jdk17` + +##### spring-boot + +* `System property`: `runtime.code.version.points.spring-boot` +* `version`: `2.x.x`,`3.x.x` +* `description`: specify the spring-boot version of the running environment +* `plugins`: + * servicename: `default`,`2.x.x` + * spring-gateway: `default`,`2.x.x` + * springweb: `default`,`2.x.x` + * spring-boot-gateway-3.5.3: `3.x.x` + * spring-boot-rest-template-3.5.3: `3.x.x` + * spring-boot-servicename-3.5.3: `3.x.x` +* eg. `runtime.code.version.points.spring-boot=3.x.x` + +###### about spring boot 3.x.x +When your code uses Spring Boot 3.x.x, it means that your code depends on JDK 17+ and Spring Boot 3+. + +In this case, you need to add two configurations for the agent to take effect: +```properties +runtime.code.version.points.jdk=jdk17 +runtime.code.version.points.spring-boot=3.x.x +``` + #### Internal HTTP Server EaseAgent opens port `9900` by default to receive configuration change notifications and Prometheus requests. diff --git a/httpserver/pom.xml b/httpserver/pom.xml index 3b4c360ef..77bfdd41f 100644 --- a/httpserver/pom.xml +++ b/httpserver/pom.xml @@ -22,7 +22,7 @@ easeagent com.megaease.easeagent - 2.2.9 + 2.3.0 4.0.0 diff --git a/loader/pom.xml b/loader/pom.xml index fca9385a4..2f3971df7 100644 --- a/loader/pom.xml +++ b/loader/pom.xml @@ -21,18 +21,13 @@ easeagent com.megaease.easeagent - 2.2.9 + 2.3.0 4.0.0 loader - - org.springframework.boot - spring-boot-loader - 2.3.8.RELEASE - com.google.guava guava diff --git a/loader/src/main/java/com/megaease/easeagent/EaseAgentClassLoader.java b/loader/src/main/java/com/megaease/easeagent/EaseAgentClassLoader.java new file mode 100644 index 000000000..eb09047c1 --- /dev/null +++ b/loader/src/main/java/com/megaease/easeagent/EaseAgentClassLoader.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent; + +import java.lang.ref.WeakReference; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * EaseAgent's exclusive classloader, used to isolate classes + */ +public class EaseAgentClassLoader extends URLClassLoader { + static { + ClassLoader.registerAsParallelCapable(); + } + + private final Set> externals = new CopyOnWriteArraySet<>(); + + public EaseAgentClassLoader(URL[] urls, ClassLoader parent) { + super(urls, parent); + } + + @SuppressWarnings("unused") + public void add(ClassLoader cl) { + if (cl != null && !Objects.equals(cl, this)) { + externals.add(new WeakReference<>(cl)); + } + } + + @Override + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + try { + return super.loadClass(name, resolve); + } catch (ClassNotFoundException e) { + for (WeakReference external : externals) { + try { + ClassLoader cl = external.get(); + if (cl == null) { + continue; + } + final Class aClass = cl.loadClass(name); + if (resolve) { + resolveClass(aClass); + } + return aClass; + } catch (ClassNotFoundException ignored) { + // ignored + } + } + + throw e; + } + } + + @Override + public URL findResource(String name) { + URL url = super.findResource(name); + if (url == null) { + for (WeakReference external : externals) { + try { + ClassLoader cl = external.get(); + url = cl.getResource(name); + if (url != null) { + return url; + } + } catch (Exception ignored) { + // ignored + } + } + } + return url; + } +} diff --git a/loader/src/main/java/com/megaease/easeagent/JarCache.java b/loader/src/main/java/com/megaease/easeagent/JarCache.java new file mode 100644 index 000000000..0a3b5e664 --- /dev/null +++ b/loader/src/main/java/com/megaease/easeagent/JarCache.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent; + +import java.io.*; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import java.util.jar.Attributes; +import java.util.jar.JarFile; +import java.util.jar.Manifest; + +/** + * EaseAgent's jar reading cache. + * Only one layer of sub-jar packages is read, and multi-layer nested reading is not supported. + * easeagent has only one layer of sub-jar packages, so only one layer needs to be read. + * It does not need to use spring-boot's loader, which reduces dependencies and facilitates spring-boot business upgrades + */ +public class JarCache { + private static final int EOF = -1; + private static final int BUFFER_SIZE = 4096; + + private final JarFile jarFile; + private final Map childJars; + private final Map childUrls; + + public JarCache(JarFile jarFile, Map childJars, Map childUrls) throws IOException { + this.jarFile = jarFile; + this.childJars = childJars; + this.childUrls = childUrls; + } + + public ArrayList nestJarUrls(String prefix) { + ArrayList urls = new ArrayList<>(); + for (Map.Entry entry : childUrls.entrySet()) { + if (entry.getKey().startsWith(prefix)) { + urls.add(entry.getValue()); + } + } + return urls; + } + + public ArrayList nestJarFiles(String prefix) { + ArrayList jarFiles = new ArrayList<>(); + for (Map.Entry entry : childJars.entrySet()) { + if (entry.getKey().startsWith(prefix)) { + jarFiles.add(entry.getValue()); + } + } + return jarFiles; + } + + public Manifest getManifest() throws IOException { + return this.jarFile.getManifest(); + } + + + static JarCache build(File file) throws IOException { + final JarFile jarFile = new JarFile(file); + String tmpDir = getTmpDir(jarFile); + Map childJars = new HashMap<>(); + Map childUrls = new HashMap<>(); + jarFile.stream().forEach(jarEntry -> { + String name = jarEntry.getName(); + if (!jarEntry.isDirectory() && name.endsWith(".jar")) { + try (InputStream input = jarFile.getInputStream(jarEntry)) { + File output = createTempJarFile(tmpDir, input, jarEntry.getName()); + JarFile childJarFile = new JarFile(output); + childJars.put(name, childJarFile); + childUrls.put(name, output.toURI().toURL()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + }); + return new JarCache(jarFile, childJars, childUrls); + } + + private static File createTempJarFile(String tmpDir, InputStream input, String outputName) throws IOException { + File dir; + String fName = (new File(outputName)).getName(); + if (fName.length() < outputName.length()) { + String localDir = outputName.substring(0, outputName.length() - fName.length()); + Path path = Paths.get(tmpDir + File.separatorChar + localDir); + dir = Files.createDirectories(path).toFile(); + } else { + dir = new File(tmpDir); + } + File f = new File(dir, fName); + f.deleteOnExit(); + try (FileOutputStream outputStream = new FileOutputStream(f)) { + copy(input, outputStream); + } + + return f; + } + + public static void copy(InputStream input, OutputStream output) throws IOException { + int n; + final byte[] buffer = new byte[BUFFER_SIZE]; + while (EOF != (n = input.read(buffer))) { + output.write(buffer, 0, n); + } + } + + public static String getTmpDir(JarFile jarFile) throws IOException { + String tmp = System.getProperty("java.io.tmpdir"); + Random random = new Random(); + String dirName = "easeagent-" + getAttribute(jarFile, "Easeagent-Version") + "-" + Math.abs(random.nextLong()); + if (tmp != null && tmp.endsWith(String.valueOf(File.separatorChar))) { + return tmp + dirName + File.separatorChar; + } + return tmp + File.separatorChar + dirName + File.separatorChar; + } + + public static String getAttribute(JarFile jarFile, String key) throws IOException { + final Attributes attributes = jarFile.getManifest().getMainAttributes(); + return attributes.getValue(key); + } + +} diff --git a/loader/src/main/java/com/megaease/easeagent/JarUtils.java b/loader/src/main/java/com/megaease/easeagent/JarUtils.java deleted file mode 100644 index 98c36b75d..000000000 --- a/loader/src/main/java/com/megaease/easeagent/JarUtils.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (c) 2021, MegaEase - * All rights reserved. - * - * 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.megaease.easeagent; - -import java.io.*; -import java.lang.ref.SoftReference; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; - -public class JarUtils { - - private JarUtils() { - } - - private static final int EOF = -1; - private static final int BUFFER_SIZE = 4096; - - private static final File TMP_FILE = new File(AccessController.doPrivileged( - new PrivilegedAction() { - @Override - public String run() { - return System.getProperty("java.io.tmpdir") + File.separatorChar + "easeagent" + File.separatorChar; - } - }) - ); - - private static SoftReference> fileCache; - - static { - fileCache = new SoftReference<>(new ConcurrentHashMap<>()); - } - - private static final String SEPARATOR = "!/"; - private static final String FILE_PROTOCOL = "file:"; - - static JarFile getNestedJarFile(URL url) throws IOException { - StringSequence spec = new StringSequence(url.getFile()); - Map cache = fileCache.get(); - JarFile jarFile = (cache != null) ? cache.get(spec.toString()) : null; - - if (jarFile != null) { - return jarFile; - } else { - jarFile = getRootJarFileFromUrl(url); - } - - int separator; - int index = indexOfRootSpec(spec); - if (index == -1) { - return null; - } - StringSequence entryName; - if ((separator = spec.indexOf(SEPARATOR, index)) > 0) { - entryName = spec.subSequence(index, separator); - } else { - entryName = spec.subSequence(index); - } - JarEntry jarEntry = jarFile.getJarEntry(entryName.toString()); - if (jarEntry == null) { - return null; - } - try (InputStream input = jarFile.getInputStream(jarEntry)) { - File output = createTempJarFile(input, jarEntry.getName()); - jarFile = new JarFile(output); - addToRootFileCache(url.getPath(), jarFile); - } - - return jarFile; - } - - public static void copy(InputStream input, OutputStream output) throws IOException { - int n; - final byte[] buffer = new byte[BUFFER_SIZE]; - while (EOF != (n = input.read(buffer))) { - output.write(buffer, 0, n); - } - } - - private static File createTempJarFile(InputStream input, String outputName) throws IOException { - File dir; - String fName = (new File(outputName)).getName(); - if (fName.length() < outputName.length()) { - String localDir = outputName.substring(0, outputName.length() - fName.length()); - Path path = Paths.get(TMP_FILE.getPath() + File.separatorChar + localDir); - dir = Files.createDirectories(path).toFile(); - } else { - dir = TMP_FILE; - } - File f = new File(dir, fName); - f.deleteOnExit(); - try (FileOutputStream outputStream = new FileOutputStream(f)) { - copy(input, outputStream); - } - - return f; - } - - private static int indexOfRootSpec(StringSequence file) { - int separatorIndex = file.indexOf(SEPARATOR); - if (separatorIndex < 0) { - return -1; - } - return separatorIndex + SEPARATOR.length(); - } - - public static String getRootJarFileName(URL url) throws MalformedURLException { - String spec = url.getFile(); - int separatorIndex = spec.indexOf(SEPARATOR); - if (separatorIndex == -1) { - throw new MalformedURLException("Jar URL does not contain !/ separator"); - } - return spec.substring(0, separatorIndex); - } - - public static JarFile getRootJarFileFromUrl(URL url) throws IOException { - String name = getRootJarFileName(url); - return getRootJarFile(name); - } - - private static JarFile getRootJarFile(String name) throws IOException { - try { - if (!name.startsWith(FILE_PROTOCOL)) { - throw new IllegalStateException("Not a file URL"); - } - File file = new File(URI.create(name)); - Map cache = fileCache.get(); - JarFile result = (cache != null) ? cache.get(name) : null; - if (result == null) { - result = new JarFile(file); - addToRootFileCache(name, result); - } - return result; - } catch (Exception ex) { - throw new IOException("Unable to open root Jar file '" + name + "'", ex); - } - } - - /** - * Add the given {@link JarFile} to the root file cache. - * - * @param fileName the source file to add - * @param jarFile the jar file. - */ - static void addToRootFileCache(String fileName, JarFile jarFile) { - Map cache = fileCache.get(); - if (cache == null) { - cache = new ConcurrentHashMap<>(8); - fileCache = new SoftReference<>(cache); - } - cache.put(fileName, jarFile); - } -} diff --git a/loader/src/main/java/com/megaease/easeagent/Main.java b/loader/src/main/java/com/megaease/easeagent/Main.java index 915bfd4ed..73f82d402 100644 --- a/loader/src/main/java/com/megaease/easeagent/Main.java +++ b/loader/src/main/java/com/megaease/easeagent/Main.java @@ -18,26 +18,17 @@ package com.megaease.easeagent; import com.google.common.base.Strings; -import com.google.common.collect.Lists; import lombok.SneakyThrows; -import org.springframework.boot.loader.LaunchedURLClassLoader; -import org.springframework.boot.loader.archive.Archive; -import org.springframework.boot.loader.archive.JarFileArchive; import java.io.File; -import java.io.IOException; import java.lang.instrument.Instrumentation; -import java.lang.ref.WeakReference; import java.lang.reflect.Field; import java.net.*; import java.security.CodeSource; import java.security.ProtectionDomain; import java.util.ArrayList; import java.util.Arrays; -import java.util.Objects; -import java.util.Set; import java.util.concurrent.Callable; -import java.util.concurrent.CopyOnWriteArraySet; import java.util.jar.Attributes; import java.util.jar.JarFile; @@ -52,30 +43,31 @@ public class Main { private static final String EASEAGENT_LOG_CONF_ENV_KEY = "EASEAGENT_LOG_CONF"; private static final String DEFAULT_AGENT_LOG_CONF = "easeagent-log4j2.xml"; private static ClassLoader loader; + private static JarCache JAR_CACHE; public static void premain(final String args, final Instrumentation inst) throws Exception { File jar = getArchiveFileContains(); - final JarFileArchive archive = new JarFileArchive(jar); + JAR_CACHE = JarCache.build(jar); // custom classloader - ArrayList urls = nestArchiveUrls(archive, LIB); - urls.addAll(nestArchiveUrls(archive, PLUGINS)); - urls.addAll(nestArchiveUrls(archive, SLf4J2)); + ArrayList urls = JAR_CACHE.nestJarUrls(LIB); + urls.addAll(JAR_CACHE.nestJarUrls(PLUGINS)); + urls.addAll(JAR_CACHE.nestJarUrls(SLf4J2)); File p = new File(jar.getParent() + File.separator + "plugins"); if (p.exists()) { urls.addAll(directoryPluginUrls(p)); } - loader = new CompoundableClassLoader(urls.toArray(new URL[0])); + loader = buildClassLoader(urls.toArray(new URL[0])); // install bootstrap jar - final ArrayList bootUrls = nestArchiveUrls(archive, BOOTSTRAP); + final ArrayList bootUrls = JAR_CACHE.nestJarFiles(BOOTSTRAP); bootUrls.forEach(url -> installBootstrapJar(url, inst)); - final Attributes attributes = archive.getManifest().getMainAttributes(); + final Attributes attributes = JAR_CACHE.getManifest().getMainAttributes(); final String loggingProperty = attributes.getValue(LOGGING_PROPERTY); final String bootstrap = attributes.getValue("Bootstrap-Class"); - initEaseAgentSlf4j2Dir(archive, loader); + initEaseAgentSlf4j2Dir(JAR_CACHE, loader); switchLoggingProperty(loader, loggingProperty, () -> { initAgentSlf4jMDC(loader); @@ -99,17 +91,12 @@ private static void initAgentSlf4jMDC(ClassLoader loader) { } } - private static void installBootstrapJar(URL url, Instrumentation inst) { - try { - JarFile file = JarUtils.getNestedJarFile(url); - inst.appendToBootstrapClassLoaderSearch(file); - } catch (IOException e) { - e.printStackTrace(); - } + private static void installBootstrapJar(JarFile file, Instrumentation inst) { + inst.appendToBootstrapClassLoaderSearch(file); } - private static void initEaseAgentSlf4j2Dir(JarFileArchive archive, final ClassLoader bootstrapLoader) throws Exception { - final URL[] slf4j2Urls = nestArchiveUrls(archive, SLf4J2).toArray(new URL[0]); + private static void initEaseAgentSlf4j2Dir(JarCache archive, final ClassLoader bootstrapLoader) throws Exception { + final URL[] slf4j2Urls = archive.nestJarUrls(SLf4J2).toArray(new URL[0]); final ClassLoader slf4j2Loader = new URLClassLoader(slf4j2Urls, null); Class classLoaderSupplier = bootstrapLoader.loadClass("com.megaease.easeagent.log4j2.FinalClassloaderSupplier"); Field field = classLoaderSupplier.getDeclaredField("CLASSLOADER"); @@ -161,25 +148,6 @@ private static String getLogConfigPath() { return logConfigPath; } - private static ArrayList nestArchiveUrls(JarFileArchive archive, String prefix) throws IOException { - ArrayList archives = Lists.newArrayList( - archive.getNestedArchives(entry -> !entry.isDirectory() && entry.getName().startsWith(prefix), - entry -> true - )); - - final ArrayList urls = new ArrayList<>(archives.size()); - - archives.forEach(item -> { - try { - urls.add(item.getUrl()); - } catch (MalformedURLException e) { - e.printStackTrace(); - } - }); - - return urls; - } - private static ArrayList directoryPluginUrls(File directory) { if (!directory.isDirectory()) { return new ArrayList<>(); @@ -223,63 +191,8 @@ private static File getArchiveFileContains() throws URISyntaxException { return root; } - public static class CompoundableClassLoader extends LaunchedURLClassLoader { - private final Set> externals = new CopyOnWriteArraySet<>(); - - CompoundableClassLoader(URL[] urls) { - super(urls, Main.BOOTSTRAP_CLASS_LOADER); - } - - @SuppressWarnings("unused") - public void add(ClassLoader cl) { - if (cl != null && !Objects.equals(cl, this)) { - externals.add(new WeakReference<>(cl)); - } - } - - @Override - protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { - try { - return super.loadClass(name, resolve); - } catch (ClassNotFoundException e) { - for (WeakReference external : externals) { - try { - ClassLoader cl = external.get(); - if (cl == null) { - continue; - } - final Class aClass = cl.loadClass(name); - if (resolve) { - resolveClass(aClass); - } - return aClass; - } catch (ClassNotFoundException ignored) { - // ignored - } - } - - throw e; - } - } - - @Override - public URL findResource(String name) { - URL url = super.findResource(name); - if (url == null) { - for (WeakReference external : externals) { - try { - ClassLoader cl = external.get(); - url = cl.getResource(name); - if (url != null) { - return url; - } - } catch (Exception ignored) { - // ignored - } - } - } - return url; - } + static ClassLoader buildClassLoader(URL[] urls) { + return new EaseAgentClassLoader(urls, BOOTSTRAP_CLASS_LOADER); } @SneakyThrows diff --git a/loader/src/test/java/com/megaease/easeagent/MainTest.java b/loader/src/test/java/com/megaease/easeagent/MainTest.java new file mode 100644 index 000000000..04ec521f9 --- /dev/null +++ b/loader/src/test/java/com/megaease/easeagent/MainTest.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent; + +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; + +import static org.junit.Assert.assertNotNull; + +public class MainTest { + + @Test + public void buildClassLoader() throws IOException, ClassNotFoundException { + File jar = new File(ClassLoader.getSystemResource("test-mock-load.jar").getPath()); + JarCache jarFileArchive = JarCache.build(jar); + ArrayList urls = jarFileArchive.nestJarUrls("mock/"); + ClassLoader loader = Main.buildClassLoader(urls.toArray(new URL[0])); + Class logbackPlugin = loader.loadClass("com.megaease.easeagent.mock.utils.MockSystemEnv"); + assertNotNull(logbackPlugin); + } +} diff --git a/loader/src/test/resources/test-mock-load.jar b/loader/src/test/resources/test-mock-load.jar new file mode 100644 index 000000000..ca487b411 Binary files /dev/null and b/loader/src/test/resources/test-mock-load.jar differ diff --git a/log4j2/log4j2-api/pom.xml b/log4j2/log4j2-api/pom.xml index 2d95ec14c..d2e5c4e0a 100644 --- a/log4j2/log4j2-api/pom.xml +++ b/log4j2/log4j2-api/pom.xml @@ -22,7 +22,7 @@ log4j2 com.megaease.easeagent - 2.2.9 + 2.3.0 4.0.0 diff --git a/log4j2/log4j2-impl/pom.xml b/log4j2/log4j2-impl/pom.xml index db7e9bdec..d21d39c21 100644 --- a/log4j2/log4j2-impl/pom.xml +++ b/log4j2/log4j2-impl/pom.xml @@ -22,7 +22,7 @@ log4j2 com.megaease.easeagent - 2.2.9 + 2.3.0 4.0.0 diff --git a/log4j2/pom.xml b/log4j2/pom.xml index 41d8168a0..ff6d81fc0 100644 --- a/log4j2/pom.xml +++ b/log4j2/pom.xml @@ -21,7 +21,7 @@ easeagent com.megaease.easeagent - 2.2.9 + 2.3.0 4.0.0 diff --git a/metrics/pom.xml b/metrics/pom.xml index 4409f31f1..061253602 100644 --- a/metrics/pom.xml +++ b/metrics/pom.xml @@ -21,7 +21,7 @@ easeagent com.megaease.easeagent - 2.2.9 + 2.3.0 4.0.0 diff --git a/mock/config-mock/pom.xml b/mock/config-mock/pom.xml index d773ca996..aa30338e6 100644 --- a/mock/config-mock/pom.xml +++ b/mock/config-mock/pom.xml @@ -22,7 +22,7 @@ mock com.megaease.easeagent - 2.2.9 + 2.3.0 4.0.0 diff --git a/mock/context-mock/pom.xml b/mock/context-mock/pom.xml index ed97b2996..7c408976d 100644 --- a/mock/context-mock/pom.xml +++ b/mock/context-mock/pom.xml @@ -22,7 +22,7 @@ mock com.megaease.easeagent - 2.2.9 + 2.3.0 4.0.0 diff --git a/mock/log4j2-mock/pom.xml b/mock/log4j2-mock/pom.xml index bf2280723..57709e4ee 100644 --- a/mock/log4j2-mock/pom.xml +++ b/mock/log4j2-mock/pom.xml @@ -22,7 +22,7 @@ mock com.megaease.easeagent - 2.2.9 + 2.3.0 4.0.0 diff --git a/mock/metrics-mock/pom.xml b/mock/metrics-mock/pom.xml index 8ed94519a..a19009e08 100644 --- a/mock/metrics-mock/pom.xml +++ b/mock/metrics-mock/pom.xml @@ -22,7 +22,7 @@ mock com.megaease.easeagent - 2.2.9 + 2.3.0 4.0.0 diff --git a/mock/plugin-api-mock/pom.xml b/mock/plugin-api-mock/pom.xml index 0b7d197a8..a03f2deff 100644 --- a/mock/plugin-api-mock/pom.xml +++ b/mock/plugin-api-mock/pom.xml @@ -22,7 +22,7 @@ mock com.megaease.easeagent - 2.2.9 + 2.3.0 4.0.0 diff --git a/mock/pom.xml b/mock/pom.xml index b282d19b1..8e483ce0c 100644 --- a/mock/pom.xml +++ b/mock/pom.xml @@ -22,7 +22,7 @@ easeagent com.megaease.easeagent - 2.2.9 + 2.3.0 4.0.0 diff --git a/mock/report-mock/pom.xml b/mock/report-mock/pom.xml index 96a632f9d..c3435f2c5 100644 --- a/mock/report-mock/pom.xml +++ b/mock/report-mock/pom.xml @@ -22,7 +22,7 @@ mock com.megaease.easeagent - 2.2.9 + 2.3.0 4.0.0 diff --git a/mock/utils-mock/pom.xml b/mock/utils-mock/pom.xml index a1382d8a4..d7b1d3a91 100644 --- a/mock/utils-mock/pom.xml +++ b/mock/utils-mock/pom.xml @@ -22,7 +22,7 @@ mock com.megaease.easeagent - 2.2.9 + 2.3.0 4.0.0 diff --git a/mock/zipkin-mock/pom.xml b/mock/zipkin-mock/pom.xml index d3d5a6835..c7c671897 100644 --- a/mock/zipkin-mock/pom.xml +++ b/mock/zipkin-mock/pom.xml @@ -22,7 +22,7 @@ mock com.megaease.easeagent - 2.2.9 + 2.3.0 4.0.0 diff --git a/plugin-api/pom.xml b/plugin-api/pom.xml index 8e0400b2c..7c14d9ef3 100644 --- a/plugin-api/pom.xml +++ b/plugin-api/pom.xml @@ -24,7 +24,7 @@ easeagent com.megaease.easeagent - 2.2.9 + 2.3.0 plugin-api diff --git a/plugin-api/src/main/java/com/megaease/easeagent/plugin/CodeVersion.java b/plugin-api/src/main/java/com/megaease/easeagent/plugin/CodeVersion.java new file mode 100644 index 000000000..0e4475e76 --- /dev/null +++ b/plugin-api/src/main/java/com/megaease/easeagent/plugin/CodeVersion.java @@ -0,0 +1,72 @@ +package com.megaease.easeagent.plugin; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * code of versions for control whether to load. + *

+ * If CodeVersion.builder().build() is returned, it means it will load forever + *

+ * The configuration format is as follows: runtime.code.version.points.{key}={version} + *

+ * If CodeVersion.builder().key("jdk").add("default").build(), it means it will load by default, + * but not load by specified like runtime.code.version.points.jdk=jdk10 + *

+ * When multiple versions are specified, it means that it can be loaded by multiple versions: + * CodeVersion.builder().key("jdk").add("jdk10").add("jdk11").build() + * The following two configurations are load: + * runtime.code.version.points.jdk=jdk10 + * runtime.code.version.points.jdk=jdk11 + */ +public class CodeVersion { + private final String key; + private final Set versions; + + public CodeVersion(String key, Set versions) { + this.key = key; + this.versions = versions; + } + + public boolean isEmpty() { + return key == null || key.trim().isEmpty() || versions == null || versions.isEmpty(); + } + + public String getKey() { + return key; + } + + public Set getVersions() { + return versions; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + String key = ""; + Set versions = new HashSet<>(); + + public Builder key(String key) { + this.key = key; + return this; + } + + public Builder add(String version) { + this.versions.add(version); + return this; + } + + public CodeVersion build() { + if (this.versions.isEmpty()) { + return new CodeVersion(this.key, Collections.emptySet()); + } else if (this.versions.size() == 1) { + return new CodeVersion(this.key, Collections.singleton(this.versions.iterator().next())); + } else { + return new CodeVersion(this.key, Collections.unmodifiableSet(this.versions)); + } + } + } +} diff --git a/plugin-api/src/main/java/com/megaease/easeagent/plugin/Points.java b/plugin-api/src/main/java/com/megaease/easeagent/plugin/Points.java index f0309331c..1d6427599 100644 --- a/plugin-api/src/main/java/com/megaease/easeagent/plugin/Points.java +++ b/plugin-api/src/main/java/com/megaease/easeagent/plugin/Points.java @@ -22,6 +22,7 @@ import com.megaease.easeagent.plugin.matcher.loader.ClassLoaderMatcher; import com.megaease.easeagent.plugin.matcher.loader.IClassLoaderMatcher; +import java.util.Collections; import java.util.Set; /** @@ -29,17 +30,38 @@ * and also can be defined through @OnClass and @OnMethod annotation */ public interface Points { + String DEFAULT_VERSION = "default"; + Set DEFAULT_VERSIONS = Collections.singleton(DEFAULT_VERSION); + + CodeVersion EMPTY_VERSION = CodeVersion.builder().build(); + + + /** + * eg. + * versions=CodeVersion.builder().key("jdk").add("default").add("jdk8").build() + * do not set or set the following value to load: runtime.code.version.points.jdk=jdk8 + *

+ * when set for not load: runtime.code.version.points.jdk=jdk17 + * but load from Points: versions=CodeVersion.builder().key("jdk").add("jdk17").build() + * + * @see CodeVersion + * @return CodeVersion code of versions for control whether to load, If EMPTY_VERSIONS is returned, it means it will load forever + */ + default CodeVersion codeVersions() { + return EMPTY_VERSION; + } + /** * return the defined class matcher matching a class or a group of classes * eg. * ClassMatcher.builder() - * .hadInterface(A) - * .isPublic() - * .isAbstract() - * .or() - * .hasSuperClass(B) - * .isPublic() - * .build() + * .hadInterface(A) + * .isPublic() + * .isAbstract() + * .or() + * .hasSuperClass(B) + * .isPublic() + * .build() */ IClassMatcher getClassMatcher(); @@ -47,30 +69,30 @@ public interface Points { * return the defined method matcher * eg. * MethodMatcher.builder().named("execute") - * .isPublic() - * .argNum(2) - * .arg(1, "java.lang.String") - * .build().toSet() + * .isPublic() + * .argNum(2) + * .arg(1, "java.lang.String") + * .build().toSet() * or * MethodMatcher.multiBuilder() - * .match(MethodMatcher.builder().named("") - * .argsLength(3) - * .arg(0, "org.apache.kafka.clients.consumer.ConsumerConfig") - * .qualifier("constructor") - * .build()) - * .match(MethodMatcher.builder().named("poll") - * .argsLength(1) - * .arg(0, "java.time.Duration") - * .qualifier("poll") - * .build()) - * .build(); + * .match(MethodMatcher.builder().named("") + * .argsLength(3) + * .arg(0, "org.apache.kafka.clients.consumer.ConsumerConfig") + * .qualifier("constructor") + * .build()) + * .match(MethodMatcher.builder().named("poll") + * .argsLength(1) + * .arg(0, "java.time.Duration") + * .qualifier("poll") + * .build()) + * .build(); */ Set getMethodMatcher(); /** * when return true, the transformer will add a Object field and a accessor * The dynamically added member can be accessed by AgentDynamicFieldAccessor: - * + *

* AgentDynamicFieldAccessor.setDynamicFieldValue(instance, value) * value = AgentDynamicFieldAccessor.getDynamicFieldValue(instance) */ @@ -78,6 +100,16 @@ default boolean isAddDynamicField() { return false; } + /** + * When a non-null string is returned, the converter will add an accessor to get the member variables inside the class. + * Get method: value = TypeFieldGetter.get(instance) + * @see com.megaease.easeagent.plugin.field.TypeFieldGetter#get(Object) + * @return String field name + */ + default String getTypeFieldAccessor() { + return null; + } + /** * Only match classes loaded by the ClassLoaderMatcher * default as all classloader diff --git a/plugin-api/src/main/java/com/megaease/easeagent/plugin/api/config/ConfigConst.java b/plugin-api/src/main/java/com/megaease/easeagent/plugin/api/config/ConfigConst.java index 6fd10a9d9..7bf689d6b 100644 --- a/plugin-api/src/main/java/com/megaease/easeagent/plugin/api/config/ConfigConst.java +++ b/plugin-api/src/main/java/com/megaease/easeagent/plugin/api/config/ConfigConst.java @@ -19,6 +19,7 @@ public interface ConfigConst { String AGENT_JAR_PATH = "easeagent.jar.path"; + String RUNTIME_CODE_VERSION_POINTS_PREFIX = "runtime.code.version.points."; String PLUGIN = "plugin"; String PLUGIN_GLOBAL = "global"; String DELIMITER = "."; @@ -27,6 +28,8 @@ public interface ConfigConst { String SERVICE_NAME = "name"; String SYSTEM_NAME = "system"; + String VERSION_NAME = "version"; + // domain String OBSERVABILITY = "observability"; String INTEGRABILITY = "integrability"; @@ -136,6 +139,8 @@ interface Namespace { String ASYNC = "async"; String ELASTICSEARCH = "elasticsearch"; String HTTP_SERVLET = "httpServlet"; + + String TOMCAT = "tomcat"; String JDBC = "jdbc"; String JDBC_CONNECTION = "jdbcConnection"; String JDBC_STATEMENT = "jdbcStatement"; @@ -169,7 +174,19 @@ interface PluginID { String TRACING = "tracing"; String METRIC = "metric"; String LOG = "log"; + String INIT = "init"; String REDIRECT = "redirect"; String FORWARDED = "forwarded"; } + + interface CodeVersion{ + String KEY_JDK = "jdk"; + String KEY_SPRING_BOOT = "spring-boot"; + + String VERSION_JDK8 = "jdk8"; + String VERSION_JDK17 = "jdk17"; + String VERSION_SPRING_BOOT2 = "2.x.x"; + String VERSION_SPRING_BOOT3 = "3.x.x"; + } + } diff --git a/plugin-api/src/main/java/com/megaease/easeagent/plugin/enums/Order.java b/plugin-api/src/main/java/com/megaease/easeagent/plugin/enums/Order.java index b50015105..20ecd489f 100644 --- a/plugin-api/src/main/java/com/megaease/easeagent/plugin/enums/Order.java +++ b/plugin-api/src/main/java/com/megaease/easeagent/plugin/enums/Order.java @@ -27,6 +27,7 @@ public enum Order { FOUNDATION(0, "foundation"), HIGHEST(10, "highest"), + INIT(18, ConfigConst.PluginID.INIT), REDIRECT(19, ConfigConst.PluginID.REDIRECT), HIGH(20, "high"), FORWARDED(30, ConfigConst.PluginID.FORWARDED), diff --git a/plugin-api/src/main/java/com/megaease/easeagent/plugin/field/TypeFieldGetter.java b/plugin-api/src/main/java/com/megaease/easeagent/plugin/field/TypeFieldGetter.java new file mode 100644 index 000000000..00d1710d6 --- /dev/null +++ b/plugin-api/src/main/java/com/megaease/easeagent/plugin/field/TypeFieldGetter.java @@ -0,0 +1,12 @@ +package com.megaease.easeagent.plugin.field; + +public interface TypeFieldGetter { + T getEaseAgent$$TypeField$$Data(); + + static T get(Object o) { + if (o instanceof TypeFieldGetter) { + return ((TypeFieldGetter) o).getEaseAgent$$TypeField$$Data(); + } + return null; + } +} diff --git a/plugin-api/src/main/java/com/megaease/easeagent/plugin/interceptor/AgentInterceptorChain.java b/plugin-api/src/main/java/com/megaease/easeagent/plugin/interceptor/AgentInterceptorChain.java index 49962db1c..db4465309 100644 --- a/plugin-api/src/main/java/com/megaease/easeagent/plugin/interceptor/AgentInterceptorChain.java +++ b/plugin-api/src/main/java/com/megaease/easeagent/plugin/interceptor/AgentInterceptorChain.java @@ -48,7 +48,7 @@ public void doBefore(MethodInfo methodInfo, int pos, InitializeContext context) interceptor.before(methodInfo, context); } catch (Throwable e) { // set error message to context; - log.debug("Interceptor before execute exception:" + e.getMessage()); + log.debug("Interceptor before execute exception:", e); } this.doBefore(methodInfo, pos + 1, context); } @@ -62,7 +62,7 @@ public Object doAfter(MethodInfo methodInfo, int pos, InitializeContext context) interceptor.after(methodInfo, context); } catch (Throwable e) { // set error message to context; - log.debug("Interceptor exit execute exception:" + e.getMessage()); + log.debug("Interceptor exit execute exception:", e); } return this.doAfter(methodInfo, pos - 1, context); } diff --git a/plugin-api/src/main/java/com/megaease/easeagent/plugin/interceptor/NonReentrantInterceptor.java b/plugin-api/src/main/java/com/megaease/easeagent/plugin/interceptor/NonReentrantInterceptor.java index 332cd556c..d4531f1b9 100644 --- a/plugin-api/src/main/java/com/megaease/easeagent/plugin/interceptor/NonReentrantInterceptor.java +++ b/plugin-api/src/main/java/com/megaease/easeagent/plugin/interceptor/NonReentrantInterceptor.java @@ -34,10 +34,16 @@ default void before(MethodInfo methodInfo, Context context) { @Override default void after(MethodInfo methodInfo, Context context) { - if (!context.exit(getEnterKey(methodInfo, context), 1)) { + Object key = getEnterKey(methodInfo, context); + if (!context.exit(key, 1)) { return; } - doAfter(methodInfo, context); + try { + context.enter(key); + doAfter(methodInfo, context); + } finally { + context.exit(key); + } } default Object getEnterKey(MethodInfo methodInfo, Context context) { diff --git a/plugin-api/src/main/java/com/megaease/easeagent/plugin/utils/ClassUtils.java b/plugin-api/src/main/java/com/megaease/easeagent/plugin/utils/ClassUtils.java index ef168a578..2a906bd67 100644 --- a/plugin-api/src/main/java/com/megaease/easeagent/plugin/utils/ClassUtils.java +++ b/plugin-api/src/main/java/com/megaease/easeagent/plugin/utils/ClassUtils.java @@ -17,6 +17,8 @@ package com.megaease.easeagent.plugin.utils; +import java.lang.reflect.Field; + public class ClassUtils { public static boolean hasClass(String className) { try { @@ -27,6 +29,25 @@ public static boolean hasClass(String className) { } } + public static boolean isInstance(String className, Object obj) { + try { + Class c = Thread.currentThread().getContextClassLoader().loadClass(className); + return c.isInstance(obj); + } catch (ClassNotFoundException e) { + return false; + } + } + + public static Object getStaticField(String className, String fieldName) { + try { + Class c = Thread.currentThread().getContextClassLoader().loadClass(className); + Field field = c.getDeclaredField(fieldName); + return field.get(null); + } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) { + return null; + } + } + public static abstract class TypeChecker { protected final String className; private final boolean hasClass; diff --git a/plugins/async/pom.xml b/plugins/async/pom.xml index 7378aa489..3c2b7dbee 100644 --- a/plugins/async/pom.xml +++ b/plugins/async/pom.xml @@ -23,7 +23,7 @@ com.megaease.easeagent plugins - 2.2.9 + 2.3.0 + + + + plugins + com.megaease.easeagent + 2.3.0 + + 4.0.0 + + httpurlconnection-jdk17 + + + + + com.megaease.easeagent + plugin-api + provided + + + com.megaease.easeagent + plugin-api-mock + ${project.version} + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 17 + 17 + + + + + diff --git a/plugins/httpurlconnection-jdk17/src/main/java/com/megaease/easeagent/plugin/httpurlconnection/jdk17/ForwardedPlugin.java b/plugins/httpurlconnection-jdk17/src/main/java/com/megaease/easeagent/plugin/httpurlconnection/jdk17/ForwardedPlugin.java new file mode 100644 index 000000000..814671a12 --- /dev/null +++ b/plugins/httpurlconnection-jdk17/src/main/java/com/megaease/easeagent/plugin/httpurlconnection/jdk17/ForwardedPlugin.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2023, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.httpurlconnection.jdk17; + +import com.megaease.easeagent.plugin.AgentPlugin; +import com.megaease.easeagent.plugin.api.config.ConfigConst; + +public class ForwardedPlugin implements AgentPlugin { + @Override + public String getNamespace() { + return ConfigConst.Namespace.FORWARDED; + } + + @Override + public String getDomain() { + return ConfigConst.INTEGRABILITY; + } +} diff --git a/plugins/httpurlconnection-jdk17/src/main/java/com/megaease/easeagent/plugin/httpurlconnection/jdk17/HttpURLConnectionPlugin.java b/plugins/httpurlconnection-jdk17/src/main/java/com/megaease/easeagent/plugin/httpurlconnection/jdk17/HttpURLConnectionPlugin.java new file mode 100644 index 000000000..80d734e8b --- /dev/null +++ b/plugins/httpurlconnection-jdk17/src/main/java/com/megaease/easeagent/plugin/httpurlconnection/jdk17/HttpURLConnectionPlugin.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2023, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.httpurlconnection.jdk17; + +import com.megaease.easeagent.plugin.AgentPlugin; +import com.megaease.easeagent.plugin.api.config.ConfigConst; + +public class HttpURLConnectionPlugin implements AgentPlugin { + @Override + public String getNamespace() { + return ConfigConst.Namespace.HTTPURLCONNECTION; + } + + @Override + public String getDomain() { + return ConfigConst.OBSERVABILITY; + } +} diff --git a/plugins/httpurlconnection-jdk17/src/main/java/com/megaease/easeagent/plugin/httpurlconnection/jdk17/advice/HttpURLConnectionAdvice.java b/plugins/httpurlconnection-jdk17/src/main/java/com/megaease/easeagent/plugin/httpurlconnection/jdk17/advice/HttpURLConnectionAdvice.java new file mode 100644 index 000000000..48fe9ddd1 --- /dev/null +++ b/plugins/httpurlconnection-jdk17/src/main/java/com/megaease/easeagent/plugin/httpurlconnection/jdk17/advice/HttpURLConnectionAdvice.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2023, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.httpurlconnection.jdk17.advice; + +import com.megaease.easeagent.plugin.CodeVersion; +import com.megaease.easeagent.plugin.Points; +import com.megaease.easeagent.plugin.api.config.ConfigConst; +import com.megaease.easeagent.plugin.matcher.ClassMatcher; +import com.megaease.easeagent.plugin.matcher.IClassMatcher; +import com.megaease.easeagent.plugin.matcher.IMethodMatcher; +import com.megaease.easeagent.plugin.matcher.MethodMatcher; + +import java.util.Set; + +public class HttpURLConnectionAdvice implements Points { + private final static CodeVersion VERSIONS = CodeVersion.builder() + .key(ConfigConst.CodeVersion.KEY_JDK) + .add(Points.DEFAULT_VERSION) + .add(ConfigConst.CodeVersion.VERSION_JDK17).build(); + + private final String typeFieldAccessor = "connected"; + + @Override + public CodeVersion codeVersions() { + return VERSIONS; + } + + @Override + public IClassMatcher getClassMatcher() { + return ClassMatcher.builder() + .hasSuperClass("java.net.HttpURLConnection") + .or().hasInterface("java.net.URLConnection") + .build(); + } + + @Override + public Set getMethodMatcher() { + return MethodMatcher.multiBuilder() + .match(MethodMatcher.builder().isPublic() + .named("getResponseCode") + .or().named("connect") + .build()) + .build(); + } + + @Override + public boolean isAddDynamicField() { + return true; + } + + @Override + public String getTypeFieldAccessor() { + return typeFieldAccessor; + } +} diff --git a/plugins/httpurlconnection-jdk17/src/main/java/com/megaease/easeagent/plugin/httpurlconnection/jdk17/interceptor/DynamicFieldUtils.java b/plugins/httpurlconnection-jdk17/src/main/java/com/megaease/easeagent/plugin/httpurlconnection/jdk17/interceptor/DynamicFieldUtils.java new file mode 100644 index 000000000..40b55feeb --- /dev/null +++ b/plugins/httpurlconnection-jdk17/src/main/java/com/megaease/easeagent/plugin/httpurlconnection/jdk17/interceptor/DynamicFieldUtils.java @@ -0,0 +1,37 @@ +package com.megaease.easeagent.plugin.httpurlconnection.jdk17.interceptor; + +import com.megaease.easeagent.plugin.api.logging.Logger; +import com.megaease.easeagent.plugin.bridge.EaseAgent; +import com.megaease.easeagent.plugin.field.AgentDynamicFieldAccessor; +import com.megaease.easeagent.plugin.field.DynamicFieldAccessor; + +import java.util.HashSet; +import java.util.Set; + +public class DynamicFieldUtils { + private static final Logger logger = EaseAgent.getLogger(DynamicFieldUtils.class); + + public static Set getOrCreateSet(Object obj) { + if (!(obj instanceof DynamicFieldAccessor)) { + logger.warn("java.net.HttpURLConnection must implements " + DynamicFieldAccessor.class.getName()); + return null; + } + Object fieldValue = AgentDynamicFieldAccessor.getDynamicFieldValue(obj); + Set set; + if (fieldValue instanceof Set) { + set = AgentDynamicFieldAccessor.getDynamicFieldValue(obj); + } else { + set = new HashSet<>(); + AgentDynamicFieldAccessor.setDynamicFieldValue(obj, set); + } + return set; + } + + public static boolean enterKey(Object obj, String key) { + Set set = getOrCreateSet(obj); + if (set == null) { + return false; + } + return set.add(key); + } +} diff --git a/plugins/httpurlconnection-jdk17/src/main/java/com/megaease/easeagent/plugin/httpurlconnection/jdk17/interceptor/HttpURLConnectionForwardedInterceptor.java b/plugins/httpurlconnection-jdk17/src/main/java/com/megaease/easeagent/plugin/httpurlconnection/jdk17/interceptor/HttpURLConnectionForwardedInterceptor.java new file mode 100644 index 000000000..569ca634f --- /dev/null +++ b/plugins/httpurlconnection-jdk17/src/main/java/com/megaease/easeagent/plugin/httpurlconnection/jdk17/interceptor/HttpURLConnectionForwardedInterceptor.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2023, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.httpurlconnection.jdk17.interceptor; + +import com.megaease.easeagent.plugin.annotation.AdviceTo; +import com.megaease.easeagent.plugin.api.Context; +import com.megaease.easeagent.plugin.api.config.ConfigConst; +import com.megaease.easeagent.plugin.api.logging.Logger; +import com.megaease.easeagent.plugin.bridge.EaseAgent; +import com.megaease.easeagent.plugin.enums.Order; +import com.megaease.easeagent.plugin.httpurlconnection.jdk17.ForwardedPlugin; +import com.megaease.easeagent.plugin.httpurlconnection.jdk17.advice.HttpURLConnectionAdvice; +import com.megaease.easeagent.plugin.interceptor.Interceptor; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; + +import java.net.HttpURLConnection; + +@AdviceTo(value = HttpURLConnectionAdvice.class, qualifier = "default", plugin = ForwardedPlugin.class) +public class HttpURLConnectionForwardedInterceptor implements Interceptor { + private static final Logger log = EaseAgent.getLogger(HttpURLConnectionForwardedInterceptor.class); + + @Override + public void before(MethodInfo methodInfo, Context context) { + Object invoker = methodInfo.getInvoker(); + if (!DynamicFieldUtils.enterKey(invoker, "HttpURLConnectionGetResponseCodeForwardedInterceptor.before")) { + return; + } + if (HttpURLConnectionUtils.isConnected(invoker)) { + if (log.isDebugEnabled()) { + log.debug("the HttpURLConnection Already connected, skip HttpURLConnectionGetResponseCodeForwardedInterceptor"); + } + return; + } + HttpURLConnection httpURLConnection = (HttpURLConnection) methodInfo.getInvoker(); + context.injectForwardedHeaders(httpURLConnection::setRequestProperty); + } + + @Override + public String getType() { + return ConfigConst.PluginID.FORWARDED; + } + + @Override + public int order() { + return Order.FORWARDED.getOrder(); + } +} diff --git a/plugins/httpurlconnection-jdk17/src/main/java/com/megaease/easeagent/plugin/httpurlconnection/jdk17/interceptor/HttpURLConnectionInterceptor.java b/plugins/httpurlconnection-jdk17/src/main/java/com/megaease/easeagent/plugin/httpurlconnection/jdk17/interceptor/HttpURLConnectionInterceptor.java new file mode 100644 index 000000000..e4f4c6c89 --- /dev/null +++ b/plugins/httpurlconnection-jdk17/src/main/java/com/megaease/easeagent/plugin/httpurlconnection/jdk17/interceptor/HttpURLConnectionInterceptor.java @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2023, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.httpurlconnection.jdk17.interceptor; + +import com.megaease.easeagent.plugin.annotation.AdviceTo; +import com.megaease.easeagent.plugin.api.Context; +import com.megaease.easeagent.plugin.api.logging.Logger; +import com.megaease.easeagent.plugin.api.trace.Span; +import com.megaease.easeagent.plugin.bridge.EaseAgent; +import com.megaease.easeagent.plugin.httpurlconnection.jdk17.HttpURLConnectionPlugin; +import com.megaease.easeagent.plugin.httpurlconnection.jdk17.advice.HttpURLConnectionAdvice; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import com.megaease.easeagent.plugin.tools.trace.BaseHttpClientTracingInterceptor; +import com.megaease.easeagent.plugin.tools.trace.HttpRequest; +import com.megaease.easeagent.plugin.tools.trace.HttpResponse; +import lombok.SneakyThrows; + +import java.net.HttpURLConnection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +@AdviceTo(value = HttpURLConnectionAdvice.class, qualifier = "default", plugin = HttpURLConnectionPlugin.class) +public class HttpURLConnectionInterceptor extends BaseHttpClientTracingInterceptor { + private static final Logger log = EaseAgent.getLogger(HttpURLConnectionInterceptor.class); + + @Override + public Object getProgressKey() { + return HttpURLConnectionInterceptor.class; + } + + @Override + public void doBefore(MethodInfo methodInfo, Context context) { + Object invoker = methodInfo.getInvoker(); + if (!DynamicFieldUtils.enterKey(invoker, "HttpURLConnectionGetResponseCodeInterceptor.before")) { + return; + } + if (HttpURLConnectionUtils.isConnected(invoker)) { + if (log.isDebugEnabled()) { + log.debug("the HttpURLConnection Already connected, skip HttpURLConnectionGetResponseCodeInterceptor"); + } + DynamicFieldUtils.enterKey(methodInfo.getInvoker(), "HttpURLConnectionGetResponseCodeInterceptor.connected"); + return; + } + super.doBefore(methodInfo, context); + } + + @Override + public void doAfter(MethodInfo methodInfo, Context context) { + if (DynamicFieldUtils.enterKey(methodInfo.getInvoker(), "HttpURLConnectionGetResponseCodeInterceptor.connected") + && DynamicFieldUtils.enterKey(methodInfo.getInvoker(), "HttpURLConnectionGetResponseCodeInterceptor.after")) { + super.doAfter(methodInfo, context); + } + } + + @Override + protected HttpRequest getRequest(MethodInfo methodInfo, Context context) { + return new InternalRequest((HttpURLConnection) methodInfo.getInvoker()); + } + + + @Override + protected HttpResponse getResponse(MethodInfo methodInfo, Context context) { + return new InternalResponse(methodInfo.getThrowable(), (HttpURLConnection) methodInfo.getInvoker()); + } + + + final static class InternalRequest implements HttpRequest { + + private final HttpURLConnection httpURLConn; + + public InternalRequest(HttpURLConnection httpURLConn) { + this.httpURLConn = httpURLConn; + } + + + @Override + public String method() { + return httpURLConn.getRequestMethod(); + } + + @Override + public String path() { + return httpURLConn.getURL().toString(); + } + + @Override + public String route() { + return null; + } + + @Override + public String getRemoteAddr() { + return null; + } + + @Override + public int getRemotePort() { + return 0; + } + + @Override + public Span.Kind kind() { + return Span.Kind.CLIENT; + } + + @Override + public String header(String name) { + return httpURLConn.getRequestProperty(name); + } + + @Override + public boolean cacheScope() { + return false; + } + + @Override + public void setHeader(String name, String value) { + httpURLConn.setRequestProperty(name, value); + } + + } + + final static class InternalResponse implements HttpResponse { + private final Throwable caught; + private final HttpURLConnection httpURLConn; + private final Map> headers; + + public InternalResponse(Throwable caught, HttpURLConnection httpURLConn) { + this.caught = caught; + this.httpURLConn = httpURLConn; + this.headers = getResponseHeaders(httpURLConn); + } + + @Override + public String method() { + return httpURLConn.getRequestMethod(); + } + + @Override + public String route() { + return null; + } + + @SneakyThrows + @Override + public int statusCode() { + return this.httpURLConn.getResponseCode(); + } + + @Override + public Throwable maybeError() { + return caught; + } + + + @Override + public String header(String name) { + List h = this.headers.get(name); + if (h == null) { + return null; + } + return h.stream().findFirst().orElse(null); + } + + private Map> getResponseHeaders(HttpURLConnection uc) { + Map> headers = new HashMap<>(); + for (Map.Entry> e : uc.getHeaderFields().entrySet()) { + if (e.getKey() != null) { + headers.put(e.getKey(), Collections.unmodifiableList(e.getValue())); + } + } + return headers; + } + } +} diff --git a/plugins/httpurlconnection-jdk17/src/main/java/com/megaease/easeagent/plugin/httpurlconnection/jdk17/interceptor/HttpURLConnectionUtils.java b/plugins/httpurlconnection-jdk17/src/main/java/com/megaease/easeagent/plugin/httpurlconnection/jdk17/interceptor/HttpURLConnectionUtils.java new file mode 100644 index 000000000..fcdb166c0 --- /dev/null +++ b/plugins/httpurlconnection-jdk17/src/main/java/com/megaease/easeagent/plugin/httpurlconnection/jdk17/interceptor/HttpURLConnectionUtils.java @@ -0,0 +1,10 @@ +package com.megaease.easeagent.plugin.httpurlconnection.jdk17.interceptor; + +import com.megaease.easeagent.plugin.field.TypeFieldGetter; + +public class HttpURLConnectionUtils { + public static boolean isConnected(Object obj) { + Boolean connected = TypeFieldGetter.get(obj); + return Boolean.TRUE.equals(connected); + } +} diff --git a/plugins/httpurlconnection-jdk17/src/test/java/com/megaease/easeagent/plugin/httpurlconnection/jdk17/interceptor/HttpURLConnectionForwardedInterceptorTest.java b/plugins/httpurlconnection-jdk17/src/test/java/com/megaease/easeagent/plugin/httpurlconnection/jdk17/interceptor/HttpURLConnectionForwardedInterceptorTest.java new file mode 100644 index 000000000..34fcb6499 --- /dev/null +++ b/plugins/httpurlconnection-jdk17/src/test/java/com/megaease/easeagent/plugin/httpurlconnection/jdk17/interceptor/HttpURLConnectionForwardedInterceptorTest.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2023, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.httpurlconnection.jdk17.interceptor; + +import com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner; +import com.megaease.easeagent.plugin.api.Context; +import com.megaease.easeagent.plugin.api.config.ConfigConst; +import com.megaease.easeagent.plugin.bridge.EaseAgent; +import com.megaease.easeagent.plugin.field.AgentDynamicFieldAccessor; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.HttpURLConnection; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +@RunWith(EaseAgentJunit4ClassRunner.class) +public class HttpURLConnectionForwardedInterceptorTest { + + @Test + public void before() { + Context context = EaseAgent.getContext(); + HttpURLConnection conn = TestUtils.mockHttpURLConnection(); + MethodInfo methodInfo = TestUtils.mockMethodInfo(conn); + HttpURLConnectionForwardedInterceptor httpClientDoExecuteForwardedInterceptor = new HttpURLConnectionForwardedInterceptor(); + + httpClientDoExecuteForwardedInterceptor.before(methodInfo, context); + assertNull(conn.getRequestProperty(TestUtils.FORWARDED_NAME)); + context.put(TestUtils.FORWARDED_NAME, TestUtils.FORWARDED_VALUE); + AgentDynamicFieldAccessor.setDynamicFieldValue(conn, null); + httpClientDoExecuteForwardedInterceptor.before(methodInfo, context); + assertEquals(TestUtils.FORWARDED_VALUE, conn.getRequestProperty(TestUtils.FORWARDED_NAME)); + context.remove(TestUtils.FORWARDED_NAME); + } + + @Test + public void getType() { + HttpURLConnectionForwardedInterceptor httpClientDoExecuteForwardedInterceptor = new HttpURLConnectionForwardedInterceptor(); + assertEquals(ConfigConst.PluginID.FORWARDED, httpClientDoExecuteForwardedInterceptor.getType()); + } +} diff --git a/plugins/httpurlconnection-jdk17/src/test/java/com/megaease/easeagent/plugin/httpurlconnection/jdk17/interceptor/HttpURLConnectionInterceptorTest.java b/plugins/httpurlconnection-jdk17/src/test/java/com/megaease/easeagent/plugin/httpurlconnection/jdk17/interceptor/HttpURLConnectionInterceptorTest.java new file mode 100644 index 000000000..33ba1a187 --- /dev/null +++ b/plugins/httpurlconnection-jdk17/src/test/java/com/megaease/easeagent/plugin/httpurlconnection/jdk17/interceptor/HttpURLConnectionInterceptorTest.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2023, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.httpurlconnection.jdk17.interceptor; + +import com.megaease.easeagent.mock.plugin.api.MockEaseAgent; +import com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner; +import com.megaease.easeagent.plugin.api.Context; +import com.megaease.easeagent.plugin.api.trace.Scope; +import com.megaease.easeagent.plugin.api.trace.Span; +import com.megaease.easeagent.plugin.bridge.EaseAgent; +import com.megaease.easeagent.plugin.field.AgentDynamicFieldAccessor; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import com.megaease.easeagent.plugin.report.tracing.ReportSpan; +import com.megaease.easeagent.plugin.tools.trace.HttpRequest; +import com.megaease.easeagent.plugin.tools.trace.HttpResponse; +import lombok.SneakyThrows; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +@RunWith(EaseAgentJunit4ClassRunner.class) +public class HttpURLConnectionInterceptorTest { + + @SneakyThrows + @Test + public void before() { + Context context = EaseAgent.getContext(); + MethodInfo methodInfo = TestUtils.mockMethodInfo(); + + HttpURLConnectionInterceptor httpURLConnectionWriteRequestsInterceptor = new HttpURLConnectionInterceptor(); + MockEaseAgent.cleanLastSpan(); + httpURLConnectionWriteRequestsInterceptor.before(methodInfo, context); + httpURLConnectionWriteRequestsInterceptor.after(methodInfo, context); + ReportSpan mockSpan = MockEaseAgent.getLastSpan(); + assertNotNull(mockSpan); + assertEquals(Span.Kind.CLIENT.name(), mockSpan.kind()); + assertEquals(TestUtils.RESPONSE_TAG_VALUE, mockSpan.tag(TestUtils.RESPONSE_TAG_NAME)); + assertNull(mockSpan.parentId()); + + Span span = context.nextSpan(); + try (Scope ignored = span.maybeScope()) { + AgentDynamicFieldAccessor.setDynamicFieldValue(methodInfo.getInvoker(), null); + httpURLConnectionWriteRequestsInterceptor.doBefore(methodInfo, context); + httpURLConnectionWriteRequestsInterceptor.doAfter(methodInfo, context); + mockSpan = MockEaseAgent.getLastSpan(); + assertEquals(span.traceIdString(), mockSpan.traceId()); + assertEquals(span.spanIdString(), mockSpan.parentId()); + assertNotNull(mockSpan.id()); + } + span.abandon(); + } + + @Test + public void after() { + before(); + } + + + @SneakyThrows + @Test + public void getRequest() { + Context context = EaseAgent.getContext(); + MethodInfo methodInfo = TestUtils.mockMethodInfo(); + + HttpURLConnectionInterceptor httpClientDoExecuteInterceptor = new HttpURLConnectionInterceptor(); + HttpRequest request = httpClientDoExecuteInterceptor.getRequest(methodInfo, context); + assertEquals(Span.Kind.CLIENT, request.kind()); + assertEquals("GET", request.method()); + } + + @SneakyThrows + @Test + public void getResponse() { + Context context = EaseAgent.getContext(); + MethodInfo methodInfo = TestUtils.mockMethodInfo(); + + HttpURLConnectionInterceptor httpClientDoExecuteInterceptor = new HttpURLConnectionInterceptor(); + + HttpResponse httpResponse = httpClientDoExecuteInterceptor.getResponse(methodInfo, context); + assertEquals(200, httpResponse.statusCode()); + } +} diff --git a/plugins/httpurlconnection-jdk17/src/test/java/com/megaease/easeagent/plugin/httpurlconnection/jdk17/interceptor/TestUtils.java b/plugins/httpurlconnection-jdk17/src/test/java/com/megaease/easeagent/plugin/httpurlconnection/jdk17/interceptor/TestUtils.java new file mode 100644 index 000000000..afdc9315b --- /dev/null +++ b/plugins/httpurlconnection-jdk17/src/test/java/com/megaease/easeagent/plugin/httpurlconnection/jdk17/interceptor/TestUtils.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2023, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.httpurlconnection.jdk17.interceptor; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import com.google.common.net.HttpHeaders; +import com.megaease.easeagent.plugin.field.DynamicFieldAccessor; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import lombok.SneakyThrows; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +final class TestUtils { + public static final String FORWARDED_NAME = "X-Forwarded-For"; + public static final String FORWARDED_VALUE = "testForwarded"; + public static final String RESPONSE_TAG_NAME = "X-EG-Test"; + public static final String RESPONSE_TAG_VALUE = "X-EG-Test-Value"; + + @SneakyThrows + static MethodInfo mockMethodInfo() { + HttpURLConnection httpURLConnection = mockHttpURLConnection(); + return mockMethodInfo(httpURLConnection); + } + + static MethodInfo mockMethodInfo(HttpURLConnection httpURLConnection) { + MethodInfo methodInfo = MethodInfo.builder() + .invoker(httpURLConnection).retValue(httpURLConnection) + .build(); + return methodInfo; + } + + @SneakyThrows + static HttpURLConnection mockHttpURLConnection() { + URL url = new URL("http://127.0.0.1:8080"); + Map responseHeader = ImmutableMap.of(TestUtils.RESPONSE_TAG_NAME, TestUtils.RESPONSE_TAG_VALUE); + return getConnection(url, "GET", null, responseHeader); + } + + static HttpURLConnection getConnection( + URL url, String method, Map requestHeaders, Map responseHeader) throws IOException { + + HttpURLConnection conn = new HttpURLConnectionTest(url, responseHeader); + conn.setRequestMethod(method); + conn.setDoInput(true); + conn.setDoOutput(true); + conn.setRequestProperty(HttpHeaders.HOST, url.getHost()); + if (requestHeaders != null) { + for (String key : requestHeaders.keySet()) { + conn.setRequestProperty(key, requestHeaders.get(key)); + } + } + + return conn; + } + + public static class HttpURLConnectionTest extends HttpURLConnection implements DynamicFieldAccessor { + Map responseHeader; + Object easeAgent$$DynamicField$$Data; + + public HttpURLConnectionTest(URL u, Map responseHeader) { + super(u); + this.responseHeader = responseHeader; + } + + @Override + public void setEaseAgent$$DynamicField$$Data(Object data) { + this.easeAgent$$DynamicField$$Data = data; + } + + @Override + public Object getEaseAgent$$DynamicField$$Data() { + return easeAgent$$DynamicField$$Data; + } + + + @Override + public void connect() throws IOException { + + } + + @Override + public void disconnect() { + + } + + @Override + public boolean usingProxy() { + return false; + } + + @Override + public int getResponseCode() throws IOException { + return 200; + } + + @Override + public Map> getHeaderFields() { + Map> fields = new HashMap<>(); + for (String key : responseHeader.keySet()) { + fields.put(key, Lists.newArrayList(responseHeader.get(key))); + } + return fields; + } + } +} diff --git a/plugins/httpurlconnection-jdk17/src/test/resources/mock_agent.properties b/plugins/httpurlconnection-jdk17/src/test/resources/mock_agent.properties new file mode 100644 index 000000000..8fb0c2983 --- /dev/null +++ b/plugins/httpurlconnection-jdk17/src/test/resources/mock_agent.properties @@ -0,0 +1,19 @@ +# +# Copyright (c) 2023, MegaEase +# All rights reserved. +# +# 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. +# + +easeagent.progress.forwarded.headers=X-Forwarded-For +observability.tracings.tag.response.headers.eg.0=X-EG-Test diff --git a/plugins/httpurlconnection/README.md b/plugins/httpurlconnection/README.md new file mode 100644 index 000000000..4217dc3cb --- /dev/null +++ b/plugins/httpurlconnection/README.md @@ -0,0 +1,27 @@ +# jdk http url connection + +## Points + +`java.net.HttpURLConnection:getResponseCode` + +### points code version jdk:jdk8 config and default + +```properties +runtime.code.version.points.jdk=jdk8 +``` +when not config `runtime.code.version.points.jdk` it is load + +## config + +### tracing config +```properties +plugin.observability.httpURLConnection.tracing.enabled=true +``` + +### support forwarded +```properties +plugin.integrability.forwarded.forwarded.enabled=true +``` + + + diff --git a/plugins/httpurlconnection/pom.xml b/plugins/httpurlconnection/pom.xml index fc26fed9f..5611d87ee 100644 --- a/plugins/httpurlconnection/pom.xml +++ b/plugins/httpurlconnection/pom.xml @@ -22,7 +22,7 @@ plugins com.megaease.easeagent - 2.2.9 + 2.3.0 4.0.0 diff --git a/plugins/httpurlconnection/src/main/java/com/megaease/easeagent/plugin/httpurlconnection/advice/HttpURLConnectionGetResponseCodeAdvice.java b/plugins/httpurlconnection/src/main/java/com/megaease/easeagent/plugin/httpurlconnection/advice/HttpURLConnectionGetResponseCodeAdvice.java index 1a1156117..7e71047a7 100644 --- a/plugins/httpurlconnection/src/main/java/com/megaease/easeagent/plugin/httpurlconnection/advice/HttpURLConnectionGetResponseCodeAdvice.java +++ b/plugins/httpurlconnection/src/main/java/com/megaease/easeagent/plugin/httpurlconnection/advice/HttpURLConnectionGetResponseCodeAdvice.java @@ -17,7 +17,9 @@ package com.megaease.easeagent.plugin.httpurlconnection.advice; +import com.megaease.easeagent.plugin.CodeVersion; import com.megaease.easeagent.plugin.Points; +import com.megaease.easeagent.plugin.api.config.ConfigConst; import com.megaease.easeagent.plugin.matcher.ClassMatcher; import com.megaease.easeagent.plugin.matcher.IClassMatcher; import com.megaease.easeagent.plugin.matcher.IMethodMatcher; @@ -26,6 +28,15 @@ import java.util.Set; public class HttpURLConnectionGetResponseCodeAdvice implements Points { + private final static CodeVersion VERSIONS = CodeVersion.builder() + .key(ConfigConst.CodeVersion.KEY_JDK) + .add(Points.DEFAULT_VERSION) + .add(ConfigConst.CodeVersion.VERSION_JDK8).build(); + + @Override + public CodeVersion codeVersions() { + return VERSIONS; + } @Override public IClassMatcher getClassMatcher() { diff --git a/plugins/jdbc/pom.xml b/plugins/jdbc/pom.xml index 32bf01f4a..ea97645db 100644 --- a/plugins/jdbc/pom.xml +++ b/plugins/jdbc/pom.xml @@ -23,7 +23,7 @@ com.megaease.easeagent plugins - 2.2.9 + 2.3.0 + + + + plugins + com.megaease.easeagent + 2.3.0 + + 4.0.0 + + spring-boot-3.5.3 + + pom + + + + + spring-boot-rest-template-3.5.3 + spring-boot-gateway-3.5.3 + spring-boot-servicename-3.5.3 + + + + 17 + 2025.0.0 + 3.5.3 + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${java.version.17} + ${java.version.17} + + + + + + diff --git a/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/pom.xml b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/pom.xml new file mode 100644 index 000000000..a36e75816 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/pom.xml @@ -0,0 +1,83 @@ + + + + + + spring-boot-3.5.3 + com.megaease.easeagent + 2.3.0 + + 4.0.0 + + spring-boot-gateway-3.5.3 + + + + com.megaease.easeagent + plugin-api + provided + + + org.springframework.cloud + spring-cloud-starter-gateway + + + org.springframework.boot + spring-boot-starter-logging + + + provided + + + + com.megaease.easeagent + plugin-api-mock + ${project.version} + test + + + + org.springframework.boot + spring-boot-starter-test + 3.5.3 + + + org.springframework.boot + spring-boot-starter-logging + + + test + + + + com.fasterxml.jackson.core + jackson-core + 2.19.1 + test + + + com.fasterxml.jackson.core + jackson-databind + 2.19.1 + test + + + + diff --git a/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/AccessPlugin.java b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/AccessPlugin.java new file mode 100644 index 000000000..59f60bd79 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/AccessPlugin.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2017, MegaEase + * All rights reserved. + * + * 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 easeagent.plugin.spring353.gateway; + +import com.megaease.easeagent.plugin.AgentPlugin; +import com.megaease.easeagent.plugin.api.config.ConfigConst; + +public class AccessPlugin implements AgentPlugin { + @Override + public String getNamespace() { + return ConfigConst.Namespace.ACCESS; + } + + @Override + public String getDomain() { + return ConfigConst.OBSERVABILITY; + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/ForwardedPlugin.java b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/ForwardedPlugin.java new file mode 100644 index 000000000..91d4ddd08 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/ForwardedPlugin.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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 easeagent.plugin.spring353.gateway; + +import com.megaease.easeagent.plugin.AgentPlugin; +import com.megaease.easeagent.plugin.api.config.ConfigConst; + +public class ForwardedPlugin implements AgentPlugin { + @Override + public String getNamespace() { + return ConfigConst.Namespace.FORWARDED; + } + + @Override + public String getDomain() { + return ConfigConst.INTEGRABILITY; + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/GatewayCons.java b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/GatewayCons.java new file mode 100644 index 000000000..bcbe3c824 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/GatewayCons.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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 easeagent.plugin.spring353.gateway; + +import com.megaease.easeagent.plugin.CodeVersion; +import com.megaease.easeagent.plugin.api.config.ConfigConst; + +public interface GatewayCons { + String SPAN_KEY = GatewayCons.class.getName() + ".SPAN"; + String CHILD_SPAN_KEY = GatewayCons.class.getName() + ".CHILD_SPAN"; + String CLIENT_RECEIVE_CALLBACK_KEY = GatewayCons.class.getName() + ".CLIENT_RECEIVE_CALLBACK"; + + CodeVersion VERSIONS = CodeVersion.builder() + .key(ConfigConst.CodeVersion.KEY_SPRING_BOOT) + .add(ConfigConst.CodeVersion.VERSION_SPRING_BOOT3).build(); +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/SpringGatewayPlugin.java b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/SpringGatewayPlugin.java new file mode 100644 index 000000000..d8dce7c1a --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/SpringGatewayPlugin.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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 easeagent.plugin.spring353.gateway; + +import com.megaease.easeagent.plugin.AgentPlugin; +import com.megaease.easeagent.plugin.api.config.ConfigConst; + +public class SpringGatewayPlugin implements AgentPlugin { + + @Override + public String getNamespace() { + return ConfigConst.Namespace.SPRING_GATEWAY; + } + + @Override + public String getDomain() { + return ConfigConst.OBSERVABILITY; + } + +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/advice/AgentGlobalFilterAdvice.java b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/advice/AgentGlobalFilterAdvice.java new file mode 100644 index 000000000..89d6e5b7e --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/advice/AgentGlobalFilterAdvice.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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 easeagent.plugin.spring353.gateway.advice; + +import com.megaease.easeagent.plugin.CodeVersion; +import com.megaease.easeagent.plugin.Points; +import com.megaease.easeagent.plugin.matcher.ClassMatcher; +import com.megaease.easeagent.plugin.matcher.IClassMatcher; +import com.megaease.easeagent.plugin.matcher.IMethodMatcher; +import com.megaease.easeagent.plugin.matcher.MethodMatcher; +import easeagent.plugin.spring353.gateway.GatewayCons; + +import java.util.Set; + +public class AgentGlobalFilterAdvice implements Points { + @Override + public CodeVersion codeVersions() { + return GatewayCons.VERSIONS; + } + + @Override + public IClassMatcher getClassMatcher() { + return ClassMatcher.builder() + .hasClassName("easeagent.plugin.spring353.gateway.interceptor.initialize.AgentGlobalFilter") + .build(); + } + + @Override + public Set getMethodMatcher() { + return MethodMatcher.builder() + .named("filter") + .build().toSet(); + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/advice/HttpHeadersFilterAdvice.java b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/advice/HttpHeadersFilterAdvice.java new file mode 100644 index 000000000..d831d51d6 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/advice/HttpHeadersFilterAdvice.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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 easeagent.plugin.spring353.gateway.advice; + +import com.megaease.easeagent.plugin.CodeVersion; +import com.megaease.easeagent.plugin.Points; +import com.megaease.easeagent.plugin.matcher.ClassMatcher; +import com.megaease.easeagent.plugin.matcher.IClassMatcher; +import com.megaease.easeagent.plugin.matcher.IMethodMatcher; +import com.megaease.easeagent.plugin.matcher.MethodMatcher; +import easeagent.plugin.spring353.gateway.GatewayCons; + +import java.util.Set; + +public class HttpHeadersFilterAdvice implements Points { + @Override + public CodeVersion codeVersions() { + return GatewayCons.VERSIONS; + } + + @Override + public IClassMatcher getClassMatcher() { + return ClassMatcher.builder() + .hasClassName("org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter") + .build(); + } + + @Override + public Set getMethodMatcher() { + return MethodMatcher.multiBuilder() + .match(MethodMatcher.builder().named("filterRequest") + .build()) + .build(); + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/advice/InitGlobalFilterAdvice.java b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/advice/InitGlobalFilterAdvice.java new file mode 100644 index 000000000..0711226a3 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/advice/InitGlobalFilterAdvice.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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 easeagent.plugin.spring353.gateway.advice; + +import com.megaease.easeagent.plugin.CodeVersion; +import com.megaease.easeagent.plugin.Points; +import com.megaease.easeagent.plugin.matcher.ClassMatcher; +import com.megaease.easeagent.plugin.matcher.IClassMatcher; +import com.megaease.easeagent.plugin.matcher.IMethodMatcher; +import com.megaease.easeagent.plugin.matcher.MethodMatcher; +import easeagent.plugin.spring353.gateway.GatewayCons; + +import java.util.Set; + +public class InitGlobalFilterAdvice implements Points { + @Override + public CodeVersion codeVersions() { + return GatewayCons.VERSIONS; + } + + @Override + public IClassMatcher getClassMatcher() { + return ClassMatcher.builder() + .hasClassName("org.springframework.cloud.gateway.config.GatewayAutoConfiguration") + .or().hasClassName("org.springframework.cloud.gateway.config.GatewayAutoConfiguration$GatewayActuatorConfiguration") + .build(); + } + + @Override + public Set getMethodMatcher() { + return MethodMatcher.multiBuilder() + .match(MethodMatcher.builder() + .named("filteringWebHandler") + .or().named("gatewayControllerEndpoint") + .or().named("gatewayLegacyControllerEndpoint") + .build()) + .build(); + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/TimeUtils.java b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/TimeUtils.java new file mode 100644 index 000000000..8663ee569 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/TimeUtils.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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 easeagent.plugin.spring353.gateway.interceptor; + +import com.megaease.easeagent.plugin.api.Context; + +public class TimeUtils { + public static long startTime(Context context, Object key) { + Long start = context.get(key); + if (start == null) { + start = System.currentTimeMillis(); + context.put(key, start); + } + return start; + } + + public static Long removeStartTime(Context context, Object key) { + return context.remove(key); + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/forwarded/GatewayServerForwardedInterceptor.java b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/forwarded/GatewayServerForwardedInterceptor.java new file mode 100644 index 000000000..ec297c170 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/forwarded/GatewayServerForwardedInterceptor.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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 easeagent.plugin.spring353.gateway.interceptor.forwarded; + +import com.megaease.easeagent.plugin.annotation.AdviceTo; +import com.megaease.easeagent.plugin.api.Cleaner; +import com.megaease.easeagent.plugin.api.Context; +import com.megaease.easeagent.plugin.api.config.ConfigConst; +import com.megaease.easeagent.plugin.enums.Order; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor; +import easeagent.plugin.spring353.gateway.ForwardedPlugin; +import easeagent.plugin.spring353.gateway.advice.AgentGlobalFilterAdvice; +import easeagent.plugin.spring353.gateway.interceptor.tracing.FluxHttpServerRequest; +import org.springframework.web.server.ServerWebExchange; + +@AdviceTo(value = AgentGlobalFilterAdvice.class, plugin = ForwardedPlugin.class) +public class GatewayServerForwardedInterceptor implements NonReentrantInterceptor { + private static final Object FORWARDED_KEY = new Object(); + + + @Override + public void doBefore(MethodInfo methodInfo, Context context) { + ServerWebExchange exchange = (ServerWebExchange) methodInfo.getArgs()[0]; + FluxHttpServerRequest httpServerRequest = new FluxHttpServerRequest(exchange.getRequest()); + Cleaner cleaner = context.importForwardedHeaders(httpServerRequest); + context.put(FORWARDED_KEY, cleaner); + } + + @Override + public void doAfter(MethodInfo methodInfo, Context context) { + Cleaner cleaner = context.remove(FORWARDED_KEY); + if (cleaner != null) { + cleaner.close(); + } + } + + @Override + public String getType() { + return ConfigConst.PluginID.FORWARDED; + } + + @Override + public int order() { + return Order.FORWARDED.getOrder(); + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/initialize/AgentGlobalFilter.java b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/initialize/AgentGlobalFilter.java new file mode 100644 index 000000000..0f216a5e8 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/initialize/AgentGlobalFilter.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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 easeagent.plugin.spring353.gateway.interceptor.initialize; + +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +public class AgentGlobalFilter implements GlobalFilter { + public AgentGlobalFilter() { + } + + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + return chain.filter(exchange); + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/initialize/GlobalFilterInterceptor.java b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/initialize/GlobalFilterInterceptor.java new file mode 100644 index 000000000..125fc75aa --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/initialize/GlobalFilterInterceptor.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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 easeagent.plugin.spring353.gateway.interceptor.initialize; + +import com.megaease.easeagent.plugin.annotation.AdviceTo; +import com.megaease.easeagent.plugin.api.Context; +import com.megaease.easeagent.plugin.enums.Order; +import com.megaease.easeagent.plugin.interceptor.Interceptor; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import easeagent.plugin.spring353.gateway.SpringGatewayPlugin; +import easeagent.plugin.spring353.gateway.advice.InitGlobalFilterAdvice; +import org.springframework.cloud.gateway.filter.GlobalFilter; + +import java.util.List; + +@AdviceTo(value = InitGlobalFilterAdvice.class, plugin = SpringGatewayPlugin.class) +public class GlobalFilterInterceptor implements Interceptor { + @Override + @SuppressWarnings("unchecked") + public void before(MethodInfo methodInfo, Context context) { + List list = null; + switch (methodInfo.getMethod()) { + case "filteringWebHandler": + case "gatewayControllerEndpoint": + list = (List) methodInfo.getArgs()[0]; + break; + case "gatewayLegacyControllerEndpoint": + list = (List) methodInfo.getArgs()[1]; + break; + } + if (list == null || hasAgentFilter(list)) { + return; + } + list.add(0, new AgentGlobalFilter()); + } + + private boolean hasAgentFilter(List list) { + for (GlobalFilter globalFilter : list) { + if (globalFilter instanceof AgentGlobalFilter) { + return true; + } + } + return false; + } + + @Override + public String getType() { + return Order.INIT.getName(); + } + + @Override + public int order() { + return Order.INIT.getOrder(); + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/log/GatewayAccessLogInterceptor.java b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/log/GatewayAccessLogInterceptor.java new file mode 100644 index 000000000..ca529977c --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/log/GatewayAccessLogInterceptor.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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 easeagent.plugin.spring353.gateway.interceptor.log; + +import com.megaease.easeagent.plugin.annotation.AdviceTo; +import com.megaease.easeagent.plugin.api.Context; +import com.megaease.easeagent.plugin.api.context.AsyncContext; +import com.megaease.easeagent.plugin.api.context.RequestContext; +import com.megaease.easeagent.plugin.api.logging.AccessLogInfo; +import com.megaease.easeagent.plugin.api.trace.Span; +import com.megaease.easeagent.plugin.bridge.EaseAgent; +import com.megaease.easeagent.plugin.enums.Order; +import com.megaease.easeagent.plugin.interceptor.Interceptor; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import com.megaease.easeagent.plugin.tools.metrics.AccessLogServerInfo; +import com.megaease.easeagent.plugin.tools.metrics.HttpLog; +import easeagent.plugin.spring353.gateway.AccessPlugin; +import easeagent.plugin.spring353.gateway.advice.AgentGlobalFilterAdvice; +import easeagent.plugin.spring353.gateway.GatewayCons; +import easeagent.plugin.spring353.gateway.reactor.AgentMono; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +import static easeagent.plugin.spring353.gateway.interceptor.TimeUtils.removeStartTime; +import static easeagent.plugin.spring353.gateway.interceptor.TimeUtils.startTime; + + +@AdviceTo(value = AgentGlobalFilterAdvice.class, plugin = AccessPlugin.class) +public class GatewayAccessLogInterceptor implements Interceptor { + private static final Object START_TIME = new Object(); + private final HttpLog httpLog = new HttpLog(); + + @Override + public void before(MethodInfo methodInfo, Context context) { + ServerWebExchange exchange = (ServerWebExchange) methodInfo.getArgs()[0]; + AccessLogServerInfo serverInfo = this.serverInfo(exchange); + Long beginTime = startTime(context, START_TIME); + AccessLogInfo accessLog = this.httpLog.prepare(getSystem(), + getServiceName(), beginTime, getSpan(exchange), serverInfo); + exchange.getAttributes().put(AccessLogInfo.class.getName(), accessLog); + } + + @Override + @SuppressWarnings("unchecked") + public void after(MethodInfo methodInfo, Context context) { + try { + // async + Mono mono = (Mono) methodInfo.getRetValue(); + methodInfo.setRetValue(new AgentMono(mono, methodInfo, context.exportAsync(), this::finishCallback)); + } finally { + removeStartTime(context, START_TIME); + } + } + + Span getSpan(ServerWebExchange exchange) { + RequestContext pCtx = exchange.getAttribute(GatewayCons.SPAN_KEY); + if (pCtx == null) { + return null; + } + return pCtx.span(); + } + + private void finishCallback(MethodInfo methodInfo, AsyncContext ctx) { + ServerWebExchange exchange = (ServerWebExchange) methodInfo.getArgs()[0]; + AccessLogInfo accessLog = exchange.getAttribute(AccessLogInfo.class.getName()); + if (accessLog == null) { + return; + } + Long beginTime = ctx.get(START_TIME); + AccessLogServerInfo serverInfo = this.serverInfo(exchange); + this.httpLog.finish(accessLog, methodInfo.isSuccess(), beginTime, serverInfo); + EaseAgent.getAgentReport().report(accessLog); + } + + AccessLogServerInfo serverInfo(ServerWebExchange exchange) { + SpringGatewayAccessLogServerInfo serverInfo = new SpringGatewayAccessLogServerInfo(); + serverInfo.load(exchange); + return serverInfo; + } + + String getSystem() { + return EaseAgent.getConfig("system"); + } + + String getServiceName() { + return EaseAgent.getConfig("name"); + } + + @Override + public String getType() { + return Order.LOG.getName(); + } + + @Override + public int order() { + return Order.LOG.getOrder(); + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/log/SpringGatewayAccessLogServerInfo.java b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/log/SpringGatewayAccessLogServerInfo.java new file mode 100644 index 000000000..975a97783 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/log/SpringGatewayAccessLogServerInfo.java @@ -0,0 +1,94 @@ + /* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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 easeagent.plugin.spring353.gateway.interceptor.log; + +import com.megaease.easeagent.plugin.tools.metrics.AccessLogServerInfo; +import org.springframework.cloud.gateway.route.Route; +import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; +import org.springframework.web.server.ServerWebExchange; + +import java.net.InetSocketAddress; +import java.util.Map; +import java.util.Optional; + +public class SpringGatewayAccessLogServerInfo implements AccessLogServerInfo { + private ServerWebExchange exchange; + + public void load(ServerWebExchange exchange) { + this.exchange = exchange; + } + + @Override + public String getMethod() { + return exchange.getRequest().getMethod().name(); + } + + @Override + public String getHeader(String key) { + return exchange.getRequest().getHeaders().getFirst(key); + } + + @Override + public String getRemoteAddr() { + InetSocketAddress remoteAddress = exchange.getRequest().getRemoteAddress(); + return remoteAddress == null ? null : remoteAddress.getHostString(); + } + + @Override + public String getRequestURI() { + return exchange.getRequest().getURI().toString(); + } + + @Override + public int getResponseBufferSize() { + return 0; + } + + @Override + public String getMatchURL() { + HttpMethod httpMethod = exchange.getRequest().getMethod(); + if (httpMethod == null) { + return ""; + } + Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR); + if (route != null && route.getUri() != null) { + return httpMethod.name() + " " + route.getUri().toString(); + } + return httpMethod.name() + " " + getRequestURI(); + } + + @Override + public Map findHeaders() { + return exchange.getRequest().getHeaders().toSingleValueMap(); + } + + @Override + public Map findQueries() { + return exchange.getRequest().getQueryParams().toSingleValueMap(); + } + + @Override + public String getStatusCode() { + HttpStatusCode rawStatusCode = exchange.getResponse().getStatusCode(); + return Optional.ofNullable(rawStatusCode).map(e -> e.value() + "").orElse("0"); + } +} + diff --git a/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/metric/GatewayMetricsInterceptor.java b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/metric/GatewayMetricsInterceptor.java new file mode 100644 index 000000000..06b1ac378 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/metric/GatewayMetricsInterceptor.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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 easeagent.plugin.spring353.gateway.interceptor.metric; + +import com.megaease.easeagent.plugin.annotation.AdviceTo; +import com.megaease.easeagent.plugin.api.Context; +import com.megaease.easeagent.plugin.api.config.IPluginConfig; +import com.megaease.easeagent.plugin.api.context.AsyncContext; +import com.megaease.easeagent.plugin.api.metric.ServiceMetricRegistry; +import com.megaease.easeagent.plugin.api.metric.name.Tags; +import com.megaease.easeagent.plugin.enums.Order; +import com.megaease.easeagent.plugin.interceptor.Interceptor; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import com.megaease.easeagent.plugin.tools.metrics.ServerMetric; +import com.megaease.easeagent.plugin.utils.SystemClock; +import easeagent.plugin.spring353.gateway.SpringGatewayPlugin; +import easeagent.plugin.spring353.gateway.advice.AgentGlobalFilterAdvice; +import easeagent.plugin.spring353.gateway.reactor.AgentMono; +import org.springframework.cloud.gateway.route.Route; +import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatusCode; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +import static easeagent.plugin.spring353.gateway.interceptor.TimeUtils.removeStartTime; +import static easeagent.plugin.spring353.gateway.interceptor.TimeUtils.startTime; + + +@AdviceTo(value = AgentGlobalFilterAdvice.class, plugin = SpringGatewayPlugin.class) +public class GatewayMetricsInterceptor implements Interceptor { + private static Object START_TIME = new Object(); + private static volatile ServerMetric SERVER_METRIC = null; + + @Override + public void init(IPluginConfig config, String className, String methodName, String methodDescriptor) { + SERVER_METRIC = ServiceMetricRegistry.getOrCreate(config, + new Tags("application", "http-request", "url"), ServerMetric.SERVICE_METRIC_SUPPLIER); + } + + @Override + public void before(MethodInfo methodInfo, Context context) { + startTime(context, START_TIME); + // context.put(START, SystemClock.now()); + } + + + @Override + @SuppressWarnings("unchecked") + public void after(MethodInfo methodInfo, Context context) { + try { + ServerWebExchange exchange = (ServerWebExchange) methodInfo.getArgs()[0]; + if (!methodInfo.isSuccess()) { + String key = getKey(exchange); + Long start = startTime(context, START_TIME); + long end = System.currentTimeMillis(); + SERVER_METRIC.collectMetric(key, 500, methodInfo.getThrowable(), start, end); + return; + } + // async + Mono mono = (Mono) methodInfo.getRetValue(); + methodInfo.setRetValue(new AgentMono(mono, methodInfo, context.exportAsync(), this::finishCallback)); + } finally { + removeStartTime(context, START_TIME); + } + } + + void finishCallback(MethodInfo methodInfo, AsyncContext ctx) { + ServerWebExchange exchange = (ServerWebExchange) methodInfo.getArgs()[0]; + String key = getKey(exchange); + HttpStatusCode statusCode = exchange.getResponse().getStatusCode(); + int code = 0; + if (statusCode != null) { + code = statusCode.value(); + } + SERVER_METRIC.collectMetric(key, code, methodInfo.getThrowable(), + ctx.get(START_TIME), SystemClock.now()); + } + + public static String getKey(ServerWebExchange exchange) { + HttpMethod httpMethod = exchange.getRequest().getMethod(); + if (httpMethod == null) { + return ""; + } + Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR); + String key; + if (route != null && route.getUri() != null) { + key = httpMethod.name() + " " + route.getUri().toString(); + } else { + key = httpMethod.name() + " " + exchange.getRequest().getURI(); + } + return key; + } + + @Override + public String getType() { + return Order.METRIC.getName(); + } + + @Override + public int order() { + return Order.METRIC.getOrder(); + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/tracing/FluxHttpServerRequest.java b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/tracing/FluxHttpServerRequest.java new file mode 100644 index 000000000..ebc144665 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/tracing/FluxHttpServerRequest.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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 easeagent.plugin.spring353.gateway.interceptor.tracing; + +import com.megaease.easeagent.plugin.api.trace.Span; +import com.megaease.easeagent.plugin.tools.trace.HttpRequest; +import org.springframework.http.HttpHeaders; +import org.springframework.http.server.reactive.ServerHttpRequest; + +public class FluxHttpServerRequest implements HttpRequest { + private final ServerHttpRequest request; + + public FluxHttpServerRequest(ServerHttpRequest request) { + this.request = request; + } + + @Override + public Span.Kind kind() { + return Span.Kind.SERVER; + } + + @Override + public String header(String name) { + HttpHeaders headers = this.request.getHeaders(); + return headers.getFirst(name); + } + + @Override + public boolean cacheScope() { + return false; + } + + @Override + public void setHeader(String name, String value) { + } + + @Override + public String method() { + return this.request.getMethod().name(); + } + + @Override + public String path() { + return this.request.getPath().value(); + } + + @Override + public String route() { + return null; + } + + @Override + public String getRemoteAddr() { + if (this.request != null && this.request.getRemoteAddress() != null) { + return this.request.getRemoteAddress().getAddress().getHostAddress(); + } else { + return "Unknown"; + } + } + + @Override + public int getRemotePort() { + if (this.request != null && this.request.getRemoteAddress() != null) { + return this.request.getRemoteAddress().getPort(); + } else { + return 0; + } + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/tracing/FluxHttpServerResponse.java b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/tracing/FluxHttpServerResponse.java new file mode 100644 index 000000000..c9b04c7a1 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/tracing/FluxHttpServerResponse.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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 easeagent.plugin.spring353.gateway.interceptor.tracing; + +import com.megaease.easeagent.plugin.tools.trace.HttpResponse; +import org.springframework.http.HttpHeaders; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.web.reactive.HandlerMapping; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.util.pattern.PathPattern; + +public class FluxHttpServerResponse implements HttpResponse { + private final FluxHttpServerRequest request; + private final ServerHttpResponse response; + private final String route; + private final Throwable error; + + public FluxHttpServerResponse(FluxHttpServerRequest request, + ServerHttpResponse response, String route, Throwable error) { + this.request = request; + this.response = response; + this.route = route; + this.error = error; + } + + public FluxHttpServerResponse(ServerWebExchange exchange, Throwable error) { + this.request = new FluxHttpServerRequest(exchange.getRequest()); + this.response = exchange.getResponse(); + + PathPattern bestPattern = exchange.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE); + String route = null; + if (bestPattern != null) { + route = bestPattern.getPatternString(); + } + + this.route = route; + this.error = error; + } + + @Override + public String header(String name) { + if (this.response == null) { + return null; + } + HttpHeaders hs = this.response.getHeaders(); + return hs.getFirst(name); + } + + @Override + public String method() { + return this.request.method(); + } + + @Override + public String route() { + return this.route; + } + + @Override + public int statusCode() { + if (this.response != null && this.response.getStatusCode() != null) { + return this.response.getStatusCode().value(); + } else { + return 0; + } + } + + @Override + public Throwable maybeError() { + return this.error; + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/tracing/GatewayServerTracingInterceptor.java b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/tracing/GatewayServerTracingInterceptor.java new file mode 100644 index 000000000..4c5af7b9b --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/tracing/GatewayServerTracingInterceptor.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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 easeagent.plugin.spring353.gateway.interceptor.tracing; + +import com.megaease.easeagent.plugin.annotation.AdviceTo; +import com.megaease.easeagent.plugin.api.Cleaner; +import com.megaease.easeagent.plugin.api.Context; +import com.megaease.easeagent.plugin.api.context.AsyncContext; +import com.megaease.easeagent.plugin.api.context.RequestContext; +import com.megaease.easeagent.plugin.bridge.EaseAgent; +import com.megaease.easeagent.plugin.enums.Order; +import com.megaease.easeagent.plugin.interceptor.Interceptor; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import com.megaease.easeagent.plugin.tools.trace.HttpResponse; +import com.megaease.easeagent.plugin.tools.trace.HttpUtils; +import easeagent.plugin.spring353.gateway.SpringGatewayPlugin; +import easeagent.plugin.spring353.gateway.advice.AgentGlobalFilterAdvice; +import easeagent.plugin.spring353.gateway.GatewayCons; +import easeagent.plugin.spring353.gateway.reactor.AgentMono; +import org.springframework.web.reactive.HandlerMapping; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.util.pattern.PathPattern; +import reactor.core.publisher.Mono; + +import java.util.function.BiConsumer; + +@AdviceTo(value = AgentGlobalFilterAdvice.class, plugin = SpringGatewayPlugin.class) +public class GatewayServerTracingInterceptor implements Interceptor { + static final String SPAN_CONTEXT_KEY = GatewayServerTracingInterceptor.class.getName() + "-P-CTX"; + + @Override + public void before(MethodInfo methodInfo, Context context) { + ServerWebExchange exchange = (ServerWebExchange) methodInfo.getArgs()[0]; + FluxHttpServerRequest httpServerRequest = new FluxHttpServerRequest(exchange.getRequest()); + RequestContext pCtx = context.serverReceive(httpServerRequest); + HttpUtils.handleReceive(pCtx.span(), httpServerRequest); + context.put(SPAN_CONTEXT_KEY, pCtx); + context.put(FluxHttpServerRequest.class, httpServerRequest); + exchange.getAttributes().put(GatewayCons.SPAN_KEY, pCtx); + } + + private void cleanContext(Context context) { + context.remove(FluxHttpServerRequest.class); + context.remove(SPAN_CONTEXT_KEY); + } + + @Override + @SuppressWarnings("unchecked") + public void after(MethodInfo methodInfo, Context context) { + RequestContext pCtx = context.get(SPAN_CONTEXT_KEY); + if (pCtx == null) { + return; + } + try { + if (!methodInfo.isSuccess()) { + pCtx.span().error(methodInfo.getThrowable()); + pCtx.span().finish(); + return; + } + + // async + Mono mono = (Mono) methodInfo.getRetValue(); + methodInfo.setRetValue(new AgentMono(mono, methodInfo, context.exportAsync(), this::finishCallback)); + } finally { + cleanContext(context); + pCtx.scope().close(); + } + + } + + void finishCallback(MethodInfo methodInfo, AsyncContext ctx) { + try (Cleaner ignored = ctx.importToCurrent()) { + RequestContext pCtx = EaseAgent.getContext().get(SPAN_CONTEXT_KEY); + ServerWebExchange exchange = (ServerWebExchange) methodInfo.getArgs()[0]; + BiConsumer consumer = exchange.getAttribute(GatewayCons.CLIENT_RECEIVE_CALLBACK_KEY); + if (consumer != null) { + consumer.accept(exchange, methodInfo); + } + + FluxHttpServerRequest httpServerRequest = EaseAgent.getContext().get(FluxHttpServerRequest.class); + PathPattern bestPattern = exchange.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE); + String route = null; + if (bestPattern != null) { + route = bestPattern.getPatternString(); + } + HttpResponse response = new FluxHttpServerResponse(httpServerRequest, + exchange.getResponse(), route, methodInfo.getThrowable()); + HttpUtils.finish(pCtx.span(), response); + exchange.getAttributes().remove(GatewayCons.SPAN_KEY); + } + } + + @Override + public String getType() { + return Order.TRACING.getName(); + } + + @Override + public int order() { + return Order.TRACING.getOrder(); + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/tracing/HttpHeadersFilterTracingInterceptor.java b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/tracing/HttpHeadersFilterTracingInterceptor.java new file mode 100644 index 000000000..5939fb9e4 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/interceptor/tracing/HttpHeadersFilterTracingInterceptor.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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 easeagent.plugin.spring353.gateway.interceptor.tracing; + +import com.megaease.easeagent.plugin.annotation.AdviceTo; +import com.megaease.easeagent.plugin.api.Context; +import com.megaease.easeagent.plugin.api.context.RequestContext; +import com.megaease.easeagent.plugin.api.trace.Scope; +import com.megaease.easeagent.plugin.api.trace.Span; +import com.megaease.easeagent.plugin.enums.Order; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor; +import com.megaease.easeagent.plugin.tools.trace.HttpUtils; +import easeagent.plugin.spring353.gateway.SpringGatewayPlugin; +import easeagent.plugin.spring353.gateway.advice.HttpHeadersFilterAdvice; +import easeagent.plugin.spring353.gateway.GatewayCons; +import org.springframework.http.HttpHeaders; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.web.server.ServerWebExchange; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; + +@AdviceTo(value = HttpHeadersFilterAdvice.class, plugin = SpringGatewayPlugin.class) +public class HttpHeadersFilterTracingInterceptor implements NonReentrantInterceptor { + // org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter + static final String CLIENT_HEADER_ATTR = HttpHeadersFilterTracingInterceptor.class.getName() + ".Headers"; + + @Override + public void doAfter(MethodInfo methodInfo, Context context) { + ServerWebExchange exchange = (ServerWebExchange) methodInfo.getArgs()[1]; + HttpHeaders retHttpHeaders = (HttpHeaders) methodInfo.getRetValue(); + RequestContext pCtx = exchange.getAttribute(GatewayCons.SPAN_KEY); + if (pCtx == null) { + return; + } + FluxHttpServerRequest request = new HeaderFilterRequest(exchange.getRequest()); + + RequestContext pnCtx = context.clientRequest(request); + try (Scope ignored = pnCtx.scope()) { + pnCtx.span().start(); + exchange.getAttributes().put(GatewayCons.CHILD_SPAN_KEY, pnCtx); + Map map = getHeadersFromExchange(exchange); + map.putAll(retHttpHeaders.toSingleValueMap()); + map.putAll(pnCtx.getHeaders()); + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setAll(map); + methodInfo.setRetValue(httpHeaders); + + BiConsumer consumer = (serverWebExchange, info) -> { + RequestContext p = serverWebExchange.getAttribute(GatewayCons.CHILD_SPAN_KEY); + if (p == null) { + return; + } + FluxHttpServerResponse response = new FluxHttpServerResponse(serverWebExchange, info.getThrowable()); + HttpUtils.save(p.span(), response); + p.finish(response); + }; + exchange.getAttributes().put(GatewayCons.CLIENT_RECEIVE_CALLBACK_KEY, consumer); + } + } + + @Override + public int order() { + return Order.HIGH.getOrder(); + } + + private Map getHeadersFromExchange(ServerWebExchange exchange) { + Map headers = exchange.getAttribute(CLIENT_HEADER_ATTR); + if (headers == null) { + headers = new HashMap<>(); + exchange.getAttributes().put(CLIENT_HEADER_ATTR, headers); + } + return headers; + } + + static class HeaderFilterRequest extends FluxHttpServerRequest { + public HeaderFilterRequest(ServerHttpRequest request) { + super(request); + } + + + @Override + public Span.Kind kind() { + return Span.Kind.CLIENT; + } + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/reactor/AgentCoreSubscriber.java b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/reactor/AgentCoreSubscriber.java new file mode 100644 index 000000000..5e582ad78 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/reactor/AgentCoreSubscriber.java @@ -0,0 +1,76 @@ + /* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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 easeagent.plugin.spring353.gateway.reactor; + + import com.megaease.easeagent.plugin.api.context.AsyncContext; + import com.megaease.easeagent.plugin.interceptor.MethodInfo; + import org.reactivestreams.Subscription; + import reactor.core.CoreSubscriber; + + import javax.annotation.Nonnull; + import java.util.function.BiConsumer; + + public class AgentCoreSubscriber implements CoreSubscriber { + + private final CoreSubscriber actual; + private final MethodInfo methodInfo; + private final AsyncContext asyncContext; + private final BiConsumer finish; + + @SuppressWarnings("unchecked") + public AgentCoreSubscriber(CoreSubscriber actual, + MethodInfo methodInfo, + // Object context, + AsyncContext async, + BiConsumer finish) { + this.actual = (CoreSubscriber) actual; + this.methodInfo = methodInfo; + this.finish = finish; + this.asyncContext = async; + } + + @Nonnull + @Override + public reactor.util.context.Context currentContext() { + return actual.currentContext(); + } + + @Override + public void onSubscribe(@Nonnull Subscription s) { + actual.onSubscribe(s); + } + + @Override + public void onNext(Void t) { + actual.onNext(t); + } + + @Override + public void onError(Throwable t) { + actual.onError(t); + methodInfo.setThrowable(t); + finish.accept(this.methodInfo, asyncContext); + } + + @Override + public void onComplete() { + actual.onComplete(); + finish.accept(this.methodInfo, asyncContext); + } + } + diff --git a/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/reactor/AgentMono.java b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/reactor/AgentMono.java new file mode 100644 index 000000000..0f257e281 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/main/java/easeagent/plugin/spring353/gateway/reactor/AgentMono.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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 easeagent.plugin.spring353.gateway.reactor; + +import com.megaease.easeagent.plugin.api.Cleaner; +import com.megaease.easeagent.plugin.api.context.AsyncContext; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import reactor.core.CoreSubscriber; +import reactor.core.publisher.Mono; + +import javax.annotation.Nonnull; +import java.util.function.BiConsumer; + +public class AgentMono extends Mono { + private final Mono source; + private final MethodInfo methodInfo; + private final AsyncContext asyncContext; + private final BiConsumer finish; + + public AgentMono(Mono mono, MethodInfo methodInfo, + AsyncContext async, + BiConsumer consumer) { + this.source = mono; + this.methodInfo = methodInfo; + this.finish = consumer; + this.asyncContext = async; + } + + @Override + public void subscribe(@Nonnull CoreSubscriber actual) { + try (Cleaner ignored = asyncContext.importToCurrent()) { + this.source.subscribe(new AgentCoreSubscriber(actual, methodInfo, + asyncContext, finish)); + } + } + + public MethodInfo getMethodInfo() { + return methodInfo; + } + + public AsyncContext getAsyncContext() { + return asyncContext; + } + + public BiConsumer getFinish() { + return finish; + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/TestConst.java b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/TestConst.java new file mode 100644 index 000000000..a6e41dec8 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/TestConst.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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 easeagent.plugin.spring353.gateway; + +public class TestConst { + public static final String FORWARDED_NAME = "X-Forwarded-For"; + public static final String FORWARDED_VALUE = "testForwarded"; +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/TestServerWebExchangeUtils.java b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/TestServerWebExchangeUtils.java new file mode 100644 index 000000000..52e1259b9 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/TestServerWebExchangeUtils.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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 easeagent.plugin.spring353.gateway; + +import org.springframework.http.HttpStatus; +import org.springframework.mock.http.server.reactive.MockServerHttpRequest; +import org.springframework.mock.web.server.MockServerWebExchange; +import org.springframework.web.reactive.HandlerMapping; +import org.springframework.web.util.pattern.PathPattern; +import org.springframework.web.util.pattern.PathPatternParser; + +import java.net.InetSocketAddress; + +public class TestServerWebExchangeUtils { + + public static final MockServerHttpRequest.BaseBuilder builder() { + return MockServerHttpRequest.get("http://192.168.0.12:8080/test", "a=b") + .header(TestConst.FORWARDED_NAME, TestConst.FORWARDED_VALUE) + .queryParam("a", "b"); + } + + public static final MockServerWebExchange build(MockServerHttpRequest.BaseBuilder requestBuilder) { + MockServerHttpRequest mockServerHttpRequest = requestBuilder.build(); + return MockServerWebExchange.builder(mockServerHttpRequest).build(); + } + + + public static final MockServerWebExchange mockServerWebExchange() { + MockServerHttpRequest mockServerHttpRequest = TestServerWebExchangeUtils.builder() + .remoteAddress(new InetSocketAddress("192.168.0.12", 8080)).build(); + MockServerWebExchange mockServerWebExchange = MockServerWebExchange.builder(mockServerHttpRequest).build(); + mockServerWebExchange.getResponse().setStatusCode(HttpStatus.OK); + PathPatternParser parser = new PathPatternParser(); + PathPattern pathPattern = parser.parse("/test"); + mockServerWebExchange.getAttributes().put(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, pathPattern); + return mockServerWebExchange; + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/TimeUtilsTest.java b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/TimeUtilsTest.java new file mode 100644 index 000000000..99fbd7199 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/TimeUtilsTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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 easeagent.plugin.spring353.gateway.interceptor; + +import com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner; +import com.megaease.easeagent.plugin.bridge.EaseAgent; +import easeagent.plugin.spring353.gateway.interceptor.TimeUtils; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + + +@RunWith(EaseAgentJunit4ClassRunner.class) +public class TimeUtilsTest { + + @Test + public void startTime() throws InterruptedException { + Object key = new Object(); + long startTime = TimeUtils.startTime(EaseAgent.getContext(), key); + Thread.sleep(10); + assertEquals(startTime, TimeUtils.startTime(EaseAgent.getContext(), key)); + Object key2 = new Object(); + assertNotEquals(startTime, TimeUtils.startTime(EaseAgent.getContext(), key2)); + } + + @Test + public void removeStartTime() throws InterruptedException { + Object key = new Object(); + long startTime = TimeUtils.startTime(EaseAgent.getContext(), key); + Long startObj = TimeUtils.removeStartTime(EaseAgent.getContext(), key); + assertNotNull(startObj); + assertEquals(startTime, (long) startObj); + Thread.sleep(10); + assertNotEquals(startTime, TimeUtils.startTime(EaseAgent.getContext(), key)); + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/forwarded/GatewayServerForwardedInterceptorTest.java b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/forwarded/GatewayServerForwardedInterceptorTest.java new file mode 100644 index 000000000..76dc7c5ff --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/forwarded/GatewayServerForwardedInterceptorTest.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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 easeagent.plugin.spring353.gateway.interceptor.forwarded; + +import com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner; +import com.megaease.easeagent.plugin.api.Context; +import com.megaease.easeagent.plugin.api.config.ConfigConst; +import com.megaease.easeagent.plugin.bridge.EaseAgent; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import easeagent.plugin.spring353.gateway.TestConst; +import easeagent.plugin.spring353.gateway.TestServerWebExchangeUtils; +import static easeagent.plugin.spring353.gateway.TestServerWebExchangeUtils.builder; + +import easeagent.plugin.spring353.gateway.interceptor.forwarded.GatewayServerForwardedInterceptor; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.mock.web.server.MockServerWebExchange; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +@RunWith(EaseAgentJunit4ClassRunner.class) +public class GatewayServerForwardedInterceptorTest { + + @Test + public void doBefore() { + GatewayServerForwardedInterceptor interceptor = new GatewayServerForwardedInterceptor(); + MockServerWebExchange mockServerWebExchange = TestServerWebExchangeUtils.build(builder().header(TestConst.FORWARDED_NAME, TestConst.FORWARDED_VALUE)); + Context context = EaseAgent.getContext(); + MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build(); + interceptor.doBefore(methodInfo, context); + assertEquals(TestConst.FORWARDED_VALUE, context.get(TestConst.FORWARDED_NAME)); + interceptor.doAfter(methodInfo, context); + assertNull(context.get(TestConst.FORWARDED_NAME)); + } + + @Test + public void doAfter() { + doBefore(); + } + + @Test + public void getType() { + GatewayServerForwardedInterceptor interceptor = new GatewayServerForwardedInterceptor(); + assertEquals(ConfigConst.PluginID.FORWARDED, interceptor.getType()); + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/initialize/AgentGlobalFilterTest.java b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/initialize/AgentGlobalFilterTest.java new file mode 100644 index 000000000..5279089ef --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/initialize/AgentGlobalFilterTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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 easeagent.plugin.spring353.gateway.interceptor.initialize; + +import org.junit.Test; +import org.springframework.mock.web.server.MockServerWebExchange; + +import java.util.concurrent.atomic.AtomicBoolean; + +import static easeagent.plugin.spring353.gateway.TestServerWebExchangeUtils.mockServerWebExchange; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +public class AgentGlobalFilterTest { + + @Test + public void filter() { + AgentGlobalFilter agentGlobalFilter = new AgentGlobalFilter(); + AtomicBoolean ran = new AtomicBoolean(true); + MockServerWebExchange mockServerWebExchange = mockServerWebExchange(); + agentGlobalFilter.filter(mockServerWebExchange, exchange -> { + assertSame(mockServerWebExchange, exchange); + ran.set(true); + return null; + }); + assertTrue(ran.get()); + } + + +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/initialize/GlobalFilterInterceptorTest.java b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/initialize/GlobalFilterInterceptorTest.java new file mode 100644 index 000000000..1917b6632 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/initialize/GlobalFilterInterceptorTest.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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 easeagent.plugin.spring353.gateway.interceptor.initialize; + +import com.megaease.easeagent.plugin.api.config.ConfigConst; +import com.megaease.easeagent.plugin.enums.Order; +import com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +public class GlobalFilterInterceptorTest { + + @Test + public void before() { + GlobalFilterInterceptor interceptor = new GlobalFilterInterceptor(); + List arg = new ArrayList(); + MethodInfo methodInfo = MethodInfo.builder().method("filteringWebHandler").args(new Object[]{arg}).build(); + interceptor.before(methodInfo, null); + assertEquals(1, arg.size()); + assertEquals(1, arg.size()); + arg.clear(); + methodInfo = MethodInfo.builder().method("gatewayControllerEndpoint").args(new Object[]{arg}).build(); + interceptor.before(methodInfo, null); + assertEquals(1, arg.size()); + arg.clear(); + methodInfo = MethodInfo.builder().method("gatewayLegacyControllerEndpoint").args(new Object[]{null, arg}).build(); + interceptor.before(methodInfo, null); + assertEquals(1, arg.size()); + + } + + @Test + public void getType() { + GlobalFilterInterceptor interceptor = new GlobalFilterInterceptor(); + assertEquals(ConfigConst.PluginID.INIT, interceptor.getType()); + } + + @Test + public void order() { + GlobalFilterInterceptor interceptor = new GlobalFilterInterceptor(); + assertEquals(Order.INIT.getOrder(), interceptor.order()); + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/log/GatewayAccessLogInfoInterceptorTest.java b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/log/GatewayAccessLogInfoInterceptorTest.java new file mode 100644 index 000000000..b4111b773 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/log/GatewayAccessLogInfoInterceptorTest.java @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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 easeagent.plugin.spring353.gateway.interceptor.log; + +import com.megaease.easeagent.mock.plugin.api.MockEaseAgent; +import com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner; +import com.megaease.easeagent.mock.plugin.api.utils.InterceptorTestUtils; +import com.megaease.easeagent.mock.plugin.api.utils.TagVerifier; +import com.megaease.easeagent.mock.report.MockReport; +import com.megaease.easeagent.plugin.api.Context; +import com.megaease.easeagent.plugin.api.config.ConfigConst; +import com.megaease.easeagent.plugin.api.context.RequestContext; +import com.megaease.easeagent.plugin.api.logging.AccessLogInfo; +import com.megaease.easeagent.plugin.api.trace.Scope; +import com.megaease.easeagent.plugin.api.trace.Span; +import com.megaease.easeagent.plugin.bridge.EaseAgent; +import com.megaease.easeagent.plugin.enums.Order; +import com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import com.megaease.easeagent.plugin.utils.common.HostAddress; +import easeagent.plugin.spring353.gateway.AccessPlugin; +import easeagent.plugin.spring353.gateway.TestConst; +import easeagent.plugin.spring353.gateway.TestServerWebExchangeUtils; +import easeagent.plugin.spring353.gateway.interceptor.log.GatewayAccessLogInterceptor; +import easeagent.plugin.spring353.gateway.interceptor.TimeUtils; +import easeagent.plugin.spring353.gateway.interceptor.tracing.GatewayServerTracingInterceptorTest; +import easeagent.plugin.spring353.gateway.reactor.AgentMono; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.mock.web.server.MockServerWebExchange; + +import static org.junit.Assert.*; + +@RunWith(EaseAgentJunit4ClassRunner.class) +public class GatewayAccessLogInfoInterceptorTest { + private Object startTime = AgentFieldReflectAccessor.getStaticFieldValue(GatewayAccessLogInterceptor.class, "START_TIME"); + + @Test + public void before() { + GatewayAccessLogInterceptor interceptor = new GatewayAccessLogInterceptor(); + Context context = EaseAgent.getContext(); + MockServerWebExchange mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange(); + MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build(); + interceptor.before(methodInfo, context); + AccessLogInfo accessLog = (AccessLogInfo) mockServerWebExchange.getAttributes().get(AccessLogInfo.class.getName()); + assertNotNull(accessLog); + verify(accessLog, TimeUtils.startTime(context, startTime)); + assertNull(accessLog.getTraceId()); + assertNull(accessLog.getSpanId()); + assertNull(accessLog.getParentSpanId()); + + mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange(); + methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build(); + interceptor.before(methodInfo, context); + RequestContext requestContext = GatewayServerTracingInterceptorTest.beforeGatewayServerTracing(mockServerWebExchange); + Span span = requestContext.span(); + try (Scope ignored = requestContext.scope()) { + interceptor.before(methodInfo, context); + accessLog = (AccessLogInfo) mockServerWebExchange.getAttributes().get(AccessLogInfo.class.getName()); + assertNotNull(accessLog); + verify(accessLog, TimeUtils.startTime(context, startTime)); + assertEquals(span.traceIdString(), accessLog.getTraceId()); + assertEquals(span.spanIdString(), accessLog.getSpanId()); + assertEquals(span.parentIdString(), accessLog.getParentSpanId()); + } + span.finish(); + } + + + public void verify(AccessLogInfo accessLog, long startTime) { + assertEquals("test-gateway-system", accessLog.getSystem()); + assertEquals("test-gateway-service", accessLog.getService()); + assertEquals(HostAddress.localhost(), accessLog.getHostName()); + assertEquals(HostAddress.getHostIpv4(), accessLog.getHostIpv4()); + assertEquals("GET http://192.168.0.12:8080/test?a=b", accessLog.getUrl()); + assertEquals("GET", accessLog.getMethod()); + assertEquals(TestConst.FORWARDED_VALUE, accessLog.getHeaders().get(TestConst.FORWARDED_NAME)); + assertEquals(startTime, accessLog.getBeginTime()); + assertEquals("b", accessLog.getQueries().get("a")); + assertEquals(TestConst.FORWARDED_VALUE, accessLog.getClientIP()); + assertTrue(accessLog.getBeginCpuTime() > 0); + } + + @Test + public void after() throws InterruptedException { + EaseAgent.agentReport = MockReport.getAgentReport(); + GatewayAccessLogInterceptor interceptor = new GatewayAccessLogInterceptor(); + InterceptorTestUtils.init(interceptor, new AccessPlugin()); + Context context = EaseAgent.getContext(); + MockServerWebExchange mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange(); + MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build(); + interceptor.before(methodInfo, context); + Long start = context.get(startTime); + assertNotNull(start); + interceptor.after(methodInfo, context); + assertNull(context.get(startTime)); + assertTrue(methodInfo.getRetValue() instanceof AgentMono); + AgentMono agentMono = (AgentMono) methodInfo.getRetValue(); + TagVerifier tagVerifier = new TagVerifier().add("type", "access-log").add("system", "test-gateway-system"); + // LastJsonReporter lastJsonReporter = MockEaseAgent.lastMetricJsonReporter(tagVerifier::verifyAnd); + Thread thread = new Thread(() -> agentMono.getFinish().accept(agentMono.getMethodInfo(), agentMono.getAsyncContext())); + thread.start(); + thread.join(); + AccessLogInfo accessLog = MockEaseAgent.getLastLog(); + verify(accessLog, start); + } + + @Test + public void serverInfo() { + MockServerWebExchange exchange = TestServerWebExchangeUtils.mockServerWebExchange(); + GatewayAccessLogInterceptor interceptor = new GatewayAccessLogInterceptor(); + assertNotNull(interceptor.serverInfo(exchange)); + } + + @Test + public void getSystem() { + GatewayAccessLogInterceptor interceptor = new GatewayAccessLogInterceptor(); + assertEquals("test-gateway-system", interceptor.getSystem()); + } + + @Test + public void getServiceName() { + GatewayAccessLogInterceptor interceptor = new GatewayAccessLogInterceptor(); + assertEquals("test-gateway-service", interceptor.getServiceName()); + } + + @Test + public void getType() { + GatewayAccessLogInterceptor interceptor = new GatewayAccessLogInterceptor(); + assertEquals(ConfigConst.PluginID.LOG, interceptor.getType()); + } + + @Test + public void order() { + GatewayAccessLogInterceptor interceptor = new GatewayAccessLogInterceptor(); + assertEquals(Order.LOG.getOrder(), interceptor.order()); + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/log/SpringGatewayAccessLogInfoServerInfoTest.java b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/log/SpringGatewayAccessLogInfoServerInfoTest.java new file mode 100644 index 000000000..5c95c7033 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/log/SpringGatewayAccessLogInfoServerInfoTest.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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 easeagent.plugin.spring353.gateway.interceptor.log; + +import easeagent.plugin.spring353.gateway.TestServerWebExchangeUtils; +import easeagent.plugin.spring353.gateway.interceptor.log.SpringGatewayAccessLogServerInfo; +import easeagent.plugin.spring353.gateway.interceptor.metric.MockRouteBuilder; +import org.junit.Test; +import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; +import org.springframework.mock.http.server.reactive.MockServerHttpRequest; +import org.springframework.mock.web.server.MockServerWebExchange; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Map; + +import static org.junit.Assert.assertEquals; + +public class SpringGatewayAccessLogInfoServerInfoTest { + + @Test + public void load() { + getMethod(); + } + + @Test + public void getMethod() { + SpringGatewayAccessLogServerInfo springGatewayAccessLogServerInfo = new SpringGatewayAccessLogServerInfo(); + springGatewayAccessLogServerInfo.load(TestServerWebExchangeUtils.mockServerWebExchange()); + assertEquals("GET", springGatewayAccessLogServerInfo.getMethod()); + } + + @Test + public void getHeader() { + String key = "testKey"; + String value = "testValue"; + MockServerHttpRequest.BaseBuilder baseBuilder = TestServerWebExchangeUtils.builder().header(key, value); + SpringGatewayAccessLogServerInfo springGatewayAccessLogServerInfo = new SpringGatewayAccessLogServerInfo(); + springGatewayAccessLogServerInfo.load(TestServerWebExchangeUtils.build(baseBuilder)); + assertEquals(value, springGatewayAccessLogServerInfo.getHeader(key)); + } + + @Test + public void getRemoteAddr() { + SpringGatewayAccessLogServerInfo springGatewayAccessLogServerInfo = new SpringGatewayAccessLogServerInfo(); + springGatewayAccessLogServerInfo.load(TestServerWebExchangeUtils.mockServerWebExchange()); + assertEquals("192.168.0.12", springGatewayAccessLogServerInfo.getRemoteAddr()); + } + + @Test + public void getRequestURI() { + SpringGatewayAccessLogServerInfo springGatewayAccessLogServerInfo = new SpringGatewayAccessLogServerInfo(); + springGatewayAccessLogServerInfo.load(TestServerWebExchangeUtils.mockServerWebExchange()); + assertEquals("http://192.168.0.12:8080/test?a=b", springGatewayAccessLogServerInfo.getRequestURI()); + } + + @Test + public void getResponseBufferSize() { + SpringGatewayAccessLogServerInfo springGatewayAccessLogServerInfo = new SpringGatewayAccessLogServerInfo(); + assertEquals(0, springGatewayAccessLogServerInfo.getResponseBufferSize()); + } + + + @Test + public void getMatchURL() throws URISyntaxException { + SpringGatewayAccessLogServerInfo springGatewayAccessLogServerInfo = new SpringGatewayAccessLogServerInfo(); + MockServerWebExchange exchange = TestServerWebExchangeUtils.mockServerWebExchange(); + springGatewayAccessLogServerInfo.load(exchange); + assertEquals("GET http://192.168.0.12:8080/test?a=b", springGatewayAccessLogServerInfo.getMatchURL()); + String url = "http://192.168.0.12:8080/"; + exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR, new MockRouteBuilder().uri(new URI(url)).id("t").build()); + assertEquals("GET " + url, springGatewayAccessLogServerInfo.getMatchURL()); + } + + @Test + public void findHeaders() { + String key = "testKey"; + String value = "testValue"; + MockServerHttpRequest.BaseBuilder baseBuilder = TestServerWebExchangeUtils.builder().header(key, value); + SpringGatewayAccessLogServerInfo springGatewayAccessLogServerInfo = new SpringGatewayAccessLogServerInfo(); + springGatewayAccessLogServerInfo.load(TestServerWebExchangeUtils.build(baseBuilder)); + Map headers = springGatewayAccessLogServerInfo.findHeaders(); + assertEquals(value, headers.get(key)); + } + + @Test + public void findQueries() { + SpringGatewayAccessLogServerInfo springGatewayAccessLogServerInfo = new SpringGatewayAccessLogServerInfo(); + springGatewayAccessLogServerInfo.load(TestServerWebExchangeUtils.mockServerWebExchange()); + Map queries = springGatewayAccessLogServerInfo.findQueries(); + assertEquals(1, queries.size()); + assertEquals("b", queries.get("a")); + } + + @Test + public void getStatusCode() { + SpringGatewayAccessLogServerInfo springGatewayAccessLogServerInfo = new SpringGatewayAccessLogServerInfo(); + springGatewayAccessLogServerInfo.load(TestServerWebExchangeUtils.mockServerWebExchange()); + assertEquals("200", springGatewayAccessLogServerInfo.getStatusCode()); + } + +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/metric/GatewayMetricsInterceptorTest.java b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/metric/GatewayMetricsInterceptorTest.java new file mode 100644 index 000000000..1b450ca3a --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/metric/GatewayMetricsInterceptorTest.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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 easeagent.plugin.spring353.gateway.interceptor.metric; + +import com.megaease.easeagent.mock.plugin.api.MockEaseAgent; +import com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner; +import com.megaease.easeagent.mock.plugin.api.utils.InterceptorTestUtils; +import com.megaease.easeagent.mock.plugin.api.utils.TagVerifier; +import com.megaease.easeagent.mock.report.impl.LastJsonReporter; +import com.megaease.easeagent.plugin.api.Context; +import com.megaease.easeagent.plugin.api.config.ConfigConst; +import com.megaease.easeagent.plugin.api.metric.name.MetricField; +import com.megaease.easeagent.plugin.bridge.EaseAgent; +import com.megaease.easeagent.plugin.enums.Order; +import com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import easeagent.plugin.spring353.gateway.SpringGatewayPlugin; +import easeagent.plugin.spring353.gateway.TestServerWebExchangeUtils; +import easeagent.plugin.spring353.gateway.reactor.AgentMono; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.mock.web.server.MockServerWebExchange; +import org.springframework.web.server.ServerWebExchange; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Map; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@RunWith(EaseAgentJunit4ClassRunner.class) +public class GatewayMetricsInterceptorTest { + private Object startTime = AgentFieldReflectAccessor.getStaticFieldValue(GatewayMetricsInterceptor.class, "START_TIME"); + + @Test + public void init() { + GatewayMetricsInterceptor interceptor = new GatewayMetricsInterceptor(); + InterceptorTestUtils.init(interceptor, new SpringGatewayPlugin()); + assertNotNull(AgentFieldReflectAccessor.getStaticFieldValue(GatewayMetricsInterceptor.class, "SERVER_METRIC")); + } + + @Test + public void before() { + GatewayMetricsInterceptor interceptor = new GatewayMetricsInterceptor(); + Context context = EaseAgent.getContext(); + interceptor.before(null, context); + assertNotNull(context.get(startTime)); + } + + public Map getMetric(LastJsonReporter lastJsonReporter) { + return lastJsonReporter.flushAndOnlyOne(); + } + + @Test + public void after() throws InterruptedException { + GatewayMetricsInterceptor interceptor = new GatewayMetricsInterceptor(); + InterceptorTestUtils.init(interceptor, new SpringGatewayPlugin()); + Context context = EaseAgent.getContext(); + interceptor.before(null, context); + MockServerWebExchange mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange(); + MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build(); + methodInfo.throwable(new RuntimeException("test error")); + interceptor.after(methodInfo, context); + assertNull(methodInfo.getRetValue()); + + TagVerifier tagVerifier = new TagVerifier() + .add("category", "application") + .add("type", "http-request") + .add("url", GatewayMetricsInterceptor.getKey(mockServerWebExchange)); + LastJsonReporter lastJsonReporter = MockEaseAgent.lastMetricJsonReporter(tagVerifier::verifyAnd); + Map metric = getMetric(lastJsonReporter); + assertEquals(1, metric.get(MetricField.EXECUTION_COUNT.getField())); + assertEquals(1, metric.get(MetricField.EXECUTION_ERROR_COUNT.getField())); + + methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build(); + interceptor.before(null, context); + interceptor.after(methodInfo, context); + assertTrue(methodInfo.getRetValue() instanceof AgentMono); + final AgentMono agentMono2 = (AgentMono) methodInfo.getRetValue(); + + Thread thread = new Thread(() -> agentMono2.getFinish().accept(agentMono2.getMethodInfo(), agentMono2.getAsyncContext())); + thread.start(); + thread.join(); + + lastJsonReporter.clean(); + metric = getMetric(lastJsonReporter); + assertEquals(2, metric.get(MetricField.EXECUTION_COUNT.getField())); + assertEquals(1, metric.get(MetricField.EXECUTION_ERROR_COUNT.getField())); + + } + + @Test + public void finishCallback() { + } + + @Test + public void getKey() throws URISyntaxException { + ServerWebExchange webExchange = mock(ServerWebExchange.class); + when(webExchange.getRequest()).thenReturn(mock(ServerHttpRequest.class)); + assertEquals("", GatewayMetricsInterceptor.getKey(webExchange)); + + MockServerWebExchange exchange = TestServerWebExchangeUtils.mockServerWebExchange(); + assertEquals("GET http://192.168.0.12:8080/test?a=b", GatewayMetricsInterceptor.getKey(exchange)); + + String url = "http://loca:8080/test"; + exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR, new MockRouteBuilder().uri(new URI(url)).id("t").build()); + assertEquals("GET " + url, GatewayMetricsInterceptor.getKey(exchange)); + } + + @Test + public void getType() { + GatewayMetricsInterceptor interceptor = new GatewayMetricsInterceptor(); + assertEquals(ConfigConst.PluginID.METRIC, interceptor.getType()); + } + + @Test + public void order() { + GatewayMetricsInterceptor interceptor = new GatewayMetricsInterceptor(); + assertEquals(Order.METRIC.getOrder(), interceptor.order()); + } +} diff --git a/plugins/httpservlet/src/main/java/com/megaease/easeagent/plugin/httpservlet/utils/SpringWebUtils.java b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/metric/MockRouteBuilder.java similarity index 62% rename from plugins/httpservlet/src/main/java/com/megaease/easeagent/plugin/httpservlet/utils/SpringWebUtils.java rename to plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/metric/MockRouteBuilder.java index 3eb9883a5..5b03e97ae 100644 --- a/plugins/httpservlet/src/main/java/com/megaease/easeagent/plugin/httpservlet/utils/SpringWebUtils.java +++ b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/metric/MockRouteBuilder.java @@ -1,12 +1,12 @@ /* - * Copyright (c) 2017, MegaEase + * Copyright (c) 2021, MegaEase * All rights reserved. * * 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 + * 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, @@ -15,11 +15,12 @@ * limitations under the License. */ -package com.megaease.easeagent.plugin.httpservlet.utils; +package easeagent.plugin.spring353.gateway.interceptor.metric; -public class SpringWebUtils { +import org.springframework.cloud.gateway.route.Route; - public static String getBestMatchingPatternAttribute(){ - return org.springframework.web.servlet.HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE; +public class MockRouteBuilder extends Route.Builder { + public MockRouteBuilder() { + predicate = serverWebExchange -> true; } } diff --git a/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/tracing/FluxHttpServerRequestTest.java b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/tracing/FluxHttpServerRequestTest.java new file mode 100644 index 000000000..558e1b238 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/tracing/FluxHttpServerRequestTest.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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 easeagent.plugin.spring353.gateway.interceptor.tracing; + +import com.megaease.easeagent.plugin.api.trace.Span; +import easeagent.plugin.spring353.gateway.TestServerWebExchangeUtils; +import org.junit.Test; + +import java.net.InetSocketAddress; + +import static org.junit.Assert.*; + +public class FluxHttpServerRequestTest { + + @Test + public void kind() { + FluxHttpServerRequest fluxHttpServerRequest = new FluxHttpServerRequest(TestServerWebExchangeUtils.builder().build()); + assertEquals(Span.Kind.SERVER, fluxHttpServerRequest.kind()); + } + + @Test + public void header() { + String key = "testKey"; + String value = "testValue"; + FluxHttpServerRequest fluxHttpServerRequest = new FluxHttpServerRequest(TestServerWebExchangeUtils.builder() + .header(key, value).build()); + assertEquals(value, fluxHttpServerRequest.header(key)); + } + + @Test + public void cacheScope() { + FluxHttpServerRequest fluxHttpServerRequest = new FluxHttpServerRequest(TestServerWebExchangeUtils.builder().build()); + assertFalse(fluxHttpServerRequest.cacheScope()); + } + + @Test + public void setHeader() { + String key = "testKey"; + String value = "testValue"; + FluxHttpServerRequest fluxHttpServerRequest = new FluxHttpServerRequest(TestServerWebExchangeUtils.builder().build()); + fluxHttpServerRequest.setHeader(key, value); + assertNull(fluxHttpServerRequest.header(key)); + } + + @Test + public void method() { + FluxHttpServerRequest fluxHttpServerRequest = new FluxHttpServerRequest(TestServerWebExchangeUtils.builder().build()); + assertEquals("GET", fluxHttpServerRequest.method()); + } + + @Test + public void path() { + FluxHttpServerRequest fluxHttpServerRequest = new FluxHttpServerRequest(TestServerWebExchangeUtils.builder().build()); + assertEquals("/test", fluxHttpServerRequest.path()); + } + + @Test + public void route() { + FluxHttpServerRequest fluxHttpServerRequest = new FluxHttpServerRequest(TestServerWebExchangeUtils.builder().build()); + assertEquals(null, fluxHttpServerRequest.route()); + } + + @Test + public void getRemoteAddr() { + + FluxHttpServerRequest fluxHttpServerRequest = new FluxHttpServerRequest(TestServerWebExchangeUtils.builder() + .remoteAddress(new InetSocketAddress("192.168.0.12", 8080)).build()); + assertEquals("192.168.0.12", fluxHttpServerRequest.getRemoteAddr()); + } + + @Test + public void getRemotePort() { + FluxHttpServerRequest fluxHttpServerRequest = new FluxHttpServerRequest(TestServerWebExchangeUtils.builder() + .remoteAddress(new InetSocketAddress("192.168.0.12", 8080)).build()); + assertEquals(8080, fluxHttpServerRequest.getRemotePort()); + + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/tracing/FluxHttpServerResponseTest.java b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/tracing/FluxHttpServerResponseTest.java new file mode 100644 index 000000000..f25fd59cc --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/tracing/FluxHttpServerResponseTest.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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 easeagent.plugin.spring353.gateway.interceptor.tracing; + +import easeagent.plugin.spring353.gateway.TestServerWebExchangeUtils; +import org.junit.Test; +import org.springframework.mock.web.server.MockServerWebExchange; + +import static org.junit.Assert.*; + +public class FluxHttpServerResponseTest { + + + @Test + public void header() { + MockServerWebExchange mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange(); + String key = "testKey"; + String value = "testValue"; + mockServerWebExchange.getResponse().getHeaders().add(key, value); + FluxHttpServerResponse fluxHttpServerResponse = new FluxHttpServerResponse(mockServerWebExchange, null); + assertEquals(value, fluxHttpServerResponse.header(key)); + + } + + @Test + public void method() { + MockServerWebExchange mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange(); + FluxHttpServerResponse fluxHttpServerResponse = new FluxHttpServerResponse(mockServerWebExchange, null); + assertEquals("GET", fluxHttpServerResponse.method()); + } + + @Test + public void route() { + MockServerWebExchange mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange(); + FluxHttpServerResponse fluxHttpServerResponse = new FluxHttpServerResponse(mockServerWebExchange, null); + assertEquals("/test", fluxHttpServerResponse.route()); + } + + @Test + public void statusCode() { + MockServerWebExchange mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange(); + FluxHttpServerResponse fluxHttpServerResponse = new FluxHttpServerResponse(mockServerWebExchange, null); + assertEquals(200, fluxHttpServerResponse.statusCode()); + + } + + @Test + public void maybeError() { + MockServerWebExchange mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange(); + FluxHttpServerResponse fluxHttpServerResponse = new FluxHttpServerResponse(mockServerWebExchange, null); + assertNull(fluxHttpServerResponse.maybeError()); + RuntimeException err = new RuntimeException(); + fluxHttpServerResponse = new FluxHttpServerResponse(mockServerWebExchange, err); + assertSame(err, fluxHttpServerResponse.maybeError()); + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/tracing/GatewayServerTracingInterceptorTest.java b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/tracing/GatewayServerTracingInterceptorTest.java new file mode 100644 index 000000000..a72033f1a --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/tracing/GatewayServerTracingInterceptorTest.java @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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 easeagent.plugin.spring353.gateway.interceptor.tracing; + +import com.megaease.easeagent.mock.plugin.api.MockEaseAgent; +import com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner; +import com.megaease.easeagent.mock.plugin.api.utils.SpanTestUtils; +import com.megaease.easeagent.plugin.api.Context; +import com.megaease.easeagent.plugin.api.config.ConfigConst; +import com.megaease.easeagent.plugin.api.context.RequestContext; +import com.megaease.easeagent.plugin.bridge.EaseAgent; +import com.megaease.easeagent.plugin.enums.Order; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import com.megaease.easeagent.plugin.report.tracing.ReportSpan; +import easeagent.plugin.spring353.gateway.TestServerWebExchangeUtils; +import easeagent.plugin.spring353.gateway.GatewayCons; +import easeagent.plugin.spring353.gateway.reactor.AgentMono; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.mock.http.server.reactive.MockServerHttpRequest; +import org.springframework.mock.web.server.MockServerWebExchange; +import org.springframework.web.server.ServerWebExchange; + +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BiConsumer; + +import static org.junit.Assert.*; + +@RunWith(EaseAgentJunit4ClassRunner.class) +public class GatewayServerTracingInterceptorTest { + + @Test + public void before() { + GatewayServerTracingInterceptor interceptor = new GatewayServerTracingInterceptor(); + Context context = EaseAgent.getContext(); + MockServerWebExchange mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange(); + MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build(); + interceptor.before(methodInfo, context); + assertNotNull(context.get(GatewayServerTracingInterceptor.SPAN_CONTEXT_KEY)); + assertNotNull(context.get(FluxHttpServerRequest.class)); + RequestContext requestContext = context.remove(GatewayServerTracingInterceptor.SPAN_CONTEXT_KEY); + assertTrue(context.currentTracing().hasCurrentSpan()); + requestContext.scope().close(); + assertFalse(context.currentTracing().hasCurrentSpan()); + context.remove(FluxHttpServerRequest.class); + requestContext.span().abandon(); + + + MockServerHttpRequest.BaseBuilder baseBuilder = TestServerWebExchangeUtils.builder(); + for (Map.Entry entry : requestContext.getHeaders().entrySet()) { + baseBuilder.header(entry.getKey(), entry.getValue()); + } + mockServerWebExchange = TestServerWebExchangeUtils.build(baseBuilder); + methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build(); + interceptor.before(methodInfo, context); + RequestContext requestContext2 = context.remove(GatewayServerTracingInterceptor.SPAN_CONTEXT_KEY); + assertTrue(context.currentTracing().hasCurrentSpan()); + requestContext2.scope().close(); + assertFalse(context.currentTracing().hasCurrentSpan()); + context.remove(FluxHttpServerRequest.class); + requestContext2.span().finish(); + ReportSpan mockSpan = MockEaseAgent.getLastSpan(); + SpanTestUtils.sameId(requestContext.span(), mockSpan); + } + + @Test + public void after() { + GatewayServerTracingInterceptor interceptor = new GatewayServerTracingInterceptor(); + Context context = EaseAgent.getContext(); + MockServerWebExchange mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange(); + MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build(); + interceptor.before(methodInfo, context); + String errorInfo = "test error"; + methodInfo.throwable(new RuntimeException(errorInfo)); + interceptor.after(methodInfo, context); + ReportSpan reportSpan = MockEaseAgent.getLastSpan(); + assertNotNull(reportSpan); + assertTrue(reportSpan.hasError()); + assertEquals(errorInfo, reportSpan.errorInfo()); + assertNull(context.get(GatewayServerTracingInterceptor.SPAN_CONTEXT_KEY)); + assertNull(context.get(FluxHttpServerRequest.class)); + assertNull(methodInfo.getRetValue()); + + + mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange(); + methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build(); + interceptor.before(methodInfo, context); + RequestContext requestContext = context.get(GatewayServerTracingInterceptor.SPAN_CONTEXT_KEY); + interceptor.after(methodInfo, context); + assertNull(context.get(GatewayServerTracingInterceptor.SPAN_CONTEXT_KEY)); + assertNull(context.get(FluxHttpServerRequest.class)); + assertNotNull(methodInfo.getRetValue()); + assertTrue(methodInfo.getRetValue() instanceof AgentMono); + + assertFalse(context.currentTracing().hasCurrentSpan()); + requestContext.span().abandon(); + } + + + @Test + public void finishCallback() throws InterruptedException { + GatewayServerTracingInterceptor interceptor = new GatewayServerTracingInterceptor(); + Context context = EaseAgent.getContext(); + MockServerWebExchange mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange(); + MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build(); + interceptor.before(methodInfo, context); + RequestContext requestContext = context.get(GatewayServerTracingInterceptor.SPAN_CONTEXT_KEY); + interceptor.after(methodInfo, context); + assertNotNull(methodInfo.getRetValue()); + assertTrue(methodInfo.getRetValue() instanceof AgentMono); + assertNull(MockEaseAgent.getLastSpan()); + + AtomicBoolean atomicBoolean = new AtomicBoolean(false); + BiConsumer consumer = (methodInfo1, exchange) -> atomicBoolean.set(true); + mockServerWebExchange.getAttributes().put(GatewayCons.CLIENT_RECEIVE_CALLBACK_KEY, consumer); + AgentMono agentMono = (AgentMono) methodInfo.getRetValue(); + Thread thread = new Thread(() -> agentMono.getFinish().accept(agentMono.getMethodInfo(), agentMono.getAsyncContext())); + thread.start(); + thread.join(); + assertTrue(atomicBoolean.get()); + ReportSpan reportSpan = MockEaseAgent.getLastSpan(); + assertTrue(reportSpan.name().contains("/test")); + SpanTestUtils.sameId(requestContext.span(), reportSpan); + } + + public static RequestContext beforeGatewayServerTracing(MockServerWebExchange mockServerWebExchange) { + GatewayServerTracingInterceptor interceptor = new GatewayServerTracingInterceptor(); + Context context = EaseAgent.getContext(); + MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build(); + interceptor.before(methodInfo, context); + assertNotNull(context.get(GatewayServerTracingInterceptor.SPAN_CONTEXT_KEY)); + return context.get(GatewayServerTracingInterceptor.SPAN_CONTEXT_KEY); + } + + + @Test + public void getType() { + GatewayServerTracingInterceptor interceptor = new GatewayServerTracingInterceptor(); + assertEquals(ConfigConst.PluginID.TRACING, interceptor.getType()); + } + + @Test + public void order() { + GatewayServerTracingInterceptor interceptor = new GatewayServerTracingInterceptor(); + assertEquals(Order.TRACING.getOrder(), interceptor.order()); + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/tracing/HttpHeadersFilterTracingInterceptorTest.java b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/tracing/HttpHeadersFilterTracingInterceptorTest.java new file mode 100644 index 000000000..baa773535 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/interceptor/tracing/HttpHeadersFilterTracingInterceptorTest.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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 easeagent.plugin.spring353.gateway.interceptor.tracing; + +import com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner; +import com.megaease.easeagent.plugin.api.context.RequestContext; +import com.megaease.easeagent.plugin.api.trace.Scope; +import com.megaease.easeagent.plugin.api.trace.Span; +import com.megaease.easeagent.plugin.bridge.EaseAgent; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import easeagent.plugin.spring353.gateway.TestServerWebExchangeUtils; +import easeagent.plugin.spring353.gateway.GatewayCons; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.http.HttpHeaders; +import org.springframework.mock.web.server.MockServerWebExchange; + +import java.util.Collection; + +import static org.junit.Assert.*; + +@RunWith(EaseAgentJunit4ClassRunner.class) +public class HttpHeadersFilterTracingInterceptorTest { + + @Test + public void doAfter() { + HttpHeadersFilterTracingInterceptor interceptor = new HttpHeadersFilterTracingInterceptor(); + + MockServerWebExchange mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange(); + MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{null, mockServerWebExchange}).retValue(new HttpHeaders()).build(); + interceptor.doAfter(methodInfo, EaseAgent.getContext()); + assertNull(mockServerWebExchange.getAttribute(GatewayCons.CHILD_SPAN_KEY)); + + + + mockServerWebExchange = TestServerWebExchangeUtils.mockServerWebExchange(); + methodInfo = MethodInfo.builder().args(new Object[]{null, mockServerWebExchange}).retValue(new HttpHeaders()).build(); + + RequestContext requestContext = GatewayServerTracingInterceptorTest.beforeGatewayServerTracing(mockServerWebExchange); + Span span = requestContext.span(); + try (Scope ignored = requestContext.scope()) { + interceptor.doAfter(methodInfo, EaseAgent.getContext()); + RequestContext clientContext = mockServerWebExchange.getAttribute(GatewayCons.CHILD_SPAN_KEY); + HttpHeaders ret = (HttpHeaders) methodInfo.getRetValue(); + Collection headers = ret.toSingleValueMap().values(); + assertTrue(headers.contains(clientContext.span().traceIdString())); + assertTrue(headers.contains(clientContext.span().spanIdString())); + assertTrue(headers.contains(clientContext.span().parentIdString())); + assertEquals(span.traceIdString(), clientContext.span().traceIdString()); + assertEquals(span.spanIdString(), clientContext.span().parentIdString()); + clientContext.scope().close(); + clientContext.span().abandon(); + } + span.abandon(); + } + + @Test + public void testHeaderFilterRequest() { + HttpHeadersFilterTracingInterceptor.HeaderFilterRequest headerFilterRequest = new HttpHeadersFilterTracingInterceptor.HeaderFilterRequest(null); + assertEquals(Span.Kind.CLIENT, headerFilterRequest.kind()); + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/reactor/AgentCoreSubscriberTest.java b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/reactor/AgentCoreSubscriberTest.java new file mode 100644 index 000000000..d88f1683d --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/reactor/AgentCoreSubscriberTest.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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 easeagent.plugin.spring353.gateway.reactor; + +import com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner; +import com.megaease.easeagent.plugin.api.context.AsyncContext; +import com.megaease.easeagent.plugin.bridge.EaseAgent; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.reactivestreams.Subscription; + +import static org.junit.Assert.*; + +@RunWith(EaseAgentJunit4ClassRunner.class) +public class AgentCoreSubscriberTest { + + @Test + public void currentContext() { + MockCoreSubscriber mockCoreSubscriber = new MockCoreSubscriber(); + AgentCoreSubscriber agentCoreSubscriber = new AgentCoreSubscriber(mockCoreSubscriber, null, null, null); + agentCoreSubscriber.currentContext(); + assertTrue(mockCoreSubscriber.currentContext.get()); + } + + @Test + public void onSubscribe() { + MockCoreSubscriber mockCoreSubscriber = new MockCoreSubscriber(); + AgentCoreSubscriber agentCoreSubscriber = new AgentCoreSubscriber(mockCoreSubscriber, null, null, null); + agentCoreSubscriber.onSubscribe(new Subscription() { + @Override + public void request(long l) { + + } + + @Override + public void cancel() { + + } + }); + assertTrue(mockCoreSubscriber.onSubscribe.get()); + + } + + @Test + public void onNext() { + MockCoreSubscriber mockCoreSubscriber = new MockCoreSubscriber(); + AgentCoreSubscriber agentCoreSubscriber = new AgentCoreSubscriber(mockCoreSubscriber, null, null, null); + agentCoreSubscriber.onNext(null); + assertTrue(mockCoreSubscriber.onNext.get()); + } + + @Test + public void onError() { + MockCoreSubscriber mockCoreSubscriber = new MockCoreSubscriber(); + MethodInfo errorMethodInfo = MethodInfo.builder().build(); + AsyncContext errorAsyncContext = EaseAgent.getContext().exportAsync(); + RuntimeException runtimeException = new RuntimeException(); + AgentCoreSubscriber agentCoreSubscriber = new AgentCoreSubscriber(mockCoreSubscriber, errorMethodInfo, errorAsyncContext, (methodInfo, asyncContext) -> { + assertNotNull(methodInfo); + assertNotNull(asyncContext); + assertSame(errorMethodInfo, methodInfo); + assertSame(errorAsyncContext, asyncContext); + assertFalse(methodInfo.isSuccess()); + assertSame(runtimeException, methodInfo.getThrowable()); + }); + agentCoreSubscriber.onError(runtimeException); + assertTrue(mockCoreSubscriber.onError.get()); + } + + @Test + public void onComplete() { + MockCoreSubscriber mockCoreSubscriber = new MockCoreSubscriber(); + MethodInfo completeMethodInfo = MethodInfo.builder().build(); + AsyncContext completeAsyncContext = EaseAgent.getContext().exportAsync(); + AgentCoreSubscriber agentCoreSubscriber = new AgentCoreSubscriber(mockCoreSubscriber, completeMethodInfo, completeAsyncContext, (methodInfo, asyncContext) -> { + assertNotNull(methodInfo); + assertNotNull(asyncContext); + assertSame(completeMethodInfo, methodInfo); + assertSame(completeAsyncContext, asyncContext); + assertTrue(methodInfo.isSuccess()); + }); + agentCoreSubscriber.onComplete(); + assertTrue(mockCoreSubscriber.onComplete.get()); + + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/reactor/AgentMonoTest.java b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/reactor/AgentMonoTest.java new file mode 100644 index 000000000..2f5e6be64 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/reactor/AgentMonoTest.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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 easeagent.plugin.spring353.gateway.reactor; + +import com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner; +import com.megaease.easeagent.plugin.api.Cleaner; +import com.megaease.easeagent.plugin.api.Context; +import com.megaease.easeagent.plugin.api.context.AsyncContext; +import com.megaease.easeagent.plugin.api.trace.Scope; +import com.megaease.easeagent.plugin.api.trace.Span; +import com.megaease.easeagent.plugin.bridge.EaseAgent; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import org.junit.Test; +import org.junit.runner.RunWith; +import reactor.core.CoreSubscriber; +import reactor.core.publisher.Mono; + +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +@RunWith(EaseAgentJunit4ClassRunner.class) +public class AgentMonoTest { + + @Test + public void subscribe() { + AtomicBoolean ran = new AtomicBoolean(false); + Mono mono = new Mono() { + @Override + public void subscribe(CoreSubscriber coreSubscriber) { + ran.set(true); + } + }; + + MethodInfo methodInfo = MethodInfo.builder().build(); + AgentMono agentMono = new AgentMono(mono, methodInfo, EaseAgent.getContext().exportAsync(), null); + agentMono.subscribe(new MockCoreSubscriber()); + assertTrue(ran.get()); + } + + @Test + public void testImportToCurrent() throws InterruptedException { + Context context = EaseAgent.getContext(); + Span span = context.nextSpan(); + Thread thread; + try (Scope ignored4 = span.maybeScope()) { + AsyncContext asyncContext1 = context.exportAsync(); + AsyncContext asyncContext2 = context.exportAsync(); + AsyncContext asyncContext3 = context.exportAsync(); + thread = new Thread(() -> { + Context asyncContext = EaseAgent.getContext(); + assertFalse(asyncContext.currentTracing().hasCurrentSpan()); + try (Cleaner ignored = asyncContext1.importToCurrent()) { + assertTrue(asyncContext.currentTracing().hasCurrentSpan()); + try (Cleaner ignored1 = asyncContext2.importToCurrent()) { + assertTrue(asyncContext.currentTracing().hasCurrentSpan()); + try (Cleaner ignored2 = asyncContext3.importToCurrent()) { + assertTrue(asyncContext.currentTracing().hasCurrentSpan()); + } + assertTrue(asyncContext.currentTracing().hasCurrentSpan()); + } + assertTrue(asyncContext.currentTracing().hasCurrentSpan()); + } + assertFalse(asyncContext.currentTracing().hasCurrentSpan()); + }); + } + thread.start(); + thread.join(); + span.finish(); + } + +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/reactor/MockCoreSubscriber.java b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/reactor/MockCoreSubscriber.java new file mode 100644 index 000000000..95cbef7fd --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/java/easeagent/plugin/spring353/gateway/reactor/MockCoreSubscriber.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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 easeagent.plugin.spring353.gateway.reactor; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.util.context.Context; + +import java.util.Collections; +import java.util.concurrent.atomic.AtomicBoolean; + +public class MockCoreSubscriber implements CoreSubscriber { + AtomicBoolean currentContext = new AtomicBoolean(false); + AtomicBoolean onSubscribe = new AtomicBoolean(false); + AtomicBoolean onNext = new AtomicBoolean(false); + AtomicBoolean onError = new AtomicBoolean(false); + AtomicBoolean onComplete = new AtomicBoolean(false); + + + @Override + public Context currentContext() { + currentContext.set(true); + return Context.of(Collections.emptyMap()); + } + + @Override + public void onSubscribe(Subscription subscription) { + onSubscribe.set(true); + } + + @Override + public void onNext(Void aVoid) { + onNext.set(true); + } + + @Override + public void onError(Throwable throwable) { + onError.set(true); + } + + @Override + public void onComplete() { + onComplete.set(true); + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/resources/mock_agent.properties b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/resources/mock_agent.properties new file mode 100644 index 000000000..545c4a1bf --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-gateway-3.5.3/src/test/resources/mock_agent.properties @@ -0,0 +1,5 @@ +name=test-gateway-service +system=test-gateway-system +plugin.observability.springGateway.metric.interval=200 +plugin.observability.springGateway.metric.intervalUnit=MILLISECONDS +easeagent.progress.forwarded.headers=X-Forwarded-For diff --git a/plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/pom.xml b/plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/pom.xml new file mode 100644 index 000000000..9bfe59f3e --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/pom.xml @@ -0,0 +1,59 @@ + + + + + + spring-boot-3.5.3 + com.megaease.easeagent + 2.3.0 + + 4.0.0 + + spring-boot-rest-template-3.5.3 + + + + com.megaease.easeagent + plugin-api + provided + + + + org.springframework.boot + spring-boot-starter-web + provided + 3.5.3 + + + org.springframework.boot + spring-boot-starter-logging + + + + + + com.megaease.easeagent + plugin-api-mock + ${project.version} + test + + + + diff --git a/plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/src/main/java/com/megaease/easeagent/plugin/rest/template/ForwardedPlugin.java b/plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/src/main/java/com/megaease/easeagent/plugin/rest/template/ForwardedPlugin.java new file mode 100644 index 000000000..b458de490 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/src/main/java/com/megaease/easeagent/plugin/rest/template/ForwardedPlugin.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.rest.template; + +import com.megaease.easeagent.plugin.AgentPlugin; +import com.megaease.easeagent.plugin.api.config.ConfigConst; + +public class ForwardedPlugin implements AgentPlugin { + @Override + public String getNamespace() { + return ConfigConst.Namespace.FORWARDED; + } + + @Override + public String getDomain() { + return ConfigConst.INTEGRABILITY; + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/src/main/java/com/megaease/easeagent/plugin/rest/template/RestTemplatePlugin.java b/plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/src/main/java/com/megaease/easeagent/plugin/rest/template/RestTemplatePlugin.java new file mode 100644 index 000000000..33434e6f3 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/src/main/java/com/megaease/easeagent/plugin/rest/template/RestTemplatePlugin.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.rest.template; + +import com.megaease.easeagent.plugin.AgentPlugin; +import com.megaease.easeagent.plugin.api.config.ConfigConst; + +public class RestTemplatePlugin implements AgentPlugin { + @Override + public String getNamespace() { + return ConfigConst.Namespace.REST_TEMPLATE; + } + + @Override + public String getDomain() { + return ConfigConst.OBSERVABILITY; + } + +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/src/main/java/com/megaease/easeagent/plugin/rest/template/advice/ClientHttpRequestAdvice.java b/plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/src/main/java/com/megaease/easeagent/plugin/rest/template/advice/ClientHttpRequestAdvice.java new file mode 100644 index 000000000..bdbd1fae8 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/src/main/java/com/megaease/easeagent/plugin/rest/template/advice/ClientHttpRequestAdvice.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2017, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.rest.template.advice; + +import com.megaease.easeagent.plugin.CodeVersion; +import com.megaease.easeagent.plugin.Points; +import com.megaease.easeagent.plugin.api.config.ConfigConst; +import com.megaease.easeagent.plugin.matcher.ClassMatcher; +import com.megaease.easeagent.plugin.matcher.IClassMatcher; +import com.megaease.easeagent.plugin.matcher.IMethodMatcher; +import com.megaease.easeagent.plugin.matcher.MethodMatcher; + +import java.util.Set; + +public class ClientHttpRequestAdvice implements Points { + + private final static CodeVersion VERSIONS = CodeVersion.builder() + .key(ConfigConst.CodeVersion.KEY_SPRING_BOOT) + .add(ConfigConst.CodeVersion.VERSION_SPRING_BOOT3).build(); + + @Override + public CodeVersion codeVersions() { + return VERSIONS; + } + + @Override + public IClassMatcher getClassMatcher() { + return ClassMatcher.builder().hasInterface("org.springframework.http.client.ClientHttpRequest").notInterface() + .build(); + } + + @Override + public Set getMethodMatcher() { + return MethodMatcher.multiBuilder() + .match(MethodMatcher.builder().named("execute") + .returnType("org.springframework.http.client.ClientHttpResponse") + .qualifier("default") + .build()) + .build(); + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/src/main/java/com/megaease/easeagent/plugin/rest/template/interceptor/forwarded/RestTemplateForwardedInterceptor.java b/plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/src/main/java/com/megaease/easeagent/plugin/rest/template/interceptor/forwarded/RestTemplateForwardedInterceptor.java new file mode 100644 index 000000000..c2b8b3052 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/src/main/java/com/megaease/easeagent/plugin/rest/template/interceptor/forwarded/RestTemplateForwardedInterceptor.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.rest.template.interceptor.forwarded; + +import com.megaease.easeagent.plugin.annotation.AdviceTo; +import com.megaease.easeagent.plugin.api.Context; +import com.megaease.easeagent.plugin.api.config.ConfigConst; +import com.megaease.easeagent.plugin.enums.Order; +import com.megaease.easeagent.plugin.interceptor.Interceptor; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import com.megaease.easeagent.plugin.rest.template.ForwardedPlugin; +import com.megaease.easeagent.plugin.rest.template.advice.ClientHttpRequestAdvice; +import org.springframework.http.HttpHeaders; +import org.springframework.http.client.ClientHttpRequest; + +@AdviceTo(value = ClientHttpRequestAdvice.class, plugin = ForwardedPlugin.class) +public class RestTemplateForwardedInterceptor implements Interceptor { + @Override + public void before(MethodInfo methodInfo, Context context) { + ClientHttpRequest clientHttpRequest = (ClientHttpRequest) methodInfo.getInvoker(); + HttpHeaders httpHeaders = clientHttpRequest.getHeaders(); + context.injectForwardedHeaders(httpHeaders::add); + } + + + @Override + public String getType() { + return ConfigConst.PluginID.FORWARDED; + } + + @Override + public int order() { + return Order.FORWARDED.getOrder(); + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/src/main/java/com/megaease/easeagent/plugin/rest/template/interceptor/tracing/ClientHttpRequestInterceptor.java b/plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/src/main/java/com/megaease/easeagent/plugin/rest/template/interceptor/tracing/ClientHttpRequestInterceptor.java new file mode 100644 index 000000000..93b675e83 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/src/main/java/com/megaease/easeagent/plugin/rest/template/interceptor/tracing/ClientHttpRequestInterceptor.java @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2017, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.rest.template.interceptor.tracing; + +import com.megaease.easeagent.plugin.annotation.AdviceTo; +import com.megaease.easeagent.plugin.api.Context; +import com.megaease.easeagent.plugin.api.trace.Span; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import com.megaease.easeagent.plugin.rest.template.RestTemplatePlugin; +import com.megaease.easeagent.plugin.rest.template.advice.ClientHttpRequestAdvice; +import com.megaease.easeagent.plugin.tools.trace.BaseHttpClientTracingInterceptor; +import com.megaease.easeagent.plugin.tools.trace.HttpRequest; +import com.megaease.easeagent.plugin.tools.trace.HttpResponse; +import lombok.SneakyThrows; +import org.springframework.http.HttpHeaders; +import org.springframework.http.client.ClientHttpRequest; +import org.springframework.http.client.ClientHttpResponse; + +import java.util.List; + +@AdviceTo(value = ClientHttpRequestAdvice.class, plugin = RestTemplatePlugin.class) +public class ClientHttpRequestInterceptor extends BaseHttpClientTracingInterceptor { + private static final Object PROGRESS_CONTEXT = new Object(); + + @Override + public Object getProgressKey() { + return PROGRESS_CONTEXT; + } + + @Override + protected HttpRequest getRequest(MethodInfo methodInfo, Context context) { + ClientHttpRequest clientHttpRequest = (ClientHttpRequest) methodInfo.getInvoker(); + return new ClientRequestWrapper(clientHttpRequest); + } + + @Override + protected HttpResponse getResponse(MethodInfo methodInfo, Context context) { + ClientHttpRequest clientHttpRequest = (ClientHttpRequest) methodInfo.getInvoker(); + ClientHttpResponse response = (ClientHttpResponse) methodInfo.getRetValue(); + return new ClientResponseWrapper(methodInfo.getThrowable(), clientHttpRequest, response); + } + + private static String getFirstHeaderValue(HttpHeaders headers, String name) { + List values = headers.get(name); + if (values == null || values.isEmpty()) { + return null; + } + return values.get(0); + } + + static class ClientRequestWrapper implements HttpRequest { + + private final ClientHttpRequest request; + + public ClientRequestWrapper(ClientHttpRequest request) { + this.request = request; + } + + @Override + public Span.Kind kind() { + return Span.Kind.CLIENT; + } + + + @Override + public String method() { + return request.getMethod().name(); + } + + @Override + public String path() { + return request.getURI().getPath(); + } + + @Override + public String route() { + return null; + } + + @Override + public String getRemoteAddr() { + return request.getURI().getHost(); + } + + @Override + public int getRemotePort() { + return request.getURI().getPort(); + } + + @Override + public String header(String name) { + return getFirstHeaderValue(request.getHeaders(), name); + } + + + @Override + public boolean cacheScope() { + return false; + } + + @Override + public void setHeader(String name, String value) { + request.getHeaders().add(name, value); + } + } + + static class ClientResponseWrapper implements HttpResponse { + private final Throwable caught; + private final ClientHttpRequest request; + private final ClientHttpResponse response; + + public ClientResponseWrapper(Throwable caught, ClientHttpRequest request, ClientHttpResponse response) { + this.caught = caught; + this.request = request; + this.response = response; + } + + @Override + public String method() { + return request.getMethod().name(); + } + + @Override + public String route() { + return null; + } + + @SneakyThrows + @Override + public int statusCode() { + if (response == null) { + return 500; + } + return response.getStatusCode().value(); + } + + @Override + public Throwable maybeError() { + return caught; + } + + @Override + public String header(String name) { + if (response == null) { + return ""; + } + return getFirstHeaderValue(response.getHeaders(), name); + } + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/src/test/java/com/megaease/easeagent/plugin/rest/template/interceptor/TestConst.java b/plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/src/test/java/com/megaease/easeagent/plugin/rest/template/interceptor/TestConst.java new file mode 100644 index 000000000..ea6baf93d --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/src/test/java/com/megaease/easeagent/plugin/rest/template/interceptor/TestConst.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.rest.template.interceptor; + +public class TestConst { + public static final String FORWARDED_NAME = "X-Forwarded-For"; + public static final String FORWARDED_VALUE = "testForwarded"; + public static final String RESPONSE_TAG_NAME = "X-EG-Test"; + public static final String RESPONSE_TAG_VALUE = "X-EG-Test-Value"; +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/src/test/java/com/megaease/easeagent/plugin/rest/template/interceptor/forwarded/RestTemplateForwardedInterceptorTest.java b/plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/src/test/java/com/megaease/easeagent/plugin/rest/template/interceptor/forwarded/RestTemplateForwardedInterceptorTest.java new file mode 100644 index 000000000..884e8827f --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/src/test/java/com/megaease/easeagent/plugin/rest/template/interceptor/forwarded/RestTemplateForwardedInterceptorTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.rest.template.interceptor.forwarded; + +import com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner; +import com.megaease.easeagent.plugin.api.Context; +import com.megaease.easeagent.plugin.api.config.ConfigConst; +import com.megaease.easeagent.plugin.bridge.EaseAgent; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import com.megaease.easeagent.plugin.rest.template.interceptor.TestConst; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.http.HttpMethod; +import org.springframework.http.client.ClientHttpRequest; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.http.client.SimpleClientHttpRequestFactory; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; + +import static org.junit.Assert.*; + +@RunWith(EaseAgentJunit4ClassRunner.class) +public class RestTemplateForwardedInterceptorTest { + + @Test + public void before() throws URISyntaxException, IOException { + ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); + ClientHttpRequest request = requestFactory.createRequest(new URI("http://127.0.0.1:8080/test"), HttpMethod.GET); + RestTemplateForwardedInterceptor restTemplateForwardedInterceptor = new RestTemplateForwardedInterceptor(); + + MethodInfo methodInfo = MethodInfo.builder().invoker(request).build(); + Context context = EaseAgent.getContext(); + restTemplateForwardedInterceptor.before(methodInfo, context); + assertNull(request.getHeaders().getFirst(TestConst.FORWARDED_NAME)); + context.put(TestConst.FORWARDED_NAME, TestConst.FORWARDED_VALUE); + try { + restTemplateForwardedInterceptor.before(methodInfo, context); + assertNotNull(request.getHeaders().get(TestConst.FORWARDED_NAME)); + assertEquals(TestConst.FORWARDED_VALUE, request.getHeaders().getFirst(TestConst.FORWARDED_NAME)); + } finally { + context.remove(TestConst.FORWARDED_NAME); + } + + + } + + @Test + public void getType() { + RestTemplateForwardedInterceptor restTemplateForwardedInterceptor = new RestTemplateForwardedInterceptor(); + assertEquals(ConfigConst.PluginID.FORWARDED, restTemplateForwardedInterceptor.getType()); + + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/src/test/java/com/megaease/easeagent/plugin/rest/template/interceptor/tracing/ClientHttpRequestInterceptorTest.java b/plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/src/test/java/com/megaease/easeagent/plugin/rest/template/interceptor/tracing/ClientHttpRequestInterceptorTest.java new file mode 100644 index 000000000..8e8284adf --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/src/test/java/com/megaease/easeagent/plugin/rest/template/interceptor/tracing/ClientHttpRequestInterceptorTest.java @@ -0,0 +1,122 @@ +package com.megaease.easeagent.plugin.rest.template.interceptor.tracing; + +import com.megaease.easeagent.mock.plugin.api.MockEaseAgent; +import com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner; +import com.megaease.easeagent.plugin.api.Context; +import com.megaease.easeagent.plugin.api.trace.Scope; +import com.megaease.easeagent.plugin.api.trace.Span; +import com.megaease.easeagent.plugin.bridge.EaseAgent; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import com.megaease.easeagent.plugin.report.tracing.ReportSpan; +import com.megaease.easeagent.plugin.rest.template.interceptor.TestConst; +import com.megaease.easeagent.plugin.tools.trace.HttpRequest; +import com.megaease.easeagent.plugin.tools.trace.HttpResponse; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.http.HttpMethod; +import org.springframework.http.client.*; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; + +import static org.junit.Assert.*; + + + +@RunWith(EaseAgentJunit4ClassRunner.class) +public class ClientHttpRequestInterceptorTest { + + + @Test + public void testRestTemplate() throws URISyntaxException, IOException { + String url = "http://127.0.0.1:8080/test"; + + ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); + ClientHttpRequest request = requestFactory.createRequest(new URI(url), HttpMethod.GET); + ClientHttpResponse clientHttpResponse = SimpleClientHttpResponseFactory.createMockResponse(url); + MethodInfo.MethodInfoBuilder methodInfoBuilder = MethodInfo.builder(); + MethodInfo methodInfo = methodInfoBuilder.invoker(request).build(); + Context context = EaseAgent.getContext(); + ClientHttpRequestInterceptor clientHttpRequestInterceptor = new ClientHttpRequestInterceptor(); + MockEaseAgent.cleanLastSpan(); + + clientHttpRequestInterceptor.before(methodInfo, context); + methodInfo = methodInfoBuilder.retValue(clientHttpResponse).build(); + clientHttpRequestInterceptor.after(methodInfo, context); + + ReportSpan mockSpan = MockEaseAgent.getLastSpan(); + assertNotNull(mockSpan); + assertEquals(Span.Kind.CLIENT.name(), mockSpan.kind()); + assertEquals(TestConst.RESPONSE_TAG_VALUE, mockSpan.tag(TestConst.RESPONSE_TAG_NAME)); + assertNull(mockSpan.parentId()); + + request = requestFactory.createRequest(new URI(url), HttpMethod.GET); + methodInfo = methodInfoBuilder.invoker(request).build(); + clientHttpRequestInterceptor.before(methodInfo, context); + methodInfo = methodInfoBuilder.retValue(clientHttpResponse).build(); + RuntimeException runtimeException = new RuntimeException("test error"); + methodInfo.throwable(runtimeException); + clientHttpRequestInterceptor.after(methodInfo, context); + + mockSpan = MockEaseAgent.getLastSpan(); + assertNotNull(mockSpan); + assertEquals(Span.Kind.CLIENT.name(), mockSpan.kind()); + assertEquals(TestConst.RESPONSE_TAG_VALUE, mockSpan.tag(TestConst.RESPONSE_TAG_NAME)); + assertNull(mockSpan.parentId()); + + + request = requestFactory.createRequest(new URI(url), HttpMethod.GET); + methodInfo = methodInfoBuilder.invoker(request).build(); + Span span = context.nextSpan(); + try (Scope ignored = span.maybeScope()) { + clientHttpRequestInterceptor.before(methodInfo, context); + methodInfo = methodInfoBuilder.retValue(clientHttpResponse).build(); + clientHttpRequestInterceptor.after(methodInfo, context); + mockSpan = MockEaseAgent.getLastSpan(); + assertEquals(span.traceIdString(), mockSpan.traceId()); + assertEquals(span.spanIdString(), mockSpan.parentId()); + assertNotNull(mockSpan.id()); + } + span.finish(); + } + + @Test + public void getRequest() throws URISyntaxException, IOException { + ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); + ClientHttpRequest request = requestFactory.createRequest(new URI("http://127.0.0.1:8080/test"), HttpMethod.GET); + MethodInfo methodInfo = MethodInfo.builder().invoker(request).build(); + Context context = EaseAgent.getContext(); + + ClientHttpRequestInterceptor clientHttpRequestInterceptor = new ClientHttpRequestInterceptor(); + HttpRequest httpRequest = clientHttpRequestInterceptor.getRequest(methodInfo, context); + assertEquals(com.megaease.easeagent.plugin.api.trace.Span.Kind.CLIENT, httpRequest.kind()); + assertEquals("GET", httpRequest.method()); + assertEquals("/test", httpRequest.path()); + } + + @Test + public void getResponse() throws IOException, URISyntaxException { + String url = "http://127.0.0.1:8080/test"; + ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); + ClientHttpRequest request = requestFactory.createRequest(new URI(url), HttpMethod.GET); + ClientHttpResponse clientHttpResponse = SimpleClientHttpResponseFactory.createMockResponse(url); + MethodInfo.MethodInfoBuilder methodInfoBuilder = MethodInfo.builder().invoker(request).retValue(clientHttpResponse); + MethodInfo methodInfo = methodInfoBuilder.build(); + + ClientHttpRequestInterceptor clientHttpRequestInterceptor = new ClientHttpRequestInterceptor(); + HttpResponse httpResponse = clientHttpRequestInterceptor.getResponse(methodInfo, EaseAgent.getContext()); + assertEquals("GET", httpResponse.method()); + assertNull(httpResponse.route()); + assertNull(httpResponse.maybeError()); + assertEquals(200, httpResponse.statusCode()); + + RuntimeException runtimeException = new RuntimeException("test error"); + methodInfoBuilder.throwable(runtimeException); + httpResponse = clientHttpRequestInterceptor.getResponse(methodInfoBuilder.build(), EaseAgent.getContext()); + assertEquals(runtimeException, httpResponse.maybeError()); + + + } + +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/src/test/java/org/springframework/http/client/SimpleClientHttpResponseFactory.java b/plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/src/test/java/org/springframework/http/client/SimpleClientHttpResponseFactory.java new file mode 100644 index 000000000..b04163f51 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/src/test/java/org/springframework/http/client/SimpleClientHttpResponseFactory.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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 org.springframework.http.client; + + +import com.megaease.easeagent.plugin.rest.template.interceptor.TestConst; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; + +public class SimpleClientHttpResponseFactory { + public static ClientHttpResponse createMockResponse(String urlStr) throws IOException { + URL url = new URL(urlStr); + HttpURLConnection httpURLConnection = new HttpURLConnection(url) { + @Override + public void disconnect() { + + } + + @Override + public boolean usingProxy() { + return false; + } + + @Override + public void connect() throws IOException { + + } + + @Override + public int getResponseCode() throws IOException { + return 200; + } + + @Override + public String getHeaderFieldKey(int n) { + if (n == 0) { + return TestConst.RESPONSE_TAG_NAME; + } + return super.getHeaderFieldKey(n); + } + + @Override + public String getHeaderField(int n) { + if (n == 0) { + return TestConst.RESPONSE_TAG_VALUE; + } + return super.getHeaderField(n); + } + }; + return new SimpleClientHttpResponse(httpURLConnection); + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/src/test/resources/mock_agent.properties b/plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/src/test/resources/mock_agent.properties new file mode 100644 index 000000000..4fe7262b9 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-rest-template-3.5.3/src/test/resources/mock_agent.properties @@ -0,0 +1,3 @@ +plugin.integrability.global.forwarded.enabled=true +easeagent.progress.forwarded.headers=X-Forwarded-For +observability.tracings.tag.response.headers.eg.0=X-EG-Test diff --git a/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/pom.xml b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/pom.xml new file mode 100644 index 000000000..735b985ad --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/pom.xml @@ -0,0 +1,109 @@ + + + + + + spring-boot-3.5.3 + com.megaease.easeagent + 2.3.0 + + 4.0.0 + + spring-boot-servicename-3.5.3 + + + + com.megaease.easeagent + plugin-api + provided + + + + + org.springframework.boot + spring-boot-starter-web + provided + 3.5.3 + + + org.springframework.boot + spring-boot-starter-logging + + + + + + org.springframework.cloud + spring-cloud-starter-gateway + + + org.springframework.boot + spring-boot-starter-logging + + + provided + + + + org.springframework.cloud + spring-cloud-starter-openfeign + provided + + + org.springframework.boot + spring-boot-starter-logging + + + + + + com.megaease.easeagent + plugin-api-mock + ${project.version} + test + + + + org.springframework.boot + spring-boot-starter-test + 3.5.3 + + + org.springframework.boot + spring-boot-starter-logging + + + test + + + com.fasterxml.jackson.core + jackson-core + 2.19.1 + test + + + com.fasterxml.jackson.core + jackson-databind + 2.19.1 + test + + + + diff --git a/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/Const.java b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/Const.java new file mode 100644 index 000000000..9713f3e93 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/Const.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2017, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.servicename.springboot353; + +import com.megaease.easeagent.plugin.CodeVersion; +import com.megaease.easeagent.plugin.Points; +import com.megaease.easeagent.plugin.api.config.ConfigConst; + +public interface Const { + String FeignBlockingLoadBalancerClient = "org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient"; + String RetryableFeignBlockingLoadBalancerClient = "org.springframework.cloud.openfeign.loadbalancer.RetryableFeignBlockingLoadBalancerClient"; + //----------------- FeignClient end --------------- + + //----------------- RestTemplate begin --------------- + String RetryLoadBalancerInterceptor = "org.springframework.cloud.client.loadbalancer.RetryLoadBalancerInterceptor"; + + String LoadBalancerInterceptor = "org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor"; + //----------------- RestTemplate end --------------- + + //----------------- web client begin --------------- + String ReactorLoadBalancerExchangeFilterFunction = "org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerExchangeFilterFunction"; + + String RetryableLoadBalancerExchangeFilterFunction = "org.springframework.cloud.client.loadbalancer.reactive.RetryableLoadBalancerExchangeFilterFunction"; + //----------------- web client end --------------- + + //----------------- spring gateway begin --------------- + String FilteringWebHandler = "org.springframework.cloud.gateway.handler.FilteringWebHandler"; + //----------------- spring gateway end --------------- + String DEFAULT_PROPAGATE_HEAD = "X-Mesh-RPC-Service"; + String PROPAGATE_HEAD_CONFIG = "propagate.head"; + + CodeVersion VERSIONS = CodeVersion.builder() + .key(ConfigConst.CodeVersion.KEY_SPRING_BOOT) + .add(Points.DEFAULT_VERSION) + .add(ConfigConst.CodeVersion.VERSION_SPRING_BOOT3).build(); +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/ReflectionTool.java b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/ReflectionTool.java new file mode 100644 index 000000000..25514700a --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/ReflectionTool.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2017, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.servicename.springboot353; + +import org.springframework.util.ReflectionUtils; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class ReflectionTool { + public static Object invokeMethod(Object own, String method, Object... args) throws ReflectiveOperationException { + Class[] types = new Class[args.length]; + for (int i = 0; i < args.length; i++) { + types[i] = args[i].getClass(); + } + Method md = ReflectionUtils.findMethod(own.getClass(), method, types); + ReflectionUtils.makeAccessible(md); + return ReflectionUtils.invokeMethod(md, own, args); + } + + public static Object extractField(Object own, String field) throws ReflectiveOperationException { + Field fd = ReflectionUtils.findField(own.getClass(), field); + ReflectionUtils.makeAccessible(fd); + return ReflectionUtils.getField(fd, own); + } + + + public static boolean hasText(String val) { + return val != null && val.trim().length() > 0; + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/ServiceNamePlugin.java b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/ServiceNamePlugin.java new file mode 100644 index 000000000..cafb6eed8 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/ServiceNamePlugin.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2017, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.servicename.springboot353; + +import com.megaease.easeagent.plugin.AgentPlugin; +import com.megaease.easeagent.plugin.api.config.ConfigConst; + +public class ServiceNamePlugin implements AgentPlugin { + @Override + public String getNamespace() { + return ConfigConst.Namespace.SERVICE_NAME; + } + + @Override + public String getDomain() { + return ConfigConst.INTEGRABILITY; + + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/ServiceNamePluginConfig.java b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/ServiceNamePluginConfig.java new file mode 100644 index 000000000..e3ba0f15d --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/ServiceNamePluginConfig.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2017, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.servicename.springboot353; + +import com.megaease.easeagent.plugin.api.config.AutoRefreshConfigSupplier; +import com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfig; +import com.megaease.easeagent.plugin.api.config.IPluginConfig; +import com.megaease.easeagent.plugin.utils.common.StringUtils; + +import static com.megaease.easeagent.plugin.servicename.springboot353.Const.DEFAULT_PROPAGATE_HEAD; +import static com.megaease.easeagent.plugin.servicename.springboot353.Const.PROPAGATE_HEAD_CONFIG; + +public class ServiceNamePluginConfig implements AutoRefreshPluginConfig { + public static final AutoRefreshConfigSupplier SUPPLIER = new AutoRefreshConfigSupplier() { + @Override + public ServiceNamePluginConfig newInstance() { + return new ServiceNamePluginConfig(); + } + }; + + private volatile String propagateHead = DEFAULT_PROPAGATE_HEAD; + + public String getPropagateHead() { + return propagateHead; + } + + @Override + public void onChange(IPluginConfig oldConfig, IPluginConfig newConfig) { + String propagateHead = newConfig.getString(PROPAGATE_HEAD_CONFIG); + if (StringUtils.isEmpty(propagateHead) || StringUtils.isEmpty(propagateHead.trim())) { + return; + } + this.propagateHead = propagateHead.trim(); + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/advice/FeignClientLoadBalancerClientAdvice.java b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/advice/FeignClientLoadBalancerClientAdvice.java new file mode 100644 index 000000000..5607a6230 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/advice/FeignClientLoadBalancerClientAdvice.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2017, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.servicename.springboot353.advice; + +import com.megaease.easeagent.plugin.CodeVersion; +import com.megaease.easeagent.plugin.Points; +import com.megaease.easeagent.plugin.matcher.IClassMatcher; +import com.megaease.easeagent.plugin.matcher.IMethodMatcher; +import com.megaease.easeagent.plugin.matcher.MethodMatcher; +import com.megaease.easeagent.plugin.servicename.springboot353.Const; + +import java.util.Set; + +import static com.megaease.easeagent.plugin.tools.matcher.ClassMatcherUtils.name; + +// OpenFeign +public class FeignClientLoadBalancerClientAdvice implements Points { + @Override + public CodeVersion codeVersions() { + return Const.VERSIONS; + } + + //.type(named(FeignBlockingLoadBalancerClient)) + // .transform(feignBlockingLoadBalancerClientExecute(named("execute").and(takesArguments(2)) + // .and(takesArgument(0, named("feign.Request"))) + // )) + @Override + public IClassMatcher getClassMatcher() { + return name(Const.FeignBlockingLoadBalancerClient).or(name(Const.RetryableFeignBlockingLoadBalancerClient)); + } + + @Override + public Set getMethodMatcher() { + return MethodMatcher.multiBuilder() + .match(MethodMatcher.builder().named("execute") + .argsLength(2) + .arg(0, "feign.Request") + .qualifier("servicename") + .build()) + .build(); + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/advice/FilteringWebHandlerAdvice.java b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/advice/FilteringWebHandlerAdvice.java new file mode 100644 index 000000000..d921b4cf1 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/advice/FilteringWebHandlerAdvice.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2017, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.servicename.springboot353.advice; + +import com.megaease.easeagent.plugin.CodeVersion; +import com.megaease.easeagent.plugin.Points; +import com.megaease.easeagent.plugin.matcher.IClassMatcher; +import com.megaease.easeagent.plugin.matcher.IMethodMatcher; +import com.megaease.easeagent.plugin.matcher.MethodMatcher; +import com.megaease.easeagent.plugin.servicename.springboot353.Const; + +import java.util.Set; + +import static com.megaease.easeagent.plugin.tools.matcher.ClassMatcherUtils.name; + +// Spring Cloud Gateway +public class FilteringWebHandlerAdvice implements Points {//FilteringWebHandlerInterceptor + // .type(named(FilteringWebHandler)) + // .transform(filteringWebHandlerHandle(named("handle").and(takesArguments(1)) + // .and(takesArgument(0, named("org.springframework.web.server.ServerWebExchange"))) + // )) + + @Override + public CodeVersion codeVersions() { + return Const.VERSIONS; + } + + + @Override + public IClassMatcher getClassMatcher() { + return name(Const.FilteringWebHandler); + } + + @Override + public Set getMethodMatcher() { + return MethodMatcher.multiBuilder() + .match(MethodMatcher.builder().named("handle") + .argsLength(1) + .arg(0, "org.springframework.web.server.ServerWebExchange") + .qualifier("servicename") + .build()) + .build(); + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/advice/RestTemplateInterceptAdvice.java b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/advice/RestTemplateInterceptAdvice.java new file mode 100644 index 000000000..da8ec796a --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/advice/RestTemplateInterceptAdvice.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2017, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.servicename.springboot353.advice; + +import com.megaease.easeagent.plugin.CodeVersion; +import com.megaease.easeagent.plugin.Points; +import com.megaease.easeagent.plugin.matcher.IClassMatcher; +import com.megaease.easeagent.plugin.matcher.IMethodMatcher; +import com.megaease.easeagent.plugin.matcher.MethodMatcher; +import com.megaease.easeagent.plugin.servicename.springboot353.Const; + +import java.util.Set; + +import static com.megaease.easeagent.plugin.tools.matcher.ClassMatcherUtils.name; + +public class RestTemplateInterceptAdvice implements Points { + @Override + public CodeVersion codeVersions() { + return Const.VERSIONS; + } + + @Override + public IClassMatcher getClassMatcher() { + return name(Const.RetryLoadBalancerInterceptor).or(name(Const.LoadBalancerInterceptor)); + } + + @Override + public Set getMethodMatcher() { + return MethodMatcher.multiBuilder() + .match(MethodMatcher.builder().named("intercept") + .argsLength(3) + .arg(0, "org.springframework.http.HttpRequest") + .qualifier("servicename") + .build()) + .build(); + } + + +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/advice/WebClientFilterAdvice.java b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/advice/WebClientFilterAdvice.java new file mode 100644 index 000000000..78714f5ca --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/advice/WebClientFilterAdvice.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2017, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.servicename.springboot353.advice; + +import com.megaease.easeagent.plugin.CodeVersion; +import com.megaease.easeagent.plugin.Points; +import com.megaease.easeagent.plugin.matcher.IClassMatcher; +import com.megaease.easeagent.plugin.matcher.IMethodMatcher; +import com.megaease.easeagent.plugin.matcher.MethodMatcher; +import com.megaease.easeagent.plugin.servicename.springboot353.Const; + +import java.util.Set; + +import static com.megaease.easeagent.plugin.tools.matcher.ClassMatcherUtils.name; + +public class WebClientFilterAdvice implements Points { + @Override + public CodeVersion codeVersions() { + return Const.VERSIONS; + } + + //// WebClient + // .type(namedOneOf(ReactorLoadBalancerExchangeFilterFunction, LoadBalancerExchangeFilterFunction)) + // .transform(webClientFilter(named("filter").and(takesArguments(2)) + // .and(takesArgument(0, named("org.springframework.web.reactive.function.client.ClientRequest"))) + // )) + @Override + public IClassMatcher getClassMatcher() { + return name(Const.ReactorLoadBalancerExchangeFilterFunction).or(name(Const.RetryableLoadBalancerExchangeFilterFunction)); + } + + @Override + public Set getMethodMatcher() { + return MethodMatcher.multiBuilder() + .match(MethodMatcher.builder().named("filter") + .argsLength(2) + .arg(0, "org.springframework.web.reactive.function.client.ClientRequest") + .qualifier("servicename") + .build()) + .build(); + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/BaseServiceNameInterceptor.java b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/BaseServiceNameInterceptor.java new file mode 100644 index 000000000..e18c77218 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/BaseServiceNameInterceptor.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2017, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.servicename.springboot353.interceptor; + +import com.megaease.easeagent.plugin.api.config.AutoRefreshPluginConfigRegistry; +import com.megaease.easeagent.plugin.api.config.IPluginConfig; +import com.megaease.easeagent.plugin.enums.Order; +import com.megaease.easeagent.plugin.interceptor.Interceptor; +import com.megaease.easeagent.plugin.servicename.springboot353.ServiceNamePluginConfig; + +public abstract class BaseServiceNameInterceptor implements Interceptor { + protected static ServiceNamePluginConfig config = null; + + @Override + public void init(IPluginConfig pConfig, String className, String methodName, String methodDescriptor) { + config = AutoRefreshPluginConfigRegistry.getOrCreate(pConfig.domain(), pConfig.namespace(), pConfig.id(), ServiceNamePluginConfig.SUPPLIER); + } + + @Override + public int order() { + return Order.HIGH.getOrder(); + } + + @Override + public String getType() { + return "addServiceNameHead"; + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/FeignClientLoadBalancerClientInterceptor.java b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/FeignClientLoadBalancerClientInterceptor.java new file mode 100644 index 000000000..ed505608b --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/FeignClientLoadBalancerClientInterceptor.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2017, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.servicename.springboot353.interceptor; + +import com.megaease.easeagent.plugin.annotation.AdviceTo; +import com.megaease.easeagent.plugin.api.Context; +import com.megaease.easeagent.plugin.api.logging.Logger; +import com.megaease.easeagent.plugin.bridge.EaseAgent; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import com.megaease.easeagent.plugin.servicename.springboot353.ReflectionTool; +import com.megaease.easeagent.plugin.servicename.springboot353.advice.FeignClientLoadBalancerClientAdvice; +import feign.Request; + +import java.net.URI; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; + +@AdviceTo(value = FeignClientLoadBalancerClientAdvice.class, qualifier = "servicename") +public class FeignClientLoadBalancerClientInterceptor extends BaseServiceNameInterceptor { + private static final Logger LOGGER = EaseAgent.getLogger(FeignClientLoadBalancerClientInterceptor.class); + + @Override + public void before(MethodInfo methodInfo, Context context) { + String method = methodInfo.getMethod(); + try { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("enter method [{}]", method); + } + Request request = (Request) methodInfo.getArgs()[0]; + String url = request.url(); + String host = URI.create(url).getHost(); + if (ReflectionTool.hasText(host)) { + final HashMap> newHeaders = new HashMap<>(request.headers()); + newHeaders.put(config.getPropagateHead(), Collections.singleton(host)); + context.injectForwardedHeaders((name, value) -> newHeaders.put(name, Collections.singleton(value))); + final Request newRequest = Request.create(request.httpMethod(), request.url(), newHeaders, request.body(), request.charset(), request.requestTemplate()); + methodInfo.changeArg(0, newRequest); + } + } catch (Throwable e) { + LOGGER.warn("intercept method [{}] failure", method, e); + } + } + +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/FilteringWebHandlerInterceptor.java b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/FilteringWebHandlerInterceptor.java new file mode 100644 index 000000000..9efcda686 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/FilteringWebHandlerInterceptor.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2017, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.servicename.springboot353.interceptor; + +import com.megaease.easeagent.plugin.annotation.AdviceTo; +import com.megaease.easeagent.plugin.api.Context; +import com.megaease.easeagent.plugin.api.logging.Logger; +import com.megaease.easeagent.plugin.bridge.EaseAgent; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import com.megaease.easeagent.plugin.servicename.springboot353.Const; +import com.megaease.easeagent.plugin.servicename.springboot353.advice.FilteringWebHandlerAdvice; +import org.springframework.cloud.gateway.route.Route; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.util.StringUtils; +import org.springframework.web.server.ServerWebExchange; + +import java.net.URI; +import java.util.Map; + +@AdviceTo(value = FilteringWebHandlerAdvice.class, qualifier = "servicename") +public class FilteringWebHandlerInterceptor extends BaseServiceNameInterceptor { + private static final Logger LOGGER = EaseAgent.getLogger(FilteringWebHandlerInterceptor.class); + + private static volatile String routeAttribute = null; + + @Override + public void before(MethodInfo methodInfo, Context context) { + String method = methodInfo.getMethod(); + try { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("enter method [{}]", method); + } + ServerWebExchange exchange = (ServerWebExchange) methodInfo.getArgs()[0]; + Route route = getRoute(exchange); + if (route == null) { + return; + } + URI uri = route.getUri(); + String scheme = uri.getScheme(); + if (!scheme.equals("lb")) { + return; + } + String host = uri.getHost(); + if (!StringUtils.hasText(host)) { + return; + } + ServerHttpRequest newRequest = exchange.getRequest().mutate().header(config.getPropagateHead(), host).build(); + ServerWebExchange newExchange = exchange.mutate().request(newRequest).build(); + methodInfo.changeArg(0, newExchange); + } catch (Throwable e) { + LOGGER.warn("intercept method [{}] failure", method, e); + } + } + + public Route getRoute(ServerWebExchange exchange) { + if (routeAttribute != null) { + return exchange.getAttribute(routeAttribute); + } + for (Map.Entry entry : exchange.getAttributes().entrySet()) { + Object value = entry.getValue(); + if (value instanceof Route) { + routeAttribute = entry.getKey(); + return (Route) value; + } + } + return null; + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/RestTemplateInterceptInterceptor.java b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/RestTemplateInterceptInterceptor.java new file mode 100644 index 000000000..88d3dec85 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/RestTemplateInterceptInterceptor.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2017, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.servicename.springboot353.interceptor; + +import com.megaease.easeagent.plugin.annotation.AdviceTo; +import com.megaease.easeagent.plugin.api.Context; +import com.megaease.easeagent.plugin.api.logging.Logger; +import com.megaease.easeagent.plugin.bridge.EaseAgent; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import com.megaease.easeagent.plugin.servicename.springboot353.ReflectionTool; +import com.megaease.easeagent.plugin.servicename.springboot353.advice.RestTemplateInterceptAdvice; +import org.springframework.util.MultiValueMap; + +import java.net.URI; + +@AdviceTo(value = RestTemplateInterceptAdvice.class, qualifier = "servicename") +public class RestTemplateInterceptInterceptor extends BaseServiceNameInterceptor { + private static final Logger LOGGER = EaseAgent.getLogger(RestTemplateInterceptInterceptor.class); + + @Override + public void before(MethodInfo methodInfo, Context context) { + String method = methodInfo.getMethod(); + try { + Object request = methodInfo.getArgs()[0]; + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("enter method [{}]", method); + } + URI uri = (URI) ReflectionTool.invokeMethod(request, "getURI"); + String host = uri.getHost(); + if (ReflectionTool.hasText(host)) { + Object fakeHeaders = ReflectionTool.invokeMethod(request, "getHeaders");//org.springframework.http.HttpHeaders + @SuppressWarnings("unchecked") + MultiValueMap headers = (MultiValueMap) ReflectionTool.extractField(fakeHeaders, "headers"); + headers.add(config.getPropagateHead(), host); + context.injectForwardedHeaders(headers::add); + } + } catch (Throwable e) { + LOGGER.warn("intercept method [{}] failure", method, e); + } + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/WebClientFilterInterceptor.java b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/WebClientFilterInterceptor.java new file mode 100644 index 000000000..779381c60 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/main/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/WebClientFilterInterceptor.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2017, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.servicename.springboot353.interceptor; + +import com.megaease.easeagent.plugin.annotation.AdviceTo; +import com.megaease.easeagent.plugin.api.Context; +import com.megaease.easeagent.plugin.api.logging.Logger; +import com.megaease.easeagent.plugin.bridge.EaseAgent; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import com.megaease.easeagent.plugin.servicename.springboot353.ReflectionTool; +import com.megaease.easeagent.plugin.servicename.springboot353.advice.WebClientFilterAdvice; +import org.springframework.util.MultiValueMap; + +import java.net.URI; + +@AdviceTo(value = WebClientFilterAdvice.class, qualifier = "servicename") +public class WebClientFilterInterceptor extends BaseServiceNameInterceptor { + private static final Logger LOGGER = EaseAgent.getLogger(WebClientFilterInterceptor.class); + + @Override + public void before(MethodInfo methodInfo, Context context) { + String method = methodInfo.getMethod(); + try { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("enter method [{}]", method); + } + Object request = methodInfo.getArgs()[0]; + URI uri = (URI) ReflectionTool.invokeMethod(request, "url"); + String host = uri.getHost(); + if (ReflectionTool.hasText(host)) { + Object fakeHeaders = ReflectionTool.invokeMethod(request, "headers");//org.springframework.http.HttpHeaders + @SuppressWarnings("unchecked") + MultiValueMap headers = (MultiValueMap) ReflectionTool.extractField(fakeHeaders, "headers"); + headers.add(config.getPropagateHead(), host); + context.injectForwardedHeaders(headers::add); + } + } catch (Throwable e) { + LOGGER.warn("intercept method [{}] failure", method, e); + } + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/test/java/com/megaease/easeagent/plugin/servicename/springboot353/ReflectionToolTest.java b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/test/java/com/megaease/easeagent/plugin/servicename/springboot353/ReflectionToolTest.java new file mode 100644 index 000000000..401398466 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/test/java/com/megaease/easeagent/plugin/servicename/springboot353/ReflectionToolTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.servicename.springboot353; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class ReflectionToolTest { + + @Test + public void invokeMethod() throws ReflectiveOperationException { + String name = "testName"; + TestClass testClass = new TestClass(name); + assertEquals(name, ReflectionTool.invokeMethod(testClass, "get")); + String p = "ttt"; + assertEquals(name + p, ReflectionTool.invokeMethod(testClass, "get", p)); + } + + @Test + public void extractField() throws ReflectiveOperationException { + String name = "testName"; + TestClass testClass = new TestClass(name); + assertEquals(name, ReflectionTool.extractField(testClass, "name")); + } + + @Test + public void hasText() { + assertFalse(ReflectionTool.hasText(null)); + assertFalse(ReflectionTool.hasText("")); + assertFalse(ReflectionTool.hasText(" ")); + assertTrue(ReflectionTool.hasText("ab")); + assertTrue(ReflectionTool.hasText(" ab ")); + } + + class TestClass { + private final String name; + + public TestClass(String name) { + this.name = name; + } + + private String get() { + return name; + } + + private String get(String p) { + return name + p; + } + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/test/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/BaseServiceNameInterceptorTest.java b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/test/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/BaseServiceNameInterceptorTest.java new file mode 100644 index 000000000..ce8fa46e4 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/test/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/BaseServiceNameInterceptorTest.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.servicename.springboot353.interceptor; + +import com.megaease.easeagent.plugin.api.Context; +import com.megaease.easeagent.plugin.api.config.IPluginConfig; +import com.megaease.easeagent.plugin.bridge.EaseAgent; +import com.megaease.easeagent.plugin.enums.Order; +import com.megaease.easeagent.plugin.interceptor.Interceptor; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import com.megaease.easeagent.plugin.servicename.springboot353.Const; +import com.megaease.easeagent.plugin.servicename.springboot353.ServiceNamePlugin; +import com.megaease.easeagent.plugin.servicename.springboot353.ServiceNamePluginConfig; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class BaseServiceNameInterceptorTest { + + public static void initInterceptor(Interceptor interceptor) { + ServiceNamePlugin plugin = new ServiceNamePlugin(); + IPluginConfig config = EaseAgent.getConfig(plugin.getDomain(), plugin.getNamespace(), interceptor.getType()); + interceptor.init(config, "", "", ""); + } + + + @Test + public void init() { + MockBaseServiceNameInterceptor mockBaseServiceNameInterceptor = new MockBaseServiceNameInterceptor(); + initInterceptor(mockBaseServiceNameInterceptor); + ServiceNamePluginConfig serviceNamePluginConfig = mockBaseServiceNameInterceptor.getConfig(); + assertEquals(Const.DEFAULT_PROPAGATE_HEAD, serviceNamePluginConfig.getPropagateHead()); + } + + @Test + public void order() { + MockBaseServiceNameInterceptor mockBaseServiceNameInterceptor = new MockBaseServiceNameInterceptor(); + assertEquals(Order.HIGH.getOrder(), mockBaseServiceNameInterceptor.order()); + } + + @Test + public void getType() { + MockBaseServiceNameInterceptor mockBaseServiceNameInterceptor = new MockBaseServiceNameInterceptor(); + assertEquals("addServiceNameHead", mockBaseServiceNameInterceptor.getType()); + } + + static class MockBaseServiceNameInterceptor extends BaseServiceNameInterceptor { + + @Override + public void before(MethodInfo methodInfo, Context context) { + + } + + public ServiceNamePluginConfig getConfig() { + return config; + } + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/test/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/CheckUtils.java b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/test/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/CheckUtils.java new file mode 100644 index 000000000..07a8f0977 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/test/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/CheckUtils.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.servicename.springboot353.interceptor; + +import com.megaease.easeagent.plugin.api.trace.Getter; +import com.megaease.easeagent.plugin.servicename.springboot353.Const; + +import static org.junit.Assert.assertEquals; + +public class CheckUtils { + public static void check(Getter headers, String serviceName) { + assertEquals(serviceName, headers.header(Const.DEFAULT_PROPAGATE_HEAD)); + assertEquals(TestConst.FORWARDED_VALUE, headers.header(TestConst.FORWARDED_NAME)); + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/test/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/FeignBlockingLoadBalancerClientInterceptorTest.java b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/test/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/FeignBlockingLoadBalancerClientInterceptorTest.java new file mode 100644 index 000000000..b1bc4664c --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/test/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/FeignBlockingLoadBalancerClientInterceptorTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.servicename.springboot353.interceptor; + +import com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner; +import com.megaease.easeagent.plugin.bridge.EaseAgent; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import feign.Request; +import feign.RequestTemplate; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Collection; + +import static org.junit.Assert.assertNotNull; + +@RunWith(EaseAgentJunit4ClassRunner.class) +public class FeignBlockingLoadBalancerClientInterceptorTest { + + + @Test + public void before() { + FeignClientLoadBalancerClientInterceptor interceptor = new FeignClientLoadBalancerClientInterceptor(); + BaseServiceNameInterceptorTest.initInterceptor(interceptor); + EaseAgent.getContext().put(TestConst.FORWARDED_NAME, TestConst.FORWARDED_VALUE); + + + RequestTemplate requestTemplate = new RequestTemplate(); + String host = "TEST-SERVER"; + Request request = Request.create( + Request.HttpMethod.GET, + "http://" + host, + requestTemplate.headers(), + Request.Body.create(requestTemplate.body()), + requestTemplate + ); + + MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{request}).build(); + + interceptor.before(methodInfo, EaseAgent.getContext()); + + Request newRequest = (Request) methodInfo.getArgs()[0]; + CheckUtils.check(name -> { + Collection head = newRequest.headers().get(name); + assertNotNull(head); + return head.iterator().next(); + }, host); + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/test/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/FilteringWebHandlerInterceptorTest.java b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/test/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/FilteringWebHandlerInterceptorTest.java new file mode 100644 index 000000000..5398bac62 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/test/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/FilteringWebHandlerInterceptorTest.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.servicename.springboot353.interceptor; + +import com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner; +import com.megaease.easeagent.plugin.bridge.EaseAgent; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import com.megaease.easeagent.plugin.servicename.springboot353.Const; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.cloud.gateway.route.Route; +import org.springframework.mock.http.server.reactive.MockServerHttpRequest; +import org.springframework.mock.web.server.MockServerWebExchange; +import org.springframework.web.server.ServerWebExchange; + +import java.net.URI; +import java.net.URISyntaxException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +@RunWith(EaseAgentJunit4ClassRunner.class) +public class FilteringWebHandlerInterceptorTest { + + @Test + public void before() throws URISyntaxException { + FilteringWebHandlerInterceptor interceptor = new FilteringWebHandlerInterceptor(); + BaseServiceNameInterceptorTest.initInterceptor(interceptor); + EaseAgent.getContext().put(TestConst.FORWARDED_NAME, TestConst.FORWARDED_VALUE); + + MockServerWebExchange mockServerWebExchange = buildMockServerWebExchange(); + MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build(); + interceptor.before(methodInfo, EaseAgent.getContext()); + assertNull(header(mockServerWebExchange, FilteringWebHandlerInterceptor.config.getPropagateHead())); + + mockServerWebExchange = buildMockServerWebExchange(); + Route route = new MockRouteBuilder().uri(new URI("http://127.0.0.1:8080")).id("1").build(); + mockServerWebExchange.getAttributes().put(TestConst.SERVER_WEB_EXCHANGE_ROUTE_ATTRIBUTE, route); + methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build(); + interceptor.before(methodInfo, EaseAgent.getContext()); + assertNull(header(mockServerWebExchange, FilteringWebHandlerInterceptor.config.getPropagateHead())); + + mockServerWebExchange = buildMockServerWebExchange(); + route = new MockRouteBuilder().uri(new URI("lb://127.0.0.1:8080")).id("11").build(); + mockServerWebExchange.getAttributes().put(TestConst.SERVER_WEB_EXCHANGE_ROUTE_ATTRIBUTE, route); + methodInfo = MethodInfo.builder().args(new Object[]{mockServerWebExchange}).build(); + interceptor.before(methodInfo, EaseAgent.getContext()); + assertEquals("127.0.0.1", header((ServerWebExchange)methodInfo.getArgs()[0], FilteringWebHandlerInterceptor.config.getPropagateHead())); + + } + + private static final String header(ServerWebExchange exchange, String name) { + return exchange.getRequest().getHeaders().getFirst(name); + } + + private static final MockServerWebExchange buildMockServerWebExchange() { + MockServerHttpRequest mockServerHttpRequest = MockServerHttpRequest.get("http://127.0.0.1:8080", "a=b").build(); + return MockServerWebExchange.builder(mockServerHttpRequest).build(); + } + + static class MockRouteBuilder extends Route.Builder { + public MockRouteBuilder() { + predicate = serverWebExchange -> true; + } + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/test/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/RestTemplateInterceptInterceptorTest.java b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/test/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/RestTemplateInterceptInterceptorTest.java new file mode 100644 index 000000000..7d8655dd2 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/test/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/RestTemplateInterceptInterceptorTest.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.servicename.springboot353.interceptor; + +import com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner; +import com.megaease.easeagent.plugin.bridge.EaseAgent; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.mock.http.client.MockClientHttpRequest; + +import java.net.URI; +import java.net.URISyntaxException; + +@RunWith(EaseAgentJunit4ClassRunner.class) +public class RestTemplateInterceptInterceptorTest { + + @Test + public void before() throws URISyntaxException { + RestTemplateInterceptInterceptor interceptor = new RestTemplateInterceptInterceptor(); + BaseServiceNameInterceptorTest.initInterceptor(interceptor); + EaseAgent.getContext().put(TestConst.FORWARDED_NAME, TestConst.FORWARDED_VALUE); + + MockClientHttpRequest httpRequest = new MockClientHttpRequest(); + String host = "TEST-SERVER"; + httpRequest.setURI(new URI("http://" + host)); + MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpRequest}).build(); + + interceptor.before(methodInfo, EaseAgent.getContext()); + + MockClientHttpRequest newRequest = (MockClientHttpRequest) methodInfo.getArgs()[0]; + CheckUtils.check(name -> newRequest.getHeaders().getFirst(name), host); + + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/test/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/TestConst.java b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/test/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/TestConst.java new file mode 100644 index 000000000..915bb1c17 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/test/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/TestConst.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.servicename.springboot353.interceptor; + +public class TestConst { + public static final String FORWARDED_NAME = "X-Forwarded-For"; + public static final String FORWARDED_VALUE = "testForwarded"; + + public static final String SERVER_WEB_EXCHANGE_ROUTE_ATTRIBUTE = "org.springframework.cloud.gateway.support.ServerWebExchangeUtils.gatewayRoute"; +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/test/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/WebClientFilterInterceptorTest.java b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/test/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/WebClientFilterInterceptorTest.java new file mode 100644 index 000000000..b275e5a14 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/test/java/com/megaease/easeagent/plugin/servicename/springboot353/interceptor/WebClientFilterInterceptorTest.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.servicename.springboot353.interceptor; + +import com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner; +import com.megaease.easeagent.plugin.bridge.EaseAgent; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.http.HttpMethod; +import org.springframework.web.reactive.function.client.ClientRequest; + +import java.net.URI; +import java.net.URISyntaxException; + +@RunWith(EaseAgentJunit4ClassRunner.class) +public class WebClientFilterInterceptorTest { + + @Test + public void before() throws URISyntaxException { + WebClientFilterInterceptor interceptor = new WebClientFilterInterceptor(); + BaseServiceNameInterceptorTest.initInterceptor(interceptor); + EaseAgent.getContext().put(TestConst.FORWARDED_NAME, TestConst.FORWARDED_VALUE); + + String host = "TEST-SERVER"; + ClientRequest clientRequest = build(new URI("http://" + host)); + + MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{clientRequest}).build(); + + interceptor.before(methodInfo, EaseAgent.getContext()); + + ClientRequest newRequest = (ClientRequest) methodInfo.getArgs()[0]; + CheckUtils.check(name -> newRequest.headers().getFirst(name), host); + + } + + public static ClientRequest build(URI url) { + return ClientRequest.create(HttpMethod.GET, url).build(); + } +} diff --git a/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/test/resources/mock_agent.properties b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/test/resources/mock_agent.properties new file mode 100644 index 000000000..02ac57871 --- /dev/null +++ b/plugins/spring-boot-3.5.3/spring-boot-servicename-3.5.3/src/test/resources/mock_agent.properties @@ -0,0 +1 @@ +easeagent.progress.forwarded.headers=X-Forwarded-For diff --git a/plugins/spring-gateway/pom.xml b/plugins/spring-gateway/pom.xml index b86e86ead..ff28cb5f3 100644 --- a/plugins/spring-gateway/pom.xml +++ b/plugins/spring-gateway/pom.xml @@ -22,7 +22,7 @@ plugins com.megaease.easeagent - 2.2.9 + 2.3.0 4.0.0 @@ -46,9 +46,11 @@ 3.0.3 provided + org.springframework spring-webflux + 5.3.18 provided @@ -61,7 +63,7 @@ org.springframework spring-test - ${version.spring} + 5.3.18 test diff --git a/plugins/spring-gateway/src/main/java/easeagent/plugin/spring/gateway/advice/AgentGlobalFilterAdvice.java b/plugins/spring-gateway/src/main/java/easeagent/plugin/spring/gateway/advice/AgentGlobalFilterAdvice.java index ce0b10955..60486ec0c 100644 --- a/plugins/spring-gateway/src/main/java/easeagent/plugin/spring/gateway/advice/AgentGlobalFilterAdvice.java +++ b/plugins/spring-gateway/src/main/java/easeagent/plugin/spring/gateway/advice/AgentGlobalFilterAdvice.java @@ -17,6 +17,7 @@ package easeagent.plugin.spring.gateway.advice; +import com.megaease.easeagent.plugin.CodeVersion; import com.megaease.easeagent.plugin.Points; import com.megaease.easeagent.plugin.matcher.ClassMatcher; import com.megaease.easeagent.plugin.matcher.IClassMatcher; @@ -26,6 +27,11 @@ import java.util.Set; public class AgentGlobalFilterAdvice implements Points { + @Override + public CodeVersion codeVersions() { + return CodeCons.VERSIONS; + } + @Override public IClassMatcher getClassMatcher() { return ClassMatcher.builder() diff --git a/plugins/spring-gateway/src/main/java/easeagent/plugin/spring/gateway/advice/CodeCons.java b/plugins/spring-gateway/src/main/java/easeagent/plugin/spring/gateway/advice/CodeCons.java new file mode 100644 index 000000000..7c0747f3c --- /dev/null +++ b/plugins/spring-gateway/src/main/java/easeagent/plugin/spring/gateway/advice/CodeCons.java @@ -0,0 +1,12 @@ +package easeagent.plugin.spring.gateway.advice; + +import com.megaease.easeagent.plugin.CodeVersion; +import com.megaease.easeagent.plugin.Points; +import com.megaease.easeagent.plugin.api.config.ConfigConst; + +public class CodeCons { + public final static CodeVersion VERSIONS = CodeVersion.builder() + .key(ConfigConst.CodeVersion.KEY_SPRING_BOOT) + .add(Points.DEFAULT_VERSION) + .add(ConfigConst.CodeVersion.VERSION_SPRING_BOOT2).build(); +} diff --git a/plugins/spring-gateway/src/main/java/easeagent/plugin/spring/gateway/advice/HttpHeadersFilterAdvice.java b/plugins/spring-gateway/src/main/java/easeagent/plugin/spring/gateway/advice/HttpHeadersFilterAdvice.java index c1b45c372..c38e365f9 100644 --- a/plugins/spring-gateway/src/main/java/easeagent/plugin/spring/gateway/advice/HttpHeadersFilterAdvice.java +++ b/plugins/spring-gateway/src/main/java/easeagent/plugin/spring/gateway/advice/HttpHeadersFilterAdvice.java @@ -17,6 +17,7 @@ package easeagent.plugin.spring.gateway.advice; +import com.megaease.easeagent.plugin.CodeVersion; import com.megaease.easeagent.plugin.Points; import com.megaease.easeagent.plugin.matcher.ClassMatcher; import com.megaease.easeagent.plugin.matcher.IClassMatcher; @@ -26,6 +27,11 @@ import java.util.Set; public class HttpHeadersFilterAdvice implements Points { + @Override + public CodeVersion codeVersions() { + return CodeCons.VERSIONS; + } + @Override public IClassMatcher getClassMatcher() { return ClassMatcher.builder() diff --git a/plugins/spring-gateway/src/main/java/easeagent/plugin/spring/gateway/advice/InitGlobalFilterAdvice.java b/plugins/spring-gateway/src/main/java/easeagent/plugin/spring/gateway/advice/InitGlobalFilterAdvice.java index 11f941b72..3bbc43d8d 100644 --- a/plugins/spring-gateway/src/main/java/easeagent/plugin/spring/gateway/advice/InitGlobalFilterAdvice.java +++ b/plugins/spring-gateway/src/main/java/easeagent/plugin/spring/gateway/advice/InitGlobalFilterAdvice.java @@ -17,6 +17,7 @@ package easeagent.plugin.spring.gateway.advice; +import com.megaease.easeagent.plugin.CodeVersion; import com.megaease.easeagent.plugin.Points; import com.megaease.easeagent.plugin.matcher.ClassMatcher; import com.megaease.easeagent.plugin.matcher.IClassMatcher; @@ -26,6 +27,11 @@ import java.util.Set; public class InitGlobalFilterAdvice implements Points { + @Override + public CodeVersion codeVersions() { + return CodeCons.VERSIONS; + } + @Override public IClassMatcher getClassMatcher() { return ClassMatcher.builder() diff --git a/plugins/spring-gateway/src/main/java/easeagent/plugin/spring/gateway/interceptor/initialize/GlobalFilterInterceptor.java b/plugins/spring-gateway/src/main/java/easeagent/plugin/spring/gateway/interceptor/initialize/GlobalFilterInterceptor.java index 8ff6471dc..d406393ce 100644 --- a/plugins/spring-gateway/src/main/java/easeagent/plugin/spring/gateway/interceptor/initialize/GlobalFilterInterceptor.java +++ b/plugins/spring-gateway/src/main/java/easeagent/plugin/spring/gateway/interceptor/initialize/GlobalFilterInterceptor.java @@ -30,14 +30,10 @@ @AdviceTo(value = InitGlobalFilterAdvice.class, plugin = SpringGatewayPlugin.class) public class GlobalFilterInterceptor implements Interceptor { - private static boolean loadAgentFilter = false; @Override @SuppressWarnings("unchecked") public void before(MethodInfo methodInfo, Context context) { - if (loadAgentFilter) { - return; - } List list = null; switch (methodInfo.getMethod()) { case "filteringWebHandler": @@ -48,20 +44,28 @@ public void before(MethodInfo methodInfo, Context context) { list = (List) methodInfo.getArgs()[1]; break; } - if (list == null) { + if (list == null || hasAgentFilter(list)) { return; } - loadAgentFilter = true; list.add(0, new AgentGlobalFilter()); } + private boolean hasAgentFilter(List list) { + for (GlobalFilter globalFilter : list) { + if (globalFilter instanceof AgentGlobalFilter) { + return true; + } + } + return false; + } + @Override public String getType() { - return Order.TRACING.getName(); + return Order.INIT.getName(); } @Override public int order() { - return Order.TRACING_INIT.getOrder(); + return Order.INIT.getOrder(); } } diff --git a/plugins/spring-gateway/src/test/java/easeagent/plugin/spring/gateway/interceptor/initialize/GlobalFilterInterceptorTest.java b/plugins/spring-gateway/src/test/java/easeagent/plugin/spring/gateway/interceptor/initialize/GlobalFilterInterceptorTest.java index 84a92482d..83052cd88 100644 --- a/plugins/spring-gateway/src/test/java/easeagent/plugin/spring/gateway/interceptor/initialize/GlobalFilterInterceptorTest.java +++ b/plugins/spring-gateway/src/test/java/easeagent/plugin/spring/gateway/interceptor/initialize/GlobalFilterInterceptorTest.java @@ -38,12 +38,10 @@ public void before() { interceptor.before(methodInfo, null); assertEquals(1, arg.size()); assertEquals(1, arg.size()); - AgentFieldReflectAccessor.setStaticFieldValue(GlobalFilterInterceptor.class, "loadAgentFilter", false); arg.clear(); methodInfo = MethodInfo.builder().method("gatewayControllerEndpoint").args(new Object[]{arg}).build(); interceptor.before(methodInfo, null); assertEquals(1, arg.size()); - AgentFieldReflectAccessor.setStaticFieldValue(GlobalFilterInterceptor.class, "loadAgentFilter", false); arg.clear(); methodInfo = MethodInfo.builder().method("gatewayLegacyControllerEndpoint").args(new Object[]{null, arg}).build(); interceptor.before(methodInfo, null); @@ -54,12 +52,12 @@ public void before() { @Test public void getType() { GlobalFilterInterceptor interceptor = new GlobalFilterInterceptor(); - assertEquals(ConfigConst.PluginID.TRACING, interceptor.getType()); + assertEquals(ConfigConst.PluginID.INIT, interceptor.getType()); } @Test public void order() { GlobalFilterInterceptor interceptor = new GlobalFilterInterceptor(); - assertEquals(Order.TRACING_INIT.getOrder(), interceptor.order()); + assertEquals(Order.INIT.getOrder(), interceptor.order()); } } diff --git a/plugins/springweb/README.md b/plugins/springweb/README.md new file mode 100644 index 000000000..9129e0df8 --- /dev/null +++ b/plugins/springweb/README.md @@ -0,0 +1,30 @@ +# spring-boot http client plugin + +## Points + +* resTemplate points: `org.springframework.http.client.ClientHttpRequest:execute` + * points code version spring-boot:2.x.x config and default + ```properties + runtime.code.version.points.spring-boot=2.x.x + ``` + when not config `runtime.code.version.points.spring-boot` it is load +* feignClient points: `feign.Client:execute` +* webclient points: `org.springframework.web.reactive.function.client.WebClient$Builder:build` + + +## config + +### tracing config +```properties +plugin.observability.webclient.tracing.enabled=true +plugin.observability.resTemplate.tracing.enabled=true +plugin.observability.feignClient.tracing.enabled=true +``` + +### support forwarded +```properties +plugin.integrability.forwarded.forwarded.enabled=true +``` + + + diff --git a/plugins/springweb/pom.xml b/plugins/springweb/pom.xml index a5c0f6afe..66debee6a 100644 --- a/plugins/springweb/pom.xml +++ b/plugins/springweb/pom.xml @@ -22,7 +22,7 @@ plugins com.megaease.easeagent - 2.2.9 + 2.3.0 4.0.0 @@ -55,6 +55,7 @@ org.springframework spring-webflux + 5.3.18 provided diff --git a/plugins/springweb/src/main/java/com/megaease/easeagent/plugin/springweb/advice/ClientHttpRequestAdvice.java b/plugins/springweb/src/main/java/com/megaease/easeagent/plugin/springweb/advice/ClientHttpRequestAdvice.java index b7c25fd0a..654ac63b8 100644 --- a/plugins/springweb/src/main/java/com/megaease/easeagent/plugin/springweb/advice/ClientHttpRequestAdvice.java +++ b/plugins/springweb/src/main/java/com/megaease/easeagent/plugin/springweb/advice/ClientHttpRequestAdvice.java @@ -17,7 +17,9 @@ package com.megaease.easeagent.plugin.springweb.advice; +import com.megaease.easeagent.plugin.CodeVersion; import com.megaease.easeagent.plugin.Points; +import com.megaease.easeagent.plugin.api.config.ConfigConst; import com.megaease.easeagent.plugin.matcher.ClassMatcher; import com.megaease.easeagent.plugin.matcher.IClassMatcher; import com.megaease.easeagent.plugin.matcher.IMethodMatcher; @@ -26,6 +28,17 @@ import java.util.Set; public class ClientHttpRequestAdvice implements Points { + + private final static CodeVersion VERSIONS = CodeVersion.builder() + .key(ConfigConst.CodeVersion.KEY_SPRING_BOOT) + .add(Points.DEFAULT_VERSION) + .add(ConfigConst.CodeVersion.VERSION_SPRING_BOOT2).build(); + + @Override + public CodeVersion codeVersions() { + return VERSIONS; + } + @Override public IClassMatcher getClassMatcher() { return ClassMatcher.builder().hasInterface("org.springframework.http.client.ClientHttpRequest").notInterface() diff --git a/plugins/tomcat-jdk17/pom.xml b/plugins/tomcat-jdk17/pom.xml new file mode 100644 index 000000000..5de048a5e --- /dev/null +++ b/plugins/tomcat-jdk17/pom.xml @@ -0,0 +1,97 @@ + + + + + + plugins + com.megaease.easeagent + 2.3.0 + + 4.0.0 + + tomcat-jdk17 + + + + + com.megaease.easeagent + plugin-api + provided + + + org.apache.tomcat.embed + tomcat-embed-core + 10.1.42 + + + org.apache.tomcat + tomcat-annotations-api + + + provided + + + + com.megaease.easeagent + plugin-api-mock + ${project.version} + test + + + org.springframework.boot + spring-boot-starter-web + 3.5.3 + + + org.springframework.boot + spring-boot-starter-logging + + + test + + + org.springframework.boot + spring-boot-starter-test + 3.5.3 + + + org.springframework.boot + spring-boot-starter-logging + + + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 17 + 17 + + + + + + diff --git a/plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/AccessPlugin.java b/plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/AccessPlugin.java new file mode 100644 index 000000000..2322d7c89 --- /dev/null +++ b/plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/AccessPlugin.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2017, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.tomcat; + +import com.megaease.easeagent.plugin.AgentPlugin; +import com.megaease.easeagent.plugin.api.config.ConfigConst; +import com.megaease.easeagent.plugin.enums.Order; + +public class AccessPlugin implements AgentPlugin { + @Override + public String getNamespace() { + return ConfigConst.Namespace.ACCESS; + } + + @Override + public String getDomain() { + return ConfigConst.OBSERVABILITY; + } + + @Override + public int order() { + return Order.HIGH.getOrder(); + } +} diff --git a/plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/ForwardedPlugin.java b/plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/ForwardedPlugin.java new file mode 100644 index 000000000..3121817b5 --- /dev/null +++ b/plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/ForwardedPlugin.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.tomcat; + +import com.megaease.easeagent.plugin.AgentPlugin; +import com.megaease.easeagent.plugin.api.config.ConfigConst; +import com.megaease.easeagent.plugin.enums.Order; + +public class ForwardedPlugin implements AgentPlugin { + @Override + public String getNamespace() { + return ConfigConst.Namespace.FORWARDED; + } + + @Override + public String getDomain() { + return ConfigConst.INTEGRABILITY; + } + + @Override + public int order() { + return Order.FORWARDED.getOrder(); + } +} diff --git a/plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/TomcatPlugin.java b/plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/TomcatPlugin.java new file mode 100644 index 000000000..7a6718766 --- /dev/null +++ b/plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/TomcatPlugin.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.tomcat; + +import com.megaease.easeagent.plugin.AgentPlugin; +import com.megaease.easeagent.plugin.api.config.ConfigConst; +import com.megaease.easeagent.plugin.enums.Order; + +import static com.megaease.easeagent.plugin.api.config.ConfigConst.Namespace.TOMCAT; + +public class TomcatPlugin implements AgentPlugin { + @Override + public String getNamespace() { + return TOMCAT; + } + + @Override + public String getDomain() { + return ConfigConst.OBSERVABILITY; + } + + @Override + public int order() { + return Order.HIGH.getOrder(); + } +} diff --git a/plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/advice/FilterChainPoints.java b/plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/advice/FilterChainPoints.java new file mode 100644 index 000000000..718eeb976 --- /dev/null +++ b/plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/advice/FilterChainPoints.java @@ -0,0 +1,51 @@ +package com.megaease.easeagent.plugin.tomcat.advice; + +import com.megaease.easeagent.plugin.CodeVersion; +import com.megaease.easeagent.plugin.Points; +import com.megaease.easeagent.plugin.api.config.ConfigConst; +import com.megaease.easeagent.plugin.matcher.ClassMatcher; +import com.megaease.easeagent.plugin.matcher.IClassMatcher; +import com.megaease.easeagent.plugin.matcher.IMethodMatcher; +import com.megaease.easeagent.plugin.matcher.MethodMatcher; + +import java.util.Set; + +public class FilterChainPoints implements Points { + private static final String FILTER_NAME = "jakarta.servlet.FilterChain"; + private static final String HTTP_SERVLET_NAME = "jakarta.servlet.http.HttpServlet"; + static final String SERVLET_REQUEST = "jakarta.servlet.ServletRequest"; + static final String SERVLET_RESPONSE = "jakarta.servlet.ServletResponse"; + + private final static CodeVersion VERSIONS = CodeVersion.builder() + .key(ConfigConst.CodeVersion.KEY_JDK) + .add(ConfigConst.CodeVersion.VERSION_JDK17).build(); + + @Override + public CodeVersion codeVersions() { + return VERSIONS; + } + + @Override + public IClassMatcher getClassMatcher() { + return ClassMatcher.builder() + .hasInterface(FILTER_NAME) + .build().or(ClassMatcher.builder() + .hasSuperClass(HTTP_SERVLET_NAME) + .build()); + } + + @Override + public Set getMethodMatcher() { + return MethodMatcher.multiBuilder() + .match(MethodMatcher.builder().named("doFilter") + .arg(0, SERVLET_REQUEST) + .arg(1, SERVLET_RESPONSE) + .or() + .named("service") + .arg(0, SERVLET_REQUEST) + .arg(1, SERVLET_RESPONSE) + .qualifier("default") + .build()) + .build(); + } +} diff --git a/plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/interceptor/BaseServletInterceptor.java b/plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/interceptor/BaseServletInterceptor.java new file mode 100644 index 000000000..d2d018dba --- /dev/null +++ b/plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/interceptor/BaseServletInterceptor.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2017, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.tomcat.interceptor; + +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import com.megaease.easeagent.plugin.api.Context; +import com.megaease.easeagent.plugin.tomcat.utils.InternalAsyncListener; +import com.megaease.easeagent.plugin.tomcat.utils.ServletUtils; +import com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +public abstract class BaseServletInterceptor implements NonReentrantInterceptor { + + @Override + public void doBefore(MethodInfo methodInfo, Context context) { + HttpServletRequest httpServletRequest = (HttpServletRequest) methodInfo.getArgs()[0]; + ServletUtils.startTime(httpServletRequest); + } + + @Override + public void doAfter(MethodInfo methodInfo, Context context) { + HttpServletRequest httpServletRequest = (HttpServletRequest) methodInfo.getArgs()[0]; + final long start = ServletUtils.startTime(httpServletRequest); + if (ServletUtils.markProcessed(httpServletRequest, getAfterMark())) { + return; + } + String httpRoute = ServletUtils.getHttpRouteAttributeFromRequest(httpServletRequest); + final String key = httpServletRequest.getMethod() + " " + httpRoute; + HttpServletResponse httpServletResponse = (HttpServletResponse) methodInfo.getArgs()[1]; + if (methodInfo.getThrowable() != null) { + internalAfter(methodInfo.getThrowable(), key, httpServletRequest, httpServletResponse, start); + } else if (httpServletRequest.isAsyncStarted()) { + httpServletRequest.getAsyncContext().addListener(new InternalAsyncListener( + asyncEvent -> { + HttpServletResponse suppliedResponse = (HttpServletResponse) asyncEvent.getSuppliedResponse(); + internalAfter(asyncEvent.getThrowable(), key, httpServletRequest, suppliedResponse, start); + } + + ) + ); + } else { + internalAfter(null, key, httpServletRequest, httpServletResponse, start); + } + } + + protected abstract String getAfterMark(); + + abstract void internalAfter(Throwable throwable, String key, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, long start); +} diff --git a/plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/interceptor/FilterChainForwardedInterceptor.java b/plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/interceptor/FilterChainForwardedInterceptor.java new file mode 100644 index 000000000..00bcfeb99 --- /dev/null +++ b/plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/interceptor/FilterChainForwardedInterceptor.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.tomcat.interceptor; + +import com.megaease.easeagent.plugin.annotation.AdviceTo; +import com.megaease.easeagent.plugin.api.Cleaner; +import com.megaease.easeagent.plugin.api.Context; +import com.megaease.easeagent.plugin.api.config.ConfigConst; +import com.megaease.easeagent.plugin.enums.Order; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor; +import com.megaease.easeagent.plugin.tomcat.ForwardedPlugin; +import com.megaease.easeagent.plugin.tomcat.advice.FilterChainPoints; +import com.megaease.easeagent.plugin.tools.trace.HttpRequest; +import jakarta.servlet.http.HttpServletRequest; + +@AdviceTo(value = FilterChainPoints.class, qualifier = "default", plugin = ForwardedPlugin.class) +public class FilterChainForwardedInterceptor implements NonReentrantInterceptor { + private static final Object FORWARDED_KEY = new Object(); + + @Override + public void doBefore(MethodInfo methodInfo, Context context) { + HttpServletRequest httpServletRequest = (HttpServletRequest) methodInfo.getArgs()[0]; + HttpRequest httpRequest = new HttpServerRequest(httpServletRequest); + Cleaner cleaner = context.importForwardedHeaders(httpRequest); + context.put(FORWARDED_KEY, cleaner); + } + + @Override + public void doAfter(MethodInfo methodInfo, Context context) { + Cleaner cleaner = context.remove(FORWARDED_KEY); + if (cleaner != null) { + cleaner.close(); + } + } + + @Override + public String getType() { + return ConfigConst.PluginID.FORWARDED; + } + + @Override + public int order() { + return Order.FORWARDED.getOrder(); + } +} + diff --git a/plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/interceptor/FilterChainMetricInterceptor.java b/plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/interceptor/FilterChainMetricInterceptor.java new file mode 100644 index 000000000..2d59643f4 --- /dev/null +++ b/plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/interceptor/FilterChainMetricInterceptor.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2017, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.tomcat.interceptor; + +import com.megaease.easeagent.plugin.annotation.AdviceTo; +import com.megaease.easeagent.plugin.api.config.IPluginConfig; +import com.megaease.easeagent.plugin.api.metric.ServiceMetricRegistry; +import com.megaease.easeagent.plugin.api.metric.name.Tags; +import com.megaease.easeagent.plugin.enums.Order; +import com.megaease.easeagent.plugin.tomcat.TomcatPlugin; +import com.megaease.easeagent.plugin.tomcat.advice.FilterChainPoints; +import com.megaease.easeagent.plugin.tools.metrics.ServerMetric; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +@AdviceTo(value = FilterChainPoints.class, qualifier = "default", plugin = TomcatPlugin.class) +public class FilterChainMetricInterceptor extends BaseServletInterceptor { + private static final String AFTER_MARK = FilterChainMetricInterceptor.class.getName() + "$AfterMark"; + private static volatile ServerMetric SERVER_METRIC = null; + + @Override + public void init(IPluginConfig config, String className, String methodName, String methodDescriptor) { + SERVER_METRIC = ServiceMetricRegistry.getOrCreate(config, new Tags("application", "http-request", "url"), ServerMetric.SERVICE_METRIC_SUPPLIER); + } + + @Override + protected String getAfterMark() { + return AFTER_MARK; + } + + @Override + public void internalAfter(Throwable throwable, String key, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, long start) { + long end = System.currentTimeMillis(); + SERVER_METRIC.collectMetric(key, httpServletResponse.getStatus(), throwable, start, end); + } + + @Override + public String getType() { + return Order.METRIC.getName(); + } + + @Override + public int order() { + return Order.METRIC.getOrder(); + } +} diff --git a/plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/interceptor/FilterChainTraceInterceptor.java b/plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/interceptor/FilterChainTraceInterceptor.java new file mode 100644 index 000000000..0a8f3bab6 --- /dev/null +++ b/plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/interceptor/FilterChainTraceInterceptor.java @@ -0,0 +1,171 @@ +package com.megaease.easeagent.plugin.tomcat.interceptor; + +import com.megaease.easeagent.plugin.annotation.AdviceTo; +import com.megaease.easeagent.plugin.api.Context; +import com.megaease.easeagent.plugin.api.context.RequestContext; +import com.megaease.easeagent.plugin.api.trace.Span; +import com.megaease.easeagent.plugin.enums.Order; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import com.megaease.easeagent.plugin.interceptor.NonReentrantInterceptor; +import com.megaease.easeagent.plugin.tomcat.TomcatPlugin; +import com.megaease.easeagent.plugin.tomcat.advice.FilterChainPoints; +import com.megaease.easeagent.plugin.tomcat.utils.ServletUtils; +import com.megaease.easeagent.plugin.tools.trace.HttpRequest; +import com.megaease.easeagent.plugin.tools.trace.HttpResponse; +import com.megaease.easeagent.plugin.tools.trace.HttpUtils; +import com.megaease.easeagent.plugin.tools.trace.TraceConst; +import jakarta.servlet.*; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import java.util.concurrent.atomic.AtomicBoolean; + +@AdviceTo(value = FilterChainPoints.class, plugin = TomcatPlugin.class) +public class FilterChainTraceInterceptor implements NonReentrantInterceptor { + private static final String AFTER_MARK = FilterChainTraceInterceptor.class.getName() + "$AfterMark"; + private static final String ERROR_KEY = "error"; + + @Override + public void doBefore(MethodInfo methodInfo, Context context) { + HttpServletRequest httpServletRequest = (HttpServletRequest) methodInfo.getArgs()[0]; + RequestContext requestContext = (RequestContext) httpServletRequest.getAttribute(ServletUtils.PROGRESS_CONTEXT); + if (requestContext != null) { + return; + } + HttpRequest httpRequest = new HttpServerRequest(httpServletRequest); + requestContext = context.serverReceive(httpRequest); + httpServletRequest.setAttribute(ServletUtils.PROGRESS_CONTEXT, requestContext); + HttpUtils.handleReceive(requestContext.span().start(), httpRequest); + } + + @Override + public void doAfter(MethodInfo methodInfo, Context context) { + HttpServletRequest httpServletRequest = (HttpServletRequest) methodInfo.getArgs()[0]; + if (ServletUtils.markProcessed(httpServletRequest, AFTER_MARK)) { + return; + } + HttpServletResponse httpServletResponse = (HttpServletResponse) methodInfo.getArgs()[1]; + RequestContext requestContext = (RequestContext) httpServletRequest.getAttribute(ServletUtils.PROGRESS_CONTEXT); + try { + Span span = requestContext.span(); + if (!httpServletRequest.isAsyncStarted()) { + span.tag(TraceConst.HTTP_TAG_ROUTE, ServletUtils.getHttpRouteAttributeFromRequest(httpServletRequest)); + HttpUtils.finish(span, new Response(methodInfo.getThrowable(), httpServletRequest, httpServletResponse)); + } else if (methodInfo.getThrowable() != null) { + span.error(methodInfo.getThrowable()); + span.finish(); + } else { + httpServletRequest.getAsyncContext().addListener(new TracingAsyncListener(requestContext), httpServletRequest, httpServletResponse); + } + } finally { + requestContext.scope().close(); + } + } + + @Override + public int order() { + return Order.TRACING.getOrder(); + } + + + public static class Response implements HttpResponse { + private final Throwable caught; + private final HttpServletRequest httpServletRequest; + private final HttpServletResponse httpServletResponse; + + public Response(Throwable caught, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) { + this.caught = caught; + this.httpServletRequest = httpServletRequest; + this.httpServletResponse = httpServletResponse; + } + + @Override + public String method() { + return httpServletRequest.getMethod(); + } + + @Override + public String route() { + Object maybeRoute = httpServletRequest.getAttribute(TraceConst.HTTP_ATTRIBUTE_ROUTE); + return maybeRoute instanceof String ? (String) maybeRoute : null; + } + + @Override + public int statusCode() { + if (httpServletResponse == null) { + return 0; + } + int result = httpServletResponse.getStatus(); + if (caught != null && result == 200) { + if (caught instanceof UnavailableException) { + return ((UnavailableException) caught).isPermanent() ? 404 : 503; + } else { + return 500; + } + } else { + return result; + } + } + + @Override + public Throwable maybeError() { + if (caught != null) { + return caught; + } + Object maybeError = httpServletRequest.getAttribute(ERROR_KEY); + if (maybeError instanceof Throwable) { + return (Throwable) maybeError; + } else { + maybeError = httpServletRequest.getAttribute("javax.servlet.error.exception"); + return maybeError instanceof Throwable ? (Throwable) maybeError : null; + } + } + + @Override + public String header(String name) { + return httpServletResponse.getHeader(name); + } + } + + public static final class TracingAsyncListener implements AsyncListener { + final RequestContext requestContext; + final AtomicBoolean sendHandled = new AtomicBoolean(); + + TracingAsyncListener(RequestContext requestContext) { + this.requestContext = requestContext; + } + + public void onComplete(AsyncEvent e) { + HttpServletRequest req = (HttpServletRequest) e.getSuppliedRequest(); + if (sendHandled.compareAndSet(false, true)) { + HttpServletResponse res = (HttpServletResponse) e.getSuppliedResponse(); + Response response = new Response(e.getThrowable(), req, res); + HttpUtils.save(requestContext.span(), response); + requestContext.finish(response); + } + + } + + public void onTimeout(AsyncEvent e) { + onError(e); + } + + public void onError(AsyncEvent e) { + ServletRequest request = e.getSuppliedRequest(); + if (request.getAttribute(ERROR_KEY) == null) { + request.setAttribute(ERROR_KEY, e.getThrowable()); + } + } + + public void onStartAsync(AsyncEvent e) { + AsyncContext eventAsyncContext = e.getAsyncContext(); + if (eventAsyncContext != null) { + eventAsyncContext.addListener(this, e.getSuppliedRequest(), e.getSuppliedResponse()); + } + } + + public String toString() { + return "TracingAsyncListener{" + this.requestContext + "}"; + } + } +} diff --git a/plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/interceptor/HttpServerRequest.java b/plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/interceptor/HttpServerRequest.java new file mode 100644 index 000000000..71071d225 --- /dev/null +++ b/plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/interceptor/HttpServerRequest.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2017, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.tomcat.interceptor; + +import com.megaease.easeagent.plugin.api.trace.Span; +import com.megaease.easeagent.plugin.tools.trace.HttpRequest; +import com.megaease.easeagent.plugin.tools.trace.TraceConst; + +import jakarta.servlet.http.HttpServletRequest; + +public class HttpServerRequest implements HttpRequest { + protected final HttpServletRequest delegate; + + public HttpServerRequest(HttpServletRequest httpServletRequest) { + this.delegate = httpServletRequest; + } + + @Override + public Span.Kind kind() { + return Span.Kind.SERVER; + } + + @Override + public String method() { + return delegate.getMethod(); + } + + @Override + public String path() { + return delegate.getRequestURI(); + } + + @Override + public String route() { + Object maybeRoute = this.delegate.getAttribute(TraceConst.HTTP_ATTRIBUTE_ROUTE); + return maybeRoute instanceof String ? (String) maybeRoute : null; + } + + @Override + public String getRemoteAddr() { + return this.delegate.getRemoteAddr(); + } + + @Override + public int getRemotePort() { + return this.delegate.getRemotePort(); + } + + @Override + public String header(String name) { + return this.delegate.getHeader(name); + } + + @Override + public boolean cacheScope() { + return false; + } + + @Override + public void setHeader(String name, String value) { +// this.delegate.setAttribute(name, value); + } +} + diff --git a/plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/interceptor/TomcatAccessLogServerInfo.java b/plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/interceptor/TomcatAccessLogServerInfo.java new file mode 100644 index 000000000..d7b2cc2e9 --- /dev/null +++ b/plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/interceptor/TomcatAccessLogServerInfo.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2017, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.tomcat.interceptor; + +import com.megaease.easeagent.plugin.tomcat.utils.ServletUtils; +import com.megaease.easeagent.plugin.tools.metrics.AccessLogServerInfo; +import com.megaease.easeagent.plugin.utils.common.StringUtils; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + +public class TomcatAccessLogServerInfo implements AccessLogServerInfo { + + private HttpServletRequest request; + private HttpServletResponse response; + + public void load(HttpServletRequest request, HttpServletResponse response) { + this.request = request; + this.response = response; + } + + @Override + public String getMethod() { + return request.getMethod(); + } + + @Override + public String getHeader(String key) { + return request.getHeader(key); + } + + @Override + public String getRemoteAddr() { + return request.getRemoteAddr(); + } + + @Override + public String getRequestURI() { + return request.getRequestURI(); + } + + @Override + public int getResponseBufferSize() { + return response.getBufferSize(); + } + + @Override + public String getMatchURL() { + String matchURL = ServletUtils.matchUrlBySpringWeb(request); + if (StringUtils.isEmpty(matchURL)) { + return ""; + } + return request.getMethod() + " " + matchURL; + } + + @Override + public Map findHeaders() { + Map headers = new HashMap<>(); + final Enumeration headerNames = request.getHeaderNames(); + while (headerNames.hasMoreElements()) { + final String key = headerNames.nextElement(); + headers.put(key, request.getHeader(key)); + } + return headers; + } + + @Override + public Map findQueries() { + return ServletUtils.getQueries4SingleValue(request); + } + + @Override + public String getStatusCode() { + return String.valueOf(response.getStatus()); + } + +} diff --git a/plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/interceptor/TomcatHttpLogInterceptor.java b/plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/interceptor/TomcatHttpLogInterceptor.java new file mode 100644 index 000000000..4d4811233 --- /dev/null +++ b/plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/interceptor/TomcatHttpLogInterceptor.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2017, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.tomcat.interceptor; + +import com.megaease.easeagent.plugin.annotation.AdviceTo; +import com.megaease.easeagent.plugin.api.Context; +import com.megaease.easeagent.plugin.api.context.RequestContext; +import com.megaease.easeagent.plugin.api.logging.AccessLogInfo; +import com.megaease.easeagent.plugin.api.trace.Span; +import com.megaease.easeagent.plugin.bridge.EaseAgent; +import com.megaease.easeagent.plugin.enums.Order; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import com.megaease.easeagent.plugin.tomcat.AccessPlugin; +import com.megaease.easeagent.plugin.tomcat.advice.FilterChainPoints; +import com.megaease.easeagent.plugin.tomcat.utils.ServletUtils; +import com.megaease.easeagent.plugin.tools.metrics.AccessLogServerInfo; +import com.megaease.easeagent.plugin.tools.metrics.HttpLog; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +@AdviceTo(value = FilterChainPoints.class, plugin = AccessPlugin.class) +public class TomcatHttpLogInterceptor extends BaseServletInterceptor { + private static final String BEFORE_MARK = TomcatHttpLogInterceptor.class.getName() + "$BeforeMark"; + private static final String AFTER_MARK = TomcatHttpLogInterceptor.class.getName() + "$AfterMark"; + private final HttpLog httpLog = new HttpLog(); + + public AccessLogServerInfo serverInfo(HttpServletRequest request, HttpServletResponse response) { + TomcatAccessLogServerInfo serverInfo = (TomcatAccessLogServerInfo) request.getAttribute(TomcatAccessLogServerInfo.class.getName()); + if (serverInfo == null) { + serverInfo = new TomcatAccessLogServerInfo(); + request.setAttribute(TomcatAccessLogServerInfo.class.getName(), serverInfo); + } + serverInfo.load(request, response); + return serverInfo; + } + + private Span getSpan(HttpServletRequest httpServletRequest, Context context) { + RequestContext requestContext = (RequestContext) httpServletRequest.getAttribute(ServletUtils.PROGRESS_CONTEXT); + if (requestContext != null) { + return requestContext.span(); + } + return context.currentTracing().currentSpan(); + } + + private String getSystem() { + return EaseAgent.getConfig("system"); + } + + private String getServiceName() { + return EaseAgent.getConfig("name"); + } + + + @Override + public void doBefore(MethodInfo methodInfo, Context context) { + HttpServletRequest httpServletRequest = (HttpServletRequest) methodInfo.getArgs()[0]; + if (ServletUtils.markProcessed(httpServletRequest, BEFORE_MARK)) { + return; + } + HttpServletResponse httpServletResponse = (HttpServletResponse) methodInfo.getArgs()[1]; + Long beginTime = ServletUtils.startTime(httpServletRequest); + Span span = getSpan(httpServletRequest, context); + AccessLogServerInfo serverInfo = this.serverInfo(httpServletRequest, httpServletResponse); + AccessLogInfo accessLog = this.httpLog.prepare(getSystem(), getServiceName(), beginTime, span, serverInfo); + httpServletRequest.setAttribute(AccessLogInfo.class.getName(), accessLog); + } + + @Override + protected String getAfterMark() { + return AFTER_MARK; + } + + @Override + void internalAfter(Throwable throwable, String key, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, long start) { + Long beginTime = ServletUtils.startTime(httpServletRequest); + AccessLogInfo accessLog = (AccessLogInfo) httpServletRequest.getAttribute(AccessLogInfo.class.getName()); + AccessLogServerInfo serverInfo = this.serverInfo(httpServletRequest, httpServletResponse); + this.httpLog.finish(accessLog, throwable == null, beginTime, serverInfo); + EaseAgent.agentReport.report(accessLog); + } + + @Override + public String getType() { + return Order.LOG.getName(); + } + + @Override + public int order() { + return Order.LOG.getOrder(); + } +} diff --git a/plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/utils/InternalAsyncListener.java b/plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/utils/InternalAsyncListener.java new file mode 100644 index 000000000..9a0669084 --- /dev/null +++ b/plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/utils/InternalAsyncListener.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2017, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.tomcat.utils; + +import jakarta.servlet.AsyncContext; +import jakarta.servlet.AsyncEvent; +import jakarta.servlet.AsyncListener; + +import java.util.function.Consumer; + +public class InternalAsyncListener implements AsyncListener { + + private final Consumer consumer; + + public InternalAsyncListener(Consumer consumer) { + this.consumer = consumer; + } + + @Override + public void onComplete(AsyncEvent event) { + this.consumer.accept(event); + } + + @Override + public void onTimeout(AsyncEvent event) { + } + + @Override + public void onError(AsyncEvent event) { + } + + @Override + public void onStartAsync(AsyncEvent event) { + AsyncContext eventAsyncContext = event.getAsyncContext(); + if (eventAsyncContext != null) { + eventAsyncContext.addListener(this, event.getSuppliedRequest(), event.getSuppliedResponse()); + } + } +} diff --git a/plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/utils/ServletUtils.java b/plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/utils/ServletUtils.java new file mode 100644 index 000000000..68c7fb5d9 --- /dev/null +++ b/plugins/tomcat-jdk17/src/main/java/com/megaease/easeagent/plugin/tomcat/utils/ServletUtils.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2017, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.tomcat.utils; + +import com.megaease.easeagent.plugin.api.logging.Logger; +import com.megaease.easeagent.plugin.bridge.EaseAgent; +import com.megaease.easeagent.plugin.tomcat.interceptor.FilterChainTraceInterceptor; +import com.megaease.easeagent.plugin.utils.ClassUtils; +import jakarta.servlet.http.HttpServletRequest; +import lombok.SneakyThrows; + +import java.net.URLDecoder; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +public class ServletUtils { + public static final Logger LOGGER = EaseAgent.getLogger(ServletUtils.class); + public static final String START_TIME = ServletUtils.class.getName() + "$StartTime"; + public static final String PROGRESS_CONTEXT = FilterChainTraceInterceptor.class.getName() + ".RequestContext"; + public static final String HANDLER_MAPPING_CLASS = "org.springframework.web.servlet.HandlerMapping"; + public static final String BEST_MATCHING_PATTERN_ATTRIBUTE_FIELD_NAME = "BEST_MATCHING_PATTERN_ATTRIBUTE"; + public static final String BEST_MATCHING_PATTERN_ATTRIBUTE; + + static { + String pattern; + Object field = ClassUtils.getStaticField(HANDLER_MAPPING_CLASS, BEST_MATCHING_PATTERN_ATTRIBUTE_FIELD_NAME); + if (field == null) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("class<{}>.{} not found ", HANDLER_MAPPING_CLASS, BEST_MATCHING_PATTERN_ATTRIBUTE_FIELD_NAME); + } + pattern = "org.springframework.web.servlet.HandlerMapping.bestMatchingPattern"; + } else if (field instanceof String) { + pattern = (String) field; + } else { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("class<{}>.{} is not String", HANDLER_MAPPING_CLASS, BEST_MATCHING_PATTERN_ATTRIBUTE_FIELD_NAME); + } + pattern = "org.springframework.web.servlet.HandlerMapping.bestMatchingPattern"; + } + BEST_MATCHING_PATTERN_ATTRIBUTE = pattern; + } + + public static String matchUrlBySpringWeb(HttpServletRequest request) { + return (String) request.getAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE); + } + + public static String getHttpRouteAttributeFromRequest(HttpServletRequest request) { + Object httpRoute = request.getAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE); + return httpRoute != null ? httpRoute.toString() : ""; + } + + public static boolean markProcessed(HttpServletRequest request, String mark) { + if (request.getAttribute(mark) != null) { + return true; + } + request.setAttribute(mark, "m"); + return false; + } + + public static long startTime(HttpServletRequest httpServletRequest) { + Object startObj = httpServletRequest.getAttribute(START_TIME); + Long start; + if (startObj == null) { + start = System.currentTimeMillis(); + httpServletRequest.setAttribute(START_TIME, start); + } else { + start = (Long) startObj; + } + return start; + } + + + @SneakyThrows + public static Map> getQueries(HttpServletRequest httpServletRequest) { + Map> map = new HashMap<>(); + String queryString = httpServletRequest.getQueryString(); + if (queryString == null || queryString.isEmpty()) { + return map; + } + String[] pairs = queryString.split("&"); + for (String pair : pairs) { + int idx = pair.indexOf("="); + String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), "UTF-8") : pair; + if (!map.containsKey(key)) { + map.put(key, new LinkedList<>()); + } + String value = idx > 0 && pair.length() > idx + 1 ? URLDecoder.decode(pair.substring(idx + 1), "UTF-8") : null; + map.get(key).add(value); + } + return map; + } + + public static Map getQueries4SingleValue(HttpServletRequest httpServletRequest) { + Map> map = getQueries(httpServletRequest); + Map singleValueMap = new HashMap<>(); + map.forEach((key, values) -> { + if (values != null && values.size() > 0) { + singleValueMap.put(key, values.get(0)); + } + }); + return singleValueMap; + } +} diff --git a/plugins/tomcat-jdk17/src/test/java/com/megaease/easeagent/plugin/tomcat/interceptor/BaseServletInterceptorTest.java b/plugins/tomcat-jdk17/src/test/java/com/megaease/easeagent/plugin/tomcat/interceptor/BaseServletInterceptorTest.java new file mode 100644 index 000000000..281606c07 --- /dev/null +++ b/plugins/tomcat-jdk17/src/test/java/com/megaease/easeagent/plugin/tomcat/interceptor/BaseServletInterceptorTest.java @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.tomcat.interceptor; + +import com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner; +import com.megaease.easeagent.plugin.bridge.EaseAgent; +import com.megaease.easeagent.plugin.enums.Order; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import com.megaease.easeagent.plugin.tomcat.utils.ServletUtils; +import jakarta.servlet.AsyncContext; +import jakarta.servlet.AsyncEvent; +import jakarta.servlet.AsyncListener; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.mock.web.MockAsyncContext; +import org.springframework.mock.web.MockHttpServletRequest; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; + +import static org.junit.Assert.*; + +@RunWith(EaseAgentJunit4ClassRunner.class) +public class BaseServletInterceptorTest { + + @Test + public void doBefore() { + HttpServletRequest httpServletRequest = TestServletUtils.buildMockRequest(); + HttpServletResponse response = TestServletUtils.buildMockResponse(); + MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpServletRequest, response}).build(); + + MockBaseServletInterceptor mockBaseServletInterceptor = new MockBaseServletInterceptor(); + mockBaseServletInterceptor.doBefore(methodInfo, EaseAgent.getContext()); + assertNotNull(httpServletRequest.getAttribute(ServletUtils.START_TIME)); + } + + @Test + public void doAfter() { + internalAfter(null); + } + + @Test + public void getAfterMark() { + MockBaseServletInterceptor mockBaseServletInterceptor = new MockBaseServletInterceptor(); + assertEquals(MockBaseServletInterceptor.BEFORE_MARK, mockBaseServletInterceptor.getAfterMark()); + } + + @Test + public void internalAfter() { + internalAfter(new RuntimeException("test error")); + } + + private void internalAfter(Throwable error) { + HttpServletRequest httpServletRequest = TestServletUtils.buildMockRequest(); + HttpServletResponse response = TestServletUtils.buildMockResponse(); + MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpServletRequest, response}).throwable(error).build(); + + MockBaseServletInterceptor mockBaseServletInterceptor = new MockBaseServletInterceptor(); + mockBaseServletInterceptor.throwable = error; + mockBaseServletInterceptor.key = TestConst.METHOD + " " + TestConst.ROUTE; + mockBaseServletInterceptor.httpServletRequest = httpServletRequest; + mockBaseServletInterceptor.httpServletResponse = response; + mockBaseServletInterceptor.doBefore(methodInfo, EaseAgent.getContext()); + mockBaseServletInterceptor.start = (long) httpServletRequest.getAttribute(ServletUtils.START_TIME); + mockBaseServletInterceptor.doAfter(methodInfo, EaseAgent.getContext()); + assertTrue(mockBaseServletInterceptor.isRan.get()); + } + + + @Test + public void testAsync() throws InterruptedException { + runAsyncOne(AsyncContext::complete, null); + + String errorInfo = "timeout eror"; + RuntimeException error = new RuntimeException(errorInfo); + runAsyncOne(asyncContext -> { + AsyncEvent asyncEvent = new AsyncEvent(asyncContext, asyncContext.getRequest(), asyncContext.getResponse(), error); + MockAsyncContext mockAsyncContext = (MockAsyncContext) asyncContext; + for (AsyncListener asyncListener : mockAsyncContext.getListeners()) { + try { + asyncListener.onComplete(asyncEvent); + } catch (IOException e) { + throw new RuntimeException("error"); + } + } + }, error); + } + + private void runAsyncOne(Consumer asyncContextConsumer, Throwable error) throws InterruptedException { + MockHttpServletRequest httpServletRequest = TestServletUtils.buildMockRequest(); + HttpServletResponse response = TestServletUtils.buildMockResponse(); + MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpServletRequest, response}).build(); + MockBaseServletInterceptor mockBaseServletInterceptor = new MockBaseServletInterceptor(); + mockBaseServletInterceptor.key = TestConst.METHOD + " " + TestConst.ROUTE; + mockBaseServletInterceptor.httpServletRequest = httpServletRequest; + mockBaseServletInterceptor.httpServletResponse = response; + mockBaseServletInterceptor.throwable = error; + + mockBaseServletInterceptor.doBefore(methodInfo, EaseAgent.getContext()); + mockBaseServletInterceptor.start = (long) httpServletRequest.getAttribute(ServletUtils.START_TIME); + httpServletRequest.setAsyncSupported(true); + final AsyncContext asyncContext = httpServletRequest.startAsync(httpServletRequest, response); + mockBaseServletInterceptor.doAfter(methodInfo, EaseAgent.getContext()); + + Thread thread = new Thread(() -> asyncContextConsumer.accept(asyncContext)); + thread.start(); + thread.join(); + assertTrue(mockBaseServletInterceptor.isRan.get()); + } + + private static class MockBaseServletInterceptor extends BaseServletInterceptor { + private static final String BEFORE_MARK = MockBaseServletInterceptor.class.getName() + "$BeforeMark"; + private AtomicBoolean isRan = new AtomicBoolean(false); + private Throwable throwable; + private String key; + private HttpServletRequest httpServletRequest; + private HttpServletResponse httpServletResponse; + private long start; + + @Override + public int order() { + return Order.HIGH.getOrder(); + } + + + @Override + protected String getAfterMark() { + return BEFORE_MARK; + } + + @Override + void internalAfter(Throwable throwable, String key, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, long start) { + isRan.set(true); + if (this.throwable == null) { + assertNull(throwable); + } else { + assertSame(this.throwable, throwable); + } + assertEquals(this.key, key); + assertSame(this.httpServletRequest, httpServletRequest); + assertSame(this.httpServletResponse, httpServletResponse); + assertEquals(this.start, start); + } + } +} diff --git a/plugins/tomcat-jdk17/src/test/java/com/megaease/easeagent/plugin/tomcat/interceptor/FilterChainForwardedInterceptorTest.java b/plugins/tomcat-jdk17/src/test/java/com/megaease/easeagent/plugin/tomcat/interceptor/FilterChainForwardedInterceptorTest.java new file mode 100644 index 000000000..f3d53a8d5 --- /dev/null +++ b/plugins/tomcat-jdk17/src/test/java/com/megaease/easeagent/plugin/tomcat/interceptor/FilterChainForwardedInterceptorTest.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.tomcat.interceptor; + +import com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner; +import com.megaease.easeagent.plugin.api.config.ConfigConst; +import com.megaease.easeagent.plugin.bridge.EaseAgent; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import jakarta.servlet.http.HttpServletRequest; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +@RunWith(EaseAgentJunit4ClassRunner.class) +public class FilterChainForwardedInterceptorTest { + + public void testForwarded() { + HttpServletRequest httpServletRequest = TestServletUtils.buildMockRequest(); + MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpServletRequest}).build(); + FilterChainForwardedInterceptor filterChainForwardedInterceptor = new FilterChainForwardedInterceptor(); + filterChainForwardedInterceptor.doBefore(methodInfo, EaseAgent.getContext()); + assertEquals(TestConst.FORWARDED_VALUE, EaseAgent.getContext().get(TestConst.FORWARDED_NAME)); + filterChainForwardedInterceptor.doAfter(methodInfo, EaseAgent.getContext()); + assertNull(EaseAgent.getContext().get(TestConst.FORWARDED_NAME)); + } + + @Test + public void doBefore() { + testForwarded(); + } + + @Test + public void doAfter() { + testForwarded(); + } + + @Test + public void getType() { + FilterChainForwardedInterceptor filterChainForwardedInterceptor = new FilterChainForwardedInterceptor(); + assertEquals(ConfigConst.PluginID.FORWARDED, filterChainForwardedInterceptor.getType()); + } +} diff --git a/plugins/tomcat-jdk17/src/test/java/com/megaease/easeagent/plugin/tomcat/interceptor/FilterChainMetricInterceptorTest.java b/plugins/tomcat-jdk17/src/test/java/com/megaease/easeagent/plugin/tomcat/interceptor/FilterChainMetricInterceptorTest.java new file mode 100644 index 000000000..7f0e95de8 --- /dev/null +++ b/plugins/tomcat-jdk17/src/test/java/com/megaease/easeagent/plugin/tomcat/interceptor/FilterChainMetricInterceptorTest.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.tomcat.interceptor; + +import com.megaease.easeagent.mock.plugin.api.MockEaseAgent; +import com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner; +import com.megaease.easeagent.mock.plugin.api.utils.TagVerifier; +import com.megaease.easeagent.mock.report.impl.LastJsonReporter; +import com.megaease.easeagent.plugin.api.config.IPluginConfig; +import com.megaease.easeagent.plugin.api.metric.ServiceMetric; +import com.megaease.easeagent.plugin.bridge.EaseAgent; +import com.megaease.easeagent.plugin.enums.Order; +import com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import com.megaease.easeagent.plugin.tomcat.TomcatPlugin; +import jakarta.servlet.http.HttpServletResponse; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.mock.web.MockHttpServletRequest; + +import java.util.Map; +import java.util.Objects; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +@RunWith(EaseAgentJunit4ClassRunner.class) +public class FilterChainMetricInterceptorTest { + + @Test + public void init() { + FilterChainMetricInterceptor doFilterMetricInterceptor = new FilterChainMetricInterceptor(); + TomcatPlugin httpServletPlugin = new TomcatPlugin(); + IPluginConfig iPluginConfig = EaseAgent.getConfig(httpServletPlugin.getDomain(), httpServletPlugin.getNamespace(), doFilterMetricInterceptor.getType()); + doFilterMetricInterceptor.init(iPluginConfig, "", "", ""); + assertNotNull(AgentFieldReflectAccessor.getFieldValue(doFilterMetricInterceptor, "SERVER_METRIC")); + + } + + @Test + public void getAfterMark() { + FilterChainMetricInterceptor doFilterMetricInterceptor = new FilterChainMetricInterceptor(); + assertNotNull(doFilterMetricInterceptor.getAfterMark()); + + } + + public Map getMetric(LastJsonReporter lastJsonReporter) throws InterruptedException { + return lastJsonReporter.flushAndOnlyOne(); + } + + @Test + public void internalAfter() throws InterruptedException { + MockHttpServletRequest httpServletRequest = TestServletUtils.buildMockRequest(); + HttpServletResponse response = TestServletUtils.buildMockResponse(); + + FilterChainMetricInterceptor doFilterMetricInterceptor = new FilterChainMetricInterceptor(); + TomcatPlugin httpServletPlugin = new TomcatPlugin(); + IPluginConfig iPluginConfig = EaseAgent.getConfig(httpServletPlugin.getDomain(), httpServletPlugin.getNamespace(), doFilterMetricInterceptor.getType()); + int interval = iPluginConfig.getInt("interval"); + assertEquals(1, interval); + doFilterMetricInterceptor.init(iPluginConfig, "", "", ""); + + MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpServletRequest, response}).build(); + doFilterMetricInterceptor.doBefore(methodInfo, EaseAgent.getContext()); + TagVerifier tagVerifier = new TagVerifier() + .add("category", "application") + .add("type", "http-request") + .add("url", TestConst.METHOD + " " + TestConst.ROUTE); + LastJsonReporter lastJsonReporter = MockEaseAgent.lastMetricJsonReporter(tagVerifier::verifyAnd); + + doFilterMetricInterceptor.doAfter(methodInfo, EaseAgent.getContext()); + Map metric = getMetric(lastJsonReporter); + + assertEquals(1, (int) metric.get("cnt")); + assertEquals(0, (int) metric.get("errcnt")); + + MockEaseAgent.cleanMetric(Objects.requireNonNull(AgentFieldReflectAccessor.getFieldValue(doFilterMetricInterceptor, "SERVER_METRIC"))); + lastJsonReporter.clean(); + httpServletRequest = TestServletUtils.buildMockRequest(); + response = TestServletUtils.buildMockResponse(); + methodInfo = MethodInfo.builder().args(new Object[]{httpServletRequest, response}).throwable(new RuntimeException("test error")).build(); + doFilterMetricInterceptor.doBefore(methodInfo, EaseAgent.getContext()); + doFilterMetricInterceptor.doAfter(methodInfo, EaseAgent.getContext()); + + metric = getMetric(lastJsonReporter); + assertEquals(1, (int) metric.get("cnt")); + assertEquals(1, (int) metric.get("errcnt")); + } + + @Test + public void getType() { + FilterChainMetricInterceptor doFilterMetricInterceptor = new FilterChainMetricInterceptor(); + assertEquals(Order.METRIC.getName(), doFilterMetricInterceptor.getType()); + + } +} diff --git a/plugins/tomcat-jdk17/src/test/java/com/megaease/easeagent/plugin/tomcat/interceptor/FilterChainTraceInterceptorTest.java b/plugins/tomcat-jdk17/src/test/java/com/megaease/easeagent/plugin/tomcat/interceptor/FilterChainTraceInterceptorTest.java new file mode 100644 index 000000000..6b83ed7c4 --- /dev/null +++ b/plugins/tomcat-jdk17/src/test/java/com/megaease/easeagent/plugin/tomcat/interceptor/FilterChainTraceInterceptorTest.java @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.tomcat.interceptor; + +import com.megaease.easeagent.mock.plugin.api.MockEaseAgent; +import com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner; +import com.megaease.easeagent.plugin.api.Context; +import com.megaease.easeagent.plugin.api.context.RequestContext; +import com.megaease.easeagent.plugin.api.trace.Request; +import com.megaease.easeagent.plugin.api.trace.Setter; +import com.megaease.easeagent.plugin.api.trace.Span; +import com.megaease.easeagent.plugin.bridge.EaseAgent; +import com.megaease.easeagent.plugin.tomcat.utils.ServletUtils; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import com.megaease.easeagent.plugin.report.tracing.ReportSpan; +import com.megaease.easeagent.plugin.tools.trace.TraceConst; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.mock.web.MockAsyncContext; +import org.springframework.mock.web.MockHttpServletRequest; + +import jakarta.servlet.*; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import java.io.IOException; +import java.util.function.Consumer; + +import static org.junit.Assert.*; + +@RunWith(EaseAgentJunit4ClassRunner.class) +public class FilterChainTraceInterceptorTest { + + private void testTrace() { + HttpServletRequest httpServletRequest = TestServletUtils.buildMockRequest(); + HttpServletResponse response = TestServletUtils.buildMockResponse(); + MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpServletRequest, response}).build(); + + FilterChainTraceInterceptor doFilterTraceInterceptor = new FilterChainTraceInterceptor(); + doFilterTraceInterceptor.doBefore(methodInfo, EaseAgent.getContext()); + Object o = httpServletRequest.getAttribute(ServletUtils.PROGRESS_CONTEXT); + assertNotNull(o); + assertTrue(o instanceof RequestContext); + doFilterTraceInterceptor.doBefore(methodInfo, EaseAgent.getContext()); + Object o2 = httpServletRequest.getAttribute(ServletUtils.PROGRESS_CONTEXT); + assertNotNull(o2); + assertSame(o, o2); + doFilterTraceInterceptor.doAfter(methodInfo, EaseAgent.getContext()); + ReportSpan mockSpan = MockEaseAgent.getLastSpan(); + assertNotNull(mockSpan); + assertNull(mockSpan.parentId()); + checkServerSpan(mockSpan); + + MockEaseAgent.cleanLastSpan(); + doFilterTraceInterceptor.doAfter(methodInfo, EaseAgent.getContext()); + assertNull(MockEaseAgent.getLastSpan()); + } + + + private void checkServerSpan(ReportSpan mockSpan) { + assertEquals(Span.Kind.SERVER.name(), mockSpan.kind()); + assertEquals(TestConst.ROUTE, mockSpan.tag(TraceConst.HTTP_ATTRIBUTE_ROUTE)); + assertEquals(TestConst.METHOD, mockSpan.tag("http.method")); + assertEquals(TestConst.URL, mockSpan.tag("http.path")); + assertEquals(TestConst.REMOTE_ADDR, mockSpan.remoteEndpoint().ipv4()); + assertEquals(TestConst.REMOTE_PORT, mockSpan.remoteEndpoint().port()); + } + + @Test + public void testErrorTracing() { + HttpServletRequest httpServletRequest = TestServletUtils.buildMockRequest(); + HttpServletResponse response = TestServletUtils.buildMockResponse(); + String errorInfo = "test error"; + MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpServletRequest, response}).throwable(new RuntimeException(errorInfo)).build(); + FilterChainTraceInterceptor doFilterTraceInterceptor = new FilterChainTraceInterceptor(); + doFilterTraceInterceptor.doBefore(methodInfo, EaseAgent.getContext()); + doFilterTraceInterceptor.doAfter(methodInfo, EaseAgent.getContext()); + ReportSpan mockSpan = MockEaseAgent.getLastSpan(); + assertNotNull(mockSpan); + assertEquals(Span.Kind.SERVER.name(), mockSpan.kind()); + assertNull(mockSpan.parentId()); + checkServerSpan(mockSpan); + assertEquals(errorInfo, mockSpan.tag("error")); + } + + @Test + public void testHasPassHeader() { + MockHttpServletRequest httpServletRequest = TestServletUtils.buildMockRequest(); + Context context = EaseAgent.getContext(); + RequestContext requestContext = context.clientRequest(new MockClientRequest(httpServletRequest::addHeader)); + requestContext.scope().close(); + assertFalse(context.currentTracing().hasCurrentSpan()); + requestContext.span().abandon(); + + HttpServletResponse response = TestServletUtils.buildMockResponse(); + MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpServletRequest, response}).build(); + FilterChainTraceInterceptor doFilterTraceInterceptor = new FilterChainTraceInterceptor(); + doFilterTraceInterceptor.doBefore(methodInfo, EaseAgent.getContext()); + doFilterTraceInterceptor.doAfter(methodInfo, EaseAgent.getContext()); + ReportSpan mockSpan = MockEaseAgent.getLastSpan(); + assertNotNull(mockSpan); + assertEquals(requestContext.span().traceIdString(), mockSpan.traceId()); + assertEquals(requestContext.span().spanIdString(), mockSpan.id()); + assertEquals(requestContext.span().parentIdString(), mockSpan.parentId()); + checkServerSpan(mockSpan); + } + + @Test + public void testAsync() throws InterruptedException { + ReportSpan mockSpan = runAsyncOne(AsyncContext::complete); + assertNotNull(mockSpan); + assertEquals(Span.Kind.SERVER.name(), mockSpan.kind()); + assertNull(mockSpan.parentId()); + checkServerSpan(mockSpan); + + String errorInfo = "timeout eror"; + mockSpan = runAsyncOne(asyncContext -> { + AsyncEvent asyncEvent = new AsyncEvent(asyncContext, asyncContext.getRequest(), asyncContext.getResponse(), new RuntimeException(errorInfo)); + MockAsyncContext mockAsyncContext = (MockAsyncContext) asyncContext; + for (AsyncListener asyncListener : mockAsyncContext.getListeners()) { + try { + asyncListener.onTimeout(asyncEvent); + } catch (IOException e) { + throw new RuntimeException("error"); + } + } + asyncContext.complete(); + }); + assertNotNull(mockSpan); + assertEquals(Span.Kind.SERVER.name(), mockSpan.kind()); + assertNull(mockSpan.parentId()); + checkServerSpan(mockSpan); + assertEquals(errorInfo, mockSpan.tag("error")); + } + + public ReportSpan runAsyncOne(Consumer asyncContextConsumer) throws InterruptedException { + MockHttpServletRequest httpServletRequest = TestServletUtils.buildMockRequest(); + HttpServletResponse response = TestServletUtils.buildMockResponse(); + MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpServletRequest, response}).build(); + FilterChainTraceInterceptor doFilterTraceInterceptor = new FilterChainTraceInterceptor(); + doFilterTraceInterceptor.doBefore(methodInfo, EaseAgent.getContext()); + httpServletRequest.setAsyncSupported(true); + final AsyncContext asyncContext = httpServletRequest.startAsync(httpServletRequest, response); + doFilterTraceInterceptor.doAfter(methodInfo, EaseAgent.getContext()); + + MockEaseAgent.cleanLastSpan(); + Thread thread = new Thread(() -> asyncContextConsumer.accept(asyncContext)); + thread.start(); + thread.join(); + return MockEaseAgent.getLastSpan(); + } + + @Test + public void doBefore() { + testTrace(); + } + + @Test + public void doAfter() { + testTrace(); + } + + public class MockClientRequest implements Request { + private final Setter setter; + + public MockClientRequest(Setter setter) { + this.setter = setter; + } + + @Override + public Span.Kind kind() { + return Span.Kind.CLIENT; + } + + @Override + public String header(String name) { + return null; + } + + @Override + public String name() { + return "test"; + } + + @Override + public boolean cacheScope() { + return false; + } + + @Override + public void setHeader(String name, String value) { + setter.setHeader(name, value); + } + } +} diff --git a/plugins/tomcat-jdk17/src/test/java/com/megaease/easeagent/plugin/tomcat/interceptor/HttpServerRequestTest.java b/plugins/tomcat-jdk17/src/test/java/com/megaease/easeagent/plugin/tomcat/interceptor/HttpServerRequestTest.java new file mode 100644 index 000000000..0c5420225 --- /dev/null +++ b/plugins/tomcat-jdk17/src/test/java/com/megaease/easeagent/plugin/tomcat/interceptor/HttpServerRequestTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.tomcat.interceptor; + +import com.megaease.easeagent.plugin.api.trace.Span; +import com.megaease.easeagent.plugin.tomcat.interceptor.HttpServerRequest; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class HttpServerRequestTest { + public HttpServerRequest build() { + return new HttpServerRequest(TestServletUtils.buildMockRequest()); + } + + @Test + public void kind() { + assertEquals(Span.Kind.SERVER, build().kind()); + } + + @Test + public void method() { + assertEquals(TestConst.METHOD, build().method()); + } + + @Test + public void path() { + assertEquals(TestConst.URL, build().path()); + } + + @Test + public void route() { + assertEquals(TestConst.ROUTE, build().route()); + } + + @Test + public void getRemoteAddr() { + assertEquals(TestConst.REMOTE_ADDR, build().getRemoteAddr()); + } + + @Test + public void getRemotePort() { + assertEquals(TestConst.REMOTE_PORT, build().getRemotePort()); + } + + @Test + public void header() { + assertEquals(TestConst.FORWARDED_VALUE, build().header(TestConst.FORWARDED_NAME)); + } + + @Test + public void cacheScope() { + assertEquals(false, build().cacheScope()); + } +} diff --git a/plugins/tomcat-jdk17/src/test/java/com/megaease/easeagent/plugin/tomcat/interceptor/ServletAccessLogInfoServerInfoTest.java b/plugins/tomcat-jdk17/src/test/java/com/megaease/easeagent/plugin/tomcat/interceptor/ServletAccessLogInfoServerInfoTest.java new file mode 100644 index 000000000..e9190f43c --- /dev/null +++ b/plugins/tomcat-jdk17/src/test/java/com/megaease/easeagent/plugin/tomcat/interceptor/ServletAccessLogInfoServerInfoTest.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.tomcat.interceptor; + +import com.megaease.easeagent.plugin.field.AgentFieldReflectAccessor; +import jakarta.servlet.http.HttpServletResponse; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; + +import java.util.Map; + +import static org.junit.Assert.*; + +public class ServletAccessLogInfoServerInfoTest { + + private TomcatAccessLogServerInfo loadMock() { + TomcatAccessLogServerInfo servletAccessLogServerInfo = new TomcatAccessLogServerInfo(); + MockHttpServletRequest httpServletRequest = TestServletUtils.buildMockRequest(); + HttpServletResponse response = TestServletUtils.buildMockResponse(); + servletAccessLogServerInfo.load(httpServletRequest, response); + return servletAccessLogServerInfo; + } + + @Test + public void load() { + TomcatAccessLogServerInfo serverInfo = loadMock(); + assertNotNull(AgentFieldReflectAccessor.getFieldValue(serverInfo, "request")); + assertNotNull(AgentFieldReflectAccessor.getFieldValue(serverInfo, "response")); + } + + @Test + public void getMethod() { + assertEquals(TestConst.METHOD, loadMock().getMethod()); + } + + @Test + public void getHeader() { + assertEquals(TestConst.FORWARDED_VALUE, loadMock().getHeader(TestConst.FORWARDED_NAME)); + } + + @Test + public void getRemoteAddr() { + assertEquals(TestConst.REMOTE_ADDR, loadMock().getRemoteAddr()); + } + + @Test + public void getRequestURI() { + assertEquals(TestConst.URL, loadMock().getRequestURI()); + } + + @Test + public void getResponseBufferSize() { + assertEquals(TestConst.RESPONSE_BUFFER_SIZE, loadMock().getResponseBufferSize()); + } + + @Test + public void getMatchURL() { + assertEquals(TestConst.METHOD + " " + TestConst.ROUTE, loadMock().getMatchURL()); + } + + @Test + public void findHeaders() { + assertEquals(TestConst.FORWARDED_VALUE, loadMock().findHeaders().get(TestConst.FORWARDED_NAME)); + } + + @Test + public void findQueries() { + Map queries = loadMock().findQueries(); + assertEquals("10", queries.get("q1")); + assertEquals("testq", queries.get("q2")); + } + + @Test + public void getStatusCode() { + assertEquals("200", loadMock().getStatusCode()); + } +} diff --git a/plugins/tomcat-jdk17/src/test/java/com/megaease/easeagent/plugin/tomcat/interceptor/TestConst.java b/plugins/tomcat-jdk17/src/test/java/com/megaease/easeagent/plugin/tomcat/interceptor/TestConst.java new file mode 100644 index 000000000..0e0139313 --- /dev/null +++ b/plugins/tomcat-jdk17/src/test/java/com/megaease/easeagent/plugin/tomcat/interceptor/TestConst.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.tomcat.interceptor; + +public class TestConst { + public static final String FORWARDED_NAME = "X-Forwarded-For"; + public static final String FORWARDED_VALUE = "testForwarded"; + public static final String RESPONSE_TAG_NAME = "X-EG-Test"; + public static final String RESPONSE_TAG_VALUE = "X-EG-Test-Value"; + public static final String METHOD = "GET"; + public static final String URL = "http://192.168.5.1:8080/test"; + public static final String QUERY_STRING = "q1=10&q2=testq"; + public static final String ROUTE = "/test"; + public static final String REMOTE_ADDR = "192.168.5.1"; + public static final int REMOTE_PORT = 8080; + public static final int RESPONSE_BUFFER_SIZE = 1024; +} diff --git a/plugins/tomcat-jdk17/src/test/java/com/megaease/easeagent/plugin/tomcat/interceptor/TestServletUtils.java b/plugins/tomcat-jdk17/src/test/java/com/megaease/easeagent/plugin/tomcat/interceptor/TestServletUtils.java new file mode 100644 index 000000000..879706990 --- /dev/null +++ b/plugins/tomcat-jdk17/src/test/java/com/megaease/easeagent/plugin/tomcat/interceptor/TestServletUtils.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.tomcat.interceptor; + +import com.megaease.easeagent.plugin.tomcat.utils.ServletUtils; +import com.megaease.easeagent.plugin.tools.trace.TraceConst; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +public class TestServletUtils { + public static MockHttpServletRequest buildMockRequest() { + MockHttpServletRequest httpServletRequest = new MockHttpServletRequest(TestConst.METHOD, TestConst.URL); + httpServletRequest.setQueryString(TestConst.QUERY_STRING); + httpServletRequest.addHeader(TestConst.FORWARDED_NAME, TestConst.FORWARDED_VALUE); + httpServletRequest.setAttribute(TraceConst.HTTP_ATTRIBUTE_ROUTE, TestConst.ROUTE); + httpServletRequest.setAttribute(ServletUtils.BEST_MATCHING_PATTERN_ATTRIBUTE, TestConst.ROUTE); + httpServletRequest.setRemoteAddr(TestConst.REMOTE_ADDR); + httpServletRequest.setRemotePort(TestConst.REMOTE_PORT); + return httpServletRequest; + } + + public static HttpServletResponse buildMockResponse() { + MockHttpServletResponse response = new MockHttpServletResponse(); + response.setBufferSize(TestConst.RESPONSE_BUFFER_SIZE); + response.setStatus(200); + return response; + } +} diff --git a/plugins/tomcat-jdk17/src/test/java/com/megaease/easeagent/plugin/tomcat/interceptor/TomcatHttpLogInterceptorTest.java b/plugins/tomcat-jdk17/src/test/java/com/megaease/easeagent/plugin/tomcat/interceptor/TomcatHttpLogInterceptorTest.java new file mode 100644 index 000000000..931834d42 --- /dev/null +++ b/plugins/tomcat-jdk17/src/test/java/com/megaease/easeagent/plugin/tomcat/interceptor/TomcatHttpLogInterceptorTest.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.plugin.tomcat.interceptor; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.megaease.easeagent.mock.plugin.api.MockEaseAgent; +import com.megaease.easeagent.mock.plugin.api.junit.EaseAgentJunit4ClassRunner; +import com.megaease.easeagent.mock.report.MockReport; +import com.megaease.easeagent.mock.report.impl.LastJsonReporter; +import com.megaease.easeagent.plugin.api.config.IPluginConfig; +import com.megaease.easeagent.plugin.api.logging.AccessLogInfo; +import com.megaease.easeagent.plugin.bridge.EaseAgent; +import com.megaease.easeagent.plugin.enums.Order; +import com.megaease.easeagent.plugin.interceptor.MethodInfo; +import com.megaease.easeagent.plugin.tomcat.AccessPlugin; +import com.megaease.easeagent.plugin.tomcat.utils.ServletUtils; +import com.megaease.easeagent.plugin.tools.metrics.AccessLogServerInfo; +import com.megaease.easeagent.plugin.utils.common.HostAddress; +import com.megaease.easeagent.plugin.utils.common.JsonUtil; +import jakarta.servlet.http.HttpServletResponse; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.mock.web.MockHttpServletRequest; + +import static org.junit.Assert.*; + +@RunWith(EaseAgentJunit4ClassRunner.class) +public class TomcatHttpLogInterceptorTest { + + @Test + public void serverInfo() { + MockHttpServletRequest httpServletRequest = TestServletUtils.buildMockRequest(); + HttpServletResponse response = TestServletUtils.buildMockResponse(); + TomcatHttpLogInterceptor tomcatHttpLogInterceptor = new TomcatHttpLogInterceptor(); + AccessLogServerInfo accessLogServerInfo = tomcatHttpLogInterceptor.serverInfo(httpServletRequest, response); + assertSame(accessLogServerInfo, tomcatHttpLogInterceptor.serverInfo(httpServletRequest, response)); + } + + @Test + public void doBefore() throws JsonProcessingException { + internalAfter(); + } + + public void verify(AccessLogInfo accessLog, long startTime) { + assertEquals(EaseAgent.getConfig("system"), accessLog.getSystem()); + assertEquals(EaseAgent.getConfig("name"), accessLog.getService()); + assertEquals(HostAddress.localhost(), accessLog.getHostName()); + assertEquals(HostAddress.getHostIpv4(), accessLog.getHostIpv4()); + assertEquals(TestConst.METHOD + " " + TestConst.URL, accessLog.getUrl()); + assertEquals(TestConst.METHOD, accessLog.getMethod()); + assertEquals(TestConst.FORWARDED_VALUE, accessLog.getHeaders().get(TestConst.FORWARDED_NAME)); + assertEquals(startTime, accessLog.getBeginTime()); + assertEquals("10", accessLog.getQueries().get("q1")); + assertEquals("testq", accessLog.getQueries().get("q2")); + assertEquals(TestConst.FORWARDED_VALUE, accessLog.getClientIP()); + assertTrue(accessLog.getBeginCpuTime() > 0); + } + + @Test + public void getAfterMark() { + TomcatHttpLogInterceptor tomcatHttpLogInterceptor = new TomcatHttpLogInterceptor(); + assertNotNull(tomcatHttpLogInterceptor.getAfterMark()); + } + + private AccessLogInfo getRequestInfo(LastJsonReporter lastJsonReporter) { + String result = JsonUtil.toJson(lastJsonReporter.getLastOnlyOne()); + assertNotNull(result); + return JsonUtil.toObject(result, AccessLogInfo.TYPE_REFERENCE); + } + + @Test + public void internalAfter() throws JsonProcessingException { + EaseAgent.agentReport = MockReport.getAgentReport(); + MockHttpServletRequest httpServletRequest = TestServletUtils.buildMockRequest(); + HttpServletResponse response = TestServletUtils.buildMockResponse(); + TomcatHttpLogInterceptor tomcatHttpLogInterceptor = new TomcatHttpLogInterceptor(); + AccessPlugin accessPlugin = new AccessPlugin(); + IPluginConfig iPluginConfig = EaseAgent.getConfig(accessPlugin.getDomain(), accessPlugin.getNamespace(), tomcatHttpLogInterceptor.getType()); + tomcatHttpLogInterceptor.init(iPluginConfig, "", "", ""); + + MethodInfo methodInfo = MethodInfo.builder().args(new Object[]{httpServletRequest, response}).build(); + tomcatHttpLogInterceptor.doBefore(methodInfo, EaseAgent.getContext()); + Object requestInfoO = httpServletRequest.getAttribute(AccessLogInfo.class.getName()); + assertNotNull(requestInfoO); + assertTrue(requestInfoO instanceof AccessLogInfo); + AccessLogInfo accessLog = (AccessLogInfo) requestInfoO; + long start = (long) httpServletRequest.getAttribute(ServletUtils.START_TIME); + verify(accessLog, start); + LastJsonReporter lastJsonReporter = MockEaseAgent.lastMetricJsonReporter(stringObjectMap -> { + Object type = stringObjectMap.get("type"); + return type instanceof String && "access-log".equals(type); + }); + tomcatHttpLogInterceptor.doAfter(methodInfo, EaseAgent.getContext()); + // AccessLogInfo info = getRequestInfo(lastJsonReporter); + AccessLogInfo info = MockEaseAgent.getLastLog(); + verify(info, start); + assertEquals("200", info.getStatusCode()); + + lastJsonReporter.clean(); + httpServletRequest = TestServletUtils.buildMockRequest(); + response = TestServletUtils.buildMockResponse(); + methodInfo = MethodInfo.builder().args(new Object[]{httpServletRequest, response}).throwable(new RuntimeException("test error")).build(); + tomcatHttpLogInterceptor.doBefore(methodInfo, EaseAgent.getContext()); + tomcatHttpLogInterceptor.doAfter(methodInfo, EaseAgent.getContext()); + // info = getRequestInfo(lastJsonReporter); + info = MockEaseAgent.getLastLog(); + start = (long) httpServletRequest.getAttribute(ServletUtils.START_TIME); + verify(info, start); + assertEquals("500", info.getStatusCode()); + } + + @Test + public void getType() { + TomcatHttpLogInterceptor tomcatHttpLogInterceptor = new TomcatHttpLogInterceptor(); + assertEquals(Order.LOG.getName(), tomcatHttpLogInterceptor.getType()); + } +} diff --git a/plugins/tomcat-jdk17/src/test/resources/mock_agent.properties b/plugins/tomcat-jdk17/src/test/resources/mock_agent.properties new file mode 100644 index 000000000..c1e52c8bd --- /dev/null +++ b/plugins/tomcat-jdk17/src/test/resources/mock_agent.properties @@ -0,0 +1,6 @@ +name=test-httpservlet-service +system=test-httpservlet-system +easeagent.progress.forwarded.headers=X-Forwarded-For +observability.tracings.tag.response.headers.eg.0=X-EG-Test +# plugin.observability.tomcat.metric.enabled=true +plugin.observability.tomcat.metric.interval=1 diff --git a/pom.xml b/pom.xml index 330e23e42..c1821bbf0 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ com.megaease.easeagent easeagent pom - 2.2.9 + 2.3.0 plugin-api log4j2 @@ -68,12 +68,6 @@ 3.4.2 3.11 1.15 - 2.2.5.RELEASE - 5.3.18 - 2.3.13.RELEASE - Hoxton.SR5 - - 3.2.4 2.17.1 2.8.2 @@ -180,13 +174,6 @@ pom import - - org.springframework.cloud - spring-cloud-dependencies - ${version.spring.cloud} - pom - import - net.bytebuddy byte-buddy @@ -345,18 +332,6 @@ metrics-core ${version.metrics} - - org.springframework.retry - spring-retry - 1.2.5.RELEASE - - - spring-core - org.springframework - - - true - redis.clients jedis @@ -377,36 +352,6 @@ brave-instrumentation-rpc ${version.brave} - - - org.springframework.kafka - spring-kafka - ${version.spring-kafka} - - - org.springframework - * - - - - - - org.springframework - spring-webflux - ${version.spring} - io.prometheus simpleclient_dropwizard diff --git a/report/pom.xml b/report/pom.xml index f63288666..a44ca10f3 100644 --- a/report/pom.xml +++ b/report/pom.xml @@ -22,7 +22,7 @@ easeagent com.megaease.easeagent - 2.2.9 + 2.3.0 4.0.0 diff --git a/report/src/main/java/com/megaease/easeagent/report/trace/Platform.java b/report/src/main/java/com/megaease/easeagent/report/trace/Platform.java new file mode 100644 index 000000000..91839ff21 --- /dev/null +++ b/report/src/main/java/com/megaease/easeagent/report/trace/Platform.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2021, MegaEase + * All rights reserved. + * + * 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.megaease.easeagent.report.trace; + +public class Platform { + static final ThreadLocal SHORT_STRING_BUFFER = new ThreadLocal<>(); + /** Maximum character length constraint of most names, IP literals and IDs. */ + public static final int SHORT_STRING_LENGTH = 256; + + /** + * Returns a {@link ThreadLocal} reused {@code char[]} for use when decoding bytes into hex, IP + * literals, or {@link #SHORT_STRING_LENGTH short strings}. The buffer must never be leaked + * outside the method. Most will {@link String#String(char[], int, int) copy it into a string}. + */ + public static char[] shortStringBuffer() { + char[] shortStringBuffer = SHORT_STRING_BUFFER.get(); + if (shortStringBuffer == null) { + shortStringBuffer = new char[SHORT_STRING_LENGTH]; + SHORT_STRING_BUFFER.set(shortStringBuffer); + } + return shortStringBuffer; + } +} diff --git a/report/src/main/java/com/megaease/easeagent/report/trace/ReportSpanBuilder.java b/report/src/main/java/com/megaease/easeagent/report/trace/ReportSpanBuilder.java index 8c2554e3f..bd8e66c62 100644 --- a/report/src/main/java/com/megaease/easeagent/report/trace/ReportSpanBuilder.java +++ b/report/src/main/java/com/megaease/easeagent/report/trace/ReportSpanBuilder.java @@ -23,7 +23,6 @@ import com.megaease.easeagent.plugin.report.tracing.ReportSpanImpl; import zipkin2.Span; import zipkin2.Span.Kind; -import zipkin2.internal.Platform; import java.util.*; import java.util.logging.Logger; diff --git a/zipkin/pom.xml b/zipkin/pom.xml index 019ed4161..3822f2672 100644 --- a/zipkin/pom.xml +++ b/zipkin/pom.xml @@ -21,7 +21,7 @@ easeagent com.megaease.easeagent - 2.2.9 + 2.3.0 4.0.0