Skip to content

Commit 848dbc3

Browse files
authored
Add Okhttp body instrumentation and test server (#92)
* Add Okhttp body instrumentation and test server Signed-off-by: Pavol Loffay <[email protected]> * Fix muzzle Signed-off-by: Pavol Loffay <[email protected]> * remove junk Signed-off-by: Pavol Loffay <[email protected]>
1 parent c41ec01 commit 848dbc3

File tree

13 files changed

+641
-4
lines changed

13 files changed

+641
-4
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ List of supported frameworks with additional capabilities:
1616
| [Servlet](https://javaee.github.io/javaee-spec/javadocs/javax/servlet/package-summary.html) | 2.3+ |
1717
| [Spark Web Framework](https://github.com/perwendel/spark) | 2.3+ |
1818
| [gRPC](https://github.com/grpc/grpc-java) | 1.5+ |
19+
| [OkHttp](https://github.com/square/okhttp/) | 3.0+ |
1920

2021

2122
## Build

instrumentation/build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ subprojects {
99
implementation("com.google.auto.service:auto-service:1.0-rc7")
1010
annotationProcessor("com.google.auto.service:auto-service:1.0-rc7")
1111

12+
implementation("io.opentelemetry:opentelemetry-api:0.9.1")
1213
implementation("io.opentelemetry.javaagent:opentelemetry-javaagent-tooling:0.9.0")
14+
implementation("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:0.9.0")
1315
implementation(project(":javaagent-core"))
1416
implementation(project(":blocking"))
1517
}
@@ -28,6 +30,8 @@ dependencies{
2830
implementation(project(":instrumentation:servlet:servlet-3.0"))
2931
implementation(project(":instrumentation:servlet:servlet-3.1"))
3032
implementation(project(":instrumentation:spark-web-framework-2.3"))
33+
implementation(project(":instrumentation:grpc-1.5"))
34+
implementation(project(":instrumentation:okhttp:okhttp-3.0"))
3135
}
3236

3337
tasks {
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
plugins {
2+
`java-library`
3+
id("net.bytebuddy.byte-buddy-gradle-plugin") version "1.10.10"
4+
id("io.opentelemetry.instrumentation.auto-instrumentation")
5+
muzzle
6+
}
7+
8+
muzzle {
9+
pass {
10+
group = "com.squareup.okhttp3"
11+
module = "okhttp"
12+
versions = "[3.0,)"
13+
assertInverse = true
14+
}
15+
}
16+
17+
afterEvaluate{
18+
byteBuddy {
19+
transformation(closureOf<net.bytebuddy.build.gradle.Transformation> {
20+
setTasks(kotlin.collections.setOf("compileJava", "compileScala", "compileKotlin"))
21+
plugin = "io.opentelemetry.javaagent.tooling.muzzle.collector.MuzzleCodeGenerationPlugin"
22+
setClassPath(project(":javaagent-tooling").configurations["instrumentationMuzzle"] + configurations.runtimeClasspath + sourceSets["main"].output)
23+
})
24+
}
25+
}
26+
27+
dependencies {
28+
api("io.opentelemetry.javaagent.instrumentation:opentelemetry-javaagent-okhttp-3.0:0.9.0")
29+
30+
compileOnly("com.squareup.okhttp3:okhttp:3.0.0")
31+
implementation("net.bytebuddy:byte-buddy:1.10.10")
32+
33+
testImplementation(project(":testing-common"))
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Copyright The Hypertrace Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.opentelemetry.instrumentation.hypertrace.okhttp.v3_0;
18+
19+
import static java.util.Collections.singletonMap;
20+
import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
21+
import static net.bytebuddy.matcher.ElementMatchers.named;
22+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
23+
24+
import com.google.auto.service.AutoService;
25+
import io.opentelemetry.javaagent.tooling.Instrumenter;
26+
import java.util.Map;
27+
import net.bytebuddy.asm.Advice;
28+
import net.bytebuddy.description.method.MethodDescription;
29+
import net.bytebuddy.description.type.TypeDescription;
30+
import net.bytebuddy.matcher.ElementMatcher;
31+
import okhttp3.Interceptor;
32+
import okhttp3.OkHttpClient;
33+
34+
@AutoService(Instrumenter.class)
35+
public class OkHttp3BodyInstrumentation extends Instrumenter.Default {
36+
37+
public OkHttp3BodyInstrumentation() {
38+
super("okhttp", "okhttp-3");
39+
}
40+
41+
@Override
42+
public int getOrder() {
43+
return 1;
44+
}
45+
46+
@Override
47+
public ElementMatcher<TypeDescription> typeMatcher() {
48+
return named("okhttp3.OkHttpClient");
49+
}
50+
51+
@Override
52+
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
53+
return singletonMap(
54+
isConstructor().and(takesArgument(0, named("okhttp3.OkHttpClient$Builder"))),
55+
OkHttp3BodyInstrumentation.class.getName() + "$OkHttp3Advice");
56+
}
57+
58+
@Override
59+
public String[] helperClassNames() {
60+
return new String[] {
61+
"org.hypertrace.agent.core.ContentTypeUtils",
62+
"org.hypertrace.agent.core.HypertraceSemanticAttributes",
63+
packageName + ".OkHttpTracingInterceptor",
64+
};
65+
}
66+
67+
public static class OkHttp3Advice {
68+
@Advice.OnMethodEnter(suppress = Throwable.class)
69+
public static void addTracingInterceptor(@Advice.Argument(0) OkHttpClient.Builder builder) {
70+
for (Interceptor interceptor : builder.interceptors()) {
71+
if (interceptor instanceof OkHttpTracingInterceptor) {
72+
return;
73+
}
74+
}
75+
OkHttpTracingInterceptor interceptor = new OkHttpTracingInterceptor();
76+
builder.addInterceptor(interceptor);
77+
}
78+
}
79+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* Copyright The Hypertrace Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.opentelemetry.instrumentation.hypertrace.okhttp.v3_0;
18+
19+
import io.opentelemetry.common.AttributeKey;
20+
import io.opentelemetry.javaagent.instrumentation.okhttp.v3_0.OkHttpClientTracer;
21+
import io.opentelemetry.trace.Span;
22+
import java.io.IOException;
23+
import java.util.function.Function;
24+
import okhttp3.Headers;
25+
import okhttp3.Interceptor;
26+
import okhttp3.MediaType;
27+
import okhttp3.Request;
28+
import okhttp3.RequestBody;
29+
import okhttp3.Response;
30+
import okhttp3.ResponseBody;
31+
import okio.Buffer;
32+
import org.hypertrace.agent.core.ContentTypeUtils;
33+
import org.hypertrace.agent.core.HypertraceSemanticAttributes;
34+
import org.slf4j.Logger;
35+
import org.slf4j.LoggerFactory;
36+
37+
public class OkHttpTracingInterceptor implements Interceptor {
38+
private static final Logger log = LoggerFactory.getLogger(OkHttpTracingInterceptor.class);
39+
40+
private static final OkHttpClientTracer TRACER = OkHttpClientTracer.TRACER;
41+
42+
@Override
43+
public Response intercept(Chain chain) throws IOException {
44+
Span span = TRACER.getCurrentSpan();
45+
46+
Request request = chain.request();
47+
captureHeaders(span, request.headers(), HypertraceSemanticAttributes::httpRequestHeader);
48+
captureRequestBody(span, request.body());
49+
50+
Response response = chain.proceed(request);
51+
captureHeaders(span, response.headers(), HypertraceSemanticAttributes::httpResponseHeader);
52+
return captureResponseBody(span, response);
53+
}
54+
55+
private static void captureRequestBody(Span span, RequestBody requestBody) {
56+
if (requestBody == null) {
57+
return;
58+
}
59+
MediaType mediaType = requestBody.contentType();
60+
if (mediaType == null || !ContentTypeUtils.shouldCapture(mediaType.toString())) {
61+
return;
62+
}
63+
try {
64+
Buffer buffer = new Buffer();
65+
requestBody.writeTo(buffer);
66+
span.setAttribute(HypertraceSemanticAttributes.HTTP_REQUEST_BODY, buffer.readUtf8());
67+
} catch (IOException e) {
68+
log.error("Could not read request requestBody", e);
69+
}
70+
}
71+
72+
private static Response captureResponseBody(Span span, final Response response) {
73+
if (response.body() == null) {
74+
return response;
75+
}
76+
ResponseBody responseBody = response.body();
77+
MediaType mediaType = responseBody.contentType();
78+
if (mediaType == null || !ContentTypeUtils.shouldCapture(mediaType.toString())) {
79+
return response;
80+
}
81+
82+
try {
83+
String body = responseBody.string();
84+
span.setAttribute(HypertraceSemanticAttributes.HTTP_RESPONSE_BODY, body);
85+
return response
86+
.newBuilder()
87+
.body(ResponseBody.create(responseBody.contentType(), body))
88+
.build();
89+
} catch (IOException e) {
90+
log.error("Could not read response body", e);
91+
}
92+
return response;
93+
}
94+
95+
private static void captureHeaders(
96+
Span span, Headers headers, Function<String, AttributeKey<String>> headerNameProvider) {
97+
for (String name : headers.names()) {
98+
for (String value : headers.values(name)) {
99+
span.setAttribute(headerNameProvider.apply(name), value);
100+
}
101+
}
102+
}
103+
}

0 commit comments

Comments
 (0)