Skip to content

Commit d5b99f9

Browse files
authored
add support for HttpExchange along with webflux-webclient-6.x (#664)
1 parent 3c243e1 commit d5b99f9

File tree

29 files changed

+1182
-0
lines changed

29 files changed

+1182
-0
lines changed

.github/workflows/plugins-jdk17-test.1.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ jobs:
5858
- spring-6.x-scenario
5959
- resteasy-6.x-scenario
6060
- gateway-4.x-scenario
61+
- httpexchange-scenario
6162
steps:
6263
- uses: actions/checkout@v2
6364
with:

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Release Notes.
1010
* Support for tracing spring-cloud-gateway 4.x in gateway-4.x-plugin.
1111
* Fix re-transform bug when plugin enhanced class proxy parent method.
1212
* Fix error HTTP status codes not recording as SLA failures in Vert.x plugins.
13+
* Support for HttpExchange request tracing
1314

1415
#### Documentation
1516

apm-sniffer/apm-sdk-plugin/spring-plugins/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
<module>spring-kafka-2.x-plugin</module>
4444
<module>scheduled-annotation-plugin</module>
4545
<module>spring-webflux-5.x-webclient-plugin</module>
46+
<module>spring-webflux-6.x-webclient-plugin</module>
4647
<module>resttemplate-commons</module>
4748
</modules>
4849
<packaging>pom</packaging>

apm-sniffer/apm-sdk-plugin/spring-plugins/spring-webflux-5.x-webclient-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/webflux/v5/webclient/define/WebFluxWebClientInstrumentation.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@
1818

1919
package org.apache.skywalking.apm.plugin.spring.webflux.v5.webclient.define;
2020

21+
import java.util.Collections;
22+
import java.util.List;
2123
import net.bytebuddy.description.method.MethodDescription;
2224
import net.bytebuddy.matcher.ElementMatcher;
25+
import org.apache.skywalking.apm.agent.core.plugin.WitnessMethod;
2326
import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint;
2427
import org.apache.skywalking.apm.agent.core.plugin.interceptor.v2.InstanceMethodsInterceptV2Point;
2528
import org.apache.skywalking.apm.agent.core.plugin.interceptor.StaticMethodsInterceptPoint;
@@ -32,6 +35,8 @@
3235
public class WebFluxWebClientInstrumentation extends ClassInstanceMethodsEnhancePluginDefineV2 {
3336
private static final String ENHANCE_CLASS = "org.springframework.web.reactive.function.client.ExchangeFunctions$DefaultExchangeFunction";
3437
private static final String INTERCEPT_CLASS = "org.apache.skywalking.apm.plugin.spring.webflux.v5.webclient.WebFluxWebClientInterceptor";
38+
private static final String WEBFLUX_CONTEXT_WRITE_CLASS = "reactor.core.publisher.Mono";
39+
private static final String WEBFLUX_CONTEXT_WRITE_METHOD = "subscriberContext";
3540

3641
@Override
3742
protected ClassMatch enhanceClass() {
@@ -69,4 +74,10 @@ public boolean isOverrideArgs() {
6974
public StaticMethodsInterceptPoint[] getStaticMethodsInterceptPoints() {
7075
return new StaticMethodsInterceptPoint[0];
7176
}
77+
78+
@Override
79+
protected List<WitnessMethod> witnessMethods() {
80+
return Collections.singletonList(
81+
new WitnessMethod(WEBFLUX_CONTEXT_WRITE_CLASS, named(WEBFLUX_CONTEXT_WRITE_METHOD)));
82+
}
7283
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
~ Licensed to the Apache Software Foundation (ASF) under one or more
4+
~ contributor license agreements. See the NOTICE file distributed with
5+
~ this work for additional information regarding copyright ownership.
6+
~ The ASF licenses this file to You under the Apache License, Version 2.0
7+
~ (the "License"); you may not use this file except in compliance with
8+
~ the License. You may obtain a copy of the License at
9+
~
10+
~ http://www.apache.org/licenses/LICENSE-2.0
11+
~
12+
~ Unless required by applicable law or agreed to in writing, software
13+
~ distributed under the License is distributed on an "AS IS" BASIS,
14+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
~ See the License for the specific language governing permissions and
16+
~ limitations under the License.
17+
~
18+
-->
19+
20+
<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">
21+
<parent>
22+
<artifactId>spring-plugins</artifactId>
23+
<groupId>org.apache.skywalking</groupId>
24+
<version>9.2.0-SNAPSHOT</version>
25+
</parent>
26+
<modelVersion>4.0.0</modelVersion>
27+
28+
<artifactId>spring-webflux-6.x-webclient-plugin</artifactId>
29+
30+
<url>http://maven.apache.org</url>
31+
32+
<dependencies>
33+
<dependency>
34+
<groupId>org.springframework</groupId>
35+
<artifactId>spring-webflux</artifactId>
36+
<version>6.0.0</version>
37+
<scope>provided</scope>
38+
</dependency>
39+
</dependencies>
40+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
19+
package org.apache.skywalking.apm.plugin.spring.webflux.v6.webclient;
20+
21+
import org.apache.skywalking.apm.agent.core.context.CarrierItem;
22+
import org.apache.skywalking.apm.agent.core.context.ContextCarrier;
23+
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
24+
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
25+
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult;
26+
import org.springframework.http.client.reactive.ClientHttpRequest;
27+
28+
import java.lang.reflect.Method;
29+
30+
public class BodyInserterRequestInterceptor implements InstanceMethodsAroundInterceptor {
31+
32+
@Override
33+
public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
34+
MethodInterceptResult result) throws Throwable {
35+
ClientHttpRequest clientHttpRequest = (ClientHttpRequest) allArguments[0];
36+
ContextCarrier contextCarrier = (ContextCarrier) objInst.getSkyWalkingDynamicField();
37+
CarrierItem next = contextCarrier.items();
38+
while (next.hasNext()) {
39+
next = next.next();
40+
clientHttpRequest.getHeaders().set(next.getHeadKey(), next.getHeadValue());
41+
}
42+
}
43+
44+
@Override
45+
public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
46+
Object ret) throws Throwable {
47+
return ret;
48+
}
49+
50+
@Override
51+
public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
52+
Class<?>[] argumentsTypes, Throwable t) {
53+
}
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
19+
package org.apache.skywalking.apm.plugin.spring.webflux.v6.webclient;
20+
21+
import org.apache.skywalking.apm.agent.core.context.ContextCarrier;
22+
import org.apache.skywalking.apm.agent.core.context.ContextManager;
23+
import org.apache.skywalking.apm.agent.core.context.ContextSnapshot;
24+
import org.apache.skywalking.apm.agent.core.context.tag.Tags;
25+
import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan;
26+
import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer;
27+
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
28+
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.InstanceMethodsAroundInterceptorV2;
29+
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.MethodInvocationContext;
30+
import org.apache.skywalking.apm.network.trace.component.ComponentsDefine;
31+
import org.springframework.http.HttpStatusCode;
32+
import org.springframework.web.reactive.function.client.ClientRequest;
33+
import org.springframework.web.reactive.function.client.ClientResponse;
34+
import reactor.core.publisher.Mono;
35+
36+
import java.lang.reflect.Method;
37+
import java.net.URI;
38+
import java.util.Optional;
39+
40+
public class WebFluxWebClientInterceptor implements InstanceMethodsAroundInterceptorV2 {
41+
42+
@Override
43+
public void beforeMethod(EnhancedInstance objInst,
44+
Method method,
45+
Object[] allArguments,
46+
Class<?>[] argumentsTypes,
47+
MethodInvocationContext context) throws Throwable {
48+
49+
}
50+
51+
@Override
52+
public Object afterMethod(EnhancedInstance objInst,
53+
Method method,
54+
Object[] allArguments,
55+
Class<?>[] argumentsTypes,
56+
Object ret,
57+
MethodInvocationContext context) throws Throwable {
58+
// fix the problem that allArgument[0] may be null
59+
if (allArguments[0] == null) {
60+
return ret;
61+
}
62+
Mono<ClientResponse> ret1 = (Mono<ClientResponse>) ret;
63+
return Mono.deferContextual(ctx -> {
64+
65+
ClientRequest request = (ClientRequest) allArguments[0];
66+
URI uri = request.url();
67+
final String operationName = getRequestURIString(uri);
68+
final String remotePeer = getIPAndPort(uri);
69+
AbstractSpan span = ContextManager.createExitSpan(operationName, remotePeer);
70+
71+
// get ContextSnapshot from reactor context, the snapshot is set to reactor context by any other plugin
72+
// such as DispatcherHandlerHandleMethodInterceptor in spring-webflux-5.x-plugin
73+
final Optional<Object> optional = ctx.getOrEmpty("SKYWALKING_CONTEXT_SNAPSHOT");
74+
optional.ifPresent(snapshot -> ContextManager.continued((ContextSnapshot) snapshot));
75+
76+
//set components name
77+
span.setComponent(ComponentsDefine.SPRING_WEBCLIENT);
78+
Tags.URL.set(span, uri.toString());
79+
Tags.HTTP.METHOD.set(span, request.method().toString());
80+
SpanLayer.asHttp(span);
81+
82+
final ContextCarrier contextCarrier = new ContextCarrier();
83+
ContextManager.inject(contextCarrier);
84+
if (request instanceof EnhancedInstance) {
85+
((EnhancedInstance) request).setSkyWalkingDynamicField(contextCarrier);
86+
}
87+
88+
//user async interface
89+
span.prepareForAsync();
90+
ContextManager.stopSpan();
91+
return ret1.doOnSuccess(clientResponse -> {
92+
HttpStatusCode httpStatus = clientResponse.statusCode();
93+
if (httpStatus != null) {
94+
Tags.HTTP_RESPONSE_STATUS_CODE.set(span, httpStatus.value());
95+
if (httpStatus.isError()) {
96+
span.errorOccurred();
97+
}
98+
}
99+
}).doOnError(span::log).doFinally(s -> {
100+
span.asyncFinish();
101+
});
102+
});
103+
}
104+
105+
@Override
106+
public void handleMethodException(EnhancedInstance objInst,
107+
Method method,
108+
Object[] allArguments,
109+
Class<?>[] argumentsTypes,
110+
Throwable t,
111+
MethodInvocationContext context) {
112+
AbstractSpan activeSpan = ContextManager.activeSpan();
113+
activeSpan.errorOccurred();
114+
activeSpan.log(t);
115+
}
116+
117+
private String getRequestURIString(URI uri) {
118+
String requestPath = uri.getPath();
119+
return requestPath != null && requestPath.length() > 0 ? requestPath : "/";
120+
}
121+
122+
// return ip:port
123+
private String getIPAndPort(URI uri) {
124+
return uri.getHost() + ":" + uri.getPort();
125+
}
126+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
19+
package org.apache.skywalking.apm.plugin.spring.webflux.v6.webclient.define;
20+
21+
import net.bytebuddy.description.method.MethodDescription;
22+
import net.bytebuddy.matcher.ElementMatcher;
23+
import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint;
24+
import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint;
25+
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine;
26+
import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch;
27+
28+
import static net.bytebuddy.matcher.ElementMatchers.named;
29+
import static org.apache.skywalking.apm.agent.core.plugin.bytebuddy.ArgumentTypeNameMatch.takesArgumentWithType;
30+
import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName;
31+
32+
public class BodyInserterRequestInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {
33+
34+
@Override
35+
public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
36+
return new ConstructorInterceptPoint[0];
37+
}
38+
39+
@Override
40+
public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
41+
return new InstanceMethodsInterceptPoint[] {
42+
new InstanceMethodsInterceptPoint() {
43+
@Override
44+
public ElementMatcher<MethodDescription> getMethodsMatcher() {
45+
return named("writeTo").and(
46+
takesArgumentWithType(0, "org.springframework.http.client.reactive.ClientHttpRequest"));
47+
}
48+
49+
@Override
50+
public String getMethodsInterceptor() {
51+
return "org.apache.skywalking.apm.plugin.spring.webflux.v6.webclient.BodyInserterRequestInterceptor";
52+
}
53+
54+
@Override
55+
public boolean isOverrideArgs() {
56+
return false;
57+
}
58+
}
59+
};
60+
}
61+
62+
@Override
63+
protected ClassMatch enhanceClass() {
64+
return byName(
65+
"org.springframework.web.reactive.function.client.DefaultClientRequestBuilder$BodyInserterRequest");
66+
}
67+
}

0 commit comments

Comments
 (0)