提交 72f07b81 编写于 作者: L Louis Tsui 提交者: wu-sheng

Add Light4j plugin (#3323)

* Add Light4J 1.6.x plug-in

* Add unit test for Light4J plug-in

* Add missing id to server-starter

* Remove mentions of version 1.6.x

Some local testing showed that the plugin successfully generated traces for a light4j project built with light-codegen v2.x. Therefore, there is no need for a separate plugin for v2.x and no distinction needs to be made with light4j v1.6.x.

* Update supported light4j versions

* Use ContextSnapshot to maintain trace

In a typical Light4j project, the ExceptionHandler#handleRequest is the asynchronous execution point. The flow is ExceptionHandler#handleRequest -> HttpServerExchange#dispatch -> ... -> ExceptionHandler#handleRequest. This means that handleRequest is being intercepted twice and each time a LocalSpan is created. Without handling thread propagation, the trace segment gets split up.

By saving a ContextSnapshot into the enhanced object (ExceptionHandler), we can determine if it is necessary to continue the segment.

* Update logic for span creation in trace

By default, the Light4J plugin now overrides the Undertow plugin's entry span with its own. This new entry span is created just before execution of the ExceptionHandler#handleRequest in the I/O thread. The entry span is generated here because it is considered the starting point of the Light4J handler chain.

There is now also the option to enable detailed span creation when tracing requests through Light4J. These local spans mark which middleware and business handlers are involved with handling the HTTP request to a Light4J service.

* Update skywalking version

* Update light4j version

* Fix LightInstrumentation javadoc

* Remove import violating checkstyle

This import was added from the link reference in the javadoc. The javadoc has now been changed to just reference the method name.

* Move light4j agent config property

* Sync UI
上级 ad45a94a
......@@ -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");
}
......@@ -287,5 +287,13 @@ public class Config {
public static Map<String, String> RULE = new HashMap<String, String>();
}
}
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;
}
}
}
<!--
~ Licensed to the Apache Software Foundation (ASF) under one or more
~ contributor license agreements. See the NOTICE file distributed with
~ this work for additional information regarding copyright ownership.
~ The ASF licenses this file to You under the Apache License, Version 2.0
~ (the "License"); you may not use this file except in compliance with
~ the License. You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
~
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>light4j-plugins</artifactId>
<groupId>org.apache.skywalking</groupId>
<version>6.5.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>apm-light4j-plugin</artifactId>
<packaging>jar</packaging>
<name>light4j-plugin</name>
<url>http://maven.apache.org</url>
<properties>
<version.light4j>1.6.9</version.light4j>
</properties>
<dependencies>
<dependency>
<groupId>com.networknt</groupId>
<artifactId>handler</artifactId>
<version>${version.light4j}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.networknt</groupId>
<artifactId>exception</artifactId>
<version>${version.light4j}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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);
}
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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<MethodDescription> 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
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT 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
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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));
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Licensed to the Apache Software Foundation (ASF) under one or more
~ contributor license agreements. See the NOTICE file distributed with
~ this work for additional information regarding copyright ownership.
~ The ASF licenses this file to You under the Apache License, Version 2.0
~ (the "License"); you may not use this file except in compliance with
~ the License. You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
~
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-sdk-plugin</artifactId>
<version>6.5.0-SNAPSHOT</version>
</parent>
<artifactId>light4j-plugins</artifactId>
<modules>
<module>light4j-plugin</module>
</modules>
<packaging>pom</packaging>
<name>light4j-plugins</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<sdk.plugin.related.dir>/..</sdk.plugin.related.dir>
</properties>
</project>
......@@ -75,6 +75,7 @@
<module>resteasy-plugin</module>
<module>solrj-7.x-plugin</module>
<module>cassandra-java-driver-3.x-plugin</module>
<module>light4j-plugins</module>
</modules>
<packaging>pom</packaging>
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -239,6 +239,9 @@ cassandra-java-driver:
Cassandra:
id: 70
languages: Java
Light4J:
id: 71
languages: Java
# .NET/.NET Core components
......
Subproject commit 05556dc723d1baed8755bab1a7ec5d029debdb6f
Subproject commit 206a514ae71267e5b29ac58dba2dbfeaf71a7b2d
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册