Skip to content
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

### Improvements

- Decouple SentryThreadFactory from SentryOptions ([#4918](https://github.com/getsentry/sentry-java/pull/4918))
- Do not send manual log origin ([#4897](https://github.com/getsentry/sentry-java/pull/4897))

## 8.26.0
Expand Down
2 changes: 1 addition & 1 deletion sentry/api/sentry.api
Original file line number Diff line number Diff line change
Expand Up @@ -3864,7 +3864,7 @@ public final class io/sentry/SentryStackTraceFactory {
}

public final class io/sentry/SentryThreadFactory {
public fun <init> (Lio/sentry/SentryStackTraceFactory;Lio/sentry/SentryOptions;)V
public fun <init> (Lio/sentry/SentryStackTraceFactory;)V
}

public final class io/sentry/SentryTraceHeader {
Expand Down
9 changes: 6 additions & 3 deletions sentry/src/main/java/io/sentry/MainEventProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public MainEventProcessor(final @NotNull SentryOptions options) {
new SentryStackTraceFactory(this.options);

sentryExceptionFactory = new SentryExceptionFactory(sentryStackTraceFactory);
sentryThreadFactory = new SentryThreadFactory(sentryStackTraceFactory, this.options);
sentryThreadFactory = new SentryThreadFactory(sentryStackTraceFactory);
}

MainEventProcessor(
Expand Down Expand Up @@ -255,17 +255,20 @@ private void setThreads(final @NotNull SentryEvent event, final @NotNull Hint hi
if (options.isAttachThreads() || HintUtils.hasType(hint, AbnormalExit.class)) {
final Object sentrySdkHint = HintUtils.getSentrySdkHint(hint);
boolean ignoreCurrentThread = false;
boolean attachStacktrace = options.isAttachStacktrace();
if (sentrySdkHint instanceof AbnormalExit) {
ignoreCurrentThread = ((AbnormalExit) sentrySdkHint).ignoreCurrentThread();
attachStacktrace = true;
}
event.setThreads(
sentryThreadFactory.getCurrentThreads(mechanismThreadIds, ignoreCurrentThread));
sentryThreadFactory.getCurrentThreads(
mechanismThreadIds, ignoreCurrentThread, attachStacktrace));
} else if (options.isAttachStacktrace()
&& (eventExceptions == null || eventExceptions.isEmpty())
&& !isCachedHint(hint)) {
// when attachStacktrace is enabled, we attach only the current thread and its stack traces,
// if there are no exceptions, exceptions have its own stack traces.
event.setThreads(sentryThreadFactory.getCurrentThread());
event.setThreads(sentryThreadFactory.getCurrentThread(options.isAttachStacktrace()));
}
}
}
Expand Down
50 changes: 26 additions & 24 deletions sentry/src/main/java/io/sentry/SentryThreadFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,14 @@ public final class SentryThreadFactory {
/** the SentryStackTraceFactory */
private final @NotNull SentryStackTraceFactory sentryStackTraceFactory;

/** the SentryOptions. */
private final @NotNull SentryOptions options;

/**
* ctor SentryThreadFactory that takes a SentryStackTraceFactory
*
* @param sentryStackTraceFactory the SentryStackTraceFactory
* @param options the SentryOptions
*/
public SentryThreadFactory(
final @NotNull SentryStackTraceFactory sentryStackTraceFactory,
final @NotNull SentryOptions options) {
public SentryThreadFactory(final @NotNull SentryStackTraceFactory sentryStackTraceFactory) {
this.sentryStackTraceFactory =
Objects.requireNonNull(sentryStackTraceFactory, "The SentryStackTraceFactory is required.");
this.options = Objects.requireNonNull(options, "The SentryOptions is required");
}

/**
Expand All @@ -44,12 +37,12 @@ public SentryThreadFactory(
* @return a list of SentryThread with a single item or null
*/
@Nullable
List<SentryThread> getCurrentThread() {
List<SentryThread> getCurrentThread(final boolean attachStackTrace) {
final Map<Thread, StackTraceElement[]> threads = new HashMap<>();
final Thread currentThread = Thread.currentThread();
threads.put(currentThread, currentThread.getStackTrace());

return getCurrentThreads(threads, null, false);
return getCurrentThreads(threads, null, false, attachStackTrace);
}

/**
Expand All @@ -63,13 +56,18 @@ List<SentryThread> getCurrentThread() {
*/
@Nullable
List<SentryThread> getCurrentThreads(
final @Nullable List<Long> mechanismThreadIds, final boolean ignoreCurrentThread) {
return getCurrentThreads(Thread.getAllStackTraces(), mechanismThreadIds, ignoreCurrentThread);
final @Nullable List<Long> mechanismThreadIds,
final boolean ignoreCurrentThread,
final boolean attachStackTrace) {
return getCurrentThreads(
Thread.getAllStackTraces(), mechanismThreadIds, ignoreCurrentThread, attachStackTrace);
}

@Nullable
List<SentryThread> getCurrentThreads(final @Nullable List<Long> mechanismThreadIds) {
return getCurrentThreads(Thread.getAllStackTraces(), mechanismThreadIds, false);
List<SentryThread> getCurrentThreads(
final @Nullable List<Long> mechanismThreadIds, final boolean attachStackTrace) {
return getCurrentThreads(
Thread.getAllStackTraces(), mechanismThreadIds, false, attachStackTrace);
}

/**
Expand All @@ -87,7 +85,8 @@ List<SentryThread> getCurrentThreads(final @Nullable List<Long> mechanismThreadI
List<SentryThread> getCurrentThreads(
final @NotNull Map<Thread, StackTraceElement[]> threads,
final @Nullable List<Long> mechanismThreadIds,
final boolean ignoreCurrentThread) {
final boolean ignoreCurrentThread,
final boolean attachStackTrace) {
List<SentryThread> result = null;

final Thread currentThread = Thread.currentThread();
Expand All @@ -109,7 +108,7 @@ List<SentryThread> getCurrentThreads(
&& mechanismThreadIds.contains(thread.getId())
&& !ignoreCurrentThread);

result.add(getSentryThread(crashed, item.getValue(), item.getKey()));
result.add(getSentryThread(crashed, item.getValue(), item.getKey(), attachStackTrace));
}
}

Expand All @@ -127,7 +126,8 @@ List<SentryThread> getCurrentThreads(
private @NotNull SentryThread getSentryThread(
final boolean crashed,
final @NotNull StackTraceElement[] stackFramesElements,
final @NotNull Thread thread) {
final @NotNull Thread thread,
final boolean attachStacktrace) {
final SentryThread sentryThread = new SentryThread();

sentryThread.setName(thread.getName());
Expand All @@ -137,15 +137,17 @@ List<SentryThread> getCurrentThreads(
sentryThread.setState(thread.getState().name());
sentryThread.setCrashed(crashed);

final List<SentryStackFrame> frames =
sentryStackTraceFactory.getStackFrames(stackFramesElements, false);
if (attachStacktrace) {
final List<SentryStackFrame> frames =
sentryStackTraceFactory.getStackFrames(stackFramesElements, false);

if (options.isAttachStacktrace() && frames != null && !frames.isEmpty()) {
final SentryStackTrace sentryStackTrace = new SentryStackTrace(frames);
// threads are always gotten either via Thread.currentThread() or Thread.getAllStackTraces()
sentryStackTrace.setSnapshot(true);
if (frames != null && !frames.isEmpty()) {
final SentryStackTrace sentryStackTrace = new SentryStackTrace(frames);
// threads are always gotten either via Thread.currentThread() or Thread.getAllStackTraces()
sentryStackTrace.setSnapshot(true);

sentryThread.setStacktrace(sentryStackTrace);
sentryThread.setStacktrace(sentryStackTrace);
}
}

return sentryThread;
Expand Down
16 changes: 15 additions & 1 deletion sentry/src/test/java/io/sentry/MainEventProcessorTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ class MainEventProcessorTest {
}

@Test
fun `when attach threads is disabled, but the hint is Abnormal, still sets threads`() {
fun `when attach threads is disabled, but the hint is Abnormal, still sets threads and stacktraces`() {
val sut = fixture.getSut(attachThreads = false, attachStackTrace = false)

var event = SentryEvent(RuntimeException("error"))
Expand All @@ -258,6 +258,20 @@ class MainEventProcessorTest {

assertNotNull(event.threads)
assertEquals(1, event.threads!!.count { it.isCrashed == true })
assertNotNull(event.threads!!.first().stacktrace)
}

@Test
fun `when attach threads is enabled, but stacktraces is not its reflected`() {
val sut = fixture.getSut(attachThreads = true, attachStackTrace = false)

var event = SentryEvent(RuntimeException("error"))
val hint = Hint()
event = sut.process(event, hint)

assertNotNull(event.threads)
assertEquals(1, event.threads!!.count { it.isCrashed == true })
assertNull(event.threads!!.first().stacktrace)
}

@Test
Expand Down
35 changes: 17 additions & 18 deletions sentry/src/test/java/io/sentry/SentryThreadFactoryTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,9 @@ import kotlin.test.assertTrue

class SentryThreadFactoryTest {
class Fixture {
internal fun getSut(attachStacktrace: Boolean = true) =
internal fun getSut() =
SentryThreadFactory(
SentryStackTraceFactory(SentryOptions().apply { addInAppExclude("io.sentry") }),
with(SentryOptions()) {
isAttachStacktrace = attachStacktrace
this
},
SentryStackTraceFactory(SentryOptions().apply { addInAppExclude("io.sentry") })
)
}

Expand All @@ -25,34 +21,37 @@ class SentryThreadFactoryTest {
@Test
fun `when getCurrentThreads is called, not empty result`() {
val sut = fixture.getSut()
val threads = sut.getCurrentThreads(null)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

l some tests here used to implicitly have attachStacktrace=true, now we're explicitly passing false. Does that matter?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that should be fine, we have some tests for extra covering the attachStacktrace flag

val threads = sut.getCurrentThreads(null, false)
assertNotEquals(0, threads!!.count())
}

@Test
fun `when currentThreads is called, current thread is marked crashed`() {
val sut = fixture.getSut()
assertEquals(1, sut.getCurrentThreads(null)!!.filter { it.isCrashed == true }.count())
assertEquals(1, sut.getCurrentThreads(null, false)!!.filter { it.isCrashed == true }.count())
}

@Test
fun `when currentThreads is called with ignoreCurrentThread, current thread is not marked crashed`() {
val sut = fixture.getSut()
assertEquals(0, sut.getCurrentThreads(null, true)!!.filter { it.isCrashed == true }.count())
assertEquals(
0,
sut.getCurrentThreads(null, true, false)!!.filter { it.isCrashed == true }.count(),
)
}

@Test
fun `when currentThreads is called, thread state is captured`() {
val sut = fixture.getSut()
assertTrue(sut.getCurrentThreads(null)!!.all { it.state != null })
assertTrue(sut.getCurrentThreads(null, false)!!.all { it.state != null })
}

@Test
fun `when currentThreads is called, some thread stack frames are captured`() {
val sut = fixture.getSut()
assertTrue(
sut
.getCurrentThreads(null)!!
.getCurrentThreads(null, true)!!
.filter { it.stacktrace != null }
.any { it.stacktrace!!.frames!!.count() > 0 }
)
Expand All @@ -63,18 +62,18 @@ class SentryThreadFactoryTest {
val sut = fixture.getSut()
assertTrue(
sut
.getCurrentThreads(null)!!
.getCurrentThreads(null, true)!!
.filter { it.stacktrace != null }
.any { it.stacktrace!!.snapshot == true }
)
}

@Test
fun `when currentThreads and attachStacktrace is disabled, stack frames are not captured`() {
val sut = fixture.getSut(false)
val sut = fixture.getSut()
assertFalse(
sut
.getCurrentThreads(null)!!
.getCurrentThreads(null, false)!!
.filter { it.stacktrace != null }
.any { it.stacktrace!!.frames!!.count() > 0 }
)
Expand All @@ -87,15 +86,15 @@ class SentryThreadFactoryTest {
val currentThread = Thread.currentThread()
stackTraces.remove(currentThread)

val threads = sut.getCurrentThreads(stackTraces, null, false)
val threads = sut.getCurrentThreads(stackTraces, null, false, false)

assertNotNull(threads!!.firstOrNull { it.id == currentThread.id })
}

@Test
fun `When passing empty param to getCurrentThreads, returns null`() {
val sut = fixture.getSut()
val threads = sut.getCurrentThreads(mapOf(), null, false)
val threads = sut.getCurrentThreads(mapOf(), null, false, false)

assertNull(threads)
}
Expand All @@ -108,15 +107,15 @@ class SentryThreadFactoryTest {
val stacktraces = emptyArray<StackTraceElement>()
val threadList = mutableMapOf(thread to stacktraces)

val threads = sut.getCurrentThreads(threadList, threadIds, false)
val threads = sut.getCurrentThreads(threadList, threadIds, false, false)

assertNotNull(threads!!.firstOrNull { it.isCrashed == true })
}

@Test
fun `when getCurrentThread is called, returns current thread`() {
val sut = fixture.getSut()
val threads = sut.currentThread
val threads = sut.getCurrentThread(false)
assertEquals(1, threads!!.count())
}
}
Loading