Skip to content

Commit 9b3701a

Browse files
committed
Truncate large exception message when serialized
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 0d75905 commit 9b3701a

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.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)