Skip to content

Commit

Permalink
RUM-3757: Make sure error.threads always have content from error.stack
Browse files Browse the repository at this point in the history
  • Loading branch information
0xnm committed Apr 3, 2024
1 parent fb5a1de commit dac8d88
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ internal class DatadogExceptionHandler(
// region Thread.UncaughtExceptionHandler

override fun uncaughtException(t: Thread, e: Throwable) {
val threads = getAllThreadsDump(t, e)
val threads = getThreadDumps(t, e)
// write the log immediately
val logsFeature = sdkCore.getFeature(Feature.LOGS_FEATURE_NAME)
if (logsFeature != null) {
Expand Down Expand Up @@ -121,38 +121,39 @@ internal class DatadogExceptionHandler(
}
}

private fun getAllThreadsDump(
crashedThread: Thread,
crashException: Throwable
): List<ThreadDump> {
private fun getThreadDumps(crashedThread: Thread, e: Throwable): List<ThreadDump> {
return mutableListOf(
ThreadDump(
crashed = true,
name = crashedThread.name,
state = crashedThread.state.asString(),
stack = e.loggableStackTrace()
)
) + safeGetAllStacktraces()
.filterKeys { it != crashedThread }
.filterValues { it.isNotEmpty() }
.map {
val thread = it.key
ThreadDump(
name = thread.name,
state = thread.state.asString(),
stack = it.value.loggableStackTrace(),
crashed = false
)
}
}

private fun safeGetAllStacktraces(): Map<Thread, Array<StackTraceElement>> {
return try {
Thread.getAllStackTraces()
// from getAllStackTraces: A zero-length array will be returned in the map value if
// the virtual machine has no stack trace information about a thread.
.filterValues { it.isNotEmpty() }
.map {
val thread = it.key
val isCrashedThread = thread == crashedThread
val stack = if (isCrashedThread) {
crashException.loggableStackTrace()
} else {
thread.stackTrace.loggableStackTrace()
}
ThreadDump(
name = thread.name,
state = thread.state.asString(),
stack = stack,
crashed = isCrashedThread
)
}
} catch (e: SecurityException) {
sdkCore.internalLogger.log(
InternalLogger.Level.ERROR,
InternalLogger.Target.MAINTAINER,
{ "Failed to get all threads dump" },
e
)
emptyList()
emptyMap()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@ internal class DatadogExceptionHandlerTest {
val crashedThread = first { it.crashed }
assertThat(crashedThread.name).isEqualTo(currentThread.name)
assertThat(crashedThread.stack).isEqualTo(fakeThrowable.loggableStackTrace())
val nonCrashedThreadNames = filterNot { it.crashed }.map { it.name }
assertThat(nonCrashedThreadNames).doesNotContain(crashedThread.name)
}
}
verifyNoInteractions(mockPreviousHandler)
Expand Down Expand Up @@ -205,6 +207,9 @@ internal class DatadogExceptionHandlerTest {
val crashedThread = first { it.crashed }
assertThat(crashedThread.name).isEqualTo(currentThread.name)
assertThat(crashedThread.stack).isEqualTo(fakeThrowable.loggableStackTrace())
val nonCrashedThreadNames = filterNot { it.crashed }.map { it.name }
assertThat(nonCrashedThreadNames).isNotEmpty
assertThat(nonCrashedThreadNames).doesNotContain(crashedThread.name)
}
}
verify(mockPreviousHandler).uncaughtException(currentThread, fakeThrowable)
Expand Down Expand Up @@ -240,6 +245,9 @@ internal class DatadogExceptionHandlerTest {
val crashedThread = first { it.crashed }
assertThat(crashedThread.name).isEqualTo(currentThread.name)
assertThat(crashedThread.stack).isEqualTo(throwable.loggableStackTrace())
val nonCrashedThreadNames = filterNot { it.crashed }.map { it.name }
assertThat(nonCrashedThreadNames).isNotEmpty
assertThat(nonCrashedThreadNames).doesNotContain(crashedThread.name)
}
}
verify(mockPreviousHandler).uncaughtException(currentThread, throwable)
Expand Down Expand Up @@ -275,6 +283,9 @@ internal class DatadogExceptionHandlerTest {
val crashedThread = first { it.crashed }
assertThat(crashedThread.name).isEqualTo(currentThread.name)
assertThat(crashedThread.stack).isEqualTo(throwable.loggableStackTrace())
val nonCrashedThreadNames = filterNot { it.crashed }.map { it.name }
assertThat(nonCrashedThreadNames).isNotEmpty
assertThat(nonCrashedThreadNames).doesNotContain(crashedThread.name)
}
}
verify(mockPreviousHandler).uncaughtException(currentThread, throwable)
Expand Down Expand Up @@ -318,6 +329,9 @@ internal class DatadogExceptionHandlerTest {
val crashedThread = first { it.crashed }
assertThat(crashedThread.name).isEqualTo(thread.name)
assertThat(crashedThread.stack).isEqualTo(fakeThrowable.loggableStackTrace())
val nonCrashedThreadNames = filterNot { it.crashed }.map { it.name }
assertThat(nonCrashedThreadNames).isNotEmpty
assertThat(nonCrashedThreadNames).doesNotContain(crashedThread.name)
}
}
verify(mockPreviousHandler).uncaughtException(thread, fakeThrowable)
Expand Down Expand Up @@ -358,6 +372,9 @@ internal class DatadogExceptionHandlerTest {
assertThat(filter { it.crashed }).hasSize(1)
assertThat(first { it.crashed }.name).isEqualTo(crashedThread.name)
assertThat(first { it.crashed }.stack).isEqualTo(fakeThrowable.loggableStackTrace())
val nonCrashedThreadNames = filterNot { it.crashed }.map { it.name }
assertThat(nonCrashedThreadNames).isNotEmpty
assertThat(nonCrashedThreadNames).doesNotContain(crashedThread.name)
}
}
verify(mockPreviousHandler).uncaughtException(crashedThread, fakeThrowable)
Expand Down Expand Up @@ -488,6 +505,9 @@ internal class DatadogExceptionHandlerTest {
val crashedThread = first { it.crashed }
assertThat(crashedThread.name).isEqualTo(currentThread.name)
assertThat(crashedThread.stack).isEqualTo(fakeThrowable.loggableStackTrace())
val nonCrashedThreadNames = filterNot { it.crashed }.map { it.name }
assertThat(nonCrashedThreadNames).isNotEmpty
assertThat(nonCrashedThreadNames).doesNotContain(crashedThread.name)
}
}
verify(mockPreviousHandler).uncaughtException(currentThread, fakeThrowable)
Expand Down Expand Up @@ -521,6 +541,9 @@ internal class DatadogExceptionHandlerTest {
val crashedThread = first { it.crashed }
assertThat(crashedThread.name).isEqualTo(currentThread.name)
assertThat(crashedThread.stack).isEqualTo(throwable.loggableStackTrace())
val nonCrashedThreadNames = filterNot { it.crashed }.map { it.name }
assertThat(nonCrashedThreadNames).isNotEmpty
assertThat(nonCrashedThreadNames).doesNotContain(crashedThread.name)
}
}
verify(mockPreviousHandler).uncaughtException(currentThread, throwable)
Expand Down Expand Up @@ -552,6 +575,9 @@ internal class DatadogExceptionHandlerTest {
val crashedThread = first { it.crashed }
assertThat(crashedThread.name).isEqualTo(currentThread.name)
assertThat(crashedThread.stack).isEqualTo(throwable.loggableStackTrace())
val nonCrashedThreadNames = filterNot { it.crashed }.map { it.name }
assertThat(nonCrashedThreadNames).isNotEmpty
assertThat(nonCrashedThreadNames).doesNotContain(crashedThread.name)
}
}
verify(mockPreviousHandler).uncaughtException(currentThread, throwable)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,20 +50,22 @@ internal class ANRDetectorRunnable(
if (!callback.wasCalled()) {
val anrThread = handler.looper.thread
val anrException = ANRException(anrThread)
val allThreads = safeGetAllStacktraces()
val allThreads = mutableListOf(
ThreadDump(
name = anrThread.name,
state = anrThread.state.asString(),
stack = anrException.loggableStackTrace(),
crashed = false
)
) + safeGetAllStacktraces()
.filterKeys { it != anrThread }
.filterValues { it.isNotEmpty() }
.map {
val thread = it.key
val isAnrThread = thread == anrThread
val stack = if (isAnrThread) {
anrException.loggableStackTrace()
} else {
thread.stackTrace.loggableStackTrace()
}
ThreadDump(
name = thread.name,
state = thread.state.asString(),
stack = stack,
stack = thread.stackTrace.loggableStackTrace(),
crashed = false
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ internal class ANRDetectorRunnableTest {
val anrThread = allThreads.firstOrNull { it.name == Thread.currentThread().name }
check(anrThread != null)
assertThat(anrThread.stack).isEqualTo(anrExceptionCaptor.lastValue.loggableStackTrace())
assertThat(allThreads.filter { it.name == anrThread.name }).hasSize(1)
assertThat(allThreads.filterNot { it.name == anrThread.name }).isNotEmpty
}

argumentCaptor<Runnable> {
Expand Down

0 comments on commit dac8d88

Please sign in to comment.