Skip to content

Commit c320487

Browse files
authoredDec 21, 2020
Add async apache HTTP client body and headers capture (#190)
* Add body capture for async Apache client Signed-off-by: Pavol Loffay <p.loffay@gmail.com> * Some progress but failing Signed-off-by: Pavol Loffay <p.loffay@gmail.com> * Some fixes Signed-off-by: Pavol Loffay <p.loffay@gmail.com> * Working request body Signed-off-by: Pavol Loffay <p.loffay@gmail.com> * working Signed-off-by: Pavol Loffay <p.loffay@gmail.com> * Add readme Signed-off-by: Pavol Loffay <p.loffay@gmail.com> * check content types Signed-off-by: Pavol Loffay <p.loffay@gmail.com> * Fix Signed-off-by: Pavol Loffay <p.loffay@gmail.com>
1 parent b54e8b3 commit c320487

File tree

16 files changed

+746
-189
lines changed

16 files changed

+746
-189
lines changed
 

‎README.md

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ and adds following capabilities:
1414
List of supported frameworks with additional capabilities:
1515
| Library/Framework | Versions |
1616
|--------------------------------------------------------------------------------------------------------|-----------------|
17+
| [Apache HttpAsyncClient](https://hc.apache.org/index.html) | 4.1+ |
1718
| [Apache HttpClient](https://hc.apache.org/index.html) | 4.0+ |
1819
| [gRPC](https://github.com/grpc/grpc-java) | 1.5+ |
1920
| [JAX-RS Client](https://javaee.github.io/javaee-spec/javadocs/javax/ws/rs/client/package-summary.html) | 2.0+ |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
plugins {
2+
`java-library`
3+
id("net.bytebuddy.byte-buddy")
4+
id("io.opentelemetry.instrumentation.auto-instrumentation")
5+
muzzle
6+
}
7+
8+
muzzle {
9+
pass {
10+
group = "org.apache.httpcomponents"
11+
module = "httpasyncclient"
12+
// 4.0 and 4.0.1 don't copy over the traceparent (etc) http headers on redirect
13+
versions = "[4.1,)"
14+
// TODO implement a muzzle check so that 4.0.x (at least 4.0 and 4.0.1) do not get applied
15+
// and then bring back assertInverse
16+
}
17+
}
18+
19+
afterEvaluate{
20+
io.opentelemetry.instrumentation.gradle.bytebuddy.ByteBuddyPluginConfigurator(project,
21+
sourceSets.main.get(),
22+
"io.opentelemetry.javaagent.tooling.muzzle.collector.MuzzleCodeGenerationPlugin",
23+
project(":javaagent-tooling").configurations["instrumentationMuzzle"] + configurations.runtimeClasspath
24+
).configure()
25+
}
26+
27+
val versions: Map<String, String> by extra
28+
29+
dependencies {
30+
api(project(":instrumentation:java-streams"))
31+
api(project(":instrumentation:apache-httpclient-4.0"))
32+
33+
api("io.opentelemetry.javaagent.instrumentation:opentelemetry-javaagent-apache-httpasyncclient-4.1:${versions["opentelemetry_java_agent"]}")
34+
35+
implementation("org.apache.httpcomponents:httpasyncclient:4.1")
36+
37+
testImplementation(project(":testing-common"))
38+
}
39+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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.javaagent.instrumentation.apachehttpasyncclient;
18+
19+
import io.opentelemetry.context.Context;
20+
import io.opentelemetry.javaagent.instrumentation.apachehttpasyncclient.ApacheHttpAsyncClientInstrumentation.DelegatingRequestProducer;
21+
22+
/**
23+
* TODO remove once https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/1951
24+
* is merged
25+
*/
26+
public class DelegatingRequestAccessor {
27+
28+
public static Context get(DelegatingRequestProducer delegatingRequestProducer) {
29+
return delegatingRequestProducer.context;
30+
}
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
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.javaagent.instrumentation.hypertrace.apachehttpasyncclient;
18+
19+
import static io.opentelemetry.javaagent.tooling.ClassLoaderMatcher.hasClassesNamed;
20+
import static io.opentelemetry.javaagent.tooling.bytebuddy.matcher.AgentElementMatchers.implementsInterface;
21+
import static java.util.Collections.singletonMap;
22+
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
23+
import static net.bytebuddy.matcher.ElementMatchers.named;
24+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
25+
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
26+
27+
import com.google.auto.service.AutoService;
28+
import io.opentelemetry.api.trace.Span;
29+
import io.opentelemetry.context.Context;
30+
import io.opentelemetry.javaagent.instrumentation.apachehttpasyncclient.ApacheHttpAsyncClientInstrumentation.DelegatingRequestProducer;
31+
import io.opentelemetry.javaagent.instrumentation.apachehttpasyncclient.DelegatingRequestAccessor;
32+
import io.opentelemetry.javaagent.instrumentation.hypertrace.apachehttpclient.v4_0.ApacheHttpClientUtils;
33+
import io.opentelemetry.javaagent.tooling.InstrumentationModule;
34+
import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
35+
import java.io.IOException;
36+
import java.util.Collections;
37+
import java.util.List;
38+
import java.util.Map;
39+
import net.bytebuddy.asm.Advice;
40+
import net.bytebuddy.description.method.MethodDescription;
41+
import net.bytebuddy.description.type.TypeDescription;
42+
import net.bytebuddy.matcher.ElementMatcher;
43+
import org.apache.http.HttpException;
44+
import org.apache.http.HttpRequest;
45+
import org.apache.http.HttpResponse;
46+
import org.apache.http.concurrent.FutureCallback;
47+
import org.apache.http.nio.protocol.HttpAsyncRequestProducer;
48+
import org.apache.http.protocol.HttpContext;
49+
import org.apache.http.protocol.HttpCoreContext;
50+
51+
@AutoService(InstrumentationModule.class)
52+
public class ApacheAsyncClientInstrumentationModule extends InstrumentationModule {
53+
54+
public ApacheAsyncClientInstrumentationModule() {
55+
super(
56+
ApacheAsyncHttpClientInstrumentationName.PRIMARY,
57+
ApacheAsyncHttpClientInstrumentationName.OTHER);
58+
}
59+
60+
@Override
61+
public int getOrder() {
62+
return 1;
63+
}
64+
65+
@Override
66+
public List<TypeInstrumentation> typeInstrumentations() {
67+
return Collections.singletonList(new HttpAsyncClientInstrumentation());
68+
}
69+
70+
class HttpAsyncClientInstrumentation implements TypeInstrumentation {
71+
72+
@Override
73+
public ElementMatcher<ClassLoader> classLoaderOptimization() {
74+
return hasClassesNamed("org.apache.http.nio.client.HttpAsyncClient");
75+
}
76+
77+
@Override
78+
public ElementMatcher<TypeDescription> typeMatcher() {
79+
return implementsInterface(named("org.apache.http.nio.client.HttpAsyncClient"));
80+
}
81+
82+
@Override
83+
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
84+
return singletonMap(
85+
isMethod()
86+
.and(named("execute"))
87+
.and(takesArguments(4))
88+
.and(takesArgument(0, named("org.apache.http.nio.protocol.HttpAsyncRequestProducer")))
89+
.and(
90+
takesArgument(1, named("org.apache.http.nio.protocol.HttpAsyncResponseConsumer")))
91+
.and(takesArgument(2, named("org.apache.http.protocol.HttpContext")))
92+
.and(takesArgument(3, named("org.apache.http.concurrent.FutureCallback"))),
93+
ApacheAsyncClientInstrumentationModule.class.getName()
94+
+ "$HttpAsyncClient_execute_Advice");
95+
}
96+
}
97+
98+
public static class HttpAsyncClient_execute_Advice {
99+
@Advice.OnMethodEnter(suppress = Throwable.class)
100+
public static void enter(
101+
@Advice.Argument(value = 0, readOnly = false) HttpAsyncRequestProducer requestProducer,
102+
@Advice.Argument(value = 2) HttpContext httpContext,
103+
@Advice.Argument(value = 3, readOnly = false) FutureCallback futureCallback) {
104+
if (requestProducer instanceof DelegatingRequestProducer) {
105+
DelegatingRequestProducer delegatingRequestProducer =
106+
(DelegatingRequestProducer) requestProducer;
107+
Context context = DelegatingRequestAccessor.get(delegatingRequestProducer);
108+
requestProducer = new DelegatingCaptureBodyRequestProducer(context, requestProducer);
109+
futureCallback = new BodyCaptureDelegatingCallback(context, httpContext, futureCallback);
110+
}
111+
}
112+
}
113+
114+
public static class DelegatingCaptureBodyRequestProducer extends DelegatingRequestProducer {
115+
116+
final Context context;
117+
118+
public DelegatingCaptureBodyRequestProducer(
119+
Context context, HttpAsyncRequestProducer delegate) {
120+
super(context, delegate);
121+
this.context = context;
122+
}
123+
124+
@Override
125+
public HttpRequest generateRequest() throws IOException, HttpException {
126+
HttpRequest request = super.generateRequest();
127+
ApacheHttpClientUtils.traceRequest(Span.fromContext(context), request);
128+
return request;
129+
}
130+
}
131+
132+
public static class BodyCaptureDelegatingCallback<T> implements FutureCallback<T> {
133+
134+
final Context context;
135+
final FutureCallback<T> delegate;
136+
final HttpContext httpContext;
137+
138+
public BodyCaptureDelegatingCallback(
139+
Context context, HttpContext httpContext, FutureCallback<T> delegate) {
140+
this.delegate = delegate;
141+
this.context = context;
142+
this.httpContext = httpContext;
143+
}
144+
145+
@Override
146+
public void completed(T result) {
147+
HttpResponse httpResponse = getResponse(httpContext);
148+
ApacheHttpClientUtils.traceResponse(Span.fromContext(context), httpResponse);
149+
delegate.completed(result);
150+
}
151+
152+
@Override
153+
public void failed(Exception ex) {
154+
HttpResponse httpResponse = getResponse(httpContext);
155+
ApacheHttpClientUtils.traceResponse(Span.fromContext(context), httpResponse);
156+
delegate.failed(ex);
157+
}
158+
159+
@Override
160+
public void cancelled() {
161+
HttpResponse httpResponse = getResponse(httpContext);
162+
ApacheHttpClientUtils.traceResponse(Span.fromContext(context), httpResponse);
163+
delegate.cancelled();
164+
}
165+
166+
private static HttpResponse getResponse(HttpContext context) {
167+
return (HttpResponse) context.getAttribute(HttpCoreContext.HTTP_RESPONSE);
168+
}
169+
}
170+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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.javaagent.instrumentation.hypertrace.apachehttpasyncclient;
18+
19+
public class ApacheAsyncHttpClientInstrumentationName {
20+
21+
public static final String PRIMARY = "apache-httpasyncclient";
22+
public static final String[] OTHER = {
23+
"apache-httpasyncclient-4.1",
24+
"ht",
25+
"apache-httpasyncclient-ht",
26+
"apache-httpasyncclient-4.1-ht",
27+
};
28+
}

0 commit comments

Comments
 (0)