diff --git a/agent-bootstrap/src/main/java/io/opentelemetry/auto/config/Config.java b/agent-bootstrap/src/main/java/io/opentelemetry/auto/config/Config.java index c45c7d50a3..da0395ad16 100644 --- a/agent-bootstrap/src/main/java/io/opentelemetry/auto/config/Config.java +++ b/agent-bootstrap/src/main/java/io/opentelemetry/auto/config/Config.java @@ -19,18 +19,18 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedHashSet; import java.util.List; -import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.SortedSet; import java.util.regex.Pattern; import lombok.Getter; +import lombok.NonNull; import lombok.ToString; import lombok.extern.slf4j.Slf4j; @@ -46,6 +46,7 @@ import lombok.extern.slf4j.Slf4j; @Slf4j @ToString(includeFieldNames = true) public class Config { + /** Config keys below */ private static final String PREFIX = "ota."; @@ -250,13 +251,14 @@ public class Config { } /** - * @deprecated This method should only be used internally. Use the instance getter instead {@link - * #isIntegrationEnabled(SortedSet, boolean)}. * @param integrationNames * @param defaultEnabled * @return + * @deprecated This method should only be used internally. Use the instance getter instead {@link + * #isIntegrationEnabled(SortedSet, boolean)}. */ - public static boolean integrationEnabled( + @Deprecated + private static boolean integrationEnabled( final SortedSet integrationNames, final boolean defaultEnabled) { // If default is enabled, we want to enable individually, // if default is disabled, we want to disable individually. @@ -286,9 +288,10 @@ public class Config { */ public static String getSettingFromEnvironment(final String name, final String defaultValue) { String value; + final String systemPropertyName = propertyNameToSystemPropertyName(name); // System properties and properties provided from command line have the highest precedence - value = System.getProperties().getProperty(propertyNameToSystemPropertyName(name)); + value = System.getProperties().getProperty(systemPropertyName); if (null != value) { return value; } @@ -300,7 +303,7 @@ public class Config { } // If value is not defined yet, we look at properties optionally defined in a properties file - value = propertiesFromConfigFile.getProperty(propertyNameToSystemPropertyName(name)); + value = propertiesFromConfigFile.getProperty(systemPropertyName); if (null != value) { return value; } @@ -314,7 +317,8 @@ public class Config { * * @deprecated This method should only be used internally. Use the explicit getter instead. */ - public static List getListSettingFromEnvironment( + @NonNull + private static List getListSettingFromEnvironment( final String name, final String defaultValue) { return parseList(getSettingFromEnvironment(name, defaultValue)); } @@ -326,8 +330,7 @@ public class Config { */ public static Boolean getBooleanSettingFromEnvironment( final String name, final Boolean defaultValue) { - final String value = getSettingFromEnvironment(name, null); - return value == null || value.trim().isEmpty() ? defaultValue : Boolean.valueOf(value); + return getSettingFromEnvironmentWithLog(name, Boolean.class, defaultValue); } /** @@ -336,13 +339,7 @@ public class Config { * @deprecated This method should only be used internally. Use the explicit getter instead. */ public static Float getFloatSettingFromEnvironment(final String name, final Float defaultValue) { - final String value = getSettingFromEnvironment(name, null); - try { - return value == null ? defaultValue : Float.valueOf(value); - } catch (final NumberFormatException e) { - log.warn("Invalid configuration for " + name, e); - return defaultValue; - } + return getSettingFromEnvironmentWithLog(name, Float.class, defaultValue); } /** @@ -350,41 +347,20 @@ public class Config { */ private static Integer getIntegerSettingFromEnvironment( final String name, final Integer defaultValue) { - final String value = getSettingFromEnvironment(name, null); + return getSettingFromEnvironmentWithLog(name, Integer.class, defaultValue); + } + + private static T getSettingFromEnvironmentWithLog( + final String name, final Class tClass, final T defaultValue) { try { - return value == null ? defaultValue : Integer.valueOf(value); + return valueOf(getSettingFromEnvironment(name, null), tClass, defaultValue); } catch (final NumberFormatException e) { log.warn("Invalid configuration for " + name, e); return defaultValue; } } - /** - * Calls {@link #getSettingFromEnvironment(String, String)} and converts the result to a set of - * strings splitting by space or comma. - */ - private static > Set getEnumSetSettingFromEnvironment( - final String name, - final String defaultValue, - final Class clazz, - final boolean emptyResultMeansUseDefault) { - final String value = getSettingFromEnvironment(name, defaultValue); - Set result = - convertStringSetToEnumSet( - parseStringIntoSetOfNonEmptyStrings(value, SPLIT_BY_SPACE_OR_COMMA_REGEX), clazz); - - if (emptyResultMeansUseDefault && result.isEmpty()) { - // Treat empty parsing result as no value and use default instead - result = - convertStringSetToEnumSet( - parseStringIntoSetOfNonEmptyStrings(defaultValue, SPLIT_BY_SPACE_OR_COMMA_REGEX), - clazz); - } - - return result; - } - - private Set getIntegerRangeSettingFromEnvironment( + private static Set getIntegerRangeSettingFromEnvironment( final String name, final Set defaultValue) { final String value = getSettingFromEnvironment(name, null); try { @@ -402,6 +378,7 @@ public class Config { * @param setting The setting name, e.g. `trace.enabled` * @return The public facing environment variable name */ + @NonNull private static String propertyNameToEnvironmentVariableName(final String setting) { return ENV_REPLACEMENT .matcher(propertyNameToSystemPropertyName(setting).toUpperCase()) @@ -415,14 +392,39 @@ public class Config { * @param setting The setting name, e.g. `trace.config` * @return The public facing system property name */ + @NonNull private static String propertyNameToSystemPropertyName(final String setting) { return PREFIX + setting; } - private static Map getPropertyMapValue( - final Properties properties, final String name, final Map defaultValue) { - final String value = properties.getProperty(name); - return value == null || value.trim().isEmpty() ? defaultValue : parseMap(value, name); + /** + * @param value to parse by tClass::valueOf + * @param tClass should contain static parsing method "T valueOf(String)" + * @param defaultValue + * @param + * @return value == null || value.trim().isEmpty() ? defaultValue : tClass.valueOf(value) + * @throws NumberFormatException + */ + private static T valueOf( + final String value, @NonNull final Class tClass, final T defaultValue) { + if (value == null || value.trim().isEmpty()) { + log.debug("valueOf: using defaultValue '{}' for '{}' of '{}' ", defaultValue, value, tClass); + return defaultValue; + } + try { + return (T) + MethodHandles.publicLookup() + .findStatic(tClass, "valueOf", MethodType.methodType(tClass, String.class)) + .invoke(value); + } catch (final NumberFormatException e) { + throw e; + } catch (final NoSuchMethodException | IllegalAccessException e) { + log.debug("Can't invoke or access 'valueOf': ", e); + throw new NumberFormatException(e.toString()); + } catch (final Throwable e) { + log.debug("Can't parse: ", e); + throw new NumberFormatException(e.toString()); + } } private static List getPropertyListValue( @@ -433,32 +435,15 @@ public class Config { private static Boolean getPropertyBooleanValue( final Properties properties, final String name, final Boolean defaultValue) { - final String value = properties.getProperty(name); - return value == null || value.trim().isEmpty() ? defaultValue : Boolean.valueOf(value); + return valueOf(properties.getProperty(name), Boolean.class, defaultValue); } private static Integer getPropertyIntegerValue( final Properties properties, final String name, final Integer defaultValue) { - final String value = properties.getProperty(name); - return value == null || value.trim().isEmpty() ? defaultValue : Integer.valueOf(value); + return valueOf(properties.getProperty(name), Integer.class, defaultValue); } - private static > Set getPropertySetValue( - final Properties properties, final String name, final Class clazz) { - final String value = properties.getProperty(name); - if (value != null) { - final Set result = - convertStringSetToEnumSet( - parseStringIntoSetOfNonEmptyStrings(value, SPLIT_BY_SPACE_OR_COMMA_REGEX), clazz); - if (!result.isEmpty()) { - return result; - } - } - // null means parent value should be used - return null; - } - - private Set getPropertyIntegerRangeValue( + private static Set getPropertyIntegerRangeValue( final Properties properties, final String name, final Set defaultValue) { final String value = properties.getProperty(name); try { @@ -469,38 +454,9 @@ public class Config { } } - private static Map parseMap(final String str, final String settingName) { - // If we ever want to have default values besides an empty map, this will need to change. - if (str == null || str.trim().isEmpty()) { - return Collections.emptyMap(); - } - if (!str.matches("(([^,:]+:[^,:]*,)*([^,:]+:[^,:]*),?)?")) { - log.warn( - "Invalid config for {}: '{}'. Must match 'key1:value1,key2:value2'.", settingName, str); - return Collections.emptyMap(); - } - - final String[] tokens = str.split(",", -1); - final Map map = newHashMap(tokens.length); - - for (final String token : tokens) { - final String[] keyValue = token.split(":", -1); - if (keyValue.length == 2) { - final String key = keyValue[0].trim(); - final String value = keyValue[1].trim(); - if (value.length() <= 0) { - log.warn("Ignoring empty value for key '{}' in config for {}", key, settingName); - continue; - } - map.put(key, value); - } - } - return Collections.unmodifiableMap(map); - } - - private static Set parseIntegerRangeSet(String str, final String settingName) + @NonNull + private static Set parseIntegerRangeSet(@NonNull String str, final String settingName) throws NumberFormatException { - assert str != null; str = str.replaceAll("\\s", ""); if (!str.matches("\\d{3}(?:-\\d{3})?(?:,\\d{3}(?:-\\d{3})?)*")) { log.warn( @@ -530,10 +486,7 @@ public class Config { return Collections.unmodifiableSet(set); } - private static Map newHashMap(final int size) { - return new HashMap<>(size + 1, 1f); - } - + @NonNull private static List parseList(final String str) { if (str == null || str.trim().isEmpty()) { return Collections.emptyList(); @@ -547,34 +500,6 @@ public class Config { return Collections.unmodifiableList(Arrays.asList(tokens)); } - private static Set parseStringIntoSetOfNonEmptyStrings( - final String str, final String regex) { - // Using LinkedHashSet to preserve original string order - final Set result = new LinkedHashSet<>(); - // Java returns single value when splitting an empty string. We do not need that value, so - // we need to throw it out. - for (final String value : str.split(regex)) { - if (!value.isEmpty()) { - result.add(value); - } - } - return Collections.unmodifiableSet(result); - } - - private static > Set convertStringSetToEnumSet( - final Set input, final Class clazz) { - // Using LinkedHashSet to preserve original string order - final Set result = new LinkedHashSet<>(); - for (final String value : input) { - try { - result.add(Enum.valueOf(clazz, value.toUpperCase())); - } catch (final IllegalArgumentException e) { - log.debug("Cannot recognize config string value: {}, {}", value, clazz); - } - } - return Collections.unmodifiableSet(result); - } - /** * Loads the optional configuration properties file into the global {@link Properties} object. * @@ -606,23 +531,13 @@ public class Config { return properties; } - FileReader fileReader = null; - try { - fileReader = new FileReader(configurationFile); + try (final FileReader fileReader = new FileReader(configurationFile)) { properties.load(fileReader); } catch (final FileNotFoundException fnf) { log.error("Configuration file '{}' not found.", configurationFilePath); } catch (final IOException ioe) { log.error( "Configuration file '{}' cannot be accessed or correctly parsed.", configurationFilePath); - } finally { - if (fileReader != null) { - try { - fileReader.close(); - } catch (final IOException ioe) { - log.error("Configuration file '{}' was not closed correctly.", configurationFilePath); - } - } } return properties; diff --git a/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/AgentTransformers.java b/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/AgentTransformers.java deleted file mode 100644 index c91138c1e8..0000000000 --- a/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/AgentTransformers.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2020, OpenTelemetry Authors - * - * Licensed 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 io.opentelemetry.auto.tooling; - -import net.bytebuddy.agent.builder.AgentBuilder; -import net.bytebuddy.asm.TypeConstantAdjustment; -import net.bytebuddy.description.type.TypeDescription; -import net.bytebuddy.dynamic.DynamicType; -import net.bytebuddy.utility.JavaModule; - -public class AgentTransformers { - - private static final AgentBuilder.Transformer CONSTANT_ADJUSTER = - new AgentBuilder.Transformer() { - @Override - public DynamicType.Builder transform( - final DynamicType.Builder builder, - final TypeDescription typeDescription, - final ClassLoader classLoader, - final JavaModule javaModule) { - return builder.visit(TypeConstantAdjustment.INSTANCE); - } - }; - - public static AgentBuilder.Transformer defaultTransformers() { - return CONSTANT_ADJUSTER; - } -} diff --git a/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/Instrumenter.java b/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/Instrumenter.java index 6dd029a2a9..d582ba30a2 100644 --- a/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/Instrumenter.java +++ b/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/Instrumenter.java @@ -22,6 +22,7 @@ import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.not; import io.opentelemetry.auto.config.Config; +import io.opentelemetry.auto.tooling.bytebuddy.AgentTransformers; import io.opentelemetry.auto.tooling.bytebuddy.ExceptionHandlers; import io.opentelemetry.auto.tooling.context.FieldBackedProvider; import io.opentelemetry.auto.tooling.context.InstrumentationContextProvider; @@ -122,7 +123,7 @@ public interface Instrumenter { if (helperClassNames.length > 0) { agentBuilder = agentBuilder.transform( - new HelperInjector(this.getClass().getSimpleName(), helperClassNames)); + new HelperInjector(getClass().getSimpleName(), helperClassNames)); } return agentBuilder; } diff --git a/buildSrc/src/main/groovy/MuzzlePlugin.groovy b/buildSrc/src/main/groovy/MuzzlePlugin.groovy index 95b04bb3a4..1ad9ef2af1 100644 --- a/buildSrc/src/main/groovy/MuzzlePlugin.groovy +++ b/buildSrc/src/main/groovy/MuzzlePlugin.groovy @@ -24,6 +24,7 @@ import org.gradle.api.model.ObjectFactory import java.lang.reflect.Method import java.security.SecureClassLoader import java.util.concurrent.atomic.AtomicReference +import java.util.regex.Pattern /** * muzzle task plugin which runs muzzle validation against a range of dependencies. @@ -36,7 +37,8 @@ class MuzzlePlugin implements Plugin { private static final AtomicReference TOOLING_LOADER = new AtomicReference<>() static { RemoteRepository central = new RemoteRepository.Builder("central", "default", "https://repo1.maven.org/maven2/").build() - MUZZLE_REPOS = new ArrayList(Arrays.asList(central)) + RemoteRepository typesafe = new RemoteRepository.Builder("typesafe", "default", "https://repo.typesafe.com/typesafe/releases").build() + MUZZLE_REPOS = new ArrayList(Arrays.asList(central, typesafe)) } @Override @@ -343,6 +345,8 @@ class MuzzlePlugin implements Plugin { return session } + private static final Pattern GIT_SHA_PATTERN = Pattern.compile('^.*-[0-9a-f]{7,}$') + /** * Filter out snapshot-type builds from versions list. */ @@ -357,7 +361,8 @@ class MuzzlePlugin implements Plugin { version.contains(".m") || version.contains("-m") || version.contains("-dev") || - version.contains("public_draft") + version.contains("public_draft") || + version.matches(GIT_SHA_PATTERN) } return list } diff --git a/dd-trace-api/src/test/resources/apikey.very-old b/dd-trace-api/src/test/resources/apikey.very-old new file mode 100644 index 0000000000..ebe7a17f7b --- /dev/null +++ b/dd-trace-api/src/test/resources/apikey.very-old @@ -0,0 +1 @@ +test-api-key-very-old diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index 0fe410f3dd..84cac83dd8 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -7,6 +7,7 @@ ext { slf4j : "1.7.29", guava : "20.0", // Last version to support Java 7 + okhttp : "3.12.8", // 3.12.x is last version to support Java7 spock : "1.3-groovy-$spockGroovyVer", groovy : groovyVer, diff --git a/gradle/java.gradle b/gradle/java.gradle index 272bc6fbdc..5435461a3f 100644 --- a/gradle/java.gradle +++ b/gradle/java.gradle @@ -133,6 +133,9 @@ repositories { maven { url "https://adoptopenjdk.jfrog.io/adoptopenjdk/jmc-libs-snapshots" } + maven { + url "https://repo.typesafe.com/typesafe/releases" + } } dependencies { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 84a906615c..6623300beb 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew.bat b/gradlew.bat index 24467a141f..9109989e3c 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" diff --git a/instrumentation/akka-http-10.0/src/test/groovy/AkkaHttpClientInstrumentationTest.groovy b/instrumentation/akka-http-10.0/src/test/groovy/AkkaHttpClientInstrumentationTest.groovy index 65f0a12685..00bc8d05b9 100644 --- a/instrumentation/akka-http-10.0/src/test/groovy/AkkaHttpClientInstrumentationTest.groovy +++ b/instrumentation/akka-http-10.0/src/test/groovy/AkkaHttpClientInstrumentationTest.groovy @@ -22,9 +22,11 @@ import akka.stream.ActorMaterializer import io.opentelemetry.auto.bootstrap.instrumentation.decorator.HttpClientDecorator import io.opentelemetry.auto.test.base.HttpClientTest import spock.lang.Shared +import spock.lang.Timeout import static io.opentelemetry.trace.Span.Kind.CLIENT +@Timeout(5) class AkkaHttpClientInstrumentationTest extends HttpClientTest { @Shared @@ -55,6 +57,12 @@ class AkkaHttpClientInstrumentationTest extends HttpClientTest { false } + @Override + boolean testRemoteConnection() { + // Not sure how to properly set timeouts... + return false + } + def "singleRequest exception trace"() { when: // Passing null causes NPE in singleRequest diff --git a/instrumentation/apache-httpasyncclient-4.0/src/test/groovy/ApacheHttpAsyncClientCallbackTest.groovy b/instrumentation/apache-httpasyncclient-4.0/src/test/groovy/ApacheHttpAsyncClientCallbackTest.groovy index 6cc8f1991f..e0332eeaea 100644 --- a/instrumentation/apache-httpasyncclient-4.0/src/test/groovy/ApacheHttpAsyncClientCallbackTest.groovy +++ b/instrumentation/apache-httpasyncclient-4.0/src/test/groovy/ApacheHttpAsyncClientCallbackTest.groovy @@ -15,19 +15,28 @@ */ import io.opentelemetry.auto.test.base.HttpClientTest import org.apache.http.HttpResponse +import org.apache.http.client.config.RequestConfig import org.apache.http.concurrent.FutureCallback import org.apache.http.impl.nio.client.HttpAsyncClients import org.apache.http.message.BasicHeader import spock.lang.AutoCleanup import spock.lang.Shared +import spock.lang.Timeout import java.util.concurrent.CompletableFuture +@Timeout(5) class ApacheHttpAsyncClientCallbackTest extends HttpClientTest { + @Shared + RequestConfig requestConfig = RequestConfig.custom() + .setConnectTimeout(CONNECT_TIMEOUT_MS) + .setSocketTimeout(READ_TIMEOUT_MS) + .build() + @AutoCleanup @Shared - def client = HttpAsyncClients.createDefault() + def client = HttpAsyncClients.custom().setDefaultRequestConfig(requestConfig).build() def setupSpec() { client.start() @@ -68,4 +77,9 @@ class ApacheHttpAsyncClientCallbackTest extends HttpClientTest { Integer statusOnRedirectError() { return 302 } + + @Override + boolean testRemoteConnection() { + false // otherwise SocketTimeoutException for https requests + } } diff --git a/instrumentation/apache-httpasyncclient-4.0/src/test/groovy/ApacheHttpAsyncClientNullCallbackTest.groovy b/instrumentation/apache-httpasyncclient-4.0/src/test/groovy/ApacheHttpAsyncClientNullCallbackTest.groovy index 93c394a93e..8da20d1ecf 100644 --- a/instrumentation/apache-httpasyncclient-4.0/src/test/groovy/ApacheHttpAsyncClientNullCallbackTest.groovy +++ b/instrumentation/apache-httpasyncclient-4.0/src/test/groovy/ApacheHttpAsyncClientNullCallbackTest.groovy @@ -14,18 +14,27 @@ * limitations under the License. */ import io.opentelemetry.auto.test.base.HttpClientTest +import org.apache.http.client.config.RequestConfig import org.apache.http.impl.nio.client.HttpAsyncClients import org.apache.http.message.BasicHeader import spock.lang.AutoCleanup import spock.lang.Shared +import spock.lang.Timeout import java.util.concurrent.Future +@Timeout(5) class ApacheHttpAsyncClientNullCallbackTest extends HttpClientTest { + @Shared + RequestConfig requestConfig = RequestConfig.custom() + .setConnectTimeout(CONNECT_TIMEOUT_MS) + .setSocketTimeout(READ_TIMEOUT_MS) + .build() + @AutoCleanup @Shared - def client = HttpAsyncClients.createDefault() + def client = HttpAsyncClients.custom().setDefaultRequestConfig(requestConfig).build() def setupSpec() { client.start() @@ -53,4 +62,9 @@ class ApacheHttpAsyncClientNullCallbackTest extends HttpClientTest { Integer statusOnRedirectError() { return 302 } + + @Override + boolean testRemoteConnection() { + false // otherwise SocketTimeoutException for https requests + } } diff --git a/instrumentation/apache-httpasyncclient-4.0/src/test/groovy/ApacheHttpAsyncClientTest.groovy b/instrumentation/apache-httpasyncclient-4.0/src/test/groovy/ApacheHttpAsyncClientTest.groovy index 36906124a8..498fa91642 100644 --- a/instrumentation/apache-httpasyncclient-4.0/src/test/groovy/ApacheHttpAsyncClientTest.groovy +++ b/instrumentation/apache-httpasyncclient-4.0/src/test/groovy/ApacheHttpAsyncClientTest.groovy @@ -15,19 +15,28 @@ */ import io.opentelemetry.auto.test.base.HttpClientTest import org.apache.http.HttpResponse +import org.apache.http.client.config.RequestConfig import org.apache.http.concurrent.FutureCallback import org.apache.http.impl.nio.client.HttpAsyncClients import org.apache.http.message.BasicHeader import spock.lang.AutoCleanup import spock.lang.Shared +import spock.lang.Timeout import java.util.concurrent.CountDownLatch +@Timeout(5) class ApacheHttpAsyncClientTest extends HttpClientTest { + @Shared + RequestConfig requestConfig = RequestConfig.custom() + .setConnectTimeout(CONNECT_TIMEOUT_MS) + .setSocketTimeout(READ_TIMEOUT_MS) + .build() + @AutoCleanup @Shared - def client = HttpAsyncClients.createDefault() + def client = HttpAsyncClients.custom().setDefaultRequestConfig(requestConfig).build() def setupSpec() { client.start() @@ -74,4 +83,9 @@ class ApacheHttpAsyncClientTest extends HttpClientTest { Integer statusOnRedirectError() { return 302 } + + @Override + boolean testRemoteConnection() { + false // otherwise SocketTimeoutException for https requests + } } diff --git a/instrumentation/apache-httpclient/apache-httpclient-2.0/src/test/groovy/CommonsHttpClientTest.groovy b/instrumentation/apache-httpclient/apache-httpclient-2.0/src/test/groovy/CommonsHttpClientTest.groovy index 963165dbcd..e89bbc288d 100644 --- a/instrumentation/apache-httpclient/apache-httpclient-2.0/src/test/groovy/CommonsHttpClientTest.groovy +++ b/instrumentation/apache-httpclient/apache-httpclient-2.0/src/test/groovy/CommonsHttpClientTest.groovy @@ -24,11 +24,18 @@ import org.apache.commons.httpclient.methods.PostMethod import org.apache.commons.httpclient.methods.PutMethod import org.apache.commons.httpclient.methods.TraceMethod import spock.lang.Shared +import spock.lang.Timeout +@Timeout(5) class CommonsHttpClientTest extends HttpClientTest { @Shared HttpClient client = new HttpClient() + def setupSpec() { + client.setConnectionTimeout(CONNECT_TIMEOUT_MS) + client.setTimeout(READ_TIMEOUT_MS) + } + @Override int doRequest(String method, URI uri, Map headers, Closure callback) { HttpMethod httpMethod diff --git a/instrumentation/apache-httpclient/apache-httpclient-4.0/src/test/groovy/ApacheHttpClientResponseHandlerTest.groovy b/instrumentation/apache-httpclient/apache-httpclient-4.0/src/test/groovy/ApacheHttpClientResponseHandlerTest.groovy index 2efc267432..de571f42d2 100644 --- a/instrumentation/apache-httpclient/apache-httpclient-4.0/src/test/groovy/ApacheHttpClientResponseHandlerTest.groovy +++ b/instrumentation/apache-httpclient/apache-httpclient-4.0/src/test/groovy/ApacheHttpClientResponseHandlerTest.groovy @@ -18,8 +18,12 @@ import org.apache.http.HttpResponse import org.apache.http.client.ResponseHandler import org.apache.http.impl.client.DefaultHttpClient import org.apache.http.message.BasicHeader +import org.apache.http.params.HttpConnectionParams +import org.apache.http.params.HttpParams import spock.lang.Shared +import spock.lang.Timeout +@Timeout(5) class ApacheHttpClientResponseHandlerTest extends HttpClientTest { @Shared @@ -33,6 +37,12 @@ class ApacheHttpClientResponseHandlerTest extends HttpClientTest { } } + def setupSpec() { + HttpParams httpParams = client.getParams() + HttpConnectionParams.setConnectionTimeout(httpParams, CONNECT_TIMEOUT_MS) + HttpConnectionParams.setSoTimeout(httpParams, READ_TIMEOUT_MS) + } + @Override int doRequest(String method, URI uri, Map headers, Closure callback) { def request = new HttpUriRequest(method, uri) diff --git a/instrumentation/apache-httpclient/apache-httpclient-4.0/src/test/groovy/ApacheHttpClientTest.groovy b/instrumentation/apache-httpclient/apache-httpclient-4.0/src/test/groovy/ApacheHttpClientTest.groovy index 1945d30a2f..41e624f157 100644 --- a/instrumentation/apache-httpclient/apache-httpclient-4.0/src/test/groovy/ApacheHttpClientTest.groovy +++ b/instrumentation/apache-httpclient/apache-httpclient-4.0/src/test/groovy/ApacheHttpClientTest.groovy @@ -20,13 +20,22 @@ import org.apache.http.HttpResponse import org.apache.http.impl.client.DefaultHttpClient import org.apache.http.message.BasicHeader import org.apache.http.message.BasicHttpRequest +import org.apache.http.params.HttpConnectionParams +import org.apache.http.params.HttpParams import org.apache.http.protocol.BasicHttpContext import spock.lang.Shared +import spock.lang.Timeout abstract class ApacheHttpClientTest extends HttpClientTest { @Shared def client = new DefaultHttpClient() + def setupSpec() { + HttpParams httpParams = client.getParams() + HttpConnectionParams.setConnectionTimeout(httpParams, CONNECT_TIMEOUT_MS) + HttpConnectionParams.setSoTimeout(httpParams, READ_TIMEOUT_MS) + } + @Override int doRequest(String method, URI uri, Map headers, Closure callback) { def request = createRequest(method, uri) @@ -64,6 +73,7 @@ abstract class ApacheHttpClientTest extends HttpClientTes } } +@Timeout(5) class ApacheClientHostRequest extends ApacheHttpClientTest { @Override BasicHttpRequest createRequest(String method, URI uri) { @@ -74,8 +84,14 @@ class ApacheClientHostRequest extends ApacheHttpClientTest { HttpResponse executeRequest(BasicHttpRequest request, URI uri) { return client.execute(new HttpHost(uri.getHost(), uri.getPort()), request) } + + @Override + boolean testRemoteConnection() { + return false + } } +@Timeout(5) class ApacheClientHostRequestContext extends ApacheHttpClientTest { @Override BasicHttpRequest createRequest(String method, URI uri) { @@ -86,8 +102,14 @@ class ApacheClientHostRequestContext extends ApacheHttpClientTest { @Override BasicHttpRequest createRequest(String method, URI uri) { @@ -98,8 +120,14 @@ class ApacheClientHostRequestResponseHandler extends ApacheHttpClientTest response }) } + + @Override + boolean testRemoteConnection() { + return false + } } +@Timeout(5) class ApacheClientHostRequestResponseHandlerContext extends ApacheHttpClientTest { @Override BasicHttpRequest createRequest(String method, URI uri) { @@ -110,8 +138,14 @@ class ApacheClientHostRequestResponseHandlerContext extends ApacheHttpClientTest HttpResponse executeRequest(BasicHttpRequest request, URI uri) { return client.execute(new HttpHost(uri.getHost(), uri.getPort()), request, { response -> response }, new BasicHttpContext()) } + + @Override + boolean testRemoteConnection() { + return false + } } +@Timeout(5) class ApacheClientUriRequest extends ApacheHttpClientTest { @Override HttpUriRequest createRequest(String method, URI uri) { @@ -124,6 +158,7 @@ class ApacheClientUriRequest extends ApacheHttpClientTest { } } +@Timeout(5) class ApacheClientUriRequestContext extends ApacheHttpClientTest { @Override HttpUriRequest createRequest(String method, URI uri) { @@ -136,6 +171,7 @@ class ApacheClientUriRequestContext extends ApacheHttpClientTest } } +@Timeout(5) class ApacheClientUriRequestResponseHandler extends ApacheHttpClientTest { @Override HttpUriRequest createRequest(String method, URI uri) { @@ -148,6 +184,7 @@ class ApacheClientUriRequestResponseHandler extends ApacheHttpClientTest { @Override HttpUriRequest createRequest(String method, URI uri) { diff --git a/instrumentation/google-http-client-1.19/src/test/groovy/AbstractGoogleHttpClientTest.groovy b/instrumentation/google-http-client-1.19/src/test/groovy/AbstractGoogleHttpClientTest.groovy index dcdcd7c441..937a18a57f 100644 --- a/instrumentation/google-http-client-1.19/src/test/groovy/AbstractGoogleHttpClientTest.groovy +++ b/instrumentation/google-http-client-1.19/src/test/groovy/AbstractGoogleHttpClientTest.groovy @@ -38,6 +38,8 @@ abstract class AbstractGoogleHttpClientTest extends HttpClientTest { GenericUrl genericUrl = new GenericUrl(uri) HttpRequest request = requestFactory.buildRequest(method, genericUrl, null) + request.connectTimeout = CONNECT_TIMEOUT_MS + request.readTimeout = READ_TIMEOUT_MS request.getHeaders().putAll(headers) request.setThrowExceptionOnExecuteError(throwExceptionOnError) diff --git a/instrumentation/google-http-client-1.19/src/test/groovy/GoogleHttpClientAsyncTest.groovy b/instrumentation/google-http-client-1.19/src/test/groovy/GoogleHttpClientAsyncTest.groovy index 95053b70da..e29805369e 100644 --- a/instrumentation/google-http-client-1.19/src/test/groovy/GoogleHttpClientAsyncTest.groovy +++ b/instrumentation/google-http-client-1.19/src/test/groovy/GoogleHttpClientAsyncTest.groovy @@ -15,7 +15,11 @@ */ import com.google.api.client.http.HttpRequest import com.google.api.client.http.HttpResponse +import spock.lang.Retry +import spock.lang.Timeout +@Retry(condition = { !invocation.method.name.contains('circular redirects') }) +@Timeout(5) class GoogleHttpClientAsyncTest extends AbstractGoogleHttpClientTest { @Override HttpResponse executeRequest(HttpRequest request) { diff --git a/instrumentation/google-http-client-1.19/src/test/groovy/GoogleHttpClientTest.groovy b/instrumentation/google-http-client-1.19/src/test/groovy/GoogleHttpClientTest.groovy index 47063bccef..0bbf60dbdd 100644 --- a/instrumentation/google-http-client-1.19/src/test/groovy/GoogleHttpClientTest.groovy +++ b/instrumentation/google-http-client-1.19/src/test/groovy/GoogleHttpClientTest.groovy @@ -15,7 +15,11 @@ */ import com.google.api.client.http.HttpRequest import com.google.api.client.http.HttpResponse +import spock.lang.Retry +import spock.lang.Timeout +@Retry(condition = { !invocation.method.name.contains('circular redirects') }) +@Timeout(5) class GoogleHttpClientTest extends AbstractGoogleHttpClientTest { @Override HttpResponse executeRequest(HttpRequest request) { diff --git a/instrumentation/http-url-connection/src/test/groovy/HttpUrlConnectionResponseCodeOnlyTest.groovy b/instrumentation/http-url-connection/src/test/groovy/HttpUrlConnectionResponseCodeOnlyTest.groovy index 90db9070a9..fd7d5ecafa 100644 --- a/instrumentation/http-url-connection/src/test/groovy/HttpUrlConnectionResponseCodeOnlyTest.groovy +++ b/instrumentation/http-url-connection/src/test/groovy/HttpUrlConnectionResponseCodeOnlyTest.groovy @@ -14,7 +14,9 @@ * limitations under the License. */ import io.opentelemetry.auto.test.base.HttpClientTest +import spock.lang.Timeout +@Timeout(5) class HttpUrlConnectionResponseCodeOnlyTest extends HttpClientTest { @Override @@ -22,6 +24,8 @@ class HttpUrlConnectionResponseCodeOnlyTest extends HttpClientTest { HttpURLConnection connection = uri.toURL().openConnection() try { connection.setRequestMethod(method) + connection.connectTimeout = CONNECT_TIMEOUT_MS + connection.readTimeout = READ_TIMEOUT_MS headers.each { connection.setRequestProperty(it.key, it.value) } connection.setRequestProperty("Connection", "close") return connection.getResponseCode() diff --git a/instrumentation/http-url-connection/src/test/groovy/HttpUrlConnectionTest.groovy b/instrumentation/http-url-connection/src/test/groovy/HttpUrlConnectionTest.groovy index d78897b6ac..fa0093cd25 100644 --- a/instrumentation/http-url-connection/src/test/groovy/HttpUrlConnectionTest.groovy +++ b/instrumentation/http-url-connection/src/test/groovy/HttpUrlConnectionTest.groovy @@ -18,11 +18,13 @@ import io.opentelemetry.auto.instrumentation.api.Tags import io.opentelemetry.auto.test.base.HttpClientTest import spock.lang.Ignore import spock.lang.Requires +import spock.lang.Timeout import sun.net.www.protocol.https.HttpsURLConnectionImpl import static io.opentelemetry.auto.test.utils.TraceUtils.runUnderTrace import static io.opentelemetry.trace.Span.Kind.CLIENT +@Timeout(5) class HttpUrlConnectionTest extends HttpClientTest { static final RESPONSE = "Hello." @@ -36,6 +38,8 @@ class HttpUrlConnectionTest extends HttpClientTest { headers.each { connection.setRequestProperty(it.key, it.value) } connection.setRequestProperty("Connection", "close") connection.useCaches = true + connection.connectTimeout = CONNECT_TIMEOUT_MS + connection.readTimeout = READ_TIMEOUT_MS def parentSpan = TEST_TRACER.getCurrentSpan() def stream = connection.inputStream assert TEST_TRACER.getCurrentSpan() == parentSpan diff --git a/instrumentation/http-url-connection/src/test/groovy/HttpUrlConnectionUseCachesFalseTest.groovy b/instrumentation/http-url-connection/src/test/groovy/HttpUrlConnectionUseCachesFalseTest.groovy index f5e0dff0bf..e562b343b2 100644 --- a/instrumentation/http-url-connection/src/test/groovy/HttpUrlConnectionUseCachesFalseTest.groovy +++ b/instrumentation/http-url-connection/src/test/groovy/HttpUrlConnectionUseCachesFalseTest.groovy @@ -14,7 +14,9 @@ * limitations under the License. */ import io.opentelemetry.auto.test.base.HttpClientTest +import spock.lang.Timeout +@Timeout(5) class HttpUrlConnectionUseCachesFalseTest extends HttpClientTest { @Override @@ -25,6 +27,8 @@ class HttpUrlConnectionUseCachesFalseTest extends HttpClientTest { headers.each { connection.setRequestProperty(it.key, it.value) } connection.setRequestProperty("Connection", "close") connection.useCaches = false + connection.connectTimeout = CONNECT_TIMEOUT_MS + connection.readTimeout = READ_TIMEOUT_MS def parentSpan = TEST_TRACER.getCurrentSpan() def stream = connection.inputStream assert TEST_TRACER.getCurrentSpan() == parentSpan diff --git a/instrumentation/http-url-connection/src/test/groovy/SpringRestTemplateTest.groovy b/instrumentation/http-url-connection/src/test/groovy/SpringRestTemplateTest.groovy index 5439543c7b..e97bd6f2da 100644 --- a/instrumentation/http-url-connection/src/test/groovy/SpringRestTemplateTest.groovy +++ b/instrumentation/http-url-connection/src/test/groovy/SpringRestTemplateTest.groovy @@ -18,13 +18,24 @@ import org.springframework.http.HttpEntity import org.springframework.http.HttpHeaders import org.springframework.http.HttpMethod import org.springframework.http.ResponseEntity +import org.springframework.http.client.ClientHttpRequestFactory +import org.springframework.http.client.SimpleClientHttpRequestFactory import org.springframework.web.client.RestTemplate import spock.lang.Shared +import spock.lang.Timeout +@Timeout(5) class SpringRestTemplateTest extends HttpClientTest { @Shared - RestTemplate restTemplate = new RestTemplate() + ClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory() + @Shared + RestTemplate restTemplate = new RestTemplate(factory) + + def setupSpec() { + factory.connectTimeout = CONNECT_TIMEOUT_MS + factory.readTimeout = READ_TIMEOUT_MS + } @Override int doRequest(String method, URI uri, Map headers, Closure callback) { @@ -45,4 +56,10 @@ class SpringRestTemplateTest extends HttpClientTest { boolean testConnectionFailure() { false } + + @Override + boolean testRemoteConnection() { + // FIXME: exception wrapped in ResourceAccessException + return false + } } diff --git a/instrumentation/jaxrs-client/jaxrs-client-1.1/src/test/groovy/JaxRsClientV1Test.groovy b/instrumentation/jaxrs-client/jaxrs-client-1.1/src/test/groovy/JaxRsClientV1Test.groovy index a2ba64df6b..e213088316 100644 --- a/instrumentation/jaxrs-client/jaxrs-client-1.1/src/test/groovy/JaxRsClientV1Test.groovy +++ b/instrumentation/jaxrs-client/jaxrs-client-1.1/src/test/groovy/JaxRsClientV1Test.groovy @@ -19,13 +19,17 @@ import com.sun.jersey.api.client.filter.GZIPContentEncodingFilter import com.sun.jersey.api.client.filter.LoggingFilter import io.opentelemetry.auto.test.base.HttpClientTest import spock.lang.Shared +import spock.lang.Timeout +@Timeout(5) class JaxRsClientV1Test extends HttpClientTest { @Shared Client client = Client.create() def setupSpec() { + client.setConnectTimeout(CONNECT_TIMEOUT_MS) + client.setReadTimeout(READ_TIMEOUT_MS) // Add filters to ensure spans aren't duplicated. client.addFilter(new LoggingFilter()) client.addFilter(new GZIPContentEncodingFilter()) diff --git a/instrumentation/jaxrs-client/jaxrs-client-2.0/jaxrs-client-2.0.gradle b/instrumentation/jaxrs-client/jaxrs-client-2.0/jaxrs-client-2.0.gradle index 7c92113657..298c51d693 100644 --- a/instrumentation/jaxrs-client/jaxrs-client-2.0/jaxrs-client-2.0.gradle +++ b/instrumentation/jaxrs-client/jaxrs-client-2.0/jaxrs-client-2.0.gradle @@ -32,7 +32,8 @@ dependencies { testCompile group: 'javax.ws.rs', name: 'javax.ws.rs-api', version: '2.0.1' testCompile group: 'org.glassfish.jersey.core', name: 'jersey-client', version: '2.0' - testCompile group: 'org.jboss.resteasy', name: 'resteasy-client', version: '3.0.0.Final' + testCompile group: 'org.jboss.resteasy', name: 'resteasy-client', version: '3.0.5.Final' + // ^ This version has timeouts https://issues.redhat.com/browse/RESTEASY-975 testCompile group: 'org.apache.cxf', name: 'cxf-rt-rs-client', version: '3.1.0' // Doesn't work with CXF 3.0.x because their context is wrong: // https://github.com/apache/cxf/commit/335c7bad2436f08d6d54180212df5a52157c9f21 diff --git a/instrumentation/jaxrs-client/jaxrs-client-2.0/src/test/groovy/JaxRsClientAsyncTest.groovy b/instrumentation/jaxrs-client/jaxrs-client-2.0/src/test/groovy/JaxRsClientAsyncTest.groovy index 410511ebd4..c91936b06a 100644 --- a/instrumentation/jaxrs-client/jaxrs-client-2.0/src/test/groovy/JaxRsClientAsyncTest.groovy +++ b/instrumentation/jaxrs-client/jaxrs-client-2.0/src/test/groovy/JaxRsClientAsyncTest.groovy @@ -15,8 +15,11 @@ */ import io.opentelemetry.auto.test.base.HttpClientTest import org.apache.cxf.jaxrs.client.spec.ClientBuilderImpl +import org.glassfish.jersey.client.ClientConfig +import org.glassfish.jersey.client.ClientProperties import org.glassfish.jersey.client.JerseyClientBuilder import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder +import spock.lang.Timeout import javax.ws.rs.client.AsyncInvoker import javax.ws.rs.client.Client @@ -27,6 +30,7 @@ import javax.ws.rs.client.WebTarget import javax.ws.rs.core.MediaType import javax.ws.rs.core.Response import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit abstract class JaxRsClientAsyncTest extends HttpClientTest { @@ -61,11 +65,15 @@ abstract class JaxRsClientAsyncTest extends HttpClientTest { abstract ClientBuilder builder() } +@Timeout(5) class JerseyClientAsyncTest extends JaxRsClientAsyncTest { @Override ClientBuilder builder() { - return new JerseyClientBuilder() + ClientConfig config = new ClientConfig() + config.property(ClientProperties.CONNECT_TIMEOUT, CONNECT_TIMEOUT_MS) + config.property(ClientProperties.READ_TIMEOUT, READ_TIMEOUT_MS) + return new JerseyClientBuilder().withConfig(config) } boolean testCircularRedirects() { @@ -73,11 +81,14 @@ class JerseyClientAsyncTest extends JaxRsClientAsyncTest { } } +@Timeout(5) class ResteasyClientAsyncTest extends JaxRsClientAsyncTest { @Override ClientBuilder builder() { return new ResteasyClientBuilder() + .establishConnectionTimeout(CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS) + .socketTimeout(READ_TIMEOUT_MS, TimeUnit.MILLISECONDS) } boolean testRedirects() { @@ -85,6 +96,7 @@ class ResteasyClientAsyncTest extends JaxRsClientAsyncTest { } } +@Timeout(5) class CxfClientAsyncTest extends JaxRsClientAsyncTest { @Override @@ -99,4 +111,9 @@ class CxfClientAsyncTest extends JaxRsClientAsyncTest { boolean testConnectionFailure() { false } + + boolean testRemoteConnection() { + // FIXME: span not reported correctly. + false + } } diff --git a/instrumentation/jaxrs-client/jaxrs-client-2.0/src/test/groovy/JaxRsClientTest.groovy b/instrumentation/jaxrs-client/jaxrs-client-2.0/src/test/groovy/JaxRsClientTest.groovy index e763e13714..785bba5a62 100644 --- a/instrumentation/jaxrs-client/jaxrs-client-2.0/src/test/groovy/JaxRsClientTest.groovy +++ b/instrumentation/jaxrs-client/jaxrs-client-2.0/src/test/groovy/JaxRsClientTest.groovy @@ -15,8 +15,11 @@ */ import io.opentelemetry.auto.test.base.HttpClientTest import org.apache.cxf.jaxrs.client.spec.ClientBuilderImpl +import org.glassfish.jersey.client.ClientConfig +import org.glassfish.jersey.client.ClientProperties import org.glassfish.jersey.client.JerseyClientBuilder import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder +import spock.lang.Timeout import javax.ws.rs.client.Client import javax.ws.rs.client.ClientBuilder @@ -25,6 +28,7 @@ import javax.ws.rs.client.Invocation import javax.ws.rs.client.WebTarget import javax.ws.rs.core.MediaType import javax.ws.rs.core.Response +import java.util.concurrent.TimeUnit abstract class JaxRsClientTest extends HttpClientTest { @@ -45,11 +49,15 @@ abstract class JaxRsClientTest extends HttpClientTest { abstract ClientBuilder builder() } +@Timeout(5) class JerseyClientTest extends JaxRsClientTest { @Override ClientBuilder builder() { - return new JerseyClientBuilder() + ClientConfig config = new ClientConfig() + config.property(ClientProperties.CONNECT_TIMEOUT, CONNECT_TIMEOUT_MS) + config.property(ClientProperties.READ_TIMEOUT, READ_TIMEOUT_MS) + return new JerseyClientBuilder().withConfig(config) } boolean testCircularRedirects() { @@ -57,24 +65,29 @@ class JerseyClientTest extends JaxRsClientTest { } } +@Timeout(5) class ResteasyClientTest extends JaxRsClientTest { @Override ClientBuilder builder() { return new ResteasyClientBuilder() + .establishConnectionTimeout(CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS) + .socketTimeout(READ_TIMEOUT_MS, TimeUnit.MILLISECONDS) } boolean testRedirects() { false } - } +@Timeout(5) class CxfClientTest extends JaxRsClientTest { @Override ClientBuilder builder() { return new ClientBuilderImpl() +// .property(ClientImpl.HTTP_CONNECTION_TIMEOUT_PROP, (long) CONNECT_TIMEOUT_MS) +// .property(ClientImpl.HTTP_RECEIVE_TIMEOUT_PROP, (long) READ_TIMEOUT_MS) } boolean testRedirects() { @@ -84,4 +97,9 @@ class CxfClientTest extends JaxRsClientTest { boolean testConnectionFailure() { false } + + boolean testRemoteConnection() { + // FIXME: span not reported correctly. + false + } } diff --git a/instrumentation/netty/netty-3.8/src/latestDepTest/groovy/Netty38ClientTest.groovy b/instrumentation/netty/netty-3.8/src/latestDepTest/groovy/Netty38ClientTest.groovy index cad1cf8bc0..ff54900ec5 100644 --- a/instrumentation/netty/netty-3.8/src/latestDepTest/groovy/Netty38ClientTest.groovy +++ b/instrumentation/netty/netty-3.8/src/latestDepTest/groovy/Netty38ClientTest.groovy @@ -17,7 +17,6 @@ import com.ning.http.client.AsyncCompletionHandler import com.ning.http.client.AsyncHttpClient import com.ning.http.client.AsyncHttpClientConfig import com.ning.http.client.Response -import io.opentelemetry.auto.instrumentation.api.Tags import io.opentelemetry.auto.test.base.HttpClientTest import spock.lang.AutoCleanup import spock.lang.Shared @@ -65,6 +64,11 @@ class Netty38ClientTest extends HttpClientTest { false } + @Override + boolean testRemoteConnection() { + return false + } + def "connection error (unopened port)"() { given: def uri = new URI("http://127.0.0.1:$UNUSABLE_PORT/") @@ -88,7 +92,6 @@ class Netty38ClientTest extends HttpClientTest { childOf span(0) errored true tags { - "$Tags.COMPONENT" "netty" Class errorClass = ConnectException try { errorClass = Class.forName('io.netty.channel.AbstractChannel$AnnotatedConnectException') diff --git a/instrumentation/netty/netty-3.8/src/latestDepTest/groovy/Netty38ServerTest.groovy b/instrumentation/netty/netty-3.8/src/latestDepTest/groovy/Netty38ServerTest.groovy index 6382a76238..52e71e40b8 100644 --- a/instrumentation/netty/netty-3.8/src/latestDepTest/groovy/Netty38ServerTest.groovy +++ b/instrumentation/netty/netty-3.8/src/latestDepTest/groovy/Netty38ServerTest.groovy @@ -17,9 +17,9 @@ import io.opentelemetry.auto.test.base.HttpServerTest import org.jboss.netty.bootstrap.ServerBootstrap import org.jboss.netty.buffer.ChannelBuffer import org.jboss.netty.buffer.ChannelBuffers -import org.jboss.netty.channel.Channel import org.jboss.netty.channel.ChannelHandlerContext import org.jboss.netty.channel.ChannelPipeline +import org.jboss.netty.channel.ChannelPipelineFactory import org.jboss.netty.channel.DefaultChannelPipeline import org.jboss.netty.channel.DownstreamMessageEvent import org.jboss.netty.channel.ExceptionEvent @@ -35,6 +35,8 @@ import org.jboss.netty.handler.codec.http.HttpResponseStatus import org.jboss.netty.handler.codec.http.HttpServerCodec import org.jboss.netty.handler.logging.LoggingHandler import org.jboss.netty.logging.InternalLogLevel +import org.jboss.netty.logging.InternalLoggerFactory +import org.jboss.netty.logging.Slf4JLoggerFactory import org.jboss.netty.util.CharsetUtil import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.ERROR @@ -49,10 +51,17 @@ import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.LOCATION import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1 -class Netty38ServerTest extends HttpServerTest { +class Netty38ServerTest extends HttpServerTest { + + static final LoggingHandler LOGGING_HANDLER + static { + InternalLoggerFactory.setDefaultFactory(new Slf4JLoggerFactory()) + LOGGING_HANDLER = new LoggingHandler(SERVER_LOGGER.name, InternalLogLevel.DEBUG, true) + } ChannelPipeline channelPipeline() { ChannelPipeline channelPipeline = new DefaultChannelPipeline() + channelPipeline.addFirst("logger", LOGGING_HANDLER) channelPipeline.addLast("http-codec", new HttpServerCodec()) channelPipeline.addLast("controller", new SimpleChannelHandler() { @@ -103,7 +112,8 @@ class Netty38ServerTest extends HttpServerTest { @Override void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent ex) throws Exception { - ChannelBuffer buffer = ChannelBuffers.copiedBuffer(ex.getCause().getMessage(), CharsetUtil.UTF_8) + def message = ex.getCause() == null ? " " + ex.message : ex.cause.message == null ? "" : ex.cause.message + ChannelBuffer buffer = ChannelBuffers.copiedBuffer(message, CharsetUtil.UTF_8) HttpResponse response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.INTERNAL_SERVER_ERROR) response.setContent(buffer) response.headers().set(CONTENT_TYPE, "text/plain") @@ -120,17 +130,23 @@ class Netty38ServerTest extends HttpServerTest { } @Override - Channel startServer(int port) { + ServerBootstrap startServer(int port) { ServerBootstrap bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory()) - bootstrap.setParentHandler(new LoggingHandler(InternalLogLevel.INFO)) - bootstrap.setPipeline(channelPipeline()) + bootstrap.setParentHandler(LOGGING_HANDLER) + bootstrap.setPipelineFactory(new ChannelPipelineFactory() { + @Override + ChannelPipeline getPipeline() throws Exception { + return channelPipeline() + } + }) InetSocketAddress address = new InetSocketAddress(port) - return bootstrap.bind(address) + bootstrap.bind(address) + return bootstrap } @Override - void stopServer(Channel server) { - server?.disconnect() + void stopServer(ServerBootstrap server) { + server?.shutdown() } } diff --git a/instrumentation/netty/netty-3.8/src/main/java/io/opentelemetry/auto/instrumentation/netty/v3_8/ChannelFutureListenerInstrumentation.java b/instrumentation/netty/netty-3.8/src/main/java/io/opentelemetry/auto/instrumentation/netty/v3_8/ChannelFutureListenerInstrumentation.java index 239d296f9f..8218157930 100644 --- a/instrumentation/netty/netty-3.8/src/main/java/io/opentelemetry/auto/instrumentation/netty/v3_8/ChannelFutureListenerInstrumentation.java +++ b/instrumentation/netty/netty-3.8/src/main/java/io/opentelemetry/auto/instrumentation/netty/v3_8/ChannelFutureListenerInstrumentation.java @@ -26,7 +26,6 @@ import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import com.google.auto.service.AutoService; import io.opentelemetry.auto.bootstrap.ContextStore; import io.opentelemetry.auto.bootstrap.InstrumentationContext; -import io.opentelemetry.auto.instrumentation.api.Tags; import io.opentelemetry.auto.instrumentation.netty.v3_8.server.NettyHttpServerDecorator; import io.opentelemetry.auto.tooling.Instrumenter; import io.opentelemetry.context.Scope; @@ -113,11 +112,7 @@ public class ChannelFutureListenerInstrumentation extends Instrumenter.Default { final Scope parentScope = NettyHttpServerDecorator.TRACER.withSpan(continuation); final Span errorSpan = - NettyHttpServerDecorator.TRACER - .spanBuilder("CONNECT") - .setSpanKind(CLIENT) - .setAttribute(Tags.COMPONENT, "netty") - .startSpan(); + NettyHttpServerDecorator.TRACER.spanBuilder("CONNECT").setSpanKind(CLIENT).startSpan(); try (final Scope scope = NettyHttpServerDecorator.TRACER.withSpan(errorSpan)) { NettyHttpServerDecorator.DECORATE.onError(errorSpan, cause); NettyHttpServerDecorator.DECORATE.beforeFinish(errorSpan); diff --git a/instrumentation/netty/netty-3.8/src/test/groovy/Netty38ClientTest.groovy b/instrumentation/netty/netty-3.8/src/test/groovy/Netty38ClientTest.groovy index 9f0c503456..7329746a9f 100644 --- a/instrumentation/netty/netty-3.8/src/test/groovy/Netty38ClientTest.groovy +++ b/instrumentation/netty/netty-3.8/src/test/groovy/Netty38ClientTest.groovy @@ -17,7 +17,6 @@ import com.ning.http.client.AsyncCompletionHandler import com.ning.http.client.AsyncHttpClient import com.ning.http.client.AsyncHttpClientConfig import com.ning.http.client.Response -import io.opentelemetry.auto.instrumentation.api.Tags import io.opentelemetry.auto.test.base.HttpClientTest import spock.lang.AutoCleanup import spock.lang.Shared @@ -65,6 +64,11 @@ class Netty38ClientTest extends HttpClientTest { false } + @Override + boolean testRemoteConnection() { + return false + } + def "connection error (unopened port)"() { given: def uri = new URI("http://127.0.0.1:$UNUSABLE_PORT/") @@ -88,7 +92,6 @@ class Netty38ClientTest extends HttpClientTest { childOf span(0) errored true tags { - "$Tags.COMPONENT" "netty" Class errorClass = ConnectException try { errorClass = Class.forName('io.netty.channel.AbstractChannel$AnnotatedConnectException') diff --git a/instrumentation/netty/netty-3.8/src/test/groovy/Netty38ServerTest.groovy b/instrumentation/netty/netty-3.8/src/test/groovy/Netty38ServerTest.groovy index 6382a76238..f9061253a4 100644 --- a/instrumentation/netty/netty-3.8/src/test/groovy/Netty38ServerTest.groovy +++ b/instrumentation/netty/netty-3.8/src/test/groovy/Netty38ServerTest.groovy @@ -17,9 +17,9 @@ import io.opentelemetry.auto.test.base.HttpServerTest import org.jboss.netty.bootstrap.ServerBootstrap import org.jboss.netty.buffer.ChannelBuffer import org.jboss.netty.buffer.ChannelBuffers -import org.jboss.netty.channel.Channel import org.jboss.netty.channel.ChannelHandlerContext import org.jboss.netty.channel.ChannelPipeline +import org.jboss.netty.channel.ChannelPipelineFactory import org.jboss.netty.channel.DefaultChannelPipeline import org.jboss.netty.channel.DownstreamMessageEvent import org.jboss.netty.channel.ExceptionEvent @@ -35,6 +35,8 @@ import org.jboss.netty.handler.codec.http.HttpResponseStatus import org.jboss.netty.handler.codec.http.HttpServerCodec import org.jboss.netty.handler.logging.LoggingHandler import org.jboss.netty.logging.InternalLogLevel +import org.jboss.netty.logging.InternalLoggerFactory +import org.jboss.netty.logging.Slf4JLoggerFactory import org.jboss.netty.util.CharsetUtil import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.ERROR @@ -49,10 +51,17 @@ import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.LOCATION import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1 -class Netty38ServerTest extends HttpServerTest { +class Netty38ServerTest extends HttpServerTest { + + static final LoggingHandler LOGGING_HANDLER + static { + InternalLoggerFactory.setDefaultFactory(new Slf4JLoggerFactory()) + LOGGING_HANDLER = new LoggingHandler(SERVER_LOGGER.name, InternalLogLevel.DEBUG, true) + } ChannelPipeline channelPipeline() { ChannelPipeline channelPipeline = new DefaultChannelPipeline() + channelPipeline.addFirst("logger", LOGGING_HANDLER) channelPipeline.addLast("http-codec", new HttpServerCodec()) channelPipeline.addLast("controller", new SimpleChannelHandler() { @@ -103,7 +112,8 @@ class Netty38ServerTest extends HttpServerTest { @Override void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent ex) throws Exception { - ChannelBuffer buffer = ChannelBuffers.copiedBuffer(ex.getCause().getMessage(), CharsetUtil.UTF_8) + def message = ex.cause == null ? " " + ex.message : ex.cause.message == null ? "" : ex.cause.message + ChannelBuffer buffer = ChannelBuffers.copiedBuffer(message, CharsetUtil.UTF_8) HttpResponse response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.INTERNAL_SERVER_ERROR) response.setContent(buffer) response.headers().set(CONTENT_TYPE, "text/plain") @@ -120,17 +130,23 @@ class Netty38ServerTest extends HttpServerTest { } @Override - Channel startServer(int port) { + ServerBootstrap startServer(int port) { ServerBootstrap bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory()) - bootstrap.setParentHandler(new LoggingHandler(InternalLogLevel.INFO)) - bootstrap.setPipeline(channelPipeline()) + bootstrap.setParentHandler(LOGGING_HANDLER) + bootstrap.setPipelineFactory(new ChannelPipelineFactory() { + @Override + ChannelPipeline getPipeline() throws Exception { + return channelPipeline() + } + }) InetSocketAddress address = new InetSocketAddress(port) - return bootstrap.bind(address) + bootstrap.bind(address) + return bootstrap } @Override - void stopServer(Channel server) { - server?.disconnect() + void stopServer(ServerBootstrap server) { + server?.shutdown() } } diff --git a/instrumentation/netty/netty-4.0/src/test/groovy/Netty40ClientTest.groovy b/instrumentation/netty/netty-4.0/src/test/groovy/Netty40ClientTest.groovy index 3766fcdfe5..059465ee6c 100644 --- a/instrumentation/netty/netty-4.0/src/test/groovy/Netty40ClientTest.groovy +++ b/instrumentation/netty/netty-4.0/src/test/groovy/Netty40ClientTest.groovy @@ -20,6 +20,7 @@ import org.asynchttpclient.DefaultAsyncHttpClientConfig import org.asynchttpclient.Response import spock.lang.AutoCleanup import spock.lang.Shared +import spock.lang.Timeout import java.util.concurrent.ExecutionException import java.util.concurrent.TimeUnit @@ -29,6 +30,7 @@ import static io.opentelemetry.auto.test.utils.TraceUtils.basicSpan import static io.opentelemetry.auto.test.utils.TraceUtils.runUnderTrace import static org.asynchttpclient.Dsl.asyncHttpClient +@Timeout(5) class Netty40ClientTest extends HttpClientTest { @Shared @@ -62,6 +64,11 @@ class Netty40ClientTest extends HttpClientTest { false } + @Override + boolean testRemoteConnection() { + return false + } + def "connection error (unopened port)"() { given: def uri = new URI("http://127.0.0.1:$UNUSABLE_PORT/") // Use numeric address to avoid ipv4/ipv6 confusion diff --git a/instrumentation/netty/netty-4.0/src/test/groovy/Netty40ServerTest.groovy b/instrumentation/netty/netty-4.0/src/test/groovy/Netty40ServerTest.groovy index b301fbe768..19d9ef7314 100644 --- a/instrumentation/netty/netty-4.0/src/test/groovy/Netty40ServerTest.groovy +++ b/instrumentation/netty/netty-4.0/src/test/groovy/Netty40ServerTest.groovy @@ -48,16 +48,20 @@ import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.SUCC class Netty40ServerTest extends HttpServerTest { + static final LoggingHandler LOGGING_HANDLER = new LoggingHandler(SERVER_LOGGER.name, LogLevel.DEBUG) + @Override EventLoopGroup startServer(int port) { def eventLoopGroup = new NioEventLoopGroup() ServerBootstrap bootstrap = new ServerBootstrap() .group(eventLoopGroup) - .handler(new LoggingHandler(LogLevel.INFO)) + .handler(LOGGING_HANDLER) .childHandler([ initChannel: { ch -> ChannelPipeline pipeline = ch.pipeline() + pipeline.addFirst("logger", LOGGING_HANDLER) + def handlers = [new HttpRequestDecoder(), new HttpResponseEncoder()] handlers.each { pipeline.addLast(it) } pipeline.addLast([ diff --git a/instrumentation/netty/netty-4.1/src/test/groovy/Netty41ClientTest.groovy b/instrumentation/netty/netty-4.1/src/test/groovy/Netty41ClientTest.groovy index 9c9b0b35c0..f01bf63db8 100644 --- a/instrumentation/netty/netty-4.1/src/test/groovy/Netty41ClientTest.groovy +++ b/instrumentation/netty/netty-4.1/src/test/groovy/Netty41ClientTest.groovy @@ -28,6 +28,7 @@ import org.asynchttpclient.DefaultAsyncHttpClientConfig import org.asynchttpclient.Response import spock.lang.Retry import spock.lang.Shared +import spock.lang.Timeout import java.util.concurrent.ExecutionException import java.util.concurrent.TimeUnit @@ -39,6 +40,7 @@ import static io.opentelemetry.auto.test.utils.TraceUtils.runUnderTrace import static org.asynchttpclient.Dsl.asyncHttpClient @Retry +@Timeout(5) class Netty41ClientTest extends HttpClientTest { @Shared @@ -71,6 +73,11 @@ class Netty41ClientTest extends HttpClientTest { false } + @Override + boolean testRemoteConnection() { + return false + } + def "connection error (unopened port)"() { given: def uri = new URI("http://localhost:$UNUSABLE_PORT/") diff --git a/instrumentation/netty/netty-4.1/src/test/groovy/Netty41ServerTest.groovy b/instrumentation/netty/netty-4.1/src/test/groovy/Netty41ServerTest.groovy index c0175745c8..7cb79841d8 100644 --- a/instrumentation/netty/netty-4.1/src/test/groovy/Netty41ServerTest.groovy +++ b/instrumentation/netty/netty-4.1/src/test/groovy/Netty41ServerTest.groovy @@ -47,16 +47,20 @@ import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.SUCC class Netty41ServerTest extends HttpServerTest { + static final LoggingHandler LOGGING_HANDLER = new LoggingHandler(SERVER_LOGGER.name, LogLevel.DEBUG) + @Override EventLoopGroup startServer(int port) { def eventLoopGroup = new NioEventLoopGroup() ServerBootstrap bootstrap = new ServerBootstrap() .group(eventLoopGroup) - .handler(new LoggingHandler(LogLevel.INFO)) + .handler(LOGGING_HANDLER) .childHandler([ initChannel: { ch -> ChannelPipeline pipeline = ch.pipeline() + pipeline.addFirst("logger", LOGGING_HANDLER) + def handlers = [new HttpServerCodec()] handlers.each { pipeline.addLast(it) } pipeline.addLast([ diff --git a/instrumentation/okhttp-3.0/src/test/groovy/OkHttp3Test.groovy b/instrumentation/okhttp-3.0/src/test/groovy/OkHttp3Test.groovy index f000061997..ea208234a1 100644 --- a/instrumentation/okhttp-3.0/src/test/groovy/OkHttp3Test.groovy +++ b/instrumentation/okhttp-3.0/src/test/groovy/OkHttp3Test.groovy @@ -20,10 +20,18 @@ import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody import okhttp3.internal.http.HttpMethod +import spock.lang.Timeout +import java.util.concurrent.TimeUnit + +@Timeout(5) class OkHttp3Test extends HttpClientTest { - def client = new OkHttpClient() + def client = new OkHttpClient.Builder() + .connectTimeout(CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS) + .readTimeout(READ_TIMEOUT_MS, TimeUnit.MILLISECONDS) + .writeTimeout(READ_TIMEOUT_MS, TimeUnit.MILLISECONDS) + .build() @Override int doRequest(String method, URI uri, Map headers, Closure callback) { diff --git a/instrumentation/play-ws/play-ws-2.0/src/test/groovy/PlayWSClientTest.groovy b/instrumentation/play-ws/play-ws-2.0/src/test/groovy/PlayWSClientTest.groovy index 1f3466656e..08176f747d 100644 --- a/instrumentation/play-ws/play-ws-2.0/src/test/groovy/PlayWSClientTest.groovy +++ b/instrumentation/play-ws/play-ws-2.0/src/test/groovy/PlayWSClientTest.groovy @@ -23,9 +23,11 @@ import scala.concurrent.ExecutionContext import scala.concurrent.Future import scala.concurrent.duration.Duration import spock.lang.Shared +import spock.lang.Timeout import java.util.concurrent.TimeUnit +@Timeout(5) class PlayJavaWSClientTest extends PlayWSClientTestBase { @Shared StandaloneWSClient wsClient @@ -52,6 +54,7 @@ class PlayJavaWSClientTest extends PlayWSClientTestBase { } } +@Timeout(5) class PlayJavaStreamedWSClientTest extends PlayWSClientTestBase { @Shared StandaloneWSClient wsClient @@ -81,6 +84,7 @@ class PlayJavaStreamedWSClientTest extends PlayWSClientTestBase { } } +@Timeout(5) class PlayScalaWSClientTest extends PlayWSClientTestBase { @Shared play.api.libs.ws.StandaloneWSClient wsClient @@ -111,6 +115,7 @@ class PlayScalaWSClientTest extends PlayWSClientTestBase { } } +@Timeout(5) class PlayScalaStreamedWSClientTest extends PlayWSClientTestBase { @Shared play.api.libs.ws.StandaloneWSClient wsClient diff --git a/instrumentation/play/play-2.3/.gitignore b/instrumentation/play/play-2.3/.gitignore new file mode 100644 index 0000000000..5292519a25 --- /dev/null +++ b/instrumentation/play/play-2.3/.gitignore @@ -0,0 +1 @@ +logs/ \ No newline at end of file diff --git a/instrumentation/play/play-2.3/play-2.3.gradle b/instrumentation/play/play-2.3/play-2.3.gradle new file mode 100644 index 0000000000..96e14c948b --- /dev/null +++ b/instrumentation/play/play-2.3/play-2.3.gradle @@ -0,0 +1,57 @@ +ext { + minJavaVersionForTests = JavaVersion.VERSION_1_8 + // Play doesn't work with Java 9+ until 2.6.12 + maxJavaVersionForTests = JavaVersion.VERSION_1_8 +} + +apply from: "${rootDir}/gradle/instrumentation.gradle" +apply from: "${rootDir}/gradle/test-with-scala.gradle" +apply plugin: 'org.unbroken-dome.test-sets' + +muzzle { + pass { + group = 'com.typesafe.play' + module = 'play_2.11' + versions = '[2.3.0,2.4)' + assertInverse = true + } + fail { + group = 'com.typesafe.play' + module = 'play_2.12' + versions = '[,]' + } + fail { + group = 'com.typesafe.play' + module = 'play_2.13' + versions = '[,]' + } +} + +testSets { + latestDepTest { + dirName = 'test' + } +} + +dependencies { + main_java8Compile group: 'com.typesafe.play', name: 'play_2.11', version: '2.3.0' + + testCompile project(':instrumentation:netty:netty-3.8') + + testCompile group: 'com.typesafe.play', name: 'play-java_2.11', version: '2.3.0' + testCompile group: 'com.typesafe.play', name: 'play-java-ws_2.11', version: '2.3.0' + testCompile(group: 'com.typesafe.play', name: 'play-test_2.11', version: '2.3.0') { + exclude group: 'org.eclipse.jetty', module: 'jetty-websocket' + } + + latestDepTestCompile group: 'com.typesafe.play', name: 'play-java_2.11', version: '2.3.+' + latestDepTestCompile group: 'com.typesafe.play', name: 'play-java-ws_2.11', version: '2.3.+' + latestDepTestCompile(group: 'com.typesafe.play', name: 'play-test_2.11', version: '2.3.+') { + exclude group: 'org.eclipse.jetty', module: 'jetty-websocket' + } +} + +compileLatestDepTestGroovy { + classpath = classpath.plus(files(compileLatestDepTestScala.destinationDir)) + dependsOn compileLatestDepTestScala +} diff --git a/instrumentation/play/play-2.3/src/main/java/io/opentelemetry/auto/instrumentation/play/v2_3/PlayInstrumentation.java b/instrumentation/play/play-2.3/src/main/java/io/opentelemetry/auto/instrumentation/play/v2_3/PlayInstrumentation.java new file mode 100644 index 0000000000..6a346543f6 --- /dev/null +++ b/instrumentation/play/play-2.3/src/main/java/io/opentelemetry/auto/instrumentation/play/v2_3/PlayInstrumentation.java @@ -0,0 +1,67 @@ +/* + * Copyright 2020, OpenTelemetry Authors + * + * Licensed 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 io.opentelemetry.auto.instrumentation.play.v2_3; + +import static io.opentelemetry.auto.tooling.ClassLoaderMatcher.hasClassesNamed; +import static io.opentelemetry.auto.tooling.bytebuddy.matcher.AgentElementMatchers.implementsInterface; +import static java.util.Collections.singletonMap; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.returns; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import com.google.auto.service.AutoService; +import io.opentelemetry.auto.tooling.Instrumenter; +import java.util.Map; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +@AutoService(Instrumenter.class) +public final class PlayInstrumentation extends Instrumenter.Default { + + public PlayInstrumentation() { + super("play", "play-action"); + } + + @Override + public ElementMatcher classLoaderMatcher() { + // Optimization for expensive typeMatcher. + return hasClassesNamed("play.api.mvc.Action"); + } + + @Override + public ElementMatcher typeMatcher() { + return implementsInterface(named("play.api.mvc.Action")); + } + + @Override + public String[] helperClassNames() { + return new String[] { + packageName + ".PlayHttpServerDecorator", + packageName + ".RequestCompleteCallback", + packageName + ".PlayHeaders", + }; + } + + @Override + public Map, String> transformers() { + return singletonMap( + named("apply") + .and(takesArgument(0, named("play.api.mvc.Request"))) + .and(returns(named("scala.concurrent.Future"))), + packageName + ".PlayAdvice"); + } +} diff --git a/instrumentation/play/play-2.3/src/main/java8/io/opentelemetry/auto/instrumentation/play/v2_3/PlayAdvice.java b/instrumentation/play/play-2.3/src/main/java8/io/opentelemetry/auto/instrumentation/play/v2_3/PlayAdvice.java new file mode 100644 index 0000000000..ef6d1532af --- /dev/null +++ b/instrumentation/play/play-2.3/src/main/java8/io/opentelemetry/auto/instrumentation/play/v2_3/PlayAdvice.java @@ -0,0 +1,82 @@ +/* + * Copyright 2020, OpenTelemetry Authors + * + * Licensed 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 io.opentelemetry.auto.instrumentation.play.v2_3; + +import static io.opentelemetry.auto.bootstrap.instrumentation.decorator.BaseDecorator.extract; +import static io.opentelemetry.auto.instrumentation.play.v2_3.PlayHeaders.GETTER; +import static io.opentelemetry.auto.instrumentation.play.v2_3.PlayHttpServerDecorator.DECORATE; +import static io.opentelemetry.auto.instrumentation.play.v2_3.PlayHttpServerDecorator.TRACER; +import static io.opentelemetry.trace.TracingContextUtils.currentContextWith; + +import io.opentelemetry.auto.instrumentation.api.SpanWithScope; +import io.opentelemetry.auto.instrumentation.api.Tags; +import io.opentelemetry.trace.Span; +import io.opentelemetry.trace.SpanContext; +import net.bytebuddy.asm.Advice; +import play.api.mvc.Action; +import play.api.mvc.Request; +import play.api.mvc.Result; +import scala.concurrent.Future; + +public class PlayAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static SpanWithScope onEnter(@Advice.Argument(0) final Request req) { + final Span.Builder spanBuilder = TRACER.spanBuilder("play.request"); + if (!TRACER.getCurrentSpan().getContext().isValid()) { + final SpanContext extractedContext = extract(req.headers(), GETTER); + if (extractedContext.isValid()) { + spanBuilder.setParent(extractedContext); + } + } else { + // An upstream framework (e.g. akka-http, netty) has already started the span. + // Do not extract the context. + } + final Span span = spanBuilder.startSpan(); + DECORATE.afterStart(span); + DECORATE.onConnection(span, req); + + return new SpanWithScope(span, currentContextWith(span)); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void stopTraceOnResponse( + @Advice.Enter final SpanWithScope playControllerScope, + @Advice.This final Object thisAction, + @Advice.Thrown final Throwable throwable, + @Advice.Argument(0) final Request req, + @Advice.Return(readOnly = false) final Future responseFuture) { + final Span playControllerSpan = playControllerScope.getSpan(); + + // Call onRequest on return after tags are populated. + DECORATE.onRequest(playControllerSpan, req); + + if (throwable == null) { + responseFuture.onComplete( + new RequestCompleteCallback(playControllerSpan), + ((Action) thisAction).executionContext()); + } else { + DECORATE.onError(playControllerSpan, throwable); + playControllerSpan.setAttribute(Tags.HTTP_STATUS, 500); + DECORATE.beforeFinish(playControllerSpan); + playControllerSpan.end(); + } + playControllerScope.closeScope(); + + final Span rootSpan = TRACER.getCurrentSpan(); + // set the resource name on the upstream akka/netty span + DECORATE.onRequest(rootSpan, req); + } +} diff --git a/instrumentation/play/play-2.3/src/main/java8/io/opentelemetry/auto/instrumentation/play/v2_3/PlayHeaders.java b/instrumentation/play/play-2.3/src/main/java8/io/opentelemetry/auto/instrumentation/play/v2_3/PlayHeaders.java new file mode 100644 index 0000000000..0c5788e226 --- /dev/null +++ b/instrumentation/play/play-2.3/src/main/java8/io/opentelemetry/auto/instrumentation/play/v2_3/PlayHeaders.java @@ -0,0 +1,35 @@ +/* + * Copyright 2020, OpenTelemetry Authors + * + * Licensed 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 io.opentelemetry.auto.instrumentation.play.v2_3; + +import io.opentelemetry.context.propagation.HttpTextFormat; +import play.api.mvc.Headers; +import scala.Option; + +public class PlayHeaders implements HttpTextFormat.Getter { + + public static final PlayHeaders GETTER = new PlayHeaders(); + + @Override + public String get(final Headers headers, final String key) { + final Option option = headers.get(key); + if (option.isDefined()) { + return option.get(); + } else { + return null; + } + } +} diff --git a/instrumentation/play/play-2.3/src/main/java8/io/opentelemetry/auto/instrumentation/play/v2_3/PlayHttpServerDecorator.java b/instrumentation/play/play-2.3/src/main/java8/io/opentelemetry/auto/instrumentation/play/v2_3/PlayHttpServerDecorator.java new file mode 100644 index 0000000000..545bf345e8 --- /dev/null +++ b/instrumentation/play/play-2.3/src/main/java8/io/opentelemetry/auto/instrumentation/play/v2_3/PlayHttpServerDecorator.java @@ -0,0 +1,100 @@ +/* + * Copyright 2020, OpenTelemetry Authors + * + * Licensed 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 io.opentelemetry.auto.instrumentation.play.v2_3; + +import io.opentelemetry.OpenTelemetry; +import io.opentelemetry.auto.bootstrap.instrumentation.decorator.HttpServerDecorator; +import io.opentelemetry.auto.instrumentation.api.Tags; +import io.opentelemetry.trace.Span; +import io.opentelemetry.trace.Tracer; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.UndeclaredThrowableException; +import java.net.URI; +import java.net.URISyntaxException; +import lombok.extern.slf4j.Slf4j; +import play.api.mvc.Request; +import play.api.mvc.Result; +import scala.Option; + +@Slf4j +public class PlayHttpServerDecorator extends HttpServerDecorator { + public static final PlayHttpServerDecorator DECORATE = new PlayHttpServerDecorator(); + + public static final Tracer TRACER = + OpenTelemetry.getTracerProvider().get("io.opentelemetry.auto.play-2.4"); + + @Override + protected String getComponentName() { + return "play-action"; + } + + @Override + protected String method(final Request httpRequest) { + return httpRequest.method(); + } + + @Override + protected URI url(final Request request) throws URISyntaxException { + return new URI((request.secure() ? "https://" : "http://") + request.host() + request.uri()); + } + + @Override + protected String peerHostIP(final Request request) { + return request.remoteAddress(); + } + + @Override + protected Integer peerPort(final Request request) { + return null; + } + + @Override + protected Integer status(final Result httpResponse) { + return httpResponse.header().status(); + } + + @Override + public Span onRequest(final Span span, final Request request) { + super.onRequest(span, request); + if (request != null) { + // more about routes here: + // https://github.com/playframework/playframework/blob/master/documentation/manual/releases/release26/migration26/Migration26.md#router-tags-are-now-attributes + final Option pathOption = request.tags().get("ROUTE_PATTERN"); + if (!pathOption.isEmpty()) { + final String path = (String) pathOption.get(); + span.updateName(request.method() + " " + path); + } + } + return span; + } + + @Override + public Span onError(final Span span, Throwable throwable) { + span.setAttribute(Tags.HTTP_STATUS, 500); + if (throwable != null + // This can be moved to instanceof check when using Java 8. + && throwable.getClass().getName().equals("java.util.concurrent.CompletionException") + && throwable.getCause() != null) { + throwable = throwable.getCause(); + } + while ((throwable instanceof InvocationTargetException + || throwable instanceof UndeclaredThrowableException) + && throwable.getCause() != null) { + throwable = throwable.getCause(); + } + return super.onError(span, throwable); + } +} diff --git a/instrumentation/play/play-2.3/src/main/java8/io/opentelemetry/auto/instrumentation/play/v2_3/RequestCompleteCallback.java b/instrumentation/play/play-2.3/src/main/java8/io/opentelemetry/auto/instrumentation/play/v2_3/RequestCompleteCallback.java new file mode 100644 index 0000000000..aff9b0652a --- /dev/null +++ b/instrumentation/play/play-2.3/src/main/java8/io/opentelemetry/auto/instrumentation/play/v2_3/RequestCompleteCallback.java @@ -0,0 +1,49 @@ +/* + * Copyright 2020, OpenTelemetry Authors + * + * Licensed 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 io.opentelemetry.auto.instrumentation.play.v2_3; + +import static io.opentelemetry.auto.instrumentation.play.v2_3.PlayHttpServerDecorator.DECORATE; + +import io.opentelemetry.trace.Span; +import lombok.extern.slf4j.Slf4j; +import play.api.mvc.Result; +import scala.util.Try; + +@Slf4j +public class RequestCompleteCallback extends scala.runtime.AbstractFunction1, Object> { + private final Span span; + + public RequestCompleteCallback(final Span span) { + this.span = span; + } + + @Override + public Object apply(final Try result) { + try { + if (result.isFailure()) { + DECORATE.onError(span, result.failed().get()); + } else { + DECORATE.onResponse(span, result.get()); + } + DECORATE.beforeFinish(span); + } catch (final Throwable t) { + log.debug("error in play instrumentation", t); + } finally { + span.end(); + } + return null; + } +} diff --git a/instrumentation/play/play-2.3/src/test/groovy/client/PlayWSClientTest.groovy b/instrumentation/play/play-2.3/src/test/groovy/client/PlayWSClientTest.groovy new file mode 100644 index 0000000000..abe1419e0d --- /dev/null +++ b/instrumentation/play/play-2.3/src/test/groovy/client/PlayWSClientTest.groovy @@ -0,0 +1,86 @@ +/* + * Copyright 2020, OpenTelemetry Authors + * + * Licensed 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 client + +import io.opentelemetry.auto.test.base.HttpClientTest +import play.GlobalSettings +import play.libs.ws.WS +import play.test.FakeApplication +import play.test.Helpers +import spock.lang.Shared + +import java.util.concurrent.TimeUnit + +class PlayWSClientTest extends HttpClientTest { + @Shared + def application = new FakeApplication( + new File("."), + FakeApplication.getClassLoader(), + [ + "ws.timeout.connection": CONNECT_TIMEOUT_MS, + "ws.timeout.request" : READ_TIMEOUT_MS + ], + Collections.emptyList(), + new GlobalSettings() + ) + + @Shared + def client + + def setupSpec() { + Helpers.start(application) + client = WS.client() + } + + def cleanupSpec() { + Helpers.stop(application) + } + + @Override + int doRequest(String method, URI uri, Map headers, Closure callback) { + def request = client.url(uri.toString()) + headers.entrySet().each { + request.setHeader(it.key, it.value) + } + + def status = request.execute(method).map({ + callback?.call() + it + }).map({ + it.status + }) + return status.get(1, TimeUnit.SECONDS) + } + + @Override + boolean testRedirects() { + false + } + + @Override + boolean testConnectionFailure() { + false + } + + @Override + boolean testRemoteConnection() { + // On connection failures the operation and resource names end up different from expected. + // This would require a lot of changes to the base client test class to support + // span.operationName = "netty.connect" + // span.resourceName = "netty.connect" + false + } +} diff --git a/instrumentation/play/play-2.3/src/test/groovy/server/NettyServerTestInstrumentation.java b/instrumentation/play/play-2.3/src/test/groovy/server/NettyServerTestInstrumentation.java new file mode 100644 index 0000000000..ccb7fe2546 --- /dev/null +++ b/instrumentation/play/play-2.3/src/test/groovy/server/NettyServerTestInstrumentation.java @@ -0,0 +1,38 @@ +/* + * Copyright 2020, OpenTelemetry Authors + * + * Licensed 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 server; + +import static net.bytebuddy.matcher.ElementMatchers.named; + +import com.google.auto.service.AutoService; +import io.opentelemetry.auto.test.base.HttpServerTestAdvice; +import io.opentelemetry.auto.tooling.Instrumenter; +import net.bytebuddy.agent.builder.AgentBuilder; + +@AutoService(Instrumenter.class) +public class NettyServerTestInstrumentation implements Instrumenter { + + @Override + public AgentBuilder instrument(final AgentBuilder agentBuilder) { + return agentBuilder + .type(named("org.jboss.netty.handler.codec.http.HttpRequestDecoder")) + .transform( + new AgentBuilder.Transformer.ForAdvice() + .advice( + named("createMessage"), + HttpServerTestAdvice.ServerEntryAdvice.class.getName())); + } +} diff --git a/instrumentation/play/play-2.3/src/test/groovy/server/PlayAsyncServerTest.groovy b/instrumentation/play/play-2.3/src/test/groovy/server/PlayAsyncServerTest.groovy new file mode 100644 index 0000000000..4438189112 --- /dev/null +++ b/instrumentation/play/play-2.3/src/test/groovy/server/PlayAsyncServerTest.groovy @@ -0,0 +1,27 @@ +/* + * Copyright 2020, OpenTelemetry Authors + * + * Licensed 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 server + +import play.api.test.TestServer + +class PlayAsyncServerTest extends PlayServerTest { + @Override + TestServer startServer(int port) { + def server = AsyncServer.server(port) + server.start() + return server + } +} diff --git a/instrumentation/play/play-2.3/src/test/groovy/server/PlayServerTest.groovy b/instrumentation/play/play-2.3/src/test/groovy/server/PlayServerTest.groovy new file mode 100644 index 0000000000..4d07d30f1e --- /dev/null +++ b/instrumentation/play/play-2.3/src/test/groovy/server/PlayServerTest.groovy @@ -0,0 +1,70 @@ +/* + * Copyright 2020, OpenTelemetry Authors + * + * Licensed 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 server + +import io.opentelemetry.auto.instrumentation.api.MoreTags +import io.opentelemetry.auto.instrumentation.api.Tags +import io.opentelemetry.auto.test.asserts.TraceAssert +import io.opentelemetry.auto.test.base.HttpServerTest +import io.opentelemetry.sdk.trace.data.SpanData +import play.api.test.TestServer + +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.ERROR +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.EXCEPTION +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.SUCCESS +import static io.opentelemetry.trace.Span.Kind.INTERNAL + +class PlayServerTest extends HttpServerTest { + @Override + TestServer startServer(int port) { + def server = SyncServer.server(port) + server.start() + return server + } + + @Override + void stopServer(TestServer server) { + server.stop() + } + + // We don't have instrumentation for this version of netty yet + @Override + boolean hasHandlerSpan() { + true + } + + @Override + void handlerSpan(TraceAssert trace, int index, Object parent, String method = "GET", ServerEndpoint endpoint = SUCCESS) { + trace.span(index) { + operationName "play.request" + spanKind INTERNAL + errored endpoint == ERROR || endpoint == EXCEPTION + childOf((SpanData) parent) + tags { + "$MoreTags.NET_PEER_IP" { it == null || it == "127.0.0.1" } // Optional + "$Tags.HTTP_URL" String + "$Tags.HTTP_METHOD" String + "$Tags.HTTP_STATUS" Long + if (endpoint == EXCEPTION) { + errorTags(Exception, EXCEPTION.body) + } + if (endpoint.query) { + "$MoreTags.HTTP_QUERY" endpoint.query + } + } + } + } +} diff --git a/instrumentation/play/play-2.3/src/test/scala/server/AsyncServer.scala b/instrumentation/play/play-2.3/src/test/scala/server/AsyncServer.scala new file mode 100644 index 0000000000..5ebf311ac2 --- /dev/null +++ b/instrumentation/play/play-2.3/src/test/scala/server/AsyncServer.scala @@ -0,0 +1,41 @@ +/* + * Copyright 2020, OpenTelemetry Authors + * + * Licensed 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 server + +import io.opentelemetry.auto.test.base.HttpServerTest +import io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint._ +import play.api.mvc.{Action, Handler, Results} +import play.api.test.{FakeApplication, TestServer} + +import scala.concurrent.Future + +object AsyncServer { + val routes: PartialFunction[(String, String), Handler] = { + case ("GET", "/success") => Action.async { request => HttpServerTest.controller(SUCCESS, new AsyncControllerClosureAdapter(Future.successful(Results.Status(SUCCESS.getStatus).apply(SUCCESS.getBody)))) } + case ("GET", "/redirect") => Action.async { request => HttpServerTest.controller(REDIRECT, new AsyncControllerClosureAdapter(Future.successful(Results.Redirect(REDIRECT.getBody, REDIRECT.getStatus)))) } + case ("GET", "/query") => Action.async { result => HttpServerTest.controller(QUERY_PARAM, new AsyncControllerClosureAdapter(Future.successful(Results.Status(QUERY_PARAM.getStatus).apply(QUERY_PARAM.getBody)))) } + case ("GET", "/error-status") => Action.async { result => HttpServerTest.controller(ERROR, new AsyncControllerClosureAdapter(Future.successful(Results.Status(ERROR.getStatus).apply(ERROR.getBody)))) } + case ("GET", "/exception") => Action.async { result => + HttpServerTest.controller(EXCEPTION, new AsyncBlockClosureAdapter(() => { + throw new Exception(EXCEPTION.getBody) + })) + } + } + + def server(port: Int): TestServer = { + TestServer(port, FakeApplication(withGlobal = Some(new Settings()), withRoutes = routes)) + } +} diff --git a/instrumentation/play/play-2.3/src/test/scala/server/ControllerClosureAdapter.scala b/instrumentation/play/play-2.3/src/test/scala/server/ControllerClosureAdapter.scala new file mode 100644 index 0000000000..18b9601e29 --- /dev/null +++ b/instrumentation/play/play-2.3/src/test/scala/server/ControllerClosureAdapter.scala @@ -0,0 +1,37 @@ +/* + * Copyright 2020, OpenTelemetry Authors + * + * Licensed 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 server + +import groovy.lang.Closure +import play.api.mvc.Result + +import scala.concurrent.Future + +class ControllerClosureAdapter(response: Result) extends Closure[Result] { + override def call(): Result = response +} + +class BlockClosureAdapter(block: () => Result) extends Closure[Result] { + override def call(): Result = block() +} + +class AsyncControllerClosureAdapter(response: Future[Result]) extends Closure[Future[Result]] { + override def call(): Future[Result] = response +} + +class AsyncBlockClosureAdapter(block: () => Future[Result]) extends Closure[Future[Result]] { + override def call(): Future[Result] = block() +} diff --git a/instrumentation/play/play-2.3/src/test/scala/server/Settings.scala b/instrumentation/play/play-2.3/src/test/scala/server/Settings.scala new file mode 100644 index 0000000000..05c3c5aea7 --- /dev/null +++ b/instrumentation/play/play-2.3/src/test/scala/server/Settings.scala @@ -0,0 +1,27 @@ +/* + * Copyright 2020, OpenTelemetry Authors + * + * Licensed 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 server + +import play.api.GlobalSettings +import play.api.mvc.{RequestHeader, Result, Results} + +import scala.concurrent.Future + +class Settings extends GlobalSettings { + override def onError(request: RequestHeader, ex: Throwable): Future[Result] = { + Future.successful(Results.InternalServerError(ex.getCause.getMessage)) + } +} diff --git a/instrumentation/play/play-2.3/src/test/scala/server/SyncServer.scala b/instrumentation/play/play-2.3/src/test/scala/server/SyncServer.scala new file mode 100644 index 0000000000..864e04e20f --- /dev/null +++ b/instrumentation/play/play-2.3/src/test/scala/server/SyncServer.scala @@ -0,0 +1,47 @@ +/* + * Copyright 2020, OpenTelemetry Authors + * + * Licensed 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 server + +import io.opentelemetry.auto.test.base.HttpServerTest +import io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint._ +import play.api.mvc.{Action, Handler, Results} +import play.api.test.{FakeApplication, TestServer} + +object SyncServer { + val routes: PartialFunction[(String, String), Handler] = { + case ("GET", "/success") => Action { request => + HttpServerTest.controller(SUCCESS, new ControllerClosureAdapter(Results.Status(SUCCESS.getStatus).apply(SUCCESS.getBody))) + } + case ("GET", "/redirect") => Action { request => + HttpServerTest.controller(REDIRECT, new ControllerClosureAdapter(Results.Redirect(REDIRECT.getBody, REDIRECT.getStatus))) + } + case ("GET", "/query") => Action { request => + HttpServerTest.controller(QUERY_PARAM, new ControllerClosureAdapter(Results.Status(QUERY_PARAM.getStatus).apply(QUERY_PARAM.getBody))) + } + case ("GET", "/error-status") => Action { request => + HttpServerTest.controller(ERROR, new ControllerClosureAdapter(Results.Status(ERROR.getStatus).apply(ERROR.getBody))) + } + case ("GET", "/exception") => Action { request => + HttpServerTest.controller(EXCEPTION, new BlockClosureAdapter(() => { + throw new Exception(EXCEPTION.getBody) + })) + } + } + + def server(port: Int): TestServer = { + TestServer(port, FakeApplication(withGlobal = Some(new Settings()), withRoutes = routes)) + } +} diff --git a/instrumentation/play/play-2.4/src/test/groovy/client/PlayWSClientTest.groovy b/instrumentation/play/play-2.4/src/test/groovy/client/PlayWSClientTest.groovy index 21e6b39869..3488c2469f 100644 --- a/instrumentation/play/play-2.4/src/test/groovy/client/PlayWSClientTest.groovy +++ b/instrumentation/play/play-2.4/src/test/groovy/client/PlayWSClientTest.groovy @@ -20,9 +20,11 @@ import play.libs.ws.WS import spock.lang.AutoCleanup import spock.lang.Shared import spock.lang.Subject +import spock.lang.Timeout // Play 2.6+ uses a separately versioned client that shades the underlying dependency // This means our built in instrumentation won't work. +@Timeout(5) class PlayWSClientTest extends HttpClientTest { @Subject @Shared @@ -54,4 +56,9 @@ class PlayWSClientTest extends HttpClientTest { boolean testConnectionFailure() { false } + + @Override + boolean testRemoteConnection() { + return false + } } diff --git a/instrumentation/ratpack-1.4/src/test/groovy/client/RatpackHttpClientTest.groovy b/instrumentation/ratpack-1.4/src/test/groovy/client/RatpackHttpClientTest.groovy index 3df3d50f36..004e7e4347 100644 --- a/instrumentation/ratpack-1.4/src/test/groovy/client/RatpackHttpClientTest.groovy +++ b/instrumentation/ratpack-1.4/src/test/groovy/client/RatpackHttpClientTest.groovy @@ -21,7 +21,11 @@ import ratpack.http.client.HttpClient import ratpack.test.exec.ExecHarness import spock.lang.AutoCleanup import spock.lang.Shared +import spock.lang.Timeout +import java.time.Duration + +@Timeout(5) class RatpackHttpClientTest extends HttpClientTest { @AutoCleanup @@ -29,12 +33,16 @@ class RatpackHttpClientTest extends HttpClientTest { ExecHarness exec = ExecHarness.harness() @Shared - def client = HttpClient.of {} + def client = HttpClient.of { + it.readTimeout(Duration.ofSeconds(2)) + // Connect timeout added in 1.5 + } @Override int doRequest(String method, URI uri, Map headers, Closure callback) { ExecResult result = exec.yield { def resp = client.request(uri) { spec -> + spec.connectTimeout(Duration.ofSeconds(2)) spec.method(method) spec.headers { headersSpec -> headers.entrySet().each { @@ -59,4 +67,9 @@ class RatpackHttpClientTest extends HttpClientTest { boolean testConnectionFailure() { false } + + @Override + boolean testRemoteConnection() { + return false + } } diff --git a/instrumentation/spring-webflux-5.0/src/test/groovy/client/SpringWebfluxHttpClientTest.groovy b/instrumentation/spring-webflux-5.0/src/test/groovy/client/SpringWebfluxHttpClientTest.groovy index ebbe275f2e..228afe1db5 100644 --- a/instrumentation/spring-webflux-5.0/src/test/groovy/client/SpringWebfluxHttpClientTest.groovy +++ b/instrumentation/spring-webflux-5.0/src/test/groovy/client/SpringWebfluxHttpClientTest.groovy @@ -24,12 +24,14 @@ import org.springframework.web.reactive.function.client.ClientResponse import org.springframework.web.reactive.function.client.WebClient import spock.lang.Ignore import spock.lang.Shared +import spock.lang.Timeout import static io.opentelemetry.trace.Span.Kind.CLIENT // FIXME this instrumentation is not currently reliable and so is currently disabled // see DefaultWebClientInstrumentation and DefaultWebClientAdvice @Ignore +@Timeout(5) class SpringWebfluxHttpClientTest extends HttpClientTest { @Shared @@ -93,4 +95,9 @@ class SpringWebfluxHttpClientTest extends HttpClientTest { boolean testConnectionFailure() { false } + + boolean testRemoteConnection() { + // FIXME: figure out how to configure timeouts. + false + } } diff --git a/instrumentation/vertx-testing/src/test/groovy/client/VertxHttpClientTest.groovy b/instrumentation/vertx-testing/src/test/groovy/client/VertxHttpClientTest.groovy index 016ba982f4..dfdf68548d 100644 --- a/instrumentation/vertx-testing/src/test/groovy/client/VertxHttpClientTest.groovy +++ b/instrumentation/vertx-testing/src/test/groovy/client/VertxHttpClientTest.groovy @@ -18,7 +18,7 @@ package client import io.opentelemetry.auto.test.base.HttpClientTest import io.vertx.core.Vertx import io.vertx.core.VertxOptions -import io.vertx.core.http.HttpClient +import io.vertx.core.http.HttpClientOptions import io.vertx.core.http.HttpClientResponse import io.vertx.core.http.HttpMethod import spock.lang.Shared @@ -30,9 +30,11 @@ import java.util.concurrent.CompletableFuture class VertxHttpClientTest extends HttpClientTest { @Shared - Vertx vertx = Vertx.vertx(new VertxOptions()) + def vertx = Vertx.vertx(new VertxOptions()) @Shared - HttpClient httpClient = vertx.createHttpClient() + def clientOptions = new HttpClientOptions().setConnectTimeout(CONNECT_TIMEOUT_MS).setIdleTimeout(READ_TIMEOUT_MS) + @Shared + def httpClient = vertx.createHttpClient(clientOptions) @Override int doRequest(String method, URI uri, Map headers, Closure callback) { @@ -57,4 +59,9 @@ class VertxHttpClientTest extends HttpClientTest { boolean testConnectionFailure() { false } + + boolean testRemoteConnection() { + // FIXME: figure out how to configure timeouts. + false + } } diff --git a/instrumentation/vertx-testing/src/test/groovy/client/VertxRxCircuitBreakerWebClientTest.groovy b/instrumentation/vertx-testing/src/test/groovy/client/VertxRxCircuitBreakerWebClientTest.groovy index 829f88ecef..68da028ab5 100644 --- a/instrumentation/vertx-testing/src/test/groovy/client/VertxRxCircuitBreakerWebClientTest.groovy +++ b/instrumentation/vertx-testing/src/test/groovy/client/VertxRxCircuitBreakerWebClientTest.groovy @@ -19,6 +19,7 @@ import io.opentelemetry.auto.test.base.HttpClientTest import io.vertx.circuitbreaker.CircuitBreakerOptions import io.vertx.core.VertxOptions import io.vertx.core.http.HttpMethod +import io.vertx.ext.web.client.WebClientOptions import io.vertx.reactivex.circuitbreaker.CircuitBreaker import io.vertx.reactivex.core.Vertx import io.vertx.reactivex.ext.web.client.WebClient @@ -33,7 +34,9 @@ class VertxRxCircuitBreakerWebClientTest extends HttpClientTest { @Shared Vertx vertx = Vertx.vertx(new VertxOptions()) @Shared - WebClient client = WebClient.create(vertx) + def clientOptions = new WebClientOptions().setConnectTimeout(CONNECT_TIMEOUT_MS).setIdleTimeout(READ_TIMEOUT_MS) + @Shared + WebClient client = WebClient.create(vertx, clientOptions) @Shared CircuitBreaker breaker = CircuitBreaker.create("my-circuit-breaker", vertx, new CircuitBreakerOptions() @@ -73,4 +76,9 @@ class VertxRxCircuitBreakerWebClientTest extends HttpClientTest { boolean testConnectionFailure() { false } + + boolean testRemoteConnection() { + // FIXME: figure out how to configure timeouts. + false + } } diff --git a/instrumentation/vertx-testing/src/test/groovy/client/VertxRxWebClientTest.groovy b/instrumentation/vertx-testing/src/test/groovy/client/VertxRxWebClientTest.groovy index 446f6d9217..38647a5d30 100644 --- a/instrumentation/vertx-testing/src/test/groovy/client/VertxRxWebClientTest.groovy +++ b/instrumentation/vertx-testing/src/test/groovy/client/VertxRxWebClientTest.groovy @@ -18,6 +18,7 @@ package client import io.opentelemetry.auto.test.base.HttpClientTest import io.vertx.core.VertxOptions import io.vertx.core.http.HttpMethod +import io.vertx.ext.web.client.WebClientOptions import io.vertx.reactivex.core.Vertx import io.vertx.reactivex.ext.web.client.WebClient import spock.lang.Shared @@ -29,7 +30,9 @@ class VertxRxWebClientTest extends HttpClientTest { @Shared Vertx vertx = Vertx.vertx(new VertxOptions()) @Shared - WebClient client = WebClient.create(vertx) + def clientOptions = new WebClientOptions().setConnectTimeout(CONNECT_TIMEOUT_MS).setIdleTimeout(READ_TIMEOUT_MS) + @Shared + WebClient client = WebClient.create(vertx, clientOptions) @Override int doRequest(String method, URI uri, Map headers, Closure callback) { @@ -52,4 +55,9 @@ class VertxRxWebClientTest extends HttpClientTest { boolean testConnectionFailure() { false } + + boolean testRemoteConnection() { + // FIXME: figure out how to configure timeouts. + false + } } diff --git a/settings.gradle b/settings.gradle index 49c45b78a0..6eaf9b7105 100644 --- a/settings.gradle +++ b/settings.gradle @@ -111,6 +111,7 @@ include ':instrumentation:netty:netty-4.0' include ':instrumentation:netty:netty-4.1' include ':instrumentation:okhttp-3.0' include ':instrumentation:opentelemetry-api-0.3' +include ':instrumentation:play:play-2.3' include ':instrumentation:play:play-2.4' include ':instrumentation:play:play-2.6' include ':instrumentation:play-ws:play-ws-1.0' diff --git a/smoke-tests/src/main/groovy/io/opentelemetry/smoketest/AbstractSmokeTest.groovy b/smoke-tests/src/main/groovy/io/opentelemetry/smoketest/AbstractSmokeTest.groovy index 16ffa8c60c..0364e883a7 100644 --- a/smoke-tests/src/main/groovy/io/opentelemetry/smoketest/AbstractSmokeTest.groovy +++ b/smoke-tests/src/main/groovy/io/opentelemetry/smoketest/AbstractSmokeTest.groovy @@ -21,7 +21,7 @@ import spock.lang.Specification abstract class AbstractSmokeTest extends Specification { - public static final PROFILING_API_KEY = "org2_api_key" + public static final API_KEY = "some-api-key" public static final PROFILING_START_DELAY_SECONDS = 1 public static final int PROFILING_RECORDING_UPLOAD_PERIOD_SECONDS = 5 @@ -68,7 +68,7 @@ abstract class AbstractSmokeTest extends Specification { ProcessBuilder processBuilder = createProcessBuilder() processBuilder.environment().put("JAVA_HOME", System.getProperty("java.home")) - processBuilder.environment().put("DD_PROFILING_APIKEY", PROFILING_API_KEY) + processBuilder.environment().put("DD_API_KEY", API_KEY) processBuilder.redirectErrorStream(true) logfile = new File("${buildDirectory}/reports/testProcess.${this.getClass().getName()}.log") diff --git a/testing/src/main/groovy/io/opentelemetry/auto/test/base/HttpClientTest.groovy b/testing/src/main/groovy/io/opentelemetry/auto/test/base/HttpClientTest.groovy index f6cc5a50c3..65ea40e0a8 100644 --- a/testing/src/main/groovy/io/opentelemetry/auto/test/base/HttpClientTest.groovy +++ b/testing/src/main/groovy/io/opentelemetry/auto/test/base/HttpClientTest.groovy @@ -23,6 +23,7 @@ import io.opentelemetry.auto.test.AgentTestRunner import io.opentelemetry.auto.test.asserts.TraceAssert import io.opentelemetry.sdk.trace.data.SpanData import spock.lang.AutoCleanup +import spock.lang.Requires import spock.lang.Shared import spock.lang.Unroll @@ -40,6 +41,8 @@ import static org.junit.Assume.assumeTrue @Unroll abstract class HttpClientTest extends AgentTestRunner { protected static final BODY_METHODS = ["POST", "PUT"] + protected static final CONNECT_TIMEOUT_MS = 1000 + protected static final READ_TIMEOUT_MS = 2000 @AutoCleanup @Shared @@ -250,8 +253,7 @@ abstract class HttpClientTest extends AgentTestRunner { def "basic #method request with circular redirects"() { given: - assumeTrue(testRedirects()) - assumeTrue(testCircularRedirects()) + assumeTrue(testRedirects() && testCircularRedirects()) def uri = server.address.resolve("/circular-redirect") when: @@ -300,6 +302,77 @@ abstract class HttpClientTest extends AgentTestRunner { method = "GET" } + def "connection error dropped request"() { + given: + assumeTrue(testRemoteConnection()) + // https://stackoverflow.com/a/100859 + def uri = new URI("http://www.google.com:81/") + + when: + runUnderTrace("parent") { + doRequest(method, uri) + } + + then: + def ex = thrown(Exception) + def thrownException = ex instanceof ExecutionException ? ex.cause : ex + assertTraces(1) { + trace(0, 2 + extraClientSpans()) { + basicSpan(it, 0, "parent", null, thrownException) + clientSpan(it, 1, span(0), method, false, uri, null, thrownException) + } + } + + where: + method = "HEAD" + } + + def "connection error non routable address"() { + given: + assumeTrue(testRemoteConnection()) + def uri = new URI("https://192.0.2.1/") + + when: + runUnderTrace("parent") { + doRequest(method, uri) + } + + then: + def ex = thrown(Exception) + def thrownException = ex instanceof ExecutionException ? ex.cause : ex + assertTraces(1) { + trace(0, 2 + extraClientSpans()) { + basicSpan(it, 0, "parent", null, thrownException) + clientSpan(it, 1, span(0), method, false, uri, null, thrownException) + } + } + + where: + method = "HEAD" + } + + // IBM JVM has different protocol support for TLS + @Requires({ !System.getProperty("java.vm.name").contains("IBM J9 VM") }) + def "test https request"() { + given: + assumeTrue(testRemoteConnection()) + def uri = new URI("https://www.google.com/") + + when: + def status = doRequest(method, uri) + + then: + status == 200 + assertTraces(1) { + trace(0, 1 + extraClientSpans()) { + clientSpan(it, 0, null, method, false, uri) + } + } + + where: + method = "HEAD" + } + // parent span must be cast otherwise it breaks debugging classloading (junit loads it early) void clientSpan(TraceAssert trace, int index, Object parentSpan, String method = "GET", boolean tagQueryString = false, URI uri = server.address.resolve("/success"), Integer status = 200, Throwable exception = null) { trace.span(index) { @@ -312,9 +385,9 @@ abstract class HttpClientTest extends AgentTestRunner { spanKind CLIENT errored exception != null tags { - "$MoreTags.NET_PEER_NAME" "localhost" + "$MoreTags.NET_PEER_NAME" uri.host "$MoreTags.NET_PEER_IP" { it == null || it == "127.0.0.1" } // Optional - "$MoreTags.NET_PEER_PORT" uri.port + "$MoreTags.NET_PEER_PORT" uri.port > 0 ? uri.port : { it == null || it == 443 } // Optional "$Tags.HTTP_URL" { it == "${uri}" || it == "${removeFragment(uri)}" } "$Tags.HTTP_METHOD" method if (status) { @@ -366,6 +439,10 @@ abstract class HttpClientTest extends AgentTestRunner { true } + boolean testRemoteConnection() { + true + } + boolean testCallbackWithParent() { // FIXME: this hack is here because callback with parent is broken in play-ws when the stream() // function is used. There is no way to stop a test from a derived class hence the flag diff --git a/testing/src/main/groovy/io/opentelemetry/auto/test/base/HttpServerTest.groovy b/testing/src/main/groovy/io/opentelemetry/auto/test/base/HttpServerTest.groovy index e3e1333b86..7b49b68350 100644 --- a/testing/src/main/groovy/io/opentelemetry/auto/test/base/HttpServerTest.groovy +++ b/testing/src/main/groovy/io/opentelemetry/auto/test/base/HttpServerTest.groovy @@ -15,6 +15,7 @@ */ package io.opentelemetry.auto.test.base +import ch.qos.logback.classic.Level import io.opentelemetry.auto.bootstrap.instrumentation.decorator.HttpServerDecorator import io.opentelemetry.auto.instrumentation.api.MoreTags import io.opentelemetry.auto.instrumentation.api.Tags @@ -28,6 +29,8 @@ import okhttp3.HttpUrl import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response +import org.slf4j.Logger +import org.slf4j.LoggerFactory import spock.lang.Shared import spock.lang.Unroll @@ -48,6 +51,11 @@ import static org.junit.Assume.assumeTrue @Unroll abstract class HttpServerTest extends AgentTestRunner { + public static final Logger SERVER_LOGGER = LoggerFactory.getLogger("http-server") + static { + ((ch.qos.logback.classic.Logger) SERVER_LOGGER).setLevel(Level.DEBUG) + } + @Shared SERVER server @Shared diff --git a/testing/src/main/groovy/io/opentelemetry/auto/test/server/http/TestHttpServer.groovy b/testing/src/main/groovy/io/opentelemetry/auto/test/server/http/TestHttpServer.groovy index 318cc85210..62bc4fb28d 100644 --- a/testing/src/main/groovy/io/opentelemetry/auto/test/server/http/TestHttpServer.groovy +++ b/testing/src/main/groovy/io/opentelemetry/auto/test/server/http/TestHttpServer.groovy @@ -41,17 +41,14 @@ class TestHttpServer implements AutoCloseable { private static final Tracer TRACER = OpenTelemetry.getTracerProvider().get("io.opentelemetry.auto") - static TestHttpServer httpServer(boolean start = true, - @DelegatesTo(value = TestHttpServer, strategy = Closure.DELEGATE_FIRST) Closure spec) { + static TestHttpServer httpServer(@DelegatesTo(value = TestHttpServer, strategy = Closure.DELEGATE_FIRST) Closure spec) { def server = new TestHttpServer() def clone = (Closure) spec.clone() clone.delegate = server clone.resolveStrategy = Closure.DELEGATE_FIRST clone(server) - if (start) { - server.start() - } + server.start() return server } diff --git a/testing/src/main/groovy/io/opentelemetry/auto/test/utils/OkHttpUtils.java b/testing/src/main/groovy/io/opentelemetry/auto/test/utils/OkHttpUtils.java index d73b563c51..f17ec38725 100644 --- a/testing/src/main/groovy/io/opentelemetry/auto/test/utils/OkHttpUtils.java +++ b/testing/src/main/groovy/io/opentelemetry/auto/test/utils/OkHttpUtils.java @@ -17,6 +17,10 @@ package io.opentelemetry.auto.test.utils; import java.util.concurrent.TimeUnit; import okhttp3.OkHttpClient; +import okhttp3.logging.HttpLoggingInterceptor; +import okhttp3.logging.HttpLoggingInterceptor.Level; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * This class was moved from groovy to java because groovy kept trying to introspect on the @@ -24,9 +28,29 @@ import okhttp3.OkHttpClient; */ public class OkHttpUtils { + private static final Logger CLIENT_LOGGER = LoggerFactory.getLogger("http-client"); + + static { + ((ch.qos.logback.classic.Logger) CLIENT_LOGGER).setLevel(ch.qos.logback.classic.Level.DEBUG); + } + + private static final HttpLoggingInterceptor LOGGING_INTERCEPTOR = + new HttpLoggingInterceptor( + new HttpLoggingInterceptor.Logger() { + @Override + public void log(final String message) { + CLIENT_LOGGER.debug(message); + } + }); + + static { + LOGGING_INTERCEPTOR.setLevel(Level.BASIC); + } + static OkHttpClient.Builder clientBuilder() { final TimeUnit unit = TimeUnit.MINUTES; return new OkHttpClient.Builder() + .addInterceptor(LOGGING_INTERCEPTOR) .connectTimeout(1, unit) .writeTimeout(1, unit) .readTimeout(1, unit); diff --git a/testing/src/test/groovy/server/ServerTest.groovy b/testing/src/test/groovy/server/ServerTest.groovy index 26902bd6be..ab24ce1298 100644 --- a/testing/src/test/groovy/server/ServerTest.groovy +++ b/testing/src/test/groovy/server/ServerTest.groovy @@ -31,12 +31,12 @@ class ServerTest extends AgentTestRunner { def "test server lifecycle"() { setup: - def server = httpServer(startAutomatically) { + def server = httpServer { handlers {} } expect: - server.internalServer.isRunning() == startAutomatically + server.internalServer.isRunning() when: server.start() @@ -57,16 +57,13 @@ class ServerTest extends AgentTestRunner { server.internalServer.isRunning() when: - server.stop() + server.close() then: !server.internalServer.isRunning() cleanup: - server.stop() - - where: - startAutomatically << [true, false] + server.close() } def "server 404's with no handlers"() { diff --git a/testing/testing.gradle b/testing/testing.gradle index 851580513e..8eaa9c0cd6 100644 --- a/testing/testing.gradle +++ b/testing/testing.gradle @@ -26,6 +26,7 @@ dependencies { compile group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.11.0' // Last version to support Java7 compile group: 'org.eclipse.jetty', name: 'jetty-server', version: '8.0.0.v20110901' + compile group: 'com.squareup.okhttp3', name: 'logging-interceptor', version: versions.okhttp compile(project(':auto-tooling')) { // including :opentelemetry-sdk-shaded-for-testing above instead