From c32caab6e0f1a21948fe9be9788af4d4b5b90e73 Mon Sep 17 00:00:00 2001 From: Kcat <1660645816@qq.com> Date: Fri, 6 Nov 2020 00:33:47 +0800 Subject: [PATCH] Add async-http-client plugin (#5690) --- .github/workflows/plugins-test.0.yaml | 1 + CHANGES.md | 1 + .../trace/component/ComponentsDefine.java | 3 + .../asynchttpclient-2.x-plugin/pom.xml | 45 +++++ .../v2/AsyncHandlerWrapper.java | 160 ++++++++++++++++++ .../v2/ExecuteInterceptor.java | 85 ++++++++++ ...DefaultAsyncHttpClientInstrumentation.java | 70 ++++++++ .../src/main/resources/skywalking-plugin.def | 17 ++ .../v2/ExecuteInterceptorTest.java | 147 ++++++++++++++++ apm-sniffer/apm-sdk-plugin/pom.xml | 1 + .../service-agent/java-agent/Plugin-list.md | 3 +- .../java-agent/Supported-list.md | 1 + .../main/resources/component-libraries.yml | 4 +- .../asynchttpclient-scenario/bin/startup.sh | 21 +++ .../config/expectedData.yaml | 72 ++++++++ .../configuration.yml | 20 +++ .../asynchttpclient-scenario/pom.xml | 135 +++++++++++++++ .../src/main/assembly/assembly.xml | 41 +++++ .../testcase/asynchttpclient/Application.java | 34 ++++ .../asynchttpclient/CaseController.java | 73 ++++++++ .../src/main/resources/application.yaml | 21 +++ .../src/main/resources/log4j2.xml | 30 ++++ .../support-version.list | 29 ++++ 23 files changed, 1012 insertions(+), 2 deletions(-) create mode 100644 apm-sniffer/apm-sdk-plugin/asynchttpclient-2.x-plugin/pom.xml create mode 100644 apm-sniffer/apm-sdk-plugin/asynchttpclient-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/asynchttpclient/v2/AsyncHandlerWrapper.java create mode 100644 apm-sniffer/apm-sdk-plugin/asynchttpclient-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/asynchttpclient/v2/ExecuteInterceptor.java create mode 100644 apm-sniffer/apm-sdk-plugin/asynchttpclient-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/asynchttpclient/v2/define/DefaultAsyncHttpClientInstrumentation.java create mode 100644 apm-sniffer/apm-sdk-plugin/asynchttpclient-2.x-plugin/src/main/resources/skywalking-plugin.def create mode 100644 apm-sniffer/apm-sdk-plugin/asynchttpclient-2.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/asynchttpclient/v2/ExecuteInterceptorTest.java create mode 100644 test/plugin/scenarios/asynchttpclient-scenario/bin/startup.sh create mode 100644 test/plugin/scenarios/asynchttpclient-scenario/config/expectedData.yaml create mode 100644 test/plugin/scenarios/asynchttpclient-scenario/configuration.yml create mode 100644 test/plugin/scenarios/asynchttpclient-scenario/pom.xml create mode 100644 test/plugin/scenarios/asynchttpclient-scenario/src/main/assembly/assembly.xml create mode 100644 test/plugin/scenarios/asynchttpclient-scenario/src/main/java/org/apache/skywalking/apm/testcase/asynchttpclient/Application.java create mode 100644 test/plugin/scenarios/asynchttpclient-scenario/src/main/java/org/apache/skywalking/apm/testcase/asynchttpclient/CaseController.java create mode 100644 test/plugin/scenarios/asynchttpclient-scenario/src/main/resources/application.yaml create mode 100644 test/plugin/scenarios/asynchttpclient-scenario/src/main/resources/log4j2.xml create mode 100644 test/plugin/scenarios/asynchttpclient-scenario/support-version.list diff --git a/.github/workflows/plugins-test.0.yaml b/.github/workflows/plugins-test.0.yaml index ee76b483d..083561b24 100644 --- a/.github/workflows/plugins-test.0.yaml +++ b/.github/workflows/plugins-test.0.yaml @@ -48,6 +48,7 @@ jobs: - armeria-0.96minus-scenario - armeria-0.96plus-scenario - avro-scenario + - asynchttpclient-scenario - canal-scenario - cassandra-java-driver-3.x-scenario - customize-scenario diff --git a/CHANGES.md b/CHANGES.md index 1ccf845e8..5844d13ec 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -14,6 +14,7 @@ Release Notes. * Make the Feign plugin to support Java 14 * Make the okhttp3 plugin to support Java 14 * Polish tracing context related codes. +* Add the plugin for async-http-client 2.x #### OAP-Backend * Add the `@SuperDataset` annotation for BrowserErrorLog. diff --git a/apm-protocol/apm-network/src/main/java/org/apache/skywalking/apm/network/trace/component/ComponentsDefine.java b/apm-protocol/apm-network/src/main/java/org/apache/skywalking/apm/network/trace/component/ComponentsDefine.java index 91b3b449d..9c08058d0 100755 --- a/apm-protocol/apm-network/src/main/java/org/apache/skywalking/apm/network/trace/component/ComponentsDefine.java +++ b/apm-protocol/apm-network/src/main/java/org/apache/skywalking/apm/network/trace/component/ComponentsDefine.java @@ -185,4 +185,7 @@ public class ComponentsDefine { public static final OfficialComponent THRIFT_CLIENT = new OfficialComponent(101, "thrift-client"); + public static final OfficialComponent ASYNC_HTTP_CLIENT = new OfficialComponent(102, "AsyncHttpClient"); + + } \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/asynchttpclient-2.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/asynchttpclient-2.x-plugin/pom.xml new file mode 100644 index 000000000..8618bd701 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/asynchttpclient-2.x-plugin/pom.xml @@ -0,0 +1,45 @@ + + + + + + org.apache.skywalking + apm-sdk-plugin + 8.3.0-SNAPSHOT + + 4.0.0 + + apm-asynchttpclient-2.x-plugin + jar + + + 2.10.0 + + + + + + org.asynchttpclient + async-http-client + ${asynchttpclient.version} + provided + + + diff --git a/apm-sniffer/apm-sdk-plugin/asynchttpclient-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/asynchttpclient/v2/AsyncHandlerWrapper.java b/apm-sniffer/apm-sdk-plugin/asynchttpclient-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/asynchttpclient/v2/AsyncHandlerWrapper.java new file mode 100644 index 000000000..0562d34fd --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/asynchttpclient-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/asynchttpclient/v2/AsyncHandlerWrapper.java @@ -0,0 +1,160 @@ +/* + * 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.asynchttpclient.v2; + +import io.netty.channel.Channel; +import io.netty.handler.codec.http.HttpHeaders; +import java.net.InetSocketAddress; +import java.util.List; +import javax.net.ssl.SSLSession; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.logging.api.ILog; +import org.apache.skywalking.apm.agent.core.logging.api.LogManager; +import org.asynchttpclient.AsyncCompletionHandlerBase; +import org.asynchttpclient.AsyncHandler; +import org.asynchttpclient.HttpResponseBodyPart; +import org.asynchttpclient.HttpResponseStatus; +import org.asynchttpclient.netty.request.NettyRequest; + +/** + * {@link AsyncHandlerWrapper} wrapper the {@link AsyncHandler} object for tracing. + * if userAsyncHandler is null, we will set {@link AsyncCompletionHandlerBase} to avoid NPE. + */ +public class AsyncHandlerWrapper implements AsyncHandler { + + private final AsyncHandler userAsyncHandler; + private final AbstractSpan asyncSpan; + + private static ILog LOGGER = LogManager.getLogger(AsyncHandlerWrapper.class); + + public AsyncHandlerWrapper(AsyncHandler asyncHandler, AbstractSpan span) { + this.userAsyncHandler = asyncHandler == null ? new AsyncCompletionHandlerBase() : asyncHandler; + this.asyncSpan = span; + } + + @Override + public State onStatusReceived(final HttpResponseStatus httpResponseStatus) throws Exception { + return userAsyncHandler.onStatusReceived(httpResponseStatus); + } + + @Override + public State onHeadersReceived(final HttpHeaders httpHeaders) throws Exception { + return userAsyncHandler.onHeadersReceived(httpHeaders); + } + + @Override + public State onBodyPartReceived(final HttpResponseBodyPart httpResponseBodyPart) throws Exception { + return userAsyncHandler.onBodyPartReceived(httpResponseBodyPart); + } + + @Override + public State onTrailingHeadersReceived(final HttpHeaders headers) throws Exception { + return userAsyncHandler.onTrailingHeadersReceived(headers); + } + + @Override + public void onThrowable(final Throwable throwable) { + try { + asyncSpan.log(throwable); + asyncSpan.asyncFinish(); + } catch (Exception e) { + LOGGER.error("Failed to notify the async span stop.", e); + } + userAsyncHandler.onThrowable(throwable); + } + + @Override + public Object onCompleted() throws Exception { + try { + asyncSpan.asyncFinish(); + } catch (Exception e) { + LOGGER.error("Failed to notify the async span stop.", e); + } + return userAsyncHandler.onCompleted(); + } + + @Override + public void onHostnameResolutionAttempt(final String name) { + userAsyncHandler.onHostnameResolutionAttempt(name); + } + + @Override + public void onHostnameResolutionFailure(final String name, final Throwable cause) { + userAsyncHandler.onHostnameResolutionFailure(name, cause); + } + + @Override + public void onTcpConnectAttempt(final InetSocketAddress remoteAddress) { + userAsyncHandler.onTcpConnectAttempt(remoteAddress); + } + + @Override + public void onTcpConnectSuccess(final InetSocketAddress remoteAddress, final Channel connection) { + userAsyncHandler.onTcpConnectSuccess(remoteAddress, connection); + } + + @Override + public void onTcpConnectFailure(final InetSocketAddress remoteAddress, final Throwable cause) { + userAsyncHandler.onTcpConnectFailure(remoteAddress, cause); + } + + @Override + public void onTlsHandshakeAttempt() { + userAsyncHandler.onTlsHandshakeAttempt(); + } + + @Override + public void onTlsHandshakeSuccess(final SSLSession sslSession) { + userAsyncHandler.onTlsHandshakeSuccess(sslSession); + } + + @Override + public void onTlsHandshakeFailure(final Throwable cause) { + userAsyncHandler.onTlsHandshakeFailure(cause); + } + + @Override + public void onConnectionPoolAttempt() { + userAsyncHandler.onConnectionPoolAttempt(); + } + + @Override + public void onConnectionPooled(final Channel connection) { + userAsyncHandler.onConnectionPooled(connection); + } + + @Override + public void onConnectionOffer(final Channel connection) { + userAsyncHandler.onConnectionOffer(connection); + } + + @Override + public void onRequestSend(final NettyRequest request) { + userAsyncHandler.onRequestSend(request); + } + + @Override + public void onRetry() { + userAsyncHandler.onRetry(); + } + + @Override + public void onHostnameResolutionSuccess(final String name, final List list) { + userAsyncHandler.onHostnameResolutionSuccess(name, list); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/asynchttpclient-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/asynchttpclient/v2/ExecuteInterceptor.java b/apm-sniffer/apm-sdk-plugin/asynchttpclient-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/asynchttpclient/v2/ExecuteInterceptor.java new file mode 100644 index 000000000..ab1b36632 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/asynchttpclient-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/asynchttpclient/v2/ExecuteInterceptor.java @@ -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.asynchttpclient.v2; + +import io.netty.handler.codec.http.HttpHeaders; +import java.lang.reflect.Method; +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.asynchttpclient.AsyncHandler; +import org.asynchttpclient.Request; +import org.asynchttpclient.uri.Uri; + +/** + * interceptor for {@link org.asynchttpclient.DefaultAsyncHttpClient} + */ +public class ExecuteInterceptor implements InstanceMethodsAroundInterceptor { + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, MethodInterceptResult result) throws Throwable { + + final Request httpRequest = (Request) allArguments[0]; + final Uri requestUri = httpRequest.getUri(); + + AbstractSpan span = ContextManager.createExitSpan( + "AsyncHttpClient" + requestUri.getPath(), requestUri.getHost() + ":" + requestUri.getPort()); + + //We wrapped the allArguments[1] for get the real time duration, and stop the span. + allArguments[1] = new AsyncHandlerWrapper((AsyncHandler) allArguments[1], span); + + ContextCarrier contextCarrier = new ContextCarrier(); + ContextManager.inject(contextCarrier); + span.setComponent(ComponentsDefine.ASYNC_HTTP_CLIENT); + Tags.HTTP.METHOD.set(span, httpRequest.getMethod()); + Tags.URL.set(span, httpRequest.getUrl()); + SpanLayer.asHttp(span); + + //Wait the span async stop. + span.prepareForAsync(); + + final HttpHeaders headers = httpRequest.getHeaders(); + CarrierItem next = contextCarrier.items(); + while (next.hasNext()) { + next = next.next(); + headers.add(next.getHeadKey(), next.getHeadValue()); + } + } + + @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); + ContextManager.activeSpan().asyncFinish(); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/asynchttpclient-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/asynchttpclient/v2/define/DefaultAsyncHttpClientInstrumentation.java b/apm-sniffer/apm-sdk-plugin/asynchttpclient-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/asynchttpclient/v2/define/DefaultAsyncHttpClientInstrumentation.java new file mode 100644 index 000000000..c9487090f --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/asynchttpclient-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/asynchttpclient/v2/define/DefaultAsyncHttpClientInstrumentation.java @@ -0,0 +1,70 @@ +/* + * 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.asynchttpclient.v2.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static org.apache.skywalking.apm.agent.core.plugin.bytebuddy.ArgumentTypeNameMatch.takesArgumentWithType; +import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName; + +public class DefaultAsyncHttpClientInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "org.asynchttpclient.DefaultAsyncHttpClient"; + private static final String ENHANCE_METHOD = "execute"; + + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.asynchttpclient.v2.ExecuteInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[] { + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named(ENHANCE_METHOD).and(takesArgumentWithType(0, "org.asynchttpclient.Request")) + .and(takesArgumentWithType(1, "org.asynchttpclient.AsyncHandler")); + } + + @Override + public String getMethodsInterceptor() { + return INTERCEPTOR_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return true; + } + } + }; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/asynchttpclient-2.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/apm-sdk-plugin/asynchttpclient-2.x-plugin/src/main/resources/skywalking-plugin.def new file mode 100644 index 000000000..305bb1542 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/asynchttpclient-2.x-plugin/src/main/resources/skywalking-plugin.def @@ -0,0 +1,17 @@ +# 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. + +async-http-client-2.x=org.apache.skywalking.apm.plugin.asynchttpclient.v2.define.DefaultAsyncHttpClientInstrumentation \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/asynchttpclient-2.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/asynchttpclient/v2/ExecuteInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/asynchttpclient-2.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/asynchttpclient/v2/ExecuteInterceptorTest.java new file mode 100644 index 000000000..9e7f8edae --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/asynchttpclient-2.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/asynchttpclient/v2/ExecuteInterceptorTest.java @@ -0,0 +1,147 @@ +/* + * 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.asynchttpclient.v2; + +import java.util.List; +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.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.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +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.OfficialComponent; +import org.asynchttpclient.Request; +import org.asynchttpclient.RequestBuilder; +import org.asynchttpclient.Response; +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.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.modules.junit4.PowerMockRunnerDelegate; + +import static org.apache.skywalking.apm.agent.test.tools.SpanAssert.assertComponent; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +@RunWith(PowerMockRunner.class) +@PowerMockRunnerDelegate(TracingSegmentRunner.class) +@PrepareForTest(Response.class) +public class ExecuteInterceptorTest { + + @SegmentStoragePoint + private SegmentStorage segmentStorage; + + @Rule + public AgentServiceRule agentServiceRule = new AgentServiceRule(); + + @Mock + private MethodInterceptResult result; + + @Mock + private EnhancedInstance enhancedInstance; + + private Request request; + + private Object[] allArguments; + private Class[] argumentTypes; + + private ExecuteInterceptor executeInterceptor; + + @Before + public void setUp() throws Exception { + request = new RequestBuilder().setUrl("http://skywalking.org/").build(); + allArguments = new Object[] { + request, + null + }; + argumentTypes = new Class[] { + org.asynchttpclient.Request.class, + org.asynchttpclient.AsyncHandler.class + }; + executeInterceptor = new ExecuteInterceptor(); + } + + @Test + public void testSuccess() throws Throwable { + ContextManager.createEntrySpan("mock-test", new ContextCarrier()); + + executeInterceptor.beforeMethod(enhancedInstance, null, allArguments, argumentTypes, result); + executeInterceptor.afterMethod(enhancedInstance, null, allArguments, argumentTypes, null); + + AsyncHandlerWrapper asyncHandlerWrapper = (AsyncHandlerWrapper) allArguments[1]; + asyncHandlerWrapper.onCompleted(); + ContextManager.stopSpan(); + + assertThat(segmentStorage.getTraceSegments().size(), is(1)); + TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); + List spans = SegmentHelper.getSpans(traceSegment); + assertSpan(spans.get(0)); + SpanAssert.assertOccurException(spans.get(0), false); + + } + + @Test + public void testException() throws Throwable { + ContextManager.createEntrySpan("mock-test", new ContextCarrier()); + + executeInterceptor.beforeMethod(enhancedInstance, null, allArguments, argumentTypes, result); + executeInterceptor.handleMethodException( + enhancedInstance, null, allArguments, argumentTypes, new NullPointerException("testException")); + executeInterceptor.afterMethod(enhancedInstance, null, allArguments, argumentTypes, result); + + ContextManager.stopSpan(); + assertThat(segmentStorage.getTraceSegments().size(), is(1)); + TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); + List spans = SegmentHelper.getSpans(traceSegment); + + assertSpan(spans.get(0)); + SpanAssert.assertOccurException(spans.get(0), true); + SpanAssert.assertLogSize(spans.get(0), 1); + SpanAssert.assertException(SpanHelper.getLogs(spans.get(0)) + .get(0), NullPointerException.class, "testException"); + } + + @Test + public void afterMethod() throws Throwable { + Object ret = new Object(); + executeInterceptor.beforeMethod(enhancedInstance, null, allArguments, argumentTypes, result); + Object result = executeInterceptor.afterMethod(enhancedInstance, null, null, null, ret); + Assert.assertEquals(ret, result); + } + + private void assertSpan(AbstractTracingSpan span) { + assertComponent(span, new OfficialComponent(102, "AsyncHttpClient")); + SpanAssert.assertLayer(span, SpanLayer.HTTP); + SpanAssert.assertTag(span, 0, "GET"); + SpanAssert.assertTag(span, 1, "http://skywalking.org/"); + assertThat(span.isExit(), is(true)); + assertThat(span.getOperationName(), is("AsyncHttpClient/")); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/pom.xml index 679672300..757b17571 100644 --- a/apm-sniffer/apm-sdk-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/pom.xml @@ -102,6 +102,7 @@ xxl-job-2.x-plugin thrift-plugin httpclient-commons + asynchttpclient-2.x-plugin pom diff --git a/docs/en/setup/service-agent/java-agent/Plugin-list.md b/docs/en/setup/service-agent/java-agent/Plugin-list.md index ef0ed1ce0..d36329357 100644 --- a/docs/en/setup/service-agent/java-agent/Plugin-list.md +++ b/docs/en/setup/service-agent/java-agent/Plugin-list.md @@ -4,6 +4,7 @@ - armeria-085 - armeria-086 - armeria-098 +- async-http-client-2.x - avro-1.x - brpc-java - canal-1.x @@ -106,4 +107,4 @@ - undertow-2.x-plugin - vertx-core-3.x - xxl-job-2.x -- zookeeper-3.4.x \ No newline at end of file +- zookeeper-3.4.x diff --git a/docs/en/setup/service-agent/java-agent/Supported-list.md b/docs/en/setup/service-agent/java-agent/Supported-list.md index e10873f41..771a6ac34 100644 --- a/docs/en/setup/service-agent/java-agent/Supported-list.md +++ b/docs/en/setup/service-agent/java-agent/Supported-list.md @@ -23,6 +23,7 @@ * [Spring RestTemplete](https://github.com/spring-projects/spring-framework) 4.x * [Jetty Client](http://www.eclipse.org/jetty/) 9 * [Apache httpcomponent AsyncClient](https://hc.apache.org/httpcomponents-asyncclient-dev/) 4.x + * [AsyncHttpClient](https://github.com/AsyncHttpClient/async-http-client) 2.x * HTTP Gateway * [Spring Cloud Gateway](https://spring.io/projects/spring-cloud-gateway) 2.0.2.RELEASE -> 2.2.x.RELEASE (Optional²) * JDBC diff --git a/oap-server/server-bootstrap/src/main/resources/component-libraries.yml b/oap-server/server-bootstrap/src/main/resources/component-libraries.yml index 8a513e78f..193f39a2a 100755 --- a/oap-server/server-bootstrap/src/main/resources/component-libraries.yml +++ b/oap-server/server-bootstrap/src/main/resources/component-libraries.yml @@ -335,7 +335,9 @@ thrift-server: thrift-client: id: 101 languages: Java - +AsyncHttpClient: + id: 102 + languages: Java # .NET/.NET Core components # [3000, 4000) for C#/.NET only diff --git a/test/plugin/scenarios/asynchttpclient-scenario/bin/startup.sh b/test/plugin/scenarios/asynchttpclient-scenario/bin/startup.sh new file mode 100644 index 000000000..0e0e4edec --- /dev/null +++ b/test/plugin/scenarios/asynchttpclient-scenario/bin/startup.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# +# 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. + +home="$(cd "$(dirname $0)"; pwd)" + +java -jar ${agent_opts} ${home}/../libs/asynchttpclient-scenario.jar & \ No newline at end of file diff --git a/test/plugin/scenarios/asynchttpclient-scenario/config/expectedData.yaml b/test/plugin/scenarios/asynchttpclient-scenario/config/expectedData.yaml new file mode 100644 index 000000000..1eed6c1e5 --- /dev/null +++ b/test/plugin/scenarios/asynchttpclient-scenario/config/expectedData.yaml @@ -0,0 +1,72 @@ +# 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. +segmentItems: + - serviceName: asynchttpclient-scenario + segmentSize: ge 3 + segments: + - segmentId: not null + spans: + - operationName: AsyncHttpClient/asynchttpclient/back + operationId: 0 + parentSpanId: 0 + spanId: 1 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 102 + isError: false + spanType: Exit + peer: localhost:8080 + skipAnalysis: false + tags: + - {key: http.method, value: GET} + - {key: url, value: 'http://localhost:8080/asynchttpclient/back'} + - operationName: /asynchttpclient/case + operationId: 0 + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: not null + isError: false + spanType: Entry + peer: '' + skipAnalysis: false + tags: + - {key: url, value: 'http://localhost:8080/asynchttpclient/case'} + - {key: http.method, value: GET} + - segmentId: not null + spans: + - operationName: /asynchttpclient/back + operationId: 0 + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: not null + isError: false + spanType: Entry + peer: '' + skipAnalysis: false + tags: + - {key: url, value: 'http://localhost:8080/asynchttpclient/back'} + - {key: http.method, value: GET} + refs: + - {parentEndpoint: /asynchttpclient/case, networkAddress: 'localhost:8080', + refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not null, + parentService: asynchttpclient-scenario, traceId: not null} \ No newline at end of file diff --git a/test/plugin/scenarios/asynchttpclient-scenario/configuration.yml b/test/plugin/scenarios/asynchttpclient-scenario/configuration.yml new file mode 100644 index 000000000..77ac55a1c --- /dev/null +++ b/test/plugin/scenarios/asynchttpclient-scenario/configuration.yml @@ -0,0 +1,20 @@ +# 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. + +type: jvm +entryService: http://localhost:8080/asynchttpclient/case +healthCheck: http://localhost:8080/asynchttpclient/healthCheck +startScript: ./bin/startup.sh diff --git a/test/plugin/scenarios/asynchttpclient-scenario/pom.xml b/test/plugin/scenarios/asynchttpclient-scenario/pom.xml new file mode 100644 index 000000000..91453ce67 --- /dev/null +++ b/test/plugin/scenarios/asynchttpclient-scenario/pom.xml @@ -0,0 +1,135 @@ + + + + + org.apache.skywalking.apm.testcase + asynchttpclient-scenario + 1.0.0 + jar + + 4.0.0 + + + UTF-8 + UTF-8 + 1.8 + 2.10.0 + 4.1.2 + ${test.framework.version} + 1.5.9.RELEASE + 1.18.10 + 2.8.1 + + + skywalking-asynchttpclient-scenario + + + + + org.springframework.boot + spring-boot-starter + ${spring-boot.version} + + + spring-boot-starter-logging + org.springframework.boot + + + + + org.springframework.boot + spring-boot-starter-web + ${spring-boot.version} + + + org.asynchttpclient + async-http-client + ${async-http-client.version} + provided + + + org.apache.logging.log4j + log4j-api + ${log4j.version} + + + org.apache.logging.log4j + log4j-core + ${log4j.version} + + + org.projectlombok + lombok + ${lombok.version} + + + commons-logging + commons-logging + 1.2 + + + + + asynchttpclient-scenario + + + org.apache.maven.plugins + maven-compiler-plugin + 3.6.0 + + ${compiler.version} + ${compiler.version} + ${project.build.sourceEncoding} + + + + org.springframework.boot + spring-boot-maven-plugin + 1.5.9.RELEASE + + + + repackage + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + assemble + package + + single + + + + src/main/assembly/assembly.xml + + + + + + + + diff --git a/test/plugin/scenarios/asynchttpclient-scenario/src/main/assembly/assembly.xml b/test/plugin/scenarios/asynchttpclient-scenario/src/main/assembly/assembly.xml new file mode 100644 index 000000000..a015e3148 --- /dev/null +++ b/test/plugin/scenarios/asynchttpclient-scenario/src/main/assembly/assembly.xml @@ -0,0 +1,41 @@ + + + + + zip + + + + + ./bin + 0775 + + + + + + ${project.build.directory}/asynchttpclient-scenario.jar + ./libs + 0775 + + + diff --git a/test/plugin/scenarios/asynchttpclient-scenario/src/main/java/org/apache/skywalking/apm/testcase/asynchttpclient/Application.java b/test/plugin/scenarios/asynchttpclient-scenario/src/main/java/org/apache/skywalking/apm/testcase/asynchttpclient/Application.java new file mode 100644 index 000000000..4d72462f8 --- /dev/null +++ b/test/plugin/scenarios/asynchttpclient-scenario/src/main/java/org/apache/skywalking/apm/testcase/asynchttpclient/Application.java @@ -0,0 +1,34 @@ +/* + * 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.asynchttpclient; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + try { + SpringApplication.run(Application.class, args); + } catch (Exception e) { + // Never do this + } + } +} diff --git a/test/plugin/scenarios/asynchttpclient-scenario/src/main/java/org/apache/skywalking/apm/testcase/asynchttpclient/CaseController.java b/test/plugin/scenarios/asynchttpclient-scenario/src/main/java/org/apache/skywalking/apm/testcase/asynchttpclient/CaseController.java new file mode 100644 index 000000000..f6674b552 --- /dev/null +++ b/test/plugin/scenarios/asynchttpclient-scenario/src/main/java/org/apache/skywalking/apm/testcase/asynchttpclient/CaseController.java @@ -0,0 +1,73 @@ +/* + * 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.asynchttpclient; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.asynchttpclient.AsyncHttpClient; +import org.asynchttpclient.DefaultAsyncHttpClient; +import org.asynchttpclient.ListenableFuture; +import org.asynchttpclient.Request; +import org.asynchttpclient.RequestBuilder; +import org.asynchttpclient.Response; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +@Controller +@RequestMapping("/asynchttpclient") +public class CaseController { + + private static final Logger LOGGER = LogManager.getLogger(CaseController.class); + + @GetMapping("/back") + @ResponseBody + public String back() { + return "Hello back"; + } + + @GetMapping("/case") + @ResponseBody + public String asynchttpclientScenario() throws Exception { + String content = asyncRequest("http://localhost:8080/asynchttpclient/back"); + return content; + } + + @RequestMapping(value = "/healthCheck") + @ResponseBody + public String healthCheck() { + return "Success"; + } + + public static final String asyncRequest(String url) throws Exception { + + Request request = new RequestBuilder().setUrl("http://localhost:8080/asynchttpclient/back").build(); + + AsyncHttpClient asyncHttpClient = new DefaultAsyncHttpClient(); + + try { + ListenableFuture response = asyncHttpClient.executeRequest(request); + } catch (Exception e) { + LOGGER.error("AsyncHttpClient executeRequest failed" + e); + } + + return "Success"; + } +} diff --git a/test/plugin/scenarios/asynchttpclient-scenario/src/main/resources/application.yaml b/test/plugin/scenarios/asynchttpclient-scenario/src/main/resources/application.yaml new file mode 100644 index 000000000..f53375209 --- /dev/null +++ b/test/plugin/scenarios/asynchttpclient-scenario/src/main/resources/application.yaml @@ -0,0 +1,21 @@ +# +# 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. +# +# +server: + port: 8080 +logging: + config: classpath:log4j2.xml diff --git a/test/plugin/scenarios/asynchttpclient-scenario/src/main/resources/log4j2.xml b/test/plugin/scenarios/asynchttpclient-scenario/src/main/resources/log4j2.xml new file mode 100644 index 000000000..9849ed5a8 --- /dev/null +++ b/test/plugin/scenarios/asynchttpclient-scenario/src/main/resources/log4j2.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plugin/scenarios/asynchttpclient-scenario/support-version.list b/test/plugin/scenarios/asynchttpclient-scenario/support-version.list new file mode 100644 index 000000000..e8b580e7d --- /dev/null +++ b/test/plugin/scenarios/asynchttpclient-scenario/support-version.list @@ -0,0 +1,29 @@ +# 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 +# "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. + +2.0.0 +2.1.0 +2.2.0 +2.3.0 +2.4.0 +2.5.0 +2.6.0 +2.7.0 +2.8.0 +2.9.0 +2.10.0 +2.11.0 +2.12.0