Support for khttp library (#401)

This commit is contained in:
Nikita Salnikov-Tarnovski 2020-05-17 20:41:41 +03:00 committed by GitHub
parent 6d0d3f54ce
commit fe8819fb8f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 338 additions and 0 deletions

View File

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

View File

@ -0,0 +1,28 @@
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: '+'
}

View File

@ -0,0 +1,81 @@
/*
* 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();
}
}
}

View File

@ -0,0 +1,45 @@
/*
* 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();
}
}

View File

@ -0,0 +1,49 @@
/*
* 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);
}
}

View File

@ -0,0 +1,73 @@
/*
* 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");
}
}

View File

@ -0,0 +1,26 @@
/*
* 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;
}
}

View File

@ -0,0 +1,34 @@
/*
* 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
}
}

View File

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