Support for khttp library
MODIFY
README.md
README.md
@@ -34,6 +34,7 @@ to capture telemetry from a number of popular libraries and frameworks.
| [JMS](https://javaee.github.io/javaee-spec/javadocs/javax/jms/package-summary.html) | 1.1+ |
| [JSP](https://javaee.github.io/javaee-spec/javadocs/javax/servlet/jsp/package-summary.html) | 2.3+ |
| [Kafka](https://kafka.apache.org/20/javadoc/overview-summary.html) | 0.11+ |
+| [khttp](https://khttp.readthedocs.io) | 0.1.0+ |
| [Lettuce](https://github.com/lettuce-io/lettuce-core) | 4.0+ |
| [Log4j](https://logging.apache.org/log4j/2.x/) | 1.1+ |
| [Logback](https://github.com/qos-ch/logback) | 1.0+ |

ADD
instrumentation/khttp-0.1/khttp-0.1.gradle
+ext {
+ minJavaVersionForTests = JavaVersion.VERSION_1_8
+}
+
+apply from: "${rootDir}/gradle/instrumentation.gradle"
+apply plugin: 'org.unbroken-dome.test-sets'
+
+muzzle {
+ pass {
+ group = 'khttp'
+ module = 'khttp'
+ versions = "(,)"
+ assertInverse = true
+ }
+}
+
+
+testSets {
+ latestDepTest
+}
+
+dependencies {
+ compileOnly group: 'khttp', name: 'khttp', version: '0.1.0'
+
+ testCompile group: 'khttp', name: 'khttp', version: '0.1.0'
+
+ latestDepTestCompile group: 'khttp', name: 'khttp', version: '+'
+}
\ No newline at end of file

ADD
instrumentation/khttp-0.1/src/main/java/io/opentelemetry/auto/instrumentation/khttp/KHttpAdvice.java
+/*
+ * Copyright The 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.khttp;
+
+import static io.opentelemetry.auto.instrumentation.khttp.KHttpDecorator.DECORATE;
+import static io.opentelemetry.auto.instrumentation.khttp.KHttpDecorator.TRACER;
+import static io.opentelemetry.auto.instrumentation.khttp.KHttpHeadersInjectAdapter.SETTER;
+import static io.opentelemetry.auto.instrumentation.khttp.KHttpHeadersInjectAdapter.asWritable;
+import static io.opentelemetry.context.ContextUtils.withScopedContext;
+import static io.opentelemetry.trace.Span.Kind.CLIENT;
+import static io.opentelemetry.trace.TracingContextUtils.withSpan;
+
+import io.grpc.Context;
+import io.opentelemetry.OpenTelemetry;
+import io.opentelemetry.auto.bootstrap.CallDepthThreadLocalMap;
+import io.opentelemetry.auto.instrumentation.api.SpanWithScope;
+import io.opentelemetry.trace.Span;
+import java.util.Map;
+import khttp.KHttp;
+import khttp.responses.Response;
+import net.bytebuddy.asm.Advice;
+
+public class KHttpAdvice {
+
+ @Advice.OnMethodEnter(suppress = Throwable.class)
+ public static SpanWithScope methodEnter(
+ @Advice.Argument(value = 0) String method,
+ @Advice.Argument(value = 1) String uri,
+ @Advice.Argument(value = 2) Map<String, String> headers) {
+
+ final int callDepth = CallDepthThreadLocalMap.incrementCallDepth(KHttp.class);
+ if (callDepth > 0) {
+ return null;
+ }
+
+ final Span span = TRACER.spanBuilder("HTTP " + method).setSpanKind(CLIENT).startSpan();
+
+ DECORATE.afterStart(span);
+ DECORATE.onRequest(span, new RequestWrapper(method, uri));
+
+ final Context context = withSpan(span, Context.current());
+
+ OpenTelemetry.getPropagators().getHttpTextFormat().inject(context, asWritable(headers), SETTER);
+ return new SpanWithScope(span, withScopedContext(context));
+ }
+
+ @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
+ public static void methodExit(
+ @Advice.Enter final SpanWithScope spanWithScope,
+ @Advice.Return final Response result,
+ @Advice.Thrown final Throwable throwable) {
+ if (spanWithScope == null) {
+ return;
+ }
+ CallDepthThreadLocalMap.reset(KHttp.class);
+
+ try {
+ final Span span = spanWithScope.getSpan();
+
+ DECORATE.onResponse(span, result);
+ DECORATE.onError(span, throwable);
+ DECORATE.beforeFinish(span);
+ span.end();
+ } finally {
+ spanWithScope.closeScope();
+ }
+ }
+}

ADD
instrumentation/khttp-0.1/src/main/java/io/opentelemetry/auto/instrumentation/khttp/KHttpDecorator.java
+/*
+ * Copyright The 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.khttp;
+
+import io.opentelemetry.OpenTelemetry;
+import io.opentelemetry.auto.bootstrap.instrumentation.decorator.HttpClientDecorator;
+import io.opentelemetry.trace.Tracer;
+import java.net.URI;
+import java.net.URISyntaxException;
+import khttp.responses.Response;
+
+public class KHttpDecorator extends HttpClientDecorator<RequestWrapper, Response> {
+ public static final KHttpDecorator DECORATE = new KHttpDecorator();
+
+ public static final Tracer TRACER =
+ OpenTelemetry.getTracerProvider().get("io.opentelemetry.auto.khttp-0.1");
+
+ @Override
+ protected String method(RequestWrapper requestWrapper) {
+ return requestWrapper.method;
+ }
+
+ @Override
+ protected URI url(RequestWrapper requestWrapper) throws URISyntaxException {
+ return new URI(requestWrapper.uri);
+ }
+
+ @Override
+ protected Integer status(Response response) {
+ return response.getStatusCode();
+ }
+}

ADD
instrumentation/khttp-0.1/src/main/java/io/opentelemetry/auto/instrumentation/khttp/KHttpHeadersInjectAdapter.java
+/*
+ * Copyright The 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.khttp;
+
+import io.opentelemetry.context.propagation.HttpTextFormat;
+import java.util.HashMap;
+import java.util.Map;
+
+public class KHttpHeadersInjectAdapter implements HttpTextFormat.Setter<Map<String, String>> {
+
+ private static Class emptyMap;
+
+ static {
+ try {
+ emptyMap = Class.forName("kotlin.collections.EmptyMap");
+ } catch (ClassNotFoundException e) {
+ }
+ }
+
+ public static Map<String, String> asWritable(Map<String, String> headers) {
+ // EmptyMap is read-only so we have to substitute it with writable instance to be able to inject
+ // headers
+ if (emptyMap != null && emptyMap.isInstance(headers)) {
+ return new HashMap<>();
+ } else {
+ return headers;
+ }
+ }
+
+ public static final KHttpHeadersInjectAdapter SETTER = new KHttpHeadersInjectAdapter();
+
+ @Override
+ public void set(Map<String, String> carrier, String key, String value) {
+ carrier.put(key, value);
+ }
+}

ADD
instrumentation/khttp-0.1/src/main/java/io/opentelemetry/auto/instrumentation/khttp/KHttpInstrumentation.java
+/*
+ * Copyright The 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.khttp;
+
+import static io.opentelemetry.auto.tooling.ClassLoaderMatcher.hasClassesNamed;
+import static io.opentelemetry.auto.tooling.bytebuddy.matcher.AgentElementMatchers.safeHasSuperType;
+import static java.util.Collections.singletonMap;
+import static net.bytebuddy.matcher.ElementMatchers.isAbstract;
+import static net.bytebuddy.matcher.ElementMatchers.isMethod;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.not;
+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 class KHttpInstrumentation extends Instrumenter.Default {
+
+ public KHttpInstrumentation() {
+ super("khttp");
+ }
+
+ @Override
+ public ElementMatcher<ClassLoader> classLoaderMatcher() {
+ return hasClassesNamed("khttp.KHttp");
+ }
+
+ @Override
+ public ElementMatcher<TypeDescription> typeMatcher() {
+ return safeHasSuperType(named("khttp.KHttp"));
+ }
+
+ @Override
+ public String[] helperClassNames() {
+ return new String[] {
+ packageName + ".KHttpHeadersInjectAdapter",
+ packageName + ".KHttpDecorator",
+ packageName + ".RequestWrapper",
+ };
+ }
+
+ @Override
+ public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
+ return singletonMap(
+ isMethod()
+ .and(not(isAbstract()))
+ .and(named("request"))
+ .and(takesArgument(0, named("java.lang.String")))
+ .and(takesArgument(1, named("java.lang.String")))
+ .and(takesArgument(2, named("java.util.Map")))
+ .and(returns(named("khttp.responses.Response"))),
+ packageName + ".KHttpAdvice");
+ }
+}

ADD
instrumentation/khttp-0.1/src/main/java/io/opentelemetry/auto/instrumentation/khttp/RequestWrapper.java
+/*
+ * Copyright The 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.khttp;
+
+public class RequestWrapper {
+ String method;
+ String uri;
+
+ public RequestWrapper(String method, String uri) {
+ this.method = method;
+ this.uri = uri;
+ }
+}

ADD
instrumentation/khttp-0.1/src/test/groovy/KHttpClientTest.groovy
+/*
+ * Copyright The 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.
+ */
+import io.opentelemetry.auto.test.base.HttpClientTest
+import khttp.KHttp
+
+class KHttpClientTest extends HttpClientTest {
+
+ @Override
+ int doRequest(String method, URI uri, Map<String, String> headers, Closure callback) {
+ def response = KHttp.request(method, uri.toString(), headers, Collections.emptyMap(), null, null, null, null, 1)
+ if (callback != null) {
+ callback.call()
+ }
+ return response.statusCode
+ }
+
+ @Override
+ boolean testCircularRedirects() {
+ return false
+ }
+}
\ No newline at end of file

MODIFY
settings.gradle
settings.gradle
@@ -99,6 +99,7 @@ include ':instrumentation:jms-1.1'
include ':instrumentation:jsp-2.3'
include ':instrumentation:kafka-clients-0.11'
include ':instrumentation:kafka-streams-0.11'
+include ':instrumentation:khttp-0.1'
include ':instrumentation:lettuce:lettuce-4.0'
include ':instrumentation:lettuce:lettuce-5.0'
include ':instrumentation:log4j:log4j-1.1'

Support for khttp library (#401)