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 @@
easeagentcom.megaease.easeagent
- 2.2.9
+ 2.3.04.0.0
@@ -38,23 +38,6 @@
javax.servlet-apiprovided
-
- 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.easeagentmetrics
@@ -209,6 +192,7 @@
com.megaease.easeagent.Maincom.megaease.easeagent.StartBootstraplog4j.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 @@
easeagentcom.megaease.easeagent
- 2.2.9
+ 2.3.04.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 @@
easeagentcom.megaease.easeagent
- 2.2.9
+ 2.3.04.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 @@
easeagentcom.megaease.easeagent
- 2.2.9
+ 2.3.04.0.0
@@ -105,12 +105,6 @@
reactor-coreprovided
-
- org.springframework
- spring-web
- ${version.spring}
- test
- net.bytebuddybyte-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.
+
+
+
+## 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 @@
easeagentcom.megaease.easeagent
- 2.2.9
+ 2.3.04.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 @@
easeagentcom.megaease.easeagent
- 2.2.9
+ 2.3.04.0.0loader
-
- org.springframework.boot
- spring-boot-loader
- 2.3.8.RELEASE
- com.google.guavaguava
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
+
org.springframeworkspring-webflux
+ 5.3.18provided
@@ -61,7 +63,7 @@
org.springframeworkspring-test
- ${version.spring}
+ 5.3.18test
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 @@
pluginscom.megaease.easeagent
- 2.2.9
+ 2.3.04.0.0
@@ -55,6 +55,7 @@
org.springframeworkspring-webflux
+ 5.3.18provided
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.easeagenteaseagentpom
- 2.2.9
+ 2.3.0plugin-apilog4j2
@@ -68,12 +68,6 @@
3.4.23.111.15
- 2.2.5.RELEASE
- 5.3.18
- 2.3.13.RELEASE
- Hoxton.SR5
-
-
3.2.42.17.12.8.2
@@ -180,13 +174,6 @@
pomimport
-
- org.springframework.cloud
- spring-cloud-dependencies
- ${version.spring.cloud}
- pom
- import
- net.bytebuddybyte-buddy
@@ -345,18 +332,6 @@
metrics-core${version.metrics}
-
- org.springframework.retry
- spring-retry
- 1.2.5.RELEASE
-
-
- spring-core
- org.springframework
-
-
- true
- redis.clientsjedis
@@ -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.prometheussimpleclient_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 @@
easeagentcom.megaease.easeagent
- 2.2.9
+ 2.3.04.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 @@
easeagentcom.megaease.easeagent
- 2.2.9
+ 2.3.04.0.0