From 8bf7e346ee0942b28132b71ab8f375d53d518183 Mon Sep 17 00:00:00 2001 From: Andy Ai Date: Thu, 29 Aug 2019 17:00:51 +0800 Subject: [PATCH] Support play 2.x (#3207) * Support play 2.x --- .../trace/component/ComponentsDefine.java | 2 + .../bytebuddy/AnnotationTypeNameMatch.java | 72 ++++++ .../plugin/bytebuddy/ReturnTypeNameMatch.java | 68 +++++ .../AnnotationTypeNameMatchTest.java | 38 +++ .../agent/core/plugin/bytebuddy/Inject.java | 34 +++ .../agent/core/plugin/bytebuddy/Person.java | 46 ++++ .../bytebuddy/ReturnTypeNameMatchTest.java | 38 +++ .../optional-plugins/play-2.x-plugin/pom.xml | 48 ++++ .../play/v2x/HttpFiltersInterceptor.java | 66 +++++ .../apm/plugin/play/v2x/TracingFilter.java | 99 +++++++ .../v2x/define/Play2xInstrumentation.java | 98 +++++++ .../src/main/resources/skywalking-plugin.def | 17 ++ .../play/v2x/HttpFiltersInterceptorTest.java | 92 +++++++ .../play/v2x/Play2xInstrumentationTest.java | 48 ++++ .../plugin/play/v2x/TracingFilterTest.java | 242 ++++++++++++++++++ apm-sniffer/optional-plugins/pom.xml | 1 + .../setup/service-agent/java-agent/README.md | 1 + .../java-agent/Supported-list.md | 1 + docs/powered-by.md | 1 + .../test/resources/component-libraries.yml | 3 + .../main/resources/component-libraries.yml | 3 + 21 files changed, 1018 insertions(+) create mode 100644 apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bytebuddy/AnnotationTypeNameMatch.java create mode 100644 apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bytebuddy/ReturnTypeNameMatch.java create mode 100644 apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/plugin/bytebuddy/AnnotationTypeNameMatchTest.java create mode 100644 apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/plugin/bytebuddy/Inject.java create mode 100644 apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/plugin/bytebuddy/Person.java create mode 100644 apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/plugin/bytebuddy/ReturnTypeNameMatchTest.java create mode 100644 apm-sniffer/optional-plugins/play-2.x-plugin/pom.xml create mode 100644 apm-sniffer/optional-plugins/play-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/play/v2x/HttpFiltersInterceptor.java create mode 100644 apm-sniffer/optional-plugins/play-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/play/v2x/TracingFilter.java create mode 100644 apm-sniffer/optional-plugins/play-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/play/v2x/define/Play2xInstrumentation.java create mode 100644 apm-sniffer/optional-plugins/play-2.x-plugin/src/main/resources/skywalking-plugin.def create mode 100644 apm-sniffer/optional-plugins/play-2.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/play/v2x/HttpFiltersInterceptorTest.java create mode 100644 apm-sniffer/optional-plugins/play-2.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/play/v2x/Play2xInstrumentationTest.java create mode 100644 apm-sniffer/optional-plugins/play-2.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/play/v2x/TracingFilterTest.java 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 dbb70bd23..da9e76452 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 @@ -130,4 +130,6 @@ public class ComponentsDefine { public static final OfficialComponent SPRING_WEBFLUX = new OfficialComponent(67, "spring-webflux"); + public static final OfficialComponent PLAY = new OfficialComponent(68, "Play"); + } diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bytebuddy/AnnotationTypeNameMatch.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bytebuddy/AnnotationTypeNameMatch.java new file mode 100644 index 000000000..1a0da7c0e --- /dev/null +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bytebuddy/AnnotationTypeNameMatch.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 org.apache.skywalking.apm.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. + *

+ * + * @author AI + * 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 ElementMatcher.Junction isAnnotatedWithType(String annotationTypeName) { + final AnnotationTypeNameMatch matcher = new AnnotationTypeNameMatch(annotationTypeName); + return new DeclaringAnnotationMatcher(new CollectionItemMatcher(matcher)); + } +} diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bytebuddy/ReturnTypeNameMatch.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bytebuddy/ReturnTypeNameMatch.java new file mode 100644 index 000000000..c26b02f2f --- /dev/null +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bytebuddy/ReturnTypeNameMatch.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 org.apache.skywalking.apm.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. + *

+ * + * @author AI + * 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) { + 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/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/plugin/bytebuddy/AnnotationTypeNameMatchTest.java b/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/plugin/bytebuddy/AnnotationTypeNameMatchTest.java new file mode 100644 index 000000000..ce8edd402 --- /dev/null +++ b/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/plugin/bytebuddy/AnnotationTypeNameMatchTest.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 org.apache.skywalking.apm.agent.core.plugin.bytebuddy; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.junit.Assert; +import org.junit.Test; + +/** + * @author AI + * 2019-08-15 + */ +public class AnnotationTypeNameMatchTest { + + @Test + public void testMatch() throws Exception { + final ElementMatcher matcher = AnnotationTypeNameMatch.isAnnotatedWithType("org.apache.skywalking.apm.agent.core.plugin.bytebuddy.Inject"); + Assert.assertTrue(matcher.matches(new MethodDescription.ForLoadedConstructor(Person.class.getConstructor(String.class)))); + Assert.assertFalse(matcher.matches(new MethodDescription.ForLoadedConstructor(Person.class.getConstructor(String.class, int.class)))); + } + +} diff --git a/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/plugin/bytebuddy/Inject.java b/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/plugin/bytebuddy/Inject.java new file mode 100644 index 000000000..29154d904 --- /dev/null +++ b/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/plugin/bytebuddy/Inject.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 org.apache.skywalking.apm.agent.core.plugin.bytebuddy; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author AI + * 2019-08-15 + */ +@Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Inject { +} diff --git a/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/plugin/bytebuddy/Person.java b/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/plugin/bytebuddy/Person.java new file mode 100644 index 000000000..22da48562 --- /dev/null +++ b/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/plugin/bytebuddy/Person.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 org.apache.skywalking.apm.agent.core.plugin.bytebuddy; + +/** + * @author AI + * 2019-08-15 + */ +public class Person { + private int age; + private String name; + + @Inject + public Person(String name) { + this.name = name; + } + + public Person(String name, int age) { + this.age = age; + this.name = name; + } + + public String getName() { + return name; + } + + public int getAge() { + return age; + } + +} diff --git a/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/plugin/bytebuddy/ReturnTypeNameMatchTest.java b/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/plugin/bytebuddy/ReturnTypeNameMatchTest.java new file mode 100644 index 000000000..e1cd0534b --- /dev/null +++ b/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/plugin/bytebuddy/ReturnTypeNameMatchTest.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 org.apache.skywalking.apm.agent.core.plugin.bytebuddy; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.junit.Assert; +import org.junit.Test; + +/** + * @author AI + * 2019-08-15 + */ +public class ReturnTypeNameMatchTest { + + @Test + public void testMatch() throws Exception { + final ElementMatcher matcher = ReturnTypeNameMatch.returnsWithType("java.lang.String"); + Assert.assertTrue(matcher.matches(new MethodDescription.ForLoadedMethod(Person.class.getMethod("getName")))); + Assert.assertFalse(matcher.matches(new MethodDescription.ForLoadedMethod(Person.class.getMethod("getAge")))); + } + +} diff --git a/apm-sniffer/optional-plugins/play-2.x-plugin/pom.xml b/apm-sniffer/optional-plugins/play-2.x-plugin/pom.xml new file mode 100644 index 000000000..8445bc439 --- /dev/null +++ b/apm-sniffer/optional-plugins/play-2.x-plugin/pom.xml @@ -0,0 +1,48 @@ + + + + + 4.0.0 + + org.apache.skywalking + optional-plugins + 6.4.0-SNAPSHOT + + + apm-play-2.x-plugin + play-2.x-plugin + jar + + + UTF-8 + 2.7.3 + 1.8 + + + + + com.typesafe.play + play_2.12 + ${play2.version} + provided + + + + diff --git a/apm-sniffer/optional-plugins/play-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/play/v2x/HttpFiltersInterceptor.java b/apm-sniffer/optional-plugins/play-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/play/v2x/HttpFiltersInterceptor.java new file mode 100644 index 000000000..7c7198cf3 --- /dev/null +++ b/apm-sniffer/optional-plugins/play-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/play/v2x/HttpFiltersInterceptor.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 org.apache.skywalking.apm.plugin.play.v2x; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import play.api.inject.Injector; +import scala.collection.Seq; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +/** + * @author AI + * 2019-08-01 + */ +public class HttpFiltersInterceptor implements InstanceMethodsAroundInterceptor, InstanceConstructorInterceptor { + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, MethodInterceptResult result) throws Throwable { + + } + + @Override + @SuppressWarnings("unchecked") + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Object ret) throws Throwable { + Object object = objInst.getSkyWalkingDynamicField(); + Injector injector = (Injector) object; + TracingFilter filter = injector.instanceOf(TracingFilter.class); + Seq seq = (Seq) ret; + List filters = new ArrayList<>(seq.size() + 1); + filters.add(filter); + filters.addAll(scala.collection.JavaConverters.asJavaCollection(seq)); + return scala.collection.JavaConverters.asScalaBuffer(filters).toList(); + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Throwable t) { + ContextManager.activeSpan().errorOccurred().log(t); + } + + @Override + public void onConstruct(EnhancedInstance objInst, Object[] allArguments) { + objInst.setSkyWalkingDynamicField(allArguments[2]); + } + +} diff --git a/apm-sniffer/optional-plugins/play-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/play/v2x/TracingFilter.java b/apm-sniffer/optional-plugins/play-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/play/v2x/TracingFilter.java new file mode 100644 index 000000000..df6a1aa73 --- /dev/null +++ b/apm-sniffer/optional-plugins/play-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/play/v2x/TracingFilter.java @@ -0,0 +1,99 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.skywalking.apm.plugin.play.v2x; + +import akka.stream.Materializer; +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.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.network.trace.component.ComponentsDefine; +import play.api.routing.HandlerDef; +import play.mvc.Filter; +import play.mvc.Http; +import play.mvc.Result; +import play.routing.Router; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.CompletionStage; +import java.util.function.Function; +import java.util.regex.Pattern; + +/** + * @author AI + * 2019-08-01 + */ +@Singleton +public class TracingFilter extends Filter { + + private final Pattern routePattern = Pattern.compile("\\$(\\w+)\\<\\[\\^/\\]\\+\\>", Pattern.DOTALL); + + @Inject + public TracingFilter(Materializer mat) { + super(mat); + } + + @Override + public CompletionStage apply(Function> next, Http.RequestHeader request) { + HandlerDef def = null; + try { + def = request.attrs().get(Router.Attrs.HANDLER_DEF); + } catch (Throwable t) { + // ignore get HandlerDef exception + } + if (Objects.nonNull(def)) { + final ContextCarrier carrier = new ContextCarrier(); + CarrierItem items = carrier.items(); + while (items.hasNext()) { + items = items.next(); + Optional value = request.getHeaders().get(items.getHeadKey()); + if (value.isPresent()) { + items.setHeadValue(value.get()); + } + } + final String operationName = routePattern.matcher(def.path()).replaceAll("{$1}"); + final AbstractSpan span = ContextManager.createEntrySpan(operationName, carrier); + final String url = request.host() + request.uri(); + Tags.URL.set(span, url); + Tags.HTTP.METHOD.set(span, request.method()); + span.setComponent(ComponentsDefine.PLAY); + SpanLayer.asHttp(span); + span.prepareForAsync(); + CompletionStage stage = next.apply(request).thenApply(result -> { + if (result.status() >= 400) { + span.errorOccurred(); + Tags.STATUS_CODE.set(span, Integer.toString(result.status())); + } + try { + span.asyncFinish(); + } catch (Throwable t) { + ContextManager.activeSpan().errorOccurred().log(t); + } + return result; + }); + ContextManager.stopSpan(span); + return stage; + } + return next.apply(request); + } +} diff --git a/apm-sniffer/optional-plugins/play-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/play/v2x/define/Play2xInstrumentation.java b/apm-sniffer/optional-plugins/play-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/play/v2x/define/Play2xInstrumentation.java new file mode 100644 index 000000000..8cfd2427c --- /dev/null +++ b/apm-sniffer/optional-plugins/play-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/play/v2x/define/Play2xInstrumentation.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.play.v2x.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.bytebuddy.ReturnTypeNameMatch; +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 static net.bytebuddy.matcher.ElementMatchers.named; +import static org.apache.skywalking.apm.agent.core.plugin.bytebuddy.AnnotationTypeNameMatch.isAnnotatedWithType; +import static org.apache.skywalking.apm.agent.core.plugin.bytebuddy.ArgumentTypeNameMatch.takesArgumentWithType; +import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName; + +/** + * @author AI + * 2019-08-01 + */ +public class Play2xInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + private static final String ENHANCE_CLASS = "play.api.http.EnabledFilters"; + private static final String ENHANCE_METHOD = "filters"; + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.play.v2x.HttpFiltersInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[]{ + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return Play2xInstrumentation.getInjectConstructorMatcher(); + } + + @Override + public String getConstructorInterceptor() { + return INTERCEPTOR_CLASS; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[]{ + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return Play2xInstrumentation.getFiltersMethodMatcher(); + } + + @Override + public String getMethodsInterceptor() { + return INTERCEPTOR_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + } + + public static ElementMatcher getInjectConstructorMatcher() { + return isAnnotatedWithType("javax.inject.Inject") + .and(takesArgumentWithType(2, "play.api.inject.Injector")); + } + + public static ElementMatcher getFiltersMethodMatcher() { + String scala212Seq = "scala.collection.Seq"; + String scala213Seq = "scala.collection.immutable.Seq"; + return (named(ENHANCE_METHOD).and(ReturnTypeNameMatch.returnsWithType(scala212Seq))) + .or(named(ENHANCE_METHOD).and(ReturnTypeNameMatch.returnsWithType(scala213Seq))); + } + +} diff --git a/apm-sniffer/optional-plugins/play-2.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/optional-plugins/play-2.x-plugin/src/main/resources/skywalking-plugin.def new file mode 100644 index 000000000..78411249a --- /dev/null +++ b/apm-sniffer/optional-plugins/play-2.x-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. + +play-2.x=org.apache.skywalking.apm.plugin.play.v2x.define.Play2xInstrumentation diff --git a/apm-sniffer/optional-plugins/play-2.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/play/v2x/HttpFiltersInterceptorTest.java b/apm-sniffer/optional-plugins/play-2.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/play/v2x/HttpFiltersInterceptorTest.java new file mode 100644 index 000000000..292671886 --- /dev/null +++ b/apm-sniffer/optional-plugins/play-2.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/play/v2x/HttpFiltersInterceptorTest.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.play.v2x; + +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.junit.Assert; +import org.junit.Test; +import play.api.inject.BindingKey; +import play.api.inject.Injector; +import scala.collection.immutable.Seq; +import scala.collection.immutable.Seq$; +import scala.reflect.ClassTag; + +import java.util.Objects; + +/** + * @author AI + * 2019-08-07 + */ +public class HttpFiltersInterceptorTest { + + private EnhancedInstance enhancedInstance = new EnhancedInstance() { + private Object object = null; + + @Override + public Object getSkyWalkingDynamicField() { + return object; + } + + @Override + public void setSkyWalkingDynamicField(Object value) { + this.object = value; + } + }; + + private HttpFiltersInterceptor interceptor = new HttpFiltersInterceptor(); + private Injector injector = new Injector() { + @Override + public T instanceOf(ClassTag evidence) { + throw new UnsupportedOperationException(); + } + + @Override + public T instanceOf(Class clazz) { + return (T) new TracingFilter(null); + } + + @Override + public T instanceOf(BindingKey key) { + throw new UnsupportedOperationException(); + } + + @Override + public play.inject.Injector asJava() { + throw new UnsupportedOperationException(); + } + }; + + @Test + public void testBindingInjector() { + Object[] arguments = new Object[]{null, null, injector}; + interceptor.onConstruct(enhancedInstance, arguments); + Assert.assertTrue(Objects.nonNull(enhancedInstance.getSkyWalkingDynamicField())); + Assert.assertTrue(enhancedInstance.getSkyWalkingDynamicField() instanceof Injector); + } + + @Test + public void testReturningTracingFilter() throws Throwable { + Seq ret = Seq$.MODULE$.empty(); + enhancedInstance.setSkyWalkingDynamicField(injector); + Object result = interceptor.afterMethod(enhancedInstance, null, null, null, ret); + Assert.assertTrue(Objects.nonNull(result)); + Seq filters = (Seq) result; + Assert.assertTrue(filters.head() instanceof TracingFilter); + } + +} diff --git a/apm-sniffer/optional-plugins/play-2.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/play/v2x/Play2xInstrumentationTest.java b/apm-sniffer/optional-plugins/play-2.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/play/v2x/Play2xInstrumentationTest.java new file mode 100644 index 000000000..d6b08fe08 --- /dev/null +++ b/apm-sniffer/optional-plugins/play-2.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/play/v2x/Play2xInstrumentationTest.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.skywalking.apm.plugin.play.v2x; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.plugin.play.v2x.define.Play2xInstrumentation; +import org.junit.Assert; +import org.junit.Test; +import play.api.http.EnabledFilters; + +/** + * @author AI + * 2019-08-15 + */ +public class Play2xInstrumentationTest { + + @Test + public void testConstructorMatch() throws Exception { + final ElementMatcher matcher = Play2xInstrumentation.getInjectConstructorMatcher(); + final MethodDescription method = new MethodDescription.ForLoadedConstructor( + EnabledFilters.class.getConstructor(play.api.Environment.class, play.api.Configuration.class, play.api.inject.Injector.class) + ); + Assert.assertTrue(matcher.matches(method)); + } + + @Test + public void testMethodMatch() throws Exception { + final ElementMatcher matcher = Play2xInstrumentation.getFiltersMethodMatcher(); + final MethodDescription method = new MethodDescription.ForLoadedMethod(EnabledFilters.class.getMethod("filters")); + Assert.assertTrue(matcher.matches(method)); + } +} diff --git a/apm-sniffer/optional-plugins/play-2.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/play/v2x/TracingFilterTest.java b/apm-sniffer/optional-plugins/play-2.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/play/v2x/TracingFilterTest.java new file mode 100644 index 000000000..5493c8b8c --- /dev/null +++ b/apm-sniffer/optional-plugins/play-2.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/play/v2x/TracingFilterTest.java @@ -0,0 +1,242 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.play.v2x; + +import akka.stream.Materializer; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment; +import org.apache.skywalking.apm.agent.test.helper.SegmentHelper; +import org.apache.skywalking.apm.agent.test.helper.SpanHelper; +import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule; +import org.apache.skywalking.apm.agent.test.tools.SegmentStorage; +import org.apache.skywalking.apm.agent.test.tools.SegmentStoragePoint; +import org.apache.skywalking.apm.agent.test.tools.SpanAssert; +import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +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 play.api.http.MediaRange; +import play.api.mvc.RequestHeader; +import play.api.routing.HandlerDef; +import play.i18n.Lang; +import play.libs.typedmap.TypedKey; +import play.libs.typedmap.TypedMap; +import play.mvc.Http; +import play.mvc.Result; +import play.routing.Router; + +import java.security.cert.X509Certificate; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.function.Function; + +import static org.apache.skywalking.apm.agent.test.tools.SpanAssert.assertComponent; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static play.mvc.Results.badRequest; +import static play.mvc.Results.ok; + + +/** + * @author AI + * 2019-08-16 + */ +@RunWith(PowerMockRunner.class) +@PowerMockRunnerDelegate(TracingSegmentRunner.class) +public class TracingFilterTest { + + @SegmentStoragePoint + private SegmentStorage segmentStorage; + @Rule + public AgentServiceRule serviceRule = new AgentServiceRule(); + + @Mock + private Materializer materializer; + + private Http.RequestHeader request = new Http.RequestHeader() { + @Override + public String uri() { + return "/projects/1"; + } + + @Override + public String method() { + return "GET"; + } + + @Override + public String version() { + return null; + } + + @Override + public String remoteAddress() { + return null; + } + + @Override + public boolean secure() { + return false; + } + + @Override + public TypedMap attrs() { + HandlerDef def = new HandlerDef(null, null, null, "GET", + null, null, "/projects/$projectId<[^/]+>", null, null); + return TypedMap.empty().put(Router.Attrs.HANDLER_DEF, def); + } + + @Override + public Http.RequestHeader withAttrs(TypedMap typedMap) { + return null; + } + + @Override + public Http.RequestHeader addAttr(TypedKey typedKey, A a) { + return null; + } + + @Override + public Http.RequestHeader removeAttr(TypedKey typedKey) { + return null; + } + + @Override + public Http.Request withBody(Http.RequestBody requestBody) { + return null; + } + + @Override + public String host() { + return "localhost:8080"; + } + + @Override + public String path() { + return "/projects/1"; + } + + @Override + public List acceptLanguages() { + return null; + } + + @Override + public List acceptedTypes() { + return null; + } + + @Override + public boolean accepts(String s) { + return false; + } + + @Override + public Map queryString() { + return null; + } + + @Override + public String getQueryString(String s) { + return null; + } + + @Override + public Http.Cookies cookies() { + return null; + } + + @Override + public Http.Cookie cookie(String s) { + return null; + } + + @Override + public Http.Headers getHeaders() { + return new Http.Headers(new HashMap<>()); + } + + @Override + public boolean hasBody() { + return false; + } + + @Override + public Optional contentType() { + return Optional.empty(); + } + + @Override + public Optional charset() { + return Optional.empty(); + } + + @Override + public Optional> clientCertificateChain() { + return Optional.empty(); + } + + @Override + public RequestHeader asScala() { + return null; + } + }; + + @Test + public void testStatusCodeIsOk() throws Exception { + TracingFilter filter = new TracingFilter(materializer); + Function> next = requestHeader -> CompletableFuture.supplyAsync(() -> ok("Hello")); + CompletionStage result = filter.apply(next, request); + result.toCompletableFuture().get(); + assertThat(segmentStorage.getTraceSegments().size(), is(1)); + TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); + List spans = SegmentHelper.getSpans(traceSegment); + assertHttpSpan(spans.get(0)); + } + + @Test + public void testStatusCodeIsNotOk() throws Exception { + TracingFilter filter = new TracingFilter(materializer); + Function> next = requestHeader -> CompletableFuture.supplyAsync(() -> badRequest("Hello")); + CompletionStage result = filter.apply(next, request); + result.toCompletableFuture().get(); + assertThat(segmentStorage.getTraceSegments().size(), is(1)); + TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); + List spans = SegmentHelper.getSpans(traceSegment); + assertHttpSpan(spans.get(0)); + assertThat(SpanHelper.getErrorOccurred(spans.get(0)), is(true)); + } + + private void assertHttpSpan(AbstractTracingSpan span) { + assertThat(span.getOperationName(), is("/projects/{projectId}")); + assertComponent(span, ComponentsDefine.PLAY); + SpanAssert.assertTag(span, 0, "localhost:8080/projects/1"); + assertThat(span.isEntry(), is(true)); + SpanAssert.assertLayer(span, SpanLayer.HTTP); + } + +} diff --git a/apm-sniffer/optional-plugins/pom.xml b/apm-sniffer/optional-plugins/pom.xml index 0ffd350aa..bc8452631 100644 --- a/apm-sniffer/optional-plugins/pom.xml +++ b/apm-sniffer/optional-plugins/pom.xml @@ -47,6 +47,7 @@ lettuce-5.x-plugin zookeeper-3.4.x-plugin customize-enhance-plugin + play-2.x-plugin diff --git a/docs/en/setup/service-agent/java-agent/README.md b/docs/en/setup/service-agent/java-agent/README.md index 26e4c7932..5b8a5b2b0 100755 --- a/docs/en/setup/service-agent/java-agent/README.md +++ b/docs/en/setup/service-agent/java-agent/README.md @@ -116,6 +116,7 @@ Now, we have the following known optional plugins. * Plugin of Zookeeper 3.4.x in optional plugin folder. The reason of being optional plugin is, many business irrelevant traces are generated, which cause extra payload to agents and backends. At the same time, those traces may be just heartbeat(s). * [Customize enhance](Customize-enhance-trace.md) Trace methods based on description files, rather than write plugin or change source codes. * Plugin of Spring Cloud Gateway 2.1.x in optional plugin folder. Please only active this plugin when you install agent in Spring Gateway. +* Plugin of [Play Framework](https://www.playframework.com/) 2.6+ (JDK 1.8 required & Scala 2.12/2.13) in optional plugin folder. Please only active this plugin when you install agent in [Play Framework](https://www.playframework.com/). ## Bootstrap class plugins All bootstrap plugins are optional, due to unexpected risk. Bootstrap plugins are provided in `bootstrap-plugins` folder. 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 1f52dffdd..73566be41 100644 --- a/docs/en/setup/service-agent/java-agent/Supported-list.md +++ b/docs/en/setup/service-agent/java-agent/Supported-list.md @@ -12,6 +12,7 @@ * [Spring Webflux](https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html) 5.x * [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²) * 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/docs/powered-by.md b/docs/powered-by.md index 19e927743..b4875c71a 100644 --- a/docs/powered-by.md +++ b/docs/powered-by.md @@ -38,6 +38,7 @@ or providing commercial products including Apache SkyWalking. 1. GOME 国美 https://www.gome.com.cn/ 1. Guazi.com 瓜子二手车直卖网. https://www.guazi.com/ 1. guohuaitech.com 北京国槐信息科技有限公司. http://www.guohuaitech.com/ +1. GrowingIO 北京易数科技有限公司 https://www.growingio.com/ 1. Haier. 海尔消费金融 https://www.haiercash.com/ 1. Haoyunhu. 上海好运虎供应链管理有限公司 http://www.haoyunhu56.com/ 1. Huawei Inc. DevCloud. https://www.huaweicloud.com/devcloud/ 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 c3011ecb2..e539cd6bd 100755 --- a/oap-server/server-core/src/test/resources/component-libraries.yml +++ b/oap-server/server-core/src/test/resources/component-libraries.yml @@ -212,6 +212,9 @@ JdkHttp: spring-webflux: id: 67 languages: Java +Play: + id: 68 + languages: Java,Scala # .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 9a1688181..ae33ede9f 100755 --- a/oap-server/server-starter/src/main/resources/component-libraries.yml +++ b/oap-server/server-starter/src/main/resources/component-libraries.yml @@ -230,6 +230,9 @@ JdkHttp: spring-webflux: id: 67 languages: Java +Play: + id: 68 + languages: Java,Scala # .NET/.NET Core components