diff --git a/sentry-core/src/main/java/io/sentry/core/MainEventProcessor.java b/sentry-core/src/main/java/io/sentry/core/MainEventProcessor.java index 9cc7f2872..7010d6a80 100644 --- a/sentry-core/src/main/java/io/sentry/core/MainEventProcessor.java +++ b/sentry-core/src/main/java/io/sentry/core/MainEventProcessor.java @@ -5,13 +5,28 @@ public final class MainEventProcessor implements EventProcessor { private final SentryOptions options; - private final SentryThreadFactory sentryThreadFactory = new SentryThreadFactory(); - private final SentryStackTraceFactory sentryStackTraceFactory = new SentryStackTraceFactory(); - private final SentryExceptionFactory sentryExceptionFactory = - new SentryExceptionFactory(sentryStackTraceFactory); + private final SentryThreadFactory sentryThreadFactory; + private final SentryExceptionFactory sentryExceptionFactory; - MainEventProcessor(SentryOptions options) { + MainEventProcessor(final SentryOptions options) { this.options = Objects.requireNonNull(options, "The SentryOptions is required."); + + SentryStackTraceFactory sentryStackTraceFactory = + new SentryStackTraceFactory(options.getInAppExcludes(), options.getInAppIncludes()); + + sentryExceptionFactory = new SentryExceptionFactory(sentryStackTraceFactory); + sentryThreadFactory = new SentryThreadFactory(sentryStackTraceFactory); + } + + MainEventProcessor( + final SentryOptions options, + final SentryThreadFactory sentryThreadFactory, + final SentryExceptionFactory sentryExceptionFactory) { + this.options = Objects.requireNonNull(options, "The SentryOptions is required."); + this.sentryThreadFactory = + Objects.requireNonNull(sentryThreadFactory, "The SentryThreadFactory is required."); + this.sentryExceptionFactory = + Objects.requireNonNull(sentryExceptionFactory, "The SentryExceptionFactory is required."); } @Override diff --git a/sentry-core/src/main/java/io/sentry/core/SentryExceptionFactory.java b/sentry-core/src/main/java/io/sentry/core/SentryExceptionFactory.java index ae8c15565..87db05024 100644 --- a/sentry-core/src/main/java/io/sentry/core/SentryExceptionFactory.java +++ b/sentry-core/src/main/java/io/sentry/core/SentryExceptionFactory.java @@ -18,7 +18,7 @@ final class SentryExceptionFactory { private final SentryStackTraceFactory sentryStackTraceFactory; - public SentryExceptionFactory(SentryStackTraceFactory sentryStackTraceFactory) { + public SentryExceptionFactory(final SentryStackTraceFactory sentryStackTraceFactory) { this.sentryStackTraceFactory = Objects.requireNonNull(sentryStackTraceFactory, "The SentryStackTraceFactory is required."); } diff --git a/sentry-core/src/main/java/io/sentry/core/SentryOptions.java b/sentry-core/src/main/java/io/sentry/core/SentryOptions.java index ebcad889c..e57dc36fa 100644 --- a/sentry-core/src/main/java/io/sentry/core/SentryOptions.java +++ b/sentry-core/src/main/java/io/sentry/core/SentryOptions.java @@ -29,6 +29,8 @@ public final class SentryOptions { private String environment; private Proxy proxy; private Double sampling; + private List inAppExcludes; + private List inAppIncludes; public void addEventProcessor(EventProcessor eventProcessor) { eventProcessors.add(eventProcessor); @@ -188,6 +190,22 @@ public void setSampling(Double sampling) { this.sampling = sampling; } + public List getInAppExcludes() { + return inAppExcludes; + } + + public void setInAppExcludes(List inAppExcludes) { + this.inAppExcludes = inAppExcludes; + } + + public List getInAppIncludes() { + return inAppIncludes; + } + + public void setInAppIncludes(List inAppIncludes) { + this.inAppIncludes = inAppIncludes; + } + public interface BeforeSendCallback { SentryEvent execute(SentryEvent event); } @@ -197,6 +215,9 @@ public interface BeforeBreadcrumbCallback { } public SentryOptions() { + inAppExcludes = new ArrayList<>(); + inAppExcludes.add("io.sentry"); + eventProcessors.add(new MainEventProcessor(this)); integrations.add(new UncaughtExceptionHandlerIntegration()); } diff --git a/sentry-core/src/main/java/io/sentry/core/SentryStackTraceFactory.java b/sentry-core/src/main/java/io/sentry/core/SentryStackTraceFactory.java index 17815a247..b2770318d 100644 --- a/sentry-core/src/main/java/io/sentry/core/SentryStackTraceFactory.java +++ b/sentry-core/src/main/java/io/sentry/core/SentryStackTraceFactory.java @@ -8,6 +8,15 @@ /** class responsible for converting Java StackTraceElements to SentryStackFrames */ final class SentryStackTraceFactory { + private final List inAppExcludes; + private final List inAppIncludes; + + public SentryStackTraceFactory( + @Nullable final List inAppExcludes, @Nullable List inAppIncludes) { + this.inAppExcludes = inAppExcludes; + this.inAppIncludes = inAppIncludes; + } + /** * convert an Array of Java StackTraceElements to a list of SentryStackFrames * @@ -21,12 +30,13 @@ List getStackFrames(@Nullable final StackTraceElement[] elemen for (StackTraceElement item : elements) { if (item != null) { SentryStackFrame sentryStackFrame = new SentryStackFrame(); + // https://docs.sentry.io/development/sdk-dev/features/#in-app-frames + sentryStackFrame.setInApp(isInApp(item.getClassName())); sentryStackFrame.setModule(item.getClassName()); sentryStackFrame.setFunction(item.getMethodName()); sentryStackFrame.setFilename(item.getFileName()); sentryStackFrame.setLineno(item.getLineNumber()); sentryStackFrame.setNative(item.isNativeMethod()); - sentryStackFrames.add(sentryStackFrame); } } @@ -34,4 +44,26 @@ List getStackFrames(@Nullable final StackTraceElement[] elemen return sentryStackFrames; } + + private boolean isInApp(String className) { + if (className == null || className.isEmpty()) { + return true; + } + + if (inAppIncludes != null) { + for (String include : inAppIncludes) { + if (className.startsWith(include)) { + return true; + } + } + } + if (inAppExcludes != null) { + for (String exclude : inAppExcludes) { + if (className.startsWith(exclude)) { + return false; + } + } + } + return true; + } } diff --git a/sentry-core/src/main/java/io/sentry/core/SentryThreadFactory.java b/sentry-core/src/main/java/io/sentry/core/SentryThreadFactory.java index 55af992e1..00db1dfb1 100644 --- a/sentry-core/src/main/java/io/sentry/core/SentryThreadFactory.java +++ b/sentry-core/src/main/java/io/sentry/core/SentryThreadFactory.java @@ -3,6 +3,7 @@ import io.sentry.core.protocol.SentryStackFrame; import io.sentry.core.protocol.SentryStackTrace; import io.sentry.core.protocol.SentryThread; +import io.sentry.core.util.Objects; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -10,6 +11,13 @@ final class SentryThreadFactory { + private final SentryStackTraceFactory sentryStackTraceFactory; + + public SentryThreadFactory(SentryStackTraceFactory sentryStackTraceFactory) { + this.sentryStackTraceFactory = + Objects.requireNonNull(sentryStackTraceFactory, "The SentryStackTraceFactory is required."); + } + // Assumes its being called from the crashed thread. List getCurrentThreadsForCrash() { return getCurrentThreads(Thread.currentThread()); @@ -49,7 +57,6 @@ private SentryThread getSentryThread( } sentryThread.setCurrent(thread == currentThread); - SentryStackTraceFactory sentryStackTraceFactory = new SentryStackTraceFactory(); List frames = sentryStackTraceFactory.getStackFrames(stackFramesElements); if (frames.size() > 0) { diff --git a/sentry-core/src/test/java/io/sentry/core/SentryExceptionFactoryTest.kt b/sentry-core/src/test/java/io/sentry/core/SentryExceptionFactoryTest.kt index c4a5c4b14..033c0a1dc 100644 --- a/sentry-core/src/test/java/io/sentry/core/SentryExceptionFactoryTest.kt +++ b/sentry-core/src/test/java/io/sentry/core/SentryExceptionFactoryTest.kt @@ -7,7 +7,7 @@ import kotlin.test.assertEquals import kotlin.test.assertTrue class SentryExceptionFactoryTest { - private val sut = SentryExceptionFactory(SentryStackTraceFactory()) + private val sut = SentryExceptionFactory(SentryStackTraceFactory(listOf("io.sentry"), listOf())) @Test fun `when getSentryExceptions is called passing an Exception, not empty result`() { diff --git a/sentry-core/src/test/java/io/sentry/core/SentryStackTraceFactoryTest.kt b/sentry-core/src/test/java/io/sentry/core/SentryStackTraceFactoryTest.kt index c6e0e5f98..364dde34e 100644 --- a/sentry-core/src/test/java/io/sentry/core/SentryStackTraceFactoryTest.kt +++ b/sentry-core/src/test/java/io/sentry/core/SentryStackTraceFactoryTest.kt @@ -2,9 +2,11 @@ package io.sentry.core import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue class SentryStackTraceFactoryTest { - private val sut = SentryStackTraceFactory() + private val sut = SentryStackTraceFactory(listOf(), listOf()) @Test fun `when getStackFrames is called passing a valid Array, not empty result`() { @@ -20,8 +22,8 @@ class SentryStackTraceFactoryTest { @Test fun `when getStackFrames is called passing a valid array, fields should be set`() { - val element = StackTraceElement("class", "method", "fileName", -2) - val stacktraces = Array(1) { element } + val element = generateStackTrace("class") + val stacktraces = arrayOf(element) val stackFrames = sut.getStackFrames(stacktraces) assertEquals("class", stackFrames[0].module) assertEquals("method", stackFrames[0].function) @@ -29,4 +31,81 @@ class SentryStackTraceFactoryTest { assertEquals(-2, stackFrames[0].lineno) assertEquals(true, stackFrames[0].isNative) } + + //region inAppExcludes + @Test + fun `when getStackFrames is called passing a valid inAppExcludes, inApp should be false if prefix matches it`() { + val element = generateStackTrace("io.sentry.MyActivity") + val elements = arrayOf(element) + val sentryStackTraceFactory = SentryStackTraceFactory(listOf("io.sentry"), null) + val sentryElements = sentryStackTraceFactory.getStackFrames(elements) + + assertFalse(sentryElements.first().inApp) + } + + @Test + fun `when getStackFrames is called passing a valid inAppExcludes, inApp should be true if prefix doesnt matches it`() { + val element = generateStackTrace("io.myapp.MyActivity") + val elements = arrayOf(element) + val sentryStackTraceFactory = SentryStackTraceFactory(listOf("io.sentry"), null) + val sentryElements = sentryStackTraceFactory.getStackFrames(elements) + + assertTrue(sentryElements.first().inApp) + } + + @Test + fun `when getStackFrames is called passing an invalid inAppExcludes, inApp should be false`() { + val element = generateStackTrace("io.sentry.MyActivity") + val elements = arrayOf(element) + val sentryStackTraceFactory = SentryStackTraceFactory(null, null) + val sentryElements = sentryStackTraceFactory.getStackFrames(elements) + + assertTrue(sentryElements.first().inApp) + } + //endregion + + //region inAppIncludes + @Test + fun `when getStackFrames is called passing a valid inAppIncludes, inApp should be true if prefix matches it`() { + val element = generateStackTrace("io.sentry.MyActivity") + val elements = arrayOf(element) + val sentryStackTraceFactory = SentryStackTraceFactory(null, listOf("io.sentry")) + val sentryElements = sentryStackTraceFactory.getStackFrames(elements) + + assertTrue(sentryElements.first().inApp) + } + + @Test + fun `when getStackFrames is called passing a valid inAppIncludes, inApp should be true if prefix doesnt matches it`() { + val element = generateStackTrace("io.myapp.MyActivity") + val elements = arrayOf(element) + val sentryStackTraceFactory = SentryStackTraceFactory(null, listOf("io.sentry")) + val sentryElements = sentryStackTraceFactory.getStackFrames(elements) + + assertTrue(sentryElements.first().inApp) + } + + @Test + fun `when getStackFrames is called passing an invalid inAppIncludes, inApp should be true`() { + val element = generateStackTrace("io.sentry.MyActivity") + val elements = arrayOf(element) + val sentryStackTraceFactory = SentryStackTraceFactory(null, null) + val sentryElements = sentryStackTraceFactory.getStackFrames(elements) + + assertTrue(sentryElements.first().inApp) + } + //endregion + + @Test + fun `when getStackFrames is called passing a valid inAppIncludes and inAppExcludes, inApp should take precedence`() { + val element = generateStackTrace("io.sentry.MyActivity") + val elements = arrayOf(element) + val sentryStackTraceFactory = SentryStackTraceFactory(listOf("io.sentry"), listOf("io.sentry")) + val sentryElements = sentryStackTraceFactory.getStackFrames(elements) + + assertTrue(sentryElements.first().inApp) + } + + private fun generateStackTrace(className: String?) = + StackTraceElement(className, "method", "fileName", -2) } diff --git a/sentry-core/src/test/java/io/sentry/core/SentryThreadFactoryTest.kt b/sentry-core/src/test/java/io/sentry/core/SentryThreadFactoryTest.kt index eb11d35cd..aefe54c4d 100644 --- a/sentry-core/src/test/java/io/sentry/core/SentryThreadFactoryTest.kt +++ b/sentry-core/src/test/java/io/sentry/core/SentryThreadFactoryTest.kt @@ -6,7 +6,7 @@ import kotlin.test.assertTrue class SentryThreadFactoryTest { - private val sut = SentryThreadFactory() + private val sut = SentryThreadFactory(SentryStackTraceFactory(listOf("io.sentry"), listOf())) @Test fun `when getCurrentThreads is called, not empty result`() {