Skip to content

Commit 1ad9403

Browse files
authored
fix: if attach stack traces are enabled, it should attach current thread and its stack traces (#957)
1 parent d48b429 commit 1ad9403

File tree

7 files changed

+89
-29
lines changed

7 files changed

+89
-29
lines changed

sentry/src/main/java/io/sentry/MainEventProcessor.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,11 @@ private void processNonCachedEvent(SentryEvent event) {
8080
event.setSdk(options.getSdkVersion());
8181
}
8282

83-
if (event.getThreads() == null && options.isAttachThreads()) {
83+
if (event.getThreads() == null) {
84+
// collecting threadIds that came from the exception mechanism, so we can mark threads as
85+
// crashed properly
8486
List<Long> mechanismThreadIds = null;
8587
if (event.getExceptions() != null) {
86-
// collecting threadIds that came from the exception mechanism, so we can mark threads as
87-
// crashed properly
8888
for (SentryException item : event.getExceptions()) {
8989
if (item.getMechanism() != null && item.getThreadId() != null) {
9090
if (mechanismThreadIds == null) {
@@ -95,7 +95,12 @@ private void processNonCachedEvent(SentryEvent event) {
9595
}
9696
}
9797

98-
event.setThreads(sentryThreadFactory.getCurrentThreads(mechanismThreadIds));
98+
if (options.isAttachThreads()) {
99+
event.setThreads(sentryThreadFactory.getCurrentThreads(mechanismThreadIds));
100+
} else if (options.isAttachStacktrace()) {
101+
// when attachStacktrace is enabled, we attach only the current thread and its stack traces
102+
event.setThreads(sentryThreadFactory.getCurrentThread(mechanismThreadIds));
103+
}
99104
}
100105
}
101106
}

sentry/src/main/java/io/sentry/SentryOptions.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,12 +159,13 @@ public class SentryOptions {
159159
/** Sets the distribution. Think about it together with release and environment */
160160
private @Nullable String dist;
161161

162-
/** When enabled, threads are automatically attached to all logged events. */
162+
/** When enabled, all the threads are automatically attached to all logged events. */
163163
private boolean attachThreads;
164164

165165
/**
166166
* When enabled, stack traces are automatically attached to all threads logged. Stack traces are
167-
* always attached to exceptions but when this is set stack traces are also sent with threads
167+
* always attached to exceptions but when this is set stack traces are also sent with threads. If
168+
* no threads are logged, we log the current thread automatically.
168169
*/
169170
private boolean attachStacktrace = true;
170171

sentry/src/main/java/io/sentry/SentryStackTraceFactory.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,17 @@ List<SentryStackFrame> getStackFrames(@Nullable final StackTraceElement[] elemen
3636
sentryStackFrames = new ArrayList<>();
3737
for (StackTraceElement item : elements) {
3838
if (item != null) {
39+
40+
// we don't want to add our own frames
41+
final String className = item.getClassName();
42+
if (className.startsWith("io.sentry.")) {
43+
continue;
44+
}
45+
3946
final SentryStackFrame sentryStackFrame = new SentryStackFrame();
4047
// https://docs.sentry.io/development/sdk-dev/features/#in-app-frames
41-
sentryStackFrame.setInApp(isInApp(item.getClassName()));
42-
sentryStackFrame.setModule(item.getClassName());
48+
sentryStackFrame.setInApp(isInApp(className));
49+
sentryStackFrame.setModule(className);
4350
sentryStackFrame.setFunction(item.getMethodName());
4451
sentryStackFrame.setFilename(item.getFileName());
4552
// Protocol doesn't accept negative line numbers.

sentry/src/main/java/io/sentry/SentryThreadFactory.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import io.sentry.protocol.SentryThread;
66
import io.sentry.util.Objects;
77
import java.util.ArrayList;
8+
import java.util.HashMap;
89
import java.util.List;
910
import java.util.Map;
1011
import org.jetbrains.annotations.NotNull;
@@ -34,6 +35,22 @@ public SentryThreadFactory(
3435
this.options = Objects.requireNonNull(options, "The SentryOptions is required");
3536
}
3637

38+
/**
39+
* Converts the current thread to a SentryThread, it assumes its being called from the captured
40+
* thread.
41+
*
42+
* @param mechanismThreadIds list of threadIds that came from exception mechanism
43+
* @return a list of SentryThread with a single item or null
44+
*/
45+
@Nullable
46+
List<SentryThread> getCurrentThread(final @Nullable List<Long> mechanismThreadIds) {
47+
final Map<Thread, StackTraceElement[]> threads = new HashMap<>();
48+
final Thread currentThread = Thread.currentThread();
49+
threads.put(currentThread, currentThread.getStackTrace());
50+
51+
return getCurrentThreads(threads, mechanismThreadIds);
52+
}
53+
3754
/**
3855
* Converts a list of all current threads to a list of SentryThread Assumes its being called from
3956
* the crashed thread.

sentry/src/test/java/io/sentry/MainEventProcessorTest.kt

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ class MainEventProcessorTest {
2525
version = "1.2.3"
2626
}
2727
}
28-
fun getSut(attachThreads: Boolean = true): MainEventProcessor {
28+
fun getSut(attachThreads: Boolean = true, attachStackTrace: Boolean = true): MainEventProcessor {
2929
sentryOptions.isAttachThreads = attachThreads
30+
sentryOptions.isAttachStacktrace = attachStackTrace
3031
return MainEventProcessor(sentryOptions)
3132
}
3233
}
@@ -132,7 +133,7 @@ class MainEventProcessorTest {
132133

133134
@Test
134135
fun `when processing an event and attach threads is disabled, threads should not be set`() {
135-
val sut = fixture.getSut(false)
136+
val sut = fixture.getSut(attachThreads = false, attachStackTrace = false)
136137

137138
var event = SentryEvent()
138139
event = sut.process(event, null)
@@ -150,6 +151,16 @@ class MainEventProcessorTest {
150151
assertNotNull(event.threads)
151152
}
152153

154+
@Test
155+
fun `when processing an event and attach threads is disabled, but attach stacktrace is enabled, current thread should be set`() {
156+
val sut = fixture.getSut(attachThreads = false, attachStackTrace = true)
157+
158+
var event = SentryEvent()
159+
event = sut.process(event, null)
160+
161+
assertEquals(1, event.threads.count())
162+
}
163+
153164
@Test
154165
fun `sets sdkVersion in the event`() {
155166
val sut = fixture.getSut()

sentry/src/test/java/io/sentry/SentryStackTraceFactoryTest.kt

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ class SentryStackTraceFactoryTest {
1212
@Test
1313
fun `when getStackFrames is called passing a valid Array, not empty result`() {
1414
val stacktrace = Thread.currentThread().stackTrace
15-
val count = stacktrace.size
15+
// count the stack traces but ignores the test class which is io.sentry package
16+
val count = stacktrace.size - 1
1617
assertEquals(count, sut.getStackFrames(stacktrace)!!.count())
1718
}
1819

@@ -55,9 +56,9 @@ class SentryStackTraceFactoryTest {
5556
//region inAppExcludes
5657
@Test
5758
fun `when getStackFrames is called passing a valid inAppExcludes, inApp should be false if prefix matches it`() {
58-
val element = generateStackTrace("io.sentry.MyActivity")
59+
val element = generateStackTrace("io.mysentry.MyActivity")
5960
val elements = arrayOf(element)
60-
val sentryStackTraceFactory = SentryStackTraceFactory(listOf("io.sentry"), null)
61+
val sentryStackTraceFactory = SentryStackTraceFactory(listOf("io.mysentry"), null)
6162
val sentryElements = sentryStackTraceFactory.getStackFrames(elements)
6263

6364
assertFalse(sentryElements!!.first().isInApp)
@@ -67,15 +68,15 @@ class SentryStackTraceFactoryTest {
6768
fun `when getStackFrames is called passing a valid inAppExcludes, inApp should be false if prefix doesnt matches it`() {
6869
val element = generateStackTrace("io.myapp.MyActivity")
6970
val elements = arrayOf(element)
70-
val sentryStackTraceFactory = SentryStackTraceFactory(listOf("io.sentry"), null)
71+
val sentryStackTraceFactory = SentryStackTraceFactory(listOf("io.mysentry"), null)
7172
val sentryElements = sentryStackTraceFactory.getStackFrames(elements)
7273

7374
assertFalse(sentryElements!!.first().isInApp)
7475
}
7576

7677
@Test
7778
fun `when getStackFrames is called passing an invalid inAppExcludes, inApp should be false`() {
78-
val element = generateStackTrace("io.sentry.MyActivity")
79+
val element = generateStackTrace("io.mysentry.MyActivity")
7980
val elements = arrayOf(element)
8081
val sentryStackTraceFactory = SentryStackTraceFactory(null, null)
8182
val sentryElements = sentryStackTraceFactory.getStackFrames(elements)
@@ -87,9 +88,9 @@ class SentryStackTraceFactoryTest {
8788
//region inAppIncludes
8889
@Test
8990
fun `when getStackFrames is called passing a valid inAppIncludes, inApp should be true if prefix matches it`() {
90-
val element = generateStackTrace("io.sentry.MyActivity")
91+
val element = generateStackTrace("io.mysentry.MyActivity")
9192
val elements = arrayOf(element)
92-
val sentryStackTraceFactory = SentryStackTraceFactory(null, listOf("io.sentry"))
93+
val sentryStackTraceFactory = SentryStackTraceFactory(null, listOf("io.mysentry"))
9394
val sentryElements = sentryStackTraceFactory.getStackFrames(elements)
9495

9596
assertTrue(sentryElements!!.first().isInApp)
@@ -99,15 +100,15 @@ class SentryStackTraceFactoryTest {
99100
fun `when getStackFrames is called passing a valid inAppIncludes, inApp should be false if prefix doesnt matches it`() {
100101
val element = generateStackTrace("io.myapp.MyActivity")
101102
val elements = arrayOf(element)
102-
val sentryStackTraceFactory = SentryStackTraceFactory(null, listOf("io.sentry"))
103+
val sentryStackTraceFactory = SentryStackTraceFactory(null, listOf("io.mysentry"))
103104
val sentryElements = sentryStackTraceFactory.getStackFrames(elements)
104105

105106
assertFalse(sentryElements!!.first().isInApp)
106107
}
107108

108109
@Test
109110
fun `when getStackFrames is called passing an invalid inAppIncludes, inApp should be false`() {
110-
val element = generateStackTrace("io.sentry.MyActivity")
111+
val element = generateStackTrace("io.mysentry.MyActivity")
111112
val elements = arrayOf(element)
112113
val sentryStackTraceFactory = SentryStackTraceFactory(null, null)
113114
val sentryElements = sentryStackTraceFactory.getStackFrames(elements)
@@ -118,29 +119,40 @@ class SentryStackTraceFactoryTest {
118119

119120
@Test
120121
fun `when getStackFrames is called passing a valid inAppIncludes and inAppExcludes, inApp should take precedence`() {
121-
val element = generateStackTrace("io.sentry.MyActivity")
122+
val element = generateStackTrace("io.mysentry.MyActivity")
122123
val elements = arrayOf(element)
123-
val sentryStackTraceFactory = SentryStackTraceFactory(listOf("io.sentry"), listOf("io.sentry"))
124+
val sentryStackTraceFactory = SentryStackTraceFactory(listOf("io.mysentry"), listOf("io.mysentry"))
124125
val sentryElements = sentryStackTraceFactory.getStackFrames(elements)
125126

126127
assertTrue(sentryElements!!.first().isInApp)
127128
}
128129

129130
@Test
130131
fun `when class is defined in the app, inApp is true`() {
131-
val sentryStackTraceFactory = SentryStackTraceFactory(listOf("io.sentry.not"), listOf("io.sentry.inApp"))
132-
assertTrue(sentryStackTraceFactory.isInApp("io.sentry.inApp.ClassName"))
133-
assertTrue(sentryStackTraceFactory.isInApp("io.sentry.inApp.somePackage.ClassName"))
134-
assertFalse(sentryStackTraceFactory.isInApp("io.sentry.not.ClassName"))
135-
assertFalse(sentryStackTraceFactory.isInApp("io.sentry.not.somePackage.ClassName"))
132+
val sentryStackTraceFactory = SentryStackTraceFactory(listOf("io.mysentry.not"), listOf("io.mysentry.inApp"))
133+
assertTrue(sentryStackTraceFactory.isInApp("io.mysentry.inApp.ClassName"))
134+
assertTrue(sentryStackTraceFactory.isInApp("io.mysentry.inApp.somePackage.ClassName"))
135+
assertFalse(sentryStackTraceFactory.isInApp("io.mysentry.not.ClassName"))
136+
assertFalse(sentryStackTraceFactory.isInApp("io.mysentry.not.somePackage.ClassName"))
136137
}
137138

138139
@Test
139140
fun `when class is not in the list, is not inApp`() {
140-
val sentryStackTraceFactory = SentryStackTraceFactory(listOf(), listOf("io.sentry"))
141+
val sentryStackTraceFactory = SentryStackTraceFactory(listOf(), listOf("io.mysentry"))
141142
assertFalse(sentryStackTraceFactory.isInApp("com.getsentry"))
142143
}
143144

145+
@Test
146+
fun `when getStackFrames is called, remove sentry classes`() {
147+
val stacktrace = Thread.currentThread().stackTrace
148+
val sentryElement = StackTraceElement("io.sentry.element", "test", "test.java", 1)
149+
stacktrace.plusElement(sentryElement)
150+
151+
assertNull(sut.getStackFrames(stacktrace)!!.find {
152+
it.module.startsWith("io.sentry")
153+
})
154+
}
155+
144156
private fun generateStackTrace(className: String?) =
145157
StackTraceElement(className, "method", "fileName", 10)
146158
}

sentry/src/test/java/io/sentry/SentryThreadFactoryTest.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ package io.sentry
33
import kotlin.test.Test
44
import kotlin.test.assertEquals
55
import kotlin.test.assertFalse
6+
import kotlin.test.assertNotEquals
67
import kotlin.test.assertNotNull
7-
import kotlin.test.assertNotSame
88
import kotlin.test.assertNull
99
import kotlin.test.assertTrue
1010

@@ -23,7 +23,7 @@ class SentryThreadFactoryTest {
2323
fun `when getCurrentThreads is called, not empty result`() {
2424
val sut = fixture.getSut()
2525
val threads = sut.getCurrentThreads(null)
26-
assertNotSame(0, threads!!.count())
26+
assertNotEquals(0, threads!!.count())
2727
}
2828

2929
@Test
@@ -82,4 +82,11 @@ class SentryThreadFactoryTest {
8282

8383
assertNotNull(threads!!.firstOrNull { it.isCrashed })
8484
}
85+
86+
@Test
87+
fun `when getCurrentThread is called, returns current thread`() {
88+
val sut = fixture.getSut()
89+
val threads = sut.getCurrentThread(null)
90+
assertEquals(1, threads!!.count())
91+
}
8592
}

0 commit comments

Comments
 (0)