diff --git a/apm-protocol/apm-network/src/main/java/org/apache/skywalking/apm/network/trace/component/ComponentsDefine.java b/apm-protocol/apm-network/src/main/java/org/apache/skywalking/apm/network/trace/component/ComponentsDefine.java index 0105cea0a..c79b6ea83 100755 --- a/apm-protocol/apm-network/src/main/java/org/apache/skywalking/apm/network/trace/component/ComponentsDefine.java +++ b/apm-protocol/apm-network/src/main/java/org/apache/skywalking/apm/network/trace/component/ComponentsDefine.java @@ -134,4 +134,6 @@ public class ComponentsDefine { public static final OfficialComponent CASSANDRA_JAVA_DRIVER = new OfficialComponent(69, "cassandra-java-driver"); + public static final OfficialComponent LIGHT_4J = new OfficialComponent(71, "Light4J"); + } diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/conf/Config.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/conf/Config.java index ee28cfb43..5a8eca4c1 100755 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/conf/Config.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/conf/Config.java @@ -287,5 +287,13 @@ public class Config { public static Map RULE = new HashMap(); } } + + public static class Light4J { + /** + * If true, trace all middleware/business handlers that are part of the Light4J handler chain for a request, + * generating a local span for each. + */ + public static boolean TRACE_HANDLER_CHAIN = false; + } } } diff --git a/apm-sniffer/apm-sdk-plugin/light4j-plugins/light4j-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/light4j-plugins/light4j-plugin/pom.xml new file mode 100644 index 000000000..8fe7cd87d --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/light4j-plugins/light4j-plugin/pom.xml @@ -0,0 +1,51 @@ + + + + + light4j-plugins + org.apache.skywalking + 6.5.0-SNAPSHOT + + 4.0.0 + + apm-light4j-plugin + jar + + light4j-plugin + http://maven.apache.org + + + 1.6.9 + + + + + com.networknt + handler + ${version.light4j} + provided + + + com.networknt + exception + ${version.light4j} + provided + + + diff --git a/apm-sniffer/apm-sdk-plugin/light4j-plugins/light4j-plugin/src/main/java/org/apache/skywalking/apm/plugin/light4j/HandleRequestInterceptor.java b/apm-sniffer/apm-sdk-plugin/light4j-plugins/light4j-plugin/src/main/java/org/apache/skywalking/apm/plugin/light4j/HandleRequestInterceptor.java new file mode 100644 index 000000000..b1c7d374c --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/light4j-plugins/light4j-plugin/src/main/java/org/apache/skywalking/apm/plugin/light4j/HandleRequestInterceptor.java @@ -0,0 +1,141 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + */ + +package org.apache.skywalking.apm.plugin.light4j; + +import com.networknt.exception.ExceptionHandler; +import com.networknt.handler.MiddlewareHandler; +import com.networknt.handler.OrchestrationHandler; +import io.undertow.server.HttpServerExchange; +import io.undertow.util.HeaderMap; +import org.apache.skywalking.apm.agent.core.conf.Config; +import org.apache.skywalking.apm.agent.core.context.CarrierItem; +import org.apache.skywalking.apm.agent.core.context.ContextCarrier; +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.tag.Tags; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; + +import java.lang.reflect.Method; + +/** + * {@link HandleRequestInterceptor} creates an entry span before the execution of + * {@link com.networknt.exception.ExceptionHandler#handleRequest(HttpServerExchange)} in the I/O thread. + * + * If the {@link Config.Plugin.Light4J#TRACE_HANDLER_CHAIN} flag is set, additionally a local span is produced for each + * {@link com.networknt.handler.MiddlewareHandler} and business handler before their respective + * {@link com.networknt.handler.LightHttpHandler#handleRequest(HttpServerExchange)} method executes. + * Since {@link com.networknt.handler.LightHttpHandler} is implemented by various middleware and business handlers and + * the Light4J framework delegates to these in succession, a chain of + * {@link org.apache.skywalking.apm.agent.core.context.trace.LocalSpan}s will be produced. + * + * @author tsuilouis + */ +public class HandleRequestInterceptor implements InstanceMethodsAroundInterceptor { + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + MethodInterceptResult result) { + if (isExceptionHandler(objInst)) { + HttpServerExchange exchange = (HttpServerExchange) allArguments[0]; + + if (exchange.isInIoThread()) { + + String operationName = exchange.getRequestPath() + "@" + exchange.getRequestMethod(); + final HeaderMap headers = exchange.getRequestHeaders(); + final ContextCarrier contextCarrier = new ContextCarrier(); + + CarrierItem next = contextCarrier.items(); + while (next.hasNext()) { + next = next.next(); + next.setHeadValue(headers.getFirst(next.getHeadKey())); + } + + AbstractSpan span = ContextManager.createEntrySpan(operationName, contextCarrier); + Tags.URL.set(span, exchange.getRequestURL()); + Tags.HTTP.METHOD.set(span, exchange.getRequestMethod().toString()); + span.setComponent(ComponentsDefine.LIGHT_4J); + SpanLayer.asHttp(span); + + if (exchange.getStatusCode() >= 400) { + span.errorOccurred(); + Tags.STATUS_CODE.set(span, String.valueOf(exchange.getStatusCode())); + } + + ContextManager.stopSpan(span); + + objInst.setSkyWalkingDynamicField(ContextManager.capture()); + } else if (Config.Plugin.Light4J.TRACE_HANDLER_CHAIN) { + String operationName = objInst.getClass().getName() + "." + method.getName(); + + ContextSnapshot snapshot = (ContextSnapshot) objInst.getSkyWalkingDynamicField(); + ContextManager.createLocalSpan(operationName) + .setComponent(ComponentsDefine.LIGHT_4J); + + ContextManager.continued(snapshot); + } + } else if (Config.Plugin.Light4J.TRACE_HANDLER_CHAIN && + (isMiddlewareHandler(objInst) || isBusinessHandler(objInst))) { + String operationName = objInst.getClass().getName() + "." + method.getName(); + + ContextManager.createLocalSpan(operationName) + .setComponent(ComponentsDefine.LIGHT_4J); + } + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + Object ret) { + if (isExceptionHandler(objInst)) { + HttpServerExchange exchange = (HttpServerExchange) allArguments[0]; + + if (Config.Plugin.Light4J.TRACE_HANDLER_CHAIN && !exchange.isInIoThread()) { + ContextManager.stopSpan(); + } + } else if (Config.Plugin.Light4J.TRACE_HANDLER_CHAIN && + (isMiddlewareHandler(objInst) || isBusinessHandler(objInst))) { + ContextManager.stopSpan(); + } + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, + Throwable t) { + ContextManager.activeSpan().errorOccurred().log(t); + } + + private boolean isBusinessHandler(EnhancedInstance objInst) { + return !objInst.getClass().getInterfaces()[0].equals(MiddlewareHandler.class) && !objInst.getClass().equals(OrchestrationHandler.class); + } + + private boolean isMiddlewareHandler(EnhancedInstance objInst) { + return objInst.getClass().getInterfaces()[0].equals(MiddlewareHandler.class); + } + + private boolean isExceptionHandler(EnhancedInstance objInst) { + return objInst.getClass().equals(ExceptionHandler.class); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/light4j-plugins/light4j-plugin/src/main/java/org/apache/skywalking/apm/plugin/light4j/define/LightInstrumentation.java b/apm-sniffer/apm-sdk-plugin/light4j-plugins/light4j-plugin/src/main/java/org/apache/skywalking/apm/plugin/light4j/define/LightInstrumentation.java new file mode 100644 index 000000000..3f2101f64 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/light4j-plugins/light4j-plugin/src/main/java/org/apache/skywalking/apm/plugin/light4j/define/LightInstrumentation.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.skywalking.apm.plugin.light4j.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import net.bytebuddy.matcher.ElementMatchers; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.HierarchyMatch; + +/** + * This instrumentation is applied to the handleRequest method of {@link com.networknt.handler.LightHttpHandler} + * using {@link org.apache.skywalking.apm.plugin.light4j.HandleRequestInterceptor}. + * + * @author tsuilouis + */ +public class LightInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "com.networknt.handler.LightHttpHandler"; + private static final String ENHANCE_METHOD = "handleRequest"; + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.light4j.HandleRequestInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return HierarchyMatch.byHierarchyMatch(new String[] {ENHANCE_CLASS}); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return null; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[] { + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return ElementMatchers.named(ENHANCE_METHOD); + } + + @Override + public String getMethodsInterceptor() { + return INTERCEPTOR_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + } +} \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/light4j-plugins/light4j-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/apm-sdk-plugin/light4j-plugins/light4j-plugin/src/main/resources/skywalking-plugin.def new file mode 100644 index 000000000..8305555bc --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/light4j-plugins/light4j-plugin/src/main/resources/skywalking-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. + +light4j=org.apache.skywalking.apm.plugin.light4j.define.LightInstrumentation diff --git a/apm-sniffer/apm-sdk-plugin/light4j-plugins/light4j-plugin/src/test/java/org/apache/skywalking/apm/plugin/light4j/HandleRequestInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/light4j-plugins/light4j-plugin/src/test/java/org/apache/skywalking/apm/plugin/light4j/HandleRequestInterceptorTest.java new file mode 100644 index 000000000..43d641847 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/light4j-plugins/light4j-plugin/src/test/java/org/apache/skywalking/apm/plugin/light4j/HandleRequestInterceptorTest.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.skywalking.apm.plugin.light4j; + +import com.networknt.exception.ExceptionHandler; +import io.undertow.server.HttpServerExchange; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.apache.skywalking.apm.agent.test.tools.*; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.modules.junit4.PowerMockRunnerDelegate; + +import java.lang.reflect.Method; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +/** + * @author tsuilouis + */ +@RunWith(PowerMockRunner.class) +@PowerMockRunnerDelegate(TracingSegmentRunner.class) +public class HandleRequestInterceptorTest { + + private HandleRequestInterceptor handleRequestInterceptor; + + @SegmentStoragePoint + private SegmentStorage segmentStorage; + + @Rule + public AgentServiceRule serviceRule = new AgentServiceRule(); + + @Mock + private MethodInterceptResult methodInterceptResult; + + private EnhancedInstance enhancedInstance; + + @Before + public void setUp() throws Exception { + handleRequestInterceptor = new HandleRequestInterceptor(); + + enhancedInstance = new EnhancedInstance() { + @Override + public Object getSkyWalkingDynamicField() { + return null; + } + + @Override + public void setSkyWalkingDynamicField(Object value) { + + } + }; + } + + @Test + public void testHandleRequest() throws Throwable { + Method method = ExceptionHandler.class.getMethod("handleRequest", HttpServerExchange.class); + + handleRequestInterceptor.beforeMethod(enhancedInstance, method, null, null, methodInterceptResult); + handleRequestInterceptor.afterMethod(enhancedInstance, null, null, null, null); + + assertThat(segmentStorage.getTraceSegments().size(), is(0)); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/light4j-plugins/pom.xml b/apm-sniffer/apm-sdk-plugin/light4j-plugins/pom.xml new file mode 100644 index 000000000..c880e2f8c --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/light4j-plugins/pom.xml @@ -0,0 +1,42 @@ + + + + + 4.0.0 + + + org.apache.skywalking + apm-sdk-plugin + 6.5.0-SNAPSHOT + + + light4j-plugins + + light4j-plugin + + pom + + light4j-plugins + http://maven.apache.org + + + UTF-8 + /.. + + diff --git a/apm-sniffer/apm-sdk-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/pom.xml index f3be93541..886dadb97 100644 --- a/apm-sniffer/apm-sdk-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/pom.xml @@ -75,6 +75,7 @@ resteasy-plugin solrj-7.x-plugin cassandra-java-driver-3.x-plugin + light4j-plugins pom diff --git a/docs/en/setup/service-agent/java-agent/README.md b/docs/en/setup/service-agent/java-agent/README.md index 6f4f27a45..e3fa37d0b 100755 --- a/docs/en/setup/service-agent/java-agent/README.md +++ b/docs/en/setup/service-agent/java-agent/README.md @@ -102,6 +102,7 @@ property key | Description | Default | `plugin.mysql.sql_parameters_max_length`|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.|`512`| `plugin.solrj.trace_statement`|If true, trace all the query parameters(include deleteByIds and deleteByQuery) in Solr query request, default is false.|`false`| `plugin.solrj.trace_ops_params`|If true, trace all the operation parameters in Solr request, default is false.|`false`| +`plugin.light4j.trace_handler_chain`|If true, trace all middleware/business handlers that are part of the Light4J handler chain for a request.|false| `plugin.opgroup.*`|Support operation name customize group rules in different plugins. Read [Group rule supported plugins](op_name_group_rule.md)|Not set| ## Optional Plugins diff --git a/docs/en/setup/service-agent/java-agent/Supported-list.md b/docs/en/setup/service-agent/java-agent/Supported-list.md index b04758bf1..51ddb54cf 100644 --- a/docs/en/setup/service-agent/java-agent/Supported-list.md +++ b/docs/en/setup/service-agent/java-agent/Supported-list.md @@ -13,6 +13,7 @@ * [Undertow](http://undertow.io/) 2.0.0.Final -> 2.0.13.Final * [RESTEasy](https://resteasy.github.io/) 3.1.0.Final -> 3.7.0.Final * [Play Framework](https://www.playframework.com/) 2.6.x -> 2.7.x (Optional²) + * [Light4J Microservices Framework](https://doc.networknt.com/) 1.6.x -> 2.x * HTTP Client * [Feign](https://github.com/OpenFeign/feign) 9.x * [Netflix Spring Cloud Feign](https://github.com/spring-cloud/spring-cloud-netflix/tree/master/spring-cloud-starter-feign) 1.1.x, 1.2.x, 1.3.x diff --git a/oap-server/server-core/src/test/resources/component-libraries.yml b/oap-server/server-core/src/test/resources/component-libraries.yml index a2b6568fc..bdcf6c2c9 100755 --- a/oap-server/server-core/src/test/resources/component-libraries.yml +++ b/oap-server/server-core/src/test/resources/component-libraries.yml @@ -221,6 +221,9 @@ cassandra-java-driver: Cassandra: id: 70 languages: Java +Light4J: + id: 71 + languages: Java # .NET/.NET Core components # [3000, 4000) for C#/.NET only diff --git a/oap-server/server-starter/src/main/resources/component-libraries.yml b/oap-server/server-starter/src/main/resources/component-libraries.yml index 711a3ba2c..88463767f 100755 --- a/oap-server/server-starter/src/main/resources/component-libraries.yml +++ b/oap-server/server-starter/src/main/resources/component-libraries.yml @@ -239,6 +239,9 @@ cassandra-java-driver: Cassandra: id: 70 languages: Java +Light4J: + id: 71 + languages: Java # .NET/.NET Core components diff --git a/skywalking-ui b/skywalking-ui index 05556dc72..206a514ae 160000 --- a/skywalking-ui +++ b/skywalking-ui @@ -1 +1 @@ -Subproject commit 05556dc723d1baed8755bab1a7ec5d029debdb6f +Subproject commit 206a514ae71267e5b29ac58dba2dbfeaf71a7b2d