diff --git a/.gitignore b/.gitignore index 9f4d3c066dcc6264b04bb893cf1907ace7b75d59..1f3158b2014ab4706e9396fbca8ca09c1a56f43d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,10 @@ target/ !**/src/test/**/target/ logs/ +### Agent ### +cookasyncagent/ +**/dependency-reduced-pom.xml + ### STS ### .apt_generated .classpath diff --git a/arithmetic/src/test/java/com/example/ArithmeticApplicationTests.java b/arithmetic/src/test/java/com/example/ArithmeticApplicationTests.java deleted file mode 100644 index 4cb79dc5603ef6d9e47a6f43cf5ac0d904ed3e2d..0000000000000000000000000000000000000000 --- a/arithmetic/src/test/java/com/example/ArithmeticApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.example; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class ArithmeticApplicationTests { - - @Test - void contextLoads() { - } - -} diff --git a/common/pom.xml b/common/pom.xml index 24a808a6b0e786ba30dc5785a4ef9153b569dd68..5b986e793588dadc1cdea7af2bc159ee7816b8ec 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -11,7 +11,6 @@ common - common 公共 @@ -46,6 +45,60 @@ hutool-all 5.8.16 + + + com.alibaba + fastjson + 1.2.83 + + + org.springframework.boot + spring-boot-starter-log4j2 + + + org.slf4j + slf4j-log4j12 + + + org.apache.logging.log4j + log4j-core + + + + org.apache.logging.log4j + log4j-api + + + org.apache.logging.log4j + log4j-slf4j-impl + + + org.apache.logging.log4j + log4j-web + + + + + org.apache.logging.log4j + log4j-core + 2.17.0 + + + + org.apache.logging.log4j + log4j-api + 2.17.0 + + + org.apache.logging.log4j + log4j-slf4j-impl + 2.17.0 + + + org.apache.logging.log4j + log4j-web + 2.17.0 + diff --git a/cook-async-agent/config/agent.config b/cook-async-agent/config/agent.config new file mode 100644 index 0000000000000000000000000000000000000000..26de71ccab699954740031b678e34c6f0f415550 --- /dev/null +++ b/cook-async-agent/config/agent.config @@ -0,0 +1,274 @@ +# 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. + +# The service name in UI +# ${service name} = [${group name}::]${logic name} +# The group name is optional only. +agent.service_name=${SW_AGENT_NAME:Your_ApplicationName} + +# The agent namespace +agent.namespace=${SW_AGENT_NAMESPACE:} + +# The agent cluster +agent.cluster=${SW_AGENT_CLUSTER:} + +# The number of sampled traces per 3 seconds +# Negative or zero means off, by default +agent.sample_n_per_3_secs=${SW_AGENT_SAMPLE:-1} + +# Authentication active is based on backend setting, see application.yml for more details. +agent.authentication=${SW_AGENT_AUTHENTICATION:} + +# The max number of TraceSegmentRef in a single span to keep memory cost estimatable. +agent.trace_segment_ref_limit_per_span=${SW_TRACE_SEGMENT_LIMIT:500} + +# The max amount of spans in a single segment. +# Through this config item, SkyWalking keep your application memory cost estimated. +agent.span_limit_per_segment=${SW_AGENT_SPAN_LIMIT:300} + +# If the operation name of the first span is included in this set, this segment should be ignored. Multiple values should be separated by `,`. +agent.ignore_suffix=${SW_AGENT_IGNORE_SUFFIX:.jpg,.jpeg,.js,.css,.png,.bmp,.gif,.ico,.mp3,.mp4,.html,.svg} + +# 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. +agent.is_open_debugging_class=${SW_AGENT_OPEN_DEBUG:false} + +# If true, SkyWalking agent will cache all instrumented classes files to memory or disk files (decided by class cache mode), +# allow other javaagent to enhance those classes that enhanced by SkyWalking agent. +agent.is_cache_enhanced_class=${SW_AGENT_CACHE_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 +agent.class_cache_mode=${SW_AGENT_CLASS_CACHE_MODE:MEMORY} + +# Instance name is the identity of an instance, should be unique in the service. If empty, SkyWalking agent will +# generate an 32-bit uuid. BY Default, SkyWalking uses UUID@hostname as the instance name. Max length is 50(UTF-8 char) +agent.instance_name=${SW_AGENT_INSTANCE_NAME:} + +# service instance properties in json format. e.g. agent.instance_properties_json = {"org": "apache-skywalking"} +agent.instance_properties_json=${SW_INSTANCE_PROPERTIES_JSON:} + +# How depth the agent goes, when log all cause exceptions. +agent.cause_exception_depth=${SW_AGENT_CAUSE_EXCEPTION_DEPTH:5} + +# Force reconnection period of grpc, based on grpc_channel_check_interval. +agent.force_reconnection_period=${SW_AGENT_FORCE_RECONNECTION_PERIOD:1} + +# The operationName max length +# Notice, in the current practice, we don't recommend the length over 190. +agent.operation_name_threshold=${SW_AGENT_OPERATION_NAME_THRESHOLD:150} + +# Keep tracing even the backend is not available if this value is true. +agent.keep_tracing=${SW_AGENT_KEEP_TRACING:false} + +# The agent use gRPC plain text in default. +# If true, SkyWalking agent uses TLS even no CA file detected. +agent.force_tls=${SW_AGENT_FORCE_TLS:false} + +# gRPC SSL trusted ca file. +agent.ssl_trusted_ca_path=${SW_AGENT_SSL_TRUSTED_CA_PATH:/ca/ca.crt} + +# enable mTLS when ssl_key_path and ssl_cert_chain_path exist. +agent.ssl_key_path=${SW_AGENT_SSL_KEY_PATH:} + +agent.ssl_cert_chain_path=${SW_AGENT_SSL_CERT_CHAIN_PATH:} + +# Limit the length of the ipv4 list size. +osinfo.ipv4_list_size=${SW_AGENT_OSINFO_IPV4_LIST_SIZE:10} + +# grpc channel status check interval. +collector.grpc_channel_check_interval=${SW_AGENT_COLLECTOR_GRPC_CHANNEL_CHECK_INTERVAL:30} +# Agent heartbeat report period. Unit, second. +collector.heartbeat_period=${SW_AGENT_COLLECTOR_HEARTBEAT_PERIOD:30} +# The agent sends the instance properties to the backend every +# collector.heartbeat_period * collector.properties_report_period_factor seconds +collector.properties_report_period_factor=${SW_AGENT_COLLECTOR_PROPERTIES_REPORT_PERIOD_FACTOR:10} +# Backend service addresses. +collector.backend_service=${SW_AGENT_COLLECTOR_BACKEND_SERVICES:127.0.0.1:11800} +# How long grpc client will timeout in sending data to upstream. Unit is second. +collector.grpc_upstream_timeout=${SW_AGENT_COLLECTOR_GRPC_UPSTREAM_TIMEOUT:30} +# Sniffer get profile task list interval. +collector.get_profile_task_interval=${SW_AGENT_COLLECTOR_GET_PROFILE_TASK_INTERVAL:20} +# Sniffer get agent dynamic config interval. +collector.get_agent_dynamic_config_interval=${SW_AGENT_COLLECTOR_GET_AGENT_DYNAMIC_CONFIG_INTERVAL:20} +# If true, skywalking agent will enable periodically resolving DNS to update receiver service addresses. +collector.is_resolve_dns_periodically=${SW_AGENT_COLLECTOR_IS_RESOLVE_DNS_PERIODICALLY:false} + +# Logging level +logging.level=${SW_LOGGING_LEVEL:INFO} +# Logging file_name +logging.file_name=${SW_LOGGING_FILE_NAME:cook-async-api.log} +# Log output. Default is FILE. Use CONSOLE means output to stdout. +logging.output=${SW_LOGGING_OUTPUT:FILE} +# Log files directory. Default is blank string, meaning use "{thecook-asyncAgentJarDir}/logs " to output logs. +# {thecook-asyncAgentJarDir} is the directory where the cook-async agent jar file is located +logging.dir=${SW_LOGGING_DIR:} +# Logger resolver: PATTERN or JSON. The default is PATTERN, which uses logging.pattern to print traditional text logs. +# JSON resolver prints logs in JSON format. +logging.resolver=${SW_LOGGING_RESOLVER:PATTERN} +# Logging format. There are all conversion specifiers: +# * %level means log level. +# * %timestamp means now of time with format yyyy-MM-dd HH:mm:ss:SSS. +# * %thread means name of current thread. +# * %msg means some message which user logged. +# * %class means SimpleName of TargetClass. +# * %throwable means a throwable which user called. +# * %agent_name means agent.service_name. Only apply to the PatternLogger. +logging.pattern=${SW_LOGGING_PATTERN:%level %timestamp %thread %class : %msg %throwable} +# Logging max_file_size, default: 300 * 1024 * 1024 = 314572800 +logging.max_file_size=${SW_LOGGING_MAX_FILE_SIZE:314572800} +# 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. +logging.max_history_files=${SW_LOGGING_MAX_HISTORY_FILES:-1} + +# 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. +# Besides, the annotation named IgnoredException in the trace toolkit is another way to configure ignored exceptions. +statuscheck.ignored_exceptions=${SW_STATUSCHECK_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. +statuscheck.max_recursive_depth=${SW_STATUSCHECK_MAX_RECURSIVE_DEPTH:1} + +# Max element count in the correlation context +correlation.element_max_number=${SW_CORRELATION_ELEMENT_MAX_NUMBER:3} + +# Max value length of each element. +correlation.value_max_length=${SW_CORRELATION_VALUE_MAX_LENGTH:128} +# Tag the span by the key/value in the correlation context, when the keys listed here exist. +correlation.auto_tag_keys=${SW_CORRELATION_AUTO_TAG_KEYS:} +# The buffer size of collected JVM info. +jvm.buffer_size=${SW_JVM_BUFFER_SIZE:600} +# The buffer channel size. +buffer.channel_size=${SW_BUFFER_CHANNEL_SIZE:5} +# The buffer size. +buffer.buffer_size=${SW_BUFFER_BUFFER_SIZE:300} +# If true, skywalking agent will enable profile when user create a new profile task. Otherwise disable profile. +profile.active=${SW_AGENT_PROFILE_ACTIVE:true} +# Parallel monitor segment count +profile.max_parallel=${SW_AGENT_PROFILE_MAX_PARALLEL:5} +# Max monitor segment time(minutes), if current segment monitor time out of limit, then stop it. +profile.duration=${SW_AGENT_PROFILE_DURATION:10} +# Max dump thread stack depth +profile.dump_max_stack_depth=${SW_AGENT_PROFILE_DUMP_MAX_STACK_DEPTH:500} +# Snapshot transport to backend buffer size +profile.snapshot_transport_buffer_size=${SW_AGENT_PROFILE_SNAPSHOT_TRANSPORT_BUFFER_SIZE:4500} +# If true, the agent collects and reports metrics to the backend. +meter.active=${SW_METER_ACTIVE:true} +# Report meters interval. The unit is second +meter.report_interval=${SW_METER_REPORT_INTERVAL:20} +# Max size of the meter pool +meter.max_meter_size=${SW_METER_MAX_METER_SIZE:500} +# The max size of message to send to server.Default is 10 MB +log.max_message_size=${SW_GRPC_LOG_MAX_MESSAGE_SIZE:10485760} + +# Mount the specific folders of the plugins. Plugins in mounted folders would work. +plugin.mount=${SW_MOUNT_FOLDERS:plugins,activations} +# Peer maximum description limit. +plugin.peer_max_length=${SW_PLUGIN_PEER_MAX_LENGTH:200} +# Exclude some plugins define in plugins dir.Plugin names is defined in [Agent plugin list](Plugin-list.md) +plugin.exclude_plugins=${SW_EXCLUDE_PLUGINS:} +# If true, trace all the parameters in MongoDB access, default is false. Only trace the operation, not include parameters. +plugin.mongodb.trace_param=${SW_PLUGIN_MONGODB_TRACE_PARAM:false} +# If set to positive number, the `WriteRequest.params` would be truncated to this length, otherwise it would be completely saved, which may cause performance problem. +plugin.mongodb.filter_length_limit=${SW_PLUGIN_MONGODB_FILTER_LENGTH_LIMIT:256} +# If true, trace all the DSL(Domain Specific Language) in ElasticSearch access, default is false. +plugin.elasticsearch.trace_dsl=${SW_PLUGIN_ELASTICSEARCH_TRACE_DSL:false} +# If true, the fully qualified method name will be used as the endpoint name instead of the request URL, default is false. +plugin.springmvc.use_qualified_name_as_endpoint_name=${SW_PLUGIN_SPRINGMVC_USE_QUALIFIED_NAME_AS_ENDPOINT_NAME:false} +# If true, the fully qualified method name will be used as the operation name instead of the given operation name, default is false. +plugin.toolit.use_qualified_name_as_operation_name=${SW_PLUGIN_TOOLIT_USE_QUALIFIED_NAME_AS_OPERATION_NAME:false} +# If set to true, the parameters of the sql (typically `java.sql.PreparedStatement`) would be collected. +plugin.jdbc.trace_sql_parameters=${SW_JDBC_TRACE_SQL_PARAMETERS:false} +# If set to positive number, the `db.sql.parameters` would be truncated to this length, otherwise it would be completely saved, which may cause performance problem. +plugin.jdbc.sql_parameters_max_length=${SW_PLUGIN_JDBC_SQL_PARAMETERS_MAX_LENGTH:512} +# If set to positive number, the `db.statement` would be truncated to this length, otherwise it would be completely saved, which may cause performance problem. +plugin.jdbc.sql_body_max_length=${SW_PLUGIN_JDBC_SQL_BODY_MAX_LENGTH:2048} +# If true, trace all the query parameters(include deleteByIds and deleteByQuery) in Solr query request, default is false. +plugin.solrj.trace_statement=${SW_PLUGIN_SOLRJ_TRACE_STATEMENT:false} +# If true, trace all the operation parameters in Solr request, default is false. +plugin.solrj.trace_ops_params=${SW_PLUGIN_SOLRJ_TRACE_OPS_PARAMS:false} +# If true, trace all middleware/business handlers that are part of the Light4J handler chain for a request. +plugin.light4j.trace_handler_chain=${SW_PLUGIN_LIGHT4J_TRACE_HANDLER_CHAIN:false} +# If true, the transaction definition name will be simplified. +plugin.springtransaction.simplify_transaction_definition_name=${SW_PLUGIN_SPRINGTRANSACTION_SIMPLIFY_TRANSACTION_DEFINITION_NAME:false} +# Threading classes (`java.lang.Runnable` and `java.util.concurrent.Callable`) and their subclasses, including anonymous inner classes whose name match any one of the `THREADING_CLASS_PREFIXES` (splitted by `,`) will be instrumented, make sure to only specify as narrow prefixes as what you're expecting to instrument, (`java.` and `javax.` will be ignored due to safety issues) +plugin.jdkthreading.threading_class_prefixes=${SW_PLUGIN_JDKTHREADING_THREADING_CLASS_PREFIXES:} +# This config item controls that whether the Tomcat plugin should collect the parameters of the request. Also, activate implicitly in the profiled trace. +plugin.tomcat.collect_http_params=${SW_PLUGIN_TOMCAT_COLLECT_HTTP_PARAMS:false} +# This config item controls that whether the SpringMVC plugin should collect the parameters of the request, when your Spring application is based on Tomcat, consider only setting either `plugin.tomcat.collect_http_params` or `plugin.springmvc.collect_http_params`. Also, activate implicitly in the profiled trace. +plugin.springmvc.collect_http_params=${SW_PLUGIN_SPRINGMVC_COLLECT_HTTP_PARAMS:false} +# This config item controls that whether the HttpClient plugin should collect the parameters of the request +plugin.httpclient.collect_http_params=${SW_PLUGIN_HTTPCLIENT_COLLECT_HTTP_PARAMS:false} +# When `COLLECT_HTTP_PARAMS` is enabled, how many characters to keep and send to the OAP backend, use negative values to keep and send the complete parameters, NB. this config item is added for the sake of performance. +plugin.http.http_params_length_threshold=${SW_PLUGIN_HTTP_HTTP_PARAMS_LENGTH_THRESHOLD:1024} +# When `include_http_headers` declares header names, this threshold controls the length limitation of all header values. use negative values to keep and send the complete headers. Note. this config item is added for the sake of performance. +plugin.http.http_headers_length_threshold=${SW_PLUGIN_HTTP_HTTP_HEADERS_LENGTH_THRESHOLD:2048} +# Set the header names, which should be collected by the plugin. Header name must follow `javax.servlet.http` definition. Multiple names should be split by comma. +plugin.http.include_http_headers=${SW_PLUGIN_HTTP_INCLUDE_HTTP_HEADERS:} +# This config item controls that whether the Feign plugin should collect the http body of the request. +plugin.feign.collect_request_body=${SW_PLUGIN_FEIGN_COLLECT_REQUEST_BODY:false} +# When `COLLECT_REQUEST_BODY` is enabled, how many characters to keep and send to the OAP backend, use negative values to keep and send the complete body. +plugin.feign.filter_length_limit=${SW_PLUGIN_FEIGN_FILTER_LENGTH_LIMIT:1024} +# When `COLLECT_REQUEST_BODY` is enabled and content-type start with SUPPORTED_CONTENT_TYPES_PREFIX, collect the body of the request , multiple paths should be separated by `,` +plugin.feign.supported_content_types_prefix=${SW_PLUGIN_FEIGN_SUPPORTED_CONTENT_TYPES_PREFIX:application/json,text/} +# If true, trace all the influxql(query and write) in InfluxDB access, default is true. +plugin.influxdb.trace_influxql=${SW_PLUGIN_INFLUXDB_TRACE_INFLUXQL:true} +# Apache Dubbo consumer collect `arguments` in RPC call, use `Object#toString` to collect `arguments`. +plugin.dubbo.collect_consumer_arguments=${SW_PLUGIN_DUBBO_COLLECT_CONSUMER_ARGUMENTS:false} +# When `plugin.dubbo.collect_consumer_arguments` is `true`, Arguments of length from the front will to the OAP backend +plugin.dubbo.consumer_arguments_length_threshold=${SW_PLUGIN_DUBBO_CONSUMER_ARGUMENTS_LENGTH_THRESHOLD:256} +# Apache Dubbo provider collect `arguments` in RPC call, use `Object#toString` to collect `arguments`. +plugin.dubbo.collect_provider_arguments=${SW_PLUGIN_DUBBO_COLLECT_PROVIDER_ARGUMENTS:false} +# When `plugin.dubbo.collect_provider_arguments` is `true`, Arguments of length from the front will to the OAP backend +plugin.dubbo.provider_arguments_length_threshold=${SW_PLUGIN_DUBBO_PROVIDER_ARGUMENTS_LENGTH_THRESHOLD:256} +# A list of host/port pairs to use for establishing the initial connection to the Kafka cluster. +plugin.kafka.bootstrap_servers=${SW_KAFKA_BOOTSTRAP_SERVERS:localhost:9092} +# Timeout period of reading topics from the Kafka server, the unit is second. +plugin.kafka.get_topic_timeout=${SW_GET_TOPIC_TIMEOUT:10} +# Kafka producer configuration. Read [producer configure](http://kafka.apache.org/24/documentation.html#producerconfigs) +# to get more details. Check document for more details and examples. +plugin.kafka.producer_config=${sw_plugin_kafka_producer_config:} +# Configure Kafka Producer configuration in JSON format. Notice it will be overridden by plugin.kafka.producer_config[key], if the key duplication. +plugin.kafka.producer_config_json=${SW_PLUGIN_KAFKA_PRODUCER_CONFIG_JSON:} +# Specify which Kafka topic name for Meter System data to report to. +plugin.kafka.topic_meter=${SW_PLUGIN_KAFKA_TOPIC_METER:skywalking-meters} +# Specify which Kafka topic name for JVM metrics data to report to. +plugin.kafka.topic_metrics=${SW_PLUGIN_KAFKA_TOPIC_METRICS:skywalking-metrics} +# Specify which Kafka topic name for traces data to report to. +plugin.kafka.topic_segment=${SW_PLUGIN_KAFKA_TOPIC_SEGMENT:skywalking-segments} +# Specify which Kafka topic name for Thread Profiling snapshot to report to. +plugin.kafka.topic_profiling=${SW_PLUGIN_KAFKA_TOPIC_PROFILINGS:skywalking-profilings} +# Specify which Kafka topic name for the register or heartbeat data of Service Instance to report to. +plugin.kafka.topic_management=${SW_PLUGIN_KAFKA_TOPIC_MANAGEMENT:skywalking-managements} +# Specify which Kafka topic name for the logging data to report to. +plugin.kafka.topic_logging=${SW_PLUGIN_KAFKA_TOPIC_LOGGING:skywalking-logs} +# isolate multi OAP server when using same Kafka cluster (final topic name will append namespace before Kafka topics with `-` ). +plugin.kafka.namespace=${SW_KAFKA_NAMESPACE:} +# Match spring beans with regular expression for the class name. Multiple expressions could be separated by a comma. This only works when `Spring annotation plugin` has been activated. +plugin.springannotation.classname_match_regex=${SW_SPRINGANNOTATION_CLASSNAME_MATCH_REGEX:} +# Whether or not to transmit logged data as formatted or un-formatted. +plugin.toolkit.log.transmit_formatted=${SW_PLUGIN_TOOLKIT_LOG_TRANSMIT_FORMATTED:true} +# If set to true, the parameters of Redis commands would be collected by Lettuce agent. +plugin.lettuce.trace_redis_parameters=${SW_PLUGIN_LETTUCE_TRACE_REDIS_PARAMETERS:false} +# If set to positive number and `plugin.lettuce.trace_redis_parameters` is set to `true`, Redis command parameters would be collected and truncated to this length. +plugin.lettuce.redis_parameter_max_length=${SW_PLUGIN_LETTUCE_REDIS_PARAMETER_MAX_LENGTH:128} +# If set to true, the parameters of the cypher would be collected. +plugin.neo4j.trace_cypher_parameters=${SW_PLUGIN_NEO4J_TRACE_CYPHER_PARAMETERS:false} +# If set to positive number, the `db.cypher.parameters` would be truncated to this length, otherwise it would be completely saved, which may cause performance problem. +plugin.neo4j.cypher_parameters_max_length=${SW_PLUGIN_NEO4J_CYPHER_PARAMETERS_MAX_LENGTH:512} +# If set to positive number, the `db.statement` would be truncated to this length, otherwise it would be completely saved, which may cause performance problem. +plugin.neo4j.cypher_body_max_length=${SW_PLUGIN_NEO4J_CYPHER_BODY_MAX_LENGTH:2048} +# If set to a positive number and activate `trace sampler CPU policy plugin`, the trace would not be collected when agent process CPU usage percent is greater than `plugin.cpupolicy.sample_cpu_usage_percent_limit`. +plugin.cpupolicy.sample_cpu_usage_percent_limit=${SW_SAMPLE_CPU_USAGE_PERCENT_LIMIT:-1} diff --git a/cook-async-agent/cook-async-agent-bootstrap/pom.xml b/cook-async-agent/cook-async-agent-bootstrap/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..ac862663891329a9387fe4e573b7c782548965a9 --- /dev/null +++ b/cook-async-agent/cook-async-agent-bootstrap/pom.xml @@ -0,0 +1,141 @@ + + + + + 4.0.0 + + com.example + cook-async-agent + ${revision} + + cook-async-agent-bootstrap + + jar + + + UTF-8 + com.example.agent.bootstrap.CookAsyncAgent + true + true + net.bytebuddy + ${shade.package}.${shade.net.bytebuddy.source} + + + + + com.example + cook-async-agent-core + ${project.version} + + + + + cook-async-agent + + + maven-shade-plugin + + + package + + shade + + + false + true + true + true + + + + ${premain.class} + ${can.redefine.classes} + ${can.retransform.classes} + + + + + + *:gson + io.grpc:* + io.netty:* + io.opencensus:* + com.google.*:* + com.google.guava:guava + org.checkerframework:checker-compat-qual + org.codehaus.mojo:animal-sniffer-annotations + io.perfmark:* + org.slf4j:* + + + + + ${shade.net.bytebuddy.source} + ${shade.net.bytebuddy.target} + + + + + net.bytebuddy:byte-buddy + + META-INF/versions/9/module-info.class + + + + + + + + + maven-antrun-plugin + + + clean + clean + + run + + + + + + + + + package + package + + run + + + + + + + + + + + + + + + + + diff --git a/cook-async-agent/cook-async-agent-bootstrap/src/main/java/com/example/agent/bootstrap/CookAsyncAgent.java b/cook-async-agent/cook-async-agent-bootstrap/src/main/java/com/example/agent/bootstrap/CookAsyncAgent.java new file mode 100644 index 0000000000000000000000000000000000000000..b318a095d65b2f282bea28838a6d639fa4dc0539 --- /dev/null +++ b/cook-async-agent/cook-async-agent-bootstrap/src/main/java/com/example/agent/bootstrap/CookAsyncAgent.java @@ -0,0 +1,263 @@ +/* + * 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 com.example.agent.bootstrap; + +import com.example.agent.core.boot.AgentPackageNotFoundException; +import com.example.agent.core.boot.ServiceManager; +import com.example.agent.core.conf.Config; +import com.example.agent.core.conf.SnifferConfigInitializer; +import com.example.agent.core.jvm.LoadedLibraryCollector; +import com.example.agent.core.logging.api.ILog; +import com.example.agent.core.logging.api.LogManager; +import com.example.agent.core.plugin.AbstractClassEnhancePluginDefine; +import com.example.agent.core.plugin.EnhanceContext; +import com.example.agent.core.plugin.InstrumentDebuggingClass; +import com.example.agent.core.plugin.PluginBootstrap; +import com.example.agent.core.plugin.PluginException; +import com.example.agent.core.plugin.PluginFinder; +import com.example.agent.core.plugin.bootstrap.BootstrapInstrumentBoost; +import com.example.agent.core.plugin.bytebuddy.CacheableTransformerDecorator; +import com.example.agent.core.plugin.jdk9module.JDK9ModuleExporter; +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.agent.builder.AgentBuilder; +import net.bytebuddy.description.NamedElement; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.dynamic.DynamicType; +import net.bytebuddy.dynamic.scaffold.TypeValidation; +import net.bytebuddy.matcher.ElementMatcher; +import net.bytebuddy.matcher.ElementMatchers; +import net.bytebuddy.utility.JavaModule; + +import java.lang.instrument.Instrumentation; +import java.security.ProtectionDomain; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static net.bytebuddy.matcher.ElementMatchers.nameContains; +import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; +import static net.bytebuddy.matcher.ElementMatchers.not; + +/** + * cook Agent + */ +public class CookAsyncAgent { + + private static ILog LOGGER = LogManager.getLogger(CookAsyncAgent.class); + + /** + * Main entrance. Use byte-buddy transform to enhance all classes, which define in plugins. + */ + public static void premain(String agentArgs, Instrumentation instrumentation) throws PluginException { + final PluginFinder pluginFinder; + try { + SnifferConfigInitializer.initializeCoreConfig(agentArgs); + } catch (Exception e) { + // try to resolve a new logger, and use the new logger to write the error log here + LogManager.getLogger(CookAsyncAgent.class) + .error(e, "cook-async agent initialized failure. Shutting down."); + return; + } finally { + // refresh logger again after initialization finishes + LOGGER = LogManager.getLogger(CookAsyncAgent.class); + } + + try { + pluginFinder = new PluginFinder(new PluginBootstrap().loadPlugins()); + } catch (AgentPackageNotFoundException ape) { + LOGGER.error(ape, "Locate agent.jar failure. Shutting down."); + return; + } catch (Exception e) { + LOGGER.error(e, "cook-async agent initialized failure. Shutting down."); + return; + } + + final ByteBuddy byteBuddy = new ByteBuddy().with(TypeValidation.of(Config.Agent.IS_OPEN_DEBUGGING_CLASS)); + + AgentBuilder agentBuilder = new AgentBuilder.Default(byteBuddy).ignore( + nameStartsWith("net.bytebuddy.") + .or(nameStartsWith("org.slf4j.")) + .or(nameStartsWith("org.groovy.")) + .or(nameContains("javassist")) + .or(nameContains(".asm.")) + .or(nameContains(".reflectasm.")) + .or(nameStartsWith("sun.reflect")) + .or(allCookAsyncAgentExcludeToolkit()) + .or(ElementMatchers.isSynthetic())); + + JDK9ModuleExporter.EdgeClasses edgeClasses = new JDK9ModuleExporter.EdgeClasses(); + try { + agentBuilder = BootstrapInstrumentBoost.inject(pluginFinder, instrumentation, agentBuilder, edgeClasses); + } catch (Exception e) { + LOGGER.error(e, "cook-async agent inject bootstrap instrumentation failure. Shutting down."); + return; + } + + try { + agentBuilder = JDK9ModuleExporter.openReadEdge(instrumentation, agentBuilder, edgeClasses); + } catch (Exception e) { + LOGGER.error(e, "cook-async agent open read edge in JDK 9+ failure. Shutting down."); + return; + } + + if (Config.Agent.IS_CACHE_ENHANCED_CLASS) { + try { + agentBuilder = agentBuilder.with(new CacheableTransformerDecorator(Config.Agent.CLASS_CACHE_MODE)); + LOGGER.info("cook-async agent class cache [{}] activated.", Config.Agent.CLASS_CACHE_MODE); + } catch (Exception e) { + LOGGER.error(e, "cook-async agent can't active class cache."); + } + } + + agentBuilder.type(pluginFinder.buildMatch()) + .transform(new Transformer(pluginFinder)) + .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION) + .with(new RedefinitionListener()) + .with(new Listener()) + .installOn(instrumentation); + + PluginFinder.pluginInitCompleted(); + + try { + ServiceManager.INSTANCE.boot(); + } catch (Exception e) { + LOGGER.error(e, "cook-async agent boot failure."); + } + +// try { +// Class.forName("java.util.concurrent.ThreadPoolExecutor"); +// } catch (ClassNotFoundException e) { +// LOGGER.error(e, "cook-async agent boot failure."); +// } + + Runtime.getRuntime() + .addShutdownHook(new Thread(ServiceManager.INSTANCE::shutdown, "cook-async service shutdown thread")); + } + + /** + * transformer + */ + private static class Transformer implements AgentBuilder.Transformer { + + private PluginFinder pluginFinder; + + Transformer(PluginFinder pluginFinder) { + this.pluginFinder = pluginFinder; + } + + @Override + public DynamicType.Builder transform(final DynamicType.Builder builder, + final TypeDescription typeDescription, + final ClassLoader classLoader, + final JavaModule javaModule, + final ProtectionDomain protectionDomain) { + LoadedLibraryCollector.registerURLClassLoader(classLoader); + List pluginDefines = pluginFinder.find(typeDescription); + if (pluginDefines.size() > 0) { + DynamicType.Builder newBuilder = builder; + EnhanceContext context = new EnhanceContext(); + for (AbstractClassEnhancePluginDefine define : pluginDefines) { + DynamicType.Builder possibleNewBuilder = define.define( + typeDescription, newBuilder, classLoader, context); + if (possibleNewBuilder != null) { + newBuilder = possibleNewBuilder; + } + } + if (context.isEnhanced()) { + LOGGER.debug("Finish the prepare stage for {}.", typeDescription.getName()); + } + + return newBuilder; + } + + LOGGER.debug("Matched class {}, but ignore by finding mechanism.", typeDescription.getTypeName()); + return builder; + } + } + + private static ElementMatcher.Junction allCookAsyncAgentExcludeToolkit() { + return nameStartsWith("com.example").and(not(nameStartsWith("com.example.agent.toolkit."))); + } + + /** + * listener + */ + private static class Listener implements AgentBuilder.Listener { + + @Override + public void onDiscovery(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) { + + } + + @Override + public void onTransformation(final TypeDescription typeDescription, + final ClassLoader classLoader, + final JavaModule module, + final boolean loaded, + final DynamicType dynamicType) { + if (LOGGER.isDebugEnable()) { + LOGGER.debug("On Transformation class {}.", typeDescription.getName()); + } + + InstrumentDebuggingClass.INSTANCE.log(dynamicType); + } + + @Override + public void onIgnored(final TypeDescription typeDescription, + final ClassLoader classLoader, + final JavaModule module, + final boolean loaded) { + + } + + @Override + public void onError(final String typeName, + final ClassLoader classLoader, + final JavaModule module, + final boolean loaded, + final Throwable throwable) { + LOGGER.error("Enhance class " + typeName + " error.", throwable); + } + + @Override + public void onComplete(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) { + } + } + + /** + * redefinition listener + */ + private static class RedefinitionListener implements AgentBuilder.RedefinitionStrategy.Listener { + + @Override + public void onBatch(int index, List> batch, List> types) { + /* do nothing */ + } + + @Override + public Iterable>> onError(int index, List> batch, Throwable throwable, List> types) { + LOGGER.error(throwable, "index={}, batch={}, types={}", index, batch, types); + return Collections.emptyList(); + } + + @Override + public void onComplete(int amount, List> types, Map>, Throwable> failures) { + /* do nothing */ + } + } +} diff --git a/cook-async-agent/cook-async-agent-core/pom.xml b/cook-async-agent/cook-async-agent-core/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..4472c3da9fd0c349e6ad5c7f1a30629974a364e0 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/pom.xml @@ -0,0 +1,175 @@ + + + + + 4.0.0 + + com.example + cook-async-agent + ${revision} + + cook-async-agent-core + + + UTF-8 + 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 + + + + + net.bytebuddy + byte-buddy + + + com.google.code.gson + gson + + + org.projectlombok + lombok + + + net.bytebuddy + byte-buddy-agent + test + + + com.github.tomakehurst + wiremock + test + + + jackson-annotations + com.fasterxml.jackson.core + + + jackson-core + com.fasterxml.jackson.core + + + jackson-databind + com.fasterxml.jackson.core + + + + + org.openjdk.jmh + jmh-generator-annprocess + test + + + com.google.code.gson + gson + + + + + + + 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 + + + + + + + + + + + + + diff --git a/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/base64/Base64.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/base64/Base64.java new file mode 100644 index 0000000000000000000000000000000000000000..681af8e68f69cbcf3331fca6e0d2e6c30e3aa232 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/boot/AgentPackageNotFoundException.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/boot/AgentPackageNotFoundException.java new file mode 100644 index 0000000000000000000000000000000000000000..e527983dda06a2d35e2fa5eb2d28a210f70b1767 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.boot; + +public class AgentPackageNotFoundException extends Exception { + + public AgentPackageNotFoundException(String message) { + super(message); + } +} diff --git a/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/boot/AgentPackagePath.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/boot/AgentPackagePath.java new file mode 100644 index 0000000000000000000000000000000000000000..60fae3a67dfb12cb2aebbc75b5bbf9d78a97160e --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/boot/AgentPackagePath.java @@ -0,0 +1,86 @@ +/* + * 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 com.example.agent.core.boot; + + +import com.example.agent.core.logging.api.ILog; +import com.example.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 cook-async 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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/boot/BootService.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/boot/BootService.java new file mode 100644 index 0000000000000000000000000000000000000000..57ba4e975968a17b02ff3dde03c804eb4085fe12 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/boot/DefaultImplementor.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/boot/DefaultImplementor.java new file mode 100644 index 0000000000000000000000000000000000000000..23ee7f3a5d019b57900a03c837f90692d7200c8f --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/boot/DefaultNamedThreadFactory.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/boot/DefaultNamedThreadFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..fb67c63fb559dd2f4552a27f5f2eca3644315e04 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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 = "Cook-Async-jAgent-" + 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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/boot/OverrideImplementor.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/boot/OverrideImplementor.java new file mode 100644 index 0000000000000000000000000000000000000000..0171ddae3def8e59d4d9ea621c83f9e0ac76d17d --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/boot/PluginConfig.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/boot/PluginConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..6fe6085138b0142a15b05679c051b3b3f1e5ec31 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/boot/ServiceConflictException.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/boot/ServiceConflictException.java new file mode 100644 index 0000000000000000000000000000000000000000..73198ca629701c5f388daed1e343b3b2a8bc404a --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.boot; + +public class ServiceConflictException extends RuntimeException { + + public ServiceConflictException(String message) { + super(message); + } +} diff --git a/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/boot/ServiceManager.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/boot/ServiceManager.java new file mode 100644 index 0000000000000000000000000000000000000000..d64cdc9b56b1eda33f54e154a91c584109584f54 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/boot/ServiceManager.java @@ -0,0 +1,149 @@ +/* + * 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 com.example.agent.core.boot; + +import com.example.agent.core.logging.api.ILog; +import com.example.agent.core.logging.api.LogManager; +import com.example.agent.core.plugin.loader.AgentClassLoader; + +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; + +/** + * 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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/boot/SpringBootConfigInitializer.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/boot/SpringBootConfigInitializer.java new file mode 100644 index 0000000000000000000000000000000000000000..750fb5958187e635f16e13b1d05fd05cef9e72fa --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/boot/SpringBootConfigInitializer.java @@ -0,0 +1,88 @@ +/* + * 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 com.example.agent.core.boot; + + +import com.example.agent.core.logging.api.ILog; +import com.example.agent.core.logging.api.LogManager; +import com.example.agent.core.util.ConfigInitializer; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Properties; +import java.util.Set; + +public class SpringBootConfigInitializer { + + private static final ILog LOG = LogManager.getLogger(SpringBootConfigInitializer.class); + + private static final Set> SPRING_BOOT_CONFIG_LIST = Collections.synchronizedSet(new HashSet<>()); + + private static final long MAX_CACHE_TIME = 30L * 60L * 1000L; // half an hour. + + private static final int MAX_CACHE_SIZE = 1000; + + private static long PROPERTIES_LOAD_TIME; + + public static Properties SPRING_PROPERTIES = null; + + private SpringBootConfigInitializer() { + + } + + public static boolean isSpringPropertiesEmpty() { + return SPRING_PROPERTIES == null || SPRING_PROPERTIES.isEmpty(); + } + + public static synchronized void initializeConfig(SpringBootConfigNode springBootConfig) { + if (SPRING_PROPERTIES != null) { + try { + LOG.info("initialize Spring Config Class {}.", springBootConfig.root()); + ConfigInitializer.initialize(SPRING_PROPERTIES, springBootConfig.root(), true); + } catch (Throwable e) { + LOG.error(e, "Failed to set the agent settings {} to Config={} ", SPRING_PROPERTIES, springBootConfig.root()); + } + } + boolean isStarting = PROPERTIES_LOAD_TIME == 0L; + boolean overtime = System.currentTimeMillis() - PROPERTIES_LOAD_TIME > MAX_CACHE_TIME; + boolean oversize = SPRING_BOOT_CONFIG_LIST.size() > MAX_CACHE_SIZE; + // avoid memory leak. + if (isStarting || (!oversize && !overtime)) { + SPRING_BOOT_CONFIG_LIST.add(springBootConfig.root()); + } else { + LOG.warn("spirng Config Class is skipped {}.", springBootConfig.root()); + } + } + + public static synchronized void setSpringProperties(Properties properties) { + if (properties != null && (SPRING_PROPERTIES == null || properties.size() > SPRING_PROPERTIES.size())) { + LOG.info("set Spring Config Properties before : {}.", SPRING_PROPERTIES); + SPRING_PROPERTIES = properties; + LOG.info("set Spring Config Properties after : {}.", SPRING_PROPERTIES); + PROPERTIES_LOAD_TIME = System.currentTimeMillis(); + } + for (Class clazz : SPRING_BOOT_CONFIG_LIST) { + try { + LOG.info("initialize Spring Config Class in loop {}.", clazz); + ConfigInitializer.initialize(SPRING_PROPERTIES, clazz); + } catch (Throwable e) { + LOG.error(e, "Failed to set the agent Config={} from settings {}", clazz, properties); + } + } + } +} diff --git a/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/boot/SpringBootConfigNode.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/boot/SpringBootConfigNode.java new file mode 100644 index 0000000000000000000000000000000000000000..a861e5533364957f745bde1c28e5a4cab41df1cb --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/boot/SpringBootConfigNode.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 com.example.agent.core.boot; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface SpringBootConfigNode { + + /** + * @return Class as the root to do config initialization. + */ + Class root(); +} diff --git a/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/conf/Config.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/conf/Config.java new file mode 100644 index 0000000000000000000000000000000000000000..e99d95d60606bf81a2f1075df9a4d9fc0f3a0624 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/conf/Config.java @@ -0,0 +1,407 @@ +/* + * 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 com.example.agent.core.conf; + + +import com.example.agent.core.logging.core.LogLevel; +import com.example.agent.core.logging.core.LogOutput; +import com.example.agent.core.logging.core.ResolverType; +import com.example.agent.core.logging.core.WriterFactory; +import com.example.agent.core.plugin.bytebuddy.ClassCacheMode; +import com.example.agent.core.util.Length; + +import java.util.Arrays; +import java.util.List; + +/** + * This is the core config in cook-async 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, cook-async agent will save all instrumented classes files in `/debugging` folder. cook-async team + * may ask for these files in order to resolve compatible problem. + */ + public static boolean IS_OPEN_DEBUGGING_CLASS = false; + + /** + * If true, cook-async 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 cook-async 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": + * "com.example"} + */ + 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 cook-async 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, cook-async 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, cook-async 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, cook-async 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 = "cook-async-api.log"; + + /** + * Log files directory. Default is blank string, means, use "{thecook-asyncAgentJarDir}/logs " to output logs. + * {thecook-asyncAgentJarDir} is the directory where the cook-async 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 com.example.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 ThreadPool { + + public static List EXCLUDE_PACKAGE_PREFIX = Arrays.asList( + "java", "sun", "okhttp3", "retrofit2", "reactor", + "org.apache", "io.netty", "org.springframework", "com.ctrip", "com.google", + "io.undertow", "org.xnio", "org.jboss", "com.zaxxer", "org.redisson", "com.alibaba", + "com.netflix", "com.mysql", "rx.internal", "io.shardingjdbc", "org.drools", "org.elasticsearch", + "ch.qos.logback", "net.sf.ehcache"); + } + + public static class Apollo { + + public static class App { + + public static String ID; + } + public static String META; + + public static class BootStrap { + + public static boolean ENABLED = false; + + public static List NAMESPACES; + } + } + } + + 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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/conf/ConfigNotFoundException.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/conf/ConfigNotFoundException.java new file mode 100644 index 0000000000000000000000000000000000000000..5bfadb10ae5be5c7107aa22bf2d6fbfe9bf82e85 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/conf/Constants.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/conf/Constants.java new file mode 100644 index 0000000000000000000000000000000000000000..fc1dcb39ef60bbb3487f8932da39009f6ca4abdb --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/conf/Constants.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 com.example.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; + + public static String SPRING_BOOT_CONFIG_PREFIX = "spring.dynamic.thread-pool"; + +} diff --git a/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/conf/RuntimeContextConfiguration.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/conf/RuntimeContextConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..d19202fb89e1828b6fcc575f9e7b681c59b18ea0 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/conf/SnifferConfigInitializer.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/conf/SnifferConfigInitializer.java new file mode 100644 index 0000000000000000000000000000000000000000..77f2d088abad107fdd0a0a517064a26d8692d203 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/conf/SnifferConfigInitializer.java @@ -0,0 +1,236 @@ +/* + * 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 com.example.agent.core.conf; + + +import com.example.agent.core.boot.AgentPackageNotFoundException; +import com.example.agent.core.boot.AgentPackagePath; +import com.example.agent.core.logging.api.ILog; +import com.example.agent.core.logging.api.LogManager; +import com.example.agent.core.logging.core.JsonLogResolver; +import com.example.agent.core.logging.core.PatternLogResolver; +import com.example.agent.core.util.ConfigInitializer; +import com.example.agent.core.util.PropertyPlaceholderHelper; +import com.example.agent.core.util.StringUtil; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import static com.example.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 = "cook-async_config"; + private static final String DEFAULT_CONFIG_FILE_NAME = "/config/agent.config"; + private static final String ENV_KEY_PREFIX = "cook-async."; + 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 `cook-async.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, cook-async 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 `cook-async`, the result should be as + * same as in `agent.config` + *

+ * such as: Property key of `agent.service_name` should be `cook-async.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 + * + * + */ + 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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/conf/dynamic/AgentConfigChangeWatcher.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/conf/dynamic/AgentConfigChangeWatcher.java new file mode 100644 index 0000000000000000000000000000000000000000..f857994b65502c4a1ac18998f2a8ecf4c394b8da --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/jvm/LoadedLibraryCollector.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/jvm/LoadedLibraryCollector.java new file mode 100644 index 0000000000000000000000000000000000000000..34b5811f25d13ebef86dd4ab9415bb76a8e4cb7d --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/jvm/LoadedLibraryCollector.java @@ -0,0 +1,132 @@ +/* + * 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 com.example.agent.core.jvm; + +import com.example.agent.core.logging.api.ILog; +import com.example.agent.core.logging.api.LogManager; +import com.example.agent.core.util.CollectionUtil; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import java.io.File; +import java.lang.management.ManagementFactory; +import java.net.URL; +import java.net.URLClassLoader; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class LoadedLibraryCollector { + + private static final ILog LOGGER = LogManager.getLogger(LoadedLibraryCollector.class); + private static final String JAR_SEPARATOR = "!"; + private static Set CURRENT_URL_CLASSLOADER_SET = new HashSet<>(); + private static final Gson GSON = new GsonBuilder().disableHtmlEscaping().create(); + /** + * Prevent OOM in special scenes + */ + private static int CURRENT_URL_CLASSLOADER_SET_MAX_SIZE = 50; + + public static void registerURLClassLoader(ClassLoader classLoader) { + if (CURRENT_URL_CLASSLOADER_SET.size() < CURRENT_URL_CLASSLOADER_SET_MAX_SIZE && classLoader instanceof URLClassLoader) { + CURRENT_URL_CLASSLOADER_SET.add(classLoader); + } + } + + private static String getVmStartTime() { + long startTime = ManagementFactory.getRuntimeMXBean().getStartTime(); + return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(startTime)); + } + + private static List getVmArgs() { + List vmArgs = ManagementFactory.getRuntimeMXBean().getInputArguments(); + List sortedVmArgs = new ArrayList<>(vmArgs); + Collections.sort(sortedVmArgs); + return sortedVmArgs; + } + + private static List getLibJarNames() { + List classLoaderUrls = loadClassLoaderUrls(); + return extractLibJarNamesFromURLs(classLoaderUrls); + } + + private static List loadClassLoaderUrls() { + List classLoaderUrls = new ArrayList<>(); + for (ClassLoader classLoader : CURRENT_URL_CLASSLOADER_SET) { + try { + URLClassLoader webappClassLoader = (URLClassLoader) classLoader; + URL[] urls = webappClassLoader.getURLs(); + classLoaderUrls.addAll(Arrays.asList(urls)); + } catch (Exception e) { + LOGGER.warn("Load classloader urls exception: {}", e.getMessage()); + } + } + return classLoaderUrls; + } + + private static List extractLibJarNamesFromURLs(List urls) { + Set libJarNames = new HashSet<>(); + for (URL url : urls) { + try { + String libJarName = extractLibJarName(url); + if (libJarName.endsWith(".jar")) { + libJarNames.add(libJarName); + } + } catch (Exception e) { + LOGGER.warn("Extracting library name exception: {}", e.getMessage()); + } + } + List sortedLibJarNames = new ArrayList<>(libJarNames.size()); + if (!CollectionUtil.isEmpty(libJarNames)) { + sortedLibJarNames.addAll(libJarNames); + Collections.sort(sortedLibJarNames); + } + return sortedLibJarNames; + } + + private static String extractLibJarName(URL url) { + String protocol = url.getProtocol(); + if (protocol.equals("file")) { + return extractNameFromFile(url.toString()); + } else if (protocol.equals("jar")) { + return extractNameFromJar(url.toString()); + } else { + return ""; + } + } + + private static String extractNameFromFile(String fileUri) { + int lastIndexOfSeparator = fileUri.lastIndexOf(File.separator); + if (lastIndexOfSeparator < 0) { + return fileUri; + } else { + return fileUri.substring(lastIndexOfSeparator + 1); + } + } + + private static String extractNameFromJar(String jarUri) { + String uri = jarUri.substring(0, jarUri.lastIndexOf(JAR_SEPARATOR)); + return extractNameFromFile(uri); + } + +} \ No newline at end of file diff --git a/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/api/ILog.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/api/ILog.java new file mode 100644 index 0000000000000000000000000000000000000000..c93251838d1d96ef881ce65b45e18650e045c2cd --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/api/LogManager.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/api/LogManager.java new file mode 100644 index 0000000000000000000000000000000000000000..8bb0fe6d5a91fc64aa70920699deb6c58d9e9891 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/api/LogManager.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 com.example.agent.core.logging.api; + + +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/api/LogResolver.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/api/LogResolver.java new file mode 100644 index 0000000000000000000000000000000000000000..3c28a0ad7fb5a441b2b0664ca51d7fcd1e6892ec --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/api/NoopLogger.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/api/NoopLogger.java new file mode 100644 index 0000000000000000000000000000000000000000..8325329f6d95799e9ffd9cf15296625e0437d484 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/AbstractLogger.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/AbstractLogger.java new file mode 100644 index 0000000000000000000000000000000000000000..9e19c36cf43e3dab8dac801ddfceb2a6b4058883 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/AbstractLogger.java @@ -0,0 +1,223 @@ +/* + * 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 com.example.agent.core.logging.core; + + +import com.example.agent.core.conf.Config; +import com.example.agent.core.logging.api.ILog; +import com.example.agent.core.logging.core.converters.AgentNameConverter; +import com.example.agent.core.logging.core.converters.ClassConverter; +import com.example.agent.core.logging.core.converters.DateConverter; +import com.example.agent.core.logging.core.converters.LevelConverter; +import com.example.agent.core.logging.core.converters.MessageConverter; +import com.example.agent.core.logging.core.converters.ThreadConverter; +import com.example.agent.core.logging.core.converters.ThrowableConverter; + +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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/Converter.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/Converter.java new file mode 100644 index 0000000000000000000000000000000000000000..aa7453d7970c347545187161c1ff7142005d7be4 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/FileWriter.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/FileWriter.java new file mode 100644 index 0000000000000000000000000000000000000000..9ecad3cdbf1ac2e2c72f098232fb5b7402f3b385 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/FileWriter.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 com.example.agent.core.logging.core; + +import com.example.agent.core.boot.DefaultNamedThreadFactory; +import com.example.agent.core.conf.Config; +import com.example.agent.core.conf.Constants; +import com.example.agent.core.util.RunnableWithExceptionProtection; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FilenameFilter; +import java.io.IOException; +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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/IWriter.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/IWriter.java new file mode 100644 index 0000000000000000000000000000000000000000..42a07373e55ffefec543b8ef0bc284f8462d5aab --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.logging.core; + +public interface IWriter { + + void write(String message); +} diff --git a/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/JsonLogResolver.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/JsonLogResolver.java new file mode 100644 index 0000000000000000000000000000000000000000..c825b9fb789b5a5fbc64b718a70dfabf445ed558 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/JsonLogResolver.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 com.example.agent.core.logging.core; + + +import com.example.agent.core.logging.api.ILog; +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/JsonLogger.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/JsonLogger.java new file mode 100644 index 0000000000000000000000000000000000000000..7ee4a9f905104bed72a2836b525d4e1bf7c9c3f6 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.logging.core; + +import com.example.agent.core.logging.core.converters.LiteralConverter; +import com.google.gson.Gson; + +import java.util.HashMap; +import java.util.Map; + +/** + * An alternative logger for the cook-async 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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/LogEvent.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/LogEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..7fe3dce498a4240a3807fc596ab7b8248765c216 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/LogLevel.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/LogLevel.java new file mode 100644 index 0000000000000000000000000000000000000000..79283924c542c4fee9caeb53da7e91d55c503ce4 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.logging.core; + +public enum LogLevel { + TRACE, DEBUG, INFO, WARN, ERROR, OFF +} diff --git a/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/LogMessageHolder.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/LogMessageHolder.java new file mode 100644 index 0000000000000000000000000000000000000000..61d3bc31668f5c2a06c1ce7248bfc5acee081211 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/LogOutput.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/LogOutput.java new file mode 100644 index 0000000000000000000000000000000000000000..8e731822d04b5634d255160bd07ed08706080eac --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.logging.core; + +public enum LogOutput { + FILE, CONSOLE +} diff --git a/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/Parser.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/Parser.java new file mode 100644 index 0000000000000000000000000000000000000000..0ca7ed7a2ce6ece3e8e521da0e0aeba467c6aa6c --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.logging.core; + +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/PatternLogResolver.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/PatternLogResolver.java new file mode 100644 index 0000000000000000000000000000000000000000..8834d34620ef1aa8b8a90d8490e0acad487e993d --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/PatternLogResolver.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 com.example.agent.core.logging.core; + + +import com.example.agent.core.conf.Config; +import com.example.agent.core.logging.api.ILog; +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/PatternLogger.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/PatternLogger.java new file mode 100644 index 0000000000000000000000000000000000000000..e9ac32f7dd1f8da64857d94046510fb6edf634cb --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/PatternLogger.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 com.example.agent.core.logging.core; + + +import com.example.agent.core.logging.api.ILog; +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/ResolverType.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/ResolverType.java new file mode 100644 index 0000000000000000000000000000000000000000..94adf50728a49369e4cbc14b4387c8f27ee61570 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.logging.core; + +public enum ResolverType { + JSON, PATTERN +} diff --git a/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/SystemOutWriter.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/SystemOutWriter.java new file mode 100644 index 0000000000000000000000000000000000000000..da4083051b4544c9a3cd0aefef72c431c4feffa4 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/WriterFactory.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/WriterFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..8624dc791e6fe1aeabf60e8aa3f68ebd7586d279 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/WriterFactory.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 com.example.agent.core.logging.core; + +import com.example.agent.core.boot.AgentPackageNotFoundException; +import com.example.agent.core.boot.AgentPackagePath; +import com.example.agent.core.conf.Config; +import com.example.agent.core.conf.SnifferConfigInitializer; +import com.example.agent.core.plugin.PluginFinder; +import com.example.agent.core.util.StringUtil; + +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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/converters/AgentNameConverter.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/converters/AgentNameConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..f811107eb985dabf3424db55f9df53da4c1303bd --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/converters/AgentNameConverter.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 com.example.agent.core.logging.core.converters; + + +import com.example.agent.core.conf.Config; +import com.example.agent.core.logging.core.Converter; +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/converters/ClassConverter.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/converters/ClassConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..d02003e531516f8ac0ed480aaf13b3db9123187f --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/converters/ClassConverter.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 com.example.agent.core.logging.core.converters; + + +import com.example.agent.core.logging.core.Converter; +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/converters/DateConverter.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/converters/DateConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..60c75b416de7cc58ce65160a5f771e5834c44bf3 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/converters/DateConverter.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 com.example.agent.core.logging.core.converters; + + +import com.example.agent.core.logging.core.Converter; +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/converters/LevelConverter.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/converters/LevelConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..cbdb5f60c78f256c37da9fecffdf427cf3c86023 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.logging.core.converters; + +import com.example.agent.core.logging.core.Converter; +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/converters/LiteralConverter.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/converters/LiteralConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..17d0cc38b02efedbbaf831f784bcd6931b3b9922 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/converters/LiteralConverter.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 com.example.agent.core.logging.core.converters; + + +import com.example.agent.core.logging.core.Converter; +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/converters/MessageConverter.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/converters/MessageConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..b3ccbfa9aae4da4fe25357ef6bbe3c382a028775 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/converters/MessageConverter.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 com.example.agent.core.logging.core.converters; + + +import com.example.agent.core.logging.core.Converter; +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/converters/ThreadConverter.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/converters/ThreadConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..b9c021ff514049f9a6fb5cd8b5340167fd9636da --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/converters/ThreadConverter.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 com.example.agent.core.logging.core.converters; + + +import com.example.agent.core.logging.core.Converter; +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/converters/ThrowableConverter.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/converters/ThrowableConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..9724021050696afbcc245dc8649fcf149cee415f --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/logging/core/converters/ThrowableConverter.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 com.example.agent.core.logging.core.converters; + + +import com.example.agent.core.conf.Constants; +import com.example.agent.core.logging.core.Converter; +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/os/OSUtil.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/os/OSUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..351ad3702db582312b9f4b72b3db7189b36bb2f9 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/os/OSUtil.java @@ -0,0 +1,104 @@ +/* + * 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 com.example.agent.core.os; + +import java.lang.management.ManagementFactory; +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.net.UnknownHostException; +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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/os/ProcessorUtil.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/os/ProcessorUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..0847d2a74d025760f4daefcc10c65c32c30b9ed8 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.os; + +import java.lang.management.ManagementFactory; + +public class ProcessorUtil { + + public static int getNumberOfProcessors() { + return ManagementFactory.getOperatingSystemMXBean().getAvailableProcessors(); + } +} diff --git a/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/AbstractClassEnhancePluginDefine.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/AbstractClassEnhancePluginDefine.java new file mode 100644 index 0000000000000000000000000000000000000000..fe3c97fd9d49917bcfa9d33f0c8584c9acd84503 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin; + +import com.example.agent.core.logging.api.ILog; +import com.example.agent.core.logging.api.LogManager; +import com.example.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import com.example.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import com.example.agent.core.plugin.interceptor.StaticMethodsInterceptPoint; +import com.example.agent.core.plugin.interceptor.enhance.ClassEnhancePluginDefine; +import com.example.agent.core.plugin.interceptor.v2.InstanceMethodsInterceptV2Point; +import com.example.agent.core.plugin.interceptor.v2.StaticMethodsInterceptV2Point; +import com.example.agent.core.plugin.match.ClassMatch; +import com.example.agent.core.util.CollectionUtil; +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/ByteBuddyCoreClasses.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/ByteBuddyCoreClasses.java new file mode 100644 index 0000000000000000000000000000000000000000..5b4a5c83c92699661fd9754e8de63dd3fb6bf0a1 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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 = "com.example.agent.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/DynamicPluginLoader.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/DynamicPluginLoader.java new file mode 100644 index 0000000000000000000000000000000000000000..29e2a9923033d0f11b3fe3dd379cfd3ea6b83210 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin; + +import com.example.agent.core.plugin.loader.AgentClassLoader; +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/EnhanceContext.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/EnhanceContext.java new file mode 100644 index 0000000000000000000000000000000000000000..9c84fc1df2e64febf8c752e4ccde9b3ebb2da92c --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin; + +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/InstrumentDebuggingClass.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/InstrumentDebuggingClass.java new file mode 100644 index 0000000000000000000000000000000000000000..5e5dd1c0608a42dc6237b553cb36f014dcd095bf --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin; + +import com.example.agent.core.boot.AgentPackageNotFoundException; +import com.example.agent.core.boot.AgentPackagePath; +import com.example.agent.core.conf.Config; +import com.example.agent.core.logging.api.ILog; +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/PluginBootstrap.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/PluginBootstrap.java new file mode 100644 index 0000000000000000000000000000000000000000..e4f3be145851cb3b88969d0c9d607cf3e4f0a675 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin; + +import com.example.agent.core.boot.AgentPackageNotFoundException; +import com.example.agent.core.logging.api.ILog; +import com.example.agent.core.logging.api.LogManager; +import com.example.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 (cook-async-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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/PluginCfg.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/PluginCfg.java new file mode 100644 index 0000000000000000000000000000000000000000..7b769014ef11d48d63afef3fa2acbe6befacb8c7 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin; + +import com.example.agent.core.logging.api.ILog; +import com.example.agent.core.logging.api.LogManager; +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/PluginDefine.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/PluginDefine.java new file mode 100644 index 0000000000000000000000000000000000000000..d2b0bf1f0251a9c93b84917655c93b1a4599437b --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin; + +import com.example.agent.core.plugin.exception.IllegalPluginDefineException; +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/PluginException.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/PluginException.java new file mode 100644 index 0000000000000000000000000000000000000000..5f11a20db0455073e239890f66e9d80c1b5ffa3b --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/PluginFinder.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/PluginFinder.java new file mode 100644 index 0000000000000000000000000000000000000000..2954671a99551abbb7b4ae22e1e86cd65eff793b --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/PluginFinder.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 com.example.agent.core.plugin; + +import com.example.agent.core.plugin.bytebuddy.AbstractJunction; +import com.example.agent.core.plugin.match.ClassMatch; +import com.example.agent.core.plugin.match.IndirectMatch; +import com.example.agent.core.plugin.match.NameMatch; +import com.example.agent.core.plugin.match.ProtectiveShieldMatcher; +import net.bytebuddy.description.NamedElement; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/PluginResourcesResolver.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/PluginResourcesResolver.java new file mode 100644 index 0000000000000000000000000000000000000000..e9475546b15000bb67af14d4d6e4432b56e14a8e --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin; + +import com.example.agent.core.logging.api.ILog; +import com.example.agent.core.logging.api.LogManager; +import com.example.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 'cook-async-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("cook-plugin.def"); + + while (urls.hasMoreElements()) { + URL pluginUrl = urls.nextElement(); + cfgUrlPaths.add(pluginUrl); + LOGGER.info("find cook-async plugin define in {}", pluginUrl); + } + + return cfgUrlPaths; + } catch (IOException e) { + LOGGER.error("read resources failure.", e); + } + return null; + } +} diff --git a/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/PluginSelector.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/PluginSelector.java new file mode 100644 index 0000000000000000000000000000000000000000..7b88f1e8af38a3b586f137e8cba8b95694953ba1 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/PluginSelector.java @@ -0,0 +1,49 @@ +/* + * 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 com.example.agent.core.plugin; + +import com.example.agent.core.conf.Config; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/WitnessFinder.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/WitnessFinder.java new file mode 100644 index 0000000000000000000000000000000000000000..f30dbb0cfdc964f7ad2dddf88f7c298d3cc3c50e --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/WitnessMethod.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/WitnessMethod.java new file mode 100644 index 0000000000000000000000000000000000000000..4f5d28da083d8bb41b408f6aa0d6248d29493baf --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bootstrap/BootstrapInstrumentBoost.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bootstrap/BootstrapInstrumentBoost.java new file mode 100644 index 0000000000000000000000000000000000000000..59d25c50bf31328c3d514c3517f0d9d4971580c0 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bootstrap/BootstrapInstrumentBoost.java @@ -0,0 +1,301 @@ +/* + * 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 com.example.agent.core.plugin.bootstrap; + +import com.example.agent.core.logging.api.ILog; +import com.example.agent.core.logging.api.LogManager; +import com.example.agent.core.plugin.AbstractClassEnhancePluginDefine; +import com.example.agent.core.plugin.ByteBuddyCoreClasses; +import com.example.agent.core.plugin.InstrumentDebuggingClass; +import com.example.agent.core.plugin.PluginException; +import com.example.agent.core.plugin.PluginFinder; +import com.example.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import com.example.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import com.example.agent.core.plugin.interceptor.StaticMethodsInterceptPoint; +import com.example.agent.core.plugin.interceptor.v2.InstanceMethodsInterceptV2Point; +import com.example.agent.core.plugin.interceptor.v2.StaticMethodsInterceptV2Point; +import com.example.agent.core.plugin.jdk9module.JDK9ModuleExporter; +import com.example.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 = { + "com.example.agent.core.plugin.interceptor.enhance.BootstrapInterRuntimeAssist", + "com.example.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor", + "com.example.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor", + "com.example.agent.core.plugin.interceptor.enhance.StaticMethodsAroundInterceptor", + "com.example.agent.core.plugin.bootstrap.IBootstrapLog", + "com.example.agent.core.plugin.interceptor.enhance.EnhancedInstance", + "com.example.agent.core.plugin.interceptor.enhance.OverrideCallable", + "com.example.agent.core.plugin.interceptor.enhance.MethodInterceptResult", + + // interceptor v2 + "com.example.agent.core.plugin.interceptor.enhance.v2.InstanceMethodsAroundInterceptorV2", + "com.example.agent.core.plugin.interceptor.enhance.v2.StaticMethodsAroundInterceptorV2", + "com.example.agent.core.plugin.interceptor.enhance.v2.MethodInvocationContext", + }; + + private static String INSTANCE_METHOD_DELEGATE_TEMPLATE = "com.example.agent.core.plugin.bootstrap.template.InstanceMethodInterTemplate"; + private static String INSTANCE_METHOD_WITH_OVERRIDE_ARGS_DELEGATE_TEMPLATE = "com.example.agent.core.plugin.bootstrap.template.InstanceMethodInterWithOverrideArgsTemplate"; + private static String CONSTRUCTOR_DELEGATE_TEMPLATE = "com.example.agent.core.plugin.bootstrap.template.ConstructorInterTemplate"; + private static String STATIC_METHOD_DELEGATE_TEMPLATE = "com.example.agent.core.plugin.bootstrap.template.StaticMethodInterTemplate"; + private static String STATIC_METHOD_WITH_OVERRIDE_ARGS_DELEGATE_TEMPLATE = "com.example.agent.core.plugin.bootstrap.template.StaticMethodInterWithOverrideArgsTemplate"; + + private static String INSTANCE_METHOD_V2_DELEGATE_TEMPLATE = "com.example.agent.core.plugin.bootstrap.template.v2.InstanceMethodInterV2Template"; + private static String INSTANCE_METHOD_V2_WITH_OVERRIDE_ARGS_DELEGATE_TEMPLATE = "com.example.agent.core.plugin.bootstrap.template.v2.InstanceMethodInterV2WithOverrideArgsTemplate"; + private static String STATIC_METHOD_V2_DELEGATE_TEMPLATE = "com.example.agent.core.plugin.bootstrap.template.v2.StaticMethodInterV2Template"; + private static String STATIC_METHOD_V2_WITH_OVERRIDE_ARGS_DELEGATE_TEMPLATE = "com.example.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 cook-async 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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bootstrap/BootstrapPluginLogBridge.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bootstrap/BootstrapPluginLogBridge.java new file mode 100644 index 0000000000000000000000000000000000000000..17e0c9b2f404ef5e4e7d550dc3ceca4732c133c3 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin.bootstrap; + +import com.example.agent.core.logging.api.ILog; +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bootstrap/IBootstrapLog.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bootstrap/IBootstrapLog.java new file mode 100644 index 0000000000000000000000000000000000000000..d5dc2cfd1da82d1db73c8189e8be66042b06d2ab --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bootstrap/template/ConstructorInterTemplate.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bootstrap/template/ConstructorInterTemplate.java new file mode 100644 index 0000000000000000000000000000000000000000..ab51fe96dddabc7f3484ac472c971b7315122fae --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin.bootstrap.template; + +import com.example.agent.core.plugin.bootstrap.IBootstrapLog; +import com.example.agent.core.plugin.interceptor.enhance.BootstrapInterRuntimeAssist; +import com.example.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bootstrap/template/InstanceMethodInterTemplate.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bootstrap/template/InstanceMethodInterTemplate.java new file mode 100644 index 0000000000000000000000000000000000000000..a2390710f0dd5df8420a2ddd29782d197c549efa --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bootstrap/template/InstanceMethodInterTemplate.java @@ -0,0 +1,125 @@ +/* + * 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 com.example.agent.core.plugin.bootstrap.template; + +import com.example.agent.core.plugin.bootstrap.IBootstrapLog; +import com.example.agent.core.plugin.interceptor.enhance.BootstrapInterRuntimeAssist; +import com.example.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import com.example.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import com.example.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +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 net.bytebuddy.implementation.bind.annotation.This; + +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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bootstrap/template/InstanceMethodInterWithOverrideArgsTemplate.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bootstrap/template/InstanceMethodInterWithOverrideArgsTemplate.java new file mode 100644 index 0000000000000000000000000000000000000000..b94bc16498a5125f819b1c7d85a91bed1355451f --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bootstrap/template/InstanceMethodInterWithOverrideArgsTemplate.java @@ -0,0 +1,125 @@ +/* + * 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 com.example.agent.core.plugin.bootstrap.template; + +import com.example.agent.core.plugin.bootstrap.IBootstrapLog; +import com.example.agent.core.plugin.interceptor.enhance.BootstrapInterRuntimeAssist; +import com.example.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import com.example.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import com.example.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import com.example.agent.core.plugin.interceptor.enhance.OverrideCallable; +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 net.bytebuddy.implementation.bind.annotation.This; + +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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bootstrap/template/StaticMethodInterTemplate.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bootstrap/template/StaticMethodInterTemplate.java new file mode 100644 index 0000000000000000000000000000000000000000..249285e4d25342a85db0036e876a189a0fe8e5e1 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin.bootstrap.template; + +import com.example.agent.core.plugin.bootstrap.IBootstrapLog; +import com.example.agent.core.plugin.interceptor.enhance.BootstrapInterRuntimeAssist; +import com.example.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bootstrap/template/StaticMethodInterWithOverrideArgsTemplate.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bootstrap/template/StaticMethodInterWithOverrideArgsTemplate.java new file mode 100644 index 0000000000000000000000000000000000000000..666143cba77b027bea8ec52bc7d74846d14ba281 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin.bootstrap.template; + +import com.example.agent.core.plugin.bootstrap.IBootstrapLog; +import com.example.agent.core.plugin.interceptor.enhance.BootstrapInterRuntimeAssist; +import com.example.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import com.example.agent.core.plugin.interceptor.enhance.OverrideCallable; +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bootstrap/template/v2/InstanceMethodInterV2Template.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bootstrap/template/v2/InstanceMethodInterV2Template.java new file mode 100644 index 0000000000000000000000000000000000000000..75254fdafe8bd9ca4ccf60dce9bc7d88837d2272 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bootstrap/template/v2/InstanceMethodInterV2Template.java @@ -0,0 +1,128 @@ +/* + * 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 com.example.agent.core.plugin.bootstrap.template.v2; + +import com.example.agent.core.plugin.bootstrap.IBootstrapLog; +import com.example.agent.core.plugin.interceptor.enhance.BootstrapInterRuntimeAssist; +import com.example.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import com.example.agent.core.plugin.interceptor.enhance.v2.InstanceMethodsAroundInterceptorV2; +import com.example.agent.core.plugin.interceptor.enhance.v2.MethodInvocationContext; +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 net.bytebuddy.implementation.bind.annotation.This; + +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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bootstrap/template/v2/InstanceMethodInterV2WithOverrideArgsTemplate.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bootstrap/template/v2/InstanceMethodInterV2WithOverrideArgsTemplate.java new file mode 100644 index 0000000000000000000000000000000000000000..a87d4e665181d4d44dd1bb58457a8acc1f617b9e --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bootstrap/template/v2/InstanceMethodInterV2WithOverrideArgsTemplate.java @@ -0,0 +1,128 @@ +/* + * 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 com.example.agent.core.plugin.bootstrap.template.v2; + +import com.example.agent.core.plugin.bootstrap.IBootstrapLog; +import com.example.agent.core.plugin.interceptor.enhance.BootstrapInterRuntimeAssist; +import com.example.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import com.example.agent.core.plugin.interceptor.enhance.OverrideCallable; +import com.example.agent.core.plugin.interceptor.enhance.v2.InstanceMethodsAroundInterceptorV2; +import com.example.agent.core.plugin.interceptor.enhance.v2.MethodInvocationContext; +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 net.bytebuddy.implementation.bind.annotation.This; + +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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bootstrap/template/v2/StaticMethodInterV2Template.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bootstrap/template/v2/StaticMethodInterV2Template.java new file mode 100644 index 0000000000000000000000000000000000000000..46a439dbc601e071e6e000f921ae8ac0a4cdf28e --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin.bootstrap.template.v2; + +import com.example.agent.core.plugin.bootstrap.IBootstrapLog; +import com.example.agent.core.plugin.interceptor.enhance.BootstrapInterRuntimeAssist; +import com.example.agent.core.plugin.interceptor.enhance.v2.MethodInvocationContext; +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bootstrap/template/v2/StaticMethodInterV2WithOverrideArgsTemplate.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bootstrap/template/v2/StaticMethodInterV2WithOverrideArgsTemplate.java new file mode 100644 index 0000000000000000000000000000000000000000..52fd5a282cfce7ed14926ecd155ab9a1e4769ccd --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin.bootstrap.template.v2; + +import com.example.agent.core.plugin.bootstrap.IBootstrapLog; +import com.example.agent.core.plugin.interceptor.enhance.BootstrapInterRuntimeAssist; +import com.example.agent.core.plugin.interceptor.enhance.OverrideCallable; +import com.example.agent.core.plugin.interceptor.enhance.v2.MethodInvocationContext; +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bytebuddy/AbstractJunction.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bytebuddy/AbstractJunction.java new file mode 100644 index 0000000000000000000000000000000000000000..b68a6ab92338fffac5ee2122f868e73a9ae45227 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bytebuddy/AnnotationTypeNameMatch.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bytebuddy/AnnotationTypeNameMatch.java new file mode 100644 index 0000000000000000000000000000000000000000..f325d03ff8a2d3443fbd069b1296cffb400a3677 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bytebuddy/ArgumentTypeNameMatch.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bytebuddy/ArgumentTypeNameMatch.java new file mode 100644 index 0000000000000000000000000000000000000000..f0b1da838e8af05f831dd98731d3cc2f724308c5 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bytebuddy/ArrayTypeNameChecker.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bytebuddy/ArrayTypeNameChecker.java new file mode 100644 index 0000000000000000000000000000000000000000..7f7e6c91057eac0d7abdb2fd9b97f49a646a8c7d --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bytebuddy/CacheableTransformerDecorator.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bytebuddy/CacheableTransformerDecorator.java new file mode 100644 index 0000000000000000000000000000000000000000..3ac05e5209acd158557e37fcc25091af71bc9401 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bytebuddy/CacheableTransformerDecorator.java @@ -0,0 +1,195 @@ +/* + * 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 com.example.agent.core.plugin.bytebuddy; + +import com.example.agent.core.boot.AgentPackageNotFoundException; +import com.example.agent.core.boot.AgentPackagePath; +import com.example.agent.core.logging.api.ILog; +import com.example.agent.core.logging.api.LogManager; +import com.example.agent.core.util.FileUtils; +import com.example.agent.core.util.IOUtils; +import net.bytebuddy.agent.builder.AgentBuilder; +import net.bytebuddy.agent.builder.ResettableClassFileTransformer; +import net.bytebuddy.utility.RandomString; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +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 cook-async 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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bytebuddy/ClassCacheMode.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bytebuddy/ClassCacheMode.java new file mode 100644 index 0000000000000000000000000000000000000000..a9e1eae4af0d25f7aa9a1189dcd7499de9ca8f54 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin.bytebuddy; + +/** + * ByteBuddy class cache mode + */ +public enum ClassCacheMode { + FILE, MEMORY +} diff --git a/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bytebuddy/ReturnTypeNameMatch.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/bytebuddy/ReturnTypeNameMatch.java new file mode 100644 index 0000000000000000000000000000000000000000..1bac3492e555e5752853467823e6247306b67fc0 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/exception/IllegalPluginDefineException.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/exception/IllegalPluginDefineException.java new file mode 100644 index 0000000000000000000000000000000000000000..a80470e152b99a23407dfd3390d134588b3c8ae5 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin.exception; + +/** + * Thrown to indicate that a illegal format plugin definition has been defined in cook-async-plugin.define. + */ +public class IllegalPluginDefineException extends Exception { + + public IllegalPluginDefineException(String define) { + super("Illegal plugin define : " + define); + } +} diff --git a/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/ConstructorInterceptPoint.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/ConstructorInterceptPoint.java new file mode 100644 index 0000000000000000000000000000000000000000..3f0e592826217245aafdf2b114ac9696034548da --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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 + * com.example.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor} + */ + String getConstructorInterceptor(); +} diff --git a/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/DeclaredInstanceMethodsInterceptPoint.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/DeclaredInstanceMethodsInterceptPoint.java new file mode 100644 index 0000000000000000000000000000000000000000..28d83a0ceb37fd7b658166e6b3342222767a6f0c --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/EnhanceException.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/EnhanceException.java new file mode 100644 index 0000000000000000000000000000000000000000..a728ee3aec337089a9586405b89d4729c3ab9a83 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin.interceptor; + +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/InstanceMethodsInterceptPoint.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/InstanceMethodsInterceptPoint.java new file mode 100644 index 0000000000000000000000000000000000000000..6d5ecd0721982e6523c06361f8feea08772d9cc5 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/StaticMethodsInterceptPoint.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/StaticMethodsInterceptPoint.java new file mode 100644 index 0000000000000000000000000000000000000000..0653def7e0ad6a2f20d5035f2598d53316cd800e --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/BootstrapInterRuntimeAssist.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/BootstrapInterRuntimeAssist.java new file mode 100644 index 0000000000000000000000000000000000000000..5e1f147fc0f00689f377a576ad6bd21f224a244b --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin.interceptor.enhance; + +import com.example.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 = "com.example.agent.core.plugin.loader.AgentClassLoader"; + private static final String DEFAULT_AGENT_CLASSLOADER_INSTANCE = "DEFAULT_LOADER"; + private static final String LOG_MANAGER_CLASS = "com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/ClassEnhancePluginDefine.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/ClassEnhancePluginDefine.java new file mode 100644 index 0000000000000000000000000000000000000000..2a97d7d8adadf4e406027e89fd20eb7904bb6396 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/ClassEnhancePluginDefine.java @@ -0,0 +1,238 @@ +/* + * 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 com.example.agent.core.plugin.interceptor.enhance; + +import com.example.agent.core.logging.api.ILog; +import com.example.agent.core.logging.api.LogManager; +import com.example.agent.core.plugin.AbstractClassEnhancePluginDefine; +import com.example.agent.core.plugin.EnhanceContext; +import com.example.agent.core.plugin.PluginException; +import com.example.agent.core.plugin.bootstrap.BootstrapInstrumentBoost; +import com.example.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import com.example.agent.core.plugin.interceptor.DeclaredInstanceMethodsInterceptPoint; +import com.example.agent.core.plugin.interceptor.EnhanceException; +import com.example.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import com.example.agent.core.plugin.interceptor.StaticMethodsInterceptPoint; +import com.example.agent.core.plugin.interceptor.v2.InstanceMethodsInterceptV2Point; +import com.example.agent.core.plugin.interceptor.v2.StaticMethodsInterceptV2Point; +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/ClassInstanceMethodsEnhancePluginDefine.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/ClassInstanceMethodsEnhancePluginDefine.java new file mode 100644 index 0000000000000000000000000000000000000000..45ce1cdbadc8e81054b146da972f345b42ebe1a1 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin.interceptor.enhance; + +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/ClassStaticMethodsEnhancePluginDefine.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/ClassStaticMethodsEnhancePluginDefine.java new file mode 100644 index 0000000000000000000000000000000000000000..cae02f09fe911e00fc14ed0bf5657819946ca19c --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin.interceptor.enhance; + +import com.example.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/ConstructorInter.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/ConstructorInter.java new file mode 100644 index 0000000000000000000000000000000000000000..6402866ad6c86a32e463c4f79196b820b20e5898 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin.interceptor.enhance; + +import com.example.agent.core.logging.api.ILog; +import com.example.agent.core.logging.api.LogManager; +import com.example.agent.core.plugin.PluginException; +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/EnhancedInstance.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/EnhancedInstance.java new file mode 100644 index 0000000000000000000000000000000000000000..f6aa859315121f8376662b2e58522acfda3a9548 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin.interceptor.enhance; + +public interface EnhancedInstance { + + Object getCookDynamicField(); + + void setCookDynamicField(Object value); +} diff --git a/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/InstMethodsInter.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/InstMethodsInter.java new file mode 100644 index 0000000000000000000000000000000000000000..af54cea82cbedaa44b87aafbe7afb1fd7b829a56 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/InstMethodsInter.java @@ -0,0 +1,105 @@ +/* + * 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 com.example.agent.core.plugin.interceptor.enhance; + +import com.example.agent.core.logging.api.ILog; +import com.example.agent.core.logging.api.LogManager; +import com.example.agent.core.plugin.PluginException; +import com.example.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 net.bytebuddy.implementation.bind.annotation.This; + +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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/InstMethodsInterWithOverrideArgs.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/InstMethodsInterWithOverrideArgs.java new file mode 100644 index 0000000000000000000000000000000000000000..1aae7652ef71819e3c7a9b05668f6cc2465fa948 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/InstMethodsInterWithOverrideArgs.java @@ -0,0 +1,104 @@ +/* + * 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 com.example.agent.core.plugin.interceptor.enhance; + +import com.example.agent.core.logging.api.ILog; +import com.example.agent.core.logging.api.LogManager; +import com.example.agent.core.plugin.PluginException; +import com.example.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 net.bytebuddy.implementation.bind.annotation.This; + +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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/InstanceConstructorInterceptor.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/InstanceConstructorInterceptor.java new file mode 100644 index 0000000000000000000000000000000000000000..862a2e4d4ed09cfaccd317117744837f17deed76 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/InstanceMethodsAroundInterceptor.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/InstanceMethodsAroundInterceptor.java new file mode 100644 index 0000000000000000000000000000000000000000..c449674a2464c29a4b646169246ddcb5df9ee1a6 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/MethodInterceptResult.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/MethodInterceptResult.java new file mode 100644 index 0000000000000000000000000000000000000000..cee5a76c5af4e09f2740bc88d15cb083198084b4 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/OverrideCallable.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/OverrideCallable.java new file mode 100644 index 0000000000000000000000000000000000000000..4765bb74b2afa33dbeddb0f99b6878c51b74f2c5 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin.interceptor.enhance; + +public interface OverrideCallable { + + Object call(Object[] args); +} diff --git a/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/StaticMethodsAroundInterceptor.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/StaticMethodsAroundInterceptor.java new file mode 100644 index 0000000000000000000000000000000000000000..ad030447e6bcdc2e574e63f0a02caacd170016d2 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/StaticMethodsInter.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/StaticMethodsInter.java new file mode 100644 index 0000000000000000000000000000000000000000..b357bd402572c86db2112d750c5e313ab319e4c9 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin.interceptor.enhance; + +import com.example.agent.core.logging.api.ILog; +import com.example.agent.core.logging.api.LogManager; +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/StaticMethodsInterWithOverrideArgs.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/StaticMethodsInterWithOverrideArgs.java new file mode 100644 index 0000000000000000000000000000000000000000..59e310e4be5945e0585f20f411bcc50b1e0dcc61 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin.interceptor.enhance; + +import com.example.agent.core.logging.api.ILog; +import com.example.agent.core.logging.api.LogManager; +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/v2/ClassEnhancePluginDefineV2.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/v2/ClassEnhancePluginDefineV2.java new file mode 100644 index 0000000000000000000000000000000000000000..5740976ad259e24aa7ba29faf57b62bd1ffa0cb5 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin.interceptor.enhance.v2; + +import com.example.agent.core.plugin.AbstractClassEnhancePluginDefine; +import com.example.agent.core.plugin.EnhanceContext; +import com.example.agent.core.plugin.PluginException; +import com.example.agent.core.plugin.bootstrap.BootstrapInstrumentBoost; +import com.example.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import com.example.agent.core.plugin.interceptor.EnhanceException; +import com.example.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import com.example.agent.core.plugin.interceptor.StaticMethodsInterceptPoint; +import com.example.agent.core.plugin.interceptor.enhance.ConstructorInter; +import com.example.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import com.example.agent.core.plugin.interceptor.enhance.OverrideCallable; +import com.example.agent.core.plugin.interceptor.v2.ConstructorInterceptV2Point; +import com.example.agent.core.plugin.interceptor.v2.DeclaredInstanceMethodsInterceptV2Point; +import com.example.agent.core.plugin.interceptor.v2.InstanceMethodsInterceptV2Point; +import com.example.agent.core.plugin.interceptor.v2.StaticMethodsInterceptV2Point; +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/v2/ClassInstanceMethodsEnhancePluginDefineV2.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/v2/ClassInstanceMethodsEnhancePluginDefineV2.java new file mode 100644 index 0000000000000000000000000000000000000000..58cee5a30eff3896235c159bc552ff00d685fa76 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin.interceptor.enhance.v2; + +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/v2/ClassStaticMethodsEnhancePluginDefineV2.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/v2/ClassStaticMethodsEnhancePluginDefineV2.java new file mode 100644 index 0000000000000000000000000000000000000000..52914da37c0043689d91fbcc3afca10514e25e2c --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin.interceptor.enhance.v2; + +import com.example.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/v2/InstMethodsInterV2.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/v2/InstMethodsInterV2.java new file mode 100644 index 0000000000000000000000000000000000000000..2646dfc309d7ff41f89e9fa281a7854db65d03f0 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/v2/InstMethodsInterV2.java @@ -0,0 +1,87 @@ +/* + * 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 com.example.agent.core.plugin.interceptor.enhance.v2; + +import com.example.agent.core.logging.api.ILog; +import com.example.agent.core.logging.api.LogManager; +import com.example.agent.core.plugin.PluginException; +import com.example.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import com.example.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 net.bytebuddy.implementation.bind.annotation.This; + +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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/v2/InstMethodsInterV2WithOverrideArgs.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/v2/InstMethodsInterV2WithOverrideArgs.java new file mode 100644 index 0000000000000000000000000000000000000000..950ba5aeaf296a75d5d9537e747b7046f85b30b3 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/v2/InstMethodsInterV2WithOverrideArgs.java @@ -0,0 +1,106 @@ +/* + * 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 com.example.agent.core.plugin.interceptor.enhance.v2; + +import com.example.agent.core.logging.api.ILog; +import com.example.agent.core.logging.api.LogManager; +import com.example.agent.core.plugin.PluginException; +import com.example.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import com.example.agent.core.plugin.interceptor.enhance.OverrideCallable; +import com.example.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 net.bytebuddy.implementation.bind.annotation.This; + +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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/v2/InstanceMethodsAroundInterceptorV2.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/v2/InstanceMethodsAroundInterceptorV2.java new file mode 100644 index 0000000000000000000000000000000000000000..fa75d590cafffa4dd79974cb23703dc43c0b8220 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin.interceptor.enhance.v2; + +import com.example.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/v2/MethodInvocationContext.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/v2/MethodInvocationContext.java new file mode 100644 index 0000000000000000000000000000000000000000..2bfd482de6bc1a08ea53a26666bca00d7407f155 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin.interceptor.enhance.v2; + +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/v2/StaticMethodsAroundInterceptorV2.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/v2/StaticMethodsAroundInterceptorV2.java new file mode 100644 index 0000000000000000000000000000000000000000..5d463b85b28e969d5ba6e305a1846acf056a035a --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/v2/StaticMethodsInterV2.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/v2/StaticMethodsInterV2.java new file mode 100644 index 0000000000000000000000000000000000000000..e3de40e61aeaa9bd382ae1a88a064c1c2e699f58 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin.interceptor.enhance.v2; + +import com.example.agent.core.logging.api.ILog; +import com.example.agent.core.logging.api.LogManager; +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/v2/StaticMethodsInterV2WithOverrideArgs.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/enhance/v2/StaticMethodsInterV2WithOverrideArgs.java new file mode 100644 index 0000000000000000000000000000000000000000..c9d1e6c8c3cbd17d244a62d9e9e4251cd99a2abc --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin.interceptor.enhance.v2; + +import com.example.agent.core.logging.api.ILog; +import com.example.agent.core.logging.api.LogManager; +import com.example.agent.core.plugin.interceptor.enhance.OverrideCallable; +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/v2/ConstructorInterceptV2Point.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/v2/ConstructorInterceptV2Point.java new file mode 100644 index 0000000000000000000000000000000000000000..6707fe0bb2cb9440aff4931dee9cd000aa5ce99b --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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 + * com.example.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor} + */ + String getConstructorInterceptorV2(); + +} diff --git a/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/v2/DeclaredInstanceMethodsInterceptV2Point.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/v2/DeclaredInstanceMethodsInterceptV2Point.java new file mode 100644 index 0000000000000000000000000000000000000000..fed7531839fbfa1b9ed3a64d5229ba2f748bb5d9 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/v2/InstanceMethodsInterceptV2Point.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/v2/InstanceMethodsInterceptV2Point.java new file mode 100644 index 0000000000000000000000000000000000000000..2fd473d9db8e4911f55d392432b78cc191322bc4 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin.interceptor.v2; + +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/v2/StaticMethodsInterceptV2Point.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/interceptor/v2/StaticMethodsInterceptV2Point.java new file mode 100644 index 0000000000000000000000000000000000000000..bcff4221ff2703730565cd95e97553c6b24693e5 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin.interceptor.v2; + +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/jdk9module/JDK9ModuleExporter.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/jdk9module/JDK9ModuleExporter.java new file mode 100644 index 0000000000000000000000000000000000000000..7d7d87b477568fc16bdf2fb231288b1931417268 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin.jdk9module; + +import com.example.agent.core.logging.api.ILog; +import com.example.agent.core.logging.api.LogManager; +import com.example.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 = { + "com.example.agent.core.plugin.interceptor.enhance.EnhancedInstance", + "com.example.agent.core.plugin.interceptor.enhance.MethodInterceptResult", + "com.example.agent.core.plugin.interceptor.enhance.OverrideCallable", + "com.example.agent.core.plugin.interceptor.enhance.ConstructorInter", + "com.example.agent.core.plugin.interceptor.enhance.InstMethodsInter", + "com.example.agent.core.plugin.interceptor.enhance.InstMethodsInterWithOverrideArgs", + "com.example.agent.core.plugin.interceptor.enhance.StaticMethodsInter", + "com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/loader/AgentClassLoader.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/loader/AgentClassLoader.java new file mode 100644 index 0000000000000000000000000000000000000000..120aefc53323e91f8de32c1e0039204280c3dd6f --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/loader/AgentClassLoader.java @@ -0,0 +1,225 @@ +/* + * 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 com.example.agent.core.plugin.loader; + +import com.example.agent.core.boot.AgentPackageNotFoundException; +import com.example.agent.core.boot.AgentPackagePath; +import com.example.agent.core.boot.PluginConfig; +import com.example.agent.core.boot.SpringBootConfigInitializer; +import com.example.agent.core.boot.SpringBootConfigNode; +import com.example.agent.core.conf.Config; +import com.example.agent.core.conf.SnifferConfigInitializer; +import com.example.agent.core.logging.api.ILog; +import com.example.agent.core.logging.api.LogManager; +import com.example.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()); + } + + final SpringBootConfigNode springBootConfig = loadedClass.getAnnotation(SpringBootConfigNode.class); + if (springBootConfig != null) { + // Set up the plugin config when loaded by spring environment is prepared, just scan in here. + // Agent class loader just loaded limited classes in the plugin jar(s), so the cost of this + // isAssignableFrom would be also very limited. + SpringBootConfigInitializer.initializeConfig(springBootConfig); + } + 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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/loader/InstrumentationLoader.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/loader/InstrumentationLoader.java new file mode 100644 index 0000000000000000000000000000000000000000..0f2ffb094e53a569e831d231de7105733d87af41 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin.loader; + +import com.example.agent.core.plugin.AbstractClassEnhancePluginDefine; + +import java.util.List; + +/** + * the spi of the InstrumentationLoader. + */ + +public interface InstrumentationLoader { + + List load(AgentClassLoader classLoader); +} diff --git a/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/loader/InterceptorInstanceLoader.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/loader/InterceptorInstanceLoader.java new file mode 100644 index 0000000000000000000000000000000000000000..06db75a739f3137b0dbe903eb85d9698208a374d --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin.loader; + +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/match/ClassAnnotationMatch.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/match/ClassAnnotationMatch.java new file mode 100644 index 0000000000000000000000000000000000000000..d48295883eba0ede989481bb46a22931ff74b3ea --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/match/ClassAnnotationMatch.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 com.example.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.isAnnotatedWith; +import static net.bytebuddy.matcher.ElementMatchers.isInterface; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.not; + +/** + * 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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/match/ClassMatch.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/match/ClassMatch.java new file mode 100644 index 0000000000000000000000000000000000000000..839a2113cc8fed63d27e1e5cb50dade4d389bfc0 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin.match; + +public interface ClassMatch { +} diff --git a/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/match/HierarchyMatch.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/match/HierarchyMatch.java new file mode 100644 index 0000000000000000000000000000000000000000..ed990a22c708c82dc7284351aba0d5a448b95d26 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/match/HierarchyMatch.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 com.example.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.hasSuperType; +import static net.bytebuddy.matcher.ElementMatchers.isInterface; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.not; + +/** + * 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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/match/IndirectMatch.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/match/IndirectMatch.java new file mode 100644 index 0000000000000000000000000000000000000000..086d848b123d82e2bc1f6de846acdbff5b2c13ee --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/match/MethodAnnotationMatch.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/match/MethodAnnotationMatch.java new file mode 100644 index 0000000000000000000000000000000000000000..d8a5c99f30fdcd2101b5fc47e94d7d07bcc04a88 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/match/MethodAnnotationMatch.java @@ -0,0 +1,88 @@ +/* + * 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 com.example.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.declaresMethod; +import static net.bytebuddy.matcher.ElementMatchers.isAnnotatedWith; +import static net.bytebuddy.matcher.ElementMatchers.isInterface; +import static net.bytebuddy.matcher.ElementMatchers.named; + +/** + * 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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/match/MethodInheritanceAnnotationMatcher.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/match/MethodInheritanceAnnotationMatcher.java new file mode 100644 index 0000000000000000000000000000000000000000..0ff4bc788f7511b109800eba1b5035f119796acc --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/match/MultiClassNameMatch.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/match/MultiClassNameMatch.java new file mode 100644 index 0000000000000000000000000000000000000000..7bcf3fac78c30bec4b5d5408297b802a5f3c03fd --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/match/NameMatch.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/match/NameMatch.java new file mode 100644 index 0000000000000000000000000000000000000000..95dc4b2e711befe51c4fc0304669856385f9dc58 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/match/PrefixMatch.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/match/PrefixMatch.java new file mode 100644 index 0000000000000000000000000000000000000000..aae7fab50650a07efe89944f1ad2620b118685b6 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/match/ProtectiveShieldMatcher.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/match/ProtectiveShieldMatcher.java new file mode 100644 index 0000000000000000000000000000000000000000..8c8b47dfe115ef9c90acf7045b9aeff0e0d7362c --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin.match; + +import com.example.agent.core.logging.api.ILog; +import com.example.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 + * cook-async 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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/match/RegexMatch.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/match/RegexMatch.java new file mode 100644 index 0000000000000000000000000000000000000000..0655784723c06ad42da4562473d570872e5ccefd --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/match/logical/LogicalAndMatch.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/match/logical/LogicalAndMatch.java new file mode 100644 index 0000000000000000000000000000000000000000..36fbf1c1a337c8d3299839a3eff7d02fa3e6556c --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin.match.logical; + +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/match/logical/LogicalMatchOperation.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/match/logical/LogicalMatchOperation.java new file mode 100644 index 0000000000000000000000000000000000000000..7f7c50ec7ef929070a0f00fcff1623f35908f91f --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin.match.logical; + +import com.example.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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/match/logical/LogicalOrMatch.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/plugin/match/logical/LogicalOrMatch.java new file mode 100644 index 0000000000000000000000000000000000000000..b24993c8184097a3f1b564d4d11682f75532718b --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.plugin.match.logical; + +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/AgentThreadPoolConstants.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/AgentThreadPoolConstants.java new file mode 100644 index 0000000000000000000000000000000000000000..b51fa6c9bac165304ce6dcd0e589d0ba06340cef --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/AgentThreadPoolConstants.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 com.example.agent.core.util; + +public interface AgentThreadPoolConstants { + + String TOMCAT_NAME_PREFIX = "namePrefix"; + String DUBBO_NAME_PREFIX = "mPrefix"; + String DUBBO_THREAD_NAME = "DubboServerHandler"; + String THREAD_POOL_NAME_DUBBO = "dubbo"; + +} diff --git a/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/CollectionUtil.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/CollectionUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..f450c2ca596969cd39cdf8d1c5759f0087e79683 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/CollectionUtil.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 com.example.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(); + } + + /** + * Is empty. + * + * @param map + * @return + */ + public static boolean isEmpty(Map map) { + return map == null || map.isEmpty(); + } + + /** + * Is not empty. + * + * @param map + * @return + */ + public static boolean isNotEmpty(Map map) { + return !isEmpty(map); + } +} diff --git a/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/ConfigInitializer.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/ConfigInitializer.java new file mode 100644 index 0000000000000000000000000000000000000000..27cc5c1c9613e91f3c56045c56cc4de3f4f54f9a --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/ConfigInitializer.java @@ -0,0 +1,213 @@ +/* + * 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 com.example.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.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Properties; + +/** + * 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(), false); + } + + public static void initialize(Properties properties, Class rootConfigType, boolean isSpringProperties) throws IllegalAccessException { + initNextLevel(properties, rootConfigType, new ConfigDesc(), isSpringProperties); + } + + private static void initNextLevel(Properties properties, Class recentConfigType, + ConfigDesc parentDesc, boolean isSpringProperties) throws IllegalArgumentException, IllegalAccessException { + for (Field field : recentConfigType.getFields()) { + if (Modifier.isPublic(field.getModifiers()) && Modifier.isStatic(field.getModifiers())) { + String configKey = (parentDesc + "." + (isSpringProperties ? field.getName().replace("_", "-") : 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()) { + String simpleName = innerConfiguration.getSimpleName(); + String description = isSpringProperties ? simpleName.replace("_", "-") : simpleName; + parentDesc.append(description); + initNextLevel(properties, innerConfiguration, parentDesc, isSpringProperties); + 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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/CustomizeExpression.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/CustomizeExpression.java new file mode 100644 index 0000000000000000000000000000000000000000..ee8408f4db40e1044f2f5942ce627d6ef79ef893 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.agent.core.util; + +import com.example.agent.core.logging.api.ILog; +import com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/ExecutorNameUtil.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/ExecutorNameUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..217f193a63d75bf47c1a94a65d7ecbce94d5fc51 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/ExecutorNameUtil.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 com.example.agent.core.util; + +import com.example.agent.core.logging.api.ILog; +import com.example.agent.core.logging.api.LogManager; + +import java.lang.reflect.Field; + +public class ExecutorNameUtil { + + private static final ILog LOGGER = LogManager.getLogger(ExecutorNameUtil.class); + + public static boolean isTomcatExecutor(Object threadFactory) { + try { + if ("org.apache.tomcat.util.threads.TaskThreadFactory".equals(threadFactory.getClass().getName())) { + Field namePrefixField = threadFactory.getClass().getDeclaredField(AgentThreadPoolConstants.TOMCAT_NAME_PREFIX); + namePrefixField.setAccessible(true); + String namePrefix = (String) namePrefixField.get(threadFactory); + if (RegexUtil.isTomcatNameMatch(namePrefix)) { + return true; + } + } + } catch (Throwable t) { + LOGGER.error("Fail to put tomcat executor", t); + } + return false; + } +} diff --git a/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/FileUtils.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/FileUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..d69c47a843521e492597591736bd65fd83e6da38 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/IOUtils.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/IOUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..0a5cbccc4ba8c9cea89215bfa8f113039ab5f69e --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/IOUtils.java @@ -0,0 +1,145 @@ +/* + * 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 com.example.agent.core.util; + +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * 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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/Length.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/Length.java new file mode 100644 index 0000000000000000000000000000000000000000..f677a69a47cfeab4d9c829faed9cf14e65318d64 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/MethodUtil.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/MethodUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..d71e79a96daf09b47a9fe3d2a04904a3c5d4f7a4 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/PlaceholderConfigurerSupport.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/PlaceholderConfigurerSupport.java new file mode 100644 index 0000000000000000000000000000000000000000..3dc200fec608fc537a63b4ba6e0a2e0ba925dbd0 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/PrivateKeyUtil.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/PrivateKeyUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..52e9a0fe885a188044de2461cd4bf62c6d31f543 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/PropertyPlaceholderHelper.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/PropertyPlaceholderHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..15248be44310f58ce7cc3cdc49fb493606698c18 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/PropertyPlaceholderHelper.java @@ -0,0 +1,208 @@ +/* + * 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 com.example.agent.core.util; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +/** + * 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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/ReflectUtil.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/ReflectUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..091e5b273844ce7a7470ae1d6d3ed1dae9a89080 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/ReflectUtil.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 com.example.agent.core.util; + +import com.example.agent.core.logging.api.ILog; +import com.example.agent.core.logging.api.LogManager; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; + +public class ReflectUtil { + + private static final ILog LOGGER = LogManager.getLogger(ReflectUtil.class); + + public static List getStaticFieldsFromType(Class clazz, Class declaredType) { + Field[] fields = clazz.getDeclaredFields(); + List result = new ArrayList<>(); + for (Field field : fields) { + if (field.getType().isAssignableFrom(declaredType) + && Modifier.isStatic(field.getModifiers())) { + field.setAccessible(true); + result.add(field); + } + } + return result; + } +} diff --git a/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/RegexUtil.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/RegexUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..3b1162db4aeffce7b726abe5f5b3e9ed233cd4b5 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/RegexUtil.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 com.example.agent.core.util; + +import java.util.regex.Pattern; + +public class RegexUtil { + + private static final String TOMCAT_NAME_PATTERN_STRING = "http\\S+nio\\S+-exec-"; + private static final Pattern TOMCAT_NAME_PATTERN = Pattern.compile(TOMCAT_NAME_PATTERN_STRING); + + public static boolean isTomcatNameMatch(String executorName) { + return TOMCAT_NAME_PATTERN.matcher(executorName).find(); + } +} diff --git a/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/RunnableWithExceptionProtection.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/RunnableWithExceptionProtection.java new file mode 100644 index 0000000000000000000000000000000000000000..f4ca6b9d1f16e24194971b3f18747198f4932f19 --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/StringUtil.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/StringUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..250c1d2561172d62fa2fb35a1f506299d4682f7a --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/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 com.example.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/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/ThreadPoolPropertyKey.java b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/ThreadPoolPropertyKey.java new file mode 100644 index 0000000000000000000000000000000000000000..c39a044c4d327b0a12e6f90228d95ed1fdc4f4da --- /dev/null +++ b/cook-async-agent/cook-async-agent-core/src/main/java/com/example/agent/core/util/ThreadPoolPropertyKey.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 com.example.agent.core.util; + +public interface ThreadPoolPropertyKey { + + String THREAD_POOL_ID = "threadPoolId"; + + String CORE_POOL_SIZE = "corePoolSize"; + + String MAXIMUM_POOL_SIZE = "maximumPoolSize"; + + String ALLOW_CORE_THREAD_TIME_OUT = "allowCoreThreadTimeOut"; + + String KEEP_ALIVE_TIME = "keepAliveTime"; + + String BLOCKING_QUEUE = "blockingQueue"; + + String QUEUE_CAPACITY = "queueCapacity"; + + String REJECTED_HANDLER = "rejectedHandler"; + + String EXECUTE_TIME_OUT = "executeTimeOut"; +} diff --git a/cook-async-agent/cook-async-agent-plugin/cook-async-agent-threadpool-plugin/pom.xml b/cook-async-agent/cook-async-agent-plugin/cook-async-agent-threadpool-plugin/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..8956f0a8ca0b934c32d61bf661f29cd29f4c78bb --- /dev/null +++ b/cook-async-agent/cook-async-agent-plugin/cook-async-agent-threadpool-plugin/pom.xml @@ -0,0 +1,39 @@ + + + 4.0.0 + + com.example + cook-async-agent-plugin + ${revision} + + + cook-async-agent-threadpool-plugin + + + + com.example + threadlocal-tool + ${revision} + + + javax.servlet + javax.servlet-api + 4.0.1 + provided + + + + org.springframework + spring-webmvc + 5.2.15.RELEASE + provided + + + org.slf4j + slf4j-api + provided + + + \ No newline at end of file diff --git a/cook-async-agent/cook-async-agent-plugin/cook-async-agent-threadpool-plugin/src/main/java/com/example/agent/plugin/thread/AbstractThreadingPoolInterceptor.java b/cook-async-agent/cook-async-agent-plugin/cook-async-agent-threadpool-plugin/src/main/java/com/example/agent/plugin/thread/AbstractThreadingPoolInterceptor.java new file mode 100644 index 0000000000000000000000000000000000000000..993a382ada91a4046d188ecb7491b6c2985a9d64 --- /dev/null +++ b/cook-async-agent/cook-async-agent-plugin/cook-async-agent-threadpool-plugin/src/main/java/com/example/agent/plugin/thread/AbstractThreadingPoolInterceptor.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 com.example.agent.plugin.thread; + + +import com.example.ParameterHolder; +import com.example.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import com.example.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import com.example.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.slf4j.MDC; + +import java.lang.reflect.Method; +import java.util.Map; + +public abstract class AbstractThreadingPoolInterceptor implements InstanceMethodsAroundInterceptor { + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, MethodInterceptResult result) throws Throwable { + + + if (allArguments == null || allArguments.length < 1) { + return; + } + + Object argument = allArguments[0]; + + Object wrappedObject = wrap(argument); + if (wrappedObject != null) { + allArguments[0] = wrappedObject; + } + } + public abstract Object wrap(Object param); + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Object ret) throws Throwable { + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Throwable t) { + + } + + protected static Map getContextForTask() { + return MDC.getCopyOfContextMap(); + } + + protected static Map getContextForHold() { + return ParameterHolder.getParameterMap(); + } +} diff --git a/cook-async-agent/cook-async-agent-plugin/cook-async-agent-threadpool-plugin/src/main/java/com/example/agent/plugin/thread/ThreadPoolExecuteMethodInterceptor.java b/cook-async-agent/cook-async-agent-plugin/cook-async-agent-threadpool-plugin/src/main/java/com/example/agent/plugin/thread/ThreadPoolExecuteMethodInterceptor.java new file mode 100644 index 0000000000000000000000000000000000000000..be9bf789b96939179f489f42325fb05850424009 --- /dev/null +++ b/cook-async-agent/cook-async-agent-plugin/cook-async-agent-threadpool-plugin/src/main/java/com/example/agent/plugin/thread/ThreadPoolExecuteMethodInterceptor.java @@ -0,0 +1,58 @@ +/* + * 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 com.example.agent.plugin.thread; + + +import com.example.agent.plugin.thread.enhance.ServiceRequestEnhanceOperation; +import com.example.agent.plugin.thread.wrapper.RunnableWrapper; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; + +import java.util.Map; +import java.util.concurrent.RunnableFuture; + +public class ThreadPoolExecuteMethodInterceptor extends AbstractThreadingPoolInterceptor { + + + @Override + public Object wrap(final Object param) { + if (param instanceof RunnableWrapper) { + return null; + } + + if (param instanceof RunnableFuture) { + return null; + } + + if (param instanceof Runnable) { + Runnable runnable = (Runnable) param; + Map contextForTask = getContextForTask(); + Map contextForHold = getContextForHold(); + RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); + if (requestAttributes != null) { + requestAttributes = ServiceRequestEnhanceOperation.enhanceRequestAttributes(requestAttributes); + } + + return new RunnableWrapper(runnable,contextForTask,contextForHold,requestAttributes); + } + return param; + } + + +} diff --git a/cook-async-agent/cook-async-agent-plugin/cook-async-agent-threadpool-plugin/src/main/java/com/example/agent/plugin/thread/ThreadPoolSubmitMethodInterceptor.java b/cook-async-agent/cook-async-agent-plugin/cook-async-agent-threadpool-plugin/src/main/java/com/example/agent/plugin/thread/ThreadPoolSubmitMethodInterceptor.java new file mode 100644 index 0000000000000000000000000000000000000000..3e861730619abbb4588dfe7bc03a52b0541c55c2 --- /dev/null +++ b/cook-async-agent/cook-async-agent-plugin/cook-async-agent-threadpool-plugin/src/main/java/com/example/agent/plugin/thread/ThreadPoolSubmitMethodInterceptor.java @@ -0,0 +1,60 @@ +/* + * 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 com.example.agent.plugin.thread; + + +import com.example.agent.plugin.thread.enhance.ServiceRequestEnhanceOperation; +import com.example.agent.plugin.thread.wrapper.CallableWrapper; +import com.example.agent.plugin.thread.wrapper.RunnableWrapper; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; + +import java.util.Map; +import java.util.concurrent.Callable; + +public class ThreadPoolSubmitMethodInterceptor extends AbstractThreadingPoolInterceptor { + + @Override + public Object wrap(final Object param) { + if (param instanceof RunnableWrapper || param instanceof CallableWrapper) { + return null; + } + if (param instanceof Callable) { + Callable callable = (Callable) param; + Map contextForTask = getContextForTask(); + Map contextForHold = getContextForHold(); + RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); + if (requestAttributes != null) { + requestAttributes = ServiceRequestEnhanceOperation.enhanceRequestAttributes(requestAttributes); + } + return new CallableWrapper(callable,contextForTask,contextForHold,requestAttributes); + } + if (param instanceof Runnable) { + Runnable runnable = (Runnable) param; + Map contextForTask = getContextForTask(); + Map contextForHold = getContextForHold(); + RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); + if (requestAttributes != null) { + requestAttributes = ServiceRequestEnhanceOperation.enhanceRequestAttributes(requestAttributes); + } + return new RunnableWrapper(runnable,contextForTask,contextForHold,requestAttributes); + } + return null; + } +} diff --git a/cook-async-agent/cook-async-agent-plugin/cook-async-agent-threadpool-plugin/src/main/java/com/example/agent/plugin/thread/define/ThreadPoolExecutorInstrumentation.java b/cook-async-agent/cook-async-agent-plugin/cook-async-agent-threadpool-plugin/src/main/java/com/example/agent/plugin/thread/define/ThreadPoolExecutorInstrumentation.java new file mode 100644 index 0000000000000000000000000000000000000000..c372f6d6e244b9c33814b09648d0c3856d9ffea2 --- /dev/null +++ b/cook-async-agent/cook-async-agent-plugin/cook-async-agent-threadpool-plugin/src/main/java/com/example/agent/plugin/thread/define/ThreadPoolExecutorInstrumentation.java @@ -0,0 +1,97 @@ +/* + * 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 com.example.agent.plugin.thread.define; + +import com.example.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import com.example.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import com.example.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import com.example.agent.core.plugin.match.ClassMatch; +import com.example.agent.core.plugin.match.HierarchyMatch; +import com.example.agent.core.plugin.match.MultiClassNameMatch; +import com.example.agent.core.plugin.match.logical.LogicalMatchOperation; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import net.bytebuddy.matcher.ElementMatchers; + + +public class ThreadPoolExecutorInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "java.util.concurrent.ThreadPoolExecutor"; + + private static final String INTERCEPT_EXECUTE_METHOD = "execute"; + + private static final String INTERCEPT_SUBMIT_METHOD = "submit"; + + private static final String INTERCEPT_EXECUTE_METHOD_HANDLE = "com.example.agent.plugin.thread.ThreadPoolExecuteMethodInterceptor"; + + private static final String INTERCEPT_SUBMIT_METHOD_HANDLE = "com.example.agent.plugin.thread.ThreadPoolSubmitMethodInterceptor"; + + @Override + public boolean isBootstrapInstrumentation() { + return true; + } + + @Override + protected ClassMatch enhanceClass() { + return LogicalMatchOperation.or(HierarchyMatch.byHierarchyMatch(ENHANCE_CLASS), MultiClassNameMatch.byMultiClassMatch(ENHANCE_CLASS)); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[]{ + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return ElementMatchers.named(INTERCEPT_EXECUTE_METHOD); + } + + @Override + public String getMethodsInterceptor() { + return INTERCEPT_EXECUTE_METHOD_HANDLE; + } + + @Override + public boolean isOverrideArgs() { + return true; + } + }, + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return ElementMatchers.named(INTERCEPT_SUBMIT_METHOD); + } + + @Override + public String getMethodsInterceptor() { + return INTERCEPT_SUBMIT_METHOD_HANDLE; + } + + @Override + public boolean isOverrideArgs() { + return true; + } + } + }; + } +} diff --git a/cook-async-agent/cook-async-agent-plugin/cook-async-agent-threadpool-plugin/src/main/java/com/example/agent/plugin/thread/enhance/ServiceRequestEnhance.java b/cook-async-agent/cook-async-agent-plugin/cook-async-agent-threadpool-plugin/src/main/java/com/example/agent/plugin/thread/enhance/ServiceRequestEnhance.java new file mode 100644 index 0000000000000000000000000000000000000000..8e53fb0db6efda2b393641e53db9565c4f30fd20 --- /dev/null +++ b/cook-async-agent/cook-async-agent-plugin/cook-async-agent-threadpool-plugin/src/main/java/com/example/agent/plugin/thread/enhance/ServiceRequestEnhance.java @@ -0,0 +1,55 @@ +package com.example.agent.plugin.thread.enhance; + + +import org.springframework.util.CollectionUtils; +import org.springframework.util.LinkedCaseInsensitiveMap; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; +import java.util.Map; + +public class ServiceRequestEnhance extends HttpServletRequestWrapper { + private Map> headerMap; + + public ServiceRequestEnhance(HttpServletRequest request) { + super(request); + + headerMap = operateHeaders(request); + } + + private Map> operateHeaders(HttpServletRequest request) { + // 不区分大小写的Key的Map + Map> headers = new LinkedCaseInsensitiveMap<>(); + Enumeration headerNames = request.getHeaderNames(); + while (headerNames.hasMoreElements()) { + String headerName = headerNames.nextElement(); + if (headerName != null) { + headers.put(headerName, Collections.list(request.getHeaders(headerName))); + } + } + + return headers; + } + + @Override + public String getHeader(String name) { + List headerValues = headerMap.get(name); + + return CollectionUtils.isEmpty(headerValues) ? null : headerValues.get(0); + } + + @Override + public Enumeration getHeaders(String name) { + List headerValues = headerMap.get(name); + + return Collections.enumeration(headerValues != null ? headerValues : Collections.emptySet()); + } + + @Override + public Enumeration getHeaderNames() { + return Collections.enumeration(headerMap.keySet()); + } +} \ No newline at end of file diff --git a/cook-async-agent/cook-async-agent-plugin/cook-async-agent-threadpool-plugin/src/main/java/com/example/agent/plugin/thread/enhance/ServiceRequestEnhanceOperation.java b/cook-async-agent/cook-async-agent-plugin/cook-async-agent-threadpool-plugin/src/main/java/com/example/agent/plugin/thread/enhance/ServiceRequestEnhanceOperation.java new file mode 100644 index 0000000000000000000000000000000000000000..66f2086dbdbd2745690ea667c1c9a7121a6d207b --- /dev/null +++ b/cook-async-agent/cook-async-agent-plugin/cook-async-agent-threadpool-plugin/src/main/java/com/example/agent/plugin/thread/enhance/ServiceRequestEnhanceOperation.java @@ -0,0 +1,15 @@ +package com.example.agent.plugin.thread.enhance; + + +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; + +public class ServiceRequestEnhanceOperation { + public static RequestAttributes enhanceRequestAttributes(RequestAttributes requestAttributes) { + HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest(); + + return new ServletRequestAttributes(new ServiceRequestEnhance(request)); + } +} \ No newline at end of file diff --git a/cook-async-agent/cook-async-agent-plugin/cook-async-agent-threadpool-plugin/src/main/java/com/example/agent/plugin/thread/wrapper/BaseWrapper.java b/cook-async-agent/cook-async-agent-plugin/cook-async-agent-threadpool-plugin/src/main/java/com/example/agent/plugin/thread/wrapper/BaseWrapper.java new file mode 100644 index 0000000000000000000000000000000000000000..ed4ec5564e76e34fb5a177fe60ef9d681fdea0d4 --- /dev/null +++ b/cook-async-agent/cook-async-agent-plugin/cook-async-agent-threadpool-plugin/src/main/java/com/example/agent/plugin/thread/wrapper/BaseWrapper.java @@ -0,0 +1,48 @@ +package com.example.agent.plugin.thread.wrapper; + +import com.example.ParameterHolder; +import org.slf4j.MDC; + +import java.util.HashMap; +import java.util.Map; + +/* + * @program: cook-frame + * @description: + * @author: 星哥 + * @create: 2023-07-21 + **/ +public class BaseWrapper { + + protected static Map> preprocess(final Map parentMdcContext, final Map parentHoldContext){ + Map> map = new HashMap<>(); + Map holdContext = ParameterHolder.getParameterMap(); + Map mdcContext = MDC.getCopyOfContextMap(); + if (parentMdcContext == null) { + MDC.clear(); + } else { + MDC.setContextMap(parentMdcContext); + } + if (parentHoldContext == null) { + ParameterHolder.removeParameterMap(); + } else { + ParameterHolder.setParameterMap(parentHoldContext); + } + map.put("holdContext",holdContext); + map.put("mdcContext",mdcContext); + return map; + } + + protected static void postProcess(Map mdcContext, Map holdContext){ + if (mdcContext == null) { + MDC.clear(); + } else { + MDC.setContextMap(mdcContext); + } + if (holdContext == null) { + ParameterHolder.removeParameterMap(); + } else { + ParameterHolder.setParameterMap(holdContext); + } + } +} diff --git a/cook-async-agent/cook-async-agent-plugin/cook-async-agent-threadpool-plugin/src/main/java/com/example/agent/plugin/thread/wrapper/CallableWrapper.java b/cook-async-agent/cook-async-agent-plugin/cook-async-agent-threadpool-plugin/src/main/java/com/example/agent/plugin/thread/wrapper/CallableWrapper.java new file mode 100644 index 0000000000000000000000000000000000000000..fccf1134e227c0dc9923597d4effdcb349d154dc --- /dev/null +++ b/cook-async-agent/cook-async-agent-plugin/cook-async-agent-threadpool-plugin/src/main/java/com/example/agent/plugin/thread/wrapper/CallableWrapper.java @@ -0,0 +1,42 @@ +package com.example.agent.plugin.thread.wrapper; + +import lombok.AllArgsConstructor; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; + +import java.util.Map; +import java.util.concurrent.Callable; + +/** + * @program: cook-frame + * @description: + * @author: 星哥 + * @create: 2023-07-21 + **/ +@AllArgsConstructor +public class CallableWrapper extends BaseWrapper implements Callable { + + private final Callable callable; + + private final Map parentMdcContext; + + private final Map parentHoldContext; + + private final RequestAttributes requestAttributes; + + @Override + public Object call() throws Exception { + Map> preprocess = preprocess(parentMdcContext, parentHoldContext); + Map holdContext = preprocess.get("holdContext"); + Map mdcContext = preprocess.get("mdcContext"); + if (requestAttributes != null) { + RequestContextHolder.setRequestAttributes(requestAttributes); + } + try { + return callable.call(); + }finally { + postProcess(mdcContext,holdContext); + RequestContextHolder.resetRequestAttributes(); + } + } +} diff --git a/cook-async-agent/cook-async-agent-plugin/cook-async-agent-threadpool-plugin/src/main/java/com/example/agent/plugin/thread/wrapper/RunnableWrapper.java b/cook-async-agent/cook-async-agent-plugin/cook-async-agent-threadpool-plugin/src/main/java/com/example/agent/plugin/thread/wrapper/RunnableWrapper.java new file mode 100644 index 0000000000000000000000000000000000000000..b3b41194f2936aa5f5d09cf240bb62483d189031 --- /dev/null +++ b/cook-async-agent/cook-async-agent-plugin/cook-async-agent-threadpool-plugin/src/main/java/com/example/agent/plugin/thread/wrapper/RunnableWrapper.java @@ -0,0 +1,41 @@ +package com.example.agent.plugin.thread.wrapper; + +import lombok.AllArgsConstructor; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; + +import java.util.Map; + +/** + * @program: cook-frame + * @description: + * @author: 星哥 + * @create: 2023-07-21 + **/ +@AllArgsConstructor +public class RunnableWrapper extends BaseWrapper implements Runnable{ + + private final Runnable runnable; + + private final Map parentMdcContext; + + private final Map parentHoldContext; + + private final RequestAttributes requestAttributes; + + @Override + public void run() { + Map> preprocess = preprocess(parentMdcContext, parentHoldContext); + Map holdContext = preprocess.get("holdContext"); + Map mdcContext = preprocess.get("mdcContext"); + if (requestAttributes != null) { + RequestContextHolder.setRequestAttributes(requestAttributes); + } + try { + runnable.run(); + } finally { + postProcess(mdcContext,holdContext); + RequestContextHolder.resetRequestAttributes(); + } + } +} diff --git a/cook-async-agent/cook-async-agent-plugin/cook-async-agent-threadpool-plugin/src/main/resources/cook-plugin.def b/cook-async-agent/cook-async-agent-plugin/cook-async-agent-threadpool-plugin/src/main/resources/cook-plugin.def new file mode 100644 index 0000000000000000000000000000000000000000..366bff0d84164fa015cc6fd3afd752e2de967918 --- /dev/null +++ b/cook-async-agent/cook-async-agent-plugin/cook-async-agent-threadpool-plugin/src/main/resources/cook-plugin.def @@ -0,0 +1,17 @@ +# 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. + +thread-pool-plugin=com.example.agent.plugin.thread.define.ThreadPoolExecutorInstrumentation \ No newline at end of file diff --git a/cook-async-agent/cook-async-agent-plugin/pom.xml b/cook-async-agent/cook-async-agent-plugin/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..140feffb8f84ae8524e1b435238e5e7a2888c753 --- /dev/null +++ b/cook-async-agent/cook-async-agent-plugin/pom.xml @@ -0,0 +1,130 @@ + + + + + 4.0.0 + + com.example + cook-async-agent + ${revision} + + cook-async-agent-plugin + pom + + cook-async-agent-threadpool-plugin + + + + UTF-8 + + net.bytebuddy + ${shade.package}.${shade.net.bytebuddy.source} + + ${project.build.directory}${sdk.plugin.related.dir}/../../../cookasyncagent + + ${agent.package.dest.dir}/plugins + + 1.0b3 + 1.8.1 + + + + + com.example + cook-async-agent-core + ${project.version} + provided + + + net.bytebuddy + byte-buddy + provided + + + + + + + maven-shade-plugin + + + package + + shade + + + false + true + true + true + + + ${shade.net.bytebuddy.source} + ${shade.net.bytebuddy.target} + + + + + + + + maven-antrun-plugin + + + package + + run + + + + + + + + + + + + + + + + + + + ant-contrib + ant-contrib + ${ant-contrib.version} + + + ant + ant + + + + + org.apache.ant + ant-nodeps + ${ant-nodeps.version} + + + + + + diff --git a/cook-async-agent/dist-material/LICENSE b/cook-async-agent/dist-material/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..f84d20b7d774177d7859bd7b05ae78514b1194b9 --- /dev/null +++ b/cook-async-agent/dist-material/LICENSE @@ -0,0 +1,232 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +======================================================================= +Apache SkyWalking Subcomponents: + +The Apache SkyWalking project contains subcomponents with separate copyright +notices and license terms. Your use of the source code for the these +subcomponents is subject to the terms and conditions of the following +licenses. + +======================================================================== +Apache 2.0 licenses +======================================================================== + +The following components are provided under the Apache License. See project link for details. +The text of each license is the standard Apache 2.0 license. + + raphw (byte-buddy) 1.12.13: http://bytebuddy.net/ , Apache 2.0 + Google: grpc-java 1.44.0: https://github.com/grpc/grpc-java, Apache 2.0 + Google: gson 2.8.9: https://github.com/google/gson , Apache 2.0 + Google: proto-google-common-protos 2.0.1: https://github.com/googleapis/googleapis , Apache 2.0 + Google: jsr305 3.0.2: http://central.maven.org/maven2/com/google/code/findbugs/jsr305/3.0.0/jsr305-3.0.0.pom , Apache 2.0 + netty 4.1.79: https://github.com/netty/netty/blob/4.1/LICENSE.txt, Apache 2.0 + +======================================================================== +BSD licenses +======================================================================== + +The following components are provided under a BSD license. See project link for details. +The text of each license is also included at licenses/LICENSE-[project].txt. + + asm 9.2:https://gitlab.ow2.org , BSD-3-Clause diff --git a/cook-async-agent/dist-material/NOTICE b/cook-async-agent/dist-material/NOTICE new file mode 100644 index 0000000000000000000000000000000000000000..666f0a6985a1423bd66881b320cf4fcc6efdba7f --- /dev/null +++ b/cook-async-agent/dist-material/NOTICE @@ -0,0 +1,299 @@ +shining-starts-lk +Copyright 2017-2022 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). + +======================================================================== + +grpc-java NOTICE + +======================================================================== +Copyright 2014, gRPC Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +----------------------------------------------------------------------- + +This product contains a modified portion of 'OkHttp', an open source +HTTP & SPDY client for Android and Java applications, which can be obtained +at: + + * LICENSE: + * okhttp/third_party/okhttp/LICENSE (Apache License 2.0) + * HOMEPAGE: + * https://github.com/square/okhttp + * LOCATION_IN_GRPC: + * okhttp/third_party/okhttp + +This product contains a modified portion of 'Netty', an open source +networking library, which can be obtained at: + + * LICENSE: + * netty/third_party/netty/LICENSE.txt (Apache License 2.0) + * HOMEPAGE: + * https://netty.io + * LOCATION_IN_GRPC: + * netty/third_party/netty + +======================================================================== + +grpc NOTICE + +======================================================================== + +Copyright 2014 gRPC authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== + +netty NOTICE + +======================================================================== + + + The Netty Project + ================= + +Please visit the Netty web site for more information: + + * http://netty.io/ + +Copyright 2014 The Netty Project + +The Netty Project 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. + +Also, please refer to each LICENSE..txt file, which is located in +the 'license' directory of the distribution file, for the license terms of the +components that this product depends on. + +------------------------------------------------------------------------------- +This product contains the extensions to Java Collections Framework which has +been derived from the works by JSR-166 EG, Doug Lea, and Jason T. Greene: + + * LICENSE: + * license/LICENSE.jsr166y.txt (Public Domain) + * HOMEPAGE: + * http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/ + * http://viewvc.jboss.org/cgi-bin/viewvc.cgi/jbosscache/experimental/jsr166/ + +This product contains a modified version of Robert Harder's Public Domain +Base64 Encoder and Decoder, which can be obtained at: + + * LICENSE: + * license/LICENSE.base64.txt (Public Domain) + * HOMEPAGE: + * http://iharder.sourceforge.net/current/java/base64/ + +This product contains a modified portion of 'Webbit', an event based +WebSocket and HTTP server, which can be obtained at: + + * LICENSE: + * license/LICENSE.webbit.txt (BSD License) + * HOMEPAGE: + * https://github.com/joewalnes/webbit + +This product contains a modified portion of 'SLF4J', a simple logging +facade for Java, which can be obtained at: + + * LICENSE: + * license/LICENSE.slf4j.txt (MIT License) + * HOMEPAGE: + * http://www.slf4j.org/ + +This product contains a modified portion of 'Apache Harmony', an open source +Java SE, which can be obtained at: + + * NOTICE: + * license/NOTICE.harmony.txt + * LICENSE: + * license/LICENSE.harmony.txt (Apache License 2.0) + * HOMEPAGE: + * http://archive.apache.org/dist/harmony/ + +This product contains a modified portion of 'jbzip2', a Java bzip2 compression +and decompression library written by Matthew J. Francis. It can be obtained at: + + * LICENSE: + * license/LICENSE.jbzip2.txt (MIT License) + * HOMEPAGE: + * https://code.google.com/p/jbzip2/ + +This product contains a modified portion of 'libdivsufsort', a C API library to construct +the suffix array and the Burrows-Wheeler transformed string for any input string of +a constant-size alphabet written by Yuta Mori. It can be obtained at: + + * LICENSE: + * license/LICENSE.libdivsufsort.txt (MIT License) + * HOMEPAGE: + * https://github.com/y-256/libdivsufsort + +This product contains a modified portion of Nitsan Wakart's 'JCTools', Java Concurrency Tools for the JVM, + which can be obtained at: + + * LICENSE: + * license/LICENSE.jctools.txt (ASL2 License) + * HOMEPAGE: + * https://github.com/JCTools/JCTools + +This product optionally depends on 'JZlib', a re-implementation of zlib in +pure Java, which can be obtained at: + + * LICENSE: + * license/LICENSE.jzlib.txt (BSD style License) + * HOMEPAGE: + * http://www.jcraft.com/jzlib/ + +This product optionally depends on 'Compress-LZF', a Java library for encoding and +decoding data in LZF format, written by Tatu Saloranta. It can be obtained at: + + * LICENSE: + * license/LICENSE.compress-lzf.txt (Apache License 2.0) + * HOMEPAGE: + * https://github.com/ning/compress + +This product optionally depends on 'lz4', a LZ4 Java compression +and decompression library written by Adrien Grand. It can be obtained at: + + * LICENSE: + * license/LICENSE.lz4.txt (Apache License 2.0) + * HOMEPAGE: + * https://github.com/jpountz/lz4-java + +This product optionally depends on 'lzma-java', a LZMA Java compression +and decompression library, which can be obtained at: + + * LICENSE: + * license/LICENSE.lzma-java.txt (Apache License 2.0) + * HOMEPAGE: + * https://github.com/jponge/lzma-java + +This product contains a modified portion of 'jfastlz', a Java port of FastLZ compression +and decompression library written by William Kinney. It can be obtained at: + + * LICENSE: + * license/LICENSE.jfastlz.txt (MIT License) + * HOMEPAGE: + * https://code.google.com/p/jfastlz/ + +This product contains a modified portion of and optionally depends on 'Protocol Buffers', Google's data +interchange format, which can be obtained at: + + * LICENSE: + * license/LICENSE.protobuf.txt (New BSD License) + * HOMEPAGE: + * https://github.com/google/protobuf + +This product optionally depends on 'Bouncy Castle Crypto APIs' to generate +a temporary self-signed X.509 certificate when the JVM does not provide the +equivalent functionality. It can be obtained at: + + * LICENSE: + * license/LICENSE.bouncycastle.txt (MIT License) + * HOMEPAGE: + * http://www.bouncycastle.org/ + +This product optionally depends on 'Snappy', a compression library produced +by Google Inc, which can be obtained at: + + * LICENSE: + * license/LICENSE.snappy.txt (New BSD License) + * HOMEPAGE: + * https://github.com/google/snappy + +This product optionally depends on 'JBoss Marshalling', an alternative Java +serialization API, which can be obtained at: + + * LICENSE: + * license/LICENSE.jboss-marshalling.txt (GNU LGPL 2.1) + * HOMEPAGE: + * http://www.jboss.org/jbossmarshalling + +This product optionally depends on 'Caliper', Google's micro- +benchmarking framework, which can be obtained at: + + * LICENSE: + * license/LICENSE.caliper.txt (Apache License 2.0) + * HOMEPAGE: + * https://github.com/google/caliper + +This product optionally depends on 'Apache Commons Logging', a logging +framework, which can be obtained at: + + * LICENSE: + * license/LICENSE.commons-logging.txt (Apache License 2.0) + * HOMEPAGE: + * http://commons.apache.org/logging/ + +This product optionally depends on 'Apache Log4J', a logging framework, which +can be obtained at: + + * LICENSE: + * license/LICENSE.log4j.txt (Apache License 2.0) + * HOMEPAGE: + * http://logging.apache.org/log4j/ + +This product optionally depends on 'Aalto XML', an ultra-high performance +non-blocking XML processor, which can be obtained at: + + * LICENSE: + * license/LICENSE.aalto-xml.txt (Apache License 2.0) + * HOMEPAGE: + * http://wiki.fasterxml.com/AaltoHome + +This product contains a modified version of 'HPACK', a Java implementation of +the HTTP/2 HPACK algorithm written by Twitter. It can be obtained at: + + * LICENSE: + * license/LICENSE.hpack.txt (Apache License 2.0) + * HOMEPAGE: + * https://github.com/twitter/hpack + +This product contains a modified portion of 'Apache Commons Lang', a Java library +provides utilities for the java.lang API, which can be obtained at: + + * LICENSE: + * license/LICENSE.commons-lang.txt (Apache License 2.0) + * HOMEPAGE: + * https://commons.apache.org/proper/commons-lang/ + + +This product contains the Maven wrapper scripts from 'Maven Wrapper', that provides an easy way to ensure a user has everything necessary to run the Maven build. + + * LICENSE: + * license/LICENSE.mvn-wrapper.txt (Apache License 2.0) + * HOMEPAGE: + * https://github.com/takari/maven-wrapper + diff --git a/cook-async-agent/pom.xml b/cook-async-agent/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..4e39936bafa057ba23e5e53dbef4d2dcb0f5870c --- /dev/null +++ b/cook-async-agent/pom.xml @@ -0,0 +1,272 @@ + + + + + 4.0.0 + + com.example + cook-frame + ${revision} + + cook-async-agent + pom + + + cook-async-agent-bootstrap + cook-async-agent-core + cook-async-agent-plugin + + + + com.example.agent.dependencies + UTF-8 + 1.8 + 4.13.1 + 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 + 1.7.25 + 30.1.1-jre + 2.16.0 + + + + + + com.google.code.gson + gson + ${gson.version} + + + com.google.guava + guava + ${guava.version} + + + net.bytebuddy + byte-buddy + ${bytebuddy.version} + + + org.slf4j + slf4j-api + ${slf4j.version} + + + org.openjdk.jmh + jmh-core + ${jmh.version} + test + + + org.projectlombok + lombok + ${lombok.version} + provided + + + javax.annotation + javax.annotation-api + ${javax.annotation-api.version} + provided + + + 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} + + + org.objenesis + objenesis + ${objenesis.version} + test + + + com.github.tomakehurst + wiremock + ${wiremock.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 + + + + + + + + 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 + + + + + + + diff --git a/course-case/src/main/java/com/example/jasypt/TestJasypt.java b/course-case/src/main/java/com/example/jasypt/TestJasypt.java index 6d44769845b6f631893b5a80b163798770027fb0..cb28b77b8f85c786125baac29d0bdce0753f73c0 100644 --- a/course-case/src/main/java/com/example/jasypt/TestJasypt.java +++ b/course-case/src/main/java/com/example/jasypt/TestJasypt.java @@ -9,7 +9,7 @@ import javax.annotation.PostConstruct; /** * @program: cook-frame * @description: - * @author: k + * @author: 星哥 * @create: 2023-07-19 **/ @Component diff --git a/distribute-es/src/test/java/com/example/DistributeEsApplicationTests.java b/distribute-es/src/test/java/com/example/DistributeEsApplicationTests.java deleted file mode 100644 index 1f87f927d6cab3b9f6d5cb9c666bdd8eafeb3aba..0000000000000000000000000000000000000000 --- a/distribute-es/src/test/java/com/example/DistributeEsApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.example; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class DistributeEsApplicationTests { - - @Test - void contextLoads() { - } - -} diff --git a/id-generator/pom.xml b/id-generator/pom.xml index 6876a7cbf36111e03a5c5ed102e1e775d1a4628a..2c0000501e6a1cf00ef4088d5f458a2e8ed19069 100644 --- a/id-generator/pom.xml +++ b/id-generator/pom.xml @@ -48,6 +48,11 @@ org.springframework.boot spring-boot-starter-data-redis + + com.example + common + ${revision} + diff --git a/id-generator/src/test/java/com/example/IdGeneratorApplicationTests.java b/id-generator/src/test/java/com/example/IdGeneratorApplicationTests.java deleted file mode 100644 index 98fadf99cb6fd1e7614fd9ae4f4c3e0b2e163c60..0000000000000000000000000000000000000000 --- a/id-generator/src/test/java/com/example/IdGeneratorApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.example; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class IdGeneratorApplicationTests { - - @Test - void contextLoads() { - } - -} diff --git a/pom.xml b/pom.xml index 2bb28d6b5357036bc4d99cfbc2a1b382d416a2a9..212935da00110f1e684e446e2d4485dccc2c15e9 100644 --- a/pom.xml +++ b/pom.xml @@ -40,6 +40,8 @@ server server-client course-case + cook-async-agent + threadlocal-tool @@ -50,77 +52,11 @@ 2.2.7.RELEASE - - org.springframework.boot - spring-boot-starter-aop - - - - org.springframework.boot - spring-boot-starter-test - test - - org.projectlombok lombok - - - com.alibaba - fastjson - 1.2.83 - - - org.springframework.boot - spring-boot-starter-log4j2 - - - org.slf4j - slf4j-log4j12 - - - org.apache.logging.log4j - log4j-core - - - - org.apache.logging.log4j - log4j-api - - - org.apache.logging.log4j - log4j-slf4j-impl - - - org.apache.logging.log4j - log4j-web - - - - - org.apache.logging.log4j - log4j-core - 2.17.0 - - - - org.apache.logging.log4j - log4j-api - 2.17.0 - - - org.apache.logging.log4j - log4j-slf4j-impl - 2.17.0 - - - org.apache.logging.log4j - log4j-web - 2.17.0 - - diff --git a/redis-tool/src/test/java/com/example/DistributeCacheApplicationTests.java b/redis-tool/src/test/java/com/example/DistributeCacheApplicationTests.java deleted file mode 100644 index 0b7312ed7526cdd69df3a1ae091731ab08b2c429..0000000000000000000000000000000000000000 --- a/redis-tool/src/test/java/com/example/DistributeCacheApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.example; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class DistributeCacheApplicationTests { - - @Test - void contextLoads() { - } - -} diff --git a/server/department-service/pom.xml b/server/department-service/pom.xml index 543848b3e256a0b08167937230015041e8efbd8a..13fcefa81ae076418cc80ef741ac83f0912b34f4 100644 --- a/server/department-service/pom.xml +++ b/server/department-service/pom.xml @@ -132,6 +132,24 @@ com.alibaba.csp sentinel-datasource-nacos + + + com.example + cook-patch-register-nacos-starter + ${revision} + + + + com.example + cook-patch-config-nacos-starter + ${revision} + + + + com.example + cook-patch-work-service-starter + ${revision} + diff --git a/server/employee-service/pom.xml b/server/employee-service/pom.xml index 92b8e0c9ea93f444f78f1629f1182c1c3f2ce796..354dabc08ba866100b17565abf616127c22701e4 100644 --- a/server/employee-service/pom.xml +++ b/server/employee-service/pom.xml @@ -123,6 +123,24 @@ com.alibaba.csp sentinel-datasource-nacos + + + com.example + cook-patch-register-nacos-starter + ${revision} + + + + com.example + cook-patch-config-nacos-starter + ${revision} + + + + com.example + cook-patch-work-service-starter + ${revision} + diff --git a/server/server-case/src/main/java/com/example/controller/TestController.java b/server/server-case/src/main/java/com/example/controller/TestController.java new file mode 100644 index 0000000000000000000000000000000000000000..038b1d07eaa6e6bd2884b18fe247b31dca6536b4 --- /dev/null +++ b/server/server-case/src/main/java/com/example/controller/TestController.java @@ -0,0 +1,30 @@ +package com.example.controller; + +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * @program: cook-frame + * @description: + * @author: 星哥 + * @create: 2023-07-21 + **/ +@RestController +@RequestMapping("/test") +public class TestController { + + private ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,1,60, TimeUnit.SECONDS,new ArrayBlockingQueue<>(30)); + + @PostMapping("test") + public String test(){ + threadPoolExecutor.execute(() -> { + System.out.println("22222"); + }); + return String.valueOf(2222); + } +} diff --git a/server/server-case/src/main/java/com/example/service/AccountService.java b/server/server-case/src/main/java/com/example/service/AccountService.java index 92d73d2dab1388b274303e6d4b976bb4c5477024..d4e56918810bd62c21ac5b160cf59d226ff348e9 100644 --- a/server/server-case/src/main/java/com/example/service/AccountService.java +++ b/server/server-case/src/main/java/com/example/service/AccountService.java @@ -1,6 +1,5 @@ package com.example.service; -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.example.entity.Account; import com.example.mapper.AccountMapper; import org.springframework.beans.factory.annotation.Autowired; @@ -9,8 +8,12 @@ import org.springframework.transaction.annotation.Transactional; @Service @Transactional -public class AccountService extends ServiceImpl { +public class AccountService { @Autowired private AccountMapper accountMapper; + + public Account getById(String id){ + return accountMapper.selectById(id); + } } diff --git a/server/server-case/src/main/resources/mapper/AccountMapper.xml b/server/server-case/src/main/resources/mapper/AccountMapper.xml deleted file mode 100644 index cd8b7b9d298b9b0e3c27c2f660b9f68e6d7d0d4c..0000000000000000000000000000000000000000 --- a/server/server-case/src/main/resources/mapper/AccountMapper.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - \ No newline at end of file diff --git a/spring-cloud-extra/service-component/src/main/java/com/example/balance/ExtraRibbonAutoConfiguration.java b/spring-cloud-extra/service-component/src/main/java/com/example/balance/ExtraRibbonAutoConfiguration.java index 60f726b0cf7f9357b929a211fd4dba51dad071d4..01244fdd9e6ae31a61273ec69f325057d7787cce 100644 --- a/spring-cloud-extra/service-component/src/main/java/com/example/balance/ExtraRibbonAutoConfiguration.java +++ b/spring-cloud-extra/service-component/src/main/java/com/example/balance/ExtraRibbonAutoConfiguration.java @@ -4,7 +4,6 @@ import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration; -import org.springframework.context.annotation.Bean; /** * @program: cook-frame @@ -18,8 +17,8 @@ import org.springframework.context.annotation.Bean; @EnableConfigurationProperties(ExtraRibbonProperties.class) public class ExtraRibbonAutoConfiguration { - @Bean - public CustomEnabledRule discoveryEnabledRule(ExtraRibbonProperties extraRibbonProperties){ - return new CustomEnabledRule(extraRibbonProperties); - } +// @Bean +// public CustomEnabledRule discoveryEnabledRule(ExtraRibbonProperties extraRibbonProperties){ +// return new CustomEnabledRule(extraRibbonProperties); +// } } diff --git a/threadlocal-tool/.gitignore b/threadlocal-tool/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..a2a3040aa86debfd8826d9c2b5c816314c17d9fe --- /dev/null +++ b/threadlocal-tool/.gitignore @@ -0,0 +1,31 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/** +!**/src/test/** + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ + +### VS Code ### +.vscode/ diff --git a/threadlocal-tool/mvnw b/threadlocal-tool/mvnw new file mode 100644 index 0000000000000000000000000000000000000000..a16b5431b4c3cab50323a3f558003fd0abd87dad --- /dev/null +++ b/threadlocal-tool/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# 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 +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/threadlocal-tool/mvnw.cmd b/threadlocal-tool/mvnw.cmd new file mode 100644 index 0000000000000000000000000000000000000000..c8d43372c986d97911cdc21bd87e0cbe3d83bdda --- /dev/null +++ b/threadlocal-tool/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/threadlocal-tool/pom.xml b/threadlocal-tool/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..55c8056e940767dd21210fa35cb9e368b915407c --- /dev/null +++ b/threadlocal-tool/pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + + + com.example + cook-frame + ${revision} + + + + threadlocal-tool + threadlocal-tool + 公共 + + + 1.8 + UTF-8 + UTF-8 + + + + + commons-codec + commons-codec + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 8 + 8 + UTF-8 + + + + + + diff --git a/threadlocal-tool/src/main/java/com/example/ParameterHolder.java b/threadlocal-tool/src/main/java/com/example/ParameterHolder.java new file mode 100644 index 0000000000000000000000000000000000000000..2b70b936f28584aa08b35adab7bd2a3daf2dc067 --- /dev/null +++ b/threadlocal-tool/src/main/java/com/example/ParameterHolder.java @@ -0,0 +1,57 @@ +package com.example; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +/** + * @program: cook-frame + * @description: ThreadLocal操作持有器 + * @author: 星哥 + * @create: 2023-07-09 + **/ +public class ParameterHolder { + + private static final ThreadLocal> threadLocalMap = new ThreadLocal<>(); + + + public static void setParameter(String name, String value) { + Map map = threadLocalMap.get(); + if (map == null) { + map = new HashMap<>(); + } + map.put(name, value); + threadLocalMap.set(map); + } + + public static String getParameter(String name) { + return Optional.ofNullable(threadLocalMap.get()).map(map -> map.get(name)).orElse(null); + } + + public static void removeParameter(String name) { + Map map = threadLocalMap.get(); + if (map != null) { + map.remove(name); + } + } + + public static ThreadLocal> getThreadLocal() { + return threadLocalMap; + } + + public static Map getParameterMap() { + Map map = threadLocalMap.get(); + if (map == null) { + map = new HashMap<>(); + } + return map; + } + + public static void setParameterMap(Map map) { + threadLocalMap.set(map); + } + + public static void removeParameterMap(){ + threadLocalMap.remove(); + } +} diff --git a/threadpool/src/main/java/com/example/filter/FilterConfig.java b/threadpool/src/main/java/com/example/filter/FilterConfig.java index dc1680d3f345a90048985fdc20d0b6de10161f3d..c50b297719d2ddf3f44ec89643ecb53c2c2ec3b6 100644 --- a/threadpool/src/main/java/com/example/filter/FilterConfig.java +++ b/threadpool/src/main/java/com/example/filter/FilterConfig.java @@ -12,7 +12,7 @@ import org.springframework.web.filter.OncePerRequestFilter; public class FilterConfig { @Bean - public OncePerRequestFilter requestContextFilter(){ - return new RequestContextFilter(); + public OncePerRequestFilter requestParamContextFilter(){ + return new RequestParamContextFilter(); } } diff --git a/threadpool/src/main/java/com/example/filter/RequestContextFilter.java b/threadpool/src/main/java/com/example/filter/RequestParamContextFilter.java similarity index 94% rename from threadpool/src/main/java/com/example/filter/RequestContextFilter.java rename to threadpool/src/main/java/com/example/filter/RequestParamContextFilter.java index 8bebaa9c3ad0d828cd275e268297e71bb257c158..07f0e5edc51a235a6ba23cd881b50f86c5d01e90 100644 --- a/threadpool/src/main/java/com/example/filter/RequestContextFilter.java +++ b/threadpool/src/main/java/com/example/filter/RequestParamContextFilter.java @@ -20,7 +20,7 @@ import static com.example.constant.Constant.TRACE_ID; * @create: 2022-11-08 16:48 **/ -public class RequestContextFilter extends OncePerRequestFilter { +public class RequestParamContextFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { if (request != null) { diff --git a/tool/pom.xml b/tool/pom.xml index ab1bd8eb60f6e794d40cf0139d30c57a87228495..b9f531505ef5eda408c88f0d413c73bc30b36a4c 100644 --- a/tool/pom.xml +++ b/tool/pom.xml @@ -40,6 +40,10 @@ spring-boot-starter-web true + + org.springframework.boot + spring-boot-starter-aop +