Skip to content

Commit 22c528a

Browse files
authored
Support JAX-RS client typed entities - message body writer/reader (#162)
* Support JAX-RS client typed entities - message body writer/reader Signed-off-by: Pavol Loffay <[email protected]> * working Signed-off-by: Pavol Loffay <[email protected]> * Split modules Signed-off-by: Pavol Loffay <[email protected]> * Add muzzle Signed-off-by: Pavol Loffay <[email protected]> * Add comment Signed-off-by: Pavol Loffay <[email protected]> * Remove wrong license Signed-off-by: Pavol Loffay <[email protected]> * Fix muzzle Signed-off-by: Pavol Loffay <[email protected]>
1 parent 84526bd commit 22c528a

File tree

13 files changed

+201
-75
lines changed

13 files changed

+201
-75
lines changed

instrumentation/apache-httpclient-4.0/build.gradle.kts

+3-1
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,10 @@ afterEvaluate{
3939
val versions: Map<String, String> by extra
4040

4141
dependencies {
42-
implementation("org.apache.httpcomponents:httpclient:4.0")
42+
api(project(":instrumentation:java-streams"))
4343
api("io.opentelemetry.javaagent.instrumentation:opentelemetry-javaagent-apache-httpclient-4.0:${versions["opentelemetry_java_agent"]}")
4444

45+
implementation("org.apache.httpcomponents:httpclient:4.0")
46+
4547
testImplementation(project(":testing-common"))
4648
}

instrumentation/build.gradle.kts

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ dependencies{
3838
implementation(project(":instrumentation:okhttp:okhttp-3.0"))
3939
implementation(project(":instrumentation:apache-httpclient-4.0"))
4040
implementation(project(":instrumentation:jaxrs-client-2.0"))
41+
implementation(project(":instrumentation:java-streams"))
4142
implementation(project(":otel-extensions"))
4243
}
4344

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
plugins {
2+
`java-library`
3+
id("net.bytebuddy.byte-buddy")
4+
}
5+
6+
afterEvaluate{
7+
io.opentelemetry.instrumentation.gradle.bytebuddy.ByteBuddyPluginConfigurator(project,
8+
sourceSets.main.get(),
9+
"io.opentelemetry.javaagent.tooling.muzzle.collector.MuzzleCodeGenerationPlugin",
10+
project(":javaagent-tooling").configurations["instrumentationMuzzle"] + configurations.runtimeClasspath
11+
).configure()
12+
}
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
package io.opentelemetry.instrumentation.hypertrace.apachehttpclient.v4_0;
17+
package io.opentelemetry.instrumentation.hypertrace.java.inputstream;
1818

1919
import static io.opentelemetry.javaagent.tooling.bytebuddy.matcher.AgentElementMatchers.safeHasSuperType;
2020
import static io.opentelemetry.javaagent.tooling.matcher.NameMatchers.namedOneOf;
@@ -36,9 +36,19 @@
3636
import net.bytebuddy.description.method.MethodDescription;
3737
import net.bytebuddy.description.type.TypeDescription;
3838
import net.bytebuddy.matcher.ElementMatcher;
39+
import org.hypertrace.agent.core.GlobalObjectRegistry;
3940

4041
/**
41-
* Maybe we could add optimization to instrument the input streams only when certain classes are
42+
* {@link InputStream} instrumentation. The type matcher applies to all implementations. However
43+
* only streams that are in the {@link GlobalObjectRegistry#inputStreamToSpanAndBufferMap} are
44+
* instrumented, otherwise the instrumentation is noop.
45+
*
46+
* <p>If the stream is in the {@link GlobalObjectRegistry#inputStreamToSpanAndBufferMap} then result
47+
* of read methods is also passed to the buffered stream (value) from the map. The instrumentation
48+
* adds buffer to span from the map when read is finished (return -1), creates new span with buffer
49+
* when the original span is not recording.
50+
*
51+
* <p>Maybe we could add optimization to instrument the input streams only when certain classes are
4252
* present in classloader e.g. classes from frameworks that we instrument.
4353
*/
4454
@AutoService(InstrumentationModule.class)
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
package io.opentelemetry.instrumentation.hypertrace.apachehttpclient.v4_0;
17+
package io.opentelemetry.instrumentation.hypertrace.java.inputstream;
1818

1919
import io.opentelemetry.api.OpenTelemetry;
2020
import io.opentelemetry.api.common.AttributeKey;
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
package io.opentelemetry.instrumentation.hypertrace.apachehttpclient.v4_0;
17+
package io.opentelemetry.instrumentation.hypertrace.java.outputstream;
1818

1919
import static io.opentelemetry.javaagent.tooling.bytebuddy.matcher.AgentElementMatchers.extendsClass;
2020
import static io.opentelemetry.javaagent.tooling.matcher.NameMatchers.namedOneOf;
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
package io.opentelemetry.instrumentation.hypertrace.apachehttpclient.v4_0;
17+
package io.opentelemetry.instrumentation.hypertrace.java.outputstream;
1818

1919
import io.opentelemetry.javaagent.instrumentation.api.CallDepthThreadLocalMap;
2020
import java.io.IOException;

instrumentation/jaxrs-client-2.0/build.gradle.kts

+3-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ muzzle {
1616
group = "io.dropwizard"
1717
module = "dropwizard-client"
1818
versions = "[0.8.0,)"
19-
assertInverse = true
19+
// TODO this is set in OTEL
20+
// assertInverse = true
2021
}
2122
}
2223

@@ -31,6 +32,7 @@ afterEvaluate{
3132
val versions: Map<String, String> by extra
3233

3334
dependencies {
35+
api(project(":instrumentation:java-streams"))
3436
api("io.opentelemetry.javaagent.instrumentation:opentelemetry-javaagent-jaxrs-client-2.0-common:${versions["opentelemetry_java_agent"]}")
3537

3638
compileOnly("javax.ws.rs:javax.ws.rs-api:2.0.1")

instrumentation/jaxrs-client-2.0/src/main/java/io/opentelemetry/instrumentation/hypertrace/jaxrs/v2_0/JaxrsClientBodyCaptureFilter.java

+2-43
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,8 @@
2626
import javax.ws.rs.client.ClientRequestFilter;
2727
import javax.ws.rs.client.ClientResponseContext;
2828
import javax.ws.rs.client.ClientResponseFilter;
29-
import javax.ws.rs.core.Form;
30-
import javax.ws.rs.core.MediaType;
3129
import javax.ws.rs.core.MultivaluedMap;
3230
import org.hypertrace.agent.config.Config.AgentConfig;
33-
import org.hypertrace.agent.core.ContentTypeUtils;
3431
import org.hypertrace.agent.core.HypertraceConfig;
3532
import org.hypertrace.agent.core.HypertraceSemanticAttributes;
3633
import org.slf4j.Logger;
@@ -57,28 +54,8 @@ public void filter(ClientRequestContext requestContext) {
5754
HypertraceSemanticAttributes::httpRequestHeader,
5855
requestContext.getStringHeaders());
5956
}
60-
if (requestContext.hasEntity()
61-
&& agentConfig.getDataCapture().getHttpBody().getRequest().getValue()) {
62-
MediaType mediaType = requestContext.getMediaType();
63-
if (mediaType == null || !ContentTypeUtils.shouldCapture(mediaType.toString())) {
64-
return;
65-
}
66-
67-
Object entity = requestContext.getEntity();
68-
if (entity != null) {
69-
if (entity instanceof Form) {
70-
Form form = (Form) entity;
71-
String content = getUrlEncodedContent(form);
72-
currentSpan.setAttribute(HypertraceSemanticAttributes.HTTP_REQUEST_BODY, content);
73-
} else {
74-
currentSpan.setAttribute(
75-
HypertraceSemanticAttributes.HTTP_REQUEST_BODY, entity.toString());
76-
}
77-
}
78-
}
79-
requestContext.getEntity();
8057
} catch (Exception ex) {
81-
log.error("Exception while getting request entity or headers", ex);
58+
log.error("Exception while getting request headers", ex);
8259
}
8360
}
8461

@@ -100,26 +77,8 @@ public void filter(ClientRequestContext requestContext, ClientResponseContext re
10077
responseContext.getHeaders());
10178
}
10279
} catch (Exception ex) {
103-
log.error("Exception while getting response entity or headers", ex);
104-
}
105-
}
106-
107-
private static String getUrlEncodedContent(Form form) {
108-
MultivaluedMap<String, String> formMap = form.asMap();
109-
StringBuilder sb = new StringBuilder();
110-
if (formMap != null) {
111-
for (Map.Entry<String, List<String>> entry : formMap.entrySet()) {
112-
if (sb.length() > 0) {
113-
sb.append("&");
114-
}
115-
for (String value : entry.getValue()) {
116-
sb.append(entry.getKey());
117-
sb.append("=");
118-
sb.append(value);
119-
}
120-
}
80+
log.error("Exception while getting response headers", ex);
12181
}
122-
return sb.toString();
12382
}
12483

12584
private static void captureHeaders(

instrumentation/jaxrs-client-2.0/src/main/java/io/opentelemetry/instrumentation/hypertrace/jaxrs/v2_0/JaxrsClientEntityInterceptor.java

+78-25
Original file line numberDiff line numberDiff line change
@@ -16,63 +16,116 @@
1616

1717
package io.opentelemetry.instrumentation.hypertrace.jaxrs.v2_0;
1818

19-
import io.opentelemetry.api.OpenTelemetry;
2019
import io.opentelemetry.api.trace.Span;
21-
import io.opentelemetry.api.trace.Tracer;
22-
import io.opentelemetry.context.Context;
2320
import io.opentelemetry.javaagent.instrumentation.jaxrsclient.v2_0.ClientTracingFilter;
21+
import java.io.ByteArrayOutputStream;
2422
import java.io.IOException;
23+
import java.io.InputStream;
24+
import java.io.OutputStream;
2525
import javax.ws.rs.WebApplicationException;
26+
import javax.ws.rs.core.HttpHeaders;
2627
import javax.ws.rs.core.MediaType;
2728
import javax.ws.rs.ext.ReaderInterceptor;
2829
import javax.ws.rs.ext.ReaderInterceptorContext;
30+
import javax.ws.rs.ext.WriterInterceptor;
31+
import javax.ws.rs.ext.WriterInterceptorContext;
2932
import org.hypertrace.agent.config.Config.AgentConfig;
33+
import org.hypertrace.agent.core.ContentEncodingUtils;
34+
import org.hypertrace.agent.core.ContentLengthUtils;
3035
import org.hypertrace.agent.core.ContentTypeUtils;
36+
import org.hypertrace.agent.core.GlobalObjectRegistry;
37+
import org.hypertrace.agent.core.GlobalObjectRegistry.SpanAndBuffer;
3138
import org.hypertrace.agent.core.HypertraceConfig;
3239
import org.hypertrace.agent.core.HypertraceSemanticAttributes;
3340
import org.slf4j.Logger;
3441
import org.slf4j.LoggerFactory;
3542

36-
public class JaxrsClientEntityInterceptor implements ReaderInterceptor {
43+
public class JaxrsClientEntityInterceptor implements ReaderInterceptor, WriterInterceptor {
3744

3845
private static final Logger log = LoggerFactory.getLogger(JaxrsClientEntityInterceptor.class);
3946

40-
private static final Tracer TRACER =
41-
OpenTelemetry.getGlobalTracer("org.hypertrace.java.jaxrs.client");
42-
47+
/** Writing response body to input stream */
4348
@Override
4449
public Object aroundReadFrom(ReaderInterceptorContext context)
4550
throws IOException, WebApplicationException {
4651

47-
Object entity = context.proceed();
52+
MediaType mediaType = context.getMediaType();
53+
AgentConfig agentConfig = HypertraceConfig.get();
54+
if (mediaType == null
55+
|| !ContentTypeUtils.shouldCapture(mediaType.toString())
56+
|| !agentConfig.getDataCapture().getHttpBody().getResponse().getValue()) {
57+
return context.proceed();
58+
}
4859

4960
Object spanObj = context.getProperty(ClientTracingFilter.SPAN_PROPERTY_NAME);
5061
if (!(spanObj instanceof Span)) {
5162
log.error(
5263
"Span object is not present in the context properties, response object will not be captured");
53-
return entity;
64+
return context.proceed();
5465
}
5566
Span currentSpan = (Span) spanObj;
5667

57-
MediaType mediaType = context.getMediaType();
58-
AgentConfig agentConfig = HypertraceConfig.get();
59-
if (mediaType == null
60-
|| !ContentTypeUtils.shouldCapture(mediaType.toString())
61-
|| !agentConfig.getDataCapture().getHttpBody().getResponse().getValue()) {
62-
return entity;
68+
// TODO as optimization the type could be checked here and if it is a primitive type e.g. String
69+
// it could be read directly.
70+
// context.getType();
71+
72+
InputStream entityStream = context.getInputStream();
73+
Object entity = null;
74+
try {
75+
String encodingStr = context.getHeaders().getFirst(HttpHeaders.CONTENT_ENCODING);
76+
String contentLengthStr = context.getHeaders().getFirst(HttpHeaders.CONTENT_LENGTH);
77+
int contentLength = ContentLengthUtils.parseLength(contentLengthStr);
78+
79+
ByteArrayOutputStream buffer = new ByteArrayOutputStream(contentLength);
80+
GlobalObjectRegistry.inputStreamToSpanAndBufferMap.put(
81+
entityStream,
82+
new SpanAndBuffer(
83+
currentSpan,
84+
buffer,
85+
HypertraceSemanticAttributes.HTTP_RESPONSE_BODY,
86+
ContentEncodingUtils.toCharset(encodingStr)));
87+
entity = context.proceed();
88+
} catch (Exception ex) {
89+
log.error("Exception while capturing response body", ex);
6390
}
91+
return entity;
92+
}
93+
94+
/** Writing request body to output stream */
95+
@Override
96+
public void aroundWriteTo(WriterInterceptorContext context)
97+
throws IOException, WebApplicationException {
98+
99+
Object spanObj = context.getProperty(ClientTracingFilter.SPAN_PROPERTY_NAME);
100+
if (!(spanObj instanceof Span)) {
101+
log.error(
102+
"Span object is not present in the context properties, request body will not be captured");
103+
context.proceed();
104+
return;
105+
}
106+
Span currentSpan = (Span) spanObj;
64107

65-
if (currentSpan.isRecording()) {
66-
currentSpan.setAttribute(HypertraceSemanticAttributes.HTTP_RESPONSE_BODY, entity.toString());
67-
} else {
68-
TRACER
69-
.spanBuilder(HypertraceSemanticAttributes.ADDITIONAL_DATA_SPAN_NAME)
70-
.setParent(Context.root().with(currentSpan))
71-
.setAttribute(HypertraceSemanticAttributes.HTTP_RESPONSE_BODY, entity.toString())
72-
.startSpan()
73-
.end();
108+
AgentConfig agentConfig = HypertraceConfig.get();
109+
if (agentConfig.getDataCapture().getHttpBody().getRequest().getValue()) {
110+
MediaType mediaType = context.getMediaType();
111+
if (mediaType == null || !ContentTypeUtils.shouldCapture(mediaType.toString())) {
112+
context.proceed();
113+
return;
114+
}
74115
}
75116

76-
return entity;
117+
// TODO length is not known
118+
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
119+
OutputStream entityStream = context.getOutputStream();
120+
try {
121+
GlobalObjectRegistry.outputStreamToBufferMap.put(entityStream, buffer);
122+
context.proceed();
123+
} catch (Exception ex) {
124+
log.error("Failed to capture request body", ex);
125+
} finally {
126+
GlobalObjectRegistry.outputStreamToBufferMap.remove(entityStream);
127+
// TODO encoding is not known
128+
currentSpan.setAttribute(HypertraceSemanticAttributes.HTTP_REQUEST_BODY, buffer.toString());
129+
}
77130
}
78131
}

0 commit comments

Comments
 (0)