提交 8bf7e346 编写于 作者: A Andy Ai 提交者: wu-sheng

Support play 2.x (#3207)

* Support play 2.x 
上级 3d00d83f
......@@ -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");
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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.
* <p>
*
* @author AI
* 2019-08-15
*/
public class AnnotationTypeNameMatch<T extends AnnotationDescription> implements ElementMatcher<T> {
/**
* 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 <T> The type of the object that is being matched.
* @return new {@link AnnotationTypeNameMatch} instance.
*/
public static <T extends AnnotationSource> ElementMatcher.Junction<T> isAnnotatedWithType(String annotationTypeName) {
final AnnotationTypeNameMatch<AnnotationDescription> matcher = new AnnotationTypeNameMatch<AnnotationDescription>(annotationTypeName);
return new DeclaringAnnotationMatcher<T>(new CollectionItemMatcher<AnnotationDescription>(matcher));
}
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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.
* <p>
*
* @author AI
* 2019-08-15
*/
public class ReturnTypeNameMatch implements ElementMatcher<MethodDescription> {
/**
* 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<MethodDescription> returnsWithType(String returnTypeName) {
return new ReturnTypeNameMatch(returnTypeName);
}
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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<MethodDescription> 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))));
}
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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 {
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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;
}
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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<MethodDescription> 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"))));
}
}
<?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>optional-plugins</artifactId>
<version>6.4.0-SNAPSHOT</version>
</parent>
<artifactId>apm-play-2.x-plugin</artifactId>
<name>play-2.x-plugin</name>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<play2.version>2.7.3</play2.version>
<compiler.version>1.8</compiler.version>
</properties>
<dependencies>
<dependency>
<groupId>com.typesafe.play</groupId>
<artifactId>play_2.12</artifactId>
<version>${play2.version}</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.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<Object> 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]);
}
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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<Result> apply(Function<Http.RequestHeader, CompletionStage<Result>> 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<String> 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<Result> 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);
}
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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<MethodDescription> getConstructorMatcher() {
return Play2xInstrumentation.getInjectConstructorMatcher();
}
@Override
public String getConstructorInterceptor() {
return INTERCEPTOR_CLASS;
}
}
};
}
@Override
public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
return new InstanceMethodsInterceptPoint[]{
new InstanceMethodsInterceptPoint() {
@Override
public ElementMatcher<MethodDescription> getMethodsMatcher() {
return Play2xInstrumentation.getFiltersMethodMatcher();
}
@Override
public String getMethodsInterceptor() {
return INTERCEPTOR_CLASS;
}
@Override
public boolean isOverrideArgs() {
return false;
}
}
};
}
public static ElementMatcher<MethodDescription> getInjectConstructorMatcher() {
return isAnnotatedWithType("javax.inject.Inject")
.and(takesArgumentWithType(2, "play.api.inject.Injector"));
}
public static ElementMatcher<MethodDescription> 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)));
}
}
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT 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
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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> T instanceOf(ClassTag<T> evidence) {
throw new UnsupportedOperationException();
}
@Override
public <T> T instanceOf(Class<T> clazz) {
return (T) new TracingFilter(null);
}
@Override
public <T> T instanceOf(BindingKey<T> 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);
}
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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<MethodDescription> 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<MethodDescription> matcher = Play2xInstrumentation.getFiltersMethodMatcher();
final MethodDescription method = new MethodDescription.ForLoadedMethod(EnabledFilters.class.getMethod("filters"));
Assert.assertTrue(matcher.matches(method));
}
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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 <A> Http.RequestHeader addAttr(TypedKey<A> 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<Lang> acceptLanguages() {
return null;
}
@Override
public List<MediaRange> acceptedTypes() {
return null;
}
@Override
public boolean accepts(String s) {
return false;
}
@Override
public Map<String, String[]> 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<String> contentType() {
return Optional.empty();
}
@Override
public Optional<String> charset() {
return Optional.empty();
}
@Override
public Optional<List<X509Certificate>> clientCertificateChain() {
return Optional.empty();
}
@Override
public RequestHeader asScala() {
return null;
}
};
@Test
public void testStatusCodeIsOk() throws Exception {
TracingFilter filter = new TracingFilter(materializer);
Function<Http.RequestHeader, CompletionStage<Result>> next = requestHeader -> CompletableFuture.supplyAsync(() -> ok("Hello"));
CompletionStage<Result> result = filter.apply(next, request);
result.toCompletableFuture().get();
assertThat(segmentStorage.getTraceSegments().size(), is(1));
TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0);
List<AbstractTracingSpan> spans = SegmentHelper.getSpans(traceSegment);
assertHttpSpan(spans.get(0));
}
@Test
public void testStatusCodeIsNotOk() throws Exception {
TracingFilter filter = new TracingFilter(materializer);
Function<Http.RequestHeader, CompletionStage<Result>> next = requestHeader -> CompletableFuture.supplyAsync(() -> badRequest("Hello"));
CompletionStage<Result> result = filter.apply(next, request);
result.toCompletableFuture().get();
assertThat(segmentStorage.getTraceSegments().size(), is(1));
TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0);
List<AbstractTracingSpan> 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);
}
}
......@@ -47,6 +47,7 @@
<module>lettuce-5.x-plugin</module>
<module>zookeeper-3.4.x-plugin</module>
<module>customize-enhance-plugin</module>
<module>play-2.x-plugin</module>
</modules>
<dependencies>
......
......@@ -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.
......
......@@ -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
......
......@@ -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/
......
......@@ -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
......
......@@ -230,6 +230,9 @@ JdkHttp:
spring-webflux:
id: 67
languages: Java
Play:
id: 68
languages: Java,Scala
# .NET/.NET Core components
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册