Skip to content

Commit 2af8d1a

Browse files
lucas-zimermanLucasstefanosiano
authored
Fix: Allow MaxBreadcrumb 0 / Expose MaxBreadcrumb metadata. (#3836)
* expose max-breadcrumbs on meta data and implement disabled queue when maxbreadcrumbs sets to 0 * missing queue class and test * update changelog --------- Co-authored-by: Lucas <[email protected]> Co-authored-by: Stefano <[email protected]>
1 parent 28a11a7 commit 2af8d1a

File tree

8 files changed

+257
-1
lines changed

8 files changed

+257
-1
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44

55
### Features
66

7+
- Add meta option to set the maximum amount of breadcrumbs to be logged. ([#3836](https://github.com/getsentry/sentry-java/pull/3836))
78
- Use a separate `Random` instance per thread to improve SDK performance ([#3835](https://github.com/getsentry/sentry-java/pull/3835))
89

910
### Fixes
1011

12+
- Using MaxBreadcrumb with value 0 no longer crashes. ([#3836](https://github.com/getsentry/sentry-java/pull/3836))
1113
- Accept manifest integer values when requiring floating values ([#3823](https://github.com/getsentry/sentry-java/pull/3823))
1214

1315
## 7.16.0

sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java

+5
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ final class ManifestMetadataReader {
104104

105105
static final String ENABLE_METRICS = "io.sentry.enable-metrics";
106106

107+
static final String MAX_BREADCRUMBS = "io.sentry.max-breadcrumbs";
108+
107109
static final String REPLAYS_SESSION_SAMPLE_RATE = "io.sentry.session-replay.session-sample-rate";
108110

109111
static final String REPLAYS_ERROR_SAMPLE_RATE = "io.sentry.session-replay.on-error-sample-rate";
@@ -213,6 +215,9 @@ static void applyMetadata(
213215
SESSION_TRACKING_TIMEOUT_INTERVAL_MILLIS,
214216
options.getSessionTrackingIntervalMillis()));
215217

218+
options.setMaxBreadcrumbs(
219+
(int) readLong(metadata, logger, MAX_BREADCRUMBS, options.getMaxBreadcrumbs()));
220+
216221
options.setEnableActivityLifecycleBreadcrumbs(
217222
readBool(
218223
metadata,

sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt

+25
Original file line numberDiff line numberDiff line change
@@ -1516,6 +1516,31 @@ class ManifestMetadataReaderTest {
15161516
assertTrue(fixture.options.experimental.sessionReplay.maskViewClasses.contains(SentryReplayOptions.TEXT_VIEW_CLASS_NAME))
15171517
}
15181518

1519+
@Test
1520+
fun `applyMetadata reads maxBreadcrumbs to options and sets the value if found`() {
1521+
// Arrange
1522+
val bundle = bundleOf(ManifestMetadataReader.MAX_BREADCRUMBS to 1)
1523+
val context = fixture.getContext(metaData = bundle)
1524+
1525+
// Act
1526+
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)
1527+
1528+
// Assert
1529+
assertEquals(1, fixture.options.maxBreadcrumbs)
1530+
}
1531+
1532+
@Test
1533+
fun `applyMetadata reads maxBreadcrumbs to options and keeps default if not found`() {
1534+
// Arrange
1535+
val context = fixture.getContext()
1536+
1537+
// Act
1538+
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)
1539+
1540+
// Assert
1541+
assertEquals(100, fixture.options.maxBreadcrumbs)
1542+
}
1543+
15191544
@Test
15201545
fun `applyMetadata reads integers even when expecting floats`() {
15211546
// Arrange

sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml

+3
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,9 @@
152152
<!-- how to enable the attach screenshot feature-->
153153
<meta-data android:name="io.sentry.attach-screenshot" android:value="true" />
154154

155+
<!-- how many breadcrumbs will be stored-->
156+
<meta-data android:name="io.sentry.max-breadcrumbs" android:value="100"/>
157+
155158
<!-- how to enable the attach view hierarchy feature-->
156159
<meta-data android:name="io.sentry.attach-view-hierarchy" android:value="true" />
157160

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package io.sentry;
2+
3+
import java.io.Serializable;
4+
import java.util.AbstractCollection;
5+
import java.util.Iterator;
6+
import java.util.NoSuchElementException;
7+
import java.util.Queue;
8+
import org.jetbrains.annotations.NotNull;
9+
import org.jetbrains.annotations.Nullable;
10+
11+
final class DisabledQueue<E> extends AbstractCollection<E> implements Queue<E>, Serializable {
12+
13+
/** Serialization version. */
14+
private static final long serialVersionUID = -8423413834657610417L;
15+
16+
/** Constructor that creates a queue that does not accept any element. */
17+
public DisabledQueue() {}
18+
19+
// -----------------------------------------------------------------------
20+
/**
21+
* Returns the number of elements stored in the queue.
22+
*
23+
* @return this queue's size
24+
*/
25+
@Override
26+
public int size() {
27+
return 0;
28+
}
29+
30+
/**
31+
* Returns true if this queue is empty; false otherwise.
32+
*
33+
* @return false
34+
*/
35+
@Override
36+
public boolean isEmpty() {
37+
return false;
38+
}
39+
40+
/** Does nothing. */
41+
@Override
42+
public void clear() {}
43+
44+
/**
45+
* Since the queue is disabled, the element will not be added.
46+
*
47+
* @param element the element to add
48+
* @return false, always
49+
*/
50+
@Override
51+
public boolean add(final @NotNull E element) {
52+
return false;
53+
}
54+
55+
// -----------------------------------------------------------------------
56+
57+
/**
58+
* Receives an element but do nothing with it.
59+
*
60+
* @param element the element to add
61+
* @return false, always
62+
*/
63+
@Override
64+
public boolean offer(@NotNull E element) {
65+
return false;
66+
}
67+
68+
@Override
69+
public @Nullable E poll() {
70+
return null;
71+
}
72+
73+
@Override
74+
public @Nullable E element() {
75+
return null;
76+
}
77+
78+
@Override
79+
public @Nullable E peek() {
80+
return null;
81+
}
82+
83+
@Override
84+
public @NotNull E remove() {
85+
throw new NoSuchElementException("queue is disabled");
86+
}
87+
88+
// -----------------------------------------------------------------------
89+
90+
/**
91+
* Returns an iterator over this queue's elements.
92+
*
93+
* @return an iterator over this queue's elements
94+
*/
95+
@Override
96+
public @NotNull Iterator<E> iterator() {
97+
return new Iterator<E>() {
98+
99+
@Override
100+
public boolean hasNext() {
101+
return false;
102+
}
103+
104+
@Override
105+
public E next() {
106+
throw new NoSuchElementException();
107+
}
108+
109+
@Override
110+
public void remove() {
111+
throw new IllegalStateException();
112+
}
113+
};
114+
}
115+
}

sentry/src/main/java/io/sentry/Scope.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -754,7 +754,9 @@ public void clearAttachments() {
754754
* @return the breadcrumbs queue
755755
*/
756756
private @NotNull Queue<Breadcrumb> createBreadcrumbsList(final int maxBreadcrumb) {
757-
return SynchronizedQueue.synchronizedQueue(new CircularFifoQueue<>(maxBreadcrumb));
757+
return maxBreadcrumb > 0
758+
? SynchronizedQueue.synchronizedQueue(new CircularFifoQueue<>(maxBreadcrumb))
759+
: SynchronizedQueue.synchronizedQueue(new DisabledQueue<>());
758760
}
759761

760762
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package io.sentry
2+
import org.junit.Assert.assertThrows
3+
import java.util.NoSuchElementException
4+
import kotlin.test.Test
5+
import kotlin.test.assertEquals
6+
import kotlin.test.assertFalse
7+
import kotlin.test.assertNull
8+
9+
class DisabledQueueTest {
10+
11+
@Test
12+
fun `size starts empty`() {
13+
val queue = DisabledQueue<Int>()
14+
assertEquals(0, queue.size, "Size should always be zero.")
15+
}
16+
17+
@Test
18+
fun `add does not add elements`() {
19+
val queue = DisabledQueue<Int>()
20+
assertFalse(queue.add(1), "add should always return false.")
21+
assertEquals(0, queue.size, "Size should still be zero after attempting to add an element.")
22+
}
23+
24+
@Test
25+
fun `isEmpty returns false when created`() {
26+
val queue = DisabledQueue<Int>()
27+
assertFalse(queue.isEmpty(), "isEmpty should always return false.")
28+
}
29+
30+
@Test
31+
fun `isEmpty always returns false if add function was called`() {
32+
val queue = DisabledQueue<Int>()
33+
queue.add(1)
34+
35+
assertFalse(queue.isEmpty(), "isEmpty should always return false.")
36+
}
37+
38+
@Test
39+
fun `offer does not add elements`() {
40+
val queue = DisabledQueue<Int>()
41+
assertFalse(queue.offer(1), "offer should always return false.")
42+
assertEquals(0, queue.size, "Size should still be zero after attempting to offer an element.")
43+
}
44+
45+
@Test
46+
fun `poll returns null`() {
47+
val queue = DisabledQueue<Int>()
48+
queue.add(1)
49+
assertNull(queue.poll(), "poll should always return null.")
50+
}
51+
52+
@Test
53+
fun `peek returns null`() {
54+
val queue = DisabledQueue<Int>()
55+
queue.add(1)
56+
57+
assertNull(queue.peek(), "peek should always return null.")
58+
}
59+
60+
@Test
61+
fun `element returns null`() {
62+
val queue = DisabledQueue<Int>()
63+
assertNull(queue.element(), "element should always return null.")
64+
}
65+
66+
@Test
67+
fun `remove throws NoSuchElementException`() {
68+
val queue = DisabledQueue<Int>()
69+
assertThrows(NoSuchElementException::class.java) { queue.remove() }
70+
}
71+
72+
@Test
73+
fun `clear does nothing`() {
74+
val queue = DisabledQueue<Int>()
75+
queue.clear() // Should not throw an exception
76+
assertEquals(0, queue.size, "Size should remain zero after clear.")
77+
}
78+
79+
@Test
80+
fun `iterator has no elements`() {
81+
val queue = DisabledQueue<Int>()
82+
val iterator = queue.iterator()
83+
assertFalse(iterator.hasNext(), "Iterator should have no elements.")
84+
assertThrows(NoSuchElementException::class.java) { iterator.next() }
85+
}
86+
87+
@Test
88+
fun `iterator remove throws IllegalStateException`() {
89+
val queue = DisabledQueue<Int>()
90+
val iterator = queue.iterator()
91+
assertThrows(IllegalStateException::class.java) { iterator.remove() }
92+
}
93+
}

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

+11
Original file line numberDiff line numberDiff line change
@@ -1029,6 +1029,17 @@ class ScopeTest {
10291029
)
10301030
}
10311031

1032+
@Test
1033+
fun `creating a new scope won't crash if max breadcrumbs is set to zero`() {
1034+
val options = SentryOptions().apply {
1035+
maxBreadcrumbs = 0
1036+
}
1037+
val scope = Scope(options)
1038+
1039+
// expect no exception to be thrown
1040+
// previously was crashing, see https://github.com/getsentry/sentry-java/issues/3313
1041+
}
1042+
10321043
private fun eventProcessor(): EventProcessor {
10331044
return object : EventProcessor {
10341045
override fun process(event: SentryEvent, hint: Hint): SentryEvent? {

0 commit comments

Comments
 (0)