Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
883c722
Basic global minimum breadcrumb level implementation
KishanPRao May 19, 2026
2a086b4
Update bc level attr name; example app: added comment & updated bc me…
KishanPRao May 20, 2026
55162ba
Fix formatting
KishanPRao May 20, 2026
9d80354
Merge branch 'master' into feature/min-breadcrumb-level
KishanPRao May 20, 2026
eed2da0
Remove stray SOP
KishanPRao May 20, 2026
be75a37
Update BreadcrumbsReader, added documentation, fix potentially readin…
KishanPRao May 20, 2026
cf099b7
Check for null bc level, during enable & add bc
KishanPRao May 22, 2026
de102be
Suggestion: try-with-resources for BreadcrumbsReader input stream
KishanPRao May 22, 2026
07f5a88
Fix formatting for test & bc reader
KishanPRao May 22, 2026
a641c15
Suggestion: backward compatible default new interfaces
KishanPRao May 22, 2026
afc03f6
Suggestion: handle null bc level as actual arg (fixes tests)
KishanPRao May 22, 2026
881e1eb
Remove impl note, that could add to confusion
KishanPRao May 22, 2026
cde9e86
Update addBreadcrumb doc, for null bc-level
KishanPRao May 22, 2026
6d0d29c
suggestion: mention doc for default implementation
KishanPRao May 22, 2026
76da50d
suggestion: mention doc for default implementation
KishanPRao May 22, 2026
7578635
suggestion: mention doc for default implementation
KishanPRao May 22, 2026
dceb98f
suggestion: mention doc for default implementation
KishanPRao May 22, 2026
3c27d7c
suggestion: mention doc for default implementation
KishanPRao May 22, 2026
2732e67
fix formatting
KishanPRao May 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import android.content.Context;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import backtraceio.library.enums.BacktraceBreadcrumbLevel;
import backtraceio.library.enums.BacktraceBreadcrumbType;
import java.io.File;
import java.io.IOException;
Expand Down Expand Up @@ -145,6 +146,203 @@ public void testAddBreadcrumbRejectsDisabledType() {
}
}

@Test
public void testAddBreadcrumbRejectsLevelBelowMinimum() {
try {
cleanUp();

backtraceBreadcrumbs =
new BacktraceBreadcrumbs(context.getFilesDir().getAbsolutePath());
backtraceBreadcrumbs.enableBreadcrumbs(
context,
BacktraceBreadcrumbType.ALL,
BacktraceBreadcrumbs.DEFAULT_MAX_LOG_SIZE_BYTES,
BacktraceBreadcrumbLevel.WARNING);

// Below threshold, dropped
assertFalse(backtraceBreadcrumbs.addBreadcrumb(
"test-debug", BacktraceBreadcrumbType.MANUAL, BacktraceBreadcrumbLevel.DEBUG));
assertFalse(backtraceBreadcrumbs.addBreadcrumb(
"test-info", BacktraceBreadcrumbType.MANUAL, BacktraceBreadcrumbLevel.INFO));

List<String> breadcrumbLogFileData = BreadcrumbsReader.readBreadcrumbLogFile(
context.getFilesDir().getAbsolutePath());

// Only the configuration breadcrumb should be present.
assertEquals(1, breadcrumbLogFileData.size());
} catch (Exception ex) {
fail(ex.getMessage());
}
}

@Test
public void testAddBreadcrumbAcceptsLevelAtOrAboveMinimum() {
try {
cleanUp();

backtraceBreadcrumbs =
new BacktraceBreadcrumbs(context.getFilesDir().getAbsolutePath());
backtraceBreadcrumbs.enableBreadcrumbs(
context,
BacktraceBreadcrumbType.ALL,
BacktraceBreadcrumbs.DEFAULT_MAX_LOG_SIZE_BYTES,
BacktraceBreadcrumbLevel.WARNING);

// At threshold, retain
assertTrue(backtraceBreadcrumbs.addBreadcrumb(
"test-warning", BacktraceBreadcrumbType.MANUAL, BacktraceBreadcrumbLevel.WARNING));
// Above threshold, retain
assertTrue(backtraceBreadcrumbs.addBreadcrumb(
"test-error", BacktraceBreadcrumbType.MANUAL, BacktraceBreadcrumbLevel.ERROR));
assertTrue(backtraceBreadcrumbs.addBreadcrumb(
"test-fatal", BacktraceBreadcrumbType.MANUAL, BacktraceBreadcrumbLevel.FATAL));

List<String> breadcrumbLogFileData = BreadcrumbsReader.readBreadcrumbLogFile(
context.getFilesDir().getAbsolutePath());

// Configuration breadcrumb + three added.
assertEquals(4, breadcrumbLogFileData.size());

assertEquals("test-warning", new JSONObject(breadcrumbLogFileData.get(1)).get("message"));
assertEquals("test-error", new JSONObject(breadcrumbLogFileData.get(2)).get("message"));
assertEquals("test-fatal", new JSONObject(breadcrumbLogFileData.get(3)).get("message"));
} catch (Exception ex) {
fail(ex.getMessage());
}
}

@Test
public void testAddBreadcrumbDefaultMinLevelAcceptsAllLevels() {
try {
// default min level should be DEBUG on setUp()
assertEquals(BacktraceBreadcrumbLevel.DEBUG, backtraceBreadcrumbs.getMinBreadcrumbLevel());

assertTrue(backtraceBreadcrumbs.addBreadcrumb(
"test-debug", BacktraceBreadcrumbType.MANUAL, BacktraceBreadcrumbLevel.DEBUG));
assertTrue(backtraceBreadcrumbs.addBreadcrumb(
"test-info", BacktraceBreadcrumbType.MANUAL, BacktraceBreadcrumbLevel.INFO));
assertTrue(backtraceBreadcrumbs.addBreadcrumb(
"test-warning", BacktraceBreadcrumbType.MANUAL, BacktraceBreadcrumbLevel.WARNING));
assertTrue(backtraceBreadcrumbs.addBreadcrumb(
"test-error", BacktraceBreadcrumbType.MANUAL, BacktraceBreadcrumbLevel.ERROR));
assertTrue(backtraceBreadcrumbs.addBreadcrumb(
"test-fatal", BacktraceBreadcrumbType.MANUAL, BacktraceBreadcrumbLevel.FATAL));

List<String> breadcrumbLogFileData = BreadcrumbsReader.readBreadcrumbLogFile(
context.getFilesDir().getAbsolutePath());

// Configuration breadcrumb + five added.
assertEquals(6, breadcrumbLogFileData.size());
} catch (Exception ex) {
fail(ex.getMessage());
}
}

@Test
public void testReEnableBreadcrumbsUpdatesMinLevel() {
try {
cleanUp();

backtraceBreadcrumbs =
new BacktraceBreadcrumbs(context.getFilesDir().getAbsolutePath());
backtraceBreadcrumbs.enableBreadcrumbs(
context,
BacktraceBreadcrumbType.ALL,
BacktraceBreadcrumbs.DEFAULT_MAX_LOG_SIZE_BYTES,
BacktraceBreadcrumbLevel.DEBUG);
assertEquals(BacktraceBreadcrumbLevel.DEBUG, backtraceBreadcrumbs.getMinBreadcrumbLevel());

// Initial threshold lets DEBUG through.
assertTrue(backtraceBreadcrumbs.addBreadcrumb(
"pre-change", BacktraceBreadcrumbType.MANUAL, BacktraceBreadcrumbLevel.DEBUG));

// Tighten threshold by re-enabling.
backtraceBreadcrumbs.enableBreadcrumbs(
context,
BacktraceBreadcrumbType.ALL,
BacktraceBreadcrumbs.DEFAULT_MAX_LOG_SIZE_BYTES,
BacktraceBreadcrumbLevel.ERROR);
assertEquals(BacktraceBreadcrumbLevel.ERROR, backtraceBreadcrumbs.getMinBreadcrumbLevel());

// Now DEBUG is dropped; ERROR still goes through.
assertFalse(backtraceBreadcrumbs.addBreadcrumb(
"post-change-debug", BacktraceBreadcrumbType.MANUAL, BacktraceBreadcrumbLevel.DEBUG));
assertTrue(backtraceBreadcrumbs.addBreadcrumb(
"post-change-error", BacktraceBreadcrumbType.MANUAL, BacktraceBreadcrumbLevel.ERROR));
} catch (Exception ex) {
fail(ex.getMessage());
}
}

Comment thread
KishanPRao marked this conversation as resolved.
@Test
public void testNullMinBreadcrumbLevelDefaultsToDebug() {
try {
cleanUp();

backtraceBreadcrumbs =
new BacktraceBreadcrumbs(context.getFilesDir().getAbsolutePath());

assertTrue(backtraceBreadcrumbs.enableBreadcrumbs(context, (BacktraceBreadcrumbLevel) null));
assertEquals(BacktraceBreadcrumbLevel.DEBUG, backtraceBreadcrumbs.getMinBreadcrumbLevel());

assertTrue(backtraceBreadcrumbs.addBreadcrumb(
"debug-after-null-min-level", BacktraceBreadcrumbType.MANUAL, BacktraceBreadcrumbLevel.DEBUG));

List<String> breadcrumbLogFileData = BreadcrumbsReader.readBreadcrumbLogFile(
context.getFilesDir().getAbsolutePath());

JSONObject configBreadcrumb = new JSONObject(breadcrumbLogFileData.get(0));
assertEquals("Breadcrumbs configuration", configBreadcrumb.get("message"));
assertEquals("debug", configBreadcrumb.getJSONObject("attributes").get("breadcrumb.level"));

JSONObject debugBreadcrumb = new JSONObject(breadcrumbLogFileData.get(1));
assertEquals("debug-after-null-min-level", debugBreadcrumb.get("message"));
assertEquals("debug", debugBreadcrumb.get("level"));
} catch (Exception ex) {
fail(ex.getMessage());
}
}

@Test
public void testNullBreadcrumbLevelDefaultsToInfo() {
try {
assertTrue(backtraceBreadcrumbs.addBreadcrumb("null-level", BacktraceBreadcrumbType.MANUAL, null));

List<String> breadcrumbLogFileData = BreadcrumbsReader.readBreadcrumbLogFile(
context.getFilesDir().getAbsolutePath());

JSONObject parsedBreadcrumb = new JSONObject(breadcrumbLogFileData.get(1));
assertEquals("null-level", parsedBreadcrumb.get("message"));
assertEquals("info", parsedBreadcrumb.get("level"));
} catch (Exception ex) {
fail(ex.getMessage());
}
}

@Test
public void testConfigurationBreadcrumbIncludesMinLevel() {
try {
cleanUp();

backtraceBreadcrumbs =
new BacktraceBreadcrumbs(context.getFilesDir().getAbsolutePath());
backtraceBreadcrumbs.enableBreadcrumbs(
context,
BacktraceBreadcrumbType.ALL,
BacktraceBreadcrumbs.DEFAULT_MAX_LOG_SIZE_BYTES,
BacktraceBreadcrumbLevel.WARNING);

List<String> breadcrumbLogFileData = BreadcrumbsReader.readBreadcrumbLogFile(
context.getFilesDir().getAbsolutePath());

JSONObject configBreadcrumb = new JSONObject(breadcrumbLogFileData.get(0));
assertEquals("Breadcrumbs configuration", configBreadcrumb.get("message"));
assertEquals("warning", configBreadcrumb.getJSONObject("attributes").get("breadcrumb.level"));
} catch (Exception ex) {
fail(ex.getMessage());
}
}

@Test
public void testAddBreadcrumbWithAttributes() {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,67 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.json.JSONException;
import org.json.JSONObject;

/**
* Test helper that extracts breadcrumb JSON records from the on-disk QueueFile log.
*/
public class BreadcrumbsReader {

/**
* Reads the breadcrumb log file and returns each breadcrumb as a JSON string.
*
* <p>Lines are trimmed to their first {@code '{'}} to strip QueueFile framing bytes,
* then kept only if they parse as JSON and contain a {@code timestamp} field. This
* filters out framing noise and orphaned sub-objects exposed by rollover.</p>
*
* @param path directory containing the breadcrumb log
* @return breadcrumb records in file order
*/
public static List<String> readBreadcrumbLogFile(String path) throws IOException {
Comment thread
KishanPRao marked this conversation as resolved.
BacktraceBreadcrumbs breadcrumbs = new BacktraceBreadcrumbs(path);
File breadcrumbLogFile = new File(breadcrumbs.getBreadcrumbLogPath());

List<String> breadcrumbLogFileData = new ArrayList<String>();
Comment thread
KishanPRao marked this conversation as resolved.
FileInputStream inputStream = new FileInputStream(breadcrumbLogFile.getAbsolutePath());

// The encoding contains headers for the encoded data
// We just throw away lines that don't start with "timestamp
StringBuilder stringBuilder = new StringBuilder();
while (inputStream.available() > 0) {
char c = (char) inputStream.read();
if (c == '\n') {
String line = stringBuilder.toString();
if (line.matches(".*timestamp.*")) {
breadcrumbLogFileData.add(line);

try (FileInputStream inputStream = new FileInputStream(breadcrumbLogFile.getAbsolutePath())) {
StringBuilder stringBuilder = new StringBuilder();
int readByte;

while ((readByte = inputStream.read()) != -1) {
char c = (char) readByte;
if (c == '\n') {
addBreadcrumbJsonRecordIfValid(breadcrumbLogFileData, stringBuilder.toString());
stringBuilder.setLength(0);
continue;
}
stringBuilder = new StringBuilder();
continue;

stringBuilder.append(c);
}

if (stringBuilder.length() > 0) {
addBreadcrumbJsonRecordIfValid(breadcrumbLogFileData, stringBuilder.toString());
}
stringBuilder.append(c);
}

return breadcrumbLogFileData;
}

private static void addBreadcrumbJsonRecordIfValid(List<String> breadcrumbLogFileData, String line) {
int braceStart = line.indexOf('{');
if (braceStart < 0) {
return;
}

String candidate = line.substring(braceStart);
try {
JSONObject parsed = new JSONObject(candidate);
if (parsed.has("timestamp")) {
breadcrumbLogFileData.add(candidate);
}
} catch (JSONException ignored) {
// Ignore QueueFile framing bytes and partial rollover fragments.
}
}
}
Loading
Loading