Skip to content

Commit 2bdfc2e

Browse files
authored
[feat] add maxLogcatEntries new config param (#247)
## Summary by CodeRabbit * **New Features** * Introduced `maxLogcatEntries` configuration setting to control the Logcat buffer size; defaults to 300 entries. * Logcat buffer capacity can now be updated at runtime. * **Tests** * Added test coverage for configuration validation and buffer capacity adjustments.
1 parent 86db4ce commit 2bdfc2e

6 files changed

Lines changed: 154 additions & 30 deletions

File tree

debugoverlay-core/src/main/kotlin/com/ms/square/debugoverlay/DebugOverlay.kt

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import android.app.Application
55
import android.content.Context
66
import android.content.Intent
77
import androidx.annotation.AnyThread
8+
import androidx.annotation.IntRange
89
import androidx.annotation.MainThread
910
import androidx.annotation.RestrictTo
1011
import com.ms.square.debugoverlay.internal.InternalDebugOverlayApi
@@ -40,6 +41,7 @@ public object DebugOverlay {
4041
_overlayDataRepository?.apply {
4142
setNetworkSource(newConfig.networkRequestSource)
4243
setCustomLogSource(newConfig.customLogSource)
44+
setLogcatMaxEntries(newConfig.maxLogcatEntries)
4345
}
4446
overlayViewManager?.overlayMode?.value = newConfig.overlayMode
4547
}
@@ -82,7 +84,11 @@ public object DebugOverlay {
8284
check(!isInstalled) { "DebugOverlay already installed" }
8385

8486
overlayScope = CoroutineScope(SupervisorJob() + Dispatchers.Default).also { scope ->
85-
val repository = DebugOverlayDataRepository(application, scope).apply {
87+
val repository = DebugOverlayDataRepository(
88+
context = application,
89+
scope = scope,
90+
initialLogcatMaxEntries = config.maxLogcatEntries
91+
).apply {
8692
setNetworkSource(config.networkRequestSource)
8793
setCustomLogSource(config.customLogSource)
8894
}
@@ -220,11 +226,29 @@ public object DebugOverlay {
220226
*/
221227
public var bugReportExporter: BugReportExporter = initial.bugReportExporter
222228

229+
/**
230+
* Maximum number of entries kept in the built-in Logcat tab buffer.
231+
* Also passed to `logcat -T N` / `-t N` so it controls how many lines the
232+
* OS replays on producer start (panel open) and on bug-report snapshot.
233+
*
234+
* Default is [Config.DEFAULT_MAX_LOGCAT_ENTRIES] (300). Each entry holds a parsed
235+
* [com.ms.square.debugoverlay.model.LogEntry].
236+
*
237+
* @throws IllegalArgumentException if assigned a non-positive value.
238+
*/
239+
@IntRange(from = 1)
240+
public var maxLogcatEntries: Int = initial.maxLogcatEntries
241+
set(value) {
242+
require(value > 0) { "maxLogcatEntries must be positive, was $value" }
243+
field = value
244+
}
245+
223246
internal fun build(): Config = Config(
224247
overlayMode = overlayMode,
225248
networkRequestSource = networkRequestSource,
226249
customLogSource = customLogSource,
227-
bugReportExporter = bugReportExporter
250+
bugReportExporter = bugReportExporter,
251+
maxLogcatEntries = maxLogcatEntries
228252
)
229253
}
230254

@@ -243,6 +267,8 @@ public object DebugOverlay {
243267
* Use debugoverlay-extension-timber for Timber integration.
244268
* @property bugReportExporter The exporter used when the user submits a bug report.
245269
* Default is the built-in share sheet exporter.
270+
* @property maxLogcatEntries Maximum number of entries kept in the built-in Logcat
271+
* tab buffer. Default is [DEFAULT_MAX_LOGCAT_ENTRIES].
246272
*
247273
* @see configure
248274
*/
@@ -251,5 +277,11 @@ public object DebugOverlay {
251277
val networkRequestSource: NetworkRequestSource = NoOpNetworkRequestSource,
252278
val customLogSource: LogSource? = null,
253279
val bugReportExporter: BugReportExporter = IntentShareExporter,
254-
)
280+
val maxLogcatEntries: Int = DEFAULT_MAX_LOGCAT_ENTRIES,
281+
) {
282+
public companion object {
283+
/** Default value for [maxLogcatEntries]. */
284+
public const val DEFAULT_MAX_LOGCAT_ENTRIES: Int = 300
285+
}
286+
}
255287
}

debugoverlay-core/src/main/kotlin/com/ms/square/debugoverlay/internal/data/DebugOverlayDataRepository.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,10 @@ import kotlin.time.Duration.Companion.milliseconds
3838
/** Default name shown when a custom log source doesn't provide a source name. */
3939
internal const val DEFAULT_CUSTOM_LOG_SOURCE_NAME = "Custom"
4040

41-
internal class DebugOverlayDataRepository(context: Context, scope: CoroutineScope) {
41+
internal class DebugOverlayDataRepository(context: Context, scope: CoroutineScope, initialLogcatMaxEntries: Int) {
4242

4343
private val currentNetworkRequestSource = MutableStateFlow<NetworkRequestSource>(NoOpNetworkRequestSource)
44-
private val logcatDataSource = LogcatDataSource(scope)
44+
private val logcatDataSource = LogcatDataSource(scope, initialMaxEntries = initialLogcatMaxEntries)
4545
private val customLogSource = MutableStateFlow<LogSource?>(null)
4646
private val netStatsDataSource = NetStatsDataSource(scope)
4747
private val deviceInfoDataSource = DeviceInfoDataSource(context, scope)
@@ -116,6 +116,10 @@ internal class DebugOverlayDataRepository(context: Context, scope: CoroutineScop
116116
customLogSource.value = source
117117
}
118118

119+
fun setLogcatMaxEntries(maxEntries: Int) {
120+
logcatDataSource.maxEntries = maxEntries
121+
}
122+
119123
fun startOrResumeJankStatsTracking(activity: Activity) {
120124
jankStatsDataSource.startOrResumeTracking(activity)
121125
}

debugoverlay-core/src/main/kotlin/com/ms/square/debugoverlay/internal/data/EvictingQueue.kt

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,41 @@
11
package com.ms.square.debugoverlay.internal.data
22

3+
import androidx.annotation.GuardedBy
4+
import androidx.annotation.IntRange
35
import androidx.annotation.RestrictTo
46
import com.ms.square.debugoverlay.internal.InternalDebugOverlayApi
57

68
/**
79
* A bounded queue that automatically evicts the oldest element when capacity is reached.
810
*
9-
* Thread-safe: all operations are synchronized.
11+
* Thread-safe: all operations are synchronized on the instance monitor. The [capacity]
12+
* property is mutable; assigning a new value resizes the queue (evicting from the head
13+
* if shrinking below current size).
1014
*
11-
* @param capacity Maximum number of elements to retain. Must be positive.
15+
* @param initialCapacity Initial maximum number of elements to retain. Must be positive.
1216
*/
1317
@InternalDebugOverlayApi
1418
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
15-
public class EvictingQueue<T>(private val capacity: Int) {
19+
public class EvictingQueue<T>(initialCapacity: Int) {
1620

17-
init {
18-
require(capacity > 0) { "capacity must be positive, was $capacity" }
19-
}
21+
@GuardedBy("this")
22+
private val queue = ArrayDeque<T>(initialCapacity)
2023

21-
private val queue = ArrayDeque<T>(capacity)
24+
/**
25+
* Current maximum number of elements the queue will retain.
26+
* Assigning a smaller value evicts the oldest elements until size <= capacity.
27+
*/
28+
@IntRange(from = 1)
29+
@GuardedBy("this")
30+
public var capacity: Int = initialCapacity
31+
@Synchronized get
32+
33+
@Synchronized set(value) {
34+
field = value
35+
while (queue.size > value) {
36+
queue.removeFirst()
37+
}
38+
}
2239

2340
/**
2441
* Adds an element to the queue, evicting the oldest if at capacity.

debugoverlay-core/src/main/kotlin/com/ms/square/debugoverlay/internal/data/source/LogcatDataSource.kt

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.ms.square.debugoverlay.internal.data.source
22

33
import android.os.Build
44
import androidx.annotation.GuardedBy
5+
import androidx.annotation.IntRange
56
import com.ms.square.debugoverlay.Clearable
67
import com.ms.square.debugoverlay.LogSource
78
import com.ms.square.debugoverlay.internal.InternalDebugOverlayApi
@@ -38,7 +39,7 @@ import kotlin.time.Duration.Companion.milliseconds
3839
internal class LogcatDataSource(
3940
scope: CoroutineScope,
4041
private val parser: LogcatEntryParser = LogcatEntryParser(),
41-
private val maxEntries: Int = 300,
42+
initialMaxEntries: Int,
4243
) : LogSource,
4344
Clearable,
4445
Closeable {
@@ -49,8 +50,20 @@ internal class LogcatDataSource(
4950

5051
@GuardedBy("processLock")
5152
private var currentProcess: Process? = null
53+
private val entries = EvictingQueue<LogEntry>(initialMaxEntries)
5254

53-
private val entries = EvictingQueue<LogEntry>(maxEntries)
55+
/**
56+
* Maximum number of entries retained in the in-memory buffer and the count
57+
* requested from logcat on next subscription. The currently-running subprocess
58+
* keeps its original `-T N` arg until [WhileSubscribed][SharingStarted.WhileSubscribed]
59+
* restarts the producer (panel reopen).
60+
*/
61+
var maxEntries: Int
62+
@IntRange(from = 1)
63+
get() = entries.capacity
64+
set(@IntRange(from = 1) value) {
65+
entries.capacity = value
66+
}
5467

5568
// Drops OS-replayed entries from before the last clear (e.g. when the producer
5669
// restarts on panel reopen and `-T N` walks the OS ring buffer).

debugoverlay-core/src/test/kotlin/com/ms/square/debugoverlay/DebugOverlayTest.kt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class DebugOverlayTest {
2929
networkRequestSource = NoOpNetworkRequestSource
3030
customLogSource = null
3131
bugReportExporter = IntentShareExporter
32+
maxLogcatEntries = DebugOverlay.Config.DEFAULT_MAX_LOGCAT_ENTRIES
3233
}
3334
DebugOverlay.bugReportContributors.clear()
3435
}
@@ -41,6 +42,30 @@ class DebugOverlayTest {
4142
assertThat(config.networkRequestSource).isEqualTo(NoOpNetworkRequestSource)
4243
assertThat(config.customLogSource).isNull()
4344
assertThat(config.bugReportExporter).isSameInstanceAs(IntentShareExporter)
45+
assertThat(config.maxLogcatEntries).isEqualTo(DebugOverlay.Config.DEFAULT_MAX_LOGCAT_ENTRIES)
46+
}
47+
48+
@Test
49+
fun `configure updates maxLogcatEntries`() {
50+
DebugOverlay.configure {
51+
maxLogcatEntries = 1000
52+
}
53+
54+
assertThat(DebugOverlay.config.maxLogcatEntries).isEqualTo(1000)
55+
}
56+
57+
@Test(expected = IllegalArgumentException::class)
58+
fun `configure throws when maxLogcatEntries is zero`() {
59+
DebugOverlay.configure {
60+
maxLogcatEntries = 0
61+
}
62+
}
63+
64+
@Test(expected = IllegalArgumentException::class)
65+
fun `configure throws when maxLogcatEntries is negative`() {
66+
DebugOverlay.configure {
67+
maxLogcatEntries = -1
68+
}
4469
}
4570

4671
@Test

debugoverlay-core/src/test/kotlin/com/ms/square/debugoverlay/internal/data/EvictingQueueTest.kt

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,6 @@ import org.junit.Test
77
@OptIn(InternalDebugOverlayApi::class)
88
class EvictingQueueTest {
99

10-
@Test(expected = IllegalArgumentException::class)
11-
fun `constructor throws when capacity is zero`() {
12-
EvictingQueue<String>(0)
13-
}
14-
15-
@Test(expected = IllegalArgumentException::class)
16-
fun `constructor throws when capacity is negative`() {
17-
EvictingQueue<String>(-1)
18-
}
19-
20-
@Test
21-
fun `constructor succeeds with capacity of 1`() {
22-
val queue = EvictingQueue<String>(1)
23-
assertThat(queue.size).isEqualTo(0)
24-
}
25-
2610
@Test
2711
fun `add returns null when queue is not at capacity`() {
2812
val queue = EvictingQueue<String>(3)
@@ -141,4 +125,53 @@ class EvictingQueueTest {
141125

142126
assertThat(queue.toList()).containsExactly(null, "value", null).inOrder()
143127
}
128+
129+
@Test
130+
fun `capacity reflects initial value`() {
131+
val queue = EvictingQueue<String>(5)
132+
assertThat(queue.capacity).isEqualTo(5)
133+
}
134+
135+
@Test
136+
fun `capacity setter grows queue without dropping elements`() {
137+
val queue = EvictingQueue<Int>(2)
138+
queue.add(1)
139+
queue.add(2)
140+
141+
queue.capacity = 4
142+
143+
assertThat(queue.capacity).isEqualTo(4)
144+
assertThat(queue.toList()).containsExactly(1, 2).inOrder()
145+
assertThat(queue.add(3)).isNull()
146+
assertThat(queue.add(4)).isNull()
147+
assertThat(queue.toList()).containsExactly(1, 2, 3, 4).inOrder()
148+
}
149+
150+
@Test
151+
fun `capacity setter shrinks queue and evicts oldest elements`() {
152+
val queue = EvictingQueue<Int>(5)
153+
queue.add(1)
154+
queue.add(2)
155+
queue.add(3)
156+
queue.add(4)
157+
queue.add(5)
158+
159+
queue.capacity = 3
160+
161+
assertThat(queue.capacity).isEqualTo(3)
162+
assertThat(queue.toList()).containsExactly(3, 4, 5).inOrder()
163+
}
164+
165+
@Test
166+
fun `add respects new capacity after grow then shrink`() {
167+
val queue = EvictingQueue<Int>(2)
168+
queue.capacity = 4
169+
queue.add(1)
170+
queue.add(2)
171+
queue.add(3)
172+
queue.add(4)
173+
174+
assertThat(queue.add(5)).isEqualTo(1)
175+
assertThat(queue.toList()).containsExactly(2, 3, 4, 5).inOrder()
176+
}
144177
}

0 commit comments

Comments
 (0)