提交 bc77fda5 编写于 作者: C cyejing 提交者: wu-sheng

Enhance webflux plugin, related to Spring Gateway plugin too. (#3945)

* webflux-reactive

* add support version comment, modify gateway

* add test case, modify name

* add docs

* remove healthCheck
上级 3f48790c
......@@ -106,7 +106,7 @@ jobs:
run: ./mvnw -f test/plugin/pom.xml clean package -DskipTests docker:build -DBUILD_NO=local >/dev/null
- name: Run apm-toolkit-trace (1)
run: bash test/plugin/run.sh apm-toolkit-trace-scenario
- name: Run spring-webflux 2.x (18)
- name: Run spring-webflux 2.x (7)
run: bash test/plugin/run.sh webflux-scenario
- name: Run netty-socketio 1.x (4)
run: bash test/plugin/run.sh netty-socketio-scenario
......
......@@ -38,7 +38,6 @@
<module>mvc-annotation-commons</module>
<module>spring-commons</module>
<module>mvc-annotation-5.x-plugin</module>
<module>webflux-5.x-plugin</module>
</modules>
<packaging>pom</packaging>
......
/*
* 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.spring.webflux.v5;
/**
* @author zhaoyuguang
*/
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.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.springframework.http.HttpStatus;
import org.springframework.web.server.ServerWebExchange;
import java.lang.reflect.Method;
public class AbstractServerResponseMethodInterceptor implements InstanceMethodsAroundInterceptor {
/**
* The error reason
* see more details org.springframework.boot.web.reactive.error.DefaultErrorAttributes#storeErrorInformation
*/
private final static String ERROR_ATTRIBUTE = "org.springframework.boot.web.reactive.error.DefaultErrorAttributes.ERROR";
@Override
public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes, MethodInterceptResult result) throws Throwable {
EnhancedInstance instance = DispatcherHandlerHandleMethodInterceptor.getInstance(allArguments[0]);
if (instance != null) {
AbstractSpan span = (AbstractSpan) instance.getSkyWalkingDynamicField();
if (span == null) {
return;
}
ServerWebExchange exchange = (ServerWebExchange) allArguments[0];
HttpStatus status = exchange.getResponse().getStatusCode();
if (status != null && status.value() >= 400) {
span.errorOccurred();
if (exchange.getAttribute(ERROR_ATTRIBUTE) != null) {
span.log((Throwable) exchange.getAttribute(ERROR_ATTRIBUTE));
}
Tags.STATUS_CODE.set(span, Integer.toString(status.value()));
}
if (ContextManager.isActive()) {
ContextManager.stopSpan(span);
} else {
span.asyncFinish();
}
((EnhancedInstance) allArguments[0]).setSkyWalkingDynamicField(null);
}
}
@Override
public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes, Object ret) throws Throwable {
return ret;
}
@Override
public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes, Throwable t) {
}
}
/*
* 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.spring.webflux.v5;
/**
* @author zhaoyuguang
*/
import org.apache.skywalking.apm.agent.core.context.ContextManager;
import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan;
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 java.lang.reflect.Method;
public class DispatcherHandlerHandleResultMethodInterceptor implements InstanceMethodsAroundInterceptor {
@Override
public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
MethodInterceptResult result) throws Throwable {
}
@Override
public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
Object ret) throws Throwable {
EnhancedInstance instance = DispatcherHandlerHandleMethodInterceptor.getInstance(allArguments[0]);
if (instance != null) {
AbstractSpan span = (AbstractSpan) instance.getSkyWalkingDynamicField();
if (span != null) {
ContextManager.stopSpan(span);
instance.setSkyWalkingDynamicField(null);
}
}
return ret;
}
@Override
public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
Class<?>[] argumentsTypes, Throwable t) {
}
}
/*
* 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.spring.webflux.v5;
/**
* @author zhaoyuguang
*/
import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan;
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.springframework.web.method.HandlerMethod;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class DispatcherHandlerInvokeHandlerMethodInterceptor implements InstanceMethodsAroundInterceptor {
private static final String ROUTER_SEARCH = "$$Lambda";
private static final String ROUTER_FIELD = "arg$1";
private static final String DOT = ".";
@Override
public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
MethodInterceptResult result) throws Throwable {
EnhancedInstance instance = DispatcherHandlerHandleMethodInterceptor.getInstance(allArguments[0]);
if (instance != null) {
AbstractSpan span = (AbstractSpan) instance.getSkyWalkingDynamicField();
if (span == null) {
return;
}
String handleClassName = allArguments[1].getClass().getSimpleName();
int index = handleClassName.indexOf(ROUTER_SEARCH);
if (index != -1) {
String operationName = handleClassName.substring(0, index);
try {
Field field = allArguments[1].getClass().getDeclaredField(ROUTER_FIELD);
field.setAccessible(true);
operationName = operationName + DOT + field.get(allArguments[1]).getClass().getName();
} catch (NoSuchFieldException ignore) {
}
span.setOperationName(operationName);
} else if (allArguments[1] instanceof HandlerMethod) {
HandlerMethod handler = (HandlerMethod) allArguments[1];
span.setOperationName(getHandlerMethodOperationName(handler));
}
}
}
@Override
public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
Object ret) throws Throwable {
return ret;
}
@Override
public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
Class<?>[] argumentsTypes, Throwable t) {
}
private String getHandlerMethodOperationName(HandlerMethod handler) {
Method method = handler.getMethod();
return method.getDeclaringClass().getName() + DOT + method.getName();
}
}
/*
* 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.spring.webflux.v5.define;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.matcher.ElementMatcher;
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.match.MultiClassNameMatch.byMultiClassMatch;
/**
* @author zhaoyuguang
*/
public class AbstractServerResponseInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {
@Override
protected ClassMatch enhanceClass() {
return byMultiClassMatch("org.springframework.web.reactive.function.server.DefaultServerResponseBuilder$BodyInserterResponse",
"org.springframework.web.reactive.function.server.DefaultServerResponseBuilder$BodyInserterServerResponse",
"org.springframework.web.reactive.function.server.DefaultEntityResponseBuilder$DefaultEntityResponse");
}
@Override
public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
return new ConstructorInterceptPoint[0];
}
@Override
public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
return new InstanceMethodsInterceptPoint[] {
new InstanceMethodsInterceptPoint() {
@Override public ElementMatcher<MethodDescription> getMethodsMatcher() {
return named("writeToInternal");
}
@Override public String getMethodsInterceptor() {
return "org.apache.skywalking.apm.plugin.spring.webflux.v5.AbstractServerResponseMethodInterceptor";
}
@Override public boolean isOverrideArgs() {
return false;
}
}
};
}
}
......@@ -18,7 +18,11 @@
package org.apache.skywalking.apm.plugin.spring.cloud.gateway.v21x;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR;
import java.lang.reflect.Method;
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;
......@@ -31,9 +35,6 @@ import org.springframework.cloud.gateway.route.Route;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.lang.reflect.Method;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR;
/**
......@@ -42,6 +43,7 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.G
public class FilteringWebHandlerInterceptor implements InstanceMethodsAroundInterceptor {
private static final String SPRING_CLOUD_GATEWAY_ROUTE_PREFIX = "GATEWAY/";
@Override
public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
MethodInterceptResult result) throws Throwable {
......@@ -49,18 +51,17 @@ public class FilteringWebHandlerInterceptor implements InstanceMethodsAroundInte
if (instance == null) {
return;
}
AbstractSpan span = (AbstractSpan) instance.getSkyWalkingDynamicField();
if (span == null) {
ContextSnapshot contextSnapshot = (ContextSnapshot) instance.getSkyWalkingDynamicField();
if (contextSnapshot == null) {
return;
}
ServerWebExchange exchange = (ServerWebExchange) allArguments[0];
String operationName = SPRING_CLOUD_GATEWAY_ROUTE_PREFIX;
Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR);
operationName = operationName + route.getId();
span.setOperationName(operationName);
SWTransmitter transmitter = new SWTransmitter(span.prepareForAsync(),ContextManager.capture(),operationName);
SWTransmitter transmitter = new SWTransmitter(contextSnapshot, operationName);
instance.setSkyWalkingDynamicField(transmitter);
ContextManager.stopSpan(span);
}
@Override
......@@ -85,8 +86,6 @@ public class FilteringWebHandlerInterceptor implements InstanceMethodsAroundInte
localSpan.setComponent(ComponentsDefine.SPRING_CLOUD_GATEWAY);
ContextManager.continued(swTransmitter.getSnapshot());
ContextManager.stopSpan(localSpan);
AbstractSpan spanWebflux = swTransmitter.getSpanWebflux();
spanWebflux.asyncFinish();
}
});
}
......
......@@ -48,10 +48,8 @@ public class HttpClientOperationsStatusInterceptor implements InstanceMethodsAro
HttpResponseStatus response = (HttpResponseStatus) ret;
if (response.code() >= 400) {
Tags.STATUS_CODE.set(transmitter.getSpanGateway().errorOccurred(), String.valueOf(response.code()));
Tags.STATUS_CODE.set(transmitter.getSpanWebflux().errorOccurred(), String.valueOf(response.code()));
}
transmitter.getSpanGateway().asyncFinish();
transmitter.getSpanWebflux().asyncFinish();
objInst.setSkyWalkingDynamicField(null);
}
return ret;
......
......@@ -27,25 +27,15 @@ import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan;
public class SWTransmitter {
private AbstractSpan spanWebflux;
private AbstractSpan spanGateway;
private ContextSnapshot snapshot;
private String operationName;
public SWTransmitter(AbstractSpan spanWebflux, ContextSnapshot snapshot, String operationName) {
this.spanWebflux = spanWebflux;
public SWTransmitter(ContextSnapshot snapshot, String operationName) {
this.snapshot = snapshot;
this.operationName = operationName;
}
public AbstractSpan getSpanWebflux() {
return spanWebflux;
}
public void setSpanWebflux(AbstractSpan spanWebflux) {
this.spanWebflux = spanWebflux;
}
public AbstractSpan getSpanGateway() {
return spanGateway;
}
......
......@@ -32,10 +32,11 @@
<module>spring-annotation-plugin</module>
<module>spring-tx-plugin</module>
<module>optional-spring-cloud</module>
<module>spring-webflux-5.x-plugin</module>
</modules>
<properties>
<sdk.plugin.related.dir>/..</sdk.plugin.related.dir>
</properties>
</project>
\ No newline at end of file
</project>
<?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
......@@ -6,27 +7,28 @@
~ (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
~ 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.
~ 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">
<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>spring-plugins</artifactId>
<artifactId>optional-spring-plugins</artifactId>
<groupId>org.apache.skywalking</groupId>
<version>6.6.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>apm-webflux-5.x-plugin</artifactId>
<packaging>jar</packaging>
<artifactId>apm-spring-webflux-5.x-plugin</artifactId>
<name>webflux-5.x-plugin</name>
<url>http://maven.apache.org</url>
<dependencies>
......@@ -37,4 +39,8 @@
<scope>provided</scope>
</dependency>
</dependencies>
<properties>
<compiler.version>1.8</compiler.version>
</properties>
</project>
......@@ -18,14 +18,13 @@
package org.apache.skywalking.apm.plugin.spring.webflux.v5;
/**
* @author zhaoyuguang
*/
import java.lang.reflect.Method;
import java.util.List;
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.tag.Tags.HTTP;
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;
......@@ -33,45 +32,72 @@ import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceM
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult;
import org.apache.skywalking.apm.network.trace.component.ComponentsDefine;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.ServerWebExchangeDecorator;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.util.pattern.PathPattern;
import reactor.core.publisher.Mono;
import java.lang.reflect.Method;
import java.util.List;
/**
* @author zhaoyuguang, Born
*/
public class DispatcherHandlerHandleMethodInterceptor implements InstanceMethodsAroundInterceptor {
private static final String WIP_OPERATION_NAME = "WEBFLUX.handle";
private static final String DEFAULT_OPERATION_NAME = "WEBFLUX.handle";
@Override
public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
MethodInterceptResult result) throws Throwable {
EnhancedInstance instance = DispatcherHandlerHandleMethodInterceptor.getInstance(allArguments[0]);
if (instance != null) {
ContextCarrier contextCarrier = new ContextCarrier();
CarrierItem next = contextCarrier.items();
ServerWebExchange exchange = (ServerWebExchange) allArguments[0];
HttpHeaders headers = exchange.getRequest().getHeaders();
while (next.hasNext()) {
next = next.next();
List<String> header = headers.get(next.getHeadKey());
if (header != null && header.size() > 0) {
next.setHeadValue(header.get(0));
}
}
AbstractSpan span = ContextManager.createEntrySpan(WIP_OPERATION_NAME, contextCarrier);
span.setComponent(ComponentsDefine.SPRING_WEBFLUX);
SpanLayer.asHttp(span);
Tags.URL.set(span, exchange.getRequest().getURI().toString());
instance.setSkyWalkingDynamicField(span);
}
}
@Override
public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
Object ret) throws Throwable {
return ret;
EnhancedInstance instance = getInstance(allArguments[0]);
ServerWebExchange exchange = (ServerWebExchange) allArguments[0];
ContextCarrier carrier = new ContextCarrier();
CarrierItem next = carrier.items();
HttpHeaders headers = exchange.getRequest().getHeaders();
while (next.hasNext()) {
next = next.next();
List<String> header = headers.get(next.getHeadKey());
if (header != null && header.size() > 0) {
next.setHeadValue(header.get(0));
}
}
AbstractSpan span = ContextManager.createEntrySpan(DEFAULT_OPERATION_NAME, carrier);
span.setComponent(ComponentsDefine.SPRING_WEBFLUX);
SpanLayer.asHttp(span);
Tags.URL.set(span, exchange.getRequest().getURI().toString());
HTTP.METHOD.set(span, exchange.getRequest().getMethodValue());
instance.setSkyWalkingDynamicField(ContextManager.capture());
span.prepareForAsync();
ContextManager.stopSpan(span);
return ((Mono) ret).doFinally(s -> {
try {
Object pathPattern = exchange.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
if (pathPattern != null) {
span.setOperationName(((PathPattern) pathPattern).getPatternString());
}
HttpStatus httpStatus = exchange.getResponse().getStatusCode();
// fix webflux-2.0.0-2.1.0 version have bug. httpStatus is null. not support
if (httpStatus != null) {
Tags.STATUS_CODE.set(span, Integer.toString(httpStatus.value()));
if (httpStatus.isError()) {
span.errorOccurred();
}
}
} finally {
span.asyncFinish();
}
});
}
@Override
......@@ -81,26 +107,13 @@ public class DispatcherHandlerHandleMethodInterceptor implements InstanceMethods
public static EnhancedInstance getInstance(Object o) {
EnhancedInstance instance = null;
if (o instanceof ServerWebExchangeDecorator) {
instance = getEnhancedInstance((ServerWebExchangeDecorator) o);
} else if (o instanceof DefaultServerWebExchange) {
if (o instanceof DefaultServerWebExchange) {
instance = (EnhancedInstance) o;
} else if (o instanceof ServerWebExchangeDecorator) {
ServerWebExchange delegate = ((ServerWebExchangeDecorator) o).getDelegate();
return getInstance(delegate);
}
return instance;
}
private static EnhancedInstance getEnhancedInstance(ServerWebExchangeDecorator serverWebExchangeDecorator) {
Object o = serverWebExchangeDecorator.getDelegate();
if (o instanceof ServerWebExchangeDecorator) {
return getEnhancedInstance((ServerWebExchangeDecorator) o);
} else if (o instanceof DefaultServerWebExchange) {
return (EnhancedInstance) o;
} else if (o == null) {
throw new NullPointerException("The expected class DefaultServerWebExchange is null");
} else {
throw new RuntimeException("Unknown parameter types:" + o.getClass());
}
}
}
......@@ -23,7 +23,7 @@ import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceC
/**
*
* @author zhaoyuguang
* @author zhaoyuguang, Born
*/
public class ServerWebExchangeConstructorInterceptor implements InstanceConstructorInterceptor {
@Override
......
......@@ -29,7 +29,7 @@ import static net.bytebuddy.matcher.ElementMatchers.named;
import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName;
/**
* @author zhaoyuguang
* @author zhaoyuguang, Born
*/
public class DispatcherHandlerInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {
......@@ -52,36 +52,6 @@ public class DispatcherHandlerInstrumentation extends ClassInstanceMethodsEnhanc
return "org.apache.skywalking.apm.plugin.spring.webflux.v5.DispatcherHandlerHandleMethodInterceptor";
}
@Override
public boolean isOverrideArgs() {
return false;
}
}, new InstanceMethodsInterceptPoint() {
@Override
public ElementMatcher<MethodDescription> getMethodsMatcher() {
return named("invokeHandler");
}
@Override
public String getMethodsInterceptor() {
return "org.apache.skywalking.apm.plugin.spring.webflux.v5.DispatcherHandlerInvokeHandlerMethodInterceptor";
}
@Override
public boolean isOverrideArgs() {
return false;
}
}, new InstanceMethodsInterceptPoint() {
@Override
public ElementMatcher<MethodDescription> getMethodsMatcher() {
return named("handleResult");
}
@Override
public String getMethodsInterceptor() {
return "org.apache.skywalking.apm.plugin.spring.webflux.v5.DispatcherHandlerHandleResultMethodInterceptor";
}
@Override
public boolean isOverrideArgs() {
return false;
......
......@@ -29,9 +29,8 @@ import static net.bytebuddy.matcher.ElementMatchers.any;
import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName;
/**
* @author zhaoyuguang
* @author zhaoyuguang, Born
*/
public class ServerWebExchangeInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {
@Override
public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
......
......@@ -16,4 +16,3 @@
spring-webflux-5.x=org.apache.skywalking.apm.plugin.spring.webflux.v5.define.DispatcherHandlerInstrumentation
spring-webflux-5.x=org.apache.skywalking.apm.plugin.spring.webflux.v5.define.ServerWebExchangeInstrumentation
spring-webflux-5.x=org.apache.skywalking.apm.plugin.spring.webflux.v5.define.AbstractServerResponseInstrumentation
\ No newline at end of file
......@@ -131,9 +131,10 @@ Now, we have the following known optional plugins.
* Plugin of Lettuce 5.x(JRE 8+) in optional plugin folder. Agent is compatible in JDK 1.6+, this plugin could be used in JRE 8+, by matching the lib requirement.
* 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 Spring Cloud Gateway 2.1.x in optional plugin folder. Please only active this plugin when you install agent in Spring Gateway. spring-cloud-gateway-2.x-plugin and spring-webflux-5.x-plugin are both required.
* 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/).
* Plugin of Spring Transaction in optional plugin folder. The reason of being optional plugin is, many local span are generated, which also spend more CPU, memory and network.
* Plugin of Spring Webflux 5.x in the optional plugin folder. Because the plugin requires JDK 1.8+.
## Bootstrap class plugins
All bootstrap plugins are optional, due to unexpected risk. Bootstrap plugins are provided in `bootstrap-plugins` folder.
......
......@@ -9,7 +9,7 @@
* [Resin](http://www.caucho.com/resin-4.0/) 3 (Optional¹)
* [Resin](http://www.caucho.com/resin-4.0/) 4 (Optional¹)
* [Jetty Server](http://www.eclipse.org/jetty/) 9
* [Spring Webflux](https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html) 5.x
* [Spring Webflux](https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html) 5.x (Optional²)
* [Undertow](http://undertow.io/) 1.3.0.Final -> 2.0.27.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²)
......
......@@ -22,8 +22,8 @@ registryItems:
- {gateway-projectB-scenario: 1}
- {gateway-projectA-scenario: 1}
operationNames:
- gateway-projectB-scenario: [/provider/b/healthCheck, /provider/b/testcase]
- gateway-projectA-scenario: [GATEWAY/provider_route]
- gateway-projectB-scenario: [/provider/b/testcase]
- gateway-projectA-scenario: [WEBFLUX.handle]
heartbeat: []
segmentItems:
- applicationCode: gateway-projectB-scenario
......@@ -47,6 +47,11 @@ segmentItems:
tags:
- {key: url, value: not null}
- {key: http.method, value: GET}
refs:
- {parentEndpointId: -1, parentEndpoint: '', networkAddressId: 0,
entryEndpointId: 0, refType: CrossProcess, parentSpanId: 0, parentTraceSegmentId: not null,
parentServiceInstanceId: not null, networkAddress: 'localhost:18070', entryEndpoint: WEBFLUX.handle,
entryServiceInstanceId: not null}
- applicationCode: gateway-projectA-scenario
segmentSize: nq 0
segments:
......@@ -69,13 +74,13 @@ segmentItems:
- {key: url, value: not null}
- {key: http.method, value: GET}
refs:
- {parentEndpointId: 0, parentEndpoint: GATEWAY/provider_route, networkAddressId: 0,
- {parentEndpointId: 0, parentEndpoint: WEBFLUX.handle, networkAddressId: 0,
entryEndpointId: 0, refType: CrossThread, parentSpanId: 0, parentTraceSegmentId: not null,
parentServiceInstanceId: not null, networkAddress: '', entryEndpoint: GATEWAY/provider_route,
parentServiceInstanceId: not null, networkAddress: '', entryEndpoint: WEBFLUX.handle,
entryServiceInstanceId: not null}
- segmentId: not null
spans:
- operationName: GATEWAY/provider_route
- operationName: WEBFLUX.handle
operationId: 0
parentSpanId: -1
spanId: 0
......@@ -89,4 +94,6 @@ segmentItems:
peer: ''
peerId: 0
tags:
- {key: url, value: not null}
\ No newline at end of file
- {key: url, value: 'http://localhost:8080/provider/b/testcase'}
- {key: http.method, value: GET}
- {key: status_code, value: '200'}
......@@ -20,4 +20,4 @@ healthCheck: http://localhost:8080/provider/b/healthCheck
startScript: ./bin/startup.sh
framework: spring-cloud-gateway
runningMode: with_optional
withPlugins: apm-spring-cloud-gateway-2.x-plugin-*.jar
withPlugins: apm-spring-cloud-gateway-2.x-plugin-*.jar;apm-spring-webflux-5.x-plugin-*.jar
......@@ -39,6 +39,7 @@
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
</dependencies>
<build>
......@@ -69,4 +70,4 @@
<url>http://repo.spring.io/milestone</url>
</pluginRepository>
</pluginRepositories>
</project>
\ No newline at end of file
</project>
......@@ -33,4 +33,4 @@ spring:
key-resolver: "#{@apiKeyResolver}"
redis:
host: 127.0.0.1
port: 6379
\ No newline at end of file
port: 6379
......@@ -22,11 +22,7 @@ registryItems:
- {webflux-projectB-scenario: 1}
- {webflux-projectA-scenario: 1}
operationNames:
- webflux-projectB-scenario: [org.apache.skywalking.apm.testcase.sc.webflux.projectB.controller.TestAnnotationController.success,
org.apache.skywalking.apm.testcase.sc.webflux.projectB.controller.TestAnnotationController.hello,
WEBFLUX.handle, org.apache.skywalking.apm.testcase.sc.webflux.projectB.controller.TestAnnotationController.error,
org.apache.skywalking.apm.testcase.sc.webflux.projectB.controller.TestAnnotationController.healthCheck,
RoutingConfiguration.org.apache.skywalking.apm.testcase.sc.webflux.projectB.route.TestHandler]
- webflux-projectB-scenario: [WEBFLUX.handle]
- webflux-projectA-scenario: [/projectA/testcase]
heartbeat: []
segmentItems:
......@@ -35,7 +31,7 @@ segmentItems:
segments:
- segmentId: not null
spans:
- operationName: org.apache.skywalking.apm.testcase.sc.webflux.projectB.controller.TestAnnotationController.success
- operationName: /testcase/annotation/success
operationId: 0
parentSpanId: -1
spanId: 0
......@@ -49,15 +45,17 @@ segmentItems:
peer: ''
peerId: 0
tags:
- {key: url, value: not null}
- {key: url, value: 'http://localhost:18080/testcase/annotation/success'}
- {key: http.method, value: GET}
- {key: status_code, value: '200'}
refs:
- {parentEndpointId: 0, parentEndpoint: /projectA/testcase, networkAddressId: 0,
entryEndpointId: 0, refType: CrossProcess, parentSpanId: not null, parentTraceSegmentId: not null,
parentServiceInstanceId: not null, networkAddress: not null, entryEndpoint: /projectA/testcase,
entryEndpointId: 0, refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null,
parentServiceInstanceId: not null, networkAddress: 'localhost:18080', entryEndpoint: /projectA/testcase,
entryServiceInstanceId: not null}
- segmentId: not null
spans:
- operationName: org.apache.skywalking.apm.testcase.sc.webflux.projectB.controller.TestAnnotationController.error
- operationName: /testcase/annotation/error
operationId: 0
parentSpanId: -1
spanId: 0
......@@ -71,22 +69,17 @@ segmentItems:
peer: ''
peerId: 0
tags:
- {key: url, value: not null}
- {key: url, value: 'http://localhost:18080/testcase/annotation/error'}
- {key: http.method, value: GET}
- {key: status_code, value: '500'}
logs:
- logEvent:
- {key: event, value: error}
- {key: error.kind, value: java.lang.RuntimeException}
- {key: message, value: test_error}
- {key: stack, value: not null}
refs:
- {parentEndpointId: 0, parentEndpoint: /projectA/testcase, networkAddressId: 0,
entryEndpointId: 0, refType: CrossProcess, parentSpanId: not null, parentTraceSegmentId: not null,
parentServiceInstanceId: not null, networkAddress: not null, entryEndpoint: /projectA/testcase,
entryEndpointId: 0, refType: CrossProcess, parentSpanId: 2, parentTraceSegmentId: not null,
parentServiceInstanceId: not null, networkAddress: 'localhost:18080', entryEndpoint: /projectA/testcase,
entryServiceInstanceId: not null}
- segmentId: not null
spans:
- operationName: RoutingConfiguration.org.apache.skywalking.apm.testcase.sc.webflux.projectB.route.TestHandler
- operationName: /testcase/annotation/{test}
operationId: 0
parentSpanId: -1
spanId: 0
......@@ -100,15 +93,65 @@ segmentItems:
peer: ''
peerId: 0
tags:
- {key: url, value: not null}
- {key: url, value: 'http://localhost:18080/testcase/annotation/foo'}
- {key: http.method, value: GET}
- {key: status_code, value: '200'}
refs:
- {parentEndpointId: 0, parentEndpoint: /projectA/testcase, networkAddressId: 0,
entryEndpointId: 0, refType: CrossProcess, parentSpanId: 3, parentTraceSegmentId: not null,
parentServiceInstanceId: not null, networkAddress: 'localhost:18080', entryEndpoint: /projectA/testcase,
entryServiceInstanceId: not null}
- segmentId: not null
spans:
- operationName: /testcase/annotation/{test}
operationId: 0
parentSpanId: -1
spanId: 0
spanLayer: Http
startTime: not null
endTime: not null
componentId: 67
componentName: ''
isError: false
spanType: Entry
peer: ''
peerId: 0
tags:
- {key: url, value: 'http://localhost:18080/testcase/annotation/loo'}
- {key: http.method, value: GET}
- {key: status_code, value: '200'}
refs:
- {parentEndpointId: 0, parentEndpoint: /projectA/testcase, networkAddressId: 0,
entryEndpointId: 0, refType: CrossProcess, parentSpanId: 4, parentTraceSegmentId: not null,
parentServiceInstanceId: not null, networkAddress: 'localhost:18080', entryEndpoint: /projectA/testcase,
entryServiceInstanceId: not null}
- segmentId: not null
spans:
- operationName: /testcase/route/{test}
operationId: 0
parentSpanId: -1
spanId: 0
spanLayer: Http
startTime: not null
endTime: not null
componentId: 67
componentName: ''
isError: false
spanType: Entry
peer: ''
peerId: 0
tags:
- {key: url, value: 'http://localhost:18080/testcase/route/success'}
- {key: http.method, value: GET}
- {key: status_code, value: '200'}
refs:
- {parentEndpointId: 0, parentEndpoint: /projectA/testcase, networkAddressId: 0,
entryEndpointId: 0, refType: CrossProcess, parentSpanId: not null, parentTraceSegmentId: not null,
parentServiceInstanceId: not null, networkAddress: not null, entryEndpoint: /projectA/testcase,
entryEndpointId: 0, refType: CrossProcess, parentSpanId: 5, parentTraceSegmentId: not null,
parentServiceInstanceId: not null, networkAddress: 'localhost:18080', entryEndpoint: /projectA/testcase,
entryServiceInstanceId: not null}
- segmentId: not null
spans:
- operationName: RoutingConfiguration.org.apache.skywalking.apm.testcase.sc.webflux.projectB.route.TestHandler
- operationName: /testcase/route/{test}
operationId: 0
parentSpanId: -1
spanId: 0
......@@ -122,22 +165,17 @@ segmentItems:
peer: ''
peerId: 0
tags:
- {key: url, value: not null}
- {key: url, value: 'http://localhost:18080/testcase/route/error'}
- {key: http.method, value: GET}
- {key: status_code, value: '500'}
logs:
- logEvent:
- {key: event, value: error}
- {key: error.kind, value: java.lang.RuntimeException}
- {key: message, value: test_error}
- {key: stack, value: not null}
refs:
- {parentEndpointId: 0, parentEndpoint: /projectA/testcase, networkAddressId: 0,
entryEndpointId: 0, refType: CrossProcess, parentSpanId: not null, parentTraceSegmentId: not null,
parentServiceInstanceId: not null, networkAddress: not null, entryEndpoint: /projectA/testcase,
entryEndpointId: 0, refType: CrossProcess, parentSpanId: 6, parentTraceSegmentId: not null,
parentServiceInstanceId: not null, networkAddress: 'localhost:18080', entryEndpoint: /projectA/testcase,
entryServiceInstanceId: not null}
- segmentId: not null
spans:
- operationName: WEBFLUX.handle
- operationName: /**
operationId: 0
parentSpanId: -1
spanId: 0
......@@ -151,22 +189,17 @@ segmentItems:
peer: ''
peerId: 0
tags:
- {key: url, value: not null}
- {key: url, value: 'http://localhost:18080/notFound'}
- {key: http.method, value: GET}
- {key: status_code, value: '404'}
logs:
- logEvent:
- {key: event, value: error}
- {key: error.kind, value: org.springframework.web.server.ResponseStatusException}
- {key: message, value: not null}
- {key: stack, value: not null}
refs:
- {parentEndpointId: 0, parentEndpoint: /projectA/testcase, networkAddressId: 0,
entryEndpointId: 0, refType: CrossProcess, parentSpanId: not null, parentTraceSegmentId: not null,
parentServiceInstanceId: not null, networkAddress: not null, entryEndpoint: /projectA/testcase,
entryEndpointId: 0, refType: CrossProcess, parentSpanId: 7, parentTraceSegmentId: not null,
parentServiceInstanceId: not null, networkAddress: 'localhost:18080', entryEndpoint: /projectA/testcase,
entryServiceInstanceId: not null}
- segmentId: not null
spans:
- operationName: org.apache.skywalking.apm.testcase.sc.webflux.projectB.controller.TestAnnotationController.hello
- operationName: /testcase/annotation/mono/hello
operationId: 0
parentSpanId: -1
spanId: 0
......@@ -180,11 +213,13 @@ segmentItems:
peer: ''
peerId: 0
tags:
- {key: url, value: not null}
- {key: url, value: 'http://localhost:18080/testcase/annotation/mono/hello'}
- {key: http.method, value: GET}
- {key: status_code, value: '200'}
refs:
- {parentEndpointId: 0, parentEndpoint: /projectA/testcase, networkAddressId: 0,
entryEndpointId: 0, refType: CrossProcess, parentSpanId: not null, parentTraceSegmentId: not null,
parentServiceInstanceId: not null, networkAddress: not null, entryEndpoint: /projectA/testcase,
entryEndpointId: 0, refType: CrossProcess, parentSpanId: 8, parentTraceSegmentId: not null,
parentServiceInstanceId: not null, networkAddress: 'localhost:18080', entryEndpoint: /projectA/testcase,
entryServiceInstanceId: not null}
- applicationCode: webflux-projectA-scenario
segmentSize: nq 0
......@@ -224,7 +259,7 @@ segmentItems:
- {key: url, value: not null}
- {key: http.method, value: GET}
- {key: status_code, value: '500'}
- operationName: /testcase/route/success
- operationName: /testcase/annotation/foo
operationId: 0
parentSpanId: 0
spanId: 3
......@@ -235,6 +270,38 @@ segmentItems:
componentName: ''
isError: false
spanType: Exit
peer: localhost:18080
peerId: 0
tags:
- {key: url, value: 'http://localhost:18080/testcase/annotation/foo'}
- {key: http.method, value: GET}
- operationName: /testcase/annotation/loo
operationId: 0
parentSpanId: 0
spanId: 4
spanLayer: Http
startTime: not null
endTime: not null
componentId: 2
componentName: ''
isError: false
spanType: Exit
peer: localhost:18080
peerId: 0
tags:
- {key: url, value: 'http://localhost:18080/testcase/annotation/loo'}
- {key: http.method, value: GET}
- operationName: /testcase/route/success
operationId: 0
parentSpanId: 0
spanId: 5
spanLayer: Http
startTime: not null
endTime: not null
componentId: 2
componentName: ''
isError: false
spanType: Exit
peer: not null
peerId: 0
tags:
......@@ -243,7 +310,7 @@ segmentItems:
- operationName: /testcase/route/error
operationId: 0
parentSpanId: 0
spanId: 4
spanId: 6
spanLayer: Http
startTime: not null
endTime: not null
......@@ -260,7 +327,7 @@ segmentItems:
- operationName: /notFound
operationId: 0
parentSpanId: 0
spanId: 5
spanId: 7
spanLayer: Http
startTime: not null
endTime: not null
......@@ -277,7 +344,7 @@ segmentItems:
- operationName: /testcase/annotation/mono/hello
operationId: 0
parentSpanId: 0
spanId: 6
spanId: 8
spanLayer: Http
startTime: not null
endTime: not null
......@@ -305,4 +372,4 @@ segmentItems:
peerId: 0
tags:
- {key: url, value: not null}
- {key: http.method, value: GET}
\ No newline at end of file
- {key: http.method, value: GET}
......@@ -18,4 +18,6 @@ type: jvm
entryService: http://localhost:8080/projectA/testcase
healthCheck: http://localhost:8080/projectA/healthCheck
startScript: ./bin/startup.sh
framework: spring-webflux
\ No newline at end of file
framework: spring-webflux
runningMode: with_optional
withPlugins: apm-spring-webflux-5.x-plugin-*.jar
......@@ -35,7 +35,7 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<test.framework.version>2.0.0.RELEASE</test.framework.version>
<test.framework.version>2.1.1.RELEASE</test.framework.version>
<docker.image.version>${test.framework.version}</docker.image.version>
</properties>
......
......@@ -14,21 +14,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
2.0.0.RELEASE
2.0.1.RELEASE
2.0.2.RELEASE
2.0.3.RELEASE
2.0.4.RELEASE
2.0.5.RELEASE
2.0.6.RELEASE
2.0.7.RELEASE
2.0.8.RELEASE
2.0.9.RELEASE
2.1.0.RELEASE
# 2.0.0-2.1.0 are supported, but due to the status code bug(https://github.com/spring-projects/spring-framework/issues/21901)
# we don’t test them
2.1.1.RELEASE
2.1.2.RELEASE
2.1.3.RELEASE
2.1.4.RELEASE
2.1.5.RELEASE
2.1.6.RELEASE
2.1.7.RELEASE
\ No newline at end of file
2.1.7.RELEASE
......@@ -59,14 +59,4 @@
</plugins>
</build>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<url>http://repo.spring.io/snapshot</url>
</pluginRepository>
<pluginRepository>
<id>spring-milestones</id>
<url>http://repo.spring.io/milestone</url>
</pluginRepository>
</pluginRepositories>
</project>
\ No newline at end of file
</project>
......@@ -38,6 +38,8 @@ public class TestController {
public String testcase() throws IOException {
visit("http://" + hostBAddress + "/testcase/annotation/success");
visit("http://" + hostBAddress + "/testcase/annotation/error");
visit("http://" + hostBAddress + "/testcase/annotation/foo");
visit("http://" + hostBAddress + "/testcase/annotation/loo");
visit("http://" + hostBAddress + "/testcase/route/success");
visit("http://" + hostBAddress + "/testcase/route/error");
visit("http://" + hostBAddress + "/notFound");
......
......@@ -18,6 +18,7 @@
package org.apache.skywalking.apm.testcase.sc.webflux.projectB.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
......@@ -44,6 +45,11 @@ public class TestAnnotationController {
return "1";
}
@GetMapping("/testcase/annotation/{test}")
public Mono<String> urlPattern(@PathVariable("test") String var) {
return Mono.just(var);
}
@GetMapping("/testcase/annotation/mono/hello")
public Mono<String> hello(@RequestBody(required = false) String body) {
return Mono.just("Hello World");
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册