Skip to content

Add opt-in local diagnostics that tee AppLog - #187

Open
anod wants to merge 2 commits into
masterfrom
opt-in-diagnostics
Open

anod wants to merge 2 commits into
masterfrom
opt-in-diagnostics

Conversation

@anod

@anod anod commented Oct 28, 2026

Copy link
Copy Markdown
Owner

Why

Beta testers stop receiving update notifications until they open the app, and existing logs (Crashlytics breadcrumbs, refresh history) cannot show what happened in between. This adds opt-in, on-device diagnostics for selected accounts so a tester can share a log covering the silent period.

Split out of #186, which keeps only the sync hardening. Independent of #186; based on master.

What

  • Settings toggle shown only for accounts in a private build-time allowlist (APPWATCHER_DIAGNOSTIC_ACCOUNTS in the Gradle user home, stored in BuildConfig as SHA-256 hashes; a rollout gate, not a security boundary). Off by default; consent is stored per device and bound to the selected account, so switching accounts disables it.
  • While enabled, an AppLog tee also writes log lines to noBackupFilesDir/diagnostics (30-day / 10 MiB rotation). Logs may contain package names and error messages. Enabling records a startup snapshot: power/background restrictions, standby bucket, recent process exit reasons, and sync WorkManager state.
  • Manual Share (FileProvider snapshot under a narrow cache path) and Delete. Nothing uploads automatically.

Performance

  • Disabled or not allowed: no tee installed; AppLog cost unchanged.
  • Enabled: callers only enqueue into a bounded non-blocking channel (lines dropped and counted under load); a single IO writer appends and flushes.

Wiring

  • DiagnosticAllowlist and WorkInfoSource are explicit Koin dependencies, so Preferences and DiagnosticRecorder use singleOf without hidden defaults.

Tests

  • Consent/account binding, logging tee lifecycle, rotation/retention, sharing, queue safety, Settings events.
  • :app:testDebugUnitTest 251/251; :app:assembleDebug and main ktlint clean (pre-existing AppItemStateTest unused import untouched).

Allowlisted accounts can record app log lines to bounded on-device files and share a snapshot manually. No logging tee is installed unless diagnostics are enabled and allowed.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 28, 2026 19:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Unresolved issues preserve diagnostic consent across account changes, leave displayed log state stale, prevent recovery after file-open failures, and can leave callers suspended indefinitely during shutdown.

4 open issues
What changed in this PR

Adds opt-in, on-device diagnostics to help investigate gaps in background update notifications. This complements the sync hardening in #186 by capturing logs for manual sharing, without automatic uploads.

Changes:

  • Adds a hashed account allowlist and device-local, account-bound recording consent.
  • Introduces bounded AppLog recording, startup snapshots, retention, sharing, and deletion.
  • Wires diagnostics into Settings and dependency injection, with focused tests.
File Description
README.md Documents configuration, consent, retention, and sharing.
app/​src/​test/​java/​com/​anod/​appwatcher/​watchlist/​WatchListPagingSourceRoomTest.kt Supplies the explicit allowlist dependency.
app/​src/​test/​java/​com/​anod/​appwatcher/​watchlist/​AccountSelectionViewModelTest.kt Updates Preferences construction.
app/​src/​test/​java/​com/​anod/​appwatcher/​sync/​UpdateCheckRegistrationTest.kt Updates Preferences construction.
app/​src/​test/​java/​com/​anod/​appwatcher/​preferences/​SettingsScreenDiagnosticsTest.kt Tests diagnostic click events and recomposition.
app/​src/​test/​java/​com/​anod/​appwatcher/​diagnostics/​TestDiagnostics.kt Adds a disabled-recorder test helper.
app/​src/​test/​java/​com/​anod/​appwatcher/​diagnostics/​DiagnosticRecorderTest.kt Tests recording, sharing, queue handling, and failures.
app/​src/​test/​java/​com/​anod/​appwatcher/​diagnostics/​DiagnosticPreferencesTest.kt Tests consent persistence and account eligibility.
app/​src/​test/​java/​com/​anod/​appwatcher/​diagnostics/​DiagnosticFileStoreTest.kt Tests rotation, retention, snapshots, and deletion.
app/​src/​test/​java/​com/​anod/​appwatcher/​backup/​gdrive/​GDriveFailureTest.kt Supplies the explicit allowlist dependency.
app/​src/​test/​java/​com/​anod/​appwatcher/​accounts/​DeviceRegistrationTest.kt Updates Preferences construction.
app/​src/​test/​java/​com/​anod/​appwatcher/​accounts/​AuthRecoveringDfeApiTest.kt Updates Preferences construction.
app/​src/​test/​java/​com/​anod/​appwatcher/​accounts/​AuthAccountInitializerTest.kt Updates Preferences construction.
app/​src/​main/​res/​xml/​diagnostic_paths.xml Restricts sharing to diagnostic snapshots.
app/​src/​main/​res/​values/​strings.xml Adds diagnostic controls and status text.
app/​src/​main/​java/​com/​anod/​appwatcher/​sync/​SyncScheduler.kt Exposes work names for diagnostic queries.
app/​src/​main/​java/​com/​anod/​appwatcher/​preferences/​SettingsViewModel.kt Handles recording, sharing, deletion, and state updates.
app/​src/​main/​java/​com/​anod/​appwatcher/​preferences/​SettingsScreen.kt Dispatches diagnostic control events.
app/​src/​main/​java/​com/​anod/​appwatcher/​preferences/​Preferences.kt Adds account eligibility and device-local consent.
app/​src/​main/​java/​com/​anod/​appwatcher/​preferences/​PreferenceItems.kt Builds diagnostic controls and storage status.
app/​src/​main/​java/​com/​anod/​appwatcher/​diagnostics/​DiagnosticRecorder.kt Coordinates queued logging, metadata, and exports.
app/​src/​main/​java/​com/​anod/​appwatcher/​diagnostics/​DiagnosticLogger.kt Adds a logging tee that isolates sink failures.
app/​src/​main/​java/​com/​anod/​appwatcher/​diagnostics/​DiagnosticFileStore.kt Implements log segments, retention, and snapshots.
app/​src/​main/​java/​com/​anod/​appwatcher/​diagnostics/​DiagnosticAccounts.kt Provides normalized hashing and allowlist parsing.
app/​src/​main/​java/​com/​anod/​appwatcher/​AppWatcherApplication.kt Initializes diagnostics after the crash logger.
app/​src/​main/​java/​com/​anod/​appwatcher/​AppModule.kt Registers diagnostic dependencies.
app/​src/​main/​AndroidManifest.xml Declares the diagnostic FileProvider.
app/​build.gradle.kts Generates hashed account allowlist constants.

Review effort: Balanced

Comment on lines +40 to +41
active = File(directory, "${sequence.toString().padStart(20, '0')}-$now.log")
output = BufferedOutputStream(active!!.outputStream())
next = commands.tryReceive().getOrNull()
}
try {
store.flush()
Comment on lines +263 to +265
commands.send(Command.Operation(action, result))
// The writer may have stopped after the send; nothing will consume this command.
if (stopped.get()) drainPending()
@Volatile
private var accountSnapshot: AuthAccount? = null
set(value) {
diagnosticAccountHash = value?.name?.let(DiagnosticAccounts::hash)
- Show the Settings Local diagnostics section only when the selected
  account is on the diagnostic allowlist; drop the long description.
- Read WorkManager state via suspend Flow instead of a blocking get.
- Pass DiagnosticStorageState to preferenceItems instead of the recorder.
- Make DiagnosticFileStore limits explicit constructor arguments.
- Remove unused TestDiagnostics and the README digest claim.
- Add PreferenceItemsDiagnosticsTest and a Pixel device recorder test.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 28, 2026 21:40

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Unresolved defects can re-enable recording without renewed consent after account switches, leave saved logs unshareable, and cause persistent write failures or indefinitely suspended operations.

4 open issues

Review effort: Balanced

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants