diff --git a/hippo4j-agent/hippo4j-agent-core/dependency-reduced-pom.xml b/hippo4j-agent/hippo4j-agent-core/dependency-reduced-pom.xml new file mode 100644 index 00000000..93dafe25 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/dependency-reduced-pom.xml @@ -0,0 +1,311 @@ + + + + hippo4j-agent + cn.hippo4j + ${revision} + + 4.0.0 + hippo4j-agent-core + + + + kr.motd.maven + os-maven-plugin + ${os-maven-plugin.version} + + + initialize + + detect + + + + + + maven-shade-plugin + + + package + + shade + + + + + net.bytebuddy:byte-buddy:jar: + com.google.errorprone:error_prone_annotations:jar: + com.google.code.findbugs:jsr305:jar: + com.google.android:annotations:jar: + com.google.api.grpc:proto-google-common-protos:jar: + org.checkerframework:checker-compat-qual:jar: + org.codehaus.mojo:animal-sniffer-annotations:jar: + + + + + ${shade.com.google.source} + ${shade.com.google.target} + + + ${shade.io.grpc.source} + ${shade.io.grpc.target} + + + ${shade.io.netty.source} + ${shade.io.netty.target} + + + ${shade.io.opencensus.source} + ${shade.io.opencensus.target} + + + ${shade.io.perfmark.source} + ${shade.io.perfmark.target} + + + ${shade.org.slf4j.source} + ${shade.org.slf4j.target} + + + + + com.google.protobuf:protobuf-java + + google/protobuf/*.proto + google/protobuf/compiler/*.proto + + + + + + + + + + + + + + + net.bytebuddy + byte-buddy + 1.12.13 + compile + + + net.bytebuddy + byte-buddy-agent + 1.12.13 + test + + + com.github.tomakehurst + wiremock + 2.6.0 + test + + + jackson-annotations + com.fasterxml.jackson.core + + + jackson-core + com.fasterxml.jackson.core + + + jackson-databind + com.fasterxml.jackson.core + + + jetty-server + org.eclipse.jetty + + + jetty-servlet + org.eclipse.jetty + + + jetty-servlets + org.eclipse.jetty + + + jetty-webapp + org.eclipse.jetty + + + guava + com.google.guava + + + httpclient + org.apache.httpcomponents + + + xmlunit-core + org.xmlunit + + + xmlunit-legacy + org.xmlunit + + + json-path + com.jayway.jsonpath + + + jopt-simple + net.sf.jopt-simple + + + junit + junit + + + commons-lang3 + org.apache.commons + + + zjsonpatch + com.flipkart.zjsonpatch + + + handlebars + com.github.jknack + + + + + com.github.stefanbirkner + system-rules + 1.18.0 + test + + + junit + junit + + + junit-dep + junit + + + + + org.openjdk.jmh + jmh-generator-annprocess + 1.33 + test + + + org.powermock + powermock-api-mockito2 + 2.0.7 + test + + + powermock-api-support + org.powermock + + + mockito-core + org.mockito + + + + + org.objenesis + objenesis + 3.1 + test + + + org.openjdk.jmh + jmh-core + 1.33 + test + + + commons-math3 + org.apache.commons + + + jopt-simple + net.sf.jopt-simple + + + + + org.projectlombok + lombok + 1.18.20 + provided + + + javax.annotation + javax.annotation-api + 1.3.2 + provided + + + org.powermock + powermock-module-junit4 + 2.0.7 + test + + + powermock-module-junit4-common + org.powermock + + + hamcrest-core + org.hamcrest + + + junit + junit + + + + + + + + com.google.guava + guava + ${guava.version} + + + net.bytebuddy + byte-buddy + ${bytebuddy.version} + + + org.slf4j + slf4j-api + ${slf4j.version} + + + + + ${shade.package}.${shade.io.perfmark.source} + io.opencensus + ${shade.package}.${shade.org.slf4j.source} + ${shade.package}.${shade.io.grpc.source} + 2.0.7.Final + 1.4.1.Final + io.netty + UTF-8 + 2.6.0 + 1.18.0 + ${shade.package}.${shade.io.opencensus.source} + 1.7.25 + ${shade.package}.${shade.io.netty.source} + com.google + 30.1.1-jre + io.perfmark + org.slf4j + io.grpc + ${shade.package}.${shade.com.google.source} + + diff --git a/hippo4j-agent/hippo4j-agent-core/pom.xml b/hippo4j-agent/hippo4j-agent-core/pom.xml new file mode 100644 index 00000000..b1c4dc3f --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/pom.xml @@ -0,0 +1,188 @@ + + + 4.0.0 + + cn.hippo4j + hippo4j-agent + ${revision} + + + hippo4j-agent-core + jar + + + UTF-8 + 30.1.1-jre + 2.6.0 + 2.0.7.Final + 1.4.1.Final + com.google + ${shade.package}.${shade.com.google.source} + io.grpc + ${shade.package}.${shade.io.grpc.source} + io.netty + ${shade.package}.${shade.io.netty.source} + io.opencensus + ${shade.package}.${shade.io.opencensus.source} + io.perfmark + ${shade.package}.${shade.io.perfmark.source} + org.slf4j + ${shade.package}.${shade.org.slf4j.source} + 1.18.0 + 1.7.25 + + + + + net.bytebuddy + byte-buddy + + + com.google.code.gson + gson + ${gson.version} + + + + net.bytebuddy + byte-buddy-agent + ${bytebuddy.version} + test + + + com.github.tomakehurst + wiremock + ${wiremock.version} + test + + + jackson-annotations + com.fasterxml.jackson.core + + + jackson-core + com.fasterxml.jackson.core + + + jackson-databind + com.fasterxml.jackson.core + + + + + com.github.stefanbirkner + system-rules + ${ststem-rules.version} + test + + + org.openjdk.jmh + jmh-generator-annprocess + test + + + org.slf4j + slf4j-api + + + + + + com.google.guava + guava + ${guava.version} + + + net.bytebuddy + byte-buddy + ${bytebuddy.version} + + + org.slf4j + slf4j-api + ${slf4j.version} + + + + + + + kr.motd.maven + os-maven-plugin + ${os-maven-plugin.version} + + + initialize + + detect + + + + + + maven-shade-plugin + + + package + + shade + + + + + net.bytebuddy:byte-buddy:jar: + com.google.errorprone:error_prone_annotations:jar: + com.google.code.findbugs:jsr305:jar: + com.google.android:annotations:jar: + com.google.api.grpc:proto-google-common-protos:jar: + org.checkerframework:checker-compat-qual:jar: + org.codehaus.mojo:animal-sniffer-annotations:jar: + + + + + ${shade.com.google.source} + ${shade.com.google.target} + + + ${shade.io.grpc.source} + ${shade.io.grpc.target} + + + ${shade.io.netty.source} + ${shade.io.netty.target} + + + ${shade.io.opencensus.source} + ${shade.io.opencensus.target} + + + ${shade.io.perfmark.source} + ${shade.io.perfmark.target} + + + ${shade.org.slf4j.source} + ${shade.org.slf4j.target} + + + + + com.google.protobuf:protobuf-java + + google/protobuf/*.proto + google/protobuf/compiler/*.proto + + + + + + + + + + + + + \ No newline at end of file diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/base64/Base64.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/base64/Base64.java new file mode 100644 index 00000000..7e7c8337 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/base64/Base64.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.base64; + +import java.nio.charset.StandardCharsets; + +/** + * A wrapper of {@link java.util.Base64} with convenient conversion methods between {@code byte[]} and {@code String} + */ +public final class Base64 { + + private static final java.util.Base64.Decoder DECODER = java.util.Base64.getDecoder(); + private static final java.util.Base64.Encoder ENCODER = java.util.Base64.getEncoder(); + + private Base64() { + } + + public static String decode2UTFString(String in) { + return new String(DECODER.decode(in), StandardCharsets.UTF_8); + } + + public static String encode(String text) { + return ENCODER.encodeToString(text.getBytes(StandardCharsets.UTF_8)); + } + +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/boot/AgentPackageNotFoundException.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/boot/AgentPackageNotFoundException.java new file mode 100644 index 00000000..e26aae8a --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/boot/AgentPackageNotFoundException.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.boot; + +public class AgentPackageNotFoundException extends Exception { + + public AgentPackageNotFoundException(String message) { + super(message); + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/boot/AgentPackagePath.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/boot/AgentPackagePath.java new file mode 100644 index 00000000..3844347b --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/boot/AgentPackagePath.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.boot; + +import cn.hippo4j.agent.core.logging.api.ILog; +import cn.hippo4j.agent.core.logging.api.LogManager; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; + +/** + * AgentPackagePath is a flag and finder to locate the SkyWalking agent.jar. It gets the absolute path of the agent jar. + * The path is the required metadata for agent core looking up the plugins and toolkit activations. If the lookup + * mechanism fails, the agent will exit directly. + */ +public class AgentPackagePath { + + private static final ILog LOGGER = LogManager.getLogger(AgentPackagePath.class); + + private static File AGENT_PACKAGE_PATH; + + public static File getPath() throws AgentPackageNotFoundException { + if (AGENT_PACKAGE_PATH == null) { + AGENT_PACKAGE_PATH = findPath(); + } + return AGENT_PACKAGE_PATH; + } + + public static boolean isPathFound() { + return AGENT_PACKAGE_PATH != null; + } + + private static File findPath() throws AgentPackageNotFoundException { + String classResourcePath = AgentPackagePath.class.getName().replaceAll("\\.", "/") + ".class"; + + URL resource = ClassLoader.getSystemClassLoader().getResource(classResourcePath); + if (resource != null) { + String urlString = resource.toString(); + + LOGGER.debug("The beacon class location is {}.", urlString); + + int insidePathIndex = urlString.indexOf('!'); + boolean isInJar = insidePathIndex > -1; + + if (isInJar) { + urlString = urlString.substring(urlString.indexOf("file:"), insidePathIndex); + File agentJarFile = null; + try { + agentJarFile = new File(new URL(urlString).toURI()); + } catch (MalformedURLException | URISyntaxException e) { + LOGGER.error(e, "Can not locate agent jar file by url:" + urlString); + } + if (agentJarFile.exists()) { + return agentJarFile.getParentFile(); + } + } else { + int prefixLength = "file:".length(); + String classLocation = urlString.substring( + prefixLength, urlString.length() - classResourcePath.length()); + return new File(classLocation); + } + } + + LOGGER.error("Can not locate agent jar file."); + throw new AgentPackageNotFoundException("Can not locate agent jar file."); + } + +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/boot/BootService.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/boot/BootService.java new file mode 100644 index 00000000..c854df5f --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/boot/BootService.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.boot; + +/** + * The BootService is an interface to all remote, which need to boot when plugin mechanism begins to work. + * {@link #boot()} will be called when BootService start up. + */ +public interface BootService { + + void prepare() throws Throwable; + + void boot() throws Throwable; + + void onComplete() throws Throwable; + + void shutdown() throws Throwable; + + /** + * {@code BootService}s with higher priorities will be started earlier, and shut down later than those {@code BootService}s with lower priorities. + * + * @return the priority of this {@code BootService}. + */ + default int priority() { + return 0; + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/boot/DefaultImplementor.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/boot/DefaultImplementor.java new file mode 100644 index 00000000..b8f21af8 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/boot/DefaultImplementor.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.boot; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface DefaultImplementor { +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/boot/DefaultNamedThreadFactory.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/boot/DefaultNamedThreadFactory.java new file mode 100644 index 00000000..8b43f216 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/boot/DefaultNamedThreadFactory.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.boot; + +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +public class DefaultNamedThreadFactory implements ThreadFactory { + + private static final AtomicInteger BOOT_SERVICE_SEQ = new AtomicInteger(0); + private final AtomicInteger threadSeq = new AtomicInteger(0); + private final String namePrefix; + + public DefaultNamedThreadFactory(String name) { + namePrefix = "SkywalkingAgent-" + BOOT_SERVICE_SEQ.incrementAndGet() + "-" + name + "-"; + } + + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r, namePrefix + threadSeq.getAndIncrement()); + t.setDaemon(true); + return t; + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/boot/OverrideImplementor.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/boot/OverrideImplementor.java new file mode 100644 index 00000000..59fff1e0 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/boot/OverrideImplementor.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.boot; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface OverrideImplementor { + + Class value(); +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/boot/PluginConfig.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/boot/PluginConfig.java new file mode 100644 index 00000000..64dfff77 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/boot/PluginConfig.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.boot; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * ConfigInitializationService provides the config class which should host all parameters originally from agent setup. + * {@link org.apache.skywalking.apm.agent.core.conf.Config} provides the core level config, all plugins could implement + * this interface to have the same capability about initializing config from agent.config, system properties and system + * environment variables. + */ +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface PluginConfig { + + /** + * @return Class as the root to do config initialization. + */ + Class root(); +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/boot/ServiceConflictException.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/boot/ServiceConflictException.java new file mode 100644 index 00000000..f91f46a2 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/boot/ServiceConflictException.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.boot; + +public class ServiceConflictException extends RuntimeException { + + public ServiceConflictException(String message) { + super(message); + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/boot/ServiceManager.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/boot/ServiceManager.java new file mode 100644 index 00000000..af0aba72 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/boot/ServiceManager.java @@ -0,0 +1,143 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.boot; + +import cn.hippo4j.agent.core.logging.api.ILog; +import cn.hippo4j.agent.core.logging.api.LogManager; +import cn.hippo4j.agent.core.plugin.loader.AgentClassLoader; + +import java.util.*; + +/** + * The ServiceManager bases on {@link ServiceLoader}, load all {@link BootService} implementations. + */ +public enum ServiceManager { + + INSTANCE; + + private static final ILog LOGGER = LogManager.getLogger(ServiceManager.class); + private Map bootedServices = Collections.emptyMap(); + + public void boot() { + bootedServices = loadAllServices(); + + prepare(); + startup(); + onComplete(); + } + + public void shutdown() { + bootedServices.values().stream().sorted(Comparator.comparingInt(BootService::priority).reversed()).forEach(service -> { + try { + service.shutdown(); + } catch (Throwable e) { + LOGGER.error(e, "ServiceManager try to shutdown [{}] fail.", service.getClass().getName()); + } + }); + } + + private Map loadAllServices() { + Map bootedServices = new LinkedHashMap<>(); + List allServices = new LinkedList<>(); + load(allServices); + for (final BootService bootService : allServices) { + Class bootServiceClass = bootService.getClass(); + boolean isDefaultImplementor = bootServiceClass.isAnnotationPresent(DefaultImplementor.class); + if (isDefaultImplementor) { + if (!bootedServices.containsKey(bootServiceClass)) { + bootedServices.put(bootServiceClass, bootService); + } else { + // ignore the default service + } + } else { + OverrideImplementor overrideImplementor = bootServiceClass.getAnnotation(OverrideImplementor.class); + if (overrideImplementor == null) { + if (!bootedServices.containsKey(bootServiceClass)) { + bootedServices.put(bootServiceClass, bootService); + } else { + throw new ServiceConflictException("Duplicate service define for :" + bootServiceClass); + } + } else { + Class targetService = overrideImplementor.value(); + if (bootedServices.containsKey(targetService)) { + boolean presentDefault = bootedServices.get(targetService) + .getClass() + .isAnnotationPresent(DefaultImplementor.class); + if (presentDefault) { + bootedServices.put(targetService, bootService); + } else { + throw new ServiceConflictException( + "Service " + bootServiceClass + " overrides conflict, " + "exist more than one service want to override :" + targetService); + } + } else { + bootedServices.put(targetService, bootService); + } + } + } + + } + return bootedServices; + } + + private void prepare() { + bootedServices.values().stream().sorted(Comparator.comparingInt(BootService::priority)).forEach(service -> { + try { + service.prepare(); + } catch (Throwable e) { + LOGGER.error(e, "ServiceManager try to pre-start [{}] fail.", service.getClass().getName()); + } + }); + } + + private void startup() { + bootedServices.values().stream().sorted(Comparator.comparingInt(BootService::priority)).forEach(service -> { + try { + service.boot(); + } catch (Throwable e) { + LOGGER.error(e, "ServiceManager try to start [{}] fail.", service.getClass().getName()); + } + }); + } + + private void onComplete() { + for (BootService service : bootedServices.values()) { + try { + service.onComplete(); + } catch (Throwable e) { + LOGGER.error(e, "Service [{}] AfterBoot process fails.", service.getClass().getName()); + } + } + } + + /** + * Find a {@link BootService} implementation, which is already started. + * + * @param serviceClass class name. + * @param {@link BootService} implementation class. + * @return {@link BootService} instance + */ + public T findService(Class serviceClass) { + return (T) bootedServices.get(serviceClass); + } + + void load(List allServices) { + for (final BootService bootService : ServiceLoader.load(BootService.class, AgentClassLoader.getDefault())) { + allServices.add(bootService); + } + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/conf/Config.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/conf/Config.java new file mode 100755 index 00000000..90309f5f --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/conf/Config.java @@ -0,0 +1,380 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.conf; + +import cn.hippo4j.agent.core.logging.core.LogLevel; +import cn.hippo4j.agent.core.logging.core.LogOutput; +import cn.hippo4j.agent.core.logging.core.ResolverType; +import cn.hippo4j.agent.core.logging.core.WriterFactory; +import cn.hippo4j.agent.core.plugin.bytebuddy.ClassCacheMode; +import cn.hippo4j.agent.core.util.Length; + +import java.util.Arrays; +import java.util.List; + +/** + * This is the core config in hippo4j agent. + */ +public class Config { + + public static class Agent { + + /** + * Namespace represents a subnet, such as kubernetes namespace, or 172.10.*.*. + * + * @since 8.10.0 namespace would be added as {@link #SERVICE_NAME} suffix. + * + * Removed namespace isolating headers in cross process propagation. The HEADER name was + * `HeaderName:Namespace`. + */ + @Length(20) + public static String NAMESPACE = ""; + + /** + * Service name is showed on the UI. Suggestion: set a unique name for each service, service instance nodes + * share the same code + * + * @since 8.10.0 ${service name} = [${group name}::]${logic name}|${NAMESPACE}|${CLUSTER} + * + * The group name, namespace and cluster are optional. Once they are all blank, service name would be the final + * name. + */ + @Length(50) + public static String SERVICE_NAME = ""; + + /** + * Cluster defines the physical cluster in a data center or same network segment. In one cluster, IP address + * should be unique identify. + * + * The cluster name would be + * + * 1. Add as {@link #SERVICE_NAME} suffix. + * + * 2. Add as exit span's peer, ${CLUSTER} / original peer + * + * 3. Cross Process Propagation Header's value addressUsedAtClient[index=8] (Target address of this request used + * on the client end). + * + * @since 8.10.0 + */ + @Length(20) + public static String CLUSTER = ""; + + /** + * Authentication active is based on backend setting, see application.yml for more details. For most scenarios, + * this needs backend extensions, only basic match auth provided in default implementation. + */ + public static String AUTHENTICATION = ""; + + /** + * If true, SkyWalking agent will save all instrumented classes files in `/debugging` folder. SkyWalking team + * may ask for these files in order to resolve compatible problem. + */ + public static boolean IS_OPEN_DEBUGGING_CLASS = false; + + /** + * If true, SkyWalking agent will cache all instrumented classes to memory or disk files (decided by class cache + * mode), allow other javaagent to enhance those classes that enhanced by SkyWalking agent. + */ + public static boolean IS_CACHE_ENHANCED_CLASS = false; + + /** + * The instrumented classes cache mode: MEMORY or FILE MEMORY: cache class bytes to memory, if instrumented + * classes is too many or too large, it may take up more memory FILE: cache class bytes in `/class-cache` + * folder, automatically clean up cached class files when the application exits + */ + public static ClassCacheMode CLASS_CACHE_MODE = ClassCacheMode.MEMORY; + + /** + * The identifier of the instance + */ + @Length(50) + public volatile static String INSTANCE_NAME = ""; + + /** + * service instance properties in json format. e.g. agent.instance_properties_json = {"org": + * "apache-skywalking"} + */ + public static String INSTANCE_PROPERTIES_JSON = ""; + + /** + * How depth the agent goes, when log cause exceptions. + */ + public static int CAUSE_EXCEPTION_DEPTH = 5; + + /** + * Force reconnection period of grpc, based on grpc_channel_check_interval. If count of check grpc channel + * status more than this number. The channel check will call channel.getState(true) to requestConnection. + */ + public static long FORCE_RECONNECTION_PERIOD = 1; + + /** + * Limit the length of the operationName to prevent the overlength issue in the storage. + * + *

NOTICE

+ * In the current practice, we don't recommend the length over 190. + */ + public static int OPERATION_NAME_THRESHOLD = 150; + + /** + * Keep tracing even the backend is not available. + */ + public static boolean KEEP_TRACING = false; + + /** + * Force open TLS for gRPC channel if true. + */ + public static boolean FORCE_TLS = false; + + /** + * SSL trusted ca file. If it exists, will enable TLS for gRPC channel. + */ + public static String SSL_TRUSTED_CA_PATH = "ca" + Constants.PATH_SEPARATOR + "ca.crt"; + + /** + * Key cert chain file. If ssl_cert_chain and ssl_key exist, will enable mTLS for gRPC channel. + */ + public static String SSL_CERT_CHAIN_PATH; + + /** + * Private key file. If ssl_cert_chain and ssl_key exist, will enable mTLS for gRPC channel. + */ + public static String SSL_KEY_PATH; + } + + public static class OsInfo { + + /** + * Limit the length of the ipv4 list size. + */ + public static int IPV4_LIST_SIZE = 10; + } + + public static class Collector { + + /** + * grpc channel status check interval + */ + public static long GRPC_CHANNEL_CHECK_INTERVAL = 30; + /** + * The period in which the agent report a heartbeat to the backend. + */ + public static long HEARTBEAT_PERIOD = 30; + /** + * The agent sends the instance properties to the backend every `collector.heartbeat_period * + * collector.properties_report_period_factor` seconds + */ + public static int PROPERTIES_REPORT_PERIOD_FACTOR = 10; + /** + * Collector skywalking trace receiver service addresses. + */ + public static String BACKEND_SERVICE = ""; + /** + * How long grpc client will timeout in sending data to upstream. + */ + public static int GRPC_UPSTREAM_TIMEOUT = 30; + /** + * Get profile task list interval + */ + public static int GET_PROFILE_TASK_INTERVAL = 20; + /** + * Get agent dynamic config interval + */ + public static int GET_AGENT_DYNAMIC_CONFIG_INTERVAL = 20; + /** + * If true, skywalking agent will enable periodically resolving DNS to update receiver service addresses. + */ + public static boolean IS_RESOLVE_DNS_PERIODICALLY = false; + } + + public static class Profile { + + /** + * If true, skywalking agent will enable profile when user create a new profile task. Otherwise disable + * profile. + */ + public static boolean ACTIVE = true; + + /** + * Parallel monitor segment count + */ + public static int MAX_PARALLEL = 5; + + /** + * Max monitor segment time(minutes), if current segment monitor time out of limit, then stop it. + */ + public static int MAX_DURATION = 10; + + /** + * Max dump thread stack depth + */ + public static int DUMP_MAX_STACK_DEPTH = 500; + + /** + * Snapshot transport to backend buffer size + */ + public static int SNAPSHOT_TRANSPORT_BUFFER_SIZE = 500; + } + + public static class Meter { + + /** + * If true, skywalking agent will enable sending meters. Otherwise disable meter report. + */ + public static boolean ACTIVE = true; + + /** + * Report meters interval + */ + public static Integer REPORT_INTERVAL = 20; + + /** + * Max size of the meter count. + */ + public static Integer MAX_METER_SIZE = 500; + } + + public static class Jvm { + + /** + * The buffer size of collected JVM info. + */ + public static int BUFFER_SIZE = 60 * 10; + } + + public static class Log { + + /** + * The max size of message to send to server.Default is 10 MB. + */ + public static int MAX_MESSAGE_SIZE = 10 * 1024 * 1024; + } + + public static class Buffer { + + public static int CHANNEL_SIZE = 5; + + public static int BUFFER_SIZE = 300; + } + + public static class Logging { + + /** + * Log file name. + */ + public static String FILE_NAME = "skywalking-api.log"; + + /** + * Log files directory. Default is blank string, means, use "{theSkywalkingAgentJarDir}/logs " to output logs. + * {theSkywalkingAgentJarDir} is the directory where the skywalking agent jar file is located. + *

+ * Ref to {@link WriterFactory#getLogWriter()} + */ + public static String DIR = ""; + + /** + * The max size of log file. If the size is bigger than this, archive the current file, and write into a new + * file. + */ + public static int MAX_FILE_SIZE = 300 * 1024 * 1024; + + /** + * The max history log files. When rollover happened, if log files exceed this number, then the oldest file will + * be delete. Negative or zero means off, by default. + */ + public static int MAX_HISTORY_FILES = -1; + + /** + * The log level. Default is debug. + */ + public static LogLevel LEVEL = LogLevel.DEBUG; + + /** + * The log output. Default is FILE. + */ + public static LogOutput OUTPUT = LogOutput.FILE; + + /** + * The log resolver type. Default is PATTERN which will create PatternLogResolver later. + */ + public static ResolverType RESOLVER = ResolverType.PATTERN; + + /** + * The log patten. Default is "%level %timestamp %thread %class : %msg %throwable". Each conversion specifiers + * starts with a percent sign '%' and fis followed by conversion word. There are some default conversion + * specifiers: %thread = ThreadName %level = LogLevel {@link LogLevel} %timestamp = The now() who format is + * 'yyyy-MM-dd HH:mm:ss:SSS' %class = SimpleName of TargetClass %msg = Message of user input %throwable = + * Throwable of user input %agent_name = ServiceName of Agent {@link Agent#SERVICE_NAME} + * + * @see cn.hippo4j.agent.core.logging.core.PatternLogger#DEFAULT_CONVERTER_MAP + */ + public static String PATTERN = "%level %timestamp %thread %class : %msg %throwable"; + } + + public static class StatusCheck { + + /** + * Listed exceptions would not be treated as an error. Because in some codes, the exception is being used as a + * way of controlling business flow. + */ + public static String IGNORED_EXCEPTIONS = ""; + + /** + * The max recursive depth when checking the exception traced by the agent. Typically, we don't recommend + * setting this more than 10, which could cause a performance issue. Negative value and 0 would be ignored, + * which means all exceptions would make the span tagged in error status. + */ + public static Integer MAX_RECURSIVE_DEPTH = 1; + } + + public static class Plugin { + + /** + * Control the length of the peer field. + */ + public static int PEER_MAX_LENGTH = 200; + + /** + * Exclude activated plugins + */ + public static String EXCLUDE_PLUGINS = ""; + + /** + * Mount the folders of the plugins. The folder path is relative to agent.jar. + */ + public static List MOUNT = Arrays.asList("plugins", "activations"); + } + + public static class Correlation { + + /** + * Max element count in the correlation context. + */ + public static int ELEMENT_MAX_NUMBER = 3; + + /** + * Max value length of each element. + */ + public static int VALUE_MAX_LENGTH = 128; + + /** + * Tag the span by the key/value in the correlation context, when the keys listed here exist. + */ + public static String AUTO_TAG_KEYS = ""; + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/conf/ConfigNotFoundException.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/conf/ConfigNotFoundException.java new file mode 100644 index 00000000..9d7d387c --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/conf/ConfigNotFoundException.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.conf; + +public class ConfigNotFoundException extends Exception { + + public ConfigNotFoundException(String message, Throwable cause) { + super(message, cause); + } + + public ConfigNotFoundException(String message) { + super(message); + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/conf/Constants.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/conf/Constants.java new file mode 100644 index 00000000..26433f2e --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/conf/Constants.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.conf; + +public class Constants { + + public static String PATH_SEPARATOR = System.getProperty("file.separator", "/"); + + public static String LINE_SEPARATOR = System.getProperty("line.separator", "\n"); + + public static String EMPTY_STRING = ""; + + public static char SERVICE_NAME_PART_CONNECTOR = '|'; + + // The name of the layer that represents agent-installed services, + // which is defined at + // https://github.com/apache/skywalking/blob/85ce1645be53e46286f36c0ea206c60db2d1a716/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/Layer.java#L30 + public static String EVENT_LAYER_NAME = "GENERAL"; + + public static int NULL_VALUE = 0; +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/conf/RuntimeContextConfiguration.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/conf/RuntimeContextConfiguration.java new file mode 100644 index 00000000..0864baa0 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/conf/RuntimeContextConfiguration.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.conf; + +public class RuntimeContextConfiguration { + + public static String[] NEED_PROPAGATE_CONTEXT_KEY = new String[]{ + "SW_REQUEST", + "SW_RESPONSE", + "SW_WEBFLUX_REQUEST_KEY" + }; +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/conf/SnifferConfigInitializer.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/conf/SnifferConfigInitializer.java new file mode 100644 index 00000000..4323cc0f --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/conf/SnifferConfigInitializer.java @@ -0,0 +1,231 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.conf; + +import cn.hippo4j.agent.core.boot.AgentPackageNotFoundException; +import cn.hippo4j.agent.core.boot.AgentPackagePath; +import cn.hippo4j.agent.core.logging.api.ILog; +import cn.hippo4j.agent.core.logging.api.LogManager; +import cn.hippo4j.agent.core.logging.core.JsonLogResolver; +import cn.hippo4j.agent.core.logging.core.PatternLogResolver; +import cn.hippo4j.agent.core.util.ConfigInitializer; +import cn.hippo4j.agent.core.util.PropertyPlaceholderHelper; +import cn.hippo4j.agent.core.util.StringUtil; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import static cn.hippo4j.agent.core.conf.Constants.SERVICE_NAME_PART_CONNECTOR; + +/** + * The SnifferConfigInitializer initializes all configs in several way. + */ +public class SnifferConfigInitializer { + + private static ILog LOGGER = LogManager.getLogger(SnifferConfigInitializer.class); + private static final String SPECIFIED_CONFIG_PATH = "skywalking_config"; + private static final String DEFAULT_CONFIG_FILE_NAME = "/config/agent.config"; + private static final String ENV_KEY_PREFIX = "skywalking."; + private static Properties AGENT_SETTINGS; + private static boolean IS_INIT_COMPLETED = false; + + /** + * If the specified agent config path is set, the agent will try to locate the specified agent config. If the + * specified agent config path is not set , the agent will try to locate `agent.config`, which should be in the + * /config directory of agent package. + *

+ * Also try to override the config by system.properties. All the keys in this place should start with {@link + * #ENV_KEY_PREFIX}. e.g. in env `skywalking.agent.service_name=yourAppName` to override `agent.service_name` in + * config file. + *

+ * At the end, `agent.service_name` and `collector.servers` must not be blank. + */ + public static void initializeCoreConfig(String agentOptions) { + AGENT_SETTINGS = new Properties(); + try (final InputStreamReader configFileStream = loadConfig()) { + AGENT_SETTINGS.load(configFileStream); + for (String key : AGENT_SETTINGS.stringPropertyNames()) { + String value = (String) AGENT_SETTINGS.get(key); + AGENT_SETTINGS.put(key, PropertyPlaceholderHelper.INSTANCE.replacePlaceholders(value, AGENT_SETTINGS)); + } + + } catch (Exception e) { + LOGGER.error(e, "Failed to read the config file, skywalking is going to run in default config."); + } + + try { + overrideConfigBySystemProp(); + } catch (Exception e) { + LOGGER.error(e, "Failed to read the system properties."); + } + + agentOptions = StringUtil.trim(agentOptions, ','); + if (!StringUtil.isEmpty(agentOptions)) { + try { + agentOptions = agentOptions.trim(); + LOGGER.info("Agent options is {}.", agentOptions); + + overrideConfigByAgentOptions(agentOptions); + } catch (Exception e) { + LOGGER.error(e, "Failed to parse the agent options, val is {}.", agentOptions); + } + } + + initializeConfig(Config.class); + // reconfigure logger after config initialization + configureLogger(); + LOGGER = LogManager.getLogger(SnifferConfigInitializer.class); + + if (StringUtil.isEmpty(Config.Agent.SERVICE_NAME)) { + throw new ExceptionInInitializerError("`agent.service_name` is missing."); + } else { + if (StringUtil.isNotEmpty(Config.Agent.NAMESPACE) || StringUtil.isNotEmpty(Config.Agent.CLUSTER)) { + Config.Agent.SERVICE_NAME = StringUtil.join( + SERVICE_NAME_PART_CONNECTOR, + Config.Agent.SERVICE_NAME, + Config.Agent.NAMESPACE, + Config.Agent.CLUSTER); + } + } + if (StringUtil.isEmpty(Config.Collector.BACKEND_SERVICE)) { + throw new ExceptionInInitializerError("`collector.backend_service` is missing."); + } + if (Config.Plugin.PEER_MAX_LENGTH <= 3) { + LOGGER.warn( + "PEER_MAX_LENGTH configuration:{} error, the default value of 200 will be used.", + Config.Plugin.PEER_MAX_LENGTH); + Config.Plugin.PEER_MAX_LENGTH = 200; + } + + IS_INIT_COMPLETED = true; + } + + /** + * Initialize field values of any given config class. + * + * @param configClass to host the settings for code access. + */ + public static void initializeConfig(Class configClass) { + if (AGENT_SETTINGS == null) { + LOGGER.error("Plugin configs have to be initialized after core config initialization."); + return; + } + try { + ConfigInitializer.initialize(AGENT_SETTINGS, configClass); + } catch (IllegalAccessException e) { + LOGGER.error(e, + "Failed to set the agent settings {}" + + " to Config={} ", + AGENT_SETTINGS, configClass); + } + } + + private static void overrideConfigByAgentOptions(String agentOptions) throws IllegalArgumentException { + for (List terms : parseAgentOptions(agentOptions)) { + if (terms.size() != 2) { + throw new IllegalArgumentException("[" + terms + "] is not a key-value pair."); + } + AGENT_SETTINGS.put(terms.get(0), terms.get(1)); + } + } + + private static List> parseAgentOptions(String agentOptions) { + List> options = new ArrayList<>(); + List terms = new ArrayList<>(); + boolean isInQuotes = false; + StringBuilder currentTerm = new StringBuilder(); + for (char c : agentOptions.toCharArray()) { + if (c == '\'' || c == '"') { + isInQuotes = !isInQuotes; + } else if (c == '=' && !isInQuotes) { // key-value pair uses '=' as separator + terms.add(currentTerm.toString()); + currentTerm = new StringBuilder(); + } else if (c == ',' && !isInQuotes) { // multiple options use ',' as separator + terms.add(currentTerm.toString()); + currentTerm = new StringBuilder(); + + options.add(terms); + terms = new ArrayList<>(); + } else { + currentTerm.append(c); + } + } + // add the last term and option without separator + terms.add(currentTerm.toString()); + options.add(terms); + return options; + } + + public static boolean isInitCompleted() { + return IS_INIT_COMPLETED; + } + + /** + * Override the config by system properties. The property key must start with `skywalking`, the result should be as + * same as in `agent.config` + *

+ * such as: Property key of `agent.service_name` should be `skywalking.agent.service_name` + */ + private static void overrideConfigBySystemProp() { + Properties systemProperties = System.getProperties(); + for (final Map.Entry prop : systemProperties.entrySet()) { + String key = prop.getKey().toString(); + if (key.startsWith(ENV_KEY_PREFIX)) { + String realKey = key.substring(ENV_KEY_PREFIX.length()); + AGENT_SETTINGS.put(realKey, prop.getValue()); + } + } + } + + /** + * Load the specified config file or default config file + * + * @return the config file {@link InputStream}, or null if not needEnhance. + */ + private static InputStreamReader loadConfig() throws AgentPackageNotFoundException, ConfigNotFoundException { + String specifiedConfigPath = System.getProperty(SPECIFIED_CONFIG_PATH); + File configFile = StringUtil.isEmpty(specifiedConfigPath) ? new File( + AgentPackagePath.getPath(), DEFAULT_CONFIG_FILE_NAME) : new File(specifiedConfigPath); + + if (configFile.exists() && configFile.isFile()) { + try { + LOGGER.info("Config file found in {}.", configFile); + + return new InputStreamReader(new FileInputStream(configFile), StandardCharsets.UTF_8); + } catch (FileNotFoundException e) { + throw new ConfigNotFoundException("Failed to load agent.config", e); + } + } + throw new ConfigNotFoundException("Failed to load agent.config."); + } + + static void configureLogger() { + switch (Config.Logging.RESOLVER) { + case JSON: + LogManager.setLogResolver(new JsonLogResolver()); + break; + case PATTERN: + default: + LogManager.setLogResolver(new PatternLogResolver()); + } + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/conf/dynamic/AgentConfigChangeWatcher.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/conf/dynamic/AgentConfigChangeWatcher.java new file mode 100644 index 00000000..66bc294b --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/conf/dynamic/AgentConfigChangeWatcher.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.conf.dynamic; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +public abstract class AgentConfigChangeWatcher { + + // Config key, should match KEY in the Table of Agent Configuration Properties. + private final String propertyKey; + + public AgentConfigChangeWatcher(String propertyKey) { + this.propertyKey = propertyKey; + } + + /** + * Notify the watcher, the new value received. + * + * @param value of new. + */ + public abstract void notify(ConfigChangeEvent value); + + /** + * @return current value of current config. + */ + public abstract String value(); + + @Override + public String toString() { + return "AgentConfigChangeWatcher{" + + "propertyKey='" + propertyKey + '\'' + + '}'; + } + + @Getter + @RequiredArgsConstructor + public static class ConfigChangeEvent { + + private final String newValue; + private final EventType eventType; + } + + public enum EventType { + ADD, MODIFY, DELETE + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/api/ILog.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/api/ILog.java new file mode 100644 index 00000000..89f1031f --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/api/ILog.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.logging.api; + +/** + * The Log interface. It's very easy to understand, like any other log-component. Do just like log4j or log4j2 does. + *

+ */ +public interface ILog { + + void info(String format); + + void info(String format, Object... arguments); + + void info(Throwable t, String format, Object... arguments); + + void warn(String format, Object... arguments); + + void warn(Throwable e, String format, Object... arguments); + + void error(String format, Throwable e); + + void error(Throwable e, String format, Object... arguments); + + boolean isDebugEnable(); + + boolean isInfoEnable(); + + boolean isWarnEnable(); + + boolean isErrorEnable(); + + boolean isTraceEnabled(); + + void debug(String format); + + void debug(String format, Object... arguments); + + void debug(Throwable t, String format, Object... arguments); + + void error(String format); + + void trace(String format); + + void trace(String format, Object... arguments); + + void trace(Throwable t, String format, Object... arguments); +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/api/LogManager.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/api/LogManager.java new file mode 100644 index 00000000..cb6514bc --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/api/LogManager.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.logging.api; + +import cn.hippo4j.agent.core.logging.core.PatternLogResolver; + +/** + * LogManager is the {@link LogResolver} implementation manager. By using {@link LogResolver}, {@link + * LogManager#getLogger(Class)} returns a {@link ILog} implementation. This module use this class as the main entrance, + * and block the implementation detail about log-component. In different modules, like server or sniffer, it will use + * different implementations. + * + *

If no {@link LogResolver} is registered, return {@link NoopLogger#INSTANCE} to avoid + * {@link NullPointerException}. If {@link LogManager#setLogResolver(LogResolver)} is called twice, the second will + * override the first without any warning or exception. + * + *

Created by xin on 2016/11/10. + */ +public class LogManager { + + private static LogResolver RESOLVER = new PatternLogResolver(); + + public static void setLogResolver(LogResolver resolver) { + LogManager.RESOLVER = resolver; + } + + public static ILog getLogger(Class clazz) { + if (RESOLVER == null) { + return NoopLogger.INSTANCE; + } + return LogManager.RESOLVER.getLogger(clazz); + } + + public static ILog getLogger(String clazz) { + if (RESOLVER == null) { + return NoopLogger.INSTANCE; + } + return LogManager.RESOLVER.getLogger(clazz); + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/api/LogResolver.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/api/LogResolver.java new file mode 100644 index 00000000..4f145dbb --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/api/LogResolver.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.logging.api; + +/** + * {@link LogResolver} just do only one thing: return the {@link ILog} implementation. + *

+ */ +public interface LogResolver { + + /** + * @param clazz the class is showed in log message. + * @return {@link ILog} implementation. + */ + ILog getLogger(Class clazz); + + /** + * @param clazz the class is showed in log message. + * @return {@link ILog} implementation. + */ + ILog getLogger(String clazz); +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/api/NoopLogger.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/api/NoopLogger.java new file mode 100644 index 00000000..9f544b63 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/api/NoopLogger.java @@ -0,0 +1,122 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.logging.api; + +/** + * No operation logger implementation. Just implement {@link ILog} interface, but do nothing. + *

+ */ +public enum NoopLogger implements ILog { + + INSTANCE; + + @Override + public void info(String message) { + + } + + @Override + public void info(String format, Object... arguments) { + + } + + @Override + public void info(final Throwable t, final String format, final Object... arguments) { + + } + + @Override + public void warn(String format, Object... arguments) { + + } + + @Override + public void error(String format, Throwable e) { + + } + + @Override + public boolean isDebugEnable() { + return false; + } + + @Override + public boolean isInfoEnable() { + return false; + } + + @Override + public boolean isWarnEnable() { + return false; + } + + @Override + public boolean isErrorEnable() { + return false; + } + + @Override + public boolean isTraceEnabled() { + return false; + } + + @Override + public void debug(String format) { + + } + + @Override + public void debug(String format, Object... arguments) { + + } + + @Override + public void debug(final Throwable t, final String format, final Object... arguments) { + + } + + @Override + public void error(String format) { + + } + + @Override + public void trace(final String format) { + + } + + @Override + public void trace(final String format, final Object... arguments) { + + } + + @Override + public void trace(final Throwable t, final String format, final Object... arguments) { + + } + + @Override + public void error(Throwable e, String format, Object... arguments) { + + } + + @Override + public void warn(Throwable e, String format, Object... arguments) { + + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/AbstractLogger.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/AbstractLogger.java new file mode 100644 index 00000000..939d4738 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/AbstractLogger.java @@ -0,0 +1,216 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.logging.core; + +import cn.hippo4j.agent.core.conf.Config; +import cn.hippo4j.agent.core.logging.api.ILog; +import cn.hippo4j.agent.core.logging.core.converters.*; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; + +/** + * An abstract class to simplify the real implementation of the loggers. + * It hold the class name of the logger, and is responsible for log level check, + * message interpolation, etc. + */ +public abstract class AbstractLogger implements ILog { + + public static final Map> DEFAULT_CONVERTER_MAP = new HashMap<>(); + protected List converters = new ArrayList<>(); + + static { + DEFAULT_CONVERTER_MAP.put("thread", ThreadConverter.class); + DEFAULT_CONVERTER_MAP.put("level", LevelConverter.class); + DEFAULT_CONVERTER_MAP.put("agent_name", AgentNameConverter.class); + DEFAULT_CONVERTER_MAP.put("timestamp", DateConverter.class); + DEFAULT_CONVERTER_MAP.put("msg", MessageConverter.class); + DEFAULT_CONVERTER_MAP.put("throwable", ThrowableConverter.class); + DEFAULT_CONVERTER_MAP.put("class", ClassConverter.class); + } + + protected final String targetClass; + + public AbstractLogger(String targetClass) { + this.targetClass = targetClass; + } + + @Override + public void info(String message) { + if (this.isInfoEnable()) { + this.logger(LogLevel.INFO, message, null); + } + } + + @Override + public void info(String message, Object... objects) { + if (this.isInfoEnable()) { + this.logger(LogLevel.INFO, replaceParam(message, objects), null); + } + } + + @Override + public void info(final Throwable throwable, final String message, final Object... objects) { + if (this.isInfoEnable()) { + this.logger(LogLevel.INFO, replaceParam(message, objects), throwable); + } + } + + @Override + public void warn(String message, Object... objects) { + if (this.isWarnEnable()) { + this.logger(LogLevel.WARN, replaceParam(message, objects), null); + } + } + + @Override + public void warn(Throwable throwable, String message, Object... objects) { + if (this.isWarnEnable()) { + this.logger(LogLevel.WARN, replaceParam(message, objects), throwable); + } + } + + @Override + public void error(String message, Throwable throwable) { + if (this.isErrorEnable()) { + this.logger(LogLevel.ERROR, message, throwable); + } + } + + @Override + public void error(Throwable throwable, String message, Object... objects) { + if (this.isErrorEnable()) { + this.logger(LogLevel.ERROR, replaceParam(message, objects), throwable); + } + } + + @Override + public void error(String message) { + if (this.isErrorEnable()) { + this.logger(LogLevel.ERROR, message, null); + } + } + + @Override + public void debug(String message) { + if (this.isDebugEnable()) { + this.logger(LogLevel.DEBUG, message, null); + } + } + + @Override + public void debug(String message, Object... objects) { + if (this.isDebugEnable()) { + this.logger(LogLevel.DEBUG, replaceParam(message, objects), null); + } + } + + @Override + public void debug(Throwable throwable, String message, Object... objects) { + if (this.isDebugEnable()) { + this.logger(LogLevel.DEBUG, replaceParam(message, objects), throwable); + } + } + + @Override + public boolean isDebugEnable() { + return LogLevel.DEBUG.compareTo(Config.Logging.LEVEL) >= 0; + } + + @Override + public boolean isInfoEnable() { + return LogLevel.INFO.compareTo(Config.Logging.LEVEL) >= 0; + } + + @Override + public boolean isWarnEnable() { + return LogLevel.WARN.compareTo(Config.Logging.LEVEL) >= 0; + } + + @Override + public boolean isErrorEnable() { + return LogLevel.ERROR.compareTo(Config.Logging.LEVEL) >= 0; + } + + @Override + public boolean isTraceEnabled() { + return LogLevel.TRACE.compareTo(Config.Logging.LEVEL) >= 0; + } + + @Override + public void trace(final String message) { + if (this.isTraceEnabled()) { + this.logger(LogLevel.TRACE, message, null); + } + } + + @Override + public void trace(final String message, final Object... objects) { + if (this.isTraceEnabled()) { + this.logger(LogLevel.TRACE, replaceParam(message, objects), null); + } + } + + @Override + public void trace(final Throwable throwable, final String message, final Object... objects) { + if (this.isTraceEnabled()) { + this.logger(LogLevel.TRACE, replaceParam(message, objects), throwable); + } + } + + protected String replaceParam(String message, Object... parameters) { + if (message == null) { + return message; + } + int startSize = 0; + int parametersIndex = 0; + int index; + String tmpMessage = message; + while ((index = message.indexOf("{}", startSize)) != -1) { + if (parametersIndex >= parameters.length) { + break; + } + /** + * @Fix the Illegal group reference issue + */ + tmpMessage = tmpMessage.replaceFirst("\\{\\}", Matcher.quoteReplacement(String.valueOf(parameters[parametersIndex++]))); + startSize = index + 2; + } + return tmpMessage; + } + + protected void logger(LogLevel level, String message, Throwable e) { + WriterFactory.getLogWriter().write(this.format(level, message, e)); + } + + /** + * The abstract method left for real loggers. + * Any implementation MUST return string, which will be directly transferred to log destination, + * i.e. log files OR stdout + * + * @param level log level + * @param message log message, which has been interpolated with user-defined parameters. + * @param e throwable if exists + * @return string representation of the log, for example, raw json string for {@link JsonLogger} + */ + protected abstract String format(LogLevel level, String message, Throwable e); + +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/Converter.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/Converter.java new file mode 100644 index 00000000..0bf8d979 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/Converter.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.logging.core; + +/** + * The Converter, it is used to convert the LogEvent to the String. + * For JsonLogger, the `getKey()` method is used to generate the key for json. + */ +public interface Converter { + + String convert(LogEvent logEvent); + + String getKey(); +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/FileWriter.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/FileWriter.java new file mode 100644 index 00000000..f46ef87c --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/FileWriter.java @@ -0,0 +1,230 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.logging.core; + +import cn.hippo4j.agent.core.boot.DefaultNamedThreadFactory; +import cn.hippo4j.agent.core.conf.Config; +import cn.hippo4j.agent.core.conf.Constants; +import cn.hippo4j.agent.core.util.RunnableWithExceptionProtection; + +import java.io.*; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Date; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; + +/** + * The FileWriter support async file output, by using a queue as buffer. + */ +public class FileWriter implements IWriter { + + private static FileWriter INSTANCE; + private static final Object CREATE_LOCK = new Object(); + private FileOutputStream fileOutputStream; + private ArrayBlockingQueue logBuffer; + private volatile int fileSize; + private Pattern filenamePattern = Pattern.compile(Config.Logging.FILE_NAME + "\\.\\d{4}_\\d{2}_\\d{2}_\\d{2}_\\d{2}_\\d{2}"); + + public static FileWriter get() { + if (INSTANCE == null) { + synchronized (CREATE_LOCK) { + if (INSTANCE == null) { + INSTANCE = new FileWriter(); + } + } + } + return INSTANCE; + } + + private FileWriter() { + logBuffer = new ArrayBlockingQueue(1024); + final ArrayList outputLogs = new ArrayList(200); + Executors.newSingleThreadScheduledExecutor(new DefaultNamedThreadFactory("LogFileWriter")) + .scheduleAtFixedRate(new RunnableWithExceptionProtection(new Runnable() { + + @Override + public void run() { + try { + logBuffer.drainTo(outputLogs); + for (String log : outputLogs) { + writeToFile(log + Constants.LINE_SEPARATOR); + } + try { + if (fileOutputStream != null) { + fileOutputStream.flush(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } finally { + outputLogs.clear(); + } + } + }, new RunnableWithExceptionProtection.CallbackWhenException() { + + @Override + public void handle(Throwable t) { + } + }), 0, 1, TimeUnit.SECONDS); + } + + /** + * @param message to be written into the file. + */ + private void writeToFile(String message) { + if (prepareWriteStream()) { + try { + fileOutputStream.write(message.getBytes()); + fileSize += message.length(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + switchFile(); + } + } + } + + private void switchFile() { + if (fileSize > Config.Logging.MAX_FILE_SIZE) { + forceExecute(new Callable() { + + @Override + public Object call() throws Exception { + fileOutputStream.flush(); + return null; + } + }); + forceExecute(new Callable() { + + @Override + public Object call() throws Exception { + fileOutputStream.close(); + return null; + } + }); + forceExecute(new Callable() { + + @Override + public Object call() throws Exception { + new File(Config.Logging.DIR, Config.Logging.FILE_NAME).renameTo(new File(Config.Logging.DIR, Config.Logging.FILE_NAME + new SimpleDateFormat(".yyyy_MM_dd_HH_mm_ss") + .format(new Date()))); + return null; + } + }); + forceExecute(new Callable() { + + @Override + public Object call() throws Exception { + fileOutputStream = null; + return null; + } + }); + + if (Config.Logging.MAX_HISTORY_FILES > 0) { + deleteExpiredFiles(); + } + } + } + + /** + * load history log file name array + * + * @return history log file name array + */ + private String[] getHistoryFilePath() { + File path = new File(Config.Logging.DIR); + String[] pathArr = path.list(new FilenameFilter() { + + @Override + public boolean accept(File dir, String name) { + return filenamePattern.matcher(name).matches(); + } + }); + + return pathArr; + } + + /** + * delete expired log files + */ + private void deleteExpiredFiles() { + String[] historyFileArr = getHistoryFilePath(); + if (historyFileArr != null && historyFileArr.length > Config.Logging.MAX_HISTORY_FILES) { + + Arrays.sort(historyFileArr, new Comparator() { + + @Override + public int compare(String o1, String o2) { + return o2.compareTo(o1); + } + }); + + for (int i = Config.Logging.MAX_HISTORY_FILES; i < historyFileArr.length; i++) { + File expiredFile = new File(Config.Logging.DIR, historyFileArr[i]); + expiredFile.delete(); + } + } + } + + private void forceExecute(Callable callable) { + try { + callable.call(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * @return true if stream is prepared ready. + */ + private boolean prepareWriteStream() { + if (fileOutputStream != null) { + return true; + } + File logFilePath = new File(Config.Logging.DIR); + if (!logFilePath.exists()) { + logFilePath.mkdirs(); + } else if (!logFilePath.isDirectory()) { + System.err.println("Log dir(" + Config.Logging.DIR + ") is not a directory."); + } + try { + fileOutputStream = new FileOutputStream(new File(logFilePath, Config.Logging.FILE_NAME), true); + fileSize = Long.valueOf(new File(logFilePath, Config.Logging.FILE_NAME).length()).intValue(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + + return fileOutputStream != null; + } + + /** + * Write log to the queue. W/ performance trade off. + * + * @param message to log + */ + @Override + public void write(String message) { + logBuffer.offer(message); + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/IWriter.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/IWriter.java new file mode 100644 index 00000000..1dae6b82 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/IWriter.java @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.logging.core; + +public interface IWriter { + + void write(String message); +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/JsonLogResolver.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/JsonLogResolver.java new file mode 100644 index 00000000..913bc770 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/JsonLogResolver.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.logging.core; + +import cn.hippo4j.agent.core.logging.api.ILog; +import cn.hippo4j.agent.core.logging.api.LogResolver; +import com.google.gson.Gson; + +public class JsonLogResolver implements LogResolver { + + private static final Gson GSON = new Gson(); + + @Override + public ILog getLogger(Class aClass) { + return new JsonLogger(aClass, GSON); + } + + @Override + public ILog getLogger(String s) { + return new JsonLogger(s, GSON); + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/JsonLogger.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/JsonLogger.java new file mode 100644 index 00000000..4ef0d682 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/JsonLogger.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.logging.core; + +import cn.hippo4j.agent.core.logging.core.converters.LiteralConverter; +import com.google.gson.Gson; + +import java.util.HashMap; +import java.util.Map; + +/** + * An alternative logger for the SkyWalking agent. The default layout is + * { + * "@timestamp": "", // timestamp + * "logger": "", // name of the Logger + * "level": "", // info|debug|warn|error + * "thread": "", // thread where the log method is called + * "message": "", // your log message + * "throwable": "", + * "agent_name" "service_name" + * } + */ +public class JsonLogger extends AbstractLogger { + + private final Gson gson; + + public JsonLogger(Class targetClass, Gson gson) { + this(targetClass.getSimpleName(), gson); + } + + /** + * In the Constructor, the instances of converters are created, + * except those {@link LiteralConverter} since this class is used + * only the literals in {@link PatternLogger} , + * and thus should not be added to the json log. + * + * @param targetClass the logger class + * @param gson instance of Gson works as json serializer + */ + public JsonLogger(String targetClass, Gson gson) { + super(targetClass); + this.gson = gson; + for (Map.Entry> entry : DEFAULT_CONVERTER_MAP.entrySet()) { + final Class converterClass = entry.getValue(); + try { + if (converters instanceof LiteralConverter) { + continue; + } + converters.add(converterClass.newInstance()); + } catch (IllegalAccessException | InstantiationException e) { + throw new IllegalStateException("Create Converter error. Class: " + converterClass, e); + } + } + } + + @Override + protected String format(LogLevel level, String message, Throwable e) { + LogEvent logEvent = new LogEvent(level, message, e, this.targetClass); + Map log = new HashMap<>(this.converters.size()); + for (Converter converter : this.converters) { + log.put(converter.getKey(), converter.convert(logEvent)); + } + return this.gson.toJson(log); + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/LogEvent.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/LogEvent.java new file mode 100644 index 00000000..1246232d --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/LogEvent.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.logging.core; + +/** + * The representation of logging events. This instance is pass around to the List of Converter. + */ +public class LogEvent { + + public LogEvent(LogLevel level, String message, Throwable throwable, String targetClass) { + this.level = level; + this.message = message; + this.throwable = throwable; + this.targetClass = targetClass; + } + + private LogLevel level; + private String message; + private Throwable throwable; + private String targetClass; + + public String getTargetClass() { + return targetClass; + } + + public void setTargetClass(String targetClass) { + this.targetClass = targetClass; + } + + public LogLevel getLevel() { + return level; + } + + public void setLevel(LogLevel level) { + this.level = level; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Throwable getThrowable() { + return throwable; + } + + public void setThrowable(Throwable throwable) { + this.throwable = throwable; + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/LogLevel.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/LogLevel.java new file mode 100644 index 00000000..facc35b3 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/LogLevel.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.logging.core; + +public enum LogLevel { + TRACE, DEBUG, INFO, WARN, ERROR, OFF +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/LogMessageHolder.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/LogMessageHolder.java new file mode 100644 index 00000000..244733b5 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/LogMessageHolder.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.logging.core; + +/** + * The LogMessageHolder is a {@link String} holder, in order to in-process propagation String across the + * disruptor queue. + */ +public class LogMessageHolder { + + private String message; + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/LogOutput.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/LogOutput.java new file mode 100644 index 00000000..2bf279a8 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/LogOutput.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.logging.core; + +public enum LogOutput { + FILE, CONSOLE +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/Parser.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/Parser.java new file mode 100644 index 00000000..999b78f7 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/Parser.java @@ -0,0 +1,190 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.logging.core; + +import cn.hippo4j.agent.core.logging.core.converters.LiteralConverter; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Parser of LogPattern. It is used to parse a pattern to the List of Converter. + */ +public class Parser { + + private final Map> convertMaps; + + enum State { + LITERAL_STATE, KEYWORD_STATE + } + + public static final char ESCAPE_CHAR = '\\'; + public static final char PERCENT_CHAR = '%'; + + private final String pattern; + private final int patternLength; + private int pointer = 0; + private State state = State.LITERAL_STATE; + + public Parser(String pattern, Map> convertMaps) { + if (pattern == null || pattern.length() == 0) { + throw new IllegalArgumentException("null or empty pattern string not allowed"); + } + this.convertMaps = convertMaps; + this.pattern = pattern; + this.patternLength = pattern.length(); + } + + public List parse() { + List patternConverters = new ArrayList(); + StringBuilder buf = new StringBuilder(); + while (pointer < patternLength) { + char c = pattern.charAt(pointer); + pointer++; + switch (state) { + case LITERAL_STATE: + handleLiteralState(c, buf, patternConverters); + break; + case KEYWORD_STATE: + handleKeywordState(c, buf, patternConverters); + break; + default: + } + } + + switch (state) { + case LITERAL_STATE: + addConverter(buf, patternConverters, LiteralConverter.class); + break; + case KEYWORD_STATE: + addConverterWithKeyword(buf, patternConverters); + break; + default: + } + return combineLiteral(patternConverters); + } + + private List combineLiteral(List patternConverters) { + List converterList = new ArrayList(); + StringBuilder stringBuilder = new StringBuilder(); + for (Converter patternConverter : patternConverters) { + if (patternConverter instanceof LiteralConverter) { + stringBuilder.append(patternConverter.convert(null)); + } else { + if (stringBuilder.length() > 0) { + converterList.add(new LiteralConverter(stringBuilder.toString())); + stringBuilder.setLength(0); + } + converterList.add(patternConverter); + } + } + return converterList; + } + + private void handleKeywordState(char c, StringBuilder buf, List patternConverters) { + if (Character.isJavaIdentifierPart(c)) { + buf.append(c); + } else if (c == PERCENT_CHAR) { + addConverterWithKeyword(buf, patternConverters); + } else { + addConverterWithKeyword(buf, patternConverters); + if (c == ESCAPE_CHAR) { + escape("%", buf); + } else { + buf.append(c); + } + state = State.LITERAL_STATE; + } + } + + private void addConverterWithKeyword(StringBuilder buf, List patternConverters) { + String keyword = buf.toString(); + if (convertMaps.containsKey(keyword)) { + addConverter(buf, patternConverters, convertMaps.get(keyword)); + } else { + buf.insert(0, "%"); + addConverter(buf, patternConverters, LiteralConverter.class); + } + } + + private void handleLiteralState(char c, StringBuilder buf, List patternConverters) { + switch (c) { + case ESCAPE_CHAR: + escape("%", buf); + break; + case PERCENT_CHAR: + addConverter(buf, patternConverters, LiteralConverter.class); + state = State.KEYWORD_STATE; + break; + default: + buf.append(c); + } + + } + + private void escape(String escapeChars, StringBuilder buf) { + if (pointer < patternLength) { + char next = pattern.charAt(pointer++); + escape(escapeChars, buf, next); + } + } + + private void addConverter(StringBuilder buf, List patternConverters, Class aClass) { + if (buf.length() > 0) { + String result = buf.toString(); + if (LiteralConverter.class.equals(aClass)) { + patternConverters.add(new LiteralConverter(result)); + } else { + try { + patternConverters.add(aClass.newInstance()); + } catch (Exception e) { + throw new IllegalStateException("Create Converter error. Class: " + aClass, e); + } + } + buf.setLength(0); + } + } + + private void escape(String escapeChars, StringBuilder buf, char next) { + if (escapeChars.indexOf(next) >= 0) { + buf.append(next); + } else { + switch (next) { + case '_': + // the \_ sequence is swallowed + break; + case '\\': + buf.append(next); + break; + case 't': + buf.append('\t'); + break; + case 'r': + buf.append('\r'); + break; + case 'n': + buf.append('\n'); + break; + default: + throw new IllegalArgumentException("Illegal char " + next + ". It not allowed as escape characters."); + } + } + } + +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/PatternLogResolver.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/PatternLogResolver.java new file mode 100644 index 00000000..075c2e01 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/PatternLogResolver.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.logging.core; + +import cn.hippo4j.agent.core.conf.Config; +import cn.hippo4j.agent.core.logging.api.ILog; +import cn.hippo4j.agent.core.logging.api.LogResolver; + +public class PatternLogResolver implements LogResolver { + + @Override + public ILog getLogger(Class clazz) { + return new PatternLogger(clazz, Config.Logging.PATTERN); + } + + @Override + public ILog getLogger(String clazz) { + return new PatternLogger(clazz, Config.Logging.PATTERN); + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/PatternLogger.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/PatternLogger.java new file mode 100644 index 00000000..b9e4ef67 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/PatternLogger.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.logging.core; + +import cn.hippo4j.agent.core.logging.api.ILog; +import cn.hippo4j.agent.core.util.StringUtil; + +/** + * A flexible Logger configurable with pattern string. This is default implementation of {@link ILog} This can parse a + * pattern to the List of converter with Parser. We package LogEvent with message, level,timestamp ..., passing around + * to the List of converter to concat actually Log-String. + */ +public class PatternLogger extends AbstractLogger { + + public static final String DEFAULT_PATTERN = "%level %timestamp %thread %class : %msg %throwable"; + + private String pattern; + + public PatternLogger(Class targetClass, String pattern) { + this(targetClass.getSimpleName(), pattern); + } + + public PatternLogger(String targetClass, String pattern) { + super(targetClass); + this.setPattern(pattern); + } + + public String getPattern() { + return pattern; + } + + public void setPattern(String pattern) { + if (StringUtil.isEmpty(pattern)) { + pattern = DEFAULT_PATTERN; + } + this.pattern = pattern; + this.converters = new Parser(pattern, DEFAULT_CONVERTER_MAP).parse(); + } + + @Override + protected String format(LogLevel level, String message, Throwable t) { + LogEvent logEvent = new LogEvent(level, message, t, targetClass); + StringBuilder stringBuilder = new StringBuilder(); + for (Converter converter : this.converters) { + stringBuilder.append(converter.convert(logEvent)); + } + return stringBuilder.toString(); + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/ResolverType.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/ResolverType.java new file mode 100644 index 00000000..734d9294 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/ResolverType.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.logging.core; + +public enum ResolverType { + JSON, PATTERN +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/SystemOutWriter.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/SystemOutWriter.java new file mode 100644 index 00000000..e4cce119 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/SystemOutWriter.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.logging.core; + +import java.io.PrintStream; + +public enum SystemOutWriter implements IWriter { + + INSTANCE; + + /** + * Tricky codes for avoiding style-check. Because, in here, "system.out.println" is the only choice to output logs. + */ + @Override + public void write(String message) { + PrintStream out = System.out; + out.println(message); + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/WriterFactory.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/WriterFactory.java new file mode 100644 index 00000000..90777b21 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/WriterFactory.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.logging.core; + +import cn.hippo4j.agent.core.boot.AgentPackageNotFoundException; +import cn.hippo4j.agent.core.boot.AgentPackagePath; +import cn.hippo4j.agent.core.conf.Config; +import cn.hippo4j.agent.core.conf.SnifferConfigInitializer; +import cn.hippo4j.agent.core.plugin.PluginFinder; +import cn.hippo4j.agent.core.util.StringUtil; + +import static cn.hippo4j.agent.core.logging.core.LogOutput.FILE; + +public class WriterFactory { + + private static IWriter WRITER; + + public static IWriter getLogWriter() { + + switch (Config.Logging.OUTPUT) { + case FILE: + if (WRITER != null) { + return WRITER; + } + if (SnifferConfigInitializer.isInitCompleted() + && PluginFinder.isPluginInitCompleted() + && AgentPackagePath.isPathFound()) { + if (StringUtil.isEmpty(Config.Logging.DIR)) { + try { + Config.Logging.DIR = AgentPackagePath.getPath() + "/logs"; + } catch (AgentPackageNotFoundException e) { + e.printStackTrace(); + } + } + WRITER = FileWriter.get(); + } else { + return SystemOutWriter.INSTANCE; + } + break; + default: + return SystemOutWriter.INSTANCE; + + } + return WRITER; + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/converters/AgentNameConverter.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/converters/AgentNameConverter.java new file mode 100644 index 00000000..21090d25 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/converters/AgentNameConverter.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.logging.core.converters; + +import cn.hippo4j.agent.core.conf.Config; +import cn.hippo4j.agent.core.logging.core.Converter; +import cn.hippo4j.agent.core.logging.core.LogEvent; + +public class AgentNameConverter implements Converter { + + @Override + public String convert(LogEvent logEvent) { + return Config.Agent.SERVICE_NAME; + } + + @Override + public String getKey() { + return "agent_name"; + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/converters/ClassConverter.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/converters/ClassConverter.java new file mode 100644 index 00000000..5dc9951d --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/converters/ClassConverter.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.logging.core.converters; + +import cn.hippo4j.agent.core.logging.core.Converter; +import cn.hippo4j.agent.core.logging.core.LogEvent; + +/** + * Just return logEvent.getTargetClass(). + */ +public class ClassConverter implements Converter { + + @Override + public String convert(LogEvent logEvent) { + return logEvent.getTargetClass(); + } + + @Override + public String getKey() { + return "logger"; + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/converters/DateConverter.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/converters/DateConverter.java new file mode 100644 index 00000000..e3a68067 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/converters/DateConverter.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.logging.core.converters; + +import cn.hippo4j.agent.core.logging.core.Converter; +import cn.hippo4j.agent.core.logging.core.LogEvent; + +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * The Converter is used to return a now date with format. + */ +public class DateConverter implements Converter { + + @Override + public String convert(LogEvent logEvent) { + return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()); + } + + @Override + public String getKey() { + return "@timestamp"; + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/converters/LevelConverter.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/converters/LevelConverter.java new file mode 100644 index 00000000..775c97ce --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/converters/LevelConverter.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.logging.core.converters; + +import cn.hippo4j.agent.core.logging.core.Converter; +import cn.hippo4j.agent.core.logging.core.LogEvent; + +/** + * Just return logEvent.getLevel().name() + */ +public class LevelConverter implements Converter { + + @Override + public String convert(LogEvent logEvent) { + return logEvent.getLevel().name(); + } + + @Override + public String getKey() { + return "level"; + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/converters/LiteralConverter.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/converters/LiteralConverter.java new file mode 100644 index 00000000..bd334955 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/converters/LiteralConverter.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.logging.core.converters; + +import cn.hippo4j.agent.core.logging.core.Converter; +import cn.hippo4j.agent.core.logging.core.LogEvent; + +/** + * This Converter is used to return the literal. + */ +public class LiteralConverter implements Converter { + + private final String literal; + + public LiteralConverter(String literal) { + this.literal = literal; + } + + @Override + public String convert(LogEvent logEvent) { + return literal; + } + + @Override + public String getKey() { + return ""; + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/converters/MessageConverter.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/converters/MessageConverter.java new file mode 100644 index 00000000..a38cc3c7 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/converters/MessageConverter.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.logging.core.converters; + +import cn.hippo4j.agent.core.logging.core.Converter; +import cn.hippo4j.agent.core.logging.core.LogEvent; + +/** + * Just return the logEvent.getMessage() + */ +public class MessageConverter implements Converter { + + @Override + public String convert(LogEvent logEvent) { + return logEvent.getMessage(); + } + + @Override + public String getKey() { + return "message"; + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/converters/ThreadConverter.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/converters/ThreadConverter.java new file mode 100644 index 00000000..7bd6b16f --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/converters/ThreadConverter.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.logging.core.converters; + +import cn.hippo4j.agent.core.logging.core.Converter; +import cn.hippo4j.agent.core.logging.core.LogEvent; + +/** + * Just return the Thread.currentThread().getName() + */ +public class ThreadConverter implements Converter { + + @Override + public String convert(LogEvent logEvent) { + return Thread.currentThread().getName(); + } + + @Override + public String getKey() { + return "thread"; + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/converters/ThrowableConverter.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/converters/ThrowableConverter.java new file mode 100644 index 00000000..8d307f0a --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/logging/core/converters/ThrowableConverter.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.logging.core.converters; + +import cn.hippo4j.agent.core.conf.Constants; +import cn.hippo4j.agent.core.logging.core.Converter; +import cn.hippo4j.agent.core.logging.core.LogEvent; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +/** + * Return the StackTrace of String with logEvent.getThrowable() + */ +public class ThrowableConverter implements Converter { + + @Override + public String convert(LogEvent logEvent) { + Throwable t = logEvent.getThrowable(); + return t == null ? "" : format(t); + } + + public static String format(Throwable t) { + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + t.printStackTrace(new java.io.PrintWriter(buf, true)); + String expMessage = buf.toString(); + try { + buf.close(); + } catch (IOException e) { + e.printStackTrace(); + } + return Constants.LINE_SEPARATOR + expMessage; + } + + @Override + public String getKey() { + return "throwable"; + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/os/OSUtil.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/os/OSUtil.java new file mode 100644 index 00000000..a2025596 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/os/OSUtil.java @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.os; + +import java.lang.management.ManagementFactory; +import java.net.*; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.LinkedList; +import java.util.List; + +public class OSUtil { + + private static volatile String OS_NAME; + private static volatile String HOST_NAME; + private static volatile List IPV4_LIST; + private static volatile int PROCESS_NO = 0; + + public static String getOsName() { + if (OS_NAME == null) { + OS_NAME = System.getProperty("os.name"); + } + return OS_NAME; + } + + public static String getHostName() { + if (HOST_NAME == null) { + try { + InetAddress host = InetAddress.getLocalHost(); + HOST_NAME = host.getHostName(); + } catch (UnknownHostException e) { + HOST_NAME = "unknown"; + } + } + return HOST_NAME; + } + + public static List getAllIPV4() { + if (IPV4_LIST == null) { + IPV4_LIST = new LinkedList<>(); + try { + Enumeration interfs = NetworkInterface.getNetworkInterfaces(); + while (interfs.hasMoreElements()) { + NetworkInterface networkInterface = interfs.nextElement(); + Enumeration inetAddresses = networkInterface.getInetAddresses(); + while (inetAddresses.hasMoreElements()) { + InetAddress address = inetAddresses.nextElement(); + if (address instanceof Inet4Address) { + String addressStr = address.getHostAddress(); + if ("127.0.0.1".equals(addressStr)) { + continue; + } else if ("localhost".equals(addressStr)) { + continue; + } + IPV4_LIST.add(addressStr); + } + } + } + } catch (SocketException e) { + + } + } + return IPV4_LIST; + } + + public static String getIPV4() { + final List allIPV4 = getAllIPV4(); + if (allIPV4.size() > 0) { + return allIPV4.get(0); + } else { + return "no-hostname"; + } + } + + public static int getProcessNo() { + if (PROCESS_NO == 0) { + try { + PROCESS_NO = Integer.parseInt(ManagementFactory.getRuntimeMXBean().getName().split("@")[0]); + } catch (Exception e) { + PROCESS_NO = -1; + } + } + return PROCESS_NO; + } + +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/os/ProcessorUtil.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/os/ProcessorUtil.java new file mode 100644 index 00000000..30abb493 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/os/ProcessorUtil.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.os; + +import java.lang.management.ManagementFactory; + +public class ProcessorUtil { + + public static int getNumberOfProcessors() { + return ManagementFactory.getOperatingSystemMXBean().getAvailableProcessors(); + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/AbstractClassEnhancePluginDefine.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/AbstractClassEnhancePluginDefine.java new file mode 100644 index 00000000..41384b1a --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/AbstractClassEnhancePluginDefine.java @@ -0,0 +1,202 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin; + +import cn.hippo4j.agent.core.logging.api.ILog; +import cn.hippo4j.agent.core.logging.api.LogManager; +import cn.hippo4j.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import cn.hippo4j.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import cn.hippo4j.agent.core.plugin.interceptor.StaticMethodsInterceptPoint; +import cn.hippo4j.agent.core.plugin.interceptor.enhance.ClassEnhancePluginDefine; +import cn.hippo4j.agent.core.plugin.interceptor.v2.InstanceMethodsInterceptV2Point; +import cn.hippo4j.agent.core.plugin.interceptor.v2.StaticMethodsInterceptV2Point; +import cn.hippo4j.agent.core.plugin.match.ClassMatch; +import cn.hippo4j.agent.core.util.CollectionUtil; +import cn.hippo4j.agent.core.util.StringUtil; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.dynamic.DynamicType; + +import java.util.List; + +/** + * Basic abstract class of all sky-walking auto-instrumentation plugins. + *

+ * It provides the outline of enhancing the target class. If you want to know more about enhancing, you should go to see + * {@link ClassEnhancePluginDefine} + */ +public abstract class AbstractClassEnhancePluginDefine { + + private static final ILog LOGGER = LogManager.getLogger(AbstractClassEnhancePluginDefine.class); + + /** + * New field name. + */ + public static final String CONTEXT_ATTR_NAME = "_$EnhancedClassField_ws"; + + /** + * Main entrance of enhancing the class. + * + * @param typeDescription target class description. + * @param builder byte-buddy's builder to manipulate target class's bytecode. + * @param classLoader load the given transformClass + * @return the new builder, or null if not be enhanced. + * @throws PluginException when set builder failure. + */ + public DynamicType.Builder define(TypeDescription typeDescription, DynamicType.Builder builder, + ClassLoader classLoader, EnhanceContext context) throws PluginException { + String interceptorDefineClassName = this.getClass().getName(); + String transformClassName = typeDescription.getTypeName(); + if (StringUtil.isEmpty(transformClassName)) { + LOGGER.warn("classname of being intercepted is not defined by {}.", interceptorDefineClassName); + return null; + } + + LOGGER.debug("prepare to enhance class {} by {}.", transformClassName, interceptorDefineClassName); + WitnessFinder finder = WitnessFinder.INSTANCE; + /** + * find witness classes for enhance class + */ + String[] witnessClasses = witnessClasses(); + if (witnessClasses != null) { + for (String witnessClass : witnessClasses) { + if (!finder.exist(witnessClass, classLoader)) { + LOGGER.warn("enhance class {} by plugin {} is not activated. Witness class {} does not exist.", transformClassName, interceptorDefineClassName, witnessClass); + return null; + } + } + } + List witnessMethods = witnessMethods(); + if (!CollectionUtil.isEmpty(witnessMethods)) { + for (WitnessMethod witnessMethod : witnessMethods) { + if (!finder.exist(witnessMethod, classLoader)) { + LOGGER.warn("enhance class {} by plugin {} is not activated. Witness method {} does not exist.", transformClassName, interceptorDefineClassName, witnessMethod); + return null; + } + } + } + + /** + * find origin class source code for interceptor + */ + DynamicType.Builder newClassBuilder = this.enhance(typeDescription, builder, classLoader, context); + + context.initializationStageCompleted(); + LOGGER.debug("enhance class {} by {} completely.", transformClassName, interceptorDefineClassName); + + return newClassBuilder; + } + + /** + * Begin to define how to enhance class. After invoke this method, only means definition is finished. + * + * @param typeDescription target class description + * @param newClassBuilder byte-buddy's builder to manipulate class bytecode. + * @return new byte-buddy's builder for further manipulation. + */ + protected DynamicType.Builder enhance(TypeDescription typeDescription, DynamicType.Builder newClassBuilder, + ClassLoader classLoader, EnhanceContext context) throws PluginException { + newClassBuilder = this.enhanceClass(typeDescription, newClassBuilder, classLoader); + + newClassBuilder = this.enhanceInstance(typeDescription, newClassBuilder, classLoader, context); + + return newClassBuilder; + } + + /** + * Enhance a class to intercept constructors and class instance methods. + * + * @param typeDescription target class description + * @param newClassBuilder byte-buddy's builder to manipulate class bytecode. + * @return new byte-buddy's builder for further manipulation. + */ + protected abstract DynamicType.Builder enhanceInstance(TypeDescription typeDescription, + DynamicType.Builder newClassBuilder, ClassLoader classLoader, + EnhanceContext context) throws PluginException; + + /** + * Enhance a class to intercept class static methods. + * + * @param typeDescription target class description + * @param newClassBuilder byte-buddy's builder to manipulate class bytecode. + * @return new byte-buddy's builder for further manipulation. + */ + protected abstract DynamicType.Builder enhanceClass(TypeDescription typeDescription, DynamicType.Builder newClassBuilder, + ClassLoader classLoader) throws PluginException; + + /** + * Define the {@link ClassMatch} for filtering class. + * + * @return {@link ClassMatch} + */ + protected abstract ClassMatch enhanceClass(); + + /** + * Witness classname list. Why need witness classname? Let's see like this: A library existed two released versions + * (like 1.0, 2.0), which include the same target classes, but because of version iterator, they may have the same + * name, but different methods, or different method arguments list. So, if I want to target the particular version + * (let's say 1.0 for example), version number is obvious not an option, this is the moment you need "Witness + * classes". You can add any classes only in this particular release version ( something like class + * com.company.1.x.A, only in 1.0 ), and you can achieve the goal. + */ + protected String[] witnessClasses() { + return new String[]{}; + } + + protected List witnessMethods() { + return null; + } + + public boolean isBootstrapInstrumentation() { + return false; + } + + /** + * Constructor methods intercept point. See {@link ConstructorInterceptPoint} + * + * @return collections of {@link ConstructorInterceptPoint} + */ + public abstract ConstructorInterceptPoint[] getConstructorsInterceptPoints(); + + /** + * Instance methods intercept point. See {@link InstanceMethodsInterceptPoint} + * + * @return collections of {@link InstanceMethodsInterceptPoint} + */ + public abstract InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints(); + + /** + * Instance methods intercept v2 point. See {@link InstanceMethodsInterceptV2Point} + * + * @return collections of {@link InstanceMethodsInterceptV2Point} + */ + public abstract InstanceMethodsInterceptV2Point[] getInstanceMethodsInterceptV2Points(); + + /** + * Static methods intercept point. See {@link StaticMethodsInterceptPoint} + * + * @return collections of {@link StaticMethodsInterceptPoint} + */ + public abstract StaticMethodsInterceptPoint[] getStaticMethodsInterceptPoints(); + + /** + * Instance methods intercept v2 point. See {@link InstanceMethodsInterceptV2Point} + * + * @return collections of {@link InstanceMethodsInterceptV2Point} + */ + public abstract StaticMethodsInterceptV2Point[] getStaticMethodsInterceptV2Points(); +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/ByteBuddyCoreClasses.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/ByteBuddyCoreClasses.java new file mode 100644 index 00000000..7a5ceeb2 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/ByteBuddyCoreClasses.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin; + +/** + * All ByteBuddy core classes required to expose, including open edge for JDK 9+ module, or Bootstrap instrumentation. + */ +public class ByteBuddyCoreClasses { + + private static final String SHADE_PACKAGE = "org.apache.skywalking.apm.dependencies."; + + public static final String[] CLASSES = { + SHADE_PACKAGE + "net.bytebuddy.implementation.bind.annotation.RuntimeType", + SHADE_PACKAGE + "net.bytebuddy.implementation.bind.annotation.This", + SHADE_PACKAGE + "net.bytebuddy.implementation.bind.annotation.AllArguments", + SHADE_PACKAGE + "net.bytebuddy.implementation.bind.annotation.AllArguments$Assignment", + SHADE_PACKAGE + "net.bytebuddy.implementation.bind.annotation.SuperCall", + SHADE_PACKAGE + "net.bytebuddy.implementation.bind.annotation.Origin", + SHADE_PACKAGE + "net.bytebuddy.implementation.bind.annotation.Morph", + }; +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/DynamicPluginLoader.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/DynamicPluginLoader.java new file mode 100755 index 00000000..30ff5a94 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/DynamicPluginLoader.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin; + +import cn.hippo4j.agent.core.plugin.loader.AgentClassLoader; +import cn.hippo4j.agent.core.plugin.loader.InstrumentationLoader; + +import java.util.ArrayList; +import java.util.List; +import java.util.ServiceLoader; + +/** + * The plugin can be inserted into the kernel by implementing this spi return PluginDefine list. + */ + +public enum DynamicPluginLoader { + + INSTANCE; + + public List load(AgentClassLoader classLoader) { + List all = new ArrayList(); + for (InstrumentationLoader instrumentationLoader : ServiceLoader.load(InstrumentationLoader.class, classLoader)) { + List plugins = instrumentationLoader.load(classLoader); + if (plugins != null && !plugins.isEmpty()) { + all.addAll(plugins); + } + } + return all; + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/EnhanceContext.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/EnhanceContext.java new file mode 100644 index 00000000..c903ce86 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/EnhanceContext.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin; + +import cn.hippo4j.agent.core.plugin.interceptor.enhance.ClassEnhancePluginDefine; + +/** + * The EnhanceContext represents the context or status for processing a class. + *

+ * Based on this context, the plugin core {@link ClassEnhancePluginDefine} knows how to process the specific steps for + * every particular plugin. + */ +public class EnhanceContext { + + private boolean isEnhanced = false; + /** + * The object has already been enhanced or extended. e.g. added the new field, or implemented the new interface + */ + private boolean objectExtended = false; + + public boolean isEnhanced() { + return isEnhanced; + } + + public void initializationStageCompleted() { + isEnhanced = true; + } + + public boolean isObjectExtended() { + return objectExtended; + } + + public void extendObjectCompleted() { + objectExtended = true; + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/InstrumentDebuggingClass.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/InstrumentDebuggingClass.java new file mode 100644 index 00000000..e82258fb --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/InstrumentDebuggingClass.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin; + +import cn.hippo4j.agent.core.boot.AgentPackageNotFoundException; +import cn.hippo4j.agent.core.boot.AgentPackagePath; +import cn.hippo4j.agent.core.conf.Config; +import cn.hippo4j.agent.core.logging.api.ILog; +import cn.hippo4j.agent.core.logging.api.LogManager; +import net.bytebuddy.dynamic.DynamicType; + +import java.io.File; +import java.io.IOException; + +/** + * The manipulated class output. Write the dynamic classes to the `debugging` folder, when we need to do some debug and + * recheck. + */ +public enum InstrumentDebuggingClass { + + INSTANCE; + + private static final ILog LOGGER = LogManager.getLogger(InstrumentDebuggingClass.class); + private File debuggingClassesRootPath; + + public void log(DynamicType dynamicType) { + if (!Config.Agent.IS_OPEN_DEBUGGING_CLASS) { + return; + } + + /** + * try to do I/O things in synchronized way, to avoid unexpected situations. + */ + synchronized (INSTANCE) { + try { + if (debuggingClassesRootPath == null) { + try { + debuggingClassesRootPath = new File(AgentPackagePath.getPath(), "/debugging"); + if (!debuggingClassesRootPath.exists()) { + debuggingClassesRootPath.mkdir(); + } + } catch (AgentPackageNotFoundException e) { + LOGGER.error(e, "Can't find the root path for creating /debugging folder."); + } + } + + try { + dynamicType.saveIn(debuggingClassesRootPath); + } catch (IOException e) { + LOGGER.error(e, "Can't save class {} to file." + dynamicType.getTypeDescription().getActualName()); + } + } catch (Throwable t) { + LOGGER.error(t, "Save debugging classes fail."); + } + } + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/PluginBootstrap.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/PluginBootstrap.java new file mode 100644 index 00000000..e17c0608 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/PluginBootstrap.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin; + +import cn.hippo4j.agent.core.boot.AgentPackageNotFoundException; +import cn.hippo4j.agent.core.logging.api.ILog; +import cn.hippo4j.agent.core.logging.api.LogManager; +import cn.hippo4j.agent.core.plugin.loader.AgentClassLoader; + +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +/** + * Plugins finder. Use {@link PluginResourcesResolver} to find all plugins, and ask {@link PluginCfg} to load all plugin + * definitions. + */ +public class PluginBootstrap { + + private static final ILog LOGGER = LogManager.getLogger(PluginBootstrap.class); + + /** + * load all plugins. + * + * @return plugin definition list. + */ + public List loadPlugins() throws AgentPackageNotFoundException { + AgentClassLoader.initDefaultLoader(); + + PluginResourcesResolver resolver = new PluginResourcesResolver(); + List resources = resolver.getResources(); + + if (resources == null || resources.size() == 0) { + LOGGER.info("no plugin files (skywalking-plugin.def) found, continue to start application."); + return new ArrayList(); + } + + for (URL pluginUrl : resources) { + try { + PluginCfg.INSTANCE.load(pluginUrl.openStream()); + } catch (Throwable t) { + LOGGER.error(t, "plugin file [{}] init failure.", pluginUrl); + } + } + + List pluginClassList = PluginCfg.INSTANCE.getPluginClassList(); + + List plugins = new ArrayList(); + for (PluginDefine pluginDefine : pluginClassList) { + try { + LOGGER.debug("loading plugin class {}.", pluginDefine.getDefineClass()); + AbstractClassEnhancePluginDefine plugin = (AbstractClassEnhancePluginDefine) Class.forName(pluginDefine.getDefineClass(), true, AgentClassLoader + .getDefault()).newInstance(); + plugins.add(plugin); + } catch (Throwable t) { + LOGGER.error(t, "load plugin [{}] failure.", pluginDefine.getDefineClass()); + } + } + + plugins.addAll(DynamicPluginLoader.INSTANCE.load(AgentClassLoader.getDefault())); + + return plugins; + + } + +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/PluginCfg.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/PluginCfg.java new file mode 100644 index 00000000..81e5027b --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/PluginCfg.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin; + +import cn.hippo4j.agent.core.logging.api.ILog; +import cn.hippo4j.agent.core.logging.api.LogManager; +import cn.hippo4j.agent.core.plugin.exception.IllegalPluginDefineException; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; + +public enum PluginCfg { + + INSTANCE; + + private static final ILog LOGGER = LogManager.getLogger(PluginCfg.class); + + private List pluginClassList = new ArrayList(); + private PluginSelector pluginSelector = new PluginSelector(); + + void load(InputStream input) throws IOException { + try { + BufferedReader reader = new BufferedReader(new InputStreamReader(input)); + String pluginDefine; + while ((pluginDefine = reader.readLine()) != null) { + try { + if (pluginDefine.trim().length() == 0 || pluginDefine.startsWith("#")) { + continue; + } + PluginDefine plugin = PluginDefine.build(pluginDefine); + pluginClassList.add(plugin); + } catch (IllegalPluginDefineException e) { + LOGGER.error(e, "Failed to format plugin({}) define.", pluginDefine); + } + } + pluginClassList = pluginSelector.select(pluginClassList); + } finally { + input.close(); + } + } + + public List getPluginClassList() { + return pluginClassList; + } + +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/PluginDefine.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/PluginDefine.java new file mode 100644 index 00000000..79501e09 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/PluginDefine.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin; + +import cn.hippo4j.agent.core.plugin.exception.IllegalPluginDefineException; +import cn.hippo4j.agent.core.util.StringUtil; + +public class PluginDefine { + + /** + * Plugin name. + */ + private String name; + + /** + * The class name of plugin defined. + */ + private String defineClass; + + private PluginDefine(String name, String defineClass) { + this.name = name; + this.defineClass = defineClass; + } + + public static PluginDefine build(String define) throws IllegalPluginDefineException { + if (StringUtil.isEmpty(define)) { + throw new IllegalPluginDefineException(define); + } + + String[] pluginDefine = define.split("="); + if (pluginDefine.length != 2) { + throw new IllegalPluginDefineException(define); + } + + String pluginName = pluginDefine[0]; + String defineClass = pluginDefine[1]; + return new PluginDefine(pluginName, defineClass); + } + + public String getDefineClass() { + return defineClass; + } + + public String getName() { + return name; + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/PluginException.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/PluginException.java new file mode 100644 index 00000000..6b69a009 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/PluginException.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin; + +public class PluginException extends RuntimeException { + + private static final long serialVersionUID = -6020188711867490724L; + + public PluginException(String message) { + super(message); + } + + public PluginException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/PluginFinder.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/PluginFinder.java new file mode 100644 index 00000000..556726f8 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/PluginFinder.java @@ -0,0 +1,117 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin; + +import cn.hippo4j.agent.core.plugin.bytebuddy.AbstractJunction; +import cn.hippo4j.agent.core.plugin.match.ClassMatch; +import cn.hippo4j.agent.core.plugin.match.IndirectMatch; +import cn.hippo4j.agent.core.plugin.match.NameMatch; +import cn.hippo4j.agent.core.plugin.match.ProtectiveShieldMatcher; +import net.bytebuddy.description.NamedElement; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +import java.util.*; + +import static net.bytebuddy.matcher.ElementMatchers.isInterface; +import static net.bytebuddy.matcher.ElementMatchers.not; + +/** + * The PluginFinder represents a finder , which assist to find the one from the given {@link + * AbstractClassEnhancePluginDefine} list. + */ +public class PluginFinder { + + private final Map> nameMatchDefine = new HashMap>(); + private final List signatureMatchDefine = new ArrayList(); + private final List bootstrapClassMatchDefine = new ArrayList(); + private static boolean IS_PLUGIN_INIT_COMPLETED = false; + + public PluginFinder(List plugins) { + for (AbstractClassEnhancePluginDefine plugin : plugins) { + ClassMatch match = plugin.enhanceClass(); + + if (match == null) { + continue; + } + + if (match instanceof NameMatch) { + NameMatch nameMatch = (NameMatch) match; + LinkedList pluginDefines = nameMatchDefine.get(nameMatch.getClassName()); + if (pluginDefines == null) { + pluginDefines = new LinkedList(); + nameMatchDefine.put(nameMatch.getClassName(), pluginDefines); + } + pluginDefines.add(plugin); + } else { + signatureMatchDefine.add(plugin); + } + + if (plugin.isBootstrapInstrumentation()) { + bootstrapClassMatchDefine.add(plugin); + } + } + } + + public List find(TypeDescription typeDescription) { + List matchedPlugins = new LinkedList(); + String typeName = typeDescription.getTypeName(); + if (nameMatchDefine.containsKey(typeName)) { + matchedPlugins.addAll(nameMatchDefine.get(typeName)); + } + + for (AbstractClassEnhancePluginDefine pluginDefine : signatureMatchDefine) { + IndirectMatch match = (IndirectMatch) pluginDefine.enhanceClass(); + if (match.isMatch(typeDescription)) { + matchedPlugins.add(pluginDefine); + } + } + + return matchedPlugins; + } + + public ElementMatcher buildMatch() { + ElementMatcher.Junction judge = new AbstractJunction() { + + @Override + public boolean matches(NamedElement target) { + return nameMatchDefine.containsKey(target.getActualName()); + } + }; + judge = judge.and(not(isInterface())); + for (AbstractClassEnhancePluginDefine define : signatureMatchDefine) { + ClassMatch match = define.enhanceClass(); + if (match instanceof IndirectMatch) { + judge = judge.or(((IndirectMatch) match).buildJunction()); + } + } + return new ProtectiveShieldMatcher(judge); + } + + public List getBootstrapClassMatchDefine() { + return bootstrapClassMatchDefine; + } + + public static void pluginInitCompleted() { + IS_PLUGIN_INIT_COMPLETED = true; + } + + public static boolean isPluginInitCompleted() { + return IS_PLUGIN_INIT_COMPLETED; + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/PluginResourcesResolver.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/PluginResourcesResolver.java new file mode 100644 index 00000000..da828860 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/PluginResourcesResolver.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin; + +import cn.hippo4j.agent.core.logging.api.ILog; +import cn.hippo4j.agent.core.logging.api.LogManager; +import cn.hippo4j.agent.core.plugin.loader.AgentClassLoader; + +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; + +/** + * Use the current classloader to read all plugin define file. The file must be named 'skywalking-plugin.def' + */ +public class PluginResourcesResolver { + + private static final ILog LOGGER = LogManager.getLogger(PluginResourcesResolver.class); + + public List getResources() { + List cfgUrlPaths = new ArrayList(); + Enumeration urls; + try { + urls = AgentClassLoader.getDefault().getResources("skywalking-plugin.def"); + + while (urls.hasMoreElements()) { + URL pluginUrl = urls.nextElement(); + cfgUrlPaths.add(pluginUrl); + LOGGER.info("find skywalking plugin define in {}", pluginUrl); + } + + return cfgUrlPaths; + } catch (IOException e) { + LOGGER.error("read resources failure.", e); + } + return null; + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/PluginSelector.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/PluginSelector.java new file mode 100644 index 00000000..8f336b36 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/PluginSelector.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import cn.hippo4j.agent.core.conf.Config; + +import static cn.hippo4j.agent.core.conf.Config.Plugin.EXCLUDE_PLUGINS; + +/** + * Select some plugins in activated plugins + */ +public class PluginSelector { + + /** + * Exclude activated plugins + * + * @param pluginDefines the pluginDefines is loaded from activations directory or plugins directory + * @return real activate plugins + * @see Config.Plugin#EXCLUDE_PLUGINS + */ + public List select(List pluginDefines) { + if (!EXCLUDE_PLUGINS.isEmpty()) { + List excludes = Arrays.asList(EXCLUDE_PLUGINS.toLowerCase().split(",")); + return pluginDefines.stream() + .filter(item -> !excludes.contains(item.getName().toLowerCase())) + .collect(Collectors.toList()); + } + return pluginDefines; + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/WitnessFinder.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/WitnessFinder.java new file mode 100644 index 00000000..a7d30be2 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/WitnessFinder.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin; + +import net.bytebuddy.pool.TypePool; + +import java.util.HashMap; +import java.util.Map; + +/** + * The WitnessFinder represents a pool of {@link TypePool}s, each {@link TypePool} matches a {@link + * ClassLoader}, which helps to find the class declaration existed or not. + */ +public enum WitnessFinder { + + INSTANCE; + + private final Map poolMap = new HashMap(); + + /** + * @param classLoader for finding the witnessClass + * @return true, if the given witnessClass exists, through the given classLoader. + */ + public boolean exist(String witnessClass, ClassLoader classLoader) { + return getResolution(witnessClass, classLoader) + .isResolved(); + } + + /** + * get TypePool.Resolution of the witness class + * @param witnessClass class name + * @param classLoader classLoader for finding the witnessClass + * @return TypePool.Resolution + */ + private TypePool.Resolution getResolution(String witnessClass, ClassLoader classLoader) { + ClassLoader mappingKey = classLoader == null ? NullClassLoader.INSTANCE : classLoader; + if (!poolMap.containsKey(mappingKey)) { + synchronized (poolMap) { + if (!poolMap.containsKey(mappingKey)) { + TypePool classTypePool = classLoader == null ? TypePool.Default.ofBootLoader() : TypePool.Default.of(classLoader); + poolMap.put(mappingKey, classTypePool); + } + } + } + TypePool typePool = poolMap.get(mappingKey); + return typePool.describe(witnessClass); + } + + /** + * @param classLoader for finding the witness method + * @return true, if the given witness method exists, through the given classLoader. + */ + public boolean exist(WitnessMethod witnessMethod, ClassLoader classLoader) { + TypePool.Resolution resolution = getResolution(witnessMethod.getDeclaringClassName(), classLoader); + if (!resolution.isResolved()) { + return false; + } + return !resolution.resolve() + .getDeclaredMethods() + .filter(witnessMethod.getElementMatcher()) + .isEmpty(); + } + +} + +final class NullClassLoader extends ClassLoader { + + static NullClassLoader INSTANCE = new NullClassLoader(); +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/WitnessMethod.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/WitnessMethod.java new file mode 100644 index 00000000..46c62d2b --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/WitnessMethod.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; + +/** + * Witness Method for plugin activation + */ +@ToString +@RequiredArgsConstructor +public class WitnessMethod { + + /** + * the class or interface name where the witness method is declared. + */ + @Getter + private final String declaringClassName; + /** + * matcher to match the witness method + */ + @Getter + private final ElementMatcher elementMatcher; + +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bootstrap/BootstrapInstrumentBoost.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bootstrap/BootstrapInstrumentBoost.java new file mode 100644 index 00000000..ee4f2a84 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bootstrap/BootstrapInstrumentBoost.java @@ -0,0 +1,297 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.bootstrap; + +import cn.hippo4j.agent.core.logging.api.ILog; +import cn.hippo4j.agent.core.logging.api.LogManager; +import cn.hippo4j.agent.core.plugin.*; +import cn.hippo4j.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import cn.hippo4j.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import cn.hippo4j.agent.core.plugin.interceptor.StaticMethodsInterceptPoint; +import cn.hippo4j.agent.core.plugin.interceptor.v2.InstanceMethodsInterceptV2Point; +import cn.hippo4j.agent.core.plugin.interceptor.v2.StaticMethodsInterceptV2Point; +import cn.hippo4j.agent.core.plugin.jdk9module.JDK9ModuleExporter; +import cn.hippo4j.agent.core.plugin.loader.AgentClassLoader; +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.agent.builder.AgentBuilder; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.dynamic.ClassFileLocator; +import net.bytebuddy.dynamic.DynamicType; +import net.bytebuddy.dynamic.loading.ClassInjector; +import net.bytebuddy.pool.TypePool; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.instrument.Instrumentation; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static net.bytebuddy.matcher.ElementMatchers.named; + +/** + * If there is Bootstrap instrumentation plugin declared in plugin list, BootstrapInstrumentBoost inject the necessary + * classes into bootstrap class loader, including generated dynamic delegate classes. + */ +public class BootstrapInstrumentBoost { + + private static final ILog LOGGER = LogManager.getLogger(BootstrapInstrumentBoost.class); + + private static final String[] HIGH_PRIORITY_CLASSES = { + "org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.BootstrapInterRuntimeAssist", + "org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor", + "org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor", + "org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.StaticMethodsAroundInterceptor", + "org.apache.skywalking.apm.agent.core.plugin.bootstrap.IBootstrapLog", + "org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance", + "org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.OverrideCallable", + "org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult", + + // interceptor v2 + "org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.InstanceMethodsAroundInterceptorV2", + "org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.StaticMethodsAroundInterceptorV2", + "org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.MethodInvocationContext", + }; + + private static String INSTANCE_METHOD_DELEGATE_TEMPLATE = "org.apache.skywalking.apm.agent.core.plugin.bootstrap.template.InstanceMethodInterTemplate"; + private static String INSTANCE_METHOD_WITH_OVERRIDE_ARGS_DELEGATE_TEMPLATE = "org.apache.skywalking.apm.agent.core.plugin.bootstrap.template.InstanceMethodInterWithOverrideArgsTemplate"; + private static String CONSTRUCTOR_DELEGATE_TEMPLATE = "org.apache.skywalking.apm.agent.core.plugin.bootstrap.template.ConstructorInterTemplate"; + private static String STATIC_METHOD_DELEGATE_TEMPLATE = "org.apache.skywalking.apm.agent.core.plugin.bootstrap.template.StaticMethodInterTemplate"; + private static String STATIC_METHOD_WITH_OVERRIDE_ARGS_DELEGATE_TEMPLATE = "org.apache.skywalking.apm.agent.core.plugin.bootstrap.template.StaticMethodInterWithOverrideArgsTemplate"; + + private static String INSTANCE_METHOD_V2_DELEGATE_TEMPLATE = "org.apache.skywalking.apm.agent.core.plugin.bootstrap.template.v2.InstanceMethodInterV2Template"; + private static String INSTANCE_METHOD_V2_WITH_OVERRIDE_ARGS_DELEGATE_TEMPLATE = "org.apache.skywalking.apm.agent.core.plugin.bootstrap.template.v2.InstanceMethodInterV2WithOverrideArgsTemplate"; + private static String STATIC_METHOD_V2_DELEGATE_TEMPLATE = "org.apache.skywalking.apm.agent.core.plugin.bootstrap.template.v2.StaticMethodInterV2Template"; + private static String STATIC_METHOD_V2_WITH_OVERRIDE_ARGS_DELEGATE_TEMPLATE = "org.apache.skywalking.apm.agent.core.plugin.bootstrap.template.v2.StaticMethodInterV2WithOverrideArgsTemplate"; + + public static AgentBuilder inject(PluginFinder pluginFinder, Instrumentation instrumentation, + AgentBuilder agentBuilder, JDK9ModuleExporter.EdgeClasses edgeClasses) throws PluginException { + Map classesTypeMap = new LinkedHashMap<>(); + + if (!prepareJREInstrumentation(pluginFinder, classesTypeMap)) { + return agentBuilder; + } + + if (!prepareJREInstrumentationV2(pluginFinder, classesTypeMap)) { + return agentBuilder; + } + + for (String highPriorityClass : HIGH_PRIORITY_CLASSES) { + loadHighPriorityClass(classesTypeMap, highPriorityClass); + } + for (String highPriorityClass : ByteBuddyCoreClasses.CLASSES) { + loadHighPriorityClass(classesTypeMap, highPriorityClass); + } + + /** + * Prepare to open edge of necessary classes. + */ + for (String generatedClass : classesTypeMap.keySet()) { + edgeClasses.add(generatedClass); + } + + /** + * Inject the classes into bootstrap class loader by using Unsafe Strategy. + * ByteBuddy adapts the sun.misc.Unsafe and jdk.internal.misc.Unsafe automatically. + */ + ClassInjector.UsingUnsafe.Factory factory = ClassInjector.UsingUnsafe.Factory.resolve(instrumentation); + factory.make(null, null).injectRaw(classesTypeMap); + agentBuilder = agentBuilder.with(new AgentBuilder.InjectionStrategy.UsingUnsafe.OfFactory(factory)); + + return agentBuilder; + } + + /** + * Get the delegate class name. + * + * @param methodsInterceptor of original interceptor in the plugin + * @return generated delegate class name + */ + public static String internalDelegate(String methodsInterceptor) { + return methodsInterceptor + "_internal"; + } + + /** + * Load the delegate class from current class loader, mostly should be AppClassLoader. + * + * @param methodsInterceptor of original interceptor in the plugin + * @return generated delegate class + */ + public static Class forInternalDelegateClass(String methodsInterceptor) { + try { + return Class.forName(internalDelegate(methodsInterceptor)); + } catch (ClassNotFoundException e) { + throw new PluginException(e.getMessage(), e); + } + } + + /** + * Generate dynamic delegate for ByteBuddy + * + * @param pluginFinder gets the whole plugin list. + * @param classesTypeMap hosts the class binary. + * @return true if have JRE instrumentation requirement. + * @throws PluginException when generate failure. + */ + private static boolean prepareJREInstrumentation(PluginFinder pluginFinder, + Map classesTypeMap) throws PluginException { + TypePool typePool = TypePool.Default.of(BootstrapInstrumentBoost.class.getClassLoader()); + List bootstrapClassMatchDefines = pluginFinder.getBootstrapClassMatchDefine(); + for (AbstractClassEnhancePluginDefine define : bootstrapClassMatchDefines) { + if (Objects.nonNull(define.getInstanceMethodsInterceptPoints())) { + for (InstanceMethodsInterceptPoint point : define.getInstanceMethodsInterceptPoints()) { + if (point.isOverrideArgs()) { + generateDelegator( + classesTypeMap, typePool, INSTANCE_METHOD_WITH_OVERRIDE_ARGS_DELEGATE_TEMPLATE, point + .getMethodsInterceptor()); + } else { + generateDelegator( + classesTypeMap, typePool, INSTANCE_METHOD_DELEGATE_TEMPLATE, point.getMethodsInterceptor()); + } + } + } + + if (Objects.nonNull(define.getConstructorsInterceptPoints())) { + for (ConstructorInterceptPoint point : define.getConstructorsInterceptPoints()) { + generateDelegator( + classesTypeMap, typePool, CONSTRUCTOR_DELEGATE_TEMPLATE, point.getConstructorInterceptor()); + } + } + + if (Objects.nonNull(define.getStaticMethodsInterceptPoints())) { + for (StaticMethodsInterceptPoint point : define.getStaticMethodsInterceptPoints()) { + if (point.isOverrideArgs()) { + generateDelegator( + classesTypeMap, typePool, STATIC_METHOD_WITH_OVERRIDE_ARGS_DELEGATE_TEMPLATE, point + .getMethodsInterceptor()); + } else { + generateDelegator( + classesTypeMap, typePool, STATIC_METHOD_DELEGATE_TEMPLATE, point.getMethodsInterceptor()); + } + } + } + } + return bootstrapClassMatchDefines.size() > 0; + } + + private static boolean prepareJREInstrumentationV2(PluginFinder pluginFinder, + Map classesTypeMap) throws PluginException { + TypePool typePool = TypePool.Default.of(BootstrapInstrumentBoost.class.getClassLoader()); + List bootstrapClassMatchDefines = pluginFinder.getBootstrapClassMatchDefine(); + for (AbstractClassEnhancePluginDefine define : bootstrapClassMatchDefines) { + if (Objects.nonNull(define.getInstanceMethodsInterceptV2Points())) { + for (InstanceMethodsInterceptV2Point point : define.getInstanceMethodsInterceptV2Points()) { + if (point.isOverrideArgs()) { + generateDelegator(classesTypeMap, typePool, + INSTANCE_METHOD_V2_WITH_OVERRIDE_ARGS_DELEGATE_TEMPLATE, + point.getMethodsInterceptorV2()); + } else { + generateDelegator( + classesTypeMap, typePool, INSTANCE_METHOD_V2_DELEGATE_TEMPLATE, + point.getMethodsInterceptorV2()); + } + } + } + + if (Objects.nonNull(define.getStaticMethodsInterceptV2Points())) { + for (StaticMethodsInterceptV2Point point : define.getStaticMethodsInterceptV2Points()) { + if (point.isOverrideArgs()) { + generateDelegator(classesTypeMap, typePool, + STATIC_METHOD_V2_WITH_OVERRIDE_ARGS_DELEGATE_TEMPLATE, + point.getMethodsInterceptorV2()); + } else { + generateDelegator( + classesTypeMap, typePool, STATIC_METHOD_V2_DELEGATE_TEMPLATE, + point.getMethodsInterceptorV2()); + } + } + } + } + return bootstrapClassMatchDefines.size() > 0; + } + + /** + * Generate the delegator class based on given template class. This is preparation stage level code generation. + *

+ * One key step to avoid class confliction between AppClassLoader and BootstrapClassLoader + * + * @param classesTypeMap hosts injected binary of generated class + * @param typePool to generate new class + * @param templateClassName represents the class as template in this generation process. The templates are + * pre-defined in SkyWalking agent core. + */ + private static void generateDelegator(Map classesTypeMap, TypePool typePool, + String templateClassName, String methodsInterceptor) { + String internalInterceptorName = internalDelegate(methodsInterceptor); + try { + TypeDescription templateTypeDescription = typePool.describe(templateClassName).resolve(); + + DynamicType.Unloaded interceptorType = new ByteBuddy().redefine(templateTypeDescription, ClassFileLocator.ForClassLoader + .of(BootstrapInstrumentBoost.class.getClassLoader())) + .name(internalInterceptorName) + .field(named("TARGET_INTERCEPTOR")) + .value(methodsInterceptor) + .make(); + + classesTypeMap.put(internalInterceptorName, interceptorType.getBytes()); + + InstrumentDebuggingClass.INSTANCE.log(interceptorType); + } catch (Exception e) { + throw new PluginException("Generate Dynamic plugin failure", e); + } + } + + /** + * The class loaded by this method means it only should be loaded once in Bootstrap classloader, when bootstrap + * instrumentation active by any plugin + * + * @param loadedTypeMap hosts all injected class + * @param className to load + */ + private static void loadHighPriorityClass(Map loadedTypeMap, + String className) throws PluginException { + byte[] enhancedInstanceClassFile; + try { + String classResourceName = className.replaceAll("\\.", "/") + ".class"; + InputStream resourceAsStream = AgentClassLoader.getDefault().getResourceAsStream(classResourceName); + + if (resourceAsStream == null) { + throw new PluginException("High priority class " + className + " not found."); + } + + ByteArrayOutputStream os = new ByteArrayOutputStream(); + + byte[] buffer = new byte[1024]; + int len; + + // read bytes from the input stream and store them in buffer + while ((len = resourceAsStream.read(buffer)) != -1) { + // write bytes from the buffer into output stream + os.write(buffer, 0, len); + } + + enhancedInstanceClassFile = os.toByteArray(); + } catch (IOException e) { + throw new PluginException(e.getMessage(), e); + } + + loadedTypeMap.put(className, enhancedInstanceClassFile); + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bootstrap/BootstrapPluginLogBridge.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bootstrap/BootstrapPluginLogBridge.java new file mode 100644 index 00000000..868b1c2c --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bootstrap/BootstrapPluginLogBridge.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.bootstrap; + +import cn.hippo4j.agent.core.logging.api.ILog; +import cn.hippo4j.agent.core.logging.api.LogManager; + +/** + * The log bridge makes the ILog accessible inside bootstrap classloader, especially for internal interceptor. + */ +public class BootstrapPluginLogBridge implements IBootstrapLog { + + public static IBootstrapLog getLogger(String clazz) { + return new BootstrapPluginLogBridge(clazz); + } + + private final ILog logger; + + private BootstrapPluginLogBridge(String clazz) { + logger = LogManager.getLogger(clazz); + } + + @Override + public void info(String format) { + logger.info(format); + } + + @Override + public void info(String format, Object... arguments) { + logger.info(format, arguments); + } + + @Override + public void warn(String format, Object... arguments) { + logger.warn(format, arguments); + } + + @Override + public void warn(Throwable e, String format, Object... arguments) { + logger.warn(e, format, arguments); + } + + @Override + public void error(String format, Throwable e) { + logger.error(format, e); + } + + @Override + public void error(Throwable e, String format, Object... arguments) { + logger.error(e, format, arguments); + } + + @Override + public boolean isDebugEnable() { + return logger.isDebugEnable(); + } + + @Override + public boolean isInfoEnable() { + return logger.isInfoEnable(); + } + + @Override + public boolean isWarnEnable() { + return logger.isWarnEnable(); + } + + @Override + public boolean isErrorEnable() { + return logger.isErrorEnable(); + } + + @Override + public void debug(String format) { + logger.debug(format); + } + + @Override + public void debug(String format, Object... arguments) { + logger.debug(format, arguments); + } + + @Override + public void error(String format) { + logger.error(format); + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bootstrap/IBootstrapLog.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bootstrap/IBootstrapLog.java new file mode 100644 index 00000000..52645b44 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bootstrap/IBootstrapLog.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.bootstrap; + +/** + * The log interface used in bootstrap internal interceptors. + *

+ * Never used in any plugin or tracing core. + */ +public interface IBootstrapLog { + + void info(String format); + + void info(String format, Object... arguments); + + void warn(String format, Object... arguments); + + void warn(Throwable e, String format, Object... arguments); + + void error(String format, Throwable e); + + void error(Throwable e, String format, Object... arguments); + + boolean isDebugEnable(); + + boolean isInfoEnable(); + + boolean isWarnEnable(); + + boolean isErrorEnable(); + + void debug(String format); + + void debug(String format, Object... arguments); + + void error(String format); +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bootstrap/template/ConstructorInterTemplate.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bootstrap/template/ConstructorInterTemplate.java new file mode 100644 index 00000000..3bca42a0 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bootstrap/template/ConstructorInterTemplate.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.bootstrap.template; + +import cn.hippo4j.agent.core.plugin.bootstrap.IBootstrapLog; +import cn.hippo4j.agent.core.plugin.interceptor.enhance.BootstrapInterRuntimeAssist; +import cn.hippo4j.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import cn.hippo4j.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor; +import net.bytebuddy.implementation.bind.annotation.AllArguments; +import net.bytebuddy.implementation.bind.annotation.RuntimeType; +import net.bytebuddy.implementation.bind.annotation.This; + +public class ConstructorInterTemplate { + + /** + * This field is never set in the template, but has value in the runtime. + */ + private static String TARGET_INTERCEPTOR; + + private static InstanceConstructorInterceptor INTERCEPTOR; + private static IBootstrapLog LOGGER; + + /** + * Intercept the target constructor. + * + * @param obj target class instance. + * @param allArguments all constructor arguments + */ + @RuntimeType + public static void intercept(@This Object obj, @AllArguments Object[] allArguments) { + try { + prepare(); + + EnhancedInstance targetObject = (EnhancedInstance) obj; + + if (INTERCEPTOR == null) { + return; + } + INTERCEPTOR.onConstruct(targetObject, allArguments); + } catch (Throwable t) { + LOGGER.error("ConstructorInter failure.", t); + } + } + + /** + * Prepare the context. Link to the agent core in AppClassLoader. + */ + private static void prepare() { + if (INTERCEPTOR == null) { + ClassLoader loader = BootstrapInterRuntimeAssist.getAgentClassLoader(); + + if (loader != null) { + IBootstrapLog logger = BootstrapInterRuntimeAssist.getLogger(loader, TARGET_INTERCEPTOR); + if (logger != null) { + LOGGER = logger; + + INTERCEPTOR = BootstrapInterRuntimeAssist.createInterceptor(loader, TARGET_INTERCEPTOR, LOGGER); + } + } else { + LOGGER.error("Runtime ClassLoader not found when create {}." + TARGET_INTERCEPTOR); + } + } + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bootstrap/template/InstanceMethodInterTemplate.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bootstrap/template/InstanceMethodInterTemplate.java new file mode 100644 index 00000000..a98212a8 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bootstrap/template/InstanceMethodInterTemplate.java @@ -0,0 +1,121 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.bootstrap.template; + +import cn.hippo4j.agent.core.plugin.bootstrap.IBootstrapLog; +import cn.hippo4j.agent.core.plugin.interceptor.enhance.BootstrapInterRuntimeAssist; +import cn.hippo4j.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import cn.hippo4j.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import cn.hippo4j.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import net.bytebuddy.implementation.bind.annotation.*; + +import java.lang.reflect.Method; +import java.util.concurrent.Callable; + +public class InstanceMethodInterTemplate { + + /** + * This field is never set in the template, but has value in the runtime. + */ + private static String TARGET_INTERCEPTOR; + + private static InstanceMethodsAroundInterceptor INTERCEPTOR; + private static IBootstrapLog LOGGER; + + /** + * Intercept the target instance method. + * + * @param obj target class instance. + * @param allArguments all method arguments + * @param method method description. + * @param zuper the origin call ref. + * @return the return value of target instance method. + * @throws Exception only throw exception because of zuper.call() or unexpected exception in sky-walking ( This is a + * bug, if anything triggers this condition ). + */ + @RuntimeType + public static Object intercept(@This Object obj, @AllArguments Object[] allArguments, @SuperCall Callable zuper, + @Origin Method method) throws Throwable { + EnhancedInstance targetObject = (EnhancedInstance) obj; + + prepare(); + + MethodInterceptResult result = new MethodInterceptResult(); + try { + if (INTERCEPTOR != null) { + INTERCEPTOR.beforeMethod(targetObject, method, allArguments, method.getParameterTypes(), result); + } + } catch (Throwable t) { + if (LOGGER != null) { + LOGGER.error(t, "class[{}] before method[{}] intercept failure", obj.getClass(), method.getName()); + } + } + + Object ret = null; + try { + if (!result.isContinue()) { + ret = result._ret(); + } else { + ret = zuper.call(); + } + } catch (Throwable t) { + try { + if (INTERCEPTOR != null) { + INTERCEPTOR.handleMethodException(targetObject, method, allArguments, method.getParameterTypes(), t); + } + } catch (Throwable t2) { + if (LOGGER != null) { + LOGGER.error(t2, "class[{}] handle method[{}] exception failure", obj.getClass(), method.getName()); + } + } + throw t; + } finally { + try { + if (INTERCEPTOR != null) { + ret = INTERCEPTOR.afterMethod(targetObject, method, allArguments, method.getParameterTypes(), ret); + } + } catch (Throwable t) { + if (LOGGER != null) { + LOGGER.error(t, "class[{}] after method[{}] intercept failure", obj.getClass(), method.getName()); + } + } + } + + return ret; + } + + /** + * Prepare the context. Link to the agent core in AppClassLoader. + */ + private static void prepare() { + if (INTERCEPTOR == null) { + ClassLoader loader = BootstrapInterRuntimeAssist.getAgentClassLoader(); + + if (loader != null) { + IBootstrapLog logger = BootstrapInterRuntimeAssist.getLogger(loader, TARGET_INTERCEPTOR); + if (logger != null) { + LOGGER = logger; + + INTERCEPTOR = BootstrapInterRuntimeAssist.createInterceptor(loader, TARGET_INTERCEPTOR, LOGGER); + } + } else { + LOGGER.error("Runtime ClassLoader not found when create {}." + TARGET_INTERCEPTOR); + } + } + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bootstrap/template/InstanceMethodInterWithOverrideArgsTemplate.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bootstrap/template/InstanceMethodInterWithOverrideArgsTemplate.java new file mode 100644 index 00000000..319ffb8d --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bootstrap/template/InstanceMethodInterWithOverrideArgsTemplate.java @@ -0,0 +1,117 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.bootstrap.template; + +import cn.hippo4j.agent.core.plugin.bootstrap.IBootstrapLog; +import cn.hippo4j.agent.core.plugin.interceptor.enhance.*; +import net.bytebuddy.implementation.bind.annotation.*; + +import java.lang.reflect.Method; + +public class InstanceMethodInterWithOverrideArgsTemplate { + + /** + * This field is never set in the template, but has value in the runtime. + */ + private static String TARGET_INTERCEPTOR; + + private static InstanceMethodsAroundInterceptor INTERCEPTOR; + private static IBootstrapLog LOGGER; + + /** + * Intercept the target instance method. + * + * @param obj target class instance. + * @param allArguments all method arguments + * @param method method description. + * @param zuper the origin call ref. + * @return the return value of target instance method. + * @throws Exception only throw exception because of zuper.call() or unexpected exception in sky-walking ( This is a + * bug, if anything triggers this condition ). + */ + @RuntimeType + public static Object intercept(@This Object obj, @AllArguments Object[] allArguments, @Morph OverrideCallable zuper, + @Origin Method method) throws Throwable { + EnhancedInstance targetObject = (EnhancedInstance) obj; + + prepare(); + + MethodInterceptResult result = new MethodInterceptResult(); + try { + if (INTERCEPTOR != null) { + INTERCEPTOR.beforeMethod(targetObject, method, allArguments, method.getParameterTypes(), result); + } + } catch (Throwable t) { + if (LOGGER != null) { + LOGGER.error(t, "class[{}] before method[{}] intercept failure", obj.getClass(), method.getName()); + } + } + + Object ret = null; + try { + if (!result.isContinue()) { + ret = result._ret(); + } else { + ret = zuper.call(allArguments); + } + } catch (Throwable t) { + try { + if (INTERCEPTOR != null) { + INTERCEPTOR.handleMethodException(targetObject, method, allArguments, method.getParameterTypes(), t); + } + } catch (Throwable t2) { + if (LOGGER != null) { + LOGGER.error(t2, "class[{}] handle method[{}] exception failure", obj.getClass(), method.getName()); + } + } + throw t; + } finally { + try { + if (INTERCEPTOR != null) { + ret = INTERCEPTOR.afterMethod(targetObject, method, allArguments, method.getParameterTypes(), ret); + } + } catch (Throwable t) { + if (LOGGER != null) { + LOGGER.error(t, "class[{}] after method[{}] intercept failure", obj.getClass(), method.getName()); + } + } + } + + return ret; + } + + /** + * Prepare the context. Link to the agent core in AppClassLoader. + */ + private static void prepare() { + if (INTERCEPTOR == null) { + ClassLoader loader = BootstrapInterRuntimeAssist.getAgentClassLoader(); + + if (loader != null) { + IBootstrapLog logger = BootstrapInterRuntimeAssist.getLogger(loader, TARGET_INTERCEPTOR); + if (logger != null) { + LOGGER = logger; + + INTERCEPTOR = BootstrapInterRuntimeAssist.createInterceptor(loader, TARGET_INTERCEPTOR, LOGGER); + } + } else { + LOGGER.error("Runtime ClassLoader not found when create {}." + TARGET_INTERCEPTOR); + } + } + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bootstrap/template/StaticMethodInterTemplate.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bootstrap/template/StaticMethodInterTemplate.java new file mode 100644 index 00000000..df1abaf2 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bootstrap/template/StaticMethodInterTemplate.java @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.bootstrap.template; + +import cn.hippo4j.agent.core.plugin.bootstrap.IBootstrapLog; +import cn.hippo4j.agent.core.plugin.interceptor.enhance.BootstrapInterRuntimeAssist; +import cn.hippo4j.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import cn.hippo4j.agent.core.plugin.interceptor.enhance.StaticMethodsAroundInterceptor; +import net.bytebuddy.implementation.bind.annotation.AllArguments; +import net.bytebuddy.implementation.bind.annotation.Origin; +import net.bytebuddy.implementation.bind.annotation.RuntimeType; +import net.bytebuddy.implementation.bind.annotation.SuperCall; + +import java.lang.reflect.Method; +import java.util.concurrent.Callable; + +public class StaticMethodInterTemplate { + + /** + * This field is never set in the template, but has value in the runtime. + */ + private static String TARGET_INTERCEPTOR; + + private static StaticMethodsAroundInterceptor INTERCEPTOR; + private static IBootstrapLog LOGGER; + + /** + * Intercept the target static method. + * + * @param clazz target class + * @param allArguments all method arguments + * @param method method description. + * @param zuper the origin call ref. + * @return the return value of target static method. + * @throws Exception only throw exception because of zuper.call() or unexpected exception in sky-walking ( This is a + * bug, if anything triggers this condition ). + */ + @RuntimeType + public static Object intercept(@Origin Class clazz, @AllArguments Object[] allArguments, @Origin Method method, + @SuperCall Callable zuper) throws Throwable { + prepare(); + + MethodInterceptResult result = new MethodInterceptResult(); + try { + if (INTERCEPTOR != null) { + INTERCEPTOR.beforeMethod(clazz, method, allArguments, method.getParameterTypes(), result); + } + } catch (Throwable t) { + LOGGER.error(t, "class[{}] before static method[{}] intercept failure", clazz, method.getName()); + } + + Object ret = null; + try { + if (!result.isContinue()) { + ret = result._ret(); + } else { + ret = zuper.call(); + } + } catch (Throwable t) { + try { + if (INTERCEPTOR != null) { + INTERCEPTOR.handleMethodException(clazz, method, allArguments, method.getParameterTypes(), t); + } + } catch (Throwable t2) { + LOGGER.error(t2, "class[{}] handle static method[{}] exception failure", clazz, method.getName(), t2.getMessage()); + } + throw t; + } finally { + try { + if (INTERCEPTOR != null) { + ret = INTERCEPTOR.afterMethod(clazz, method, allArguments, method.getParameterTypes(), ret); + } + } catch (Throwable t) { + LOGGER.error(t, "class[{}] after static method[{}] intercept failure:{}", clazz, method.getName(), t.getMessage()); + } + } + return ret; + } + + /** + * Prepare the context. Link to the agent core in AppClassLoader. + */ + private static void prepare() { + if (INTERCEPTOR == null) { + ClassLoader loader = BootstrapInterRuntimeAssist.getAgentClassLoader(); + + if (loader != null) { + IBootstrapLog logger = BootstrapInterRuntimeAssist.getLogger(loader, TARGET_INTERCEPTOR); + if (logger != null) { + LOGGER = logger; + + INTERCEPTOR = BootstrapInterRuntimeAssist.createInterceptor(loader, TARGET_INTERCEPTOR, LOGGER); + } + } else { + LOGGER.error("Runtime ClassLoader not found when create {}." + TARGET_INTERCEPTOR); + } + } + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bootstrap/template/StaticMethodInterWithOverrideArgsTemplate.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bootstrap/template/StaticMethodInterWithOverrideArgsTemplate.java new file mode 100644 index 00000000..a3b0b92f --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bootstrap/template/StaticMethodInterWithOverrideArgsTemplate.java @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.bootstrap.template; + +import cn.hippo4j.agent.core.plugin.bootstrap.IBootstrapLog; +import cn.hippo4j.agent.core.plugin.interceptor.enhance.BootstrapInterRuntimeAssist; +import cn.hippo4j.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import cn.hippo4j.agent.core.plugin.interceptor.enhance.OverrideCallable; +import cn.hippo4j.agent.core.plugin.interceptor.enhance.StaticMethodsAroundInterceptor; +import net.bytebuddy.implementation.bind.annotation.AllArguments; +import net.bytebuddy.implementation.bind.annotation.Morph; +import net.bytebuddy.implementation.bind.annotation.Origin; +import net.bytebuddy.implementation.bind.annotation.RuntimeType; + +import java.lang.reflect.Method; + +public class StaticMethodInterWithOverrideArgsTemplate { + + /** + * This field is never set in the template, but has value in the runtime. + */ + private static String TARGET_INTERCEPTOR; + + private static StaticMethodsAroundInterceptor INTERCEPTOR; + private static IBootstrapLog LOGGER; + + /** + * Intercept the target static method. + * + * @param clazz target class + * @param allArguments all method arguments + * @param method method description. + * @param zuper the origin call ref. + * @return the return value of target static method. + * @throws Exception only throw exception because of zuper.call() or unexpected exception in sky-walking ( This is a + * bug, if anything triggers this condition ). + */ + @RuntimeType + public static Object intercept(@Origin Class clazz, @AllArguments Object[] allArguments, @Origin Method method, + @Morph OverrideCallable zuper) throws Throwable { + prepare(); + + MethodInterceptResult result = new MethodInterceptResult(); + try { + if (INTERCEPTOR != null) { + INTERCEPTOR.beforeMethod(clazz, method, allArguments, method.getParameterTypes(), result); + } + } catch (Throwable t) { + LOGGER.error(t, "class[{}] before static method[{}] intercept failure", clazz, method.getName()); + } + + Object ret = null; + try { + if (!result.isContinue()) { + ret = result._ret(); + } else { + ret = zuper.call(allArguments); + } + } catch (Throwable t) { + try { + if (INTERCEPTOR != null) { + INTERCEPTOR.handleMethodException(clazz, method, allArguments, method.getParameterTypes(), t); + } + } catch (Throwable t2) { + LOGGER.error(t2, "class[{}] handle static method[{}] exception failure", clazz, method.getName(), t2.getMessage()); + } + throw t; + } finally { + try { + if (INTERCEPTOR != null) { + ret = INTERCEPTOR.afterMethod(clazz, method, allArguments, method.getParameterTypes(), ret); + } + } catch (Throwable t) { + LOGGER.error(t, "class[{}] after static method[{}] intercept failure:{}", clazz, method.getName(), t.getMessage()); + } + } + return ret; + } + + /** + * Prepare the context. Link to the agent core in AppClassLoader. + */ + private static void prepare() { + if (INTERCEPTOR == null) { + ClassLoader loader = BootstrapInterRuntimeAssist.getAgentClassLoader(); + + if (loader != null) { + IBootstrapLog logger = BootstrapInterRuntimeAssist.getLogger(loader, TARGET_INTERCEPTOR); + if (logger != null) { + LOGGER = logger; + + INTERCEPTOR = BootstrapInterRuntimeAssist.createInterceptor(loader, TARGET_INTERCEPTOR, LOGGER); + } + } else { + LOGGER.error("Runtime ClassLoader not found when create {}." + TARGET_INTERCEPTOR); + } + } + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bootstrap/template/v2/InstanceMethodInterV2Template.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bootstrap/template/v2/InstanceMethodInterV2Template.java new file mode 100644 index 00000000..31b3a4e0 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bootstrap/template/v2/InstanceMethodInterV2Template.java @@ -0,0 +1,123 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.bootstrap.template.v2; + +import cn.hippo4j.agent.core.plugin.bootstrap.IBootstrapLog; +import cn.hippo4j.agent.core.plugin.interceptor.enhance.BootstrapInterRuntimeAssist; +import cn.hippo4j.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import cn.hippo4j.agent.core.plugin.interceptor.enhance.v2.InstanceMethodsAroundInterceptorV2; +import cn.hippo4j.agent.core.plugin.interceptor.enhance.v2.MethodInvocationContext; +import net.bytebuddy.implementation.bind.annotation.*; +import java.lang.reflect.Method; +import java.util.concurrent.Callable; + +/** + * This class wouldn't be loaded in real env. This is a class template for dynamic class generation. + */ +public class InstanceMethodInterV2Template { + + /** + * This field is never set in the template, but has value in the runtime. + */ + private static String TARGET_INTERCEPTOR; + + private static InstanceMethodsAroundInterceptorV2 INTERCEPTOR; + private static IBootstrapLog LOGGER; + + /** + * Intercept the target instance method. + * + * @param obj target class instance. + * @param allArguments all method arguments + * @param method method description. + * @param zuper the origin call ref. + * @return the return value of target instance method. + * @throws Exception only throw exception because of zuper.call() or unexpected exception in sky-walking ( This is a + * bug, if anything triggers this condition ). + */ + @RuntimeType + public static Object intercept(@This Object obj, @AllArguments Object[] allArguments, @SuperCall Callable zuper, + @Origin Method method) throws Throwable { + EnhancedInstance targetObject = (EnhancedInstance) obj; + + prepare(); + + MethodInvocationContext context = new MethodInvocationContext(); + try { + if (INTERCEPTOR != null) { + INTERCEPTOR.beforeMethod(targetObject, method, allArguments, method.getParameterTypes(), context); + } + } catch (Throwable t) { + if (LOGGER != null) { + LOGGER.error(t, "class[{}] before method[{}] intercept failure", obj.getClass(), method.getName()); + } + } + + Object ret = null; + try { + if (!context.isContinue()) { + ret = context._ret(); + } else { + ret = zuper.call(); + } + } catch (Throwable t) { + try { + if (INTERCEPTOR != null) { + INTERCEPTOR.handleMethodException(targetObject, method, allArguments, method.getParameterTypes(), t, context); + } + } catch (Throwable t2) { + if (LOGGER != null) { + LOGGER.error(t2, "class[{}] handle method[{}] exception failure", obj.getClass(), method.getName()); + } + } + throw t; + } finally { + try { + if (INTERCEPTOR != null) { + ret = INTERCEPTOR.afterMethod(targetObject, method, allArguments, method.getParameterTypes(), ret, context); + } + } catch (Throwable t) { + if (LOGGER != null) { + LOGGER.error(t, "class[{}] after method[{}] intercept failure", obj.getClass(), method.getName()); + } + } + } + + return ret; + } + + /** + * Prepare the context. Link to the agent core in AppClassLoader. + */ + private static void prepare() { + if (INTERCEPTOR == null) { + ClassLoader loader = BootstrapInterRuntimeAssist.getAgentClassLoader(); + + if (loader != null) { + IBootstrapLog logger = BootstrapInterRuntimeAssist.getLogger(loader, TARGET_INTERCEPTOR); + if (logger != null) { + LOGGER = logger; + + INTERCEPTOR = BootstrapInterRuntimeAssist.createInterceptor(loader, TARGET_INTERCEPTOR, LOGGER); + } + } else { + LOGGER.error("Runtime ClassLoader not found when create {}." + TARGET_INTERCEPTOR); + } + } + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bootstrap/template/v2/InstanceMethodInterV2WithOverrideArgsTemplate.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bootstrap/template/v2/InstanceMethodInterV2WithOverrideArgsTemplate.java new file mode 100644 index 00000000..be29208c --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bootstrap/template/v2/InstanceMethodInterV2WithOverrideArgsTemplate.java @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.bootstrap.template.v2; + +import cn.hippo4j.agent.core.plugin.bootstrap.IBootstrapLog; +import cn.hippo4j.agent.core.plugin.interceptor.enhance.BootstrapInterRuntimeAssist; +import cn.hippo4j.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import cn.hippo4j.agent.core.plugin.interceptor.enhance.OverrideCallable; +import cn.hippo4j.agent.core.plugin.interceptor.enhance.v2.InstanceMethodsAroundInterceptorV2; +import cn.hippo4j.agent.core.plugin.interceptor.enhance.v2.MethodInvocationContext; +import net.bytebuddy.implementation.bind.annotation.*; + +import java.lang.reflect.Method; + +/** + * This class wouldn't be loaded in real env. This is a class template for dynamic class generation. + */ +public class InstanceMethodInterV2WithOverrideArgsTemplate { + + /** + * This field is never set in the template, but has value in the runtime. + */ + private static String TARGET_INTERCEPTOR; + + private static InstanceMethodsAroundInterceptorV2 INTERCEPTOR; + private static IBootstrapLog LOGGER; + + /** + * Intercept the target instance method. + * + * @param obj target class instance. + * @param allArguments all method arguments + * @param method method description. + * @param zuper the origin call ref. + * @return the return value of target instance method. + * @throws Exception only throw exception because of zuper.call() or unexpected exception in sky-walking ( This is a + * bug, if anything triggers this condition ). + */ + @RuntimeType + public static Object intercept(@This Object obj, @AllArguments Object[] allArguments, @Morph OverrideCallable zuper, + @Origin Method method) throws Throwable { + EnhancedInstance targetObject = (EnhancedInstance) obj; + + prepare(); + + MethodInvocationContext context = new MethodInvocationContext(); + try { + if (INTERCEPTOR != null) { + INTERCEPTOR.beforeMethod(targetObject, method, allArguments, method.getParameterTypes(), context); + } + } catch (Throwable t) { + if (LOGGER != null) { + LOGGER.error(t, "class[{}] before method[{}] intercept failure", obj.getClass(), method.getName()); + } + } + + Object ret = null; + try { + if (!context.isContinue()) { + ret = context._ret(); + } else { + ret = zuper.call(allArguments); + } + } catch (Throwable t) { + try { + if (INTERCEPTOR != null) { + INTERCEPTOR.handleMethodException(targetObject, method, allArguments, method.getParameterTypes(), t, context); + } + } catch (Throwable t2) { + if (LOGGER != null) { + LOGGER.error(t2, "class[{}] handle method[{}] exception failure", obj.getClass(), method.getName()); + } + } + throw t; + } finally { + try { + if (INTERCEPTOR != null) { + ret = INTERCEPTOR.afterMethod(targetObject, method, allArguments, method.getParameterTypes(), ret, context); + } + } catch (Throwable t) { + if (LOGGER != null) { + LOGGER.error(t, "class[{}] after method[{}] intercept failure", obj.getClass(), method.getName()); + } + } + } + + return ret; + } + + /** + * Prepare the context. Link to the agent core in AppClassLoader. + */ + private static void prepare() { + if (INTERCEPTOR == null) { + ClassLoader loader = BootstrapInterRuntimeAssist.getAgentClassLoader(); + + if (loader != null) { + IBootstrapLog logger = BootstrapInterRuntimeAssist.getLogger(loader, TARGET_INTERCEPTOR); + if (logger != null) { + LOGGER = logger; + + INTERCEPTOR = BootstrapInterRuntimeAssist.createInterceptor(loader, TARGET_INTERCEPTOR, LOGGER); + } + } else { + LOGGER.error("Runtime ClassLoader not found when create {}." + TARGET_INTERCEPTOR); + } + } + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bootstrap/template/v2/StaticMethodInterV2Template.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bootstrap/template/v2/StaticMethodInterV2Template.java new file mode 100644 index 00000000..7693c597 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bootstrap/template/v2/StaticMethodInterV2Template.java @@ -0,0 +1,117 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.bootstrap.template.v2; + +import cn.hippo4j.agent.core.plugin.bootstrap.IBootstrapLog; +import cn.hippo4j.agent.core.plugin.interceptor.enhance.BootstrapInterRuntimeAssist; +import cn.hippo4j.agent.core.plugin.interceptor.enhance.v2.MethodInvocationContext; +import cn.hippo4j.agent.core.plugin.interceptor.enhance.v2.StaticMethodsAroundInterceptorV2; +import net.bytebuddy.implementation.bind.annotation.AllArguments; +import net.bytebuddy.implementation.bind.annotation.Origin; +import net.bytebuddy.implementation.bind.annotation.RuntimeType; +import net.bytebuddy.implementation.bind.annotation.SuperCall; + +import java.lang.reflect.Method; +import java.util.concurrent.Callable; + +/** + * This class wouldn't be loaded in real env. This is a class template for dynamic class generation. + */ +public class StaticMethodInterV2Template { + + /** + * This field is never set in the template, but has value in the runtime. + */ + private static String TARGET_INTERCEPTOR; + + private static StaticMethodsAroundInterceptorV2 INTERCEPTOR; + private static IBootstrapLog LOGGER; + + /** + * Intercept the target static method. + * + * @param clazz target class + * @param allArguments all method arguments + * @param method method description. + * @param zuper the origin call ref. + * @return the return value of target static method. + * @throws Exception only throw exception because of zuper.call() or unexpected exception in sky-walking ( This is a + * bug, if anything triggers this condition ). + */ + @RuntimeType + public static Object intercept(@Origin Class clazz, @AllArguments Object[] allArguments, @Origin Method method, + @SuperCall Callable zuper) throws Throwable { + prepare(); + + MethodInvocationContext context = new MethodInvocationContext(); + try { + if (INTERCEPTOR != null) { + INTERCEPTOR.beforeMethod(clazz, method, allArguments, method.getParameterTypes(), context); + } + } catch (Throwable t) { + LOGGER.error(t, "class[{}] before static method[{}] intercept failure", clazz, method.getName()); + } + + Object ret = null; + try { + if (!context.isContinue()) { + ret = context._ret(); + } else { + ret = zuper.call(); + } + } catch (Throwable t) { + try { + if (INTERCEPTOR != null) { + INTERCEPTOR.handleMethodException(clazz, method, allArguments, method.getParameterTypes(), t, context); + } + } catch (Throwable t2) { + LOGGER.error(t2, "class[{}] handle static method[{}] exception failure", clazz, method.getName(), t2.getMessage()); + } + throw t; + } finally { + try { + if (INTERCEPTOR != null) { + ret = INTERCEPTOR.afterMethod(clazz, method, allArguments, method.getParameterTypes(), ret, context); + } + } catch (Throwable t) { + LOGGER.error(t, "class[{}] after static method[{}] intercept failure:{}", clazz, method.getName(), t.getMessage()); + } + } + return ret; + } + + /** + * Prepare the context. Link to the agent core in AppClassLoader. + */ + private static void prepare() { + if (INTERCEPTOR == null) { + ClassLoader loader = BootstrapInterRuntimeAssist.getAgentClassLoader(); + + if (loader != null) { + IBootstrapLog logger = BootstrapInterRuntimeAssist.getLogger(loader, TARGET_INTERCEPTOR); + if (logger != null) { + LOGGER = logger; + + INTERCEPTOR = BootstrapInterRuntimeAssist.createInterceptor(loader, TARGET_INTERCEPTOR, LOGGER); + } + } else { + LOGGER.error("Runtime ClassLoader not found when create {}." + TARGET_INTERCEPTOR); + } + } + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bootstrap/template/v2/StaticMethodInterV2WithOverrideArgsTemplate.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bootstrap/template/v2/StaticMethodInterV2WithOverrideArgsTemplate.java new file mode 100644 index 00000000..22da45bb --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bootstrap/template/v2/StaticMethodInterV2WithOverrideArgsTemplate.java @@ -0,0 +1,117 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.bootstrap.template.v2; + +import cn.hippo4j.agent.core.plugin.bootstrap.IBootstrapLog; +import cn.hippo4j.agent.core.plugin.interceptor.enhance.BootstrapInterRuntimeAssist; +import cn.hippo4j.agent.core.plugin.interceptor.enhance.OverrideCallable; +import cn.hippo4j.agent.core.plugin.interceptor.enhance.v2.MethodInvocationContext; +import cn.hippo4j.agent.core.plugin.interceptor.enhance.v2.StaticMethodsAroundInterceptorV2; +import net.bytebuddy.implementation.bind.annotation.AllArguments; +import net.bytebuddy.implementation.bind.annotation.Morph; +import net.bytebuddy.implementation.bind.annotation.Origin; +import net.bytebuddy.implementation.bind.annotation.RuntimeType; + +import java.lang.reflect.Method; + +/** + * This class wouldn't be loaded in real env. This is a class template for dynamic class generation. + */ +public class StaticMethodInterV2WithOverrideArgsTemplate { + + /** + * This field is never set in the template, but has value in the runtime. + */ + private static String TARGET_INTERCEPTOR; + + private static StaticMethodsAroundInterceptorV2 INTERCEPTOR; + private static IBootstrapLog LOGGER; + + /** + * Intercept the target static method. + * + * @param clazz target class + * @param allArguments all method arguments + * @param method method description. + * @param zuper the origin call ref. + * @return the return value of target static method. + * @throws Exception only throw exception because of zuper.call() or unexpected exception in sky-walking ( This is a + * bug, if anything triggers this condition ). + */ + @RuntimeType + public static Object intercept(@Origin Class clazz, @AllArguments Object[] allArguments, @Origin Method method, + @Morph OverrideCallable zuper) throws Throwable { + prepare(); + + MethodInvocationContext context = new MethodInvocationContext(); + try { + if (INTERCEPTOR != null) { + INTERCEPTOR.beforeMethod(clazz, method, allArguments, method.getParameterTypes(), context); + } + } catch (Throwable t) { + LOGGER.error(t, "class[{}] before static method[{}] intercept failure", clazz, method.getName()); + } + + Object ret = null; + try { + if (!context.isContinue()) { + ret = context._ret(); + } else { + ret = zuper.call(allArguments); + } + } catch (Throwable t) { + try { + if (INTERCEPTOR != null) { + INTERCEPTOR.handleMethodException(clazz, method, allArguments, method.getParameterTypes(), t, context); + } + } catch (Throwable t2) { + LOGGER.error(t2, "class[{}] handle static method[{}] exception failure", clazz, method.getName(), t2.getMessage()); + } + throw t; + } finally { + try { + if (INTERCEPTOR != null) { + ret = INTERCEPTOR.afterMethod(clazz, method, allArguments, method.getParameterTypes(), ret, context); + } + } catch (Throwable t) { + LOGGER.error(t, "class[{}] after static method[{}] intercept failure:{}", clazz, method.getName(), t.getMessage()); + } + } + return ret; + } + + /** + * Prepare the context. Link to the agent core in AppClassLoader. + */ + private static void prepare() { + if (INTERCEPTOR == null) { + ClassLoader loader = BootstrapInterRuntimeAssist.getAgentClassLoader(); + + if (loader != null) { + IBootstrapLog logger = BootstrapInterRuntimeAssist.getLogger(loader, TARGET_INTERCEPTOR); + if (logger != null) { + LOGGER = logger; + + INTERCEPTOR = BootstrapInterRuntimeAssist.createInterceptor(loader, TARGET_INTERCEPTOR, LOGGER); + } + } else { + LOGGER.error("Runtime ClassLoader not found when create {}." + TARGET_INTERCEPTOR); + } + } + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bytebuddy/AbstractJunction.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bytebuddy/AbstractJunction.java new file mode 100644 index 00000000..4ae6ffcf --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bytebuddy/AbstractJunction.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.bytebuddy; + +import net.bytebuddy.matcher.ElementMatcher; + +public abstract class AbstractJunction implements ElementMatcher.Junction { + + @Override + public Junction and(ElementMatcher other) { + return new Conjunction(this, other); + } + + @Override + public Junction or(ElementMatcher other) { + return new Disjunction(this, other); + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bytebuddy/AnnotationTypeNameMatch.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bytebuddy/AnnotationTypeNameMatch.java new file mode 100644 index 00000000..a1c1f69b --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bytebuddy/AnnotationTypeNameMatch.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.bytebuddy; + +import net.bytebuddy.description.annotation.AnnotationDescription; +import net.bytebuddy.description.annotation.AnnotationSource; +import net.bytebuddy.matcher.CollectionItemMatcher; +import net.bytebuddy.matcher.DeclaringAnnotationMatcher; +import net.bytebuddy.matcher.ElementMatcher; + +/** + * Annotation Type match. Similar with {@link net.bytebuddy.matcher.ElementMatchers#isAnnotatedWith}, the only different + * between them is this match use {@link String} to declare the type, instead of {@link Class}. This can avoid the + * classloader risk. + *

+ * 2019-08-15 + */ +public class AnnotationTypeNameMatch implements ElementMatcher { + + /** + * the target annotation type + */ + private String annotationTypeName; + + /** + * declare the match target method with the certain type. + * + * @param annotationTypeName target annotation type + */ + private AnnotationTypeNameMatch(String annotationTypeName) { + this.annotationTypeName = annotationTypeName; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean matches(T target) { + return target.getAnnotationType().asErasure().getName().equals(annotationTypeName); + } + + /** + * The static method to create {@link AnnotationTypeNameMatch} This is a delegate method to follow byte-buddy {@link + * ElementMatcher}'s code style. + * + * @param annotationTypeName target annotation type + * @param The type of the object that is being matched. + * @return new {@link AnnotationTypeNameMatch} instance. + */ + public static Junction isAnnotatedWithType( + String annotationTypeName) { + final AnnotationTypeNameMatch matcher = new AnnotationTypeNameMatch(annotationTypeName); + return new DeclaringAnnotationMatcher(new CollectionItemMatcher(matcher)); + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bytebuddy/ArgumentTypeNameMatch.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bytebuddy/ArgumentTypeNameMatch.java new file mode 100644 index 00000000..250370f3 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bytebuddy/ArgumentTypeNameMatch.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.bytebuddy; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.method.ParameterList; +import net.bytebuddy.matcher.ElementMatcher; + +/** + * Argument Type match. Similar with {@link net.bytebuddy.matcher.ElementMatchers#takesArgument}, the only different + * between them is this match use {@link String} to declare the type, instead of {@link Class}. This can avoid the + * classloader risk. + *

+ */ +public class ArgumentTypeNameMatch implements ElementMatcher { + + /** + * the index of arguments list. + */ + private int index; + + /** + * the target argument type at {@link ArgumentTypeNameMatch#index} of the arguments list. + */ + private String argumentTypeName; + + /** + * declare the match target method with the certain index and type. + * + * @param index the index of arguments list. + * @param argumentTypeName target argument type + */ + private ArgumentTypeNameMatch(int index, String argumentTypeName) { + ArrayTypeNameChecker.check(argumentTypeName); + + this.index = index; + this.argumentTypeName = argumentTypeName; + } + + /** + * Match the target method. + * + * @param target target method description. + * @return true if matched. or false. + */ + @Override + public boolean matches(MethodDescription target) { + ParameterList parameters = target.getParameters(); + if (parameters.size() > index) { + return parameters.get(index).getType().asErasure().getName().equals(argumentTypeName); + } + + return false; + } + + /** + * The static method to create {@link ArgumentTypeNameMatch} This is a delegate method to follow byte-buddy {@link + * ElementMatcher}'s code style. + * + * @param index the index of arguments list. + * @param argumentTypeName target argument type + * @return new {@link ArgumentTypeNameMatch} instance. + */ + public static ElementMatcher takesArgumentWithType(int index, String argumentTypeName) { + return new ArgumentTypeNameMatch(index, argumentTypeName); + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bytebuddy/ArrayTypeNameChecker.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bytebuddy/ArrayTypeNameChecker.java new file mode 100644 index 00000000..907b9687 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bytebuddy/ArrayTypeNameChecker.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.bytebuddy; + +public class ArrayTypeNameChecker { + + public static void check(String typeName) { + if (typeName.endsWith("[]")) { + throw new IllegalArgumentException("Please use [Lxxx; to define an Array type, and ref to JVM Specification for details"); + } + } +} \ No newline at end of file diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bytebuddy/CacheableTransformerDecorator.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bytebuddy/CacheableTransformerDecorator.java new file mode 100644 index 00000000..54021db8 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bytebuddy/CacheableTransformerDecorator.java @@ -0,0 +1,191 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.bytebuddy; + +import cn.hippo4j.agent.core.boot.AgentPackageNotFoundException; +import cn.hippo4j.agent.core.boot.AgentPackagePath; +import cn.hippo4j.agent.core.logging.api.ILog; +import cn.hippo4j.agent.core.logging.api.LogManager; +import cn.hippo4j.agent.core.util.FileUtils; +import cn.hippo4j.agent.core.util.IOUtils; +import net.bytebuddy.agent.builder.AgentBuilder; +import net.bytebuddy.agent.builder.ResettableClassFileTransformer; +import net.bytebuddy.utility.RandomString; + +import java.io.*; +import java.lang.instrument.IllegalClassFormatException; +import java.security.ProtectionDomain; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Wrapper classFileTransformer of ByteBuddy, save the enhanced bytecode to memory cache or file cache, + * and automatically load the previously generated bytecode during the second retransform, + * to solve the problem that ByteBuddy generates auxiliary classes with different random names every time. + * Allow other javaagent to enhance those classes that enhanced by SkyWalking agent. + */ +public class CacheableTransformerDecorator implements AgentBuilder.TransformerDecorator { + + private static final ILog LOGGER = LogManager.getLogger(CacheableTransformerDecorator.class); + + private final ClassCacheMode cacheMode; + private ClassCacheResolver cacheResolver; + + public CacheableTransformerDecorator(ClassCacheMode cacheMode) throws IOException { + this.cacheMode = cacheMode; + initClassCache(); + } + + private void initClassCache() throws IOException { + if (this.cacheMode.equals(ClassCacheMode.FILE)) { + String cacheDirBase = null; + try { + cacheDirBase = AgentPackagePath.getPath() + "/class-cache"; + } catch (AgentPackageNotFoundException e) { + throw new IOException("Can't find the root path for creating /class-cache folder."); + } + File cacheDir = new File(cacheDirBase + "/class-cache-" + RandomString.make()); + if (!cacheDir.exists()) { + cacheDir.mkdirs(); + } + if (!cacheDir.exists()) { + throw new IOException("Create class cache dir failure"); + } + + cacheResolver = new FileCacheResolver(cacheDir); + } else { + cacheResolver = new MemoryCacheResolver(); + } + } + + @Override + public ResettableClassFileTransformer decorate(ResettableClassFileTransformer classFileTransformer) { + return new ResettableClassFileTransformer.WithDelegation(classFileTransformer) { + + @Override + public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { + // load from cache + byte[] classCache = cacheResolver.getClassCache(loader, className); + if (classCache != null) { + return classCache; + } + + // transform class + classfileBuffer = classFileTransformer.transform(loader, className, classBeingRedefined, protectionDomain, classfileBuffer); + + // save to cache + if (classfileBuffer != null) { + cacheResolver.putClassCache(loader, className, classfileBuffer); + } + + return classfileBuffer; + } + }; + } + + private static String getClassLoaderHash(ClassLoader loader) { + String classloader; + if (loader != null) { + classloader = Integer.toHexString(loader.hashCode()); + } else { + // classloader is null for BootstrapClassLoader + classloader = "00000000"; + } + return classloader; + } + + interface ClassCacheResolver { + + byte[] getClassCache(ClassLoader loader, String className); + + void putClassCache(ClassLoader loader, String className, byte[] classfileBuffer); + } + + static class MemoryCacheResolver implements ClassCacheResolver { + + // classloaderHashcode@className -> class bytes + private Map classCacheMap = new ConcurrentHashMap(); + + @Override + public byte[] getClassCache(ClassLoader loader, String className) { + String cacheKey = getCacheKey(loader, className); + return classCacheMap.get(cacheKey); + } + + @Override + public void putClassCache(ClassLoader loader, String className, byte[] classfileBuffer) { + String cacheKey = getCacheKey(loader, className); + classCacheMap.put(cacheKey, classfileBuffer); + } + + private String getCacheKey(ClassLoader loader, String className) { + return getClassLoaderHash(loader) + "@" + className; + } + } + + static class FileCacheResolver implements ClassCacheResolver { + + private final File cacheDir; + + FileCacheResolver(File cacheDir) { + this.cacheDir = cacheDir; + + // clean cache dir on exit + FileUtils.deleteDirectoryOnExit(cacheDir); + } + + @Override + public byte[] getClassCache(ClassLoader loader, String className) { + // load from cache + File cacheFile = getCacheFile(loader, className); + if (cacheFile.exists()) { + FileInputStream fileInputStream = null; + try { + fileInputStream = new FileInputStream(cacheFile); + return IOUtils.toByteArray(fileInputStream); + } catch (IOException e) { + LOGGER.error("load class bytes from cache file failure", e); + } finally { + IOUtils.closeQuietly(fileInputStream); + } + } + return null; + } + + @Override + public void putClassCache(ClassLoader loader, String className, byte[] classfileBuffer) { + File cacheFile = getCacheFile(loader, className); + cacheFile.getParentFile().mkdirs(); + FileOutputStream output = null; + try { + output = new FileOutputStream(cacheFile); + IOUtils.copy(new ByteArrayInputStream(classfileBuffer), output); + } catch (IOException e) { + LOGGER.error("save class bytes to cache file failure", e); + } finally { + IOUtils.closeQuietly(output); + } + } + + private File getCacheFile(ClassLoader loader, String className) { + String filename = getClassLoaderHash(loader) + "/" + className.replace('.', '/') + ".class"; + return new File(cacheDir, filename); + } + + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bytebuddy/ClassCacheMode.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bytebuddy/ClassCacheMode.java new file mode 100644 index 00000000..d5baf606 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bytebuddy/ClassCacheMode.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.bytebuddy; + +/** + * ByteBuddy class cache mode + */ +public enum ClassCacheMode { + FILE, MEMORY +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bytebuddy/ReturnTypeNameMatch.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bytebuddy/ReturnTypeNameMatch.java new file mode 100644 index 00000000..38318020 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/bytebuddy/ReturnTypeNameMatch.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.bytebuddy; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; + +/** + * Return Type match. Similar with {@link net.bytebuddy.matcher.ElementMatchers#returns}, the only different between + * them is this match use {@link String} to declare the type, instead of {@link Class}. This can avoid the classloader + * risk. + *

+ * 2019-08-15 + */ +public class ReturnTypeNameMatch implements ElementMatcher { + + /** + * the target return type + */ + private String returnTypeName; + + /** + * declare the match target method with the certain type. + * + * @param returnTypeName target return type + */ + private ReturnTypeNameMatch(String returnTypeName) { + ArrayTypeNameChecker.check(returnTypeName); + this.returnTypeName = returnTypeName; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean matches(MethodDescription target) { + return target.getReturnType().asErasure().getName().equals(returnTypeName); + } + + /** + * The static method to create {@link ReturnTypeNameMatch} This is a delegate method to follow byte-buddy {@link + * ElementMatcher}'s code style. + * + * @param returnTypeName target return type + * @return new {@link ReturnTypeNameMatch} instance. + */ + public static ElementMatcher returnsWithType(String returnTypeName) { + return new ReturnTypeNameMatch(returnTypeName); + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/exception/IllegalPluginDefineException.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/exception/IllegalPluginDefineException.java new file mode 100644 index 00000000..c7ab4ac5 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/exception/IllegalPluginDefineException.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.exception; + +/** + * Thrown to indicate that a illegal format plugin definition has been defined in skywalking-plugin.define. + */ +public class IllegalPluginDefineException extends Exception { + + public IllegalPluginDefineException(String define) { + super("Illegal plugin define : " + define); + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/ConstructorInterceptPoint.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/ConstructorInterceptPoint.java new file mode 100644 index 00000000..8d506413 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/ConstructorInterceptPoint.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.interceptor; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; + +/** + * One of the three "Intercept Point". "Intercept Point" is a definition about where and how intercept happens. In this + * "Intercept Point", the definition targets class's constructors, and the interceptor. + *

+ * ref to two others: {@link StaticMethodsInterceptPoint} and {@link InstanceMethodsInterceptPoint} + *

+ */ +public interface ConstructorInterceptPoint { + + /** + * Constructor matcher + * + * @return matcher instance. + */ + ElementMatcher getConstructorMatcher(); + + /** + * @return represents a class name, the class instance must be a instance of {@link + * org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor} + */ + String getConstructorInterceptor(); +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/DeclaredInstanceMethodsInterceptPoint.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/DeclaredInstanceMethodsInterceptPoint.java new file mode 100644 index 00000000..5ff4368a --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/DeclaredInstanceMethodsInterceptPoint.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.interceptor; + +/** + * this interface for those who only want to enhance declared method in case of some unexpected issue, such as spring + * controller + */ +public interface DeclaredInstanceMethodsInterceptPoint extends InstanceMethodsInterceptPoint { +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/EnhanceException.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/EnhanceException.java new file mode 100644 index 00000000..605bd8f5 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/EnhanceException.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.interceptor; + +import cn.hippo4j.agent.core.plugin.PluginException; + +public class EnhanceException extends PluginException { + + private static final long serialVersionUID = -2234782755784217255L; + + public EnhanceException(String message) { + super(message); + } + + public EnhanceException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/InstanceMethodsInterceptPoint.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/InstanceMethodsInterceptPoint.java new file mode 100644 index 00000000..396f471c --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/InstanceMethodsInterceptPoint.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.interceptor; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; + +/** + * One of the three "Intercept Point". "Intercept Point" is a definition about where and how intercept happens. In this + * "Intercept Point", the definition targets class's instance methods, and the interceptor. + *

+ * ref to two others: {@link ConstructorInterceptPoint} and {@link StaticMethodsInterceptPoint} + *

+ */ +public interface InstanceMethodsInterceptPoint { + + /** + * class instance methods matcher. + * + * @return methods matcher + */ + ElementMatcher getMethodsMatcher(); + + /** + * @return represents a class name, the class instance must instanceof InstanceMethodsAroundInterceptor. + */ + String getMethodsInterceptor(); + + boolean isOverrideArgs(); +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/StaticMethodsInterceptPoint.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/StaticMethodsInterceptPoint.java new file mode 100644 index 00000000..73babfa5 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/StaticMethodsInterceptPoint.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.interceptor; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; + +/** + * One of the three "Intercept Point". "Intercept Point" is a definition about where and how intercept happens. In this + * "Intercept Point", the definition targets class's static methods, and the interceptor. + *

+ * ref to two others: {@link ConstructorInterceptPoint} and {@link InstanceMethodsInterceptPoint} + *

+ */ +public interface StaticMethodsInterceptPoint { + + /** + * static methods matcher. + * + * @return matcher instance. + */ + ElementMatcher getMethodsMatcher(); + + /** + * @return represents a class name, the class instance must instanceof StaticMethodsAroundInterceptor. + */ + String getMethodsInterceptor(); + + boolean isOverrideArgs(); +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/BootstrapInterRuntimeAssist.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/BootstrapInterRuntimeAssist.java new file mode 100644 index 00000000..b5f8f353 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/BootstrapInterRuntimeAssist.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.interceptor.enhance; + +import cn.hippo4j.agent.core.plugin.bootstrap.IBootstrapLog; + +import java.io.PrintStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +/** + * This assist help all bootstrap class core interceptor. + */ +public class BootstrapInterRuntimeAssist { + + private static final String AGENT_CLASSLOADER_DEFAULT = "org.apache.skywalking.apm.agent.core.plugin.loader.AgentClassLoader"; + private static final String DEFAULT_AGENT_CLASSLOADER_INSTANCE = "DEFAULT_LOADER"; + private static final String LOG_MANAGER_CLASS = "org.apache.skywalking.apm.agent.core.plugin.bootstrap.BootstrapPluginLogBridge"; + private static final String LOG_MANAGER_GET_LOGGER_METHOD = "getLogger"; + private static final PrintStream OUT = System.out; + + public static ClassLoader getAgentClassLoader() { + try { + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + if (loader == null) { + return null; + } + Class agentClassLoaderClass = Class.forName(AGENT_CLASSLOADER_DEFAULT, true, loader); + Field defaultLoaderField = agentClassLoaderClass.getDeclaredField(DEFAULT_AGENT_CLASSLOADER_INSTANCE); + defaultLoaderField.setAccessible(true); + ClassLoader defaultAgentClassLoader = (ClassLoader) defaultLoaderField.get(null); + + return defaultAgentClassLoader; + } catch (Exception e) { + e.printStackTrace(OUT); + return null; + } + } + + public static IBootstrapLog getLogger(ClassLoader defaultAgentClassLoader, String interceptor) { + try { + Class logManagerClass = Class.forName(LOG_MANAGER_CLASS, true, defaultAgentClassLoader); + Method getLogger = logManagerClass.getMethod(LOG_MANAGER_GET_LOGGER_METHOD, String.class); + return (IBootstrapLog) getLogger.invoke(null, interceptor + "_internal"); + } catch (Exception e) { + e.printStackTrace(OUT); + return null; + } + } + + public static T createInterceptor(ClassLoader defaultAgentClassLoader, String className, IBootstrapLog log) { + try { + Class interceptor = Class.forName(className, true, defaultAgentClassLoader); + return (T) interceptor.newInstance(); + } catch (Exception e) { + log.error(e, "Interceptor[{}] not found", className); + } + return null; + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/ClassEnhancePluginDefine.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/ClassEnhancePluginDefine.java new file mode 100644 index 00000000..b83dbfa0 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/ClassEnhancePluginDefine.java @@ -0,0 +1,234 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.interceptor.enhance; + +import cn.hippo4j.agent.core.logging.api.ILog; +import cn.hippo4j.agent.core.logging.api.LogManager; +import cn.hippo4j.agent.core.plugin.AbstractClassEnhancePluginDefine; +import cn.hippo4j.agent.core.plugin.EnhanceContext; +import cn.hippo4j.agent.core.plugin.PluginException; +import cn.hippo4j.agent.core.plugin.bootstrap.BootstrapInstrumentBoost; +import cn.hippo4j.agent.core.plugin.interceptor.*; +import cn.hippo4j.agent.core.plugin.interceptor.v2.InstanceMethodsInterceptV2Point; +import cn.hippo4j.agent.core.plugin.interceptor.v2.StaticMethodsInterceptV2Point; +import cn.hippo4j.agent.core.util.StringUtil; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.dynamic.DynamicType; +import net.bytebuddy.implementation.FieldAccessor; +import net.bytebuddy.implementation.MethodDelegation; +import net.bytebuddy.implementation.SuperMethodCall; +import net.bytebuddy.implementation.bind.annotation.Morph; +import net.bytebuddy.matcher.ElementMatcher; +import net.bytebuddy.matcher.ElementMatchers; + +import static net.bytebuddy.jar.asm.Opcodes.ACC_PRIVATE; +import static net.bytebuddy.jar.asm.Opcodes.ACC_VOLATILE; +import static net.bytebuddy.matcher.ElementMatchers.isStatic; +import static net.bytebuddy.matcher.ElementMatchers.not; + +/** + * This class controls all enhance operations, including enhance constructors, instance methods and static methods. All + * the enhances base on three types interceptor point: {@link ConstructorInterceptPoint}, {@link + * InstanceMethodsInterceptPoint} and {@link StaticMethodsInterceptPoint} If plugin is going to enhance constructors, + * instance methods, or both, {@link ClassEnhancePluginDefine} will add a field of {@link Object} type. + */ +public abstract class ClassEnhancePluginDefine extends AbstractClassEnhancePluginDefine { + + private static final ILog LOGGER = LogManager.getLogger(ClassEnhancePluginDefine.class); + + /** + * Enhance a class to intercept constructors and class instance methods. + * + * @param typeDescription target class description + * @param newClassBuilder byte-buddy's builder to manipulate class bytecode. + * @return new byte-buddy's builder for further manipulation. + */ + @Override + protected DynamicType.Builder enhanceInstance(TypeDescription typeDescription, + DynamicType.Builder newClassBuilder, ClassLoader classLoader, + EnhanceContext context) throws PluginException { + ConstructorInterceptPoint[] constructorInterceptPoints = getConstructorsInterceptPoints(); + InstanceMethodsInterceptPoint[] instanceMethodsInterceptPoints = getInstanceMethodsInterceptPoints(); + String enhanceOriginClassName = typeDescription.getTypeName(); + boolean existedConstructorInterceptPoint = false; + if (constructorInterceptPoints != null && constructorInterceptPoints.length > 0) { + existedConstructorInterceptPoint = true; + } + boolean existedMethodsInterceptPoints = false; + if (instanceMethodsInterceptPoints != null && instanceMethodsInterceptPoints.length > 0) { + existedMethodsInterceptPoints = true; + } + + /** + * nothing need to be enhanced in class instance, maybe need enhance static methods. + */ + if (!existedConstructorInterceptPoint && !existedMethodsInterceptPoints) { + return newClassBuilder; + } + + /** + * Manipulate class source code.
+ * + * new class need:
+ * 1.Add field, name {@link #CONTEXT_ATTR_NAME}. + * 2.Add a field accessor for this field. + * + * And make sure the source codes manipulation only occurs once. + * + */ + if (!typeDescription.isAssignableTo(EnhancedInstance.class)) { + if (!context.isObjectExtended()) { + newClassBuilder = newClassBuilder.defineField( + CONTEXT_ATTR_NAME, Object.class, ACC_PRIVATE | ACC_VOLATILE) + .implement(EnhancedInstance.class) + .intercept(FieldAccessor.ofField(CONTEXT_ATTR_NAME)); + context.extendObjectCompleted(); + } + } + + /** + * 2. enhance constructors + */ + if (existedConstructorInterceptPoint) { + for (ConstructorInterceptPoint constructorInterceptPoint : constructorInterceptPoints) { + if (isBootstrapInstrumentation()) { + newClassBuilder = newClassBuilder.constructor(constructorInterceptPoint.getConstructorMatcher()) + .intercept(SuperMethodCall.INSTANCE.andThen(MethodDelegation.withDefaultConfiguration() + .to(BootstrapInstrumentBoost + .forInternalDelegateClass(constructorInterceptPoint + .getConstructorInterceptor())))); + } else { + newClassBuilder = newClassBuilder.constructor(constructorInterceptPoint.getConstructorMatcher()) + .intercept(SuperMethodCall.INSTANCE.andThen(MethodDelegation.withDefaultConfiguration() + .to(new ConstructorInter(constructorInterceptPoint + .getConstructorInterceptor(), classLoader)))); + } + } + } + + /** + * 3. enhance instance methods + */ + if (existedMethodsInterceptPoints) { + for (InstanceMethodsInterceptPoint instanceMethodsInterceptPoint : instanceMethodsInterceptPoints) { + String interceptor = instanceMethodsInterceptPoint.getMethodsInterceptor(); + if (StringUtil.isEmpty(interceptor)) { + throw new EnhanceException("no InstanceMethodsAroundInterceptor define to enhance class " + enhanceOriginClassName); + } + ElementMatcher.Junction junction = not(isStatic()).and(instanceMethodsInterceptPoint.getMethodsMatcher()); + if (instanceMethodsInterceptPoint instanceof DeclaredInstanceMethodsInterceptPoint) { + junction = junction.and(ElementMatchers.isDeclaredBy(typeDescription)); + } + if (instanceMethodsInterceptPoint.isOverrideArgs()) { + if (isBootstrapInstrumentation()) { + newClassBuilder = newClassBuilder.method(junction) + .intercept(MethodDelegation.withDefaultConfiguration() + .withBinders(Morph.Binder.install(OverrideCallable.class)) + .to(BootstrapInstrumentBoost.forInternalDelegateClass(interceptor))); + } else { + newClassBuilder = newClassBuilder.method(junction) + .intercept(MethodDelegation.withDefaultConfiguration() + .withBinders(Morph.Binder.install(OverrideCallable.class)) + .to(new InstMethodsInterWithOverrideArgs(interceptor, classLoader))); + } + } else { + if (isBootstrapInstrumentation()) { + newClassBuilder = newClassBuilder.method(junction) + .intercept(MethodDelegation.withDefaultConfiguration() + .to(BootstrapInstrumentBoost.forInternalDelegateClass(interceptor))); + } else { + newClassBuilder = newClassBuilder.method(junction) + .intercept(MethodDelegation.withDefaultConfiguration() + .to(new InstMethodsInter(interceptor, classLoader))); + } + } + } + } + + return newClassBuilder; + } + + /** + * Enhance a class to intercept class static methods. + * + * @param typeDescription target class description + * @param newClassBuilder byte-buddy's builder to manipulate class bytecode. + * @return new byte-buddy's builder for further manipulation. + */ + @Override + protected DynamicType.Builder enhanceClass(TypeDescription typeDescription, DynamicType.Builder newClassBuilder, + ClassLoader classLoader) throws PluginException { + StaticMethodsInterceptPoint[] staticMethodsInterceptPoints = getStaticMethodsInterceptPoints(); + String enhanceOriginClassName = typeDescription.getTypeName(); + if (staticMethodsInterceptPoints == null || staticMethodsInterceptPoints.length == 0) { + return newClassBuilder; + } + + for (StaticMethodsInterceptPoint staticMethodsInterceptPoint : staticMethodsInterceptPoints) { + String interceptor = staticMethodsInterceptPoint.getMethodsInterceptor(); + if (StringUtil.isEmpty(interceptor)) { + throw new EnhanceException("no StaticMethodsAroundInterceptor define to enhance class " + enhanceOriginClassName); + } + + if (staticMethodsInterceptPoint.isOverrideArgs()) { + if (isBootstrapInstrumentation()) { + newClassBuilder = newClassBuilder.method(isStatic().and(staticMethodsInterceptPoint.getMethodsMatcher())) + .intercept(MethodDelegation.withDefaultConfiguration() + .withBinders(Morph.Binder.install(OverrideCallable.class)) + .to(BootstrapInstrumentBoost.forInternalDelegateClass(interceptor))); + } else { + newClassBuilder = newClassBuilder.method(isStatic().and(staticMethodsInterceptPoint.getMethodsMatcher())) + .intercept(MethodDelegation.withDefaultConfiguration() + .withBinders(Morph.Binder.install(OverrideCallable.class)) + .to(new StaticMethodsInterWithOverrideArgs(interceptor))); + } + } else { + if (isBootstrapInstrumentation()) { + newClassBuilder = newClassBuilder.method(isStatic().and(staticMethodsInterceptPoint.getMethodsMatcher())) + .intercept(MethodDelegation.withDefaultConfiguration() + .to(BootstrapInstrumentBoost.forInternalDelegateClass(interceptor))); + } else { + newClassBuilder = newClassBuilder.method(isStatic().and(staticMethodsInterceptPoint.getMethodsMatcher())) + .intercept(MethodDelegation.withDefaultConfiguration() + .to(new StaticMethodsInter(interceptor))); + } + } + + } + + return newClassBuilder; + } + + /** + * @return null, means enhance no v2 instance methods. + */ + @Override + public InstanceMethodsInterceptV2Point[] getInstanceMethodsInterceptV2Points() { + return null; + } + + /** + * @return null, means enhance no v2 static methods. + */ + @Override + public StaticMethodsInterceptV2Point[] getStaticMethodsInterceptV2Points() { + return null; + } + +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/ClassInstanceMethodsEnhancePluginDefine.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/ClassInstanceMethodsEnhancePluginDefine.java new file mode 100644 index 00000000..f8b5c8c3 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/ClassInstanceMethodsEnhancePluginDefine.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.interceptor.enhance; + +import cn.hippo4j.agent.core.plugin.interceptor.StaticMethodsInterceptPoint; + +/** + * Plugins, which only need enhance class instance methods. Actually, inherit from {@link + * ClassInstanceMethodsEnhancePluginDefine} has no differences with inherit from {@link ClassEnhancePluginDefine}. Just + * override {@link ClassEnhancePluginDefine#getStaticMethodsInterceptPoints}, and return NULL, which means nothing to + * enhance. + */ +public abstract class ClassInstanceMethodsEnhancePluginDefine extends ClassEnhancePluginDefine { + + /** + * @return null, means enhance no static methods. + */ + @Override + public StaticMethodsInterceptPoint[] getStaticMethodsInterceptPoints() { + return null; + } + +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/ClassStaticMethodsEnhancePluginDefine.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/ClassStaticMethodsEnhancePluginDefine.java new file mode 100644 index 00000000..c5b04513 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/ClassStaticMethodsEnhancePluginDefine.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.interceptor.enhance; + +import cn.hippo4j.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import cn.hippo4j.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; + +/** + * Plugins, which only need enhance class static methods. Actually, inherit from {@link + * ClassStaticMethodsEnhancePluginDefine} has no differences with inherit from {@link ClassEnhancePluginDefine}. Just + * override {@link ClassEnhancePluginDefine#getConstructorsInterceptPoints} and {@link + * ClassEnhancePluginDefine#getInstanceMethodsInterceptPoints}, and return NULL, which means nothing to enhance. + */ +public abstract class ClassStaticMethodsEnhancePluginDefine extends ClassEnhancePluginDefine { + + /** + * @return null, means enhance no constructors. + */ + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return null; + } + + /** + * @return null, means enhance no instance methods. + */ + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return null; + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/ConstructorInter.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/ConstructorInter.java new file mode 100644 index 00000000..d5b24c1d --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/ConstructorInter.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.interceptor.enhance; + +import cn.hippo4j.agent.core.logging.api.ILog; +import cn.hippo4j.agent.core.logging.api.LogManager; +import cn.hippo4j.agent.core.plugin.PluginException; +import cn.hippo4j.agent.core.plugin.loader.InterceptorInstanceLoader; +import net.bytebuddy.implementation.bind.annotation.AllArguments; +import net.bytebuddy.implementation.bind.annotation.RuntimeType; +import net.bytebuddy.implementation.bind.annotation.This; + +/** + * The actual byte-buddy's interceptor to intercept constructor methods. In this class, it provides a bridge between + * byte-buddy and sky-walking plugin. + */ +public class ConstructorInter { + + private static final ILog LOGGER = LogManager.getLogger(ConstructorInter.class); + + /** + * An {@link InstanceConstructorInterceptor} This name should only stay in {@link String}, the real {@link Class} + * type will trigger classloader failure. If you want to know more, please check on books about Classloader or + * Classloader appointment mechanism. + */ + private InstanceConstructorInterceptor interceptor; + + /** + * @param constructorInterceptorClassName class full name. + */ + public ConstructorInter(String constructorInterceptorClassName, ClassLoader classLoader) throws PluginException { + try { + interceptor = InterceptorInstanceLoader.load(constructorInterceptorClassName, classLoader); + } catch (Throwable t) { + throw new PluginException("Can't create InstanceConstructorInterceptorV2.", t); + } + } + + /** + * Intercept the target constructor. + * + * @param obj target class instance. + * @param allArguments all constructor arguments + */ + @RuntimeType + public void intercept(@This Object obj, @AllArguments Object[] allArguments) { + try { + EnhancedInstance targetObject = (EnhancedInstance) obj; + + interceptor.onConstruct(targetObject, allArguments); + } catch (Throwable t) { + LOGGER.error("ConstructorInter failure.", t); + } + + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/EnhancedInstance.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/EnhancedInstance.java new file mode 100644 index 00000000..cb04a006 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/EnhancedInstance.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.interceptor.enhance; + +public interface EnhancedInstance { + + Object getSkyWalkingDynamicField(); + + void setSkyWalkingDynamicField(Object value); +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/InstMethodsInter.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/InstMethodsInter.java new file mode 100644 index 00000000..2cc28baa --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/InstMethodsInter.java @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.interceptor.enhance; + +import cn.hippo4j.agent.core.logging.api.ILog; +import cn.hippo4j.agent.core.logging.api.LogManager; +import cn.hippo4j.agent.core.plugin.PluginException; +import cn.hippo4j.agent.core.plugin.loader.InterceptorInstanceLoader; +import net.bytebuddy.implementation.bind.annotation.*; + +import java.lang.reflect.Method; +import java.util.concurrent.Callable; + +/** + * The actual byte-buddy's interceptor to intercept class instance methods. In this class, it provides a bridge between + * byte-buddy and sky-walking plugin. + */ +public class InstMethodsInter { + + private static final ILog LOGGER = LogManager.getLogger(InstMethodsInter.class); + + /** + * An {@link InstanceMethodsAroundInterceptor} This name should only stay in {@link String}, the real {@link Class} + * type will trigger classloader failure. If you want to know more, please check on books about Classloader or + * Classloader appointment mechanism. + */ + private InstanceMethodsAroundInterceptor interceptor; + + /** + * @param instanceMethodsAroundInterceptorClassName class full name. + */ + public InstMethodsInter(String instanceMethodsAroundInterceptorClassName, ClassLoader classLoader) { + try { + interceptor = InterceptorInstanceLoader.load(instanceMethodsAroundInterceptorClassName, classLoader); + } catch (Throwable t) { + throw new PluginException("Can't create InstanceMethodsAroundInterceptor.", t); + } + } + + /** + * Intercept the target instance method. + * + * @param obj target class instance. + * @param allArguments all method arguments + * @param method method description. + * @param zuper the origin call ref. + * @return the return value of target instance method. + * @throws Exception only throw exception because of zuper.call() or unexpected exception in sky-walking ( This is a + * bug, if anything triggers this condition ). + */ + @RuntimeType + public Object intercept(@This Object obj, @AllArguments Object[] allArguments, @SuperCall Callable zuper, + @Origin Method method) throws Throwable { + EnhancedInstance targetObject = (EnhancedInstance) obj; + + MethodInterceptResult result = new MethodInterceptResult(); + try { + interceptor.beforeMethod(targetObject, method, allArguments, method.getParameterTypes(), result); + } catch (Throwable t) { + LOGGER.error(t, "class[{}] before method[{}] intercept failure", obj.getClass(), method.getName()); + } + + Object ret = null; + try { + if (!result.isContinue()) { + ret = result._ret(); + } else { + ret = zuper.call(); + } + } catch (Throwable t) { + try { + interceptor.handleMethodException(targetObject, method, allArguments, method.getParameterTypes(), t); + } catch (Throwable t2) { + LOGGER.error(t2, "class[{}] handle method[{}] exception failure", obj.getClass(), method.getName()); + } + throw t; + } finally { + try { + ret = interceptor.afterMethod(targetObject, method, allArguments, method.getParameterTypes(), ret); + } catch (Throwable t) { + LOGGER.error(t, "class[{}] after method[{}] intercept failure", obj.getClass(), method.getName()); + } + } + return ret; + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/InstMethodsInterWithOverrideArgs.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/InstMethodsInterWithOverrideArgs.java new file mode 100644 index 00000000..ef7c95c6 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/InstMethodsInterWithOverrideArgs.java @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.interceptor.enhance; + +import cn.hippo4j.agent.core.logging.api.ILog; +import cn.hippo4j.agent.core.logging.api.LogManager; +import cn.hippo4j.agent.core.plugin.PluginException; +import cn.hippo4j.agent.core.plugin.loader.InterceptorInstanceLoader; +import net.bytebuddy.implementation.bind.annotation.*; + +import java.lang.reflect.Method; + +/** + * The actual byte-buddy's interceptor to intercept class instance methods. In this class, it provides a bridge between + * byte-buddy and sky-walking plugin. + */ +public class InstMethodsInterWithOverrideArgs { + + private static final ILog LOGGER = LogManager.getLogger(InstMethodsInterWithOverrideArgs.class); + + /** + * An {@link InstanceMethodsAroundInterceptor} This name should only stay in {@link String}, the real {@link Class} + * type will trigger classloader failure. If you want to know more, please check on books about Classloader or + * Classloader appointment mechanism. + */ + private InstanceMethodsAroundInterceptor interceptor; + + /** + * @param instanceMethodsAroundInterceptorClassName class full name. + */ + public InstMethodsInterWithOverrideArgs(String instanceMethodsAroundInterceptorClassName, ClassLoader classLoader) { + try { + interceptor = InterceptorInstanceLoader.load(instanceMethodsAroundInterceptorClassName, classLoader); + } catch (Throwable t) { + throw new PluginException("Can't create InstanceMethodsAroundInterceptor.", t); + } + } + + /** + * Intercept the target instance method. + * + * @param obj target class instance. + * @param allArguments all method arguments + * @param method method description. + * @param zuper the origin call ref. + * @return the return value of target instance method. + * @throws Exception only throw exception because of zuper.call() or unexpected exception in sky-walking ( This is a + * bug, if anything triggers this condition ). + */ + @RuntimeType + public Object intercept(@This Object obj, @AllArguments Object[] allArguments, @Origin Method method, + @Morph OverrideCallable zuper) throws Throwable { + EnhancedInstance targetObject = (EnhancedInstance) obj; + + MethodInterceptResult result = new MethodInterceptResult(); + try { + interceptor.beforeMethod(targetObject, method, allArguments, method.getParameterTypes(), result); + } catch (Throwable t) { + LOGGER.error(t, "class[{}] before method[{}] intercept failure", obj.getClass(), method.getName()); + } + + Object ret = null; + try { + if (!result.isContinue()) { + ret = result._ret(); + } else { + ret = zuper.call(allArguments); + } + } catch (Throwable t) { + try { + interceptor.handleMethodException(targetObject, method, allArguments, method.getParameterTypes(), t); + } catch (Throwable t2) { + LOGGER.error(t2, "class[{}] handle method[{}] exception failure", obj.getClass(), method.getName()); + } + throw t; + } finally { + try { + ret = interceptor.afterMethod(targetObject, method, allArguments, method.getParameterTypes(), ret); + } catch (Throwable t) { + LOGGER.error(t, "class[{}] after method[{}] intercept failure", obj.getClass(), method.getName()); + } + } + return ret; + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/InstanceConstructorInterceptor.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/InstanceConstructorInterceptor.java new file mode 100644 index 00000000..7cc3472c --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/InstanceConstructorInterceptor.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.interceptor.enhance; + +/** + * The instance constructor's interceptor interface. Any plugin, which wants to intercept constructor, must implement + * this interface. + *

+ */ +public interface InstanceConstructorInterceptor { + + /** + * Called after the origin constructor invocation. + */ + void onConstruct(EnhancedInstance objInst, Object[] allArguments) throws Throwable; +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/InstanceMethodsAroundInterceptor.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/InstanceMethodsAroundInterceptor.java new file mode 100644 index 00000000..97e13ad8 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/InstanceMethodsAroundInterceptor.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.interceptor.enhance; + +import java.lang.reflect.Method; + +/** + * A interceptor, which intercept method's invocation. The target methods will be defined in {@link + * ClassEnhancePluginDefine}'s subclass, most likely in {@link ClassInstanceMethodsEnhancePluginDefine} + */ +public interface InstanceMethodsAroundInterceptor { + + /** + * called before target method invocation. + * + * @param result change this result, if you want to truncate the method. + */ + void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + MethodInterceptResult result) throws Throwable; + + /** + * called after target method invocation. Even method's invocation triggers an exception. + * + * @param ret the method's original return value. May be null if the method triggers an exception. + * @return the method's actual return value. + */ + Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + Object ret) throws Throwable; + + /** + * called when occur exception. + * + * @param t the exception occur. + */ + void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t); +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/MethodInterceptResult.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/MethodInterceptResult.java new file mode 100644 index 00000000..e647f2bc --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/MethodInterceptResult.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.interceptor.enhance; + +import java.lang.reflect.Method; + +/** + * This is a method return value manipulator. When a interceptor's method, such as {@link + * InstanceMethodsAroundInterceptor#beforeMethod(EnhancedInstance, Method, Object[], Class[], MethodInterceptResult)} + * (org.apache.skywalking.apm.agent.core.plugin.interceptor.EnhancedClassInstanceContext, has this as a method argument, + * the interceptor can manipulate the method's return value.

The new value set to this object, by {@link + * MethodInterceptResult#defineReturnValue(Object)}, will override the origin return value. + */ +public class MethodInterceptResult { + + private boolean isContinue = true; + + private Object ret = null; + + /** + * define the new return value. + * + * @param ret new return value. + */ + public void defineReturnValue(Object ret) { + this.isContinue = false; + this.ret = ret; + } + + /** + * @return true, will trigger method interceptor({@link InstMethodsInter} and {@link StaticMethodsInter}) to invoke + * the origin method. Otherwise, not. + */ + public boolean isContinue() { + return isContinue; + } + + /** + * @return the new return value. + */ + public Object _ret() { + return ret; + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/OverrideCallable.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/OverrideCallable.java new file mode 100644 index 00000000..8a488dd4 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/OverrideCallable.java @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.interceptor.enhance; + +public interface OverrideCallable { + + Object call(Object[] args); +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/StaticMethodsAroundInterceptor.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/StaticMethodsAroundInterceptor.java new file mode 100644 index 00000000..d6398f19 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/StaticMethodsAroundInterceptor.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.interceptor.enhance; + +import java.lang.reflect.Method; + +/** + * The static method's interceptor interface. Any plugin, which wants to intercept static methods, must implement this + * interface. + */ +public interface StaticMethodsAroundInterceptor { + + /** + * called before target method invocation. + * + * @param result change this result, if you want to truncate the method. + */ + void beforeMethod(Class clazz, Method method, Object[] allArguments, Class[] parameterTypes, + MethodInterceptResult result); + + /** + * called after target method invocation. Even method's invocation triggers an exception. + * + * @param ret the method's original return value. + * @return the method's actual return value. + */ + Object afterMethod(Class clazz, Method method, Object[] allArguments, Class[] parameterTypes, Object ret); + + /** + * called when occur exception. + * + * @param t the exception occur. + */ + void handleMethodException(Class clazz, Method method, Object[] allArguments, Class[] parameterTypes, + Throwable t); +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/StaticMethodsInter.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/StaticMethodsInter.java new file mode 100644 index 00000000..3be9ccba --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/StaticMethodsInter.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.interceptor.enhance; + +import cn.hippo4j.agent.core.logging.api.ILog; +import cn.hippo4j.agent.core.logging.api.LogManager; +import cn.hippo4j.agent.core.plugin.loader.InterceptorInstanceLoader; +import net.bytebuddy.implementation.bind.annotation.AllArguments; +import net.bytebuddy.implementation.bind.annotation.Origin; +import net.bytebuddy.implementation.bind.annotation.RuntimeType; +import net.bytebuddy.implementation.bind.annotation.SuperCall; + +import java.lang.reflect.Method; +import java.util.concurrent.Callable; + +/** + * The actual byte-buddy's interceptor to intercept class static methods. In this class, it provides a bridge between + * byte-buddy and sky-walking plugin. + */ +public class StaticMethodsInter { + + private static final ILog LOGGER = LogManager.getLogger(StaticMethodsInter.class); + + /** + * A class full name, and instanceof {@link StaticMethodsAroundInterceptor} This name should only stay in {@link + * String}, the real {@link Class} type will trigger classloader failure. If you want to know more, please check on + * books about Classloader or Classloader appointment mechanism. + */ + private String staticMethodsAroundInterceptorClassName; + + /** + * Set the name of {@link StaticMethodsInter#staticMethodsAroundInterceptorClassName} + * + * @param staticMethodsAroundInterceptorClassName class full name. + */ + public StaticMethodsInter(String staticMethodsAroundInterceptorClassName) { + this.staticMethodsAroundInterceptorClassName = staticMethodsAroundInterceptorClassName; + } + + /** + * Intercept the target static method. + * + * @param clazz target class + * @param allArguments all method arguments + * @param method method description. + * @param zuper the origin call ref. + * @return the return value of target static method. + * @throws Exception only throw exception because of zuper.call() or unexpected exception in sky-walking ( This is a + * bug, if anything triggers this condition ). + */ + @RuntimeType + public Object intercept(@Origin Class clazz, @AllArguments Object[] allArguments, @Origin Method method, + @SuperCall Callable zuper) throws Throwable { + StaticMethodsAroundInterceptor interceptor = InterceptorInstanceLoader.load(staticMethodsAroundInterceptorClassName, clazz + .getClassLoader()); + + MethodInterceptResult result = new MethodInterceptResult(); + try { + interceptor.beforeMethod(clazz, method, allArguments, method.getParameterTypes(), result); + } catch (Throwable t) { + LOGGER.error(t, "class[{}] before static method[{}] intercept failure", clazz, method.getName()); + } + + Object ret = null; + try { + if (!result.isContinue()) { + ret = result._ret(); + } else { + ret = zuper.call(); + } + } catch (Throwable t) { + try { + interceptor.handleMethodException(clazz, method, allArguments, method.getParameterTypes(), t); + } catch (Throwable t2) { + LOGGER.error(t2, "class[{}] handle static method[{}] exception failure", clazz, method.getName(), t2.getMessage()); + } + throw t; + } finally { + try { + ret = interceptor.afterMethod(clazz, method, allArguments, method.getParameterTypes(), ret); + } catch (Throwable t) { + LOGGER.error(t, "class[{}] after static method[{}] intercept failure:{}", clazz, method.getName(), t.getMessage()); + } + } + return ret; + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/StaticMethodsInterWithOverrideArgs.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/StaticMethodsInterWithOverrideArgs.java new file mode 100644 index 00000000..99f83a17 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/StaticMethodsInterWithOverrideArgs.java @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.interceptor.enhance; + +import cn.hippo4j.agent.core.logging.api.ILog; +import cn.hippo4j.agent.core.logging.api.LogManager; +import cn.hippo4j.agent.core.plugin.loader.InterceptorInstanceLoader; +import net.bytebuddy.implementation.bind.annotation.AllArguments; +import net.bytebuddy.implementation.bind.annotation.Morph; +import net.bytebuddy.implementation.bind.annotation.Origin; +import net.bytebuddy.implementation.bind.annotation.RuntimeType; + +import java.lang.reflect.Method; + +/** + * The actual byte-buddy's interceptor to intercept class static methods. In this class, it provides a bridge between + * byte-buddy and sky-walking plugin. + */ +public class StaticMethodsInterWithOverrideArgs { + + private static final ILog LOGGER = LogManager.getLogger(StaticMethodsInterWithOverrideArgs.class); + + /** + * A class full name, and instanceof {@link StaticMethodsAroundInterceptor} This name should only stay in {@link + * String}, the real {@link Class} type will trigger classloader failure. If you want to know more, please check on + * books about Classloader or Classloader appointment mechanism. + */ + private String staticMethodsAroundInterceptorClassName; + + /** + * Set the name of {@link StaticMethodsInterWithOverrideArgs#staticMethodsAroundInterceptorClassName} + * + * @param staticMethodsAroundInterceptorClassName class full name. + */ + public StaticMethodsInterWithOverrideArgs(String staticMethodsAroundInterceptorClassName) { + this.staticMethodsAroundInterceptorClassName = staticMethodsAroundInterceptorClassName; + } + + /** + * Intercept the target static method. + * + * @param clazz target class + * @param allArguments all method arguments + * @param method method description. + * @param zuper the origin call ref. + * @return the return value of target static method. + * @throws Exception only throw exception because of zuper.call() or unexpected exception in sky-walking ( This is a + * bug, if anything triggers this condition ). + */ + @RuntimeType + public Object intercept(@Origin Class clazz, @AllArguments Object[] allArguments, @Origin Method method, + @Morph OverrideCallable zuper) throws Throwable { + StaticMethodsAroundInterceptor interceptor = InterceptorInstanceLoader.load(staticMethodsAroundInterceptorClassName, clazz + .getClassLoader()); + + MethodInterceptResult result = new MethodInterceptResult(); + try { + interceptor.beforeMethod(clazz, method, allArguments, method.getParameterTypes(), result); + } catch (Throwable t) { + LOGGER.error(t, "class[{}] before static method[{}] intercept failure", clazz, method.getName()); + } + + Object ret = null; + try { + if (!result.isContinue()) { + ret = result._ret(); + } else { + ret = zuper.call(allArguments); + } + } catch (Throwable t) { + try { + interceptor.handleMethodException(clazz, method, allArguments, method.getParameterTypes(), t); + } catch (Throwable t2) { + LOGGER.error(t2, "class[{}] handle static method[{}] exception failure", clazz, method.getName(), t2.getMessage()); + } + throw t; + } finally { + try { + ret = interceptor.afterMethod(clazz, method, allArguments, method.getParameterTypes(), ret); + } catch (Throwable t) { + LOGGER.error(t, "class[{}] after static method[{}] intercept failure:{}", clazz, method.getName(), t.getMessage()); + } + } + return ret; + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/v2/ClassEnhancePluginDefineV2.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/v2/ClassEnhancePluginDefineV2.java new file mode 100644 index 00000000..75da0262 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/v2/ClassEnhancePluginDefineV2.java @@ -0,0 +1,207 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.interceptor.enhance.v2; + +import cn.hippo4j.agent.core.plugin.AbstractClassEnhancePluginDefine; +import cn.hippo4j.agent.core.plugin.EnhanceContext; +import cn.hippo4j.agent.core.plugin.PluginException; +import cn.hippo4j.agent.core.plugin.bootstrap.BootstrapInstrumentBoost; +import cn.hippo4j.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import cn.hippo4j.agent.core.plugin.interceptor.EnhanceException; +import cn.hippo4j.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import cn.hippo4j.agent.core.plugin.interceptor.StaticMethodsInterceptPoint; +import cn.hippo4j.agent.core.plugin.interceptor.enhance.ConstructorInter; +import cn.hippo4j.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import cn.hippo4j.agent.core.plugin.interceptor.enhance.OverrideCallable; +import cn.hippo4j.agent.core.plugin.interceptor.v2.ConstructorInterceptV2Point; +import cn.hippo4j.agent.core.plugin.interceptor.v2.DeclaredInstanceMethodsInterceptV2Point; +import cn.hippo4j.agent.core.plugin.interceptor.v2.InstanceMethodsInterceptV2Point; +import cn.hippo4j.agent.core.plugin.interceptor.v2.StaticMethodsInterceptV2Point; +import cn.hippo4j.agent.core.util.StringUtil; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.dynamic.DynamicType; +import net.bytebuddy.implementation.FieldAccessor; +import net.bytebuddy.implementation.MethodDelegation; +import net.bytebuddy.implementation.SuperMethodCall; +import net.bytebuddy.implementation.bind.annotation.Morph; +import net.bytebuddy.matcher.ElementMatcher; +import net.bytebuddy.matcher.ElementMatchers; + +import static net.bytebuddy.jar.asm.Opcodes.ACC_PRIVATE; +import static net.bytebuddy.jar.asm.Opcodes.ACC_VOLATILE; +import static net.bytebuddy.matcher.ElementMatchers.isStatic; +import static net.bytebuddy.matcher.ElementMatchers.not; + +/** + * This class controls all enhance operations, including enhance constructors, instance methods and static methods. All + * the enhances base on three types interceptor point: {@link ConstructorInterceptV2Point}, {@link + * InstanceMethodsInterceptV2Point} and {@link StaticMethodsInterceptV2Point} If plugin is going to enhance constructors, + * instance methods, or both, {@link ClassEnhancePluginDefineV2} will add a field of {@link Object} type. + */ +public abstract class ClassEnhancePluginDefineV2 extends AbstractClassEnhancePluginDefine { + + @Override + protected DynamicType.Builder enhanceClass(TypeDescription typeDescription, + DynamicType.Builder newClassBuilder, + ClassLoader classLoader) throws PluginException { + StaticMethodsInterceptV2Point[] staticMethodsInterceptV2Points = getStaticMethodsInterceptV2Points(); + String enhanceOriginClassName = typeDescription.getTypeName(); + if (staticMethodsInterceptV2Points == null || staticMethodsInterceptV2Points.length == 0) { + return newClassBuilder; + } + + for (StaticMethodsInterceptV2Point staticMethodsInterceptV2Point : staticMethodsInterceptV2Points) { + String interceptor = staticMethodsInterceptV2Point.getMethodsInterceptorV2(); + if (StringUtil.isEmpty(interceptor)) { + throw new EnhanceException( + "no StaticMethodsAroundInterceptorV2 define to enhance class " + enhanceOriginClassName); + } + + if (staticMethodsInterceptV2Point.isOverrideArgs()) { + if (isBootstrapInstrumentation()) { + newClassBuilder = newClassBuilder.method( + isStatic().and(staticMethodsInterceptV2Point.getMethodsMatcher())) + .intercept(MethodDelegation.withDefaultConfiguration() + .withBinders(Morph.Binder.install(OverrideCallable.class)) + .to(BootstrapInstrumentBoost.forInternalDelegateClass(interceptor))); + } else { + newClassBuilder = newClassBuilder.method( + isStatic().and(staticMethodsInterceptV2Point.getMethodsMatcher())) + .intercept(MethodDelegation.withDefaultConfiguration() + .withBinders(Morph.Binder.install(OverrideCallable.class)) + .to(new StaticMethodsInterV2WithOverrideArgs(interceptor))); + } + } else { + if (isBootstrapInstrumentation()) { + newClassBuilder = newClassBuilder.method( + isStatic().and(staticMethodsInterceptV2Point.getMethodsMatcher())) + .intercept(MethodDelegation.withDefaultConfiguration() + .to(BootstrapInstrumentBoost.forInternalDelegateClass(interceptor))); + } else { + newClassBuilder = newClassBuilder.method( + isStatic().and(staticMethodsInterceptV2Point.getMethodsMatcher())) + .intercept(MethodDelegation.withDefaultConfiguration() + .to(new StaticMethodsInterV2(interceptor))); + } + } + + } + + return newClassBuilder; + } + + @Override + protected DynamicType.Builder enhanceInstance(TypeDescription typeDescription, + DynamicType.Builder newClassBuilder, ClassLoader classLoader, + EnhanceContext context) throws PluginException { + ConstructorInterceptPoint[] constructorInterceptPoints = getConstructorsInterceptPoints(); + InstanceMethodsInterceptV2Point[] instanceMethodsInterceptV2Points = getInstanceMethodsInterceptV2Points(); + String enhanceOriginClassName = typeDescription.getTypeName(); + + boolean existedConstructorInterceptPoint = false; + if (constructorInterceptPoints != null && constructorInterceptPoints.length > 0) { + existedConstructorInterceptPoint = true; + } + boolean existedMethodsInterceptV2Points = false; + if (instanceMethodsInterceptV2Points != null && instanceMethodsInterceptV2Points.length > 0) { + existedMethodsInterceptV2Points = true; + } + + if (!existedConstructorInterceptPoint && !existedMethodsInterceptV2Points) { + return newClassBuilder; + } + + if (!typeDescription.isAssignableTo(EnhancedInstance.class)) { + if (!context.isObjectExtended()) { + newClassBuilder = newClassBuilder.defineField( + CONTEXT_ATTR_NAME, Object.class, ACC_PRIVATE | ACC_VOLATILE) + .implement(EnhancedInstance.class) + .intercept(FieldAccessor.ofField(CONTEXT_ATTR_NAME)); + context.extendObjectCompleted(); + } + } + + if (existedConstructorInterceptPoint) { + for (ConstructorInterceptPoint constructorInterceptPoint : constructorInterceptPoints) { + if (isBootstrapInstrumentation()) { + newClassBuilder = newClassBuilder.constructor(constructorInterceptPoint.getConstructorMatcher()) + .intercept(SuperMethodCall.INSTANCE.andThen(MethodDelegation.withDefaultConfiguration() + .to(BootstrapInstrumentBoost + .forInternalDelegateClass(constructorInterceptPoint + .getConstructorInterceptor())))); + } else { + newClassBuilder = newClassBuilder.constructor(constructorInterceptPoint.getConstructorMatcher()) + .intercept(SuperMethodCall.INSTANCE.andThen(MethodDelegation.withDefaultConfiguration() + .to(new ConstructorInter(constructorInterceptPoint + .getConstructorInterceptor(), classLoader)))); + } + } + } + + if (existedMethodsInterceptV2Points) { + for (InstanceMethodsInterceptV2Point instanceMethodsInterceptV2Point : instanceMethodsInterceptV2Points) { + String interceptor = instanceMethodsInterceptV2Point.getMethodsInterceptorV2(); + if (StringUtil.isEmpty(interceptor)) { + throw new EnhanceException( + "no InstanceMethodsAroundInterceptorV2 define to enhance class " + enhanceOriginClassName); + } + ElementMatcher.Junction junction = not(isStatic()).and( + instanceMethodsInterceptV2Point.getMethodsMatcher()); + if (instanceMethodsInterceptV2Point instanceof DeclaredInstanceMethodsInterceptV2Point) { + junction = junction.and(ElementMatchers.isDeclaredBy(typeDescription)); + } + if (instanceMethodsInterceptV2Point.isOverrideArgs()) { + if (isBootstrapInstrumentation()) { + newClassBuilder = newClassBuilder.method(junction) + .intercept(MethodDelegation.withDefaultConfiguration() + .withBinders(Morph.Binder.install(OverrideCallable.class)) + .to(BootstrapInstrumentBoost.forInternalDelegateClass(interceptor))); + } else { + newClassBuilder = newClassBuilder.method(junction) + .intercept(MethodDelegation.withDefaultConfiguration() + .withBinders(Morph.Binder.install(OverrideCallable.class)) + .to(new InstMethodsInterV2WithOverrideArgs(interceptor, classLoader))); + } + } else { + if (isBootstrapInstrumentation()) { + newClassBuilder = newClassBuilder.method(junction) + .intercept(MethodDelegation.withDefaultConfiguration() + .to(BootstrapInstrumentBoost.forInternalDelegateClass(interceptor))); + } else { + newClassBuilder = newClassBuilder.method(junction) + .intercept(MethodDelegation.withDefaultConfiguration() + .to(new InstMethodsInterV2(interceptor, classLoader))); + } + } + } + } + + return newClassBuilder; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return null; + } + + @Override + public StaticMethodsInterceptPoint[] getStaticMethodsInterceptPoints() { + return null; + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/v2/ClassInstanceMethodsEnhancePluginDefineV2.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/v2/ClassInstanceMethodsEnhancePluginDefineV2.java new file mode 100644 index 00000000..2f2c7300 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/v2/ClassInstanceMethodsEnhancePluginDefineV2.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.interceptor.enhance.v2; + +import cn.hippo4j.agent.core.plugin.interceptor.v2.StaticMethodsInterceptV2Point; + +/** + * Plugins, which only need enhance class instance methods. Actually, inherit from {@link + * ClassInstanceMethodsEnhancePluginDefineV2} has no differences with inherit from {@link ClassEnhancePluginDefineV2}. + * Just override {@link ClassEnhancePluginDefineV2#getStaticMethodsInterceptPoints}, and return NULL, which means nothing + * to enhance. + */ +public abstract class ClassInstanceMethodsEnhancePluginDefineV2 extends ClassEnhancePluginDefineV2 { + + /** + * @return null, means enhance no v2 static methods. + */ + @Override + public StaticMethodsInterceptV2Point[] getStaticMethodsInterceptV2Points() { + return null; + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/v2/ClassStaticMethodsEnhancePluginDefineV2.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/v2/ClassStaticMethodsEnhancePluginDefineV2.java new file mode 100644 index 00000000..294e70d8 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/v2/ClassStaticMethodsEnhancePluginDefineV2.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.interceptor.enhance.v2; + +import cn.hippo4j.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import cn.hippo4j.agent.core.plugin.interceptor.v2.InstanceMethodsInterceptV2Point; + +/** + * Plugins, which only need enhance class static methods. Actually, inherit from {@link + * ClassStaticMethodsEnhancePluginDefineV2} has no differences with inherit from {@link ClassEnhancePluginDefineV2}. Just + * override {@link ClassEnhancePluginDefineV2#getConstructorsInterceptPoints} and {@link + * ClassEnhancePluginDefineV2#getInstanceMethodsInterceptV2Points}, and return NULL, which means nothing to enhance. + */ +public abstract class ClassStaticMethodsEnhancePluginDefineV2 extends ClassEnhancePluginDefineV2 { + + /** + * @return null, means enhance no constructors. + */ + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return null; + } + + /** + * @return null, means enhance no v2 instance methods. + */ + @Override + public InstanceMethodsInterceptV2Point[] getInstanceMethodsInterceptV2Points() { + return null; + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/v2/InstMethodsInterV2.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/v2/InstMethodsInterV2.java new file mode 100644 index 00000000..c7b1b6f3 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/v2/InstMethodsInterV2.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.interceptor.enhance.v2; + +import cn.hippo4j.agent.core.logging.api.ILog; +import cn.hippo4j.agent.core.logging.api.LogManager; +import cn.hippo4j.agent.core.plugin.PluginException; +import cn.hippo4j.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import cn.hippo4j.agent.core.plugin.loader.InterceptorInstanceLoader; +import net.bytebuddy.implementation.bind.annotation.*; + +import java.lang.reflect.Method; +import java.util.concurrent.Callable; + +/** + * The actual byte-buddy's interceptor to intercept class instance methods. In this class, it provides a bridge between + * byte-buddy and sky-walking plugin. + */ +public class InstMethodsInterV2 { + + private static final ILog LOGGER = LogManager.getLogger(InstMethodsInterV2.class); + + private InstanceMethodsAroundInterceptorV2 interceptor; + + public InstMethodsInterV2(String instanceMethodsAroundInterceptorClassName, ClassLoader classLoader) { + try { + interceptor = InterceptorInstanceLoader.load(instanceMethodsAroundInterceptorClassName, classLoader); + } catch (Throwable t) { + throw new PluginException("Can't create InstanceMethodsAroundInterceptor.", t); + } + } + + @RuntimeType + public Object intercept(@This Object obj, @AllArguments Object[] allArguments, @SuperCall Callable zuper, + @Origin Method method) throws Throwable { + EnhancedInstance targetObject = (EnhancedInstance) obj; + + MethodInvocationContext context = new MethodInvocationContext(); + try { + interceptor.beforeMethod(targetObject, method, allArguments, method.getParameterTypes(), context); + } catch (Throwable t) { + LOGGER.error(t, "class[{}] before method[{}] intercept failure", obj.getClass(), method.getName()); + } + + Object ret = null; + try { + if (!context.isContinue()) { + ret = context._ret(); + } else { + ret = zuper.call(); + } + } catch (Throwable t) { + try { + interceptor.handleMethodException(targetObject, method, allArguments, method.getParameterTypes(), t, context); + } catch (Throwable t2) { + LOGGER.error(t2, "class[{}] handle method[{}] exception failure", obj.getClass(), method.getName()); + } + throw t; + } finally { + try { + ret = interceptor.afterMethod(targetObject, method, allArguments, method.getParameterTypes(), ret, context); + } catch (Throwable t) { + LOGGER.error(t, "class[{}] after method[{}] intercept failure", obj.getClass(), method.getName()); + } + } + return ret; + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/v2/InstMethodsInterV2WithOverrideArgs.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/v2/InstMethodsInterV2WithOverrideArgs.java new file mode 100644 index 00000000..aaacb300 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/v2/InstMethodsInterV2WithOverrideArgs.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.interceptor.enhance.v2; + +import cn.hippo4j.agent.core.logging.api.ILog; +import cn.hippo4j.agent.core.logging.api.LogManager; +import cn.hippo4j.agent.core.plugin.PluginException; +import cn.hippo4j.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import cn.hippo4j.agent.core.plugin.interceptor.enhance.OverrideCallable; +import cn.hippo4j.agent.core.plugin.loader.InterceptorInstanceLoader; +import net.bytebuddy.implementation.bind.annotation.*; + +import java.lang.reflect.Method; + +/** + * The actual byte-buddy's interceptor to intercept class instance methods. In this class, it provides a bridge between + * byte-buddy and sky-walking plugin. + */ +public class InstMethodsInterV2WithOverrideArgs { + + private static final ILog LOGGER = LogManager.getLogger(InstMethodsInterV2WithOverrideArgs.class); + + /** + * An {@link InstanceMethodsAroundInterceptorV2} This name should only stay in {@link String}, the real {@link Class} + * type will trigger classloader failure. If you want to know more, please check on books about Classloader or + * Classloader appointment mechanism. + */ + private InstanceMethodsAroundInterceptorV2 interceptor; + + /** + * @param instanceMethodsAroundInterceptorClassName class full name. + */ + public InstMethodsInterV2WithOverrideArgs(String instanceMethodsAroundInterceptorClassName, ClassLoader classLoader) { + try { + interceptor = InterceptorInstanceLoader.load(instanceMethodsAroundInterceptorClassName, classLoader); + } catch (Throwable t) { + throw new PluginException("Can't create InstanceMethodsAroundInterceptor.", t); + } + } + + /** + * Intercept the target instance method. + * + * @param obj target class instance. + * @param allArguments all method arguments + * @param method method description. + * @param zuper the origin call ref. + * @return the return value of target instance method. + * @throws Exception only throw exception because of zuper.call() or unexpected exception in sky-walking ( This is a + * bug, if anything triggers this condition ). + */ + @RuntimeType + public Object intercept(@This Object obj, @AllArguments Object[] allArguments, @Origin Method method, + @Morph OverrideCallable zuper) throws Throwable { + EnhancedInstance targetObject = (EnhancedInstance) obj; + + MethodInvocationContext context = new MethodInvocationContext(); + try { + interceptor.beforeMethod(targetObject, method, allArguments, method.getParameterTypes(), context); + } catch (Throwable t) { + LOGGER.error(t, "class[{}] before method[{}] intercept failure", obj.getClass(), method.getName()); + } + + Object ret = null; + try { + if (!context.isContinue()) { + ret = context._ret(); + } else { + ret = zuper.call(allArguments); + } + } catch (Throwable t) { + try { + interceptor.handleMethodException(targetObject, method, allArguments, method.getParameterTypes(), t, context); + } catch (Throwable t2) { + LOGGER.error(t2, "class[{}] handle method[{}] exception failure", obj.getClass(), method.getName()); + } + throw t; + } finally { + try { + ret = interceptor.afterMethod(targetObject, method, allArguments, method.getParameterTypes(), ret, context); + } catch (Throwable t) { + LOGGER.error(t, "class[{}] after method[{}] intercept failure", obj.getClass(), method.getName()); + } + } + return ret; + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/v2/InstanceMethodsAroundInterceptorV2.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/v2/InstanceMethodsAroundInterceptorV2.java new file mode 100644 index 00000000..b732300f --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/v2/InstanceMethodsAroundInterceptorV2.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.interceptor.enhance.v2; + +import cn.hippo4j.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import cn.hippo4j.agent.core.plugin.interceptor.enhance.EnhancedInstance; + +import java.lang.reflect.Method; + +/** + * A v2 interceptor, which intercept method's invocation. The target methods will be defined in {@link + * ClassEnhancePluginDefineV2}'s subclass, most likely in {@link ClassInstanceMethodsEnhancePluginDefine} + */ +public interface InstanceMethodsAroundInterceptorV2 { + + /** + * called before target method invocation. + * + * @param context the method invocation context including result context. + */ + void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + MethodInvocationContext context) throws Throwable; + + /** + * called after target method invocation. Even method's invocation triggers an exception. + * + * @param ret the method's original return value. May be null if the method triggers an exception. + * @return the method's actual return value. + */ + Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + Object ret, MethodInvocationContext context) throws Throwable; + + /** + * called when occur exception. + * + * @param t the exception occur. + */ + void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t, MethodInvocationContext context); + +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/v2/MethodInvocationContext.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/v2/MethodInvocationContext.java new file mode 100644 index 00000000..a6647aa8 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/v2/MethodInvocationContext.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.interceptor.enhance.v2; + +import cn.hippo4j.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import lombok.Getter; +import lombok.Setter; + +/** + * MethodInvocationContext holds the reference to propagate it between beforeMethod and afterMethod/handleMethodException + */ +@Setter +@Getter +public class MethodInvocationContext extends MethodInterceptResult { + + /** + * A pointer for the propagating context + */ + private Object context; +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/v2/StaticMethodsAroundInterceptorV2.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/v2/StaticMethodsAroundInterceptorV2.java new file mode 100644 index 00000000..55685f07 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/v2/StaticMethodsAroundInterceptorV2.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.interceptor.enhance.v2; + +import java.lang.reflect.Method; + +/** + * The static method's interceptor v2 interface. Any plugin, which wants to intercept static methods, must implement this + * interface. + */ +public interface StaticMethodsAroundInterceptorV2 { + + /** + * called before target method invocation. + * + * @param context the method invocation context including result context. + */ + void beforeMethod(Class clazz, Method method, Object[] allArguments, Class[] parameterTypes, + MethodInvocationContext context); + + /** + * called after target method invocation. Even method's invocation triggers an exception. + * + * @param ret the method's original return value. + * @return the method's actual return value. + */ + Object afterMethod(Class clazz, Method method, Object[] allArguments, Class[] parameterTypes, Object ret, + MethodInvocationContext context); + + /** + * called when occur exception. + * + * @param t the exception occur. + */ + void handleMethodException(Class clazz, Method method, Object[] allArguments, Class[] parameterTypes, + Throwable t, MethodInvocationContext context); +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/v2/StaticMethodsInterV2.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/v2/StaticMethodsInterV2.java new file mode 100644 index 00000000..97785770 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/v2/StaticMethodsInterV2.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.interceptor.enhance.v2; + +import cn.hippo4j.agent.core.logging.api.ILog; +import cn.hippo4j.agent.core.logging.api.LogManager; +import cn.hippo4j.agent.core.plugin.loader.InterceptorInstanceLoader; +import net.bytebuddy.implementation.bind.annotation.AllArguments; +import net.bytebuddy.implementation.bind.annotation.Origin; +import net.bytebuddy.implementation.bind.annotation.RuntimeType; +import net.bytebuddy.implementation.bind.annotation.SuperCall; + +import java.lang.reflect.Method; +import java.util.concurrent.Callable; + +/** + * The actual byte-buddy's interceptor to intercept class instance methods. In this class, it provides a bridge between + * byte-buddy and sky-walking plugin. + */ +public class StaticMethodsInterV2 { + + private static final ILog LOGGER = LogManager.getLogger(StaticMethodsInterV2.class); + + /** + * A class full name, and instanceof {@link StaticMethodsAroundInterceptorV2} This name should only stay in {@link + * String}, the real {@link Class} type will trigger classloader failure. If you want to know more, please check on + * books about Classloader or Classloader appointment mechanism. + */ + private String staticMethodsAroundInterceptorClassName; + + /** + * Set the name of {@link StaticMethodsInterV2#staticMethodsAroundInterceptorClassName} + * + * @param staticMethodsAroundInterceptorClassName class full name. + */ + public StaticMethodsInterV2(String staticMethodsAroundInterceptorClassName) { + this.staticMethodsAroundInterceptorClassName = staticMethodsAroundInterceptorClassName; + } + + /** + * Intercept the target static method. + * + * @param clazz target class + * @param allArguments all method arguments + * @param method method description. + * @param zuper the origin call ref. + * @return the return value of target static method. + * @throws Exception only throw exception because of zuper.call() or unexpected exception in sky-walking ( This is a + * bug, if anything triggers this condition ). + */ + @RuntimeType + public Object intercept(@Origin Class clazz, @AllArguments Object[] allArguments, @Origin Method method, + @SuperCall Callable zuper) throws Throwable { + StaticMethodsAroundInterceptorV2 interceptor = InterceptorInstanceLoader.load(staticMethodsAroundInterceptorClassName, + clazz.getClassLoader()); + + MethodInvocationContext context = new MethodInvocationContext(); + try { + interceptor.beforeMethod(clazz, method, allArguments, method.getParameterTypes(), context); + } catch (Throwable t) { + LOGGER.error(t, "class[{}] before static method[{}] intercept failure", clazz, method.getName()); + } + + Object ret = null; + try { + if (!context.isContinue()) { + ret = context._ret(); + } else { + ret = zuper.call(); + } + } catch (Throwable t) { + try { + interceptor.handleMethodException(clazz, method, allArguments, method.getParameterTypes(), t, context); + } catch (Throwable t2) { + LOGGER.error(t2, "class[{}] handle static method[{}] exception failure", clazz, method.getName(), t2.getMessage()); + } + throw t; + } finally { + try { + ret = interceptor.afterMethod(clazz, method, allArguments, method.getParameterTypes(), ret, context); + } catch (Throwable t) { + LOGGER.error(t, "class[{}] after static method[{}] intercept failure:{}", clazz, method.getName(), t.getMessage()); + } + } + return ret; + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/v2/StaticMethodsInterV2WithOverrideArgs.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/v2/StaticMethodsInterV2WithOverrideArgs.java new file mode 100644 index 00000000..467af26a --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/enhance/v2/StaticMethodsInterV2WithOverrideArgs.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.interceptor.enhance.v2; + +import cn.hippo4j.agent.core.logging.api.ILog; +import cn.hippo4j.agent.core.logging.api.LogManager; +import cn.hippo4j.agent.core.plugin.interceptor.enhance.OverrideCallable; +import cn.hippo4j.agent.core.plugin.loader.InterceptorInstanceLoader; +import net.bytebuddy.implementation.bind.annotation.AllArguments; +import net.bytebuddy.implementation.bind.annotation.Morph; +import net.bytebuddy.implementation.bind.annotation.Origin; +import net.bytebuddy.implementation.bind.annotation.RuntimeType; + +import java.lang.reflect.Method; + +/** + * The actual byte-buddy's interceptor to intercept class instance methods. In this class, it provides a bridge between + * byte-buddy and sky-walking plugin. + */ +public class StaticMethodsInterV2WithOverrideArgs { + + private static final ILog LOGGER = LogManager.getLogger(StaticMethodsInterV2WithOverrideArgs.class); + + /** + * A class full name, and instanceof {@link StaticMethodsAroundInterceptorV2} This name should only stay in {@link + * String}, the real {@link Class} type will trigger classloader failure. If you want to know more, please check on + * books about Classloader or Classloader appointment mechanism. + */ + private String staticMethodsAroundInterceptorClassName; + + /** + * Set the name of {@link StaticMethodsInterV2WithOverrideArgs#staticMethodsAroundInterceptorClassName} + * + * @param staticMethodsAroundInterceptorClassName class full name. + */ + public StaticMethodsInterV2WithOverrideArgs(String staticMethodsAroundInterceptorClassName) { + this.staticMethodsAroundInterceptorClassName = staticMethodsAroundInterceptorClassName; + } + + /** + * Intercept the target static method. + * + * @param clazz target class + * @param allArguments all method arguments + * @param method method description. + * @param zuper the origin call ref. + * @return the return value of target static method. + * @throws Exception only throw exception because of zuper.call() or unexpected exception in sky-walking ( This is a + * bug, if anything triggers this condition ). + */ + @RuntimeType + public Object intercept(@Origin Class clazz, @AllArguments Object[] allArguments, @Origin Method method, + @Morph OverrideCallable zuper) throws Throwable { + StaticMethodsAroundInterceptorV2 interceptor = InterceptorInstanceLoader.load(staticMethodsAroundInterceptorClassName, + clazz.getClassLoader()); + + MethodInvocationContext context = new MethodInvocationContext(); + try { + interceptor.beforeMethod(clazz, method, allArguments, method.getParameterTypes(), context); + } catch (Throwable t) { + LOGGER.error(t, "class[{}] before static method[{}] intercept failure", clazz, method.getName()); + } + + Object ret = null; + try { + if (!context.isContinue()) { + ret = context._ret(); + } else { + ret = zuper.call(allArguments); + } + } catch (Throwable t) { + try { + interceptor.handleMethodException(clazz, method, allArguments, method.getParameterTypes(), t, context); + } catch (Throwable t2) { + LOGGER.error(t2, "class[{}] handle static method[{}] exception failure", clazz, method.getName(), t2.getMessage()); + } + throw t; + } finally { + try { + ret = interceptor.afterMethod(clazz, method, allArguments, method.getParameterTypes(), ret, context); + } catch (Throwable t) { + LOGGER.error(t, "class[{}] after static method[{}] intercept failure:{}", clazz, method.getName(), t.getMessage()); + } + } + return ret; + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/v2/ConstructorInterceptV2Point.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/v2/ConstructorInterceptV2Point.java new file mode 100644 index 00000000..0a035db0 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/v2/ConstructorInterceptV2Point.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.interceptor.v2; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public interface ConstructorInterceptV2Point { + + /** + * Constructor matcher + * + * @return matcher instance. + */ + ElementMatcher getConstructorMatcher(); + + /** + * @return represents a class name, the class instance must be a instance of {@link + * org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor} + */ + String getConstructorInterceptorV2(); + +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/v2/DeclaredInstanceMethodsInterceptV2Point.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/v2/DeclaredInstanceMethodsInterceptV2Point.java new file mode 100644 index 00000000..02754d87 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/v2/DeclaredInstanceMethodsInterceptV2Point.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.interceptor.v2; + +/** + * this interface for those who only want to enhance declared method in case of some unexpected issue, such as spring + * controller + */ +public interface DeclaredInstanceMethodsInterceptV2Point extends InstanceMethodsInterceptV2Point { +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/v2/InstanceMethodsInterceptV2Point.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/v2/InstanceMethodsInterceptV2Point.java new file mode 100644 index 00000000..6d4844cb --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/v2/InstanceMethodsInterceptV2Point.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.interceptor.v2; + +import cn.hippo4j.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; + +/** + * One of the three "Intercept Point". "Intercept Point" is a definition about where and how intercept happens. In this + * "Intercept Point", the definition targets class's instance methods, and the interceptor. + *

+ * ref to two others: {@link ConstructorInterceptPoint} and {@link StaticMethodsInterceptV2Point} + *

+ */ +public interface InstanceMethodsInterceptV2Point { + + /** + * class instance methods matcher. + * + * @return methods matcher + */ + ElementMatcher getMethodsMatcher(); + + /** + * @return represents a class name, the class instance must instanceof InstanceMethodsAroundInterceptorV2. + */ + String getMethodsInterceptorV2(); + + boolean isOverrideArgs(); +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/v2/StaticMethodsInterceptV2Point.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/v2/StaticMethodsInterceptV2Point.java new file mode 100644 index 00000000..aa6e5d56 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/interceptor/v2/StaticMethodsInterceptV2Point.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.interceptor.v2; + +import cn.hippo4j.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; + +/** + * One of the three "Intercept Point". "Intercept Point" is a definition about where and how intercept happens. In this + * "Intercept Point", the definition targets class's static methods, and the interceptor. + *

+ * ref to two others: {@link ConstructorInterceptPoint} and {@link InstanceMethodsInterceptV2Point} + *

+ */ +public interface StaticMethodsInterceptV2Point { + + /** + * static methods matcher. + * + * @return matcher instance. + */ + ElementMatcher getMethodsMatcher(); + + /** + * @return represents a class name, the class instance must instanceof StaticMethodsAroundInterceptorV2. + */ + String getMethodsInterceptorV2(); + + boolean isOverrideArgs(); +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/jdk9module/JDK9ModuleExporter.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/jdk9module/JDK9ModuleExporter.java new file mode 100644 index 00000000..f1923ef5 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/jdk9module/JDK9ModuleExporter.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.jdk9module; + +import cn.hippo4j.agent.core.logging.api.ILog; +import cn.hippo4j.agent.core.logging.api.LogManager; +import cn.hippo4j.agent.core.plugin.ByteBuddyCoreClasses; +import net.bytebuddy.agent.builder.AgentBuilder; + +import java.lang.instrument.Instrumentation; +import java.util.ArrayList; +import java.util.List; + +/** + * Since JDK 9, module concept has been introduced. By supporting that, agent core needs to open the read edge + */ +public class JDK9ModuleExporter { + + private static final ILog LOGGER = LogManager.getLogger(JDK9ModuleExporter.class); + + private static final String[] HIGH_PRIORITY_CLASSES = { + "org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance", + "org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult", + "org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.OverrideCallable", + "org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ConstructorInter", + "org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstMethodsInter", + "org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstMethodsInterWithOverrideArgs", + "org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.StaticMethodsInter", + "org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.StaticMethodsInterWithOverrideArgs", + }; + + /** + * Assures that all modules of the supplied types are read by the module of any instrumented type. JDK Module system + * was introduced since JDK9. + *

+ * The following codes work only JDK Module system exist. + */ + public static AgentBuilder openReadEdge(Instrumentation instrumentation, AgentBuilder agentBuilder, + EdgeClasses classes) { + for (String className : classes.classes) { + try { + agentBuilder = agentBuilder.assureReadEdgeFromAndTo(instrumentation, Class.forName(className)); + } catch (ClassNotFoundException e) { + throw new UnsupportedOperationException("Fail to open read edge for class " + className + " to public access in JDK9+", e); + } + } + for (String className : HIGH_PRIORITY_CLASSES) { + try { + agentBuilder = agentBuilder.assureReadEdgeFromAndTo(instrumentation, Class.forName(className)); + } catch (ClassNotFoundException e) { + throw new UnsupportedOperationException("Fail to open read edge for class " + className + " to public access in JDK9+", e); + } + } + + return agentBuilder; + } + + public static class EdgeClasses { + + private List classes = new ArrayList(); + + public EdgeClasses() { + for (String className : ByteBuddyCoreClasses.CLASSES) { + add(className); + } + } + + public void add(String className) { + if (!classes.contains(className)) { + classes.add(className); + } + } + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/loader/AgentClassLoader.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/loader/AgentClassLoader.java new file mode 100644 index 00000000..0539c9a7 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/loader/AgentClassLoader.java @@ -0,0 +1,216 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.loader; + +import cn.hippo4j.agent.core.boot.AgentPackageNotFoundException; +import cn.hippo4j.agent.core.boot.AgentPackagePath; +import cn.hippo4j.agent.core.boot.PluginConfig; +import cn.hippo4j.agent.core.conf.Config; +import cn.hippo4j.agent.core.conf.SnifferConfigInitializer; +import cn.hippo4j.agent.core.logging.api.ILog; +import cn.hippo4j.agent.core.logging.api.LogManager; +import cn.hippo4j.agent.core.plugin.PluginBootstrap; +import lombok.RequiredArgsConstructor; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +/** + * The AgentClassLoader represents a classloader, which is in charge of finding plugins and interceptors. + */ +public class AgentClassLoader extends ClassLoader { + + static { + /* + * Try to solve the classloader dead lock. See https://github.com/apache/skywalking/pull/2016 + */ + registerAsParallelCapable(); + } + + private static final ILog LOGGER = LogManager.getLogger(AgentClassLoader.class); + /** + * The default class loader for the agent. + */ + private static AgentClassLoader DEFAULT_LOADER; + + private List classpath; + private List allJars; + private ReentrantLock jarScanLock = new ReentrantLock(); + + public static AgentClassLoader getDefault() { + return DEFAULT_LOADER; + } + + /** + * Init the default class loader. + * + * @throws AgentPackageNotFoundException if agent package is not found. + */ + public static void initDefaultLoader() throws AgentPackageNotFoundException { + if (DEFAULT_LOADER == null) { + synchronized (AgentClassLoader.class) { + if (DEFAULT_LOADER == null) { + DEFAULT_LOADER = new AgentClassLoader(PluginBootstrap.class.getClassLoader()); + } + } + } + } + + public AgentClassLoader(ClassLoader parent) throws AgentPackageNotFoundException { + super(parent); + File agentDictionary = AgentPackagePath.getPath(); + classpath = new LinkedList<>(); + Config.Plugin.MOUNT.forEach(mountFolder -> classpath.add(new File(agentDictionary, mountFolder))); + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + List allJars = getAllJars(); + String path = name.replace('.', '/').concat(".class"); + for (Jar jar : allJars) { + JarEntry entry = jar.jarFile.getJarEntry(path); + if (entry == null) { + continue; + } + try { + URL classFileUrl = new URL("jar:file:" + jar.sourceFile.getAbsolutePath() + "!/" + path); + byte[] data; + try ( + final BufferedInputStream is = new BufferedInputStream( + classFileUrl.openStream()); + final ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + int ch; + while ((ch = is.read()) != -1) { + baos.write(ch); + } + data = baos.toByteArray(); + } + return processLoadedClass(defineClass(name, data, 0, data.length)); + } catch (IOException e) { + LOGGER.error(e, "find class fail."); + } + } + throw new ClassNotFoundException("Can't find " + name); + } + + @Override + protected URL findResource(String name) { + List allJars = getAllJars(); + for (Jar jar : allJars) { + JarEntry entry = jar.jarFile.getJarEntry(name); + if (entry != null) { + try { + return new URL("jar:file:" + jar.sourceFile.getAbsolutePath() + "!/" + name); + } catch (MalformedURLException ignored) { + } + } + } + return null; + } + + @Override + protected Enumeration findResources(String name) throws IOException { + List allResources = new LinkedList<>(); + List allJars = getAllJars(); + for (Jar jar : allJars) { + JarEntry entry = jar.jarFile.getJarEntry(name); + if (entry != null) { + allResources.add(new URL("jar:file:" + jar.sourceFile.getAbsolutePath() + "!/" + name)); + } + } + + final Iterator iterator = allResources.iterator(); + return new Enumeration() { + + @Override + public boolean hasMoreElements() { + return iterator.hasNext(); + } + + @Override + public URL nextElement() { + return iterator.next(); + } + }; + } + + private Class processLoadedClass(Class loadedClass) { + final PluginConfig pluginConfig = loadedClass.getAnnotation(PluginConfig.class); + if (pluginConfig != null) { + // Set up the plugin config when loaded by class loader at the first time. + // Agent class loader just loaded limited classes in the plugin jar(s), so the cost of this + // isAssignableFrom would be also very limited. + SnifferConfigInitializer.initializeConfig(pluginConfig.root()); + } + + return loadedClass; + } + + private List getAllJars() { + if (allJars == null) { + jarScanLock.lock(); + try { + if (allJars == null) { + allJars = doGetJars(); + } + } finally { + jarScanLock.unlock(); + } + } + + return allJars; + } + + private LinkedList doGetJars() { + LinkedList jars = new LinkedList<>(); + for (File path : classpath) { + if (path.exists() && path.isDirectory()) { + String[] jarFileNames = path.list((dir, name) -> name.endsWith(".jar")); + for (String fileName : jarFileNames) { + try { + File file = new File(path, fileName); + Jar jar = new Jar(new JarFile(file), file); + jars.add(jar); + LOGGER.info("{} loaded.", file.toString()); + } catch (IOException e) { + LOGGER.error(e, "{} jar file can't be resolved", fileName); + } + } + } + } + return jars; + } + + @RequiredArgsConstructor + private static class Jar { + + private final JarFile jarFile; + private final File sourceFile; + } +} \ No newline at end of file diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/loader/InstrumentationLoader.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/loader/InstrumentationLoader.java new file mode 100755 index 00000000..9fa470b4 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/loader/InstrumentationLoader.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.loader; + +import cn.hippo4j.agent.core.plugin.AbstractClassEnhancePluginDefine; + +import java.util.List; + +/** + * the spi of the InstrumentationLoader. + */ + +public interface InstrumentationLoader { + + List load(AgentClassLoader classLoader); +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/loader/InterceptorInstanceLoader.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/loader/InterceptorInstanceLoader.java new file mode 100644 index 00000000..cd1e0352 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/loader/InterceptorInstanceLoader.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.loader; + +import cn.hippo4j.agent.core.boot.AgentPackageNotFoundException; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReentrantLock; + +/** + * The InterceptorInstanceLoader is a classes finder and container. + *

+ * This is a very important class in sky-walking's auto-instrumentation mechanism. If you want to fully understand why + * need this, and how it works, you need have knowledge about Classloader appointment mechanism. + *

+ */ +public class InterceptorInstanceLoader { + + private static ConcurrentHashMap INSTANCE_CACHE = new ConcurrentHashMap(); + private static ReentrantLock INSTANCE_LOAD_LOCK = new ReentrantLock(); + private static Map EXTEND_PLUGIN_CLASSLOADERS = new HashMap(); + + /** + * Load an instance of interceptor, and keep it singleton. Create {@link AgentClassLoader} for each + * targetClassLoader, as an extend classloader. It can load interceptor classes from plugins, activations folders. + * + * @param className the interceptor class, which is expected to be found + * @param targetClassLoader the class loader for current application context + * @param expected type + * @return the type reference. + */ + public static T load(String className, + ClassLoader targetClassLoader) throws IllegalAccessException, InstantiationException, ClassNotFoundException, AgentPackageNotFoundException { + if (targetClassLoader == null) { + targetClassLoader = InterceptorInstanceLoader.class.getClassLoader(); + } + String instanceKey = className + "_OF_" + targetClassLoader.getClass() + .getName() + "@" + + Integer.toHexString(targetClassLoader + .hashCode()); + Object inst = INSTANCE_CACHE.get(instanceKey); + if (inst == null) { + INSTANCE_LOAD_LOCK.lock(); + ClassLoader pluginLoader; + try { + pluginLoader = EXTEND_PLUGIN_CLASSLOADERS.get(targetClassLoader); + if (pluginLoader == null) { + pluginLoader = new AgentClassLoader(targetClassLoader); + EXTEND_PLUGIN_CLASSLOADERS.put(targetClassLoader, pluginLoader); + } + } finally { + INSTANCE_LOAD_LOCK.unlock(); + } + inst = Class.forName(className, true, pluginLoader).newInstance(); + if (inst != null) { + INSTANCE_CACHE.put(instanceKey, inst); + } + } + + return (T) inst; + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/ClassAnnotationMatch.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/ClassAnnotationMatch.java new file mode 100644 index 00000000..7d8f2bd0 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/ClassAnnotationMatch.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.match; + +import net.bytebuddy.description.annotation.AnnotationDescription; +import net.bytebuddy.description.annotation.AnnotationList; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static net.bytebuddy.matcher.ElementMatchers.*; + +/** + * Match the class by the given annotations in class. + */ +public class ClassAnnotationMatch implements IndirectMatch { + + private String[] annotations; + + private ClassAnnotationMatch(String[] annotations) { + if (annotations == null || annotations.length == 0) { + throw new IllegalArgumentException("annotations is null"); + } + this.annotations = annotations; + } + + @Override + public ElementMatcher.Junction buildJunction() { + ElementMatcher.Junction junction = null; + for (String annotation : annotations) { + if (junction == null) { + junction = buildEachAnnotation(annotation); + } else { + junction = junction.and(buildEachAnnotation(annotation)); + } + } + junction = junction.and(not(isInterface())); + return junction; + } + + @Override + public boolean isMatch(TypeDescription typeDescription) { + List annotationList = new ArrayList(Arrays.asList(annotations)); + AnnotationList declaredAnnotations = typeDescription.getDeclaredAnnotations(); + for (AnnotationDescription annotation : declaredAnnotations) { + annotationList.remove(annotation.getAnnotationType().getActualName()); + } + return annotationList.isEmpty(); + } + + private ElementMatcher.Junction buildEachAnnotation(String annotationName) { + return isAnnotatedWith(named(annotationName)); + } + + public static ClassAnnotationMatch byClassAnnotationMatch(String... annotations) { + return new ClassAnnotationMatch(annotations); + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/ClassMatch.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/ClassMatch.java new file mode 100644 index 00000000..750bcf63 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/ClassMatch.java @@ -0,0 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.match; + +public interface ClassMatch { +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/HierarchyMatch.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/HierarchyMatch.java new file mode 100644 index 00000000..31e10d66 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/HierarchyMatch.java @@ -0,0 +1,99 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.match; + +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.description.type.TypeList; +import net.bytebuddy.matcher.ElementMatcher; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static net.bytebuddy.matcher.ElementMatchers.*; + +/** + * Match the class by the given super class or interfaces. + */ +public class HierarchyMatch implements IndirectMatch { + + private String[] parentTypes; + + private HierarchyMatch(String[] parentTypes) { + if (parentTypes == null || parentTypes.length == 0) { + throw new IllegalArgumentException("parentTypes is null"); + } + this.parentTypes = parentTypes; + } + + @Override + public ElementMatcher.Junction buildJunction() { + ElementMatcher.Junction junction = null; + for (String superTypeName : parentTypes) { + if (junction == null) { + junction = buildSuperClassMatcher(superTypeName); + } else { + junction = junction.and(buildSuperClassMatcher(superTypeName)); + } + } + junction = junction.and(not(isInterface())); + return junction; + } + + private ElementMatcher.Junction buildSuperClassMatcher(String superTypeName) { + return hasSuperType(named(superTypeName)); + } + + @Override + public boolean isMatch(TypeDescription typeDescription) { + List parentTypes = new ArrayList(Arrays.asList(this.parentTypes)); + + TypeList.Generic implInterfaces = typeDescription.getInterfaces(); + for (TypeDescription.Generic implInterface : implInterfaces) { + matchHierarchyClass(implInterface, parentTypes); + } + + if (typeDescription.getSuperClass() != null) { + matchHierarchyClass(typeDescription.getSuperClass(), parentTypes); + } + + return parentTypes.size() == 0; + + } + + private void matchHierarchyClass(TypeDescription.Generic clazz, List parentTypes) { + parentTypes.remove(clazz.asRawType().getTypeName()); + if (parentTypes.size() == 0) { + return; + } + + for (TypeDescription.Generic generic : clazz.getInterfaces()) { + matchHierarchyClass(generic, parentTypes); + } + + TypeDescription.Generic superClazz = clazz.getSuperClass(); + if (superClazz != null && !clazz.getTypeName().equals("java.lang.Object")) { + matchHierarchyClass(superClazz, parentTypes); + } + + } + + public static IndirectMatch byHierarchyMatch(String... parentTypes) { + return new HierarchyMatch(parentTypes); + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/IndirectMatch.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/IndirectMatch.java new file mode 100644 index 00000000..a400fdfc --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/IndirectMatch.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.match; + +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +/** + * All implementations can't direct match the class like {@link NameMatch} did. + */ +public interface IndirectMatch extends ClassMatch { + + ElementMatcher.Junction buildJunction(); + + boolean isMatch(TypeDescription typeDescription); +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/MethodAnnotationMatch.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/MethodAnnotationMatch.java new file mode 100644 index 00000000..8bb216fe --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/MethodAnnotationMatch.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.match; + +import net.bytebuddy.description.annotation.AnnotationDescription; +import net.bytebuddy.description.annotation.AnnotationList; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; +import net.bytebuddy.matcher.ElementMatchers; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static net.bytebuddy.matcher.ElementMatchers.*; + +/** + * Match the class, which has methods with the certain annotations. This is a very complex match. + */ +public class MethodAnnotationMatch implements IndirectMatch { + + private String[] annotations; + + private MethodAnnotationMatch(String[] annotations) { + if (annotations == null || annotations.length == 0) { + throw new IllegalArgumentException("annotations is null"); + } + this.annotations = annotations; + } + + @Override + public ElementMatcher.Junction buildJunction() { + ElementMatcher.Junction junction = null; + for (String annotation : annotations) { + if (junction == null) { + junction = buildEachAnnotation(annotation); + } else { + junction = junction.and(buildEachAnnotation(annotation)); + } + } + junction = declaresMethod(junction).and(ElementMatchers.not(isInterface())); + return junction; + } + + @Override + public boolean isMatch(TypeDescription typeDescription) { + for (MethodDescription.InDefinedShape methodDescription : typeDescription.getDeclaredMethods()) { + List annotationList = new ArrayList(Arrays.asList(annotations)); + + AnnotationList declaredAnnotations = methodDescription.getDeclaredAnnotations(); + for (AnnotationDescription annotation : declaredAnnotations) { + annotationList.remove(annotation.getAnnotationType().getActualName()); + } + if (annotationList.isEmpty()) { + return true; + } + } + + return false; + } + + private ElementMatcher.Junction buildEachAnnotation(String annotationName) { + return isAnnotatedWith(named(annotationName)); + } + + public static IndirectMatch byMethodAnnotationMatch(String... annotations) { + return new MethodAnnotationMatch(annotations); + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/MethodInheritanceAnnotationMatcher.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/MethodInheritanceAnnotationMatcher.java new file mode 100644 index 00000000..94fd02ae --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/MethodInheritanceAnnotationMatcher.java @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.match; + +import net.bytebuddy.build.HashCodeAndEqualsPlugin; +import net.bytebuddy.description.annotation.AnnotationList; +import net.bytebuddy.description.annotation.AnnotationSource; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.method.MethodList; +import net.bytebuddy.description.method.ParameterList; +import net.bytebuddy.description.type.TypeDefinition; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.description.type.TypeList; +import net.bytebuddy.matcher.CollectionItemMatcher; +import net.bytebuddy.matcher.ElementMatcher; + +import java.util.Objects; + +import static net.bytebuddy.matcher.ElementMatchers.annotationType; + +/** + * Matching used to match method annotations, Can match annotations on interface methods + */ +@HashCodeAndEqualsPlugin.Enhance +public class MethodInheritanceAnnotationMatcher extends ElementMatcher.Junction.AbstractBase { + + /** + * The matcher to be applied to the provided annotation list. + */ + private final ElementMatcher matcher; + + /** + * Creates a new matcher for the annotations of an annotated element. + * + * @param matcher The matcher to be applied to the provided annotation list. + */ + public MethodInheritanceAnnotationMatcher(ElementMatcher matcher) { + this.matcher = matcher; + } + + @Override + public boolean matches(T target) { + if (matcher.matches(target.getDeclaredAnnotations())) { + return true; + } + String name = target.getName(); + ParameterList parameters = target.getParameters(); + + TypeDefinition declaringType = target.getDeclaringType(); + return recursiveMatches(declaringType, name, parameters); + } + + private boolean recursiveMatches(TypeDefinition typeDefinition, String methodName, ParameterList parameters) { + TypeList.Generic interfaces = typeDefinition.getInterfaces(); + for (TypeDescription.Generic implInterface : interfaces) { + if (recursiveMatches(implInterface, methodName, parameters)) { + return true; + } + MethodList declaredMethods = implInterface.getDeclaredMethods(); + for (MethodDescription declaredMethod : declaredMethods) { + if (Objects.equals(declaredMethod.getName(), methodName) && parameterEquals(parameters, declaredMethod.getParameters())) { + return matcher.matches(declaredMethod.getDeclaredAnnotations()); + } + } + } + return false; + } + + private boolean parameterEquals(ParameterList source, ParameterList impl) { + if (source.size() != impl.size()) { + return false; + } + for (int i = 0; i < source.size(); i++) { + if (!Objects.equals(source.get(i).getType(), impl.get(i).getType())) { + return false; + } + } + return true; + } + + public static Junction byMethodInheritanceAnnotationMatcher( + ElementMatcher matcher) { + return new MethodInheritanceAnnotationMatcher(new CollectionItemMatcher<>(annotationType(matcher))); + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/MultiClassNameMatch.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/MultiClassNameMatch.java new file mode 100644 index 00000000..8ebeba25 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/MultiClassNameMatch.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.match; + +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +import java.util.Arrays; +import java.util.List; + +import static net.bytebuddy.matcher.ElementMatchers.named; + +/** + * Match class with a given set of classes. + */ +public class MultiClassNameMatch implements IndirectMatch { + + private List matchClassNames; + + private MultiClassNameMatch(String[] classNames) { + if (classNames == null || classNames.length == 0) { + throw new IllegalArgumentException("match class names is null"); + } + this.matchClassNames = Arrays.asList(classNames); + } + + @Override + public ElementMatcher.Junction buildJunction() { + ElementMatcher.Junction junction = null; + for (String name : matchClassNames) { + if (junction == null) { + junction = named(name); + } else { + junction = junction.or(named(name)); + } + } + return junction; + } + + @Override + public boolean isMatch(TypeDescription typeDescription) { + return matchClassNames.contains(typeDescription.getTypeName()); + } + + public static IndirectMatch byMultiClassMatch(String... classNames) { + return new MultiClassNameMatch(classNames); + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/NameMatch.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/NameMatch.java new file mode 100644 index 00000000..2dd9b11d --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/NameMatch.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.match; + +/** + * Match the class with an explicit class name. + */ +public class NameMatch implements ClassMatch { + + private String className; + + private NameMatch(String className) { + this.className = className; + } + + public String getClassName() { + return className; + } + + public static NameMatch byName(String className) { + return new NameMatch(className); + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/PrefixMatch.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/PrefixMatch.java new file mode 100644 index 00000000..d47d0409 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/PrefixMatch.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.match; + +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; +import net.bytebuddy.matcher.ElementMatchers; + +/** + * Match classes by any one of the given {@link #prefixes} + */ +@SuppressWarnings("rawtypes") +public class PrefixMatch implements IndirectMatch { + + private String[] prefixes; + + private PrefixMatch(String... prefixes) { + if (prefixes == null || prefixes.length == 0) { + throw new IllegalArgumentException("prefixes argument is null or empty"); + } + this.prefixes = prefixes; + } + + @Override + public ElementMatcher.Junction buildJunction() { + ElementMatcher.Junction junction = null; + + for (String prefix : prefixes) { + if (junction == null) { + junction = ElementMatchers.nameStartsWith(prefix); + } else { + junction = junction.or(ElementMatchers.nameStartsWith(prefix)); + } + } + + return junction; + } + + @Override + public boolean isMatch(TypeDescription typeDescription) { + for (final String prefix : prefixes) { + if (typeDescription.getName().startsWith(prefix)) { + return true; + } + } + return false; + } + + public static PrefixMatch nameStartsWith(final String... prefixes) { + return new PrefixMatch(prefixes); + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/ProtectiveShieldMatcher.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/ProtectiveShieldMatcher.java new file mode 100644 index 00000000..525cee21 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/ProtectiveShieldMatcher.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.match; + +import cn.hippo4j.agent.core.logging.api.ILog; +import cn.hippo4j.agent.core.logging.api.LogManager; +import net.bytebuddy.matcher.ElementMatcher; + +/** + * In some cases, some frameworks and libraries use some binary codes tech too. From the community feedback, some of + * them have compatible issues with byte-buddy core, which trigger "Can't resolve type description" exception. + *

+ * So I build this protective shield by a nested matcher. When the origin matcher(s) can't resolve the type, the + * SkyWalking agent ignores this types. + *

+ * Notice: this ignore mechanism may miss some instrumentations, but at most cases, it's same. If missing happens, + * please pay attention to the WARNING logs. + */ +public class ProtectiveShieldMatcher extends ElementMatcher.Junction.AbstractBase { + + private static final ILog LOGGER = LogManager.getLogger(ProtectiveShieldMatcher.class); + + private final ElementMatcher matcher; + + public ProtectiveShieldMatcher(ElementMatcher matcher) { + this.matcher = matcher; + } + + @Override + public boolean matches(T target) { + try { + return this.matcher.matches(target); + } catch (Throwable t) { + if (LOGGER.isDebugEnable()) { + LOGGER.debug(t, "Byte-buddy occurs exception when match type."); + } + return false; + } + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/RegexMatch.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/RegexMatch.java new file mode 100644 index 00000000..e3310b24 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/RegexMatch.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.match; + +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +import static net.bytebuddy.matcher.ElementMatchers.nameMatches; + +/** + * Match the class by given class name regex expression. + */ +public class RegexMatch implements IndirectMatch { + + private String[] regexExpressions; + + private RegexMatch(String... regexExpressions) { + if (regexExpressions == null || regexExpressions.length == 0) { + throw new IllegalArgumentException("annotations is null"); + } + this.regexExpressions = regexExpressions; + } + + @Override + public ElementMatcher.Junction buildJunction() { + ElementMatcher.Junction regexJunction = null; + for (String regexExpression : regexExpressions) { + if (regexJunction == null) { + regexJunction = nameMatches(regexExpression); + } else { + regexJunction = regexJunction.or(nameMatches(regexExpression)); + } + } + return regexJunction; + } + + @Override + public boolean isMatch(TypeDescription typeDescription) { + boolean isMatch = false; + for (String matchExpression : regexExpressions) { + isMatch = typeDescription.getTypeName().matches(matchExpression); + if (isMatch) { + break; + } + } + return isMatch; + } + + public static RegexMatch byRegexMatch(String... regexExpressions) { + return new RegexMatch(regexExpressions); + } +} \ No newline at end of file diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/logical/LogicalAndMatch.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/logical/LogicalAndMatch.java new file mode 100644 index 00000000..8f5f05f5 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/logical/LogicalAndMatch.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.match.logical; + +import cn.hippo4j.agent.core.plugin.match.IndirectMatch; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +/** + * Match classes by multiple criteria with AND conjunction + */ +public class LogicalAndMatch implements IndirectMatch { + + private final IndirectMatch[] indirectMatches; + + /** + * Don't instantiate this class directly, use {@link LogicalMatchOperation} instead + * + * @param indirectMatches the matching criteria to conjunct with AND + */ + LogicalAndMatch(final IndirectMatch... indirectMatches) { + this.indirectMatches = indirectMatches; + } + + @Override + public ElementMatcher.Junction buildJunction() { + ElementMatcher.Junction junction = null; + + for (final IndirectMatch indirectMatch : indirectMatches) { + if (junction == null) { + junction = indirectMatch.buildJunction(); + } else { + junction = junction.and(indirectMatch.buildJunction()); + } + } + + return junction; + } + + @Override + public boolean isMatch(final TypeDescription typeDescription) { + for (final IndirectMatch indirectMatch : indirectMatches) { + if (!indirectMatch.isMatch(typeDescription)) { + return false; + } + } + + return true; + } + +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/logical/LogicalMatchOperation.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/logical/LogicalMatchOperation.java new file mode 100644 index 00000000..ee19f5b0 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/logical/LogicalMatchOperation.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.match.logical; + +import cn.hippo4j.agent.core.plugin.match.IndirectMatch; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; +import net.bytebuddy.matcher.NegatingMatcher; + +/** + * Util class to help to construct logical operations on {@link org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch}s + */ +public class LogicalMatchOperation { + + public static IndirectMatch and(final IndirectMatch... matches) { + return new LogicalAndMatch(matches); + } + + public static IndirectMatch or(final IndirectMatch... matches) { + return new LogicalOrMatch(matches); + } + + public static IndirectMatch not(final IndirectMatch match) { + return new IndirectMatch() { + + @Override + public ElementMatcher.Junction buildJunction() { + return new NegatingMatcher(match.buildJunction()); + } + + @Override + public boolean isMatch(final TypeDescription typeDescription) { + return !match.isMatch(typeDescription); + } + }; + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/logical/LogicalOrMatch.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/logical/LogicalOrMatch.java new file mode 100644 index 00000000..35549a3a --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/plugin/match/logical/LogicalOrMatch.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.plugin.match.logical; + +import cn.hippo4j.agent.core.plugin.match.IndirectMatch; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +/** + * Match classes by multiple criteria with OR conjunction + */ +public class LogicalOrMatch implements IndirectMatch { + + private final IndirectMatch[] indirectMatches; + + /** + * Don't instantiate this class directly, use {@link LogicalMatchOperation} instead + * + * @param indirectMatches the matching criteria to conjunct with OR + */ + LogicalOrMatch(final IndirectMatch... indirectMatches) { + this.indirectMatches = indirectMatches; + } + + @Override + public ElementMatcher.Junction buildJunction() { + ElementMatcher.Junction junction = null; + + for (final IndirectMatch indirectMatch : indirectMatches) { + if (junction == null) { + junction = indirectMatch.buildJunction(); + } else { + junction = junction.or(indirectMatch.buildJunction()); + } + } + + return junction; + } + + @Override + public boolean isMatch(final TypeDescription typeDescription) { + for (final IndirectMatch indirectMatch : indirectMatches) { + if (indirectMatch.isMatch(typeDescription)) { + return true; + } + } + + return false; + } + +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/util/CollectionUtil.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/util/CollectionUtil.java new file mode 100644 index 00000000..0037ae76 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/util/CollectionUtil.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.util; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Some utility methods for collections. Reinvent the wheels because importing third-party libs just for some methods is + * not worthwhile in agent side + * + * @since 7.0.0 + */ +public final class CollectionUtil { + + public static String toString(final Map map) { + return map.entrySet() + .stream() + .map(entry -> entry.getKey() + "=" + Arrays.toString(entry.getValue())) + .collect(Collectors.joining("\n")); + } + + @SuppressWarnings("rawtypes") + public static boolean isEmpty(Collection collection) { + return collection == null || collection.isEmpty(); + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/util/ConfigInitializer.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/util/ConfigInitializer.java new file mode 100644 index 00000000..3bb87ccd --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/util/ConfigInitializer.java @@ -0,0 +1,202 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.util; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.*; + +/** + * Init a class's static fields by a {@link Properties}, including static fields and static inner classes. + *

+ */ +public class ConfigInitializer { + + public static void initialize(Properties properties, Class rootConfigType) throws IllegalAccessException { + initNextLevel(properties, rootConfigType, new ConfigDesc()); + } + + private static void initNextLevel(Properties properties, Class recentConfigType, + ConfigDesc parentDesc) throws IllegalArgumentException, IllegalAccessException { + for (Field field : recentConfigType.getFields()) { + if (Modifier.isPublic(field.getModifiers()) && Modifier.isStatic(field.getModifiers())) { + String configKey = (parentDesc + "." + field.getName()).toLowerCase(); + Class type = field.getType(); + + if (type.equals(Map.class)) { + /* + * Map config format is, config_key[map_key]=map_value Such as plugin.opgroup.resttemplate.rule[abc]=/url/path + */ + // Deduct two generic types of the map + ParameterizedType genericType = (ParameterizedType) field.getGenericType(); + Type[] argumentTypes = genericType.getActualTypeArguments(); + + Type keyType = null; + Type valueType = null; + if (argumentTypes != null && argumentTypes.length == 2) { + // Get key type and value type of the map + keyType = argumentTypes[0]; + valueType = argumentTypes[1]; + } + Map map = (Map) field.get(null); + // Set the map from config key and properties + setForMapType(configKey, map, properties, keyType, valueType); + } else { + /* + * Others typical field type + */ + String value = properties.getProperty(configKey); + // Convert the value into real type + final Length lengthDefine = field.getAnnotation(Length.class); + if (lengthDefine != null) { + if (value != null && value.length() > lengthDefine.value()) { + value = value.substring(0, lengthDefine.value()); + } + } + Object convertedValue = convertToTypicalType(type, value); + if (convertedValue != null) { + field.set(null, convertedValue); + } + } + } + } + for (Class innerConfiguration : recentConfigType.getClasses()) { + parentDesc.append(innerConfiguration.getSimpleName()); + initNextLevel(properties, innerConfiguration, parentDesc); + parentDesc.removeLastDesc(); + } + } + + /** + * Convert string value to typical type. + * + * @param type type to convert + * @param value string value to be converted + * @return converted value or null + */ + private static Object convertToTypicalType(Type type, String value) { + if (value == null || type == null) { + return null; + } + + Object result = null; + if (String.class.equals(type)) { + result = value; + } else if (int.class.equals(type) || Integer.class.equals(type)) { + result = Integer.valueOf(value); + } else if (long.class.equals(type) || Long.class.equals(type)) { + result = Long.valueOf(value); + } else if (boolean.class.equals(type) || Boolean.class.equals(type)) { + result = Boolean.valueOf(value); + } else if (float.class.equals(type) || Float.class.equals(type)) { + result = Float.valueOf(value); + } else if (double.class.equals(type) || Double.class.equals(type)) { + result = Double.valueOf(value); + } else if (List.class.equals(type)) { + result = convert2List(value); + } else if (type instanceof Class) { + Class clazz = (Class) type; + if (clazz.isEnum()) { + result = Enum.valueOf((Class) type, value.toUpperCase()); + } + } + return result; + } + + /** + * Set map items. + * + * @param configKey config key must not be null + * @param map map to set must not be null + * @param properties properties must not be null + * @param keyType key type of the map + * @param valueType value type of the map + */ + private static void setForMapType(String configKey, Map map, Properties properties, + final Type keyType, final Type valueType) { + + Objects.requireNonNull(configKey); + Objects.requireNonNull(map); + Objects.requireNonNull(properties); + + String prefix = configKey + "["; + String suffix = "]"; + + properties.forEach((propertyKey, propertyValue) -> { + String propertyStringKey = propertyKey.toString(); + if (propertyStringKey.startsWith(prefix) && propertyStringKey.endsWith(suffix)) { + String itemKey = propertyStringKey.substring( + prefix.length(), propertyStringKey.length() - suffix.length()); + Object keyObj; + Object valueObj; + + keyObj = convertToTypicalType(keyType, itemKey); + valueObj = convertToTypicalType(valueType, propertyValue.toString()); + + if (keyObj == null) { + keyObj = itemKey; + } + + if (valueObj == null) { + valueObj = propertyValue; + } + + map.put(keyObj, valueObj); + } + }); + } + + private static List convert2List(String value) { + if (StringUtil.isEmpty(value)) { + return Collections.emptyList(); + } + List result = new LinkedList<>(); + + String[] segments = value.split(","); + for (String segment : segments) { + String trimmedSegment = segment.trim(); + if (StringUtil.isNotEmpty(trimmedSegment)) { + result.add(trimmedSegment); + } + } + return result; + } + +} + +class ConfigDesc { + + private LinkedList descs = new LinkedList<>(); + + void append(String currentDesc) { + if (StringUtil.isNotEmpty(currentDesc)) { + descs.addLast(currentDesc); + } + } + + void removeLastDesc() { + descs.removeLast(); + } + + @Override + public String toString() { + return String.join(".", descs); + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/util/CustomizeExpression.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/util/CustomizeExpression.java new file mode 100644 index 00000000..4912936b --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/util/CustomizeExpression.java @@ -0,0 +1,120 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.util; + +import cn.hippo4j.agent.core.logging.api.ILog; +import cn.hippo4j.agent.core.logging.api.LogManager; + +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * a simple parsing expression + */ + +public class CustomizeExpression { + + private static final ILog LOGGER = LogManager.getLogger(CustomizeExpression.class); + + public static Map evaluationContext(Object[] allArguments) { + Map context = new HashMap<>(); + if (allArguments == null) { + return context; + } + for (int i = 0; i < allArguments.length; i++) { + context.put("arg[" + i + "]", allArguments[i]); + } + return context; + } + + public static Map evaluationReturnContext(Object ret) { + Map context = new HashMap<>(); + context.put("returnedObj", ret); + return context; + } + + public static String parseExpression(String expression, Map context) { + try { + String[] es = expression.split("\\."); + Object o = context.get(es[0]); + return o == null ? "null" : String.valueOf(parse(es, o, 0)); + } catch (Exception e) { + LOGGER.debug("parse expression error, expression is {}, exception is {}", expression, e.getMessage()); + } + return "null"; + } + + private static Object parse(String[] expressions, Object o, int i) { + int next = i + 1; + if (next == expressions.length) { + return o; + } else { + o = parse0(expressions[next], o); + return o == null ? "null" : parse(expressions, o, next); + } + } + + private static Object parse0(String expression, Object o) { + if (o instanceof Map) { + return matcherMap(expression, o); + } else if (o instanceof List) { + return matcherList(expression, o); + } else if (o.getClass().isArray()) { + return matcherArray(expression, o); + } else { + return matcherDefault(expression, o); + } + } + + private static Object matcherMap(String expression, Object o) { + String key = expression.replace("['", "").replace("']", ""); + return ((Map) o).get(key); + } + + private static Object matcherList(String expression, Object o) { + int index = Integer.parseInt(expression.replace("[", "").replace("]", "")); + List l = (List) o; + return l != null && l.size() > index ? l.get(index) : null; + } + + private static Object matcherArray(String expression, Object o) { + int index = Integer.parseInt(expression.replace("[", "").replace("]", "")); + return o != null && Array.getLength(o) > index ? Array.get(o, index) : null; + } + + private static Object matcherDefault(String expression, Object o) { + try { + if (expression.contains("()")) { + Method m = o.getClass().getMethod(expression.replace("()", "")); + m.setAccessible(true); + return m.invoke(o); + } else { + Field f = o.getClass().getDeclaredField(expression); + f.setAccessible(true); + return f.get(o); + } + } catch (Exception e) { + LOGGER.debug("matcher default error, expression is {}, object is {}, expression is {}", expression, o, e.getMessage()); + } + return null; + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/util/FileUtils.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/util/FileUtils.java new file mode 100644 index 00000000..2d954dda --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/util/FileUtils.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.util; + +import java.io.File; +import java.nio.file.Files; + +public class FileUtils { + + /** + * delete directories and files recursively + * + * @param dir directory to delete + */ + public static void deleteDirectory(File dir) { + File[] files = dir.listFiles(); + if (files != null) { + for (File file : files) { + if (!Files.isSymbolicLink(file.toPath())) { + if (file.isDirectory()) { + deleteDirectory(file); + } else { + file.delete(); + } + } else { + file.delete(); + } + } + } + dir.delete(); + } + + public static void deleteDirectoryOnExit(File dir) { + Runtime.getRuntime().addShutdownHook(new Thread() { + + @Override + public void run() { + FileUtils.deleteDirectory(dir); + } + }); + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/util/IOUtils.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/util/IOUtils.java new file mode 100644 index 00000000..30ac27b7 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/util/IOUtils.java @@ -0,0 +1,141 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.util; + +import java.io.*; + +/** + * Copied from commons-io-2.2 (org.apache.commons.io.IOUtils) + * Origin license: http://www.apache.org/licenses/LICENSE-2.0 + * @version $Id: IOUtils.java 1304177 2012-03-23 03:36:44Z ggregory $ + */ +public class IOUtils { + + private static final int EOF = -1; + + /** + * The default buffer size ({@value}) to use for + * {@link #copyLarge(InputStream, OutputStream)} + */ + private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; + + /** + * Get the contents of an InputStream as a byte[]. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + * + * @param input the InputStream to read from + * @return the requested byte array + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs + */ + public static byte[] toByteArray(InputStream input) throws IOException { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + copy(input, output); + return output.toByteArray(); + } + + /** + * Copy bytes from an InputStream to an + * OutputStream. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + *

+ * Large streams (over 2GB) will return a bytes copied value of + * -1 after the copy has completed since the correct + * number of bytes cannot be returned as an int. For large streams + * use the copyLarge(InputStream, OutputStream) method. + * + * @param input the InputStream to read from + * @param output the OutputStream to write to + * @return the number of bytes copied, or -1 if > Integer.MAX_VALUE + * @throws NullPointerException if the input or output is null + * @throws IOException if an I/O error occurs + * @since 1.1 + */ + public static int copy(InputStream input, OutputStream output) throws IOException { + long count = copyLarge(input, output); + if (count > Integer.MAX_VALUE) { + return -1; + } + return (int) count; + } + + /** + * Copy bytes from a large (over 2GB) InputStream to an + * OutputStream. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + *

+ * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. + * + * @param input the InputStream to read from + * @param output the OutputStream to write to + * @return the number of bytes copied + * @throws NullPointerException if the input or output is null + * @throws IOException if an I/O error occurs + * @since 1.3 + */ + public static long copyLarge(InputStream input, OutputStream output) throws IOException { + return copyLarge(input, output, new byte[DEFAULT_BUFFER_SIZE]); + } + + /** + * Copy bytes from a large (over 2GB) InputStream to an + * OutputStream. + *

+ * This method uses the provided buffer, so there is no need to use a + * BufferedInputStream. + *

+ * + * @param input the InputStream to read from + * @param output the OutputStream to write to + * @param buffer the buffer to use for the copy + * @return the number of bytes copied + * @throws NullPointerException if the input or output is null + * @throws IOException if an I/O error occurs + * @since 2.2 + */ + public static long copyLarge(InputStream input, OutputStream output, byte[] buffer) throws IOException { + long count = 0; + int n = 0; + while (EOF != (n = input.read(buffer))) { + output.write(buffer, 0, n); + count += n; + } + return count; + } + + /** + * close streams + * @param closeable the closeable handler + */ + public static void closeQuietly(Closeable closeable) { + try { + if (closeable != null) { + closeable.close(); + } + } catch (IOException ex) { + // ignore ex + } + } + +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/util/Length.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/util/Length.java new file mode 100644 index 00000000..4226d4d7 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/util/Length.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.util; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The length rule of the target field. + */ +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Length { + + int value(); +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/util/MethodUtil.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/util/MethodUtil.java new file mode 100644 index 00000000..bd7e13f6 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/util/MethodUtil.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.util; + +import java.lang.reflect.Method; + +/** + * According to the input parameter, return the OperationName for the span record, It can determine the unique method + */ + +public class MethodUtil { + + public static String generateOperationName(Method method) { + StringBuilder operationName = new StringBuilder(method.getDeclaringClass() + .getName() + "." + method.getName() + "("); + Class[] parameterTypes = method.getParameterTypes(); + for (int i = 0; i < parameterTypes.length; i++) { + operationName.append(parameterTypes[i].getName()); + if (i < (parameterTypes.length - 1)) { + operationName.append(","); + } + } + operationName.append(")"); + return operationName.toString(); + } + + /** + * This is a low-performance method, recommend to use this when have to, make sure it is only executed once and the + * result is being cached. + */ + public static boolean isMethodExist(ClassLoader classLoader, String className, String methodName, + String... parameterTypes) { + try { + Class clazz = Class.forName(className, true, classLoader); + if (parameterTypes == null || parameterTypes.length == 0) { + clazz.getDeclaredMethod(methodName); + return true; + } else { + Method[] declaredMethods = clazz.getDeclaredMethods(); + for (Method declaredMethod : declaredMethods) { + if (declaredMethod.getName().equals(methodName) + && isParameterTypesEquals(declaredMethod.getParameterTypes(), parameterTypes)) { + return true; + } + } + } + } catch (Exception e) { + // ignore + } + return false; + } + + private static boolean isParameterTypesEquals(Class[] parameterTypeClazz, String[] parameterTypeString) { + if (parameterTypeClazz == null) { + return false; + } + if (parameterTypeClazz.length != parameterTypeString.length) { + return false; + } + for (int i = 0; i < parameterTypeClazz.length; i++) { + if (!parameterTypeClazz[i].getName().equals(parameterTypeString[i])) { + return false; + } + } + return true; + + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/util/PlaceholderConfigurerSupport.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/util/PlaceholderConfigurerSupport.java new file mode 100644 index 00000000..3b2f6ffd --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/util/PlaceholderConfigurerSupport.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.util; + +public class PlaceholderConfigurerSupport { + + /** + * Default placeholder prefix: {@value} + */ + public static final String DEFAULT_PLACEHOLDER_PREFIX = "${"; + + /** + * Default placeholder suffix: {@value} + */ + public static final String DEFAULT_PLACEHOLDER_SUFFIX = "}"; + + /** + * Default value separator: {@value} + */ + public static final String DEFAULT_VALUE_SEPARATOR = ":"; + +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/util/PrivateKeyUtil.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/util/PrivateKeyUtil.java new file mode 100644 index 00000000..48df0f19 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/util/PrivateKeyUtil.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.util; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Base64; + +/** + * Util intends to parse PKCS#1 and PKCS#8 at same time. + */ +public class PrivateKeyUtil { + + private static final String PKCS_1_PEM_HEADER = "-----BEGIN RSA PRIVATE KEY-----"; + private static final String PKCS_1_PEM_FOOTER = "-----END RSA PRIVATE KEY-----"; + private static final String PKCS_8_PEM_HEADER = "-----BEGIN PRIVATE KEY-----"; + private static final String PKCS_8_PEM_FOOTER = "-----END PRIVATE KEY-----"; + + /** + * Load a RSA decryption key from a file (PEM or DER). + */ + public static InputStream loadDecryptionKey(String keyFilePath) throws IOException { + byte[] keyDataBytes = Files.readAllBytes(Paths.get(keyFilePath)); + String keyDataString = new String(keyDataBytes, StandardCharsets.UTF_8); + + if (keyDataString.contains(PKCS_1_PEM_HEADER)) { + // OpenSSL / PKCS#1 Base64 PEM encoded file + keyDataString = keyDataString.replace(PKCS_1_PEM_HEADER, ""); + keyDataString = keyDataString.replace(PKCS_1_PEM_FOOTER, ""); + keyDataString = keyDataString.replace("\n", ""); + return readPkcs1PrivateKey(Base64.getDecoder().decode(keyDataString)); + } + + return new ByteArrayInputStream(keyDataString.getBytes()); + } + + /** + * Create a InputStream instance from raw PKCS#1 bytes. Raw Java API can't recognize ASN.1 format, so we should + * convert it into a pkcs#8 format Java can understand. + */ + private static InputStream readPkcs1PrivateKey(byte[] pkcs1Bytes) { + int pkcs1Length = pkcs1Bytes.length; + int totalLength = pkcs1Length + 22; + byte[] pkcs8Header = new byte[]{ + 0x30, (byte) 0x82, (byte) ((totalLength >> 8) & 0xff), (byte) (totalLength & 0xff), // Sequence + total length + 0x2, 0x1, 0x0, // Integer (0) + 0x30, 0xD, 0x6, 0x9, 0x2A, (byte) 0x86, 0x48, (byte) 0x86, (byte) 0xF7, 0xD, 0x1, 0x1, 0x1, 0x5, 0x0, // Sequence: 1.2.840.113549.1.1.1, NULL + 0x4, (byte) 0x82, (byte) ((pkcs1Length >> 8) & 0xff), (byte) (pkcs1Length & 0xff) // Octet string + length + }; + StringBuilder pkcs8 = new StringBuilder(PKCS_8_PEM_HEADER); + pkcs8.append("\n").append(new String(Base64.getEncoder().encode(join(pkcs8Header, pkcs1Bytes)))); + pkcs8.append("\n").append(PKCS_8_PEM_FOOTER); + return new ByteArrayInputStream(pkcs8.toString().getBytes()); + } + + private static byte[] join(byte[] byteArray1, byte[] byteArray2) { + byte[] bytes = new byte[byteArray1.length + byteArray2.length]; + System.arraycopy(byteArray1, 0, bytes, 0, byteArray1.length); + System.arraycopy(byteArray2, 0, bytes, byteArray1.length, byteArray2.length); + return bytes; + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/util/PropertyPlaceholderHelper.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/util/PropertyPlaceholderHelper.java new file mode 100644 index 00000000..2b670c49 --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/util/PropertyPlaceholderHelper.java @@ -0,0 +1,204 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.util; + +import java.util.*; + +/** + * Utility class for working with Strings that have placeholder values in them. A placeholder takes the form {@code + * ${name}}. Using {@code PropertyPlaceholderHelper} these placeholders can be substituted for user-supplied values.

+ * Values for substitution can be supplied using a {@link Properties} instance or using a {@link PlaceholderResolver}. + */ +public enum PropertyPlaceholderHelper { + + INSTANCE( + PlaceholderConfigurerSupport.DEFAULT_PLACEHOLDER_PREFIX, + PlaceholderConfigurerSupport.DEFAULT_PLACEHOLDER_SUFFIX, PlaceholderConfigurerSupport.DEFAULT_VALUE_SEPARATOR, + true); + + private final String placeholderPrefix; + + private final String placeholderSuffix; + + private final String simplePrefix; + + private final String valueSeparator; + + private final boolean ignoreUnresolvablePlaceholders; + + /** + * Creates a new {@code PropertyPlaceholderHelper} that uses the supplied prefix and suffix. + * + * @param placeholderPrefix the prefix that denotes the start of a placeholder + * @param placeholderSuffix the suffix that denotes the end of a placeholder + * @param valueSeparator the separating character between the placeholder variable and the + * associated default value, if any + * @param ignoreUnresolvablePlaceholders indicates whether unresolvable placeholders should be ignored ({@code + * true}) or cause an exception ({@code false}) + */ + PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix, String valueSeparator, + boolean ignoreUnresolvablePlaceholders) { + if (StringUtil.isEmpty(placeholderPrefix) || StringUtil.isEmpty(placeholderSuffix)) { + throw new UnsupportedOperationException("'placeholderPrefix or placeholderSuffix' must not be null"); + } + + final Map wellKnownSimplePrefixes = new HashMap(4); + + wellKnownSimplePrefixes.put("}", "{"); + wellKnownSimplePrefixes.put("]", "["); + wellKnownSimplePrefixes.put(")", "("); + + this.placeholderPrefix = placeholderPrefix; + this.placeholderSuffix = placeholderSuffix; + String simplePrefixForSuffix = wellKnownSimplePrefixes.get(this.placeholderSuffix); + if (simplePrefixForSuffix != null && this.placeholderPrefix.endsWith(simplePrefixForSuffix)) { + this.simplePrefix = simplePrefixForSuffix; + } else { + this.simplePrefix = this.placeholderPrefix; + } + this.valueSeparator = valueSeparator; + this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders; + } + + /** + * Replaces all placeholders of format {@code ${name}} with the corresponding property from the supplied {@link + * Properties}. + * + * @param value the value containing the placeholders to be replaced + * @param properties the {@code Properties} to use for replacement + * @return the supplied value with placeholders replaced inline + */ + public String replacePlaceholders(String value, final Properties properties) { + return replacePlaceholders(value, new PlaceholderResolver() { + + @Override + public String resolvePlaceholder(String placeholderName) { + return getConfigValue(placeholderName, properties); + } + }); + } + + private String getConfigValue(String key, final Properties properties) { + String value = System.getProperty(key); + if (value == null) { + value = System.getenv(key); + } + if (value == null) { + value = properties.getProperty(key); + } + return value; + } + + /** + * Replaces all placeholders of format {@code ${name}} with the value returned from the supplied {@link + * PlaceholderResolver}. + * + * @param value the value containing the placeholders to be replaced + * @param placeholderResolver the {@code PlaceholderResolver} to use for replacement + * @return the supplied value with placeholders replaced inline + */ + public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) { + return parseStringValue(value, placeholderResolver, new HashSet()); + } + + protected String parseStringValue(String value, PlaceholderResolver placeholderResolver, + Set visitedPlaceholders) { + + StringBuilder result = new StringBuilder(value); + + int startIndex = value.indexOf(this.placeholderPrefix); + while (startIndex != -1) { + int endIndex = findPlaceholderEndIndex(result, startIndex); + if (endIndex != -1) { + String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex); + String originalPlaceholder = placeholder; + if (!visitedPlaceholders.add(originalPlaceholder)) { + throw new IllegalArgumentException( + "Circular placeholder reference '" + originalPlaceholder + "' in property definitions"); + } + // Recursive invocation, parsing placeholders contained in the placeholder key. + placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders); + // Now obtain the value for the fully resolved key... + String propVal = placeholderResolver.resolvePlaceholder(placeholder); + if (propVal == null && this.valueSeparator != null) { + int separatorIndex = placeholder.indexOf(this.valueSeparator); + if (separatorIndex != -1) { + String actualPlaceholder = placeholder.substring(0, separatorIndex); + String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length()); + propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder); + if (propVal == null) { + propVal = defaultValue; + } + } + } + if (propVal != null) { + // Recursive invocation, parsing placeholders contained in the + // previously resolved placeholder value. + propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders); + result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal); + startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length()); + } else if (this.ignoreUnresolvablePlaceholders) { + // Proceed with unprocessed value. + startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length()); + } else { + throw new IllegalArgumentException( + "Could not resolve placeholder '" + placeholder + "'" + " in value \"" + value + "\""); + } + visitedPlaceholders.remove(originalPlaceholder); + } else { + startIndex = -1; + } + } + return result.toString(); + } + + private int findPlaceholderEndIndex(CharSequence buf, int startIndex) { + int index = startIndex + this.placeholderPrefix.length(); + int withinNestedPlaceholder = 0; + while (index < buf.length()) { + if (StringUtil.substringMatch(buf, index, this.placeholderSuffix)) { + if (withinNestedPlaceholder > 0) { + withinNestedPlaceholder--; + index = index + this.placeholderSuffix.length(); + } else { + return index; + } + } else if (StringUtil.substringMatch(buf, index, this.simplePrefix)) { + withinNestedPlaceholder++; + index = index + this.simplePrefix.length(); + } else { + index++; + } + } + return -1; + } + + /** + * Strategy interface used to resolve replacement values for placeholders contained in Strings. + */ + public interface PlaceholderResolver { + + /** + * Resolve the supplied placeholder name to the replacement value. + * + * @param placeholderName the name of the placeholder to resolve + * @return the replacement value, or {@code null} if no replacement is to be made + */ + String resolvePlaceholder(String placeholderName); + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/util/RunnableWithExceptionProtection.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/util/RunnableWithExceptionProtection.java new file mode 100644 index 00000000..5a3d392b --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/util/RunnableWithExceptionProtection.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.util; + +public class RunnableWithExceptionProtection implements Runnable { + + private Runnable run; + private CallbackWhenException callback; + + public RunnableWithExceptionProtection(Runnable run, CallbackWhenException callback) { + this.run = run; + this.callback = callback; + } + + @Override + public void run() { + try { + run.run(); + } catch (Throwable t) { + callback.handle(t); + } + } + + public interface CallbackWhenException { + + void handle(Throwable t); + } +} diff --git a/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/util/StringUtil.java b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/util/StringUtil.java new file mode 100644 index 00000000..040f915c --- /dev/null +++ b/hippo4j-agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/util/StringUtil.java @@ -0,0 +1,110 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.hippo4j.agent.core.util; + +import java.util.function.Consumer; + +public final class StringUtil { + + public static boolean isEmpty(String str) { + return str == null || str.length() == 0; + } + + public static boolean isNotEmpty(String str) { + return !isEmpty(str); + } + + public static boolean isBlank(String str) { + return str == null || isEmpty(str.trim()); + } + + public static boolean isNotBlank(String str) { + return !isBlank(str); + } + + public static void setIfPresent(String value, Consumer setter) { + if (isNotEmpty(value)) { + setter.accept(value); + } + } + + public static String join(final char delimiter, final String... strings) { + if (strings.length == 0) { + return null; + } + if (strings.length == 1) { + return strings[0]; + } + int length = strings.length - 1; + for (final String s : strings) { + if (s == null) { + continue; + } + length += s.length(); + } + final StringBuilder sb = new StringBuilder(length); + if (strings[0] != null) { + sb.append(strings[0]); + } + for (int i = 1; i < strings.length; ++i) { + if (!isEmpty(strings[i])) { + sb.append(delimiter).append(strings[i]); + } else { + sb.append(delimiter); + } + } + return sb.toString(); + } + + public static boolean substringMatch(CharSequence str, int index, CharSequence substring) { + if (index + substring.length() > str.length()) { + return false; + } + for (int i = 0; i < substring.length(); i++) { + if (str.charAt(index + i) != substring.charAt(i)) { + return false; + } + } + return true; + } + + public static String cut(String str, int threshold) { + if (isEmpty(str) || str.length() <= threshold) { + return str; + } + return str.substring(0, threshold); + } + + public static String trim(final String str, final char ch) { + if (isEmpty(str)) { + return null; + } + + final char[] chars = str.toCharArray(); + + int i = 0, j = chars.length - 1; + // noinspection StatementWithEmptyBody + for (; i < chars.length && chars[i] == ch; i++) { + } + // noinspection StatementWithEmptyBody + for (; j > 0 && chars[j] == ch; j--) { + } + + return new String(chars, i, j - i + 1); + } +} diff --git a/hippo4j-agent/hippo4j-sdk-plugin/pom.xml b/hippo4j-agent/hippo4j-sdk-plugin/pom.xml new file mode 100644 index 00000000..7df2b687 --- /dev/null +++ b/hippo4j-agent/hippo4j-sdk-plugin/pom.xml @@ -0,0 +1,20 @@ + + + 4.0.0 + + cn.hippo4j + hippo4j-agent + ${revision} + + + hippo4j-sdk-plugin + + + 8 + 8 + UTF-8 + + + \ No newline at end of file diff --git a/hippo4j-agent/pom.xml b/hippo4j-agent/pom.xml new file mode 100644 index 00000000..ccd31a3d --- /dev/null +++ b/hippo4j-agent/pom.xml @@ -0,0 +1,254 @@ + + + 4.0.0 + + cn.hippo4j + hippo4j-all + ${revision} + + + hippo4j-agent + pom + + hippo4j-agent-core + hippo4j-sdk-plugin + + + + cn.hippo4j.agent.dependencies + 1.8 + 4.12 + 2.0.7 + 3.5.13 + 1.18.20 + + + 1.12.13 + 1.44.0 + 4.1.79.Final + 2.8.9 + 1.6.2 + 1.3.2 + 3.1 + + 6.0.53 + + + 0.6.1 + 1.6.0 + 1.8 + 2.10 + 2.8.2 + 3.1.0 + 2.22.0 + 3.2.0 + 3.1.0 + 3.1.1 + 3.0.0-M2 + 3.8.0 + 3.1.0 + 3.0.1 + 2.5 + 4.3.0 + 3.1.0 + 1.33 + 1.5 + true + + + + + + + org.powermock + powermock-api-mockito2 + test + + + net.bytebuddy + byte-buddy-agent + test + + + org.objenesis + objenesis + test + + + org.openjdk.jmh + jmh-core + ${jmh.version} + test + + + org.projectlombok + lombok + ${lombok.version} + provided + + + javax.annotation + javax.annotation-api + ${javax.annotation-api.version} + provided + + + org.powermock + powermock-module-junit4 + test + + + + + + + junit + junit + ${junit.version} + test + + + org.powermock + powermock-module-junit4 + ${powermock.version} + test + + + org.powermock + powermock-api-mockito2 + ${powermock.version} + test + + + net.bytebuddy + byte-buddy-agent + ${bytebuddy.version} + test + + + org.objenesis + objenesis + ${objenesis.version} + test + + + org.openjdk.jmh + jmh-generator-annprocess + ${jmh.version} + test + + + + + + ${project.artifactId}-${project.version} + + + + + + io.takari + maven + ${takari-maven-plugin.version} + + + maven-antrun-plugin + ${maven-antrun-plugin.version} + + + maven-deploy-plugin + ${maven-deploy-plugin.version} + + + maven-assembly-plugin + ${maven-assembly-plugin.version} + + + maven-failsafe-plugin + ${maven-failsafe-plugin.version} + + + maven-jar-plugin + ${maven-jar-plugin.version} + + true + + + + maven-shade-plugin + ${maven-shade-plugin.version} + + + + + + kr.motd.maven + os-maven-plugin + ${os-maven-plugin.version} + + + initialize + + detect + + + + + + maven-enforcer-plugin + ${maven-enforcer-plugin.version} + + + enforce-java + + enforce + + validate + + + + + 1.8 + + + 3.6 + + + + + + + + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${compiler.version} + ${compiler.version} + ${project.build.sourceEncoding} + + + + maven-resources-plugin + ${maven-resource-plugin.version} + + ${project.build.sourceEncoding} + + + + maven-source-plugin + ${maven-source-plugin.version} + + + attach-sources + none + + jar + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 6dc356a5..d4fa2285 100644 --- a/pom.xml +++ b/pom.xml @@ -41,6 +41,7 @@ hippo4j-rpc hippo4j-server hippo4j-spring-boot + hippo4j-agent