Skip to content

Commit e7394a8

Browse files
authored
Support asynchronous invocation in jetty client 9.0 and 9.x plugin (#590)
1 parent c6331df commit e7394a8

File tree

23 files changed

+1240
-15
lines changed

23 files changed

+1240
-15
lines changed

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ Callable {
150150
* Fix witness class in springmvc-annotation-5.x-plugin to avoid falling into v3 use cases.
151151
* Fix Jedis-2.x plugin bug and add test for Redis cluster scene
152152
* Merge two instrumentation classes to avoid duplicate enhancements in MySQL plugins.
153+
* Support asynchronous invocation in jetty client 9.0 and 9.x plugin
153154

154155
#### Documentation
155156

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
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.jetty.v90.client;
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.context.ContextManager;
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.InstanceMethodsAroundInterceptor;
29+
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult;
30+
import org.apache.skywalking.apm.network.trace.component.ComponentsDefine;
31+
import org.eclipse.jetty.client.HttpRequest;
32+
import org.eclipse.jetty.client.api.Response;
33+
import org.eclipse.jetty.http.HttpFields;
34+
import org.eclipse.jetty.http.HttpMethod;
35+
36+
import java.lang.reflect.Method;
37+
38+
public class AsyncHttpRequestSendInterceptor implements InstanceMethodsAroundInterceptor {
39+
@Override
40+
public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
41+
MethodInterceptResult result) throws Throwable {
42+
HttpRequest request = (HttpRequest) objInst;
43+
ContextCarrier contextCarrier = new ContextCarrier();
44+
AbstractSpan span = ContextManager.createExitSpan(request.getURI().getPath(), contextCarrier,
45+
request.getHost() + ":" + request.getPort());
46+
span.setComponent(ComponentsDefine.JETTY_CLIENT);
47+
48+
Tags.HTTP.METHOD.set(span, getHttpMethod(request));
49+
Tags.URL.set(span, request.getURI().toString());
50+
SpanLayer.asHttp(span);
51+
52+
CarrierItem next = contextCarrier.items();
53+
HttpFields field = request.getHeaders();
54+
while (next.hasNext()) {
55+
next = next.next();
56+
field.add(next.getHeadKey(), next.getHeadValue());
57+
}
58+
59+
span.prepareForAsync();
60+
request.attribute(Constants.SW_JETTY_EXIT_SPAN_KEY, span);
61+
Response.CompleteListener callback = (Response.CompleteListener) allArguments[0];
62+
allArguments[0] = new CompleteListenerWrapper(callback, ContextManager.capture());
63+
}
64+
65+
@Override
66+
public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
67+
Object ret) throws Throwable {
68+
ContextManager.stopSpan();
69+
return ret;
70+
}
71+
72+
@Override
73+
public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
74+
Class<?>[] argumentsTypes, Throwable t) {
75+
ContextManager.activeSpan().log(t);
76+
}
77+
78+
private String getHttpMethod(HttpRequest request) {
79+
HttpMethod httpMethod = HttpMethod.GET;
80+
81+
/**
82+
* The method is null if the client using GET method.
83+
*
84+
* @see org.eclipse.jetty.client.HttpRequest#GET(String uri)
85+
* @see org.eclipse.jetty.client.HttpRequest( org.eclipse.jetty.client.HttpClient client, long conversation, java.net.URI uri)
86+
*/
87+
if (request.getMethod() != null) {
88+
httpMethod = request.getMethod();
89+
}
90+
91+
return httpMethod.name();
92+
}
93+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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.jetty.v90.client;
20+
21+
import org.apache.skywalking.apm.agent.core.context.ContextManager;
22+
import org.apache.skywalking.apm.agent.core.context.ContextSnapshot;
23+
import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan;
24+
import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer;
25+
import org.apache.skywalking.apm.network.trace.component.ComponentsDefine;
26+
import org.eclipse.jetty.client.api.Response;
27+
import org.eclipse.jetty.client.api.Result;
28+
29+
public class CompleteListenerWrapper implements Response.CompleteListener {
30+
private Response.CompleteListener callback;
31+
private ContextSnapshot context;
32+
33+
public CompleteListenerWrapper(Response.CompleteListener callback, ContextSnapshot context) {
34+
this.callback = callback;
35+
this.context = context;
36+
}
37+
38+
@Override
39+
public void onComplete(Result result) {
40+
AbstractSpan span = ContextManager.createLocalSpan(Constants.PLUGIN_NAME + "/CompleteListener/onComplete");
41+
span.setComponent(ComponentsDefine.JETTY_CLIENT);
42+
SpanLayer.asHttp(span);
43+
if (context != null) {
44+
ContextManager.continued(context);
45+
}
46+
if (callback != null) {
47+
callback.onComplete(result);
48+
}
49+
ContextManager.stopSpan();
50+
}
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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.jetty.v90.client;
20+
21+
public class Constants {
22+
public final static String SW_JETTY_EXIT_SPAN_KEY = "SW_JETTY_EXIT_SPAN";
23+
24+
public final static String PLUGIN_NAME = "JettyClient9.0";
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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.jetty.v90.client;
20+
21+
import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan;
22+
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
23+
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
24+
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult;
25+
import org.eclipse.jetty.client.api.Request;
26+
import org.eclipse.jetty.client.api.Result;
27+
28+
import java.lang.reflect.Method;
29+
import java.util.Optional;
30+
31+
public class ResponseNotifierInterceptor implements InstanceMethodsAroundInterceptor {
32+
@Override
33+
public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
34+
MethodInterceptResult result) throws Throwable {
35+
}
36+
37+
@Override
38+
public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
39+
Object ret) throws Throwable {
40+
// get async span and stop it.
41+
Optional.ofNullable(getAsyncSpan(allArguments)).ifPresent(v -> v.asyncFinish());
42+
return ret;
43+
}
44+
45+
@Override
46+
public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
47+
Class<?>[] argumentsTypes, Throwable t) {
48+
Optional.ofNullable(getAsyncSpan(allArguments)).ifPresent(v -> v.log(t));
49+
}
50+
51+
private AbstractSpan getAsyncSpan(Object[] allArguments) {
52+
Result results = (Result) allArguments[1];
53+
if (results == null) {
54+
return null;
55+
}
56+
Request request = results.getRequest();
57+
if (request == null) {
58+
return null;
59+
}
60+
61+
return (AbstractSpan) request.getAttributes().get(Constants.SW_JETTY_EXIT_SPAN_KEY);
62+
}
63+
}

apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.0-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v90/client/define/HttpRequestInstrumentation.java

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,20 @@
3131

3232
/**
3333
* {@link HttpRequestInstrumentation} enhance the <code>send</code> method without argument in
34-
* <code>org.eclipse.jetty.client.HttpRequest</code> by <code>org.apache.skywalking.apm.plugin.jetty.client.SyncHttpRequestSendInterceptor</code>
35-
* and enhance the <code>send</code> with <code>org.eclipse.jetty.client.api.Response$CompleteListener</code> parameter
36-
* by <code>org.apache.skywalking.apm.plugin.jetty.client.AsyncHttpRequestSendInterceptor</code>
34+
* <code>org.eclipse.jetty.client.HttpRequest</code> by
35+
* <code>org.apache.skywalking.apm.plugin.jetty.client.SyncHttpRequestSendInterceptor</code> and enhance the
36+
* <code>send</code> with <code>org.eclipse.jetty.client.api.Response$CompleteListener</code> parameter by
37+
* <code>org.apache.skywalking.apm.plugin.jetty.client.AsyncHttpRequestSendInterceptor</code>
3738
*/
3839
public class HttpRequestInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {
3940

4041
private static final String ENHANCE_CLASS = "org.eclipse.jetty.client.HttpRequest";
4142
private static final String ENHANCE_CLASS_NAME = "send";
42-
public static final String SYNC_SEND_INTERCEPTOR = "org.apache.skywalking.apm.plugin.jetty.v90.client.SyncHttpRequestSendV90Interceptor";
43+
public static final String SYNC_SEND_INTERCEPTOR =
44+
"org.apache.skywalking.apm.plugin.jetty.v90.client.SyncHttpRequestSendV90Interceptor";
45+
46+
public static final String ASYNC_SEND_INTERCEPTOR =
47+
"org.apache.skywalking.apm.plugin.jetty.v90.client.AsyncHttpRequestSendInterceptor";
4348

4449
@Override
4550
public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
@@ -61,6 +66,23 @@ public String getMethodsInterceptor() {
6166
return SYNC_SEND_INTERCEPTOR;
6267
}
6368

69+
@Override
70+
public boolean isOverrideArgs() {
71+
return false;
72+
}
73+
},
74+
new InstanceMethodsInterceptPoint() {
75+
// async call interceptor point
76+
@Override
77+
public ElementMatcher<MethodDescription> getMethodsMatcher() {
78+
return named(ENHANCE_CLASS_NAME).and(takesArguments(1));
79+
}
80+
81+
@Override
82+
public String getMethodsInterceptor() {
83+
return ASYNC_SEND_INTERCEPTOR;
84+
}
85+
6486
@Override
6587
public boolean isOverrideArgs() {
6688
return false;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
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.jetty.v90.client.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+
import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch;
28+
29+
import java.util.List;
30+
31+
import static net.bytebuddy.matcher.ElementMatchers.named;
32+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
33+
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
34+
35+
/**
36+
* {@link ResponseNotifierInstrumentation} enhance the <code>send</code> method in
37+
* <code>org.eclipse.jetty.client.ResponseNotifier</code> by
38+
* <code>org.apache.skywalking.apm.plugin.jetty.v9.client.ResponseNotifierInterceptor</code>
39+
* <p>
40+
* It is implemented to be able to stop the asynchronous span in the attributes of the request.
41+
*/
42+
public class ResponseNotifierInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {
43+
44+
private static final String ENHANCE_CLASS = "org.eclipse.jetty.client.tar";
45+
private static final String ENHANCE_CLASS_NAME = "notifyComplete";
46+
public static final String SYNC_SEND_INTERCEPTOR =
47+
"org.apache.skywalking.apm.plugin.jetty.v9.client.ResponseNotifierInterceptor";
48+
49+
@Override
50+
public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
51+
return new ConstructorInterceptPoint[0];
52+
}
53+
54+
@Override
55+
public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
56+
return new InstanceMethodsInterceptPoint[]{
57+
new InstanceMethodsInterceptPoint() {
58+
@Override
59+
public ElementMatcher<MethodDescription> getMethodsMatcher() {
60+
return named(ENHANCE_CLASS_NAME).and(takesArguments(2)).and(takesArgument(0, List.class));
61+
}
62+
63+
@Override
64+
public String getMethodsInterceptor() {
65+
return SYNC_SEND_INTERCEPTOR;
66+
}
67+
68+
@Override
69+
public boolean isOverrideArgs() {
70+
return false;
71+
}
72+
}
73+
};
74+
}
75+
76+
@Override
77+
protected ClassMatch enhanceClass() {
78+
return NameMatch.byName(ENHANCE_CLASS);
79+
}
80+
81+
@Override
82+
protected String[] witnessClasses() {
83+
return new String[]{"org.eclipse.jetty.client.api.ProxyConfiguration"};
84+
}
85+
}

apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.0-plugin/src/main/resources/skywalking-plugin.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@
1515
# limitations under the License.
1616

1717
jetty-client-9.0=org.apache.skywalking.apm.plugin.jetty.v90.client.define.HttpRequestInstrumentation
18+
jetty-client-9.0=org.apache.skywalking.apm.plugin.jetty.v90.client.define.ResponseNotifierInstrumentation

0 commit comments

Comments
 (0)