Skip to content

Commit a8ca740

Browse files
authored
Truncate large exception message when serialized (#9686)
if exception message is larger than 2KB we truncating it for serialization into snapshot in throwable attribute. a larger message cam lead to backend drop.
1 parent 054a9d5 commit a8ca740

File tree

4 files changed

+125
-11
lines changed

4 files changed

+125
-11
lines changed

dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/JsonSnapshotSerializer.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@
1212

1313
/** Serializes snapshots in Json using Moshi */
1414
public class JsonSnapshotSerializer implements DebuggerContext.ValueSerializer {
15-
private static final String DD_TRACE_ID = "dd.trace_id";
16-
private static final String DD_SPAN_ID = "dd.span_id";
1715
private static final JsonAdapter<IntakeRequest> ADAPTER =
1816
MoshiHelper.createMoshiSnapshot().adapter(IntakeRequest.class);
1917
private static final JsonAdapter<CapturedContext.CapturedValue> VALUE_ADAPTER =

dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/MoshiSnapshotHelper.java

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import com.squareup.moshi.Moshi;
88
import com.squareup.moshi.Types;
99
import datadog.trace.bootstrap.debugger.CapturedContext;
10+
import datadog.trace.bootstrap.debugger.CapturedStackFrame;
1011
import datadog.trace.bootstrap.debugger.Limits;
1112
import datadog.trace.bootstrap.debugger.ProbeImplementation;
1213
import datadog.trace.bootstrap.debugger.ProbeLocation;
@@ -59,20 +60,25 @@ public class MoshiSnapshotHelper {
5960
public static final String ID = "id";
6061
public static final String VERSION = "version";
6162
public static final String LOCATION = "location";
63+
public static final String MESSAGE = "message";
64+
public static final String STACKTRACE = "stacktrace";
6265
private static final Duration TIME_OUT = Duration.ofMillis(200);
6366

6467
public static class SnapshotJsonFactory implements JsonAdapter.Factory {
6568
@Override
6669
public JsonAdapter<?> create(Type type, Set<? extends Annotation> set, Moshi moshi) {
6770
if (Types.equals(type, Snapshot.Captures.class)) {
6871
return new CapturesAdapter(
69-
moshi, new CapturedContextAdapter(moshi, new CapturedValueAdapter()));
72+
moshi,
73+
new CapturedContextAdapter(
74+
moshi, new CapturedValueAdapter(), new CapturedThrowableAdapter(moshi)));
7075
}
7176
if (Types.equals(type, CapturedContext.CapturedValue.class)) {
7277
return new CapturedValueAdapter();
7378
}
7479
if (Types.equals(type, CapturedContext.class)) {
75-
return new CapturedContextAdapter(moshi, new CapturedValueAdapter());
80+
return new CapturedContextAdapter(
81+
moshi, new CapturedValueAdapter(), new CapturedThrowableAdapter(moshi));
7682
}
7783
if (Types.equals(type, ProbeImplementation.class)) {
7884
return new ProbeDetailsAdapter(moshi);
@@ -128,9 +134,11 @@ public static class CapturedContextAdapter extends JsonAdapter<CapturedContext>
128134
protected final JsonAdapter<CapturedContext.CapturedValue> valueAdapter;
129135

130136
public CapturedContextAdapter(
131-
Moshi moshi, JsonAdapter<CapturedContext.CapturedValue> valueAdapter) {
137+
Moshi moshi,
138+
JsonAdapter<CapturedContext.CapturedValue> valueAdapter,
139+
CapturedThrowableAdapter throwableAdapter) {
132140
this.valueAdapter = valueAdapter;
133-
this.throwableAdapter = moshi.adapter(CapturedContext.CapturedThrowable.class);
141+
this.throwableAdapter = throwableAdapter;
134142
}
135143

136144
@Override
@@ -516,6 +524,44 @@ public void notCaptured(String reason) throws Exception {
516524
}
517525
}
518526

527+
public static class CapturedThrowableAdapter
528+
extends JsonAdapter<CapturedContext.CapturedThrowable> {
529+
private static final int MAX_EXCEPTION_MESSAGE_LENGTH = 2048;
530+
protected final JsonAdapter<List<CapturedStackFrame>> stackTraceAdapter;
531+
532+
public CapturedThrowableAdapter(Moshi moshi) {
533+
stackTraceAdapter =
534+
moshi.adapter(Types.newParameterizedType(List.class, CapturedStackFrame.class));
535+
}
536+
537+
@Override
538+
public void toJson(JsonWriter jsonWriter, CapturedContext.CapturedThrowable value)
539+
throws IOException {
540+
if (value == null) {
541+
jsonWriter.nullValue();
542+
return;
543+
}
544+
jsonWriter.beginObject();
545+
jsonWriter.name(TYPE);
546+
jsonWriter.value(value.getType());
547+
jsonWriter.name(MESSAGE);
548+
String msg = value.getMessage();
549+
if (msg != null && msg.length() > MAX_EXCEPTION_MESSAGE_LENGTH) {
550+
msg = msg.substring(0, MAX_EXCEPTION_MESSAGE_LENGTH);
551+
}
552+
jsonWriter.value(msg);
553+
jsonWriter.name(STACKTRACE);
554+
stackTraceAdapter.toJson(jsonWriter, value.getStacktrace());
555+
jsonWriter.endObject();
556+
}
557+
558+
@Override
559+
public CapturedContext.CapturedThrowable fromJson(JsonReader reader) throws IOException {
560+
// Only used in test, see MoshiSnapshotTestHelper
561+
throw new IllegalStateException("Should not reach this code.");
562+
}
563+
}
564+
519565
public static class ProbeDetailsAdapter extends JsonAdapter<ProbeImplementation> {
520566
protected final JsonAdapter<ProbeLocation> probeLocationAdapter;
521567

dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/SnapshotSerializationTest.java

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ public void roundtripEntryReturn() throws IOException {
202202
});
203203
exitCapturedContext.addReturn(
204204
CapturedContext.CapturedValue.of(String.class.getTypeName(), "foo"));
205+
exitCapturedContext.addThrowable(new RuntimeException("Illegal argument"));
205206
snapshot.setExit(exitCapturedContext);
206207
String buffer = adapter.toJson(snapshot);
207208
System.out.println(buffer);
@@ -210,9 +211,40 @@ public void roundtripEntryReturn() throws IOException {
210211
CapturedContext exit = deserializedSnapshot.getCaptures().getReturn();
211212
Assertions.assertEquals(1, entry.getLocals().size());
212213
Assertions.assertEquals(42, entry.getLocals().get("localInt").getValue());
213-
Assertions.assertEquals(2, exit.getLocals().size());
214+
Assertions.assertEquals(3, exit.getLocals().size());
214215
Assertions.assertEquals(42, exit.getLocals().get("localInt").getValue());
215216
Assertions.assertEquals("foo", exit.getLocals().get("@return").getValue());
217+
Assertions.assertEquals(
218+
"Illegal argument",
219+
((HashMap<String, CapturedContext.CapturedValue>)
220+
exit.getLocals().get("@exception").getValue())
221+
.get("detailMessage")
222+
.getValue());
223+
Assertions.assertEquals(
224+
RuntimeException.class.getTypeName(), exit.getCapturedThrowable().getType());
225+
Assertions.assertEquals("Illegal argument", exit.getCapturedThrowable().getMessage());
226+
}
227+
228+
@Test
229+
public void truncatedExceptionMessage() throws IOException {
230+
JsonAdapter<Snapshot> adapter = createSnapshotAdapter();
231+
Snapshot snapshot = createSnapshot();
232+
CapturedContext exitCapturedContext = new CapturedContext();
233+
String oneKB =
234+
"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123";
235+
String largeErrorMessage = oneKB + oneKB + oneKB + oneKB;
236+
exitCapturedContext.addThrowable(new RuntimeException(largeErrorMessage));
237+
snapshot.setExit(exitCapturedContext);
238+
String buffer = adapter.toJson(snapshot);
239+
Snapshot deserializedSnapshot = adapter.fromJson(buffer);
240+
Assertions.assertEquals(
241+
2048,
242+
deserializedSnapshot
243+
.getCaptures()
244+
.getReturn()
245+
.getCapturedThrowable()
246+
.getMessage()
247+
.length());
216248
}
217249

218250
@Test

dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/util/MoshiSnapshotTestHelper.java

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111
import static com.datadog.debugger.util.MoshiSnapshotHelper.LINES;
1212
import static com.datadog.debugger.util.MoshiSnapshotHelper.LOCALS;
1313
import static com.datadog.debugger.util.MoshiSnapshotHelper.LOCATION;
14+
import static com.datadog.debugger.util.MoshiSnapshotHelper.MESSAGE;
1415
import static com.datadog.debugger.util.MoshiSnapshotHelper.NOT_CAPTURED_REASON;
1516
import static com.datadog.debugger.util.MoshiSnapshotHelper.RETURN;
1617
import static com.datadog.debugger.util.MoshiSnapshotHelper.SIZE;
18+
import static com.datadog.debugger.util.MoshiSnapshotHelper.STACKTRACE;
1719
import static com.datadog.debugger.util.MoshiSnapshotHelper.STATIC_FIELDS;
1820
import static com.datadog.debugger.util.MoshiSnapshotHelper.THROWABLE;
1921
import static com.datadog.debugger.util.MoshiSnapshotHelper.TRUNCATED;
@@ -28,6 +30,7 @@
2830
import com.squareup.moshi.Moshi;
2931
import com.squareup.moshi.Types;
3032
import datadog.trace.bootstrap.debugger.CapturedContext;
33+
import datadog.trace.bootstrap.debugger.CapturedStackFrame;
3134
import datadog.trace.bootstrap.debugger.ProbeId;
3235
import datadog.trace.bootstrap.debugger.ProbeImplementation;
3336
import datadog.trace.bootstrap.debugger.ProbeLocation;
@@ -80,14 +83,16 @@ public static class SnapshotJsonFactory implements JsonAdapter.Factory {
8083
public JsonAdapter<?> create(Type type, Set<? extends Annotation> set, Moshi moshi) {
8184
if (Types.equals(type, Snapshot.Captures.class)) {
8285
return new MoshiSnapshotTestHelper.CapturesAdapter(
83-
moshi, new CapturedContextAdapter(moshi, new CapturedValueAdapter()));
86+
moshi,
87+
new CapturedContextAdapter(
88+
moshi, new CapturedValueAdapter(), new CapturedThrowableAdapter(moshi)));
8489
}
8590
if (Types.equals(type, CapturedContext.CapturedValue.class)) {
8691
return new MoshiSnapshotTestHelper.CapturedValueAdapter();
8792
}
8893
if (Types.equals(type, CapturedContext.class)) {
8994
return new MoshiSnapshotTestHelper.CapturedContextAdapter(
90-
moshi, new CapturedValueAdapter());
95+
moshi, new CapturedValueAdapter(), new CapturedThrowableAdapter(moshi));
9196
}
9297
if (Types.equals(type, ProbeImplementation.class)) {
9398
return new MoshiSnapshotTestHelper.ProbeDetailsAdapter(moshi);
@@ -178,8 +183,10 @@ public static class CapturedContextAdapter extends MoshiSnapshotHelper.CapturedC
178183
new CapturedContext.CapturedValue[0];
179184

180185
public CapturedContextAdapter(
181-
Moshi moshi, JsonAdapter<CapturedContext.CapturedValue> valueAdapter) {
182-
super(moshi, valueAdapter);
186+
Moshi moshi,
187+
JsonAdapter<CapturedContext.CapturedValue> valueAdapter,
188+
MoshiSnapshotHelper.CapturedThrowableAdapter throwableAdapter) {
189+
super(moshi, valueAdapter, throwableAdapter);
183190
}
184191

185192
@Override
@@ -505,6 +512,37 @@ private static Object convertPrimitive(String strValue, String type) {
505512
}
506513
}
507514

515+
public static class CapturedThrowableAdapter
516+
extends MoshiSnapshotHelper.CapturedThrowableAdapter {
517+
public CapturedThrowableAdapter(Moshi moshi) {
518+
super(moshi);
519+
}
520+
521+
@Override
522+
public CapturedContext.CapturedThrowable fromJson(JsonReader jsonReader) throws IOException {
523+
String type = null;
524+
String message = null;
525+
List<CapturedStackFrame> stacktrace = null;
526+
jsonReader.beginObject();
527+
while (jsonReader.hasNext()) {
528+
String name = jsonReader.nextName();
529+
switch (name) {
530+
case TYPE:
531+
type = jsonReader.nextString();
532+
break;
533+
case MESSAGE:
534+
message = jsonReader.nextString();
535+
break;
536+
case STACKTRACE:
537+
stacktrace = stackTraceAdapter.fromJson(jsonReader);
538+
break;
539+
}
540+
}
541+
jsonReader.endObject();
542+
return new CapturedContext.CapturedThrowable(type, message, stacktrace, null);
543+
}
544+
}
545+
508546
public static class ProbeDetailsAdapter extends MoshiSnapshotHelper.ProbeDetailsAdapter {
509547
public ProbeDetailsAdapter(Moshi moshi) {
510548
super(moshi);

0 commit comments

Comments
 (0)