Skip to content

Commit d0c2e8c

Browse files
ENG-16503 Show request body for x-www-form-urlencoded HTTP requests (#363)
* ENG-16503 Show request body for x-www-form-urlencoded HTTP requests * Fix snyk-scan issues * Switch to version 2.13.3 of jackson.dataformat * Use Jackson to convert object into JSON * Fix formatting of ContentTypeUtils
1 parent 8f9e708 commit d0c2e8c

File tree

10 files changed

+209
-24
lines changed

10 files changed

+209
-24
lines changed

instrumentation/servlet/servlet-3.0/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/servlet/v3_0/nowrapping/Servlet30AndFilterInstrumentation.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,7 @@
4949
import org.hypertrace.agent.core.instrumentation.HypertraceEvaluationException;
5050
import org.hypertrace.agent.core.instrumentation.HypertraceSemanticAttributes;
5151
import org.hypertrace.agent.core.instrumentation.SpanAndObjectPair;
52-
import org.hypertrace.agent.core.instrumentation.buffer.BoundedByteArrayOutputStream;
53-
import org.hypertrace.agent.core.instrumentation.buffer.BoundedCharArrayWriter;
54-
import org.hypertrace.agent.core.instrumentation.buffer.ByteBufferSpanPair;
55-
import org.hypertrace.agent.core.instrumentation.buffer.CharBufferSpanPair;
52+
import org.hypertrace.agent.core.instrumentation.buffer.*;
5653
import org.hypertrace.agent.core.instrumentation.utils.ContentTypeUtils;
5754
import org.hypertrace.agent.filter.FilterRegistry;
5855

@@ -180,6 +177,8 @@ public static void exit(
180177
VirtualField.find(ServletInputStream.class, ByteBufferSpanPair.class);
181178
VirtualField<BufferedReader, CharBufferSpanPair> readerContextStore =
182179
VirtualField.find(BufferedReader.class, CharBufferSpanPair.class);
180+
VirtualField<HttpServletRequest, StringMapSpanPair> urlEncodedMapContextStore =
181+
VirtualField.find(HttpServletRequest.class, StringMapSpanPair.class);
183182

184183
if (!request.isAsyncStarted()) {
185184
if (instrumentationConfig.httpHeaders().response()) {
@@ -205,7 +204,11 @@ public static void exit(
205204
if (instrumentationConfig.httpBody().request()
206205
&& ContentTypeUtils.shouldCapture(httpRequest.getContentType())) {
207206
Utils.resetRequestBodyBuffers(
208-
httpRequest, requestContextStore, inputStreamContextStore, readerContextStore);
207+
httpRequest,
208+
requestContextStore,
209+
inputStreamContextStore,
210+
readerContextStore,
211+
urlEncodedMapContextStore);
209212
}
210213
}
211214
}

instrumentation/servlet/servlet-3.0/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/servlet/v3_0/nowrapping/Utils.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,15 @@
2121
import java.io.BufferedReader;
2222
import java.io.PrintWriter;
2323
import java.io.UnsupportedEncodingException;
24+
import java.util.Map;
2425
import javax.servlet.ServletInputStream;
2526
import javax.servlet.ServletOutputStream;
2627
import javax.servlet.http.HttpServletRequest;
2728
import javax.servlet.http.HttpServletResponse;
2829
import javax.servlet.http.HttpSession;
2930
import org.hypertrace.agent.core.instrumentation.HypertraceSemanticAttributes;
3031
import org.hypertrace.agent.core.instrumentation.SpanAndObjectPair;
31-
import org.hypertrace.agent.core.instrumentation.buffer.BoundedByteArrayOutputStream;
32-
import org.hypertrace.agent.core.instrumentation.buffer.BoundedCharArrayWriter;
33-
import org.hypertrace.agent.core.instrumentation.buffer.ByteBufferSpanPair;
34-
import org.hypertrace.agent.core.instrumentation.buffer.CharBufferSpanPair;
32+
import org.hypertrace.agent.core.instrumentation.buffer.*;
3533

3634
public class Utils {
3735

@@ -87,7 +85,8 @@ public static void resetRequestBodyBuffers(
8785
HttpServletRequest httpServletRequest,
8886
VirtualField<HttpServletRequest, SpanAndObjectPair> requestContextStore,
8987
VirtualField<ServletInputStream, ByteBufferSpanPair> streamContextStore,
90-
VirtualField<BufferedReader, CharBufferSpanPair> bufferedReaderContextStore) {
88+
VirtualField<BufferedReader, CharBufferSpanPair> bufferedReaderContextStore,
89+
VirtualField<HttpServletRequest, StringMapSpanPair> urlEncodedMapContextStore) {
9190

9291
SpanAndObjectPair requestStreamReaderHolder = requestContextStore.get(httpServletRequest);
9392
if (requestStreamReaderHolder == null) {
@@ -114,6 +113,12 @@ public static void resetRequestBodyBuffers(
114113
charBufferSpanPair.captureBody(HypertraceSemanticAttributes.HTTP_REQUEST_BODY);
115114
bufferedReaderContextStore.set(bufferedReader, null);
116115
}
116+
} else if (requestStreamReaderHolder.getAssociatedObject() instanceof Map) {
117+
StringMapSpanPair stringMapSpanPair = urlEncodedMapContextStore.get(httpServletRequest);
118+
if (stringMapSpanPair != null) {
119+
stringMapSpanPair.captureBody(HypertraceSemanticAttributes.HTTP_REQUEST_BODY);
120+
urlEncodedMapContextStore.set(httpServletRequest, null);
121+
}
117122
}
118123
}
119124
}

instrumentation/servlet/servlet-3.0/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/servlet/v3_0/nowrapping/async/BodyCaptureAsyncListener.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,7 @@
3131
import org.hypertrace.agent.core.config.InstrumentationConfig;
3232
import org.hypertrace.agent.core.instrumentation.HypertraceSemanticAttributes;
3333
import org.hypertrace.agent.core.instrumentation.SpanAndObjectPair;
34-
import org.hypertrace.agent.core.instrumentation.buffer.BoundedByteArrayOutputStream;
35-
import org.hypertrace.agent.core.instrumentation.buffer.BoundedCharArrayWriter;
36-
import org.hypertrace.agent.core.instrumentation.buffer.ByteBufferSpanPair;
37-
import org.hypertrace.agent.core.instrumentation.buffer.CharBufferSpanPair;
34+
import org.hypertrace.agent.core.instrumentation.buffer.*;
3835
import org.hypertrace.agent.core.instrumentation.utils.ContentTypeUtils;
3936

4037
public final class BodyCaptureAsyncListener implements ServletAsyncListener<HttpServletResponse> {
@@ -52,6 +49,7 @@ public final class BodyCaptureAsyncListener implements ServletAsyncListener<Http
5249
private final VirtualField<HttpServletRequest, SpanAndObjectPair> requestContextStore;
5350
private final VirtualField<ServletInputStream, ByteBufferSpanPair> inputStreamContextStore;
5451
private final VirtualField<BufferedReader, CharBufferSpanPair> readerContextStore;
52+
private final VirtualField<HttpServletRequest, StringMapSpanPair> urlEncodedMapContextStore;
5553
private final HttpServletRequest request;
5654

5755
public BodyCaptureAsyncListener(
@@ -62,6 +60,7 @@ public BodyCaptureAsyncListener(
6260
VirtualField<HttpServletRequest, SpanAndObjectPair> requestContextStore,
6361
VirtualField<ServletInputStream, ByteBufferSpanPair> inputStreamContextStore,
6462
VirtualField<BufferedReader, CharBufferSpanPair> readerContextStore,
63+
VirtualField<HttpServletRequest, StringMapSpanPair> urlEncodedMapContextStore,
6564
HttpServletRequest request) {
6665
this.responseHandled = responseHandled;
6766
this.span = Span.fromContext(Servlet3Singletons.helper().getServerContext(request));
@@ -71,6 +70,7 @@ public BodyCaptureAsyncListener(
7170
this.requestContextStore = requestContextStore;
7271
this.inputStreamContextStore = inputStreamContextStore;
7372
this.readerContextStore = readerContextStore;
73+
this.urlEncodedMapContextStore = urlEncodedMapContextStore;
7474
this.request = request;
7575
}
7676

@@ -115,7 +115,11 @@ private void captureResponseDataAndClearRequestBuffer(
115115
if (instrumentationConfig.httpBody().request()
116116
&& ContentTypeUtils.shouldCapture(servletRequest.getContentType())) {
117117
Utils.resetRequestBodyBuffers(
118-
servletRequest, requestContextStore, inputStreamContextStore, readerContextStore);
118+
servletRequest,
119+
requestContextStore,
120+
inputStreamContextStore,
121+
readerContextStore,
122+
urlEncodedMapContextStore);
119123
}
120124
}
121125
}

instrumentation/servlet/servlet-3.0/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/servlet/v3_0/nowrapping/async/Servlet30AsyncInstrumentation.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,7 @@
4242
import net.bytebuddy.matcher.ElementMatcher;
4343
import org.hypertrace.agent.core.instrumentation.HypertraceCallDepthThreadLocalMap;
4444
import org.hypertrace.agent.core.instrumentation.SpanAndObjectPair;
45-
import org.hypertrace.agent.core.instrumentation.buffer.BoundedByteArrayOutputStream;
46-
import org.hypertrace.agent.core.instrumentation.buffer.BoundedCharArrayWriter;
47-
import org.hypertrace.agent.core.instrumentation.buffer.ByteBufferSpanPair;
48-
import org.hypertrace.agent.core.instrumentation.buffer.CharBufferSpanPair;
45+
import org.hypertrace.agent.core.instrumentation.buffer.*;
4946

5047
public final class Servlet30AsyncInstrumentation implements TypeInstrumentation {
5148

@@ -97,6 +94,8 @@ public static void startAsyncExit(@Advice.This ServletRequest servletRequest) {
9794
VirtualField.find(ServletInputStream.class, ByteBufferSpanPair.class);
9895
VirtualField<BufferedReader, CharBufferSpanPair> readerContextStore =
9996
VirtualField.find(BufferedReader.class, CharBufferSpanPair.class);
97+
VirtualField<HttpServletRequest, StringMapSpanPair> urlEncodedMapContextStore =
98+
VirtualField.find(HttpServletRequest.class, StringMapSpanPair.class);
10099

101100
if (servletRequest instanceof HttpServletRequest) {
102101
HttpServletRequest request = (HttpServletRequest) servletRequest;
@@ -114,6 +113,7 @@ public static void startAsyncExit(@Advice.This ServletRequest servletRequest) {
114113
requestContextStore,
115114
inputStreamContextStore,
116115
readerContextStore,
116+
urlEncodedMapContextStore,
117117
request),
118118
helper.getAsyncListenerResponse(request));
119119
accessor.setRequestAttribute(request, HYPERTRACE_ASYNC_LISTENER_ATTRIBUTE, true);

instrumentation/servlet/servlet-3.0/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/servlet/v3_0/nowrapping/request/ServletRequestInstrumentation.java

Lines changed: 92 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,14 @@
1717
package io.opentelemetry.javaagent.instrumentation.hypertrace.servlet.v3_0.nowrapping.request;
1818

1919
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasSuperType;
20-
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
21-
import static net.bytebuddy.matcher.ElementMatchers.named;
22-
import static net.bytebuddy.matcher.ElementMatchers.returns;
23-
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
20+
import static net.bytebuddy.matcher.ElementMatchers.*;
2421

2522
import io.opentelemetry.instrumentation.api.field.VirtualField;
2623
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
2724
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
2825
import java.io.BufferedReader;
26+
import java.util.HashMap;
27+
import java.util.Map;
2928
import javax.servlet.ServletInputStream;
3029
import javax.servlet.ServletRequest;
3130
import javax.servlet.http.HttpServletRequest;
@@ -36,6 +35,7 @@
3635
import org.hypertrace.agent.core.instrumentation.SpanAndObjectPair;
3736
import org.hypertrace.agent.core.instrumentation.buffer.ByteBufferSpanPair;
3837
import org.hypertrace.agent.core.instrumentation.buffer.CharBufferSpanPair;
38+
import org.hypertrace.agent.core.instrumentation.buffer.StringMapSpanPair;
3939

4040
public class ServletRequestInstrumentation implements TypeInstrumentation {
4141

@@ -58,6 +58,13 @@ public void transform(TypeTransformer transformer) {
5858
// .and(returns(BufferedReader.class))
5959
.and(isPublic()),
6060
ServletRequestInstrumentation.class.getName() + "$ServletRequest_getReader_advice");
61+
transformer.applyAdviceToMethod(
62+
named("getParameter")
63+
.and(takesArguments(1))
64+
.and(takesArgument(0, is(String.class)))
65+
.and(returns(String.class))
66+
.and(isPublic()),
67+
ServletRequestInstrumentation.class.getName() + "$ServletRequest_getParameter_advice");
6168
}
6269

6370
static class ServletRequest_getInputStream_advice {
@@ -165,4 +172,85 @@ public static void exit(
165172
spanAndObjectPair.setAssociatedObject(reader);
166173
}
167174
}
175+
176+
/** Provides instrumentation template for ServletRequest.getParameter() method. */
177+
static class ServletRequest_getParameter_advice {
178+
179+
/**
180+
* Instrumentation template for ServletRequest.getParameter() entry point.
181+
*
182+
* @param servletRequest
183+
* @return a (possibly null) SpanAndObjectPair, which will be passed to the method exit
184+
* instrumentation
185+
*/
186+
@Advice.OnMethodEnter(suppress = Throwable.class)
187+
public static SpanAndObjectPair enter(@Advice.This ServletRequest servletRequest) {
188+
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
189+
SpanAndObjectPair spanAndObjectPair =
190+
VirtualField.find(HttpServletRequest.class, SpanAndObjectPair.class)
191+
.get(httpServletRequest);
192+
if (spanAndObjectPair == null) {
193+
return null;
194+
}
195+
196+
HypertraceCallDepthThreadLocalMap.incrementCallDepth(ServletRequest.class);
197+
return spanAndObjectPair;
198+
}
199+
200+
/**
201+
* Instrumentation template for ServletRequest.getParameter() exit point(s).
202+
*
203+
* @param servletRequest the ServletRequest instance
204+
* @param returnValue the value that is being returned by getParameter()
205+
* @param parmName the argument that was passed to getParameter()
206+
* @param throwable the Throwable object, if exiting method because of a 'throw'
207+
* @param spanAndObjectPair the value returned by the getParameter() method entry
208+
* instrumentation
209+
*/
210+
@Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
211+
public static void exit(
212+
@Advice.This ServletRequest servletRequest,
213+
@Advice.Return String returnValue,
214+
@Advice.Argument(0) String parmName,
215+
@Advice.Thrown Throwable throwable,
216+
@Advice.Enter SpanAndObjectPair spanAndObjectPair) {
217+
if (spanAndObjectPair == null) {
218+
return;
219+
}
220+
221+
int callDepth = HypertraceCallDepthThreadLocalMap.decrementCallDepth(ServletRequest.class);
222+
if (callDepth > 0) {
223+
return;
224+
}
225+
226+
if (returnValue == null) {
227+
return;
228+
}
229+
230+
if (!(servletRequest instanceof HttpServletRequest) || throwable != null) {
231+
return;
232+
}
233+
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
234+
235+
Map<String, String> stringMap;
236+
237+
VirtualField<HttpServletRequest, StringMapSpanPair> contextStore =
238+
VirtualField.find(HttpServletRequest.class, StringMapSpanPair.class);
239+
240+
StringMapSpanPair stringMapSpanPair = contextStore.get(httpServletRequest);
241+
242+
if (stringMapSpanPair != null) {
243+
stringMap = stringMapSpanPair.stringMap;
244+
} else {
245+
stringMap = new HashMap<>();
246+
stringMapSpanPair =
247+
Utils.createStringMapSpanPair(
248+
stringMap, spanAndObjectPair.getSpan(), spanAndObjectPair.getHeaders());
249+
contextStore.set(httpServletRequest, stringMapSpanPair);
250+
}
251+
252+
stringMap.put(parmName, returnValue);
253+
spanAndObjectPair.setAssociatedObject(stringMap);
254+
}
255+
}
168256
}

instrumentation/servlet/servlet-3.0/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/servlet/v3_0/nowrapping/request/Utils.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.hypertrace.agent.core.instrumentation.buffer.BoundedBuffersFactory;
2424
import org.hypertrace.agent.core.instrumentation.buffer.ByteBufferSpanPair;
2525
import org.hypertrace.agent.core.instrumentation.buffer.CharBufferSpanPair;
26+
import org.hypertrace.agent.core.instrumentation.buffer.StringMapSpanPair;
2627
import org.hypertrace.agent.core.instrumentation.utils.ContentLengthUtils;
2728
import org.hypertrace.agent.core.instrumentation.utils.ContentTypeCharsetUtils;
2829
import org.hypertrace.agent.filter.FilterRegistry;
@@ -61,4 +62,17 @@ public static CharBufferSpanPair createRequestCharBufferSpanPair(
6162
filter::evaluateRequestBody,
6263
headers);
6364
}
65+
66+
/**
67+
* Create a StringMapSpanPair.
68+
*
69+
* @param stringMap
70+
* @param span
71+
* @param headers
72+
* @return
73+
*/
74+
public static StringMapSpanPair createStringMapSpanPair(
75+
Map<String, String> stringMap, Span span, Map<String, String> headers) {
76+
return new StringMapSpanPair(span, stringMap, headers);
77+
}
6478
}

javaagent-core/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ dependencies {
99
api("io.opentelemetry:opentelemetry-api:${versions["opentelemetry"]}")
1010
api("io.opentelemetry.javaagent:opentelemetry-javaagent-instrumentation-api:${versions["opentelemetry_java_agent"]}")
1111
implementation("org.slf4j:slf4j-api:${versions["slf4j"]}")
12+
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.13.3")
1213
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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 org.hypertrace.agent.core.instrumentation.buffer;
18+
19+
import static org.hypertrace.agent.core.instrumentation.utils.ContentTypeUtils.convertToJSONString;
20+
21+
import io.opentelemetry.api.common.AttributeKey;
22+
import io.opentelemetry.api.trace.Span;
23+
import java.util.Map;
24+
25+
/** Created to represent the request body that is in x-www-form-urlencoded format. */
26+
public class StringMapSpanPair {
27+
28+
public final Span span;
29+
public final Map<String, String> headers;
30+
public final Map<String, String> stringMap;
31+
32+
/** A flag to signalize that map has been added to span. */
33+
private boolean mapCaptured;
34+
35+
public StringMapSpanPair(Span span, Map<String, String> stringMap, Map<String, String> headers) {
36+
this.span = span;
37+
this.stringMap = stringMap;
38+
this.headers = headers;
39+
}
40+
41+
/**
42+
* Return a JSON string representing the contents of the x-www-form-urlencoded body.
43+
*
44+
* @param attributeKey
45+
*/
46+
public void captureBody(AttributeKey<String> attributeKey) {
47+
if (!mapCaptured) {
48+
String json = convertToJSONString(stringMap);
49+
span.setAttribute(attributeKey, json);
50+
mapCaptured = true;
51+
}
52+
}
53+
}

javaagent-core/src/main/java/org/hypertrace/agent/core/instrumentation/utils/ContentTypeUtils.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616

1717
package org.hypertrace.agent.core.instrumentation.utils;
1818

19+
import com.fasterxml.jackson.core.JsonProcessingException;
20+
import com.fasterxml.jackson.databind.ObjectMapper;
21+
1922
public class ContentTypeUtils {
2023

2124
private ContentTypeUtils() {}
@@ -62,4 +65,18 @@ public static String parseCharset(String contentType) {
6265
}
6366
return null;
6467
}
68+
69+
/**
70+
* Converts an arbitrary object into JSON format.
71+
*
72+
* @param obj
73+
* @return
74+
*/
75+
public static String convertToJSONString(Object obj) {
76+
try {
77+
return new ObjectMapper().writeValueAsString(obj);
78+
} catch (JsonProcessingException e) {
79+
throw new RuntimeException(e);
80+
}
81+
}
6582
}

0 commit comments

Comments
 (0)