diff --git a/.github/workflows/plugins-test.1.yaml b/.github/workflows/plugins-test.1.yaml index 25a9538ff..4dc1b6cbc 100644 --- a/.github/workflows/plugins-test.1.yaml +++ b/.github/workflows/plugins-test.1.yaml @@ -59,6 +59,7 @@ jobs: - httpasyncclient-scenario - httpclient-3.x-scenario - httpclient-4.3.x-scenario + - httpclient-5.x-scenario - hystrix-scenario - sentinel-scenario - influxdb-scenario diff --git a/CHANGES.md b/CHANGES.md index aa9b97c2b..2ab1ca89c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -26,6 +26,7 @@ Release Notes. rename `plugin.toolkit.log.grpc.reporter.max_message_size` to `log.max_message_size`. * Implement Kafka Log Reporter. Add config item of `agnt.conf`, `plugin.kafka.topic_logging`. * Upgrade byte-buddy to 1.11.18 +* Add plugin to support Apache HttpClient 5. #### Documentation diff --git a/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/pom.xml new file mode 100644 index 000000000..db28dc373 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/pom.xml @@ -0,0 +1,54 @@ + + + + + 4.0.0 + + org.apache.skywalking + apm-sdk-plugin + 8.8.0-SNAPSHOT + + + apm-httpclient-5.x-plugin + jar + + httpclient-5.x-plugin + + + UTF-8 + 5.0 + 4.12 + + + + + org.apache.httpcomponents.client5 + httpclient5 + ${apache-httpclient.version} + provided + + + junit + junit + ${junit.version} + test + + + diff --git a/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/httpclient/v5/Constants.java b/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/httpclient/v5/Constants.java new file mode 100644 index 000000000..2497ca8cb --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/httpclient/v5/Constants.java @@ -0,0 +1,23 @@ +/* + * 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.httpclient.v5; + +public class Constants { + + public static String SKYWALKING_CONTEXT_SNAPSHOT = "skywalking-context-snapshot"; +} diff --git a/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/httpclient/v5/HttpAsyncClientDoExecuteInterceptor.java b/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/httpclient/v5/HttpAsyncClientDoExecuteInterceptor.java new file mode 100644 index 000000000..68267fcc6 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/httpclient/v5/HttpAsyncClientDoExecuteInterceptor.java @@ -0,0 +1,59 @@ +/* + * 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.httpclient.v5; + +import org.apache.hc.core5.concurrent.FutureCallback; +import org.apache.hc.core5.http.nio.AsyncResponseConsumer; +import org.apache.hc.core5.http.protocol.HttpContext; +import org.apache.skywalking.apm.agent.core.context.ContextManager; +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.plugin.httpclient.v5.wrapper.AsyncResponseConsumerWrapper; +import org.apache.skywalking.apm.plugin.httpclient.v5.wrapper.FutureCallbackWrapper; + +import java.lang.reflect.Method; + +public class HttpAsyncClientDoExecuteInterceptor implements InstanceMethodsAroundInterceptor { + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + MethodInterceptResult result) throws Throwable { + AsyncResponseConsumer consumer = (AsyncResponseConsumer) allArguments[2]; + HttpContext context = (HttpContext) allArguments[4]; + FutureCallback callback = (FutureCallback) allArguments[5]; + allArguments[2] = new AsyncResponseConsumerWrapper(consumer); + allArguments[5] = new FutureCallbackWrapper(callback); + if (ContextManager.isActive()) { + context.setAttribute(Constants.SKYWALKING_CONTEXT_SNAPSHOT, ContextManager.capture()); + } + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + Object ret) throws Throwable { + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t) { + + } +} diff --git a/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/httpclient/v5/HttpClientDoExecuteInterceptor.java b/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/httpclient/v5/HttpClientDoExecuteInterceptor.java new file mode 100644 index 000000000..aed04d609 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/httpclient/v5/HttpClientDoExecuteInterceptor.java @@ -0,0 +1,137 @@ +/* + * 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.httpclient.v5; + +import org.apache.hc.core5.http.ClassicHttpRequest; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.HttpHost; +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.logging.api.ILog; +import org.apache.skywalking.apm.agent.core.logging.api.LogManager; +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 java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; + +public class HttpClientDoExecuteInterceptor implements InstanceMethodsAroundInterceptor { + + private static final ILog LOGGER = LogManager.getLogger(HttpClientDoExecuteInterceptor.class); + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + MethodInterceptResult result) throws Throwable { + if (allArguments[0] == null || allArguments[1] == null) { + // illegal args, can't trace. ignore. + return; + } + final HttpHost httpHost = (HttpHost) allArguments[0]; + ClassicHttpRequest httpRequest = (ClassicHttpRequest) allArguments[1]; + final ContextCarrier contextCarrier = new ContextCarrier(); + + String remotePeer = httpHost.getHostName() + ":" + port(httpHost); + + String uri = httpRequest.getUri().toString(); + String requestURI = getRequestURI(uri); + String operationName = requestURI; + AbstractSpan span = ContextManager.createExitSpan(operationName, contextCarrier, remotePeer); + + span.setComponent(ComponentsDefine.HTTPCLIENT); + Tags.URL.set(span, buildURL(httpHost, uri)); + Tags.HTTP.METHOD.set(span, httpRequest.getMethod()); + SpanLayer.asHttp(span); + + CarrierItem next = contextCarrier.items(); + while (next.hasNext()) { + next = next.next(); + httpRequest.setHeader(next.getHeadKey(), next.getHeadValue()); + } + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + Object ret) throws Throwable { + if (allArguments[0] == null || allArguments[1] == null) { + return ret; + } + + if (ret != null) { + ClassicHttpResponse response = (ClassicHttpResponse) ret; + + int statusCode = response.getCode(); + AbstractSpan span = ContextManager.activeSpan(); + if (statusCode >= 400) { + span.errorOccurred(); + Tags.HTTP_RESPONSE_STATUS_CODE.set(span, statusCode); + } + } + + ContextManager.stopSpan(); + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t) { + AbstractSpan activeSpan = ContextManager.activeSpan(); + activeSpan.log(t); + } + + private String getRequestURI(String uri) throws MalformedURLException { + if (isUrl(uri)) { + String requestPath = new URL(uri).getPath(); + return requestPath != null && requestPath.length() > 0 ? requestPath : "/"; + } else { + return uri; + } + } + + private boolean isUrl(String uri) { + String lowerUrl = uri.toLowerCase(); + return lowerUrl.startsWith("http") || lowerUrl.startsWith("https"); + } + + private String buildURL(HttpHost httpHost, String uri) { + if (isUrl(uri)) { + return uri; + } else { + StringBuilder buff = new StringBuilder(); + buff.append(httpHost.getSchemeName().toLowerCase()); + buff.append("://"); + buff.append(httpHost.getHostName()); + buff.append(":"); + buff.append(port(httpHost)); + buff.append(uri); + return buff.toString(); + } + } + + private int port(HttpHost httpHost) { + int port = httpHost.getPort(); + return port > 0 ? port : "https".equals(httpHost.getSchemeName().toLowerCase()) ? 443 : 80; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/httpclient/v5/IOSessionImplPollInterceptor.java b/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/httpclient/v5/IOSessionImplPollInterceptor.java new file mode 100644 index 000000000..fc8ef190d --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/httpclient/v5/IOSessionImplPollInterceptor.java @@ -0,0 +1,92 @@ +/* + * 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.httpclient.v5; + +import org.apache.hc.client5.http.protocol.HttpClientContext; +import org.apache.hc.core5.http.message.BasicHttpRequest; +import org.apache.hc.core5.http.nio.command.RequestExecutionCommand; +import org.apache.hc.core5.http.protocol.HttpContext; +import org.apache.hc.core5.reactor.Command; +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.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.tag.Tags; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +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 java.lang.reflect.Method; +import java.net.URI; + +public class IOSessionImplPollInterceptor 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 { + Command command = (Command) ret; + if (!(command instanceof RequestExecutionCommand)) { + return ret; + } + HttpContext httpContext = ((RequestExecutionCommand) command).getContext(); + ContextSnapshot snapshot = (ContextSnapshot) httpContext.getAttribute(Constants.SKYWALKING_CONTEXT_SNAPSHOT); + if (snapshot == null) { + return ret; + } + httpContext.removeAttribute(Constants.SKYWALKING_CONTEXT_SNAPSHOT); + AbstractSpan localSpan = ContextManager.createLocalSpan("httpasyncclient/local"); + localSpan.setComponent(ComponentsDefine.HTTP_ASYNC_CLIENT); + localSpan.setLayer(SpanLayer.HTTP); + ContextManager.continued(snapshot); + + final ContextCarrier contextCarrier = new ContextCarrier(); + BasicHttpRequest request = (BasicHttpRequest) httpContext.getAttribute(HttpClientContext.HTTP_REQUEST); + URI uri = request.getUri(); + + String operationName = uri.getPath(); + int port = uri.getPort(); + AbstractSpan span = ContextManager + .createExitSpan(operationName, contextCarrier, uri.getHost() + ":" + (port == -1 ? 80 : port)); + span.setComponent(ComponentsDefine.HTTP_ASYNC_CLIENT); + Tags.URL.set(span, uri.toURL().toString()); + Tags.HTTP.METHOD.set(span, request.getMethod()); + SpanLayer.asHttp(span); + CarrierItem next = contextCarrier.items(); + while (next.hasNext()) { + next = next.next(); + request.setHeader(next.getHeadKey(), next.getHeadValue()); + } + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t) { + + } +} diff --git a/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/httpclient/v5/define/HttpAsyncClientInstrumentation.java b/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/httpclient/v5/define/HttpAsyncClientInstrumentation.java new file mode 100644 index 000000000..d79655d0d --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/httpclient/v5/define/HttpAsyncClientInstrumentation.java @@ -0,0 +1,75 @@ +/* + * 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.httpclient.v5.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.MultiClassNameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; + +public class HttpAsyncClientInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS_MINIMAL_HTTP = "org.apache.hc.client5.http.impl.async.MinimalHttpAsyncClient"; + private static final String ENHANCE_CLASS_MINIMAL_H2 = "org.apache.hc.client5.http.impl.async.MinimalH2AsyncClient"; + private static final String ENHANCE_CLASS_INTERNAL_HTTP = "org.apache.hc.client5.http.impl.async.InternalHttpAsyncClient"; + private static final String ENHANCE_CLASS_INTERNAL_H2 = "org.apache.hc.client5.http.impl.async.InternalH2AsyncClient"; + private static final String METHOD_NAME = "doExecute"; + private static final String INTERCEPT_CLASS = "org.apache.skywalking.apm.plugin.httpclient.v5.HttpAsyncClientDoExecuteInterceptor"; + + @Override + public ClassMatch enhanceClass() { + return MultiClassNameMatch.byMultiClassMatch( + ENHANCE_CLASS_MINIMAL_HTTP, + ENHANCE_CLASS_MINIMAL_H2, + ENHANCE_CLASS_INTERNAL_HTTP, + ENHANCE_CLASS_INTERNAL_H2); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return null; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[]{ + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named(METHOD_NAME); + } + + @Override + public String getMethodsInterceptor() { + return INTERCEPT_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return true; + } + } + }; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/httpclient/v5/define/HttpClientInstrumentation.java b/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/httpclient/v5/define/HttpClientInstrumentation.java new file mode 100644 index 000000000..79593f1ad --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/httpclient/v5/define/HttpClientInstrumentation.java @@ -0,0 +1,69 @@ +/* + * 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.httpclient.v5.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.MultiClassNameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; + +public class HttpClientInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS_MINIMAL = "org.apache.hc.client5.http.impl.classic.MinimalHttpClient"; + private static final String ENHANCE_CLASS_INTERNAL = "org.apache.hc.client5.http.impl.classic.InternalHttpClient"; + private static final String METHOD_NAME = "doExecute"; + private static final String INTERCEPT_CLASS = "org.apache.skywalking.apm.plugin.httpclient.v5.HttpClientDoExecuteInterceptor"; + + @Override + public ClassMatch enhanceClass() { + return MultiClassNameMatch.byMultiClassMatch(ENHANCE_CLASS_MINIMAL, ENHANCE_CLASS_INTERNAL); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return null; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[]{ + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named(METHOD_NAME); + } + + @Override + public String getMethodsInterceptor() { + return INTERCEPT_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/httpclient/v5/define/IOSessionImplInstrumentation.java b/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/httpclient/v5/define/IOSessionImplInstrumentation.java new file mode 100644 index 000000000..584108910 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/httpclient/v5/define/IOSessionImplInstrumentation.java @@ -0,0 +1,68 @@ +/* + * 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.httpclient.v5.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName; + +public class IOSessionImplInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "org.apache.hc.core5.reactor.IOSessionImpl"; + private static final String METHOD_NAME = "poll"; + private static final String INTERCEPT_CLASS = "org.apache.skywalking.apm.plugin.httpclient.v5.IOSessionImplPollInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return null; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[]{ + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named(METHOD_NAME); + } + + @Override + public String getMethodsInterceptor() { + return INTERCEPT_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/httpclient/v5/wrapper/AsyncResponseConsumerWrapper.java b/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/httpclient/v5/wrapper/AsyncResponseConsumerWrapper.java new file mode 100644 index 000000000..9e78477f9 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/httpclient/v5/wrapper/AsyncResponseConsumerWrapper.java @@ -0,0 +1,99 @@ +/* + * 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.httpclient.v5.wrapper; + +import org.apache.hc.core5.concurrent.FutureCallback; +import org.apache.hc.core5.http.EntityDetails; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpResponse; +import org.apache.hc.core5.http.nio.AsyncResponseConsumer; +import org.apache.hc.core5.http.nio.CapacityChannel; +import org.apache.hc.core5.http.protocol.HttpContext; +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 java.io.IOException; +import java.nio.ByteBuffer; +import java.util.List; + +public class AsyncResponseConsumerWrapper implements AsyncResponseConsumer { + + private AsyncResponseConsumer consumer; + + public AsyncResponseConsumerWrapper(AsyncResponseConsumer consumer) { + this.consumer = consumer; + } + + @Override + public void consumeResponse(HttpResponse response, EntityDetails entityDetails, HttpContext context, + FutureCallback resultCallback) throws HttpException, IOException { + if (ContextManager.isActive()) { + int statusCode = response.getCode(); + if (statusCode >= 400) { + AbstractSpan span = ContextManager.activeSpan().errorOccurred(); + Tags.HTTP_RESPONSE_STATUS_CODE.set(span, statusCode); + } + ContextManager.stopSpan(); + } + consumer.consumeResponse(response, entityDetails, context, resultCallback); + } + + @Override + public void informationResponse(HttpResponse response, HttpContext context) throws HttpException, IOException { + if (ContextManager.isActive()) { + int statusCode = response.getCode(); + if (statusCode >= 400) { + AbstractSpan span = ContextManager.activeSpan().errorOccurred(); + Tags.HTTP_RESPONSE_STATUS_CODE.set(span, statusCode); + } + ContextManager.stopSpan(); + } + consumer.informationResponse(response, context); + } + + @Override + public void failed(Exception cause) { + if (ContextManager.isActive()) { + ContextManager.activeSpan().errorOccurred().log(cause); + ContextManager.stopSpan(); + } + consumer.failed(cause); + } + + @Override + public void updateCapacity(CapacityChannel capacityChannel) throws IOException { + consumer.updateCapacity(capacityChannel); + } + + @Override + public void consume(ByteBuffer src) throws IOException { + consumer.consume(src); + } + + @Override + public void streamEnd(List trailers) throws HttpException, IOException { + consumer.streamEnd(trailers); + } + + @Override + public void releaseResources() { + consumer.releaseResources(); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/httpclient/v5/wrapper/FutureCallbackWrapper.java b/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/httpclient/v5/wrapper/FutureCallbackWrapper.java new file mode 100644 index 000000000..f606856ed --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/httpclient/v5/wrapper/FutureCallbackWrapper.java @@ -0,0 +1,62 @@ +/* + * 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.httpclient.v5.wrapper; + +import org.apache.hc.core5.concurrent.FutureCallback; +import org.apache.skywalking.apm.agent.core.context.ContextManager; + +public class FutureCallbackWrapper implements FutureCallback { + + private FutureCallback callback; + + public FutureCallbackWrapper(FutureCallback callback) { + this.callback = callback; + } + + @Override + public void completed(T o) { + if (ContextManager.isActive()) { + ContextManager.stopSpan(); + } + if (callback != null) { + callback.completed(o); + } + } + + @Override + public void failed(Exception e) { + if (ContextManager.isActive()) { + ContextManager.activeSpan().errorOccurred().log(e); + ContextManager.stopSpan(); + } + if (callback != null) { + callback.failed(e); + } + } + + @Override + public void cancelled() { + if (ContextManager.isActive()) { + ContextManager.activeSpan().errorOccurred(); + ContextManager.stopSpan(); + } + if (callback != null) { + callback.cancelled(); + } + } +} diff --git a/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/main/resources/skywalking-plugin.def new file mode 100644 index 000000000..7f04889ca --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/main/resources/skywalking-plugin.def @@ -0,0 +1,19 @@ +# 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. + +httpclient-5.x=org.apache.skywalking.apm.plugin.httpclient.v5.define.HttpClientInstrumentation +httpclient-5.x=org.apache.skywalking.apm.plugin.httpclient.v5.define.HttpAsyncClientInstrumentation +httpclient-5.x=org.apache.skywalking.apm.plugin.httpclient.v5.define.IOSessionImplInstrumentation diff --git a/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/httpclient/v5/HttpClientExecuteInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/httpclient/v5/HttpClientExecuteInterceptorTest.java new file mode 100644 index 000000000..b5eb47905 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/httpclient-5.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/httpclient/v5/HttpClientExecuteInterceptorTest.java @@ -0,0 +1,198 @@ +/* + * 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.httpclient.v5; + +import org.apache.hc.core5.http.ClassicHttpRequest; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.HttpHost; +import org.apache.skywalking.apm.agent.core.boot.ServiceManager; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan; +import org.apache.skywalking.apm.agent.core.context.trace.LogDataEntity; +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.TracingSegmentRunner; +import org.hamcrest.CoreMatchers; +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.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.modules.junit4.PowerMockRunnerDelegate; + +import java.net.URI; +import java.util.List; + +import static junit.framework.TestCase.assertNotNull; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(PowerMockRunner.class) +@PowerMockRunnerDelegate(TracingSegmentRunner.class) +@PrepareForTest(HttpHost.class) +public class HttpClientExecuteInterceptorTest { + + @SegmentStoragePoint + private SegmentStorage segmentStorage; + + @Rule + public AgentServiceRule agentServiceRule = new AgentServiceRule(); + + private HttpClientDoExecuteInterceptor httpClientDoExecuteInterceptor; + + @Mock + private HttpHost httpHost; + @Mock + private ClassicHttpRequest request; + @Mock + private ClassicHttpResponse httpResponse; + + private Object[] allArguments; + private Class[] argumentsType; + + @Mock + private EnhancedInstance enhancedInstance; + + @Before + public void setUp() throws Exception { + + ServiceManager.INSTANCE.boot(); + httpClientDoExecuteInterceptor = new HttpClientDoExecuteInterceptor(); + + PowerMockito.mock(HttpHost.class); + when(httpResponse.getCode()).thenReturn(200); + when(httpHost.getHostName()).thenReturn("127.0.0.1"); + when(httpHost.getSchemeName()).thenReturn("http"); + when(request.getUri()).thenReturn(new URI("http://127.0.0.1:8080/test-web/test")); + when(request.getMethod()).thenReturn("GET"); + when(httpHost.getPort()).thenReturn(8080); + + allArguments = new Object[]{ + httpHost, + request + }; + argumentsType = new Class[]{ + httpHost.getClass(), + request.getClass() + }; + } + + @Test + public void testHttpClient() throws Throwable { + httpClientDoExecuteInterceptor.beforeMethod(enhancedInstance, null, allArguments, argumentsType, null); + httpClientDoExecuteInterceptor.afterMethod(enhancedInstance, null, allArguments, argumentsType, httpResponse); + + Assert.assertThat(segmentStorage.getTraceSegments().size(), is(1)); + TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); + + List spans = SegmentHelper.getSpans(traceSegment); + assertHttpSpan(spans.get(0)); + verify(request, times(3)).setHeader(anyString(), anyString()); + } + + @Test + public void testStatusCodeNotEquals200() throws Throwable { + when(httpResponse.getCode()).thenReturn(500); + httpClientDoExecuteInterceptor.beforeMethod(enhancedInstance, null, allArguments, argumentsType, null); + httpClientDoExecuteInterceptor.afterMethod(enhancedInstance, null, allArguments, argumentsType, httpResponse); + + Assert.assertThat(segmentStorage.getTraceSegments().size(), is(1)); + TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); + List spans = SegmentHelper.getSpans(traceSegment); + + assertThat(spans.size(), is(1)); + + List tags = SpanHelper.getTags(spans.get(0)); + assertThat(tags.size(), is(3)); + assertThat(tags.get(2).getValue(), is("500")); + + assertHttpSpan(spans.get(0)); + assertThat(SpanHelper.getErrorOccurred(spans.get(0)), is(true)); + verify(request, times(3)).setHeader(anyString(), anyString()); + } + + @Test + public void testHttpClientWithException() throws Throwable { + httpClientDoExecuteInterceptor.beforeMethod(enhancedInstance, null, allArguments, argumentsType, null); + httpClientDoExecuteInterceptor.handleMethodException(enhancedInstance, null, allArguments, argumentsType, + new RuntimeException("testException")); + httpClientDoExecuteInterceptor.afterMethod(enhancedInstance, null, allArguments, argumentsType, httpResponse); + + Assert.assertThat(segmentStorage.getTraceSegments().size(), is(1)); + TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); + List spans = SegmentHelper.getSpans(traceSegment); + + assertThat(spans.size(), is(1)); + AbstractTracingSpan span = spans.get(0); + assertHttpSpan(span); + assertThat(SpanHelper.getErrorOccurred(span), is(true)); + assertHttpSpanErrorLog(SpanHelper.getLogs(span)); + verify(request, times(3)).setHeader(anyString(), anyString()); + + } + + @Test + public void testUriNotProtocol() throws Throwable { + when(request.getUri()).thenReturn(new URI("/test-web/test")); + httpClientDoExecuteInterceptor.beforeMethod(enhancedInstance, null, allArguments, argumentsType, null); + httpClientDoExecuteInterceptor.afterMethod(enhancedInstance, null, allArguments, argumentsType, httpResponse); + + Assert.assertThat(segmentStorage.getTraceSegments().size(), is(1)); + TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); + + List spans = SegmentHelper.getSpans(traceSegment); + assertHttpSpan(spans.get(0)); + verify(request, times(3)).setHeader(anyString(), anyString()); + } + + private void assertHttpSpanErrorLog(List logs) { + assertThat(logs.size(), is(1)); + LogDataEntity logData = logs.get(0); + Assert.assertThat(logData.getLogs().size(), is(4)); + Assert.assertThat(logData.getLogs().get(0).getValue(), CoreMatchers.is("error")); + Assert.assertThat(logData.getLogs() + .get(1) + .getValue(), CoreMatchers.is(RuntimeException.class.getName())); + Assert.assertThat(logData.getLogs().get(2).getValue(), is("testException")); + assertNotNull(logData.getLogs().get(3).getValue()); + } + + private void assertHttpSpan(AbstractTracingSpan span) { + assertThat(span.getOperationName(), is("/test-web/test")); + assertThat(SpanHelper.getComponentId(span), is(2)); + List tags = SpanHelper.getTags(span); + assertThat(tags.get(0).getValue(), is("http://127.0.0.1:8080/test-web/test")); + assertThat(tags.get(1).getValue(), is("GET")); + assertThat(span.isExit(), is(true)); + } + +} diff --git a/apm-sniffer/apm-sdk-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/pom.xml index 5a302f4ef..3b85113b3 100644 --- a/apm-sniffer/apm-sdk-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/pom.xml @@ -109,6 +109,7 @@ shardingsphere-plugins druid-1.x-plugin hikaricp-3.x-4.x-plugin + httpclient-5.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 0e027ee1e..b4a67d461 100644 --- a/docs/en/setup/service-agent/java-agent/Plugin-list.md +++ b/docs/en/setup/service-agent/java-agent/Plugin-list.md @@ -31,6 +31,7 @@ - httpasyncclient-4.x - httpclient-3.x - httpclient-4.x +- httpclient-5.x - hystrix-1.x - influxdb-2.x - jdk-http-plugin 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 7872b4788..2bf88984d 100644 --- a/docs/en/setup/service-agent/java-agent/Supported-list.md +++ b/docs/en/setup/service-agent/java-agent/Supported-list.md @@ -23,7 +23,7 @@ metrics based on the tracing data. * [Feign](https://github.com/OpenFeign/feign) 9.x * [Netflix Spring Cloud Feign](https://github.com/spring-cloud/spring-cloud-openfeign) 1.1.x -> 2.x * [Okhttp](https://github.com/square/okhttp) 3.x -> 4.x - * [Apache httpcomponent HttpClient](http://hc.apache.org/) 2.0 -> 3.1, 4.2, 4.3 + * [Apache httpcomponent HttpClient](http://hc.apache.org/) 2.0 -> 3.1, 4.2, 4.3, 5.0, 5.1 * [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-4.1.x/) 4.x diff --git a/test/plugin/scenarios/httpclient-5.x-scenario/bin/startup.sh b/test/plugin/scenarios/httpclient-5.x-scenario/bin/startup.sh new file mode 100644 index 000000000..8f9602a25 --- /dev/null +++ b/test/plugin/scenarios/httpclient-5.x-scenario/bin/startup.sh @@ -0,0 +1,19 @@ +#!/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/httpclient-5.x-scenario.jar & diff --git a/test/plugin/scenarios/httpclient-5.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/httpclient-5.x-scenario/config/expectedData.yaml new file mode 100644 index 000000000..1eec51213 --- /dev/null +++ b/test/plugin/scenarios/httpclient-5.x-scenario/config/expectedData.yaml @@ -0,0 +1,120 @@ +# 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: httpclient-5.x-scenario + segmentSize: ge 4 + segments: + - segmentId: not null + spans: + - operationName: /httpclient-5.x/back + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 1 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://127.0.0.1:8080/httpclient-5.x/back'} + - {key: http.method, value: GET} + refs: + - {parentEndpoint: httpasyncclient/local, networkAddress: '127.0.0.1:8080', + refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: httpclient-5.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: /httpclient-5.x/back + parentSpanId: 0 + spanId: 1 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 26 + isError: false + spanType: Exit + peer: 127.0.0.1:8080 + tags: + - {key: url, value: 'http://127.0.0.1:8080/httpclient-5.x/back'} + - {key: http.method, value: GET} + skipAnalysis: 'false' + - operationName: httpasyncclient/local + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 26 + isError: false + spanType: Local + peer: '' + refs: + - {parentEndpoint: /httpclient-5.x/case/asyncGet, networkAddress: '', + refType: CrossThread, parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: httpclient-5.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: /httpclient-5.x/case/asyncGet + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 1 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://127.0.0.1:8080/httpclient-5.x/case/asyncGet'} + - {key: http.method, value: GET} + refs: + - {parentEndpoint: /httpclient-5.x/case/get, networkAddress: '127.0.0.1:8080', + refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: httpclient-5.x-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: /httpclient-5.x/case/asyncGet + parentSpanId: 0 + spanId: 1 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 2 + isError: false + spanType: Exit + peer: 127.0.0.1:8080 + tags: + - {key: url, value: 'http://127.0.0.1:8080/httpclient-5.x/case/asyncGet'} + - {key: http.method, value: GET} + skipAnalysis: 'false' + - operationName: /httpclient-5.x/case/get + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 1 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://127.0.0.1:8080/httpclient-5.x/case/get'} + - {key: http.method, value: GET} + skipAnalysis: 'false' diff --git a/test/plugin/scenarios/httpclient-5.x-scenario/configuration.yml b/test/plugin/scenarios/httpclient-5.x-scenario/configuration.yml new file mode 100644 index 000000000..7086b9b08 --- /dev/null +++ b/test/plugin/scenarios/httpclient-5.x-scenario/configuration.yml @@ -0,0 +1,18 @@ +# 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://127.0.0.1:8080/httpclient-5.x/case/get +healthCheck: http://127.0.0.1:8080/httpclient-5.x/case/healthcheck +startScript: ./bin/startup.sh diff --git a/test/plugin/scenarios/httpclient-5.x-scenario/pom.xml b/test/plugin/scenarios/httpclient-5.x-scenario/pom.xml new file mode 100644 index 000000000..08bc5059d --- /dev/null +++ b/test/plugin/scenarios/httpclient-5.x-scenario/pom.xml @@ -0,0 +1,131 @@ + + + + 4.0.0 + + org.apache.skywalking + httpclient-5.x-scenario + 6.5.0 + + + UTF-8 + UTF-8 + 1.8 + 5.0 + ${test.framework.version} + 1.5.9.RELEASE + 1.18.20 + 2.8.1 + + + skywalking-httpclient-5.x-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.apache.httpcomponents.client5 + httpclient5 + ${test.framework.version} + + + org.projectlombok + lombok + ${lombok.version} + + + org.apache.logging.log4j + log4j-api + ${log4j.version} + + + org.apache.logging.log4j + log4j-core + ${log4j.version} + + + commons-logging + commons-logging + 1.2 + + + + + httpclient-5.x-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/httpclient-5.x-scenario/src/main/assembly/assembly.xml b/test/plugin/scenarios/httpclient-5.x-scenario/src/main/assembly/assembly.xml new file mode 100644 index 000000000..dd71e6c76 --- /dev/null +++ b/test/plugin/scenarios/httpclient-5.x-scenario/src/main/assembly/assembly.xml @@ -0,0 +1,41 @@ + + + + + zip + + + + + ./bin + 0775 + + + + + + ${project.build.directory}/httpclient-5.x-scenario.jar + ./libs + 0775 + + + diff --git a/test/plugin/scenarios/httpclient-5.x-scenario/src/main/java/org/apache/skywalking/testcase/httpclient5/BackController.java b/test/plugin/scenarios/httpclient-5.x-scenario/src/main/java/org/apache/skywalking/testcase/httpclient5/BackController.java new file mode 100644 index 000000000..792c3e3cc --- /dev/null +++ b/test/plugin/scenarios/httpclient-5.x-scenario/src/main/java/org/apache/skywalking/testcase/httpclient5/BackController.java @@ -0,0 +1,32 @@ +/* + * 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.testcase.httpclient5; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/httpclient-5.x") +public class BackController { + @GetMapping("/back") + public String back() { + return "Hello back"; + } +} diff --git a/test/plugin/scenarios/httpclient-5.x-scenario/src/main/java/org/apache/skywalking/testcase/httpclient5/FrontController.java b/test/plugin/scenarios/httpclient-5.x-scenario/src/main/java/org/apache/skywalking/testcase/httpclient5/FrontController.java new file mode 100644 index 000000000..9631631c6 --- /dev/null +++ b/test/plugin/scenarios/httpclient-5.x-scenario/src/main/java/org/apache/skywalking/testcase/httpclient5/FrontController.java @@ -0,0 +1,84 @@ +/* + * 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.testcase.httpclient5; + +import org.apache.hc.client5.http.ClientProtocolException; +import org.apache.hc.client5.http.async.methods.SimpleHttpRequest; +import org.apache.hc.client5.http.async.methods.SimpleHttpRequests; +import org.apache.hc.client5.http.async.methods.SimpleHttpResponse; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.ParseException; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.concurrent.Future; + +@RestController +@RequestMapping("/httpclient-5.x/case") +public class FrontController { + + private static final Logger LOGGER = LogManager.getLogger(FrontController.class); + + @Autowired + private CloseableHttpClient httpClient; + + @Autowired + private CloseableHttpAsyncClient httpAsyncClient; + + @GetMapping("/healthcheck") + public String healthcheck() { + return "Success"; + } + + @GetMapping("/get") + public String get() throws Exception { + final HttpGet httpget = new HttpGet("http://127.0.0.1:8080/httpclient-5.x/case/asyncGet"); + + LOGGER.info("Executing request " + httpget.getMethod() + " " + httpget.getUri()); + try (final CloseableHttpResponse response = httpClient.execute(httpget)) { + LOGGER.info(response.getCode() + " -> " + response.getReasonPhrase()); + final HttpEntity entity = response.getEntity(); + try { + return entity != null ? EntityUtils.toString(entity) : ""; + } catch (final ParseException ex) { + throw new ClientProtocolException(ex); + } + } + } + + @GetMapping("/asyncGet") + public String asyncGet() throws Exception { + SimpleHttpRequest request = SimpleHttpRequests.get("http://127.0.0.1:8080/httpclient-5.x/back"); + LOGGER.info("Executing async request " + request.getMethod() + " " + request.getUri()); + + Future future = httpAsyncClient.execute(request, null); + SimpleHttpResponse response = future.get(); + LOGGER.info(response.getCode() + " -> " + response.getReasonPhrase()); + return response.getBodyText(); + } +} diff --git a/test/plugin/scenarios/httpclient-5.x-scenario/src/main/java/org/apache/skywalking/testcase/httpclient5/HttpClientConfig.java b/test/plugin/scenarios/httpclient-5.x-scenario/src/main/java/org/apache/skywalking/testcase/httpclient5/HttpClientConfig.java new file mode 100644 index 000000000..4f5e9f619 --- /dev/null +++ b/test/plugin/scenarios/httpclient-5.x-scenario/src/main/java/org/apache/skywalking/testcase/httpclient5/HttpClientConfig.java @@ -0,0 +1,46 @@ +/* + * 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.testcase.httpclient5; + +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.client5.http.impl.async.HttpAsyncClients; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class HttpClientConfig { + + private static final Logger LOGGER = LogManager.getLogger(HttpClientConfig.class); + + @Bean + public CloseableHttpClient getCloseableHttpClient() { + return HttpClients.createDefault(); + } + + @Bean + public CloseableHttpAsyncClient getCloseableHttpAsyncClient() { + CloseableHttpAsyncClient httpAsyncClient = HttpAsyncClients.createDefault(); + httpAsyncClient.start(); + return httpAsyncClient; + } +} diff --git a/test/plugin/scenarios/httpclient-5.x-scenario/src/main/java/org/apache/skywalking/testcase/httpclient5/Httpclient5Application.java b/test/plugin/scenarios/httpclient-5.x-scenario/src/main/java/org/apache/skywalking/testcase/httpclient5/Httpclient5Application.java new file mode 100644 index 000000000..85652ebce --- /dev/null +++ b/test/plugin/scenarios/httpclient-5.x-scenario/src/main/java/org/apache/skywalking/testcase/httpclient5/Httpclient5Application.java @@ -0,0 +1,33 @@ +/* + * 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.testcase.httpclient5; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Httpclient5Application { + + public static void main(String[] args) { + + Object[] sources = new Object[] {Httpclient5Application.class}; + SpringApplication.run(sources, args); + + } +} diff --git a/test/plugin/scenarios/httpclient-5.x-scenario/src/main/resource/application.yml b/test/plugin/scenarios/httpclient-5.x-scenario/src/main/resource/application.yml new file mode 100644 index 000000000..a2f8b0f19 --- /dev/null +++ b/test/plugin/scenarios/httpclient-5.x-scenario/src/main/resource/application.yml @@ -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. +server: + port: 8080 diff --git a/test/plugin/scenarios/httpclient-5.x-scenario/src/main/resource/log4j2.xml b/test/plugin/scenarios/httpclient-5.x-scenario/src/main/resource/log4j2.xml new file mode 100644 index 000000000..985bd03bf --- /dev/null +++ b/test/plugin/scenarios/httpclient-5.x-scenario/src/main/resource/log4j2.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plugin/scenarios/httpclient-5.x-scenario/support-version.list b/test/plugin/scenarios/httpclient-5.x-scenario/support-version.list new file mode 100644 index 000000000..ce0644ff9 --- /dev/null +++ b/test/plugin/scenarios/httpclient-5.x-scenario/support-version.list @@ -0,0 +1,18 @@ +# 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. + +5.0 +5.1