Support asynchronous invocation in jetty client 9.0 and 9.x plugin (#590)
This commit is contained in:
parent
c6331df795
commit
e7394a87da
|
|
@ -150,6 +150,7 @@ Callable {
|
|||
* Fix witness class in springmvc-annotation-5.x-plugin to avoid falling into v3 use cases.
|
||||
* Fix Jedis-2.x plugin bug and add test for Redis cluster scene
|
||||
* Merge two instrumentation classes to avoid duplicate enhancements in MySQL plugins.
|
||||
* Support asynchronous invocation in jetty client 9.0 and 9.x plugin
|
||||
|
||||
#### Documentation
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* 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.jetty.v90.client;
|
||||
|
||||
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.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.apache.skywalking.apm.network.trace.component.ComponentsDefine;
|
||||
import org.eclipse.jetty.client.HttpRequest;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class AsyncHttpRequestSendInterceptor implements InstanceMethodsAroundInterceptor {
|
||||
@Override
|
||||
public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
|
||||
MethodInterceptResult result) throws Throwable {
|
||||
HttpRequest request = (HttpRequest) objInst;
|
||||
ContextCarrier contextCarrier = new ContextCarrier();
|
||||
AbstractSpan span = ContextManager.createExitSpan(request.getURI().getPath(), contextCarrier,
|
||||
request.getHost() + ":" + request.getPort());
|
||||
span.setComponent(ComponentsDefine.JETTY_CLIENT);
|
||||
|
||||
Tags.HTTP.METHOD.set(span, getHttpMethod(request));
|
||||
Tags.URL.set(span, request.getURI().toString());
|
||||
SpanLayer.asHttp(span);
|
||||
|
||||
CarrierItem next = contextCarrier.items();
|
||||
HttpFields field = request.getHeaders();
|
||||
while (next.hasNext()) {
|
||||
next = next.next();
|
||||
field.add(next.getHeadKey(), next.getHeadValue());
|
||||
}
|
||||
|
||||
span.prepareForAsync();
|
||||
request.attribute(Constants.SW_JETTY_EXIT_SPAN_KEY, span);
|
||||
Response.CompleteListener callback = (Response.CompleteListener) allArguments[0];
|
||||
allArguments[0] = new CompleteListenerWrapper(callback, ContextManager.capture());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
|
||||
Object ret) throws Throwable {
|
||||
ContextManager.stopSpan();
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
|
||||
Class<?>[] argumentsTypes, Throwable t) {
|
||||
ContextManager.activeSpan().log(t);
|
||||
}
|
||||
|
||||
private String getHttpMethod(HttpRequest request) {
|
||||
HttpMethod httpMethod = HttpMethod.GET;
|
||||
|
||||
/**
|
||||
* The method is null if the client using GET method.
|
||||
*
|
||||
* @see org.eclipse.jetty.client.HttpRequest#GET(String uri)
|
||||
* @see org.eclipse.jetty.client.HttpRequest( org.eclipse.jetty.client.HttpClient client, long conversation, java.net.URI uri)
|
||||
*/
|
||||
if (request.getMethod() != null) {
|
||||
httpMethod = request.getMethod();
|
||||
}
|
||||
|
||||
return httpMethod.name();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 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.jetty.v90.client;
|
||||
|
||||
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.trace.AbstractSpan;
|
||||
import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer;
|
||||
import org.apache.skywalking.apm.network.trace.component.ComponentsDefine;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
|
||||
public class CompleteListenerWrapper implements Response.CompleteListener {
|
||||
private Response.CompleteListener callback;
|
||||
private ContextSnapshot context;
|
||||
|
||||
public CompleteListenerWrapper(Response.CompleteListener callback, ContextSnapshot context) {
|
||||
this.callback = callback;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(Result result) {
|
||||
AbstractSpan span = ContextManager.createLocalSpan(Constants.PLUGIN_NAME + "/CompleteListener/onComplete");
|
||||
span.setComponent(ComponentsDefine.JETTY_CLIENT);
|
||||
SpanLayer.asHttp(span);
|
||||
if (context != null) {
|
||||
ContextManager.continued(context);
|
||||
}
|
||||
if (callback != null) {
|
||||
callback.onComplete(result);
|
||||
}
|
||||
ContextManager.stopSpan();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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.jetty.v90.client;
|
||||
|
||||
public class Constants {
|
||||
public final static String SW_JETTY_EXIT_SPAN_KEY = "SW_JETTY_EXIT_SPAN";
|
||||
|
||||
public final static String PLUGIN_NAME = "JettyClient9.0";
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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.jetty.v90.client;
|
||||
|
||||
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.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Optional;
|
||||
|
||||
public class ResponseNotifierInterceptor 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 {
|
||||
// get async span and stop it.
|
||||
Optional.ofNullable(getAsyncSpan(allArguments)).ifPresent(v -> v.asyncFinish());
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
|
||||
Class<?>[] argumentsTypes, Throwable t) {
|
||||
Optional.ofNullable(getAsyncSpan(allArguments)).ifPresent(v -> v.log(t));
|
||||
}
|
||||
|
||||
private AbstractSpan getAsyncSpan(Object[] allArguments) {
|
||||
Result results = (Result) allArguments[1];
|
||||
if (results == null) {
|
||||
return null;
|
||||
}
|
||||
Request request = results.getRequest();
|
||||
if (request == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (AbstractSpan) request.getAttributes().get(Constants.SW_JETTY_EXIT_SPAN_KEY);
|
||||
}
|
||||
}
|
||||
|
|
@ -31,15 +31,20 @@ import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
|
|||
|
||||
/**
|
||||
* {@link HttpRequestInstrumentation} enhance the <code>send</code> method without argument in
|
||||
* <code>org.eclipse.jetty.client.HttpRequest</code> by <code>org.apache.skywalking.apm.plugin.jetty.client.SyncHttpRequestSendInterceptor</code>
|
||||
* and enhance the <code>send</code> with <code>org.eclipse.jetty.client.api.Response$CompleteListener</code> parameter
|
||||
* by <code>org.apache.skywalking.apm.plugin.jetty.client.AsyncHttpRequestSendInterceptor</code>
|
||||
* <code>org.eclipse.jetty.client.HttpRequest</code> by
|
||||
* <code>org.apache.skywalking.apm.plugin.jetty.client.SyncHttpRequestSendInterceptor</code> and enhance the
|
||||
* <code>send</code> with <code>org.eclipse.jetty.client.api.Response$CompleteListener</code> parameter by
|
||||
* <code>org.apache.skywalking.apm.plugin.jetty.client.AsyncHttpRequestSendInterceptor</code>
|
||||
*/
|
||||
public class HttpRequestInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {
|
||||
|
||||
private static final String ENHANCE_CLASS = "org.eclipse.jetty.client.HttpRequest";
|
||||
private static final String ENHANCE_CLASS_NAME = "send";
|
||||
public static final String SYNC_SEND_INTERCEPTOR = "org.apache.skywalking.apm.plugin.jetty.v90.client.SyncHttpRequestSendV90Interceptor";
|
||||
public static final String SYNC_SEND_INTERCEPTOR =
|
||||
"org.apache.skywalking.apm.plugin.jetty.v90.client.SyncHttpRequestSendV90Interceptor";
|
||||
|
||||
public static final String ASYNC_SEND_INTERCEPTOR =
|
||||
"org.apache.skywalking.apm.plugin.jetty.v90.client.AsyncHttpRequestSendInterceptor";
|
||||
|
||||
@Override
|
||||
public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
|
||||
|
|
@ -61,6 +66,23 @@ public class HttpRequestInstrumentation extends ClassInstanceMethodsEnhancePlugi
|
|||
return SYNC_SEND_INTERCEPTOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOverrideArgs() {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
new InstanceMethodsInterceptPoint() {
|
||||
// async call interceptor point
|
||||
@Override
|
||||
public ElementMatcher<MethodDescription> getMethodsMatcher() {
|
||||
return named(ENHANCE_CLASS_NAME).and(takesArguments(1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethodsInterceptor() {
|
||||
return ASYNC_SEND_INTERCEPTOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOverrideArgs() {
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* 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.jetty.v90.client.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 org.apache.skywalking.apm.agent.core.plugin.match.NameMatch;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
|
||||
|
||||
/**
|
||||
* {@link ResponseNotifierInstrumentation} enhance the <code>send</code> method in
|
||||
* <code>org.eclipse.jetty.client.ResponseNotifier</code> by
|
||||
* <code>org.apache.skywalking.apm.plugin.jetty.v9.client.ResponseNotifierInterceptor</code>
|
||||
* <p>
|
||||
* It is implemented to be able to stop the asynchronous span in the attributes of the request.
|
||||
*/
|
||||
public class ResponseNotifierInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {
|
||||
|
||||
private static final String ENHANCE_CLASS = "org.eclipse.jetty.client.tar";
|
||||
private static final String ENHANCE_CLASS_NAME = "notifyComplete";
|
||||
public static final String SYNC_SEND_INTERCEPTOR =
|
||||
"org.apache.skywalking.apm.plugin.jetty.v9.client.ResponseNotifierInterceptor";
|
||||
|
||||
@Override
|
||||
public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
|
||||
return new ConstructorInterceptPoint[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
|
||||
return new InstanceMethodsInterceptPoint[]{
|
||||
new InstanceMethodsInterceptPoint() {
|
||||
@Override
|
||||
public ElementMatcher<MethodDescription> getMethodsMatcher() {
|
||||
return named(ENHANCE_CLASS_NAME).and(takesArguments(2)).and(takesArgument(0, List.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethodsInterceptor() {
|
||||
return SYNC_SEND_INTERCEPTOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOverrideArgs() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClassMatch enhanceClass() {
|
||||
return NameMatch.byName(ENHANCE_CLASS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] witnessClasses() {
|
||||
return new String[]{"org.eclipse.jetty.client.api.ProxyConfiguration"};
|
||||
}
|
||||
}
|
||||
|
|
@ -15,3 +15,4 @@
|
|||
# limitations under the License.
|
||||
|
||||
jetty-client-9.0=org.apache.skywalking.apm.plugin.jetty.v90.client.define.HttpRequestInstrumentation
|
||||
jetty-client-9.0=org.apache.skywalking.apm.plugin.jetty.v90.client.define.ResponseNotifierInstrumentation
|
||||
|
|
|
|||
|
|
@ -0,0 +1,184 @@
|
|||
/*
|
||||
* 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.jetty.v90.client;
|
||||
|
||||
import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan;
|
||||
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.core.context.util.TagValuePair;
|
||||
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
|
||||
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.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.HttpRequest;
|
||||
import org.eclipse.jetty.client.ResponseNotifier;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
import org.hamcrest.MatcherAssert;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.apache.skywalking.apm.agent.test.tools.SpanAssert.assertComponent;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
@RunWith(TracingSegmentRunner.class)
|
||||
public class AsyncHttpRequestSendInterceptorTest {
|
||||
@SegmentStoragePoint
|
||||
private SegmentStorage segmentStorage;
|
||||
|
||||
@Rule
|
||||
public AgentServiceRule serviceRule = new AgentServiceRule();
|
||||
@Rule
|
||||
public MockitoRule rule = MockitoJUnit.rule();
|
||||
|
||||
private MockHttpRequest httpRequestEnhancedInstance;
|
||||
private AsyncHttpRequestSendInterceptor asyncHttpRequestSendInterceptor;
|
||||
|
||||
private MockResponseNotifier responseNotifierEnhancedInstance;
|
||||
private ResponseNotifierInterceptor responseNotifierInterceptor;
|
||||
|
||||
@Mock
|
||||
private HttpClient httpClient;
|
||||
@Mock
|
||||
private Response response;
|
||||
private Object[] allArguments;
|
||||
private Class[] argumentTypes;
|
||||
private URI uri = URI.create("http://localhost:8080/test");
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
httpRequestEnhancedInstance = new MockHttpRequest(httpClient, uri);
|
||||
responseNotifierEnhancedInstance = new MockResponseNotifier(httpClient);
|
||||
|
||||
Result results = new Result(httpRequestEnhancedInstance, response);
|
||||
allArguments = new Object[]{(Response.CompleteListener) result -> { }, results};
|
||||
argumentTypes = new Class[]{List.class, Result.class};
|
||||
|
||||
asyncHttpRequestSendInterceptor = new AsyncHttpRequestSendInterceptor();
|
||||
responseNotifierInterceptor = new ResponseNotifierInterceptor();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHttpMethodsAround() throws Throwable {
|
||||
asyncHttpRequestSendInterceptor.beforeMethod(httpRequestEnhancedInstance, null, allArguments, argumentTypes, null);
|
||||
asyncHttpRequestSendInterceptor.afterMethod(httpRequestEnhancedInstance, null, allArguments, argumentTypes, null);
|
||||
responseNotifierInterceptor.beforeMethod(responseNotifierEnhancedInstance, null, allArguments, argumentTypes, null);
|
||||
responseNotifierInterceptor.afterMethod(responseNotifierEnhancedInstance, null, allArguments, argumentTypes, null);
|
||||
|
||||
Map<String, Object> attributes = httpRequestEnhancedInstance.getAttributes();
|
||||
AbstractSpan asyncSpan = (AbstractSpan) attributes.get(Constants.SW_JETTY_EXIT_SPAN_KEY);
|
||||
assertNotNull(asyncSpan);
|
||||
|
||||
assertJettySpan();
|
||||
|
||||
Assert.assertEquals(false, SpanHelper.getErrorOccurred(asyncSpan));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMethodsAroundError() throws Throwable {
|
||||
asyncHttpRequestSendInterceptor.beforeMethod(httpRequestEnhancedInstance, null, allArguments, argumentTypes, null);
|
||||
asyncHttpRequestSendInterceptor.handleMethodException(httpRequestEnhancedInstance, null, allArguments, argumentTypes, new RuntimeException());
|
||||
asyncHttpRequestSendInterceptor.afterMethod(httpRequestEnhancedInstance, null, allArguments, argumentTypes, null);
|
||||
responseNotifierInterceptor.beforeMethod(responseNotifierEnhancedInstance, null, allArguments, argumentTypes, null);
|
||||
responseNotifierInterceptor.handleMethodException(responseNotifierEnhancedInstance, null, allArguments, argumentTypes, new RuntimeException());
|
||||
responseNotifierInterceptor.afterMethod(responseNotifierEnhancedInstance, null, allArguments, argumentTypes, null);
|
||||
|
||||
Map<String, Object> attributes = httpRequestEnhancedInstance.getAttributes();
|
||||
AbstractSpan asyncSpan = (AbstractSpan) attributes.get(Constants.SW_JETTY_EXIT_SPAN_KEY);
|
||||
assertNotNull(asyncSpan);
|
||||
|
||||
assertJettySpan();
|
||||
|
||||
Assert.assertEquals(true, SpanHelper.getErrorOccurred(asyncSpan));
|
||||
SpanAssert.assertException(SpanHelper.getLogs(asyncSpan).get(0), RuntimeException.class);
|
||||
}
|
||||
|
||||
private void assertJettySpan() {
|
||||
assertThat(segmentStorage.getTraceSegments().size(), is(1));
|
||||
TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0);
|
||||
|
||||
Assert.assertEquals(1, SegmentHelper.getSpans(traceSegment).size());
|
||||
AbstractTracingSpan finishedSpan = SegmentHelper.getSpans(traceSegment).get(0);
|
||||
assertNotNull(finishedSpan);
|
||||
assertComponent(finishedSpan, ComponentsDefine.JETTY_CLIENT);
|
||||
MatcherAssert.assertThat(finishedSpan.isExit(), is(true));
|
||||
SpanAssert.assertLayer(finishedSpan, SpanLayer.HTTP);
|
||||
|
||||
List<TagValuePair> tags = SpanHelper.getTags(finishedSpan);
|
||||
assertThat(tags.size(), is(2));
|
||||
assertThat(tags.get(0).getValue(), is("GET"));
|
||||
assertThat(tags.get(1).getValue(), is(uri.toString()));
|
||||
}
|
||||
|
||||
private class MockHttpRequest extends HttpRequest implements EnhancedInstance {
|
||||
public MockHttpRequest(HttpClient httpClient, URI uri) {
|
||||
super(httpClient, uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getURI() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getSkyWalkingDynamicField() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSkyWalkingDynamicField(Object value) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private class MockResponseNotifier extends ResponseNotifier implements EnhancedInstance {
|
||||
public MockResponseNotifier(HttpClient client) {
|
||||
super(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getSkyWalkingDynamicField() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSkyWalkingDynamicField(Object value) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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.jetty.v9.client;
|
||||
|
||||
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.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.apache.skywalking.apm.network.trace.component.ComponentsDefine;
|
||||
import org.eclipse.jetty.client.HttpRequest;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class AsyncHttpRequestSendInterceptor implements InstanceMethodsAroundInterceptor {
|
||||
@Override
|
||||
public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
|
||||
MethodInterceptResult result) throws Throwable {
|
||||
HttpRequest request = (HttpRequest) objInst;
|
||||
ContextCarrier contextCarrier = new ContextCarrier();
|
||||
AbstractSpan span = ContextManager.createExitSpan(request.getURI().getPath(), contextCarrier,
|
||||
request.getHost() + ":" + request.getPort());
|
||||
span.setComponent(ComponentsDefine.JETTY_CLIENT);
|
||||
|
||||
Tags.HTTP.METHOD.set(span, getHttpMethod(request));
|
||||
Tags.URL.set(span, request.getURI().toString());
|
||||
SpanLayer.asHttp(span);
|
||||
|
||||
CarrierItem next = contextCarrier.items();
|
||||
HttpFields field = request.getHeaders();
|
||||
while (next.hasNext()) {
|
||||
next = next.next();
|
||||
field.add(next.getHeadKey(), next.getHeadValue());
|
||||
}
|
||||
|
||||
span.prepareForAsync();
|
||||
request.attribute(Constants.SW_JETTY_EXIT_SPAN_KEY, span);
|
||||
Response.CompleteListener callback = (Response.CompleteListener) allArguments[0];
|
||||
allArguments[0] = new CompleteListenerWrapper(callback, ContextManager.capture());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
|
||||
Object ret) throws Throwable {
|
||||
ContextManager.stopSpan();
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
|
||||
Class<?>[] argumentsTypes, Throwable t) {
|
||||
ContextManager.activeSpan().log(t);
|
||||
}
|
||||
|
||||
public String getHttpMethod(HttpRequest request) {
|
||||
String method = request.getMethod();
|
||||
|
||||
if (method == null || method.length() == 0) {
|
||||
method = "GET";
|
||||
}
|
||||
|
||||
return method;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 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.jetty.v9.client;
|
||||
|
||||
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.trace.AbstractSpan;
|
||||
import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer;
|
||||
import org.apache.skywalking.apm.network.trace.component.ComponentsDefine;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
|
||||
public class CompleteListenerWrapper implements Response.CompleteListener {
|
||||
private Response.CompleteListener callback;
|
||||
private ContextSnapshot context;
|
||||
|
||||
public CompleteListenerWrapper(Response.CompleteListener callback, ContextSnapshot context) {
|
||||
this.callback = callback;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(Result result) {
|
||||
AbstractSpan span = ContextManager.createLocalSpan(Constants.PLUGIN_NAME + "/CompleteListener/onComplete");
|
||||
span.setComponent(ComponentsDefine.JETTY_CLIENT);
|
||||
SpanLayer.asHttp(span);
|
||||
if (context != null) {
|
||||
ContextManager.continued(context);
|
||||
}
|
||||
if (callback != null) {
|
||||
callback.onComplete(result);
|
||||
}
|
||||
ContextManager.stopSpan();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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.jetty.v9.client;
|
||||
|
||||
public class Constants {
|
||||
public final static String SW_JETTY_EXIT_SPAN_KEY = "SW_JETTY_EXIT_SPAN";
|
||||
|
||||
public final static String PLUGIN_NAME = "JettyClient9.x";
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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.jetty.v9.client;
|
||||
|
||||
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.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Optional;
|
||||
|
||||
public class ResponseNotifierInterceptor 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 {
|
||||
// get async span and stop it.
|
||||
Optional.ofNullable(getAsyncSpan(allArguments)).ifPresent(v -> v.asyncFinish());
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
|
||||
Class<?>[] argumentsTypes, Throwable t) {
|
||||
Optional.ofNullable(getAsyncSpan(allArguments)).ifPresent(v -> v.log(t));
|
||||
}
|
||||
|
||||
private AbstractSpan getAsyncSpan(Object[] allArguments) {
|
||||
Result results = (Result) allArguments[1];
|
||||
if (results == null) {
|
||||
return null;
|
||||
}
|
||||
Request request = results.getRequest();
|
||||
if (request == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (AbstractSpan) request.getAttributes().get(Constants.SW_JETTY_EXIT_SPAN_KEY);
|
||||
}
|
||||
}
|
||||
|
|
@ -35,15 +35,20 @@ import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
|
|||
|
||||
/**
|
||||
* {@link HttpRequestInstrumentation} enhance the <code>send</code> method without argument in
|
||||
* <code>org.eclipse.jetty.client.HttpRequest</code> by <code>org.apache.skywalking.apm.plugin.jetty.client.SyncHttpRequestSendInterceptor</code>
|
||||
* and enhance the <code>send</code> with <code>org.eclipse.jetty.client.api.Response$CompleteListener</code> parameter
|
||||
* by <code>org.apache.skywalking.apm.plugin.jetty.client.AsyncHttpRequestSendInterceptor</code>
|
||||
* <code>org.eclipse.jetty.client.HttpRequest</code> by
|
||||
* <code>org.apache.skywalking.apm.plugin.jetty.client.SyncHttpRequestSendInterceptor</code> and enhance the
|
||||
* <code>send</code> with <code>org.eclipse.jetty.client.api.Response$CompleteListener</code> parameter by
|
||||
* <code>org.apache.skywalking.apm.plugin.jetty.client.AsyncHttpRequestSendInterceptor</code>
|
||||
*/
|
||||
public class HttpRequestInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {
|
||||
|
||||
private static final String ENHANCE_CLASS = "org.eclipse.jetty.client.HttpRequest";
|
||||
private static final String ENHANCE_CLASS_NAME = "send";
|
||||
public static final String SYNC_SEND_INTERCEPTOR = "org.apache.skywalking.apm.plugin.jetty.v9.client.SyncHttpRequestSendInterceptor";
|
||||
public static final String SYNC_SEND_INTERCEPTOR =
|
||||
"org.apache.skywalking.apm.plugin.jetty.v9.client.SyncHttpRequestSendInterceptor";
|
||||
|
||||
public static final String ASYNC_SEND_INTERCEPTOR =
|
||||
"org.apache.skywalking.apm.plugin.jetty.v9.client.AsyncHttpRequestSendInterceptor";
|
||||
|
||||
@Override
|
||||
public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
|
||||
|
|
@ -54,7 +59,7 @@ public class HttpRequestInstrumentation extends ClassInstanceMethodsEnhancePlugi
|
|||
public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
|
||||
return new InstanceMethodsInterceptPoint[] {
|
||||
new InstanceMethodsInterceptPoint() {
|
||||
//sync call interceptor point
|
||||
// sync call interceptor point
|
||||
@Override
|
||||
public ElementMatcher<MethodDescription> getMethodsMatcher() {
|
||||
return named(ENHANCE_CLASS_NAME).and(takesArguments(0));
|
||||
|
|
@ -69,6 +74,23 @@ public class HttpRequestInstrumentation extends ClassInstanceMethodsEnhancePlugi
|
|||
public boolean isOverrideArgs() {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
new InstanceMethodsInterceptPoint() {
|
||||
// async call interceptor point
|
||||
@Override
|
||||
public ElementMatcher<MethodDescription> getMethodsMatcher() {
|
||||
return named(ENHANCE_CLASS_NAME).and(takesArguments(1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethodsInterceptor() {
|
||||
return ASYNC_SEND_INTERCEPTOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOverrideArgs() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* 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.jetty.v9.client.define;
|
||||
|
||||
import net.bytebuddy.description.method.MethodDescription;
|
||||
import net.bytebuddy.matcher.ElementMatcher;
|
||||
import org.apache.skywalking.apm.agent.core.plugin.WitnessMethod;
|
||||
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 org.apache.skywalking.apm.agent.core.plugin.match.NameMatch;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
|
||||
|
||||
/**
|
||||
* {@link ResponseNotifierInstrumentation} enhance the <code>send</code> method in
|
||||
* <code>org.eclipse.jetty.client.ResponseNotifier</code> by
|
||||
* <code>org.apache.skywalking.apm.plugin.jetty.v9.client.ResponseNotifierInterceptor</code>
|
||||
* <p>
|
||||
* It is implemented to be able to stop the asynchronous span in the attributes of the request.
|
||||
*/
|
||||
public class ResponseNotifierInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {
|
||||
|
||||
private static final String ENHANCE_CLASS = "org.eclipse.jetty.client.ResponseNotifier";
|
||||
private static final String ENHANCE_CLASS_NAME = "notifyComplete";
|
||||
public static final String SYNC_SEND_INTERCEPTOR =
|
||||
"org.apache.skywalking.apm.plugin.jetty.v9.client.ResponseNotifierInterceptor";
|
||||
|
||||
@Override
|
||||
public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
|
||||
return new ConstructorInterceptPoint[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
|
||||
return new InstanceMethodsInterceptPoint[]{
|
||||
new InstanceMethodsInterceptPoint() {
|
||||
@Override
|
||||
public ElementMatcher<MethodDescription> getMethodsMatcher() {
|
||||
return named(ENHANCE_CLASS_NAME).and(takesArguments(2)).and(takesArgument(0, List.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethodsInterceptor() {
|
||||
return SYNC_SEND_INTERCEPTOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOverrideArgs() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClassMatch enhanceClass() {
|
||||
return NameMatch.byName(ENHANCE_CLASS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] witnessClasses() {
|
||||
return new String[]{"org.eclipse.jetty.client.AbstractHttpClientTransport"};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<WitnessMethod> witnessMethods() {
|
||||
return Collections.singletonList(new WitnessMethod(
|
||||
"org.eclipse.jetty.http.HttpFields",
|
||||
named("getQuality")
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
@ -15,3 +15,4 @@
|
|||
# limitations under the License.
|
||||
|
||||
jetty-client-9.x=org.apache.skywalking.apm.plugin.jetty.v9.client.define.HttpRequestInstrumentation
|
||||
jetty-client-9.x=org.apache.skywalking.apm.plugin.jetty.v9.client.define.ResponseNotifierInstrumentation
|
||||
|
|
@ -0,0 +1,189 @@
|
|||
/*
|
||||
* 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.jetty.v9.client;
|
||||
|
||||
import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan;
|
||||
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.core.context.util.TagValuePair;
|
||||
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
|
||||
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.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.HttpRequest;
|
||||
import org.eclipse.jetty.client.ResponseNotifier;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
import org.hamcrest.MatcherAssert;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.apache.skywalking.apm.agent.test.tools.SpanAssert.assertComponent;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
@RunWith(TracingSegmentRunner.class)
|
||||
public class AsyncHttpRequestSendInterceptorTest {
|
||||
@SegmentStoragePoint
|
||||
private SegmentStorage segmentStorage;
|
||||
|
||||
@Rule
|
||||
public AgentServiceRule serviceRule = new AgentServiceRule();
|
||||
@Rule
|
||||
public MockitoRule rule = MockitoJUnit.rule();
|
||||
|
||||
private MockHttpRequest httpRequestEnhancedInstance;
|
||||
private AsyncHttpRequestSendInterceptor asyncHttpRequestSendInterceptor;
|
||||
|
||||
private MockResponseNotifier responseNotifierEnhancedInstance;
|
||||
private ResponseNotifierInterceptor responseNotifierInterceptor;
|
||||
|
||||
@Mock
|
||||
private HttpClient httpClient;
|
||||
@Mock
|
||||
private Response response;
|
||||
private Object[] allArguments;
|
||||
private Class[] argumentTypes;
|
||||
private URI uri = URI.create("http://localhost:8080/test");
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
httpRequestEnhancedInstance = new MockHttpRequest(httpClient, uri);
|
||||
responseNotifierEnhancedInstance = new MockResponseNotifier(httpClient);
|
||||
|
||||
Result results = new Result(httpRequestEnhancedInstance, response);
|
||||
allArguments = new Object[]{(Response.CompleteListener) result -> { }, results};
|
||||
argumentTypes = new Class[]{List.class, Result.class};
|
||||
|
||||
asyncHttpRequestSendInterceptor = new AsyncHttpRequestSendInterceptor();
|
||||
responseNotifierInterceptor = new ResponseNotifierInterceptor();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHttpMethodsAround() throws Throwable {
|
||||
asyncHttpRequestSendInterceptor.beforeMethod(httpRequestEnhancedInstance, null, allArguments, argumentTypes, null);
|
||||
asyncHttpRequestSendInterceptor.afterMethod(httpRequestEnhancedInstance, null, allArguments, argumentTypes, null);
|
||||
responseNotifierInterceptor.beforeMethod(responseNotifierEnhancedInstance, null, allArguments, argumentTypes, null);
|
||||
responseNotifierInterceptor.afterMethod(responseNotifierEnhancedInstance, null, allArguments, argumentTypes, null);
|
||||
|
||||
Map<String, Object> attributes = httpRequestEnhancedInstance.getAttributes();
|
||||
AbstractSpan asyncSpan = (AbstractSpan) attributes.get(Constants.SW_JETTY_EXIT_SPAN_KEY);
|
||||
assertNotNull(asyncSpan);
|
||||
|
||||
assertJettySpan();
|
||||
|
||||
Assert.assertEquals(false, SpanHelper.getErrorOccurred(asyncSpan));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMethodsAroundError() throws Throwable {
|
||||
asyncHttpRequestSendInterceptor.beforeMethod(httpRequestEnhancedInstance, null, allArguments, argumentTypes, null);
|
||||
asyncHttpRequestSendInterceptor.handleMethodException(httpRequestEnhancedInstance, null, allArguments, argumentTypes, new RuntimeException());
|
||||
asyncHttpRequestSendInterceptor.afterMethod(httpRequestEnhancedInstance, null, allArguments, argumentTypes, null);
|
||||
responseNotifierInterceptor.beforeMethod(responseNotifierEnhancedInstance, null, allArguments, argumentTypes, null);
|
||||
responseNotifierInterceptor.handleMethodException(responseNotifierEnhancedInstance, null, allArguments, argumentTypes, new RuntimeException());
|
||||
responseNotifierInterceptor.afterMethod(responseNotifierEnhancedInstance, null, allArguments, argumentTypes, null);
|
||||
|
||||
Map<String, Object> attributes = httpRequestEnhancedInstance.getAttributes();
|
||||
AbstractSpan asyncSpan = (AbstractSpan) attributes.get(Constants.SW_JETTY_EXIT_SPAN_KEY);
|
||||
assertNotNull(asyncSpan);
|
||||
|
||||
assertJettySpan();
|
||||
|
||||
Assert.assertEquals(true, SpanHelper.getErrorOccurred(asyncSpan));
|
||||
SpanAssert.assertException(SpanHelper.getLogs(asyncSpan).get(0), RuntimeException.class);
|
||||
}
|
||||
|
||||
private void assertJettySpan() {
|
||||
assertThat(segmentStorage.getTraceSegments().size(), is(1));
|
||||
TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0);
|
||||
|
||||
Assert.assertEquals(1, SegmentHelper.getSpans(traceSegment).size());
|
||||
AbstractTracingSpan finishedSpan = SegmentHelper.getSpans(traceSegment).get(0);
|
||||
assertNotNull(finishedSpan);
|
||||
assertComponent(finishedSpan, ComponentsDefine.JETTY_CLIENT);
|
||||
MatcherAssert.assertThat(finishedSpan.isExit(), is(true));
|
||||
SpanAssert.assertLayer(finishedSpan, SpanLayer.HTTP);
|
||||
|
||||
List<TagValuePair> tags = SpanHelper.getTags(finishedSpan);
|
||||
assertThat(tags.size(), is(2));
|
||||
assertThat(tags.get(0).getValue(), is("GET"));
|
||||
assertThat(tags.get(1).getValue(), is(uri.toString()));
|
||||
}
|
||||
|
||||
private class MockHttpRequest extends HttpRequest implements EnhancedInstance {
|
||||
public MockHttpRequest(HttpClient httpClient, URI uri) {
|
||||
super(httpClient, uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethod() {
|
||||
return "GET";
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getURI() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getSkyWalkingDynamicField() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSkyWalkingDynamicField(Object value) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private class MockResponseNotifier extends ResponseNotifier implements EnhancedInstance {
|
||||
public MockResponseNotifier(HttpClient client) {
|
||||
super(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getSkyWalkingDynamicField() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSkyWalkingDynamicField(Object value) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
# limitations under the License.
|
||||
segmentItems:
|
||||
- serviceName: jettyserver-scenario
|
||||
segmentSize: 1
|
||||
segmentSize: ge 3
|
||||
segments:
|
||||
- segmentId: not null
|
||||
spans:
|
||||
|
|
@ -38,9 +38,100 @@ segmentItems:
|
|||
refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not
|
||||
null, parentService: jettyclient-scenario, traceId: not null}
|
||||
skipAnalysis: 'false'
|
||||
- segmentId: not null
|
||||
spans:
|
||||
- operationName: /jettyserver-case/case/receiveContext-1
|
||||
parentSpanId: -1
|
||||
spanId: 0
|
||||
spanLayer: Http
|
||||
startTime: nq 0
|
||||
endTime: nq 0
|
||||
componentId: 19
|
||||
isError: false
|
||||
spanType: Entry
|
||||
peer: ''
|
||||
tags:
|
||||
- {key: url, value: 'http://localhost:18080/jettyserver-case/case/receiveContext-1'}
|
||||
- {key: http.method, value: GET}
|
||||
- {key: http.status_code, value: '200'}
|
||||
refs:
|
||||
- {parentEndpoint: GET:/jettyclient-case/case/jettyclient-case, networkAddress: 'localhost:18080',
|
||||
refType: CrossProcess, parentSpanId: 2, parentTraceSegmentId: not null, parentServiceInstance: not
|
||||
null, parentService: jettyclient-scenario, traceId: not null}
|
||||
skipAnalysis: 'false'
|
||||
- segmentId: not null
|
||||
spans:
|
||||
- operationName: /jettyserver-case/case/receiveContext-0
|
||||
parentSpanId: -1
|
||||
spanId: 0
|
||||
spanLayer: Http
|
||||
startTime: nq 0
|
||||
endTime: nq 0
|
||||
componentId: 19
|
||||
isError: false
|
||||
spanType: Entry
|
||||
peer: ''
|
||||
skipAnalysis: false
|
||||
tags:
|
||||
- { key: url, value: 'http://localhost:18080/jettyserver-case/case/receiveContext-0' }
|
||||
- { key: http.method, value: GET }
|
||||
- { key: http.status_code, value: '200' }
|
||||
refs:
|
||||
- { parentEndpoint: JettyClient9.x/CompleteListener/onComplete, networkAddress: 'localhost:18080',
|
||||
refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not null,
|
||||
parentService: jettyclient-scenario, traceId: not null }
|
||||
- serviceName: jettyclient-scenario
|
||||
segmentSize: 2
|
||||
segmentSize: ge 3
|
||||
segments:
|
||||
- segmentId: not null
|
||||
spans:
|
||||
- operationName: HEAD:/jettyclient-case/case/healthCheck
|
||||
parentSpanId: -1
|
||||
spanId: 0
|
||||
spanLayer: Http
|
||||
startTime: nq 0
|
||||
endTime: nq 0
|
||||
componentId: 1
|
||||
isError: false
|
||||
spanType: Entry
|
||||
peer: ''
|
||||
skipAnalysis: false
|
||||
tags:
|
||||
- {key: url, value: 'http://localhost:8080/jettyclient-case/case/healthCheck'}
|
||||
- {key: http.method, value: HEAD}
|
||||
- {key: http.status_code, value: '200'}
|
||||
- segmentId: not null
|
||||
spans:
|
||||
- operationName: /jettyserver-case/case/receiveContext-0
|
||||
parentSpanId: 0
|
||||
spanId: 1
|
||||
spanLayer: Http
|
||||
startTime: nq 0
|
||||
endTime: nq 0
|
||||
componentId: 2
|
||||
isError: false
|
||||
spanType: Exit
|
||||
peer: localhost:18080
|
||||
skipAnalysis: false
|
||||
tags:
|
||||
- {key: url, value: 'http://localhost:18080/jettyserver-case/case/receiveContext-0'}
|
||||
- {key: http.method, value: GET}
|
||||
- {key: http.status_code, value: '200'}
|
||||
- operationName: JettyClient9.x/CompleteListener/onComplete
|
||||
parentSpanId: -1
|
||||
spanId: 0
|
||||
spanLayer: Http
|
||||
startTime: nq 0
|
||||
endTime: nq 0
|
||||
componentId: 18
|
||||
isError: false
|
||||
spanType: Local
|
||||
peer: ''
|
||||
skipAnalysis: false
|
||||
refs:
|
||||
- {parentEndpoint: 'GET:/jettyclient-case/case/jettyclient-case', networkAddress: '',
|
||||
refType: CrossThread, parentSpanId: 2, parentTraceSegmentId: not null, parentServiceInstance: not null,
|
||||
parentService: jettyclient-scenario, traceId: not null}
|
||||
- segmentId: not null
|
||||
spans:
|
||||
- operationName: /jettyserver-case/case/receiveContext-0
|
||||
|
|
@ -53,10 +144,24 @@ segmentItems:
|
|||
isError: false
|
||||
spanType: Exit
|
||||
peer: localhost:18080
|
||||
skipAnalysis: false
|
||||
tags:
|
||||
- {key: http.method, value: GET}
|
||||
- {key: url, value: 'http://localhost:18080/jettyserver-case/case/receiveContext-0'}
|
||||
skipAnalysis: 'false'
|
||||
- operationName: /jettyserver-case/case/receiveContext-1
|
||||
parentSpanId: 0
|
||||
spanId: 2
|
||||
spanLayer: Http
|
||||
startTime: nq 0
|
||||
endTime: nq 0
|
||||
componentId: 18
|
||||
isError: false
|
||||
spanType: Exit
|
||||
peer: localhost:18080
|
||||
skipAnalysis: false
|
||||
tags:
|
||||
- {key: http.method, value: GET}
|
||||
- {key: url, value: 'http://localhost:18080/jettyserver-case/case/receiveContext-1'}
|
||||
- operationName: GET:/jettyclient-case/case/jettyclient-case
|
||||
parentSpanId: -1
|
||||
spanId: 0
|
||||
|
|
@ -66,8 +171,9 @@ segmentItems:
|
|||
componentId: 1
|
||||
isError: false
|
||||
spanType: Entry
|
||||
peer: ''
|
||||
skipAnalysis: false
|
||||
tags:
|
||||
- {key: url, value: 'http://localhost:8080/jettyclient-case/case/jettyclient-case'}
|
||||
- {key: http.method, value: GET}
|
||||
- {key: http.status_code, value: '200'}
|
||||
skipAnalysis: 'false'
|
||||
- {key: http.status_code, value: '200'}
|
||||
|
|
@ -38,6 +38,11 @@
|
|||
<artifactId>jetty-client</artifactId>
|
||||
<version>${jettyclient.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>${httpclient.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
|
|
|||
|
|
@ -16,10 +16,16 @@
|
|||
*
|
||||
*/
|
||||
|
||||
package org.apache.skywalking.apm.testcase.jettyclient.controller;
|
||||
package org.apache.skywalking.apm.testcase.jettyclient.contr;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
import org.springframework.stereotype.Controller;
|
||||
|
|
@ -45,6 +51,16 @@ public class CaseController {
|
|||
@ResponseBody
|
||||
public String jettyClientScenario() throws Exception {
|
||||
client.newRequest("http://" + jettyServerHost + ":18080/jettyserver-case/case/receiveContext-0").send();
|
||||
Response.CompleteListener listener = result -> {
|
||||
CloseableHttpClient httpclient = HttpClients.createDefault();
|
||||
HttpGet httpget = new HttpGet("http://" + jettyServerHost + ":18080/jettyserver-case/case/receiveContext-0");
|
||||
try {
|
||||
httpclient.execute(httpget);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
};
|
||||
client.newRequest("http://" + jettyServerHost + ":18080/jettyserver-case/case/receiveContext-1").send(listener);
|
||||
return "Success";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
package org.apache.skywalking.apm.testcase.jettyserver;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import org.apache.skywalking.apm.testcase.jettyserver.servlet.AsyncCaseServlet;
|
||||
import org.apache.skywalking.apm.testcase.jettyserver.servlet.CaseServlet;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
|
|
@ -31,6 +32,7 @@ public class Application {
|
|||
ServletContextHandler servletContextHandler = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
|
||||
servletContextHandler.setContextPath(contextPath);
|
||||
servletContextHandler.addServlet(CaseServlet.class, CaseServlet.SERVLET_PATH);
|
||||
servletContextHandler.addServlet(AsyncCaseServlet.class, AsyncCaseServlet.SERVLET_PATH);
|
||||
jettyServer.setHandler(servletContextHandler);
|
||||
jettyServer.start();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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.testcase.jettyserver.servlet;
|
||||
|
||||
import java.io.IOException;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
public class AsyncCaseServlet extends HttpServlet {
|
||||
public static String SERVLET_PATH = "/case/receiveContext-1";
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
try {
|
||||
Thread.sleep(2000);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -43,6 +43,8 @@
|
|||
<jettyserver.version>${test.framework.version}</jettyserver.version>
|
||||
<jettyclient.version>${test.framework.version}</jettyclient.version>
|
||||
|
||||
<httpclient.version>4.3.5</httpclient.version>
|
||||
|
||||
<log4j.version>2.6.2</log4j.version>
|
||||
</properties>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue