Merge tag 'v0.48.0' into dd-merge

This commit is contained in:
Trask Stalnaker 2020-04-11 13:19:47 -07:00
commit d24159c8d2
67 changed files with 1310 additions and 252 deletions

View File

@ -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<String> 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<String> getListSettingFromEnvironment(
@NonNull
private static List<String> 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> T getSettingFromEnvironmentWithLog(
final String name, final Class<T> 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 <T extends Enum<T>> Set<T> getEnumSetSettingFromEnvironment(
final String name,
final String defaultValue,
final Class<T> clazz,
final boolean emptyResultMeansUseDefault) {
final String value = getSettingFromEnvironment(name, defaultValue);
Set<T> 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<Integer> getIntegerRangeSettingFromEnvironment(
private static Set<Integer> getIntegerRangeSettingFromEnvironment(
final String name, final Set<Integer> 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<String, String> getPropertyMapValue(
final Properties properties, final String name, final Map<String, String> 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 <T>
* @return value == null || value.trim().isEmpty() ? defaultValue : tClass.valueOf(value)
* @throws NumberFormatException
*/
private static <T> T valueOf(
final String value, @NonNull final Class<T> 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<String> 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 <T extends Enum<T>> Set<T> getPropertySetValue(
final Properties properties, final String name, final Class<T> clazz) {
final String value = properties.getProperty(name);
if (value != null) {
final Set<T> 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<Integer> getPropertyIntegerRangeValue(
private static Set<Integer> getPropertyIntegerRangeValue(
final Properties properties, final String name, final Set<Integer> defaultValue) {
final String value = properties.getProperty(name);
try {
@ -469,38 +454,9 @@ public class Config {
}
}
private static Map<String, String> 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<String, String> 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<Integer> parseIntegerRangeSet(String str, final String settingName)
@NonNull
private static Set<Integer> 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<String, String> newHashMap(final int size) {
return new HashMap<>(size + 1, 1f);
}
@NonNull
private static List<String> 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<String> parseStringIntoSetOfNonEmptyStrings(
final String str, final String regex) {
// Using LinkedHashSet to preserve original string order
final Set<String> 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 <V extends Enum<V>> Set<V> convertStringSetToEnumSet(
final Set<String> input, final Class<V> clazz) {
// Using LinkedHashSet to preserve original string order
final Set<V> 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;

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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<Project> {
private static final AtomicReference<ClassLoader> TOOLING_LOADER = new AtomicReference<>()
static {
RemoteRepository central = new RemoteRepository.Builder("central", "default", "https://repo1.maven.org/maven2/").build()
MUZZLE_REPOS = new ArrayList<RemoteRepository>(Arrays.asList(central))
RemoteRepository typesafe = new RemoteRepository.Builder("typesafe", "default", "https://repo.typesafe.com/typesafe/releases").build()
MUZZLE_REPOS = new ArrayList<RemoteRepository>(Arrays.asList(central, typesafe))
}
@Override
@ -343,6 +345,8 @@ class MuzzlePlugin implements Plugin<Project> {
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<Project> {
version.contains(".m") ||
version.contains("-m") ||
version.contains("-dev") ||
version.contains("public_draft")
version.contains("public_draft") ||
version.matches(GIT_SHA_PATTERN)
}
return list
}

View File

@ -0,0 +1 @@
test-api-key-very-old

View File

@ -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,

View File

@ -133,6 +133,9 @@ repositories {
maven {
url "https://adoptopenjdk.jfrog.io/adoptopenjdk/jmc-libs-snapshots"
}
maven {
url "https://repo.typesafe.com/typesafe/releases"
}
}
dependencies {

View File

@ -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

3
gradlew.bat vendored
View File

@ -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"

View File

@ -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

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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<String, String> headers, Closure callback) {
HttpMethod httpMethod

View File

@ -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<String, String> headers, Closure callback) {
def request = new HttpUriRequest(method, uri)

View File

@ -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<T extends HttpRequest> 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<String, String> headers, Closure callback) {
def request = createRequest(method, uri)
@ -64,6 +73,7 @@ abstract class ApacheHttpClientTest<T extends HttpRequest> extends HttpClientTes
}
}
@Timeout(5)
class ApacheClientHostRequest extends ApacheHttpClientTest<BasicHttpRequest> {
@Override
BasicHttpRequest createRequest(String method, URI uri) {
@ -74,8 +84,14 @@ class ApacheClientHostRequest extends ApacheHttpClientTest<BasicHttpRequest> {
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<BasicHttpRequest> {
@Override
BasicHttpRequest createRequest(String method, URI uri) {
@ -86,8 +102,14 @@ class ApacheClientHostRequestContext extends ApacheHttpClientTest<BasicHttpReque
HttpResponse executeRequest(BasicHttpRequest request, URI uri) {
return client.execute(new HttpHost(uri.getHost(), uri.getPort()), request, new BasicHttpContext())
}
@Override
boolean testRemoteConnection() {
return false
}
}
@Timeout(5)
class ApacheClientHostRequestResponseHandler extends ApacheHttpClientTest<BasicHttpRequest> {
@Override
BasicHttpRequest createRequest(String method, URI uri) {
@ -98,8 +120,14 @@ class ApacheClientHostRequestResponseHandler extends ApacheHttpClientTest<BasicH
HttpResponse executeRequest(BasicHttpRequest request, URI uri) {
return client.execute(new HttpHost(uri.getHost(), uri.getPort()), request, { response -> response })
}
@Override
boolean testRemoteConnection() {
return false
}
}
@Timeout(5)
class ApacheClientHostRequestResponseHandlerContext extends ApacheHttpClientTest<BasicHttpRequest> {
@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<HttpUriRequest> {
@Override
HttpUriRequest createRequest(String method, URI uri) {
@ -124,6 +158,7 @@ class ApacheClientUriRequest extends ApacheHttpClientTest<HttpUriRequest> {
}
}
@Timeout(5)
class ApacheClientUriRequestContext extends ApacheHttpClientTest<HttpUriRequest> {
@Override
HttpUriRequest createRequest(String method, URI uri) {
@ -136,6 +171,7 @@ class ApacheClientUriRequestContext extends ApacheHttpClientTest<HttpUriRequest>
}
}
@Timeout(5)
class ApacheClientUriRequestResponseHandler extends ApacheHttpClientTest<HttpUriRequest> {
@Override
HttpUriRequest createRequest(String method, URI uri) {
@ -148,6 +184,7 @@ class ApacheClientUriRequestResponseHandler extends ApacheHttpClientTest<HttpUri
}
}
@Timeout(5)
class ApacheClientUriRequestResponseHandlerContext extends ApacheHttpClientTest<HttpUriRequest> {
@Override
HttpUriRequest createRequest(String method, URI uri) {

View File

@ -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)

View File

@ -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) {

View File

@ -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) {

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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<String, String> headers, Closure callback) {
@ -45,4 +56,10 @@ class SpringRestTemplateTest extends HttpClientTest {
boolean testConnectionFailure() {
false
}
@Override
boolean testRemoteConnection() {
// FIXME: exception wrapped in ResourceAccessException
return false
}
}

View File

@ -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())

View File

@ -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

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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')

View File

@ -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<Channel> {
class Netty38ServerTest extends HttpServerTest<ServerBootstrap> {
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<Channel> {
@Override
void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent ex) throws Exception {
ChannelBuffer buffer = ChannelBuffers.copiedBuffer(ex.getCause().getMessage(), CharsetUtil.UTF_8)
def message = ex.getCause() == null ? "<no cause> " + ex.message : ex.cause.message == null ? "<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<Channel> {
}
@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()
}
}

View File

@ -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);

View File

@ -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')

View File

@ -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<Channel> {
class Netty38ServerTest extends HttpServerTest<ServerBootstrap> {
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<Channel> {
@Override
void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent ex) throws Exception {
ChannelBuffer buffer = ChannelBuffers.copiedBuffer(ex.getCause().getMessage(), CharsetUtil.UTF_8)
def message = ex.cause == null ? "<no cause> " + ex.message : ex.cause.message == null ? "<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<Channel> {
}
@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()
}
}

View File

@ -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

View File

@ -48,16 +48,20 @@ import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.SUCC
class Netty40ServerTest extends HttpServerTest<EventLoopGroup> {
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([

View File

@ -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/")

View File

@ -47,16 +47,20 @@ import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.SUCC
class Netty41ServerTest extends HttpServerTest<EventLoopGroup> {
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([

View File

@ -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<String, String> headers, Closure callback) {

View File

@ -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

View File

@ -0,0 +1 @@
logs/

View File

@ -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
}

View File

@ -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<ClassLoader> classLoaderMatcher() {
// Optimization for expensive typeMatcher.
return hasClassesNamed("play.api.mvc.Action");
}
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return implementsInterface(named("play.api.mvc.Action"));
}
@Override
public String[] helperClassNames() {
return new String[] {
packageName + ".PlayHttpServerDecorator",
packageName + ".RequestCompleteCallback",
packageName + ".PlayHeaders",
};
}
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
return singletonMap(
named("apply")
.and(takesArgument(0, named("play.api.mvc.Request")))
.and(returns(named("scala.concurrent.Future"))),
packageName + ".PlayAdvice");
}
}

View File

@ -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<Result> 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);
}
}

View File

@ -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<Headers> {
public static final PlayHeaders GETTER = new PlayHeaders();
@Override
public String get(final Headers headers, final String key) {
final Option<String> option = headers.get(key);
if (option.isDefined()) {
return option.get();
} else {
return null;
}
}
}

View File

@ -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<Request, Request, Result> {
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);
}
}

View File

@ -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<Try<Result>, Object> {
private final Span span;
public RequestCompleteCallback(final Span span) {
this.span = span;
}
@Override
public Object apply(final Try<Result> 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;
}
}

View File

@ -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<String, String> 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
}
}

View File

@ -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()));
}
}

View File

@ -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
}
}

View File

@ -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<TestServer> {
@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
}
}
}
}
}

View File

@ -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))
}
}

View File

@ -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()
}

View File

@ -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))
}
}

View File

@ -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))
}
}

View File

@ -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
}
}

View File

@ -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<String, String> headers, Closure callback) {
ExecResult<Integer> 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
}
}

View File

@ -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
}
}

View File

@ -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<String, String> headers, Closure callback) {
@ -57,4 +59,9 @@ class VertxHttpClientTest extends HttpClientTest {
boolean testConnectionFailure() {
false
}
boolean testRemoteConnection() {
// FIXME: figure out how to configure timeouts.
false
}
}

View File

@ -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
}
}

View File

@ -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<String, String> headers, Closure callback) {
@ -52,4 +55,9 @@ class VertxRxWebClientTest extends HttpClientTest {
boolean testConnectionFailure() {
false
}
boolean testRemoteConnection() {
// FIXME: figure out how to configure timeouts.
false
}
}

View File

@ -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'

View File

@ -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")

View File

@ -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

View File

@ -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<SERVER> 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

View File

@ -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
}

View File

@ -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);

View File

@ -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"() {

View File

@ -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