6262import co .elastic .apm .agent .impl .transaction .TransactionImpl ;
6363import co .elastic .apm .agent .report .ApmServerClient ;
6464import co .elastic .apm .agent .sdk .internal .collections .LongList ;
65+ import co .elastic .apm .agent .sdk .internal .pooling .ObjectHandle ;
66+ import co .elastic .apm .agent .sdk .internal .pooling .ObjectPool ;
67+ import co .elastic .apm .agent .sdk .internal .pooling .ObjectPooling ;
68+ import co .elastic .apm .agent .sdk .internal .util .IOUtils ;
6569import co .elastic .apm .agent .sdk .logging .Logger ;
6670import co .elastic .apm .agent .sdk .logging .LoggerFactory ;
71+ import co .elastic .apm .agent .tracer .configuration .WebConfiguration ;
6772import co .elastic .apm .agent .tracer .metadata .PotentiallyMultiValuedMap ;
6873import co .elastic .apm .agent .tracer .metrics .DslJsonUtil ;
6974import co .elastic .apm .agent .tracer .metrics .Labels ;
7984import java .io .File ;
8085import java .io .IOException ;
8186import java .io .OutputStream ;
87+ import java .nio .Buffer ;
8288import java .nio .ByteBuffer ;
8389import java .nio .CharBuffer ;
90+ import java .nio .charset .CoderResult ;
8491import java .nio .charset .StandardCharsets ;
8592import java .util .ArrayList ;
8693import java .util .Arrays ;
8794import java .util .Collections ;
8895import java .util .Iterator ;
8996import java .util .List ;
9097import java .util .Map ;
98+ import java .util .concurrent .Callable ;
9199import java .util .concurrent .Future ;
92100import java .util .concurrent .TimeUnit ;
93101
@@ -104,6 +112,13 @@ public class DslJsonSerializer {
104112 private static final Logger logger = LoggerFactory .getLogger (DslJsonSerializer .class );
105113 private static final List <String > excludedStackFramesPrefixes = Arrays .asList ("java.lang.reflect." , "com.sun." , "sun." , "jdk.internal." );
106114
115+ private static final ObjectPool <? extends ObjectHandle <CharBuffer >> REQUEST_BODY_BUFFER_POOL = ObjectPooling .createWithDefaultFactory (new Callable <CharBuffer >() {
116+ @ Override
117+ public CharBuffer call () throws Exception {
118+ return CharBuffer .allocate (WebConfiguration .MAX_BODY_CAPTURE_BYTES );
119+ }
120+ });
121+
107122
108123 private final StacktraceConfigurationImpl stacktraceConfiguration ;
109124 private final ApmServerClient apmServerClient ;
@@ -1054,22 +1069,7 @@ private void serializeSpanLinks(List<TraceContextImpl> spanLinks) {
10541069 }
10551070
10561071 private void serializeOTel (SpanImpl span ) {
1057- serializeOtel (span , Collections .<IdImpl >emptyList (), requestBodyToString (span .getContext ().getHttp ().getRequestBody ()));
1058- }
1059-
1060- @ Nullable
1061- private CharSequence requestBodyToString (BodyCaptureImpl requestBody ) {
1062- //TODO: perform proper, charset aware conversion to string
1063- ByteBuffer buffer = requestBody .getBody ();
1064- if (buffer == null || buffer .position () == 0 ) {
1065- return null ;
1066- }
1067- buffer .flip ();
1068- StringBuilder result = new StringBuilder ();
1069- while (buffer .hasRemaining ()) {
1070- result .append ((char ) buffer .get ());
1071- }
1072- return result ;
1072+ serializeOtel (span , Collections .<IdImpl >emptyList (), span .getContext ().getHttp ().getRequestBody ());
10731073 }
10741074
10751075 private void serializeOTel (TransactionImpl transaction ) {
@@ -1079,11 +1079,12 @@ private void serializeOTel(TransactionImpl transaction) {
10791079 }
10801080 }
10811081
1082- private void serializeOtel (AbstractSpanImpl <?> span , List <IdImpl > profilingStackTraceIds , @ Nullable CharSequence httpRequestBody ) {
1082+ private void serializeOtel (AbstractSpanImpl <?> span , List <IdImpl > profilingStackTraceIds , @ Nullable BodyCaptureImpl httpRequestBody ) {
10831083 OTelSpanKind kind = span .getOtelKind ();
10841084 Map <String , Object > attributes = span .getOtelAttributes ();
10851085
1086- boolean hasAttributes = !attributes .isEmpty () || !profilingStackTraceIds .isEmpty () || httpRequestBody != null ;
1086+ boolean hasRequestBody = httpRequestBody != null && httpRequestBody .getBody () != null ;
1087+ boolean hasAttributes = !attributes .isEmpty () || !profilingStackTraceIds .isEmpty () || hasRequestBody ;
10871088 boolean hasKind = kind != null ;
10881089 if (hasKind || hasAttributes ) {
10891090 writeFieldName ("otel" );
@@ -1133,12 +1134,12 @@ private void serializeOtel(AbstractSpanImpl<?> span, List<IdImpl> profilingStack
11331134 }
11341135 jw .writeByte (ARRAY_END );
11351136 }
1136- if (httpRequestBody != null ) {
1137+ if (hasRequestBody ) {
11371138 if (!isFirstAttrib ) {
11381139 jw .writeByte (COMMA );
11391140 }
11401141 writeFieldName ("http.request.body.content" );
1141- jw . writeString ( httpRequestBody );
1142+ writeRequestBodyAsString ( jw , httpRequestBody );
11421143 }
11431144 jw .writeByte (OBJECT_END );
11441145 }
@@ -1148,6 +1149,38 @@ private void serializeOtel(AbstractSpanImpl<?> span, List<IdImpl> profilingStack
11481149 }
11491150 }
11501151
1152+
1153+ private void writeRequestBodyAsString (JsonWriter jw , BodyCaptureImpl requestBody ) {
1154+ try (ObjectHandle <CharBuffer > charBufferHandle = REQUEST_BODY_BUFFER_POOL .createInstance ()) {
1155+ CharBuffer charBuffer = charBufferHandle .get ();
1156+ try {
1157+ decodeRequestBodyBytes (requestBody , charBuffer );
1158+ charBuffer .flip ();
1159+ jw .writeString (charBuffer );
1160+ } finally {
1161+ ((Buffer ) charBuffer ).clear ();
1162+ }
1163+ }
1164+ }
1165+
1166+ private void decodeRequestBodyBytes (BodyCaptureImpl requestBody , CharBuffer charBuffer ) {
1167+ ByteBuffer bodyBytes = requestBody .getBody ();
1168+ ((Buffer ) bodyBytes ).flip (); //make ready for reading
1169+ CharSequence charset = requestBody .getCharset ();
1170+ if (charset != null ) {
1171+ CoderResult result = IOUtils .decode (bodyBytes , charBuffer , charset .toString ());
1172+ if (result != null && !result .isMalformed () && !result .isUnmappable ()) {
1173+ return ;
1174+ }
1175+ }
1176+ //fallback to decoding by simply casting bytes to chars
1177+ ((Buffer ) bodyBytes ).position (0 );
1178+ ((Buffer ) charBuffer ).clear ();
1179+ while (bodyBytes .hasRemaining ()) {
1180+ charBuffer .put ((char ) (((int ) bodyBytes .get ()) & 0xFF ));
1181+ }
1182+ }
1183+
11511184 private void serializeNumber (Number n , JsonWriter jw ) {
11521185 if (n instanceof Integer ) {
11531186 NumberConverter .serialize (n .intValue (), jw );
0 commit comments