Skip to content

Commit c0f6c65

Browse files
committed
Add outer exceptions support for Exception Replay
In some cases, the innermost exception could contain only third-party frames and no instrumentation is performed while the exception could be re-throw up in the stack where the call originated. This could help to root cause the inner exception. Now in that case we are climbing up the chained of exceptions to find user frames to instrument. when finding the innermost exception, we are reporting the list of all outer exceptions beginning by the innermost. The chained index is also kept in the probe and injected into the snapshot to be able to remap the frame index correctly and report it to the span as tag.
1 parent 21c0b2d commit c0f6c65

File tree

8 files changed

+215
-78
lines changed

8 files changed

+215
-78
lines changed

dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/exception/DefaultExceptionDebugger.java

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
1515
import datadog.trace.util.AgentTaskScheduler;
1616
import java.time.Duration;
17+
import java.util.ArrayDeque;
18+
import java.util.ArrayList;
19+
import java.util.Deque;
1720
import java.util.List;
1821
import org.slf4j.Logger;
1922
import org.slf4j.LoggerFactory;
@@ -75,32 +78,42 @@ public void handleException(Throwable t, AgentSpan span) {
7578
LOGGER.debug("Unable to fingerprint exception", t);
7679
return;
7780
}
78-
Throwable innerMostException = ExceptionHelper.getInnerMostThrowable(t);
81+
Deque<Throwable> chainedExceptions = new ArrayDeque<>();
82+
Throwable innerMostException = ExceptionHelper.getInnerMostThrowable(t, chainedExceptions);
7983
if (innerMostException == null) {
8084
LOGGER.debug("Unable to find root cause of exception");
8185
return;
8286
}
87+
List<Throwable> chainedExceptionsList = new ArrayList<>(chainedExceptions);
8388
if (exceptionProbeManager.isAlreadyInstrumented(fingerprint)) {
8489
ThrowableState state = exceptionProbeManager.getStateByThrowable(innerMostException);
8590
if (state == null) {
8691
LOGGER.debug("Unable to find state for throwable: {}", innerMostException.toString());
8792
return;
8893
}
89-
processSnapshotsAndSetTags(t, span, state, innerMostException, fingerprint);
94+
processSnapshotsAndSetTags(t, span, state, chainedExceptionsList, fingerprint);
9095
exceptionProbeManager.updateLastCapture(fingerprint);
9196
} else {
92-
ExceptionProbeManager.CreationResult creationResult =
93-
exceptionProbeManager.createProbesForException(innerMostException.getStackTrace());
94-
if (creationResult.probesCreated > 0) {
95-
AgentTaskScheduler.INSTANCE.execute(() -> applyExceptionConfiguration(fingerprint));
96-
} else {
97-
if (LOGGER.isDebugEnabled()) {
98-
LOGGER.debug(
99-
"No probe created, nativeFrames={}, thirdPartyFrames={} for exception: {}",
100-
creationResult.nativeFrames,
101-
creationResult.thirdPartyFrames,
102-
ExceptionHelper.foldExceptionStackTrace(innerMostException));
97+
// climb up the exception chain to find the first exception that has instrumented frames
98+
Throwable throwable;
99+
int chainedExceptionIdx = 0;
100+
while ((throwable = chainedExceptions.pollFirst()) != null) {
101+
ExceptionProbeManager.CreationResult creationResult =
102+
exceptionProbeManager.createProbesForException(
103+
throwable.getStackTrace(), chainedExceptionIdx);
104+
if (creationResult.probesCreated > 0) {
105+
AgentTaskScheduler.INSTANCE.execute(() -> applyExceptionConfiguration(fingerprint));
106+
break;
107+
} else {
108+
if (LOGGER.isDebugEnabled()) {
109+
LOGGER.debug(
110+
"No probe created, nativeFrames={}, thirdPartyFrames={} for exception: {}",
111+
creationResult.nativeFrames,
112+
creationResult.thirdPartyFrames,
113+
ExceptionHelper.foldExceptionStackTrace(throwable));
114+
}
103115
}
116+
chainedExceptionIdx++;
104117
}
105118
}
106119
}
@@ -114,7 +127,7 @@ private static void processSnapshotsAndSetTags(
114127
Throwable t,
115128
AgentSpan span,
116129
ThrowableState state,
117-
Throwable innerMostException,
130+
List<Throwable> chainedExceptions,
118131
String fingerprint) {
119132
if (span.getTag(DD_DEBUG_ERROR_EXCEPTION_ID) != null) {
120133
LOGGER.debug("Clear previous frame tags");
@@ -127,12 +140,13 @@ private static void processSnapshotsAndSetTags(
127140
}
128141
});
129142
}
130-
int[] mapping = createThrowableMapping(innerMostException, t);
131-
StackTraceElement[] innerTrace = innerMostException.getStackTrace();
132143
boolean snapshotAssigned = false;
133144
List<Snapshot> snapshots = state.getSnapshots();
134145
for (int i = 0; i < snapshots.size(); i++) {
135146
Snapshot snapshot = snapshots.get(i);
147+
Throwable currentEx = chainedExceptions.get(snapshot.getChainedExceptionIdx());
148+
int[] mapping = createThrowableMapping(currentEx, t);
149+
StackTraceElement[] innerTrace = currentEx.getStackTrace();
136150
int currentIdx = innerTrace.length - snapshot.getStack().size();
137151
if (!sanityCheckSnapshotAssignment(snapshot, innerTrace, currentIdx)) {
138152
continue;

dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/exception/ExceptionProbeManager.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ public CreationResult(int probesCreated, int thirdPartyFrames, int nativeFrames)
7979
}
8080
}
8181

82-
public CreationResult createProbesForException(StackTraceElement[] stackTraceElements) {
82+
public CreationResult createProbesForException(
83+
StackTraceElement[] stackTraceElements, int chainedExceptionIdx) {
8384
int instrumentedFrames = 0;
8485
int nativeFrames = 0;
8586
int thirdPartyFrames = 0;
@@ -102,7 +103,7 @@ public CreationResult createProbesForException(StackTraceElement[] stackTraceEle
102103
stackTraceElement.getMethodName(),
103104
null,
104105
String.valueOf(stackTraceElement.getLineNumber()));
105-
ExceptionProbe probe = createMethodProbe(this, where);
106+
ExceptionProbe probe = createMethodProbe(this, where, chainedExceptionIdx);
106107
probes.putIfAbsent(probe.getId(), probe);
107108
instrumentedFrames++;
108109
}
@@ -114,10 +115,16 @@ void addFingerprint(String fingerprint) {
114115
}
115116

116117
private static ExceptionProbe createMethodProbe(
117-
ExceptionProbeManager exceptionProbeManager, Where where) {
118+
ExceptionProbeManager exceptionProbeManager, Where where, int chainedExceptionIdx) {
118119
String probeId = UUID.randomUUID().toString();
119120
return new ExceptionProbe(
120-
new ProbeId(probeId, 0), where, null, null, null, exceptionProbeManager);
121+
new ProbeId(probeId, 0),
122+
where,
123+
null,
124+
null,
125+
null,
126+
exceptionProbeManager,
127+
chainedExceptionIdx);
121128
}
122129

123130
public boolean isAlreadyInstrumented(String fingerprint) {

dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/probe/ExceptionProbe.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,16 @@
2121
public class ExceptionProbe extends LogProbe implements ForceMethodInstrumentation {
2222
private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionProbe.class);
2323
private final transient ExceptionProbeManager exceptionProbeManager;
24+
private final transient int chainedExceptionIdx;
2425

2526
public ExceptionProbe(
2627
ProbeId probeId,
2728
Where where,
2829
ProbeCondition probeCondition,
2930
Capture capture,
3031
Sampling sampling,
31-
ExceptionProbeManager exceptionProbeManager) {
32+
ExceptionProbeManager exceptionProbeManager,
33+
int chainedExceptionIdx) {
3234
super(
3335
LANGUAGE,
3436
probeId,
@@ -42,6 +44,7 @@ public ExceptionProbe(
4244
capture,
4345
sampling);
4446
this.exceptionProbeManager = exceptionProbeManager;
47+
this.chainedExceptionIdx = chainedExceptionIdx;
4548
}
4649

4750
@Override
@@ -116,6 +119,7 @@ public void commit(
116119
// inside the stateByThrowable map
117120
clearExceptionRefs(snapshot);
118121
// add snapshot for later to wait for triggering point (ExceptionDebugger::handleException)
122+
snapshot.setChainedExceptionIdx(chainedExceptionIdx);
119123
exceptionProbeManager.addSnapshot(snapshot);
120124
LOGGER.debug(
121125
"committing exception probe id={}, snapshot id={}, exception id={}",

dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/sink/Snapshot.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public class Snapshot {
3333
private transient String message;
3434
private final transient int maxDepth;
3535
private String exceptionId;
36+
private transient int chainedExceptionIdx;
3637

3738
public Snapshot(java.lang.Thread thread, ProbeImplementation probeImplementation, int maxDepth) {
3839
this.version = VERSION;
@@ -91,6 +92,10 @@ public void setExceptionId(String exceptionId) {
9192
this.exceptionId = exceptionId;
9293
}
9394

95+
public void setChainedExceptionIdx(int chainedExceptionIdx) {
96+
this.chainedExceptionIdx = chainedExceptionIdx;
97+
}
98+
9499
public void addLine(CapturedContext context, int line) {
95100
captures.addLine(line, context);
96101
}
@@ -178,6 +183,10 @@ public String getExceptionId() {
178183
return exceptionId;
179184
}
180185

186+
public int getChainedExceptionIdx() {
187+
return chainedExceptionIdx;
188+
}
189+
181190
public void recordStackTrace(int offset) {
182191
stack.clear();
183192
int cntr = 0;

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import java.io.Writer;
77
import java.util.ArrayList;
88
import java.util.Arrays;
9+
import java.util.Deque;
910
import java.util.List;
1011
import org.slf4j.Logger;
1112

@@ -60,9 +61,19 @@ public void write(String s, int off, int len) {
6061
}
6162

6263
public static Throwable getInnerMostThrowable(Throwable t) {
64+
return getInnerMostThrowable(t, null);
65+
}
66+
67+
public static Throwable getInnerMostThrowable(Throwable t, Deque<Throwable> chainedExceptions) {
68+
if (chainedExceptions != null) {
69+
chainedExceptions.addFirst(t);
70+
}
6371
int i = 100;
6472
while (t.getCause() != null && i > 0) {
6573
t = t.getCause();
74+
if (chainedExceptions != null) {
75+
chainedExceptions.addFirst(t);
76+
}
6677
i--;
6778
}
6879
if (i == 0) {

0 commit comments

Comments
 (0)