Skip to content

Commit

Permalink
Revert "Remove khttp instrumentation (signalfx#1763)"
Browse files Browse the repository at this point in the history
This reverts commit 0c59631.
  • Loading branch information
breedx-splk committed Apr 11, 2024
1 parent 0084cf5 commit 32c39ca
Show file tree
Hide file tree
Showing 8 changed files with 468 additions and 0 deletions.
38 changes: 38 additions & 0 deletions instrumentation/khttp-0.1/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
plugins {
id("groovy")
id("splunk.instrumentation-conventions")
id("splunk.muzzle-conventions")
}

repositories {
maven {
url = uri("https://jitpack.io")
}
}

muzzle {
pass {
group.set("com.github.jkcclemens")
module.set("khttp")
versions.set("(,)")
}
}

dependencies {
compileOnly("com.github.jkcclemens:khttp:0.1.0")

testImplementation("com.github.jkcclemens:khttp:0.1.0")

testRuntimeOnly("org.junit.vintage:junit-vintage-engine")
}

tasks.withType<Test>().configureEach {
// required on jdk17
jvmArgs("--add-opens=java.base/java.net=ALL-UNNAMED")
jvmArgs("--add-opens=java.base/sun.net.www.protocol.https=ALL-UNNAMED")
jvmArgs("-XX:+IgnoreUnrecognizedVMOptions")

// override the default always_on sampler because http client test suite includes a test that
// fails with it
jvmArgs("-Dotel.traces.sampler=parentbased_always_on")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright Splunk Inc.
*
* 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 com.splunk.opentelemetry.instrumentation.khttp;

import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;

import io.opentelemetry.instrumentation.api.semconv.http.HttpClientAttributesGetter;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import khttp.responses.Response;
import org.jetbrains.annotations.Nullable;

final class KHttpHttpClientHttpAttributesGetter
implements HttpClientAttributesGetter<RequestWrapper, Response> {

@Nullable
@Override
public String getUrlFull(RequestWrapper requestWrapper) {
return requestWrapper.uri;
}

@Nullable
@Override
public String getHttpRequestMethod(RequestWrapper requestWrapper) {
return requestWrapper.method;
}

@Override
public List<String> getHttpRequestHeader(RequestWrapper requestWrapper, String name) {
return requestWrapper.headers.entrySet().stream()
.filter(e -> e.getKey().equalsIgnoreCase(name))
.map(Map.Entry::getValue)
.collect(Collectors.toList());
}

@Override
public Integer getHttpResponseStatusCode(
RequestWrapper requestWrapper, Response response, @Nullable Throwable error) {
return response.getStatusCode();
}

@Override
public List<String> getHttpResponseHeader(
RequestWrapper requestWrapper, Response response, String name) {
String header = response.getHeaders().get(name);
return header != null ? singletonList(header) : emptyList();
}

@Nullable
@Override
public String getServerAddress(RequestWrapper requestWrapper) {
if (requestWrapper.parsedUri != null) {
return requestWrapper.parsedUri.getHost();
}
return null;
}

@Nullable
@Override
public Integer getServerPort(RequestWrapper requestWrapper) {
if (requestWrapper.parsedUri != null && requestWrapper.parsedUri.getPort() > 0) {
return requestWrapper.parsedUri.getPort();
}
return null;
}

@Nullable
@Override
public String getNetworkProtocolName(RequestWrapper requestWrapper, @Nullable Response response) {
if (requestWrapper.parsedUri.getScheme().toLowerCase().startsWith("http")) {
return "http";
}
return null;
}

@Override
public String getNetworkTransport(RequestWrapper requestWrapper, @Nullable Response response) {
return "tcp";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright Splunk Inc.
*
* 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 com.splunk.opentelemetry.instrumentation.khttp;

import io.opentelemetry.context.propagation.TextMapSetter;
import org.jetbrains.annotations.Nullable;

enum KHttpHttpHeaderSetter implements TextMapSetter<RequestWrapper> {
INSTANCE;

@Override
public void set(@Nullable RequestWrapper carrier, String key, String value) {
carrier.headers.put(key, value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Copyright Splunk Inc.
*
* 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 com.splunk.opentelemetry.instrumentation.khttp;

import static com.splunk.opentelemetry.instrumentation.khttp.KHttpSingletons.instrumenter;
import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.extendsClass;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
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 io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import java.util.HashMap;
import java.util.Map;
import khttp.responses.Response;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;

public class KHttpInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<ClassLoader> classLoaderOptimization() {
return hasClassesNamed("khttp.KHttp");
}

@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return extendsClass(named("khttp.KHttp"));
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
isMethod()
.and(not(isAbstract()))
.and(named("request"))
.and(takesArgument(0, String.class))
.and(takesArgument(1, String.class))
.and(takesArgument(2, Map.class))
.and(returns(named("khttp.responses.Response"))),
KHttpInstrumentation.class.getName() + "$RequestAdvice");
}

public static class RequestAdvice {

@Advice.OnMethodEnter(suppress = Throwable.class)
public static void methodEnter(
@Advice.Argument(value = 0) String method,
@Advice.Argument(value = 1) String uri,
@Advice.Argument(value = 2, readOnly = false) Map<String, String> headers,
@Advice.Local("otelRequest") RequestWrapper request,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
Context parentContext = currentContext();
// Kotlin likes to use read-only data structures, so wrap into new writable map
headers = new HashMap<>(headers);
request = new RequestWrapper(method, uri, headers);
if (!instrumenter().shouldStart(parentContext, request)) {
return;
}

context = instrumenter().start(parentContext, request);
scope = context.makeCurrent();
}

@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void methodExit(
@Advice.Return Response response,
@Advice.Thrown Throwable throwable,
@Advice.Local("otelRequest") RequestWrapper request,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
if (scope == null) {
return;
}

scope.close();
instrumenter().end(context, request, response, throwable);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright Splunk Inc.
*
* 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 com.splunk.opentelemetry.instrumentation.khttp;

import static java.util.Collections.singletonList;

import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import java.util.List;

@AutoService(InstrumentationModule.class)
public class KHttpInstrumentationModule extends InstrumentationModule {

public KHttpInstrumentationModule() {
super("khttp");
}

@Override
public List<TypeInstrumentation> typeInstrumentations() {
return singletonList(new KHttpInstrumentation());
}

@Override
public boolean isHelperClass(String className) {
return className.startsWith("com.splunk.opentelemetry.instrumentation");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright Splunk Inc.
*
* 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 com.splunk.opentelemetry.instrumentation.khttp;

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.instrumentation.api.incubator.semconv.net.PeerServiceAttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.semconv.http.HttpClientAttributesExtractor;
import io.opentelemetry.instrumentation.api.semconv.http.HttpClientAttributesGetter;
import io.opentelemetry.instrumentation.api.semconv.http.HttpClientMetrics;
import io.opentelemetry.instrumentation.api.semconv.http.HttpSpanNameExtractor;
import io.opentelemetry.instrumentation.api.semconv.http.HttpSpanStatusExtractor;
import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig;
import khttp.responses.Response;

public final class KHttpSingletons {
private static final String INSTRUMENTATION_NAME = "com.splunk.khttp-0.1";

private static final Instrumenter<RequestWrapper, Response> INSTRUMENTER;

static {
HttpClientAttributesGetter<RequestWrapper, Response> httpAttributesGetter =
new KHttpHttpClientHttpAttributesGetter();

INSTRUMENTER =
Instrumenter.<RequestWrapper, Response>builder(
GlobalOpenTelemetry.get(),
INSTRUMENTATION_NAME,
HttpSpanNameExtractor.create(httpAttributesGetter))
.setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributesGetter))
.addAttributesExtractor(
HttpClientAttributesExtractor.builder(httpAttributesGetter)
.setCapturedRequestHeaders(CommonConfig.get().getClientRequestHeaders())
.setCapturedResponseHeaders(CommonConfig.get().getClientResponseHeaders())
.build())
.addAttributesExtractor(
PeerServiceAttributesExtractor.create(
httpAttributesGetter, CommonConfig.get().getPeerServiceResolver()))
.addOperationMetrics(HttpClientMetrics.get())
.buildClientInstrumenter(KHttpHttpHeaderSetter.INSTANCE);
}

public static Instrumenter<RequestWrapper, Response> instrumenter() {
return INSTRUMENTER;
}

private KHttpSingletons() {}
}
Loading

0 comments on commit 32c39ca

Please sign in to comment.