feat(reporting): PII-free latency breadcrumbs on the message-open path (#358) #365

Merged
JMR-dev merged 1 commits from feat-358-reader-perf-logging into main 2026-07-05 23:54:33 +00:00
JMR-dev commented 2026-07-05 22:47:17 +00:00 (Migrated from github.com)

Summary

Adds PII-free AppLog latency breadcrumbs to the message-open path (issue #358), so a debug report can show where the reader's spinner time actually goes:

  • ImapClient.withStore: splits each op's connect (CONNECT+TLS+LOGIN) time from its own work time, plus a live connect-per-op connection gauge (useful context alongside issue #125's provider connection-ceiling findings).
  • fetchBodyMarkingSeen: adds select/body/flag phase timings plus PII-free size counts (RFC822 wire size, body char count, attachment count).
  • MailRepositoryImpl.openMessage: end-to-end open latency plus the cached-vs-fetched branch taken, keyed by the account's hashed accountLogRef and the folder's logSafeFolderLabel.
  • ReaderViewModel: spinner-to-ready latency, logged separately for the success and failure paths.

All of this is PII-free: accounts are only ever logged via the existing accountLogRef one-way hash (never the raw account id/email), folders via the existing logSafeFolderLabel allowlist (system folders only; anything else logs a fixed placeholder), and every other value is a size, duration, or boolean — never message content, subjects, or addresses.

Test tax

Four existing unit-test classes exercise this code but hadn't been touched by the earlier commits in this branch, so they crashed on the now-hit (but unmocked) android.util.Log calls — a throwing no-op stub under plain JVM unit tests:

  • MailRepositoryImplCoverageTest (calls openMessage)
  • ImapClientBackfillTest (real ImapClient via GreenMail)
  • ImapFolderOpenLatencyTest (real ImapClient via GreenMail + a counting proxy)
  • ReaderViewModelActionsTest (constructs ReaderViewModel)

Each now installs mockkStatic(Log::class) in setUp()/tears it down in tearDown(), following this repo's existing convention (MailBackfillerTest.kt's inline import, or ImapClientTest.kt's fully-qualified android.util.Log form for the GreenMail-backed IMAP tests, which deliberately never import Log). detekt.yml gains two more targeted ForbiddenImport excludes (MailRepositoryImplCoverageTest.kt, ReaderViewModelActionsTest.kt) alongside the existing ones for MailRepositoryImplTest.kt / ReaderViewModelTest.kt, plus the pre-existing targeted LargeClass exclude for the already boundary-sized MailRepositoryImplTest.

ImapFolderOpenLatencyTest specifically asserts IMAP connection/LOGIN counts against a real in-process server — those assertions are untouched and still pass, since the new breadcrumbs are pure local timing/counter bookkeeping plus a log call, not additional protocol traffic.

Logging behavior itself is covered by unit tests (matching the existing logging-epic precedent: assert on breadcrumb content via RingLogBuffer, not by mocking Log calls), and the existing reader E2E/instrumented suite continues to exercise the message-open UI path end to end.

Test plan

  • :app:assembleDebug
  • :app:testDebugUnitTest (all green, including the 4 previously-broken classes — MailRepositoryImplCoverageTest 36/36, ImapClientBackfillTest 3/3, ImapFolderOpenLatencyTest 7/7 with its connection/LOGIN count assertions intact, ReaderViewModelActionsTest 10/10)
  • :app:compileDebugAndroidTestKotlin
  • :app:lintDebug
  • :app:ktlintCheck
  • :app:detekt
  • CI's full multi-API E2E matrix + API 37 preview (left to CI per this repo's fast-gate convention for logging-only changes)

🤖 Generated with Claude Code

## Summary Adds PII-free `AppLog` latency breadcrumbs to the message-open path (issue #358), so a debug report can show where the reader's spinner time actually goes: - **`ImapClient.withStore`**: splits each op's connect (CONNECT+TLS+LOGIN) time from its own work time, plus a live connect-per-op connection gauge (useful context alongside issue #125's provider connection-ceiling findings). - **`fetchBodyMarkingSeen`**: adds select/body/flag phase timings plus PII-free size counts (RFC822 wire size, body char count, attachment count). - **`MailRepositoryImpl.openMessage`**: end-to-end open latency plus the cached-vs-fetched branch taken, keyed by the account's hashed `accountLogRef` and the folder's `logSafeFolderLabel`. - **`ReaderViewModel`**: spinner-to-ready latency, logged separately for the success and failure paths. All of this is **PII-free**: accounts are only ever logged via the existing `accountLogRef` one-way hash (never the raw account id/email), folders via the existing `logSafeFolderLabel` allowlist (system folders only; anything else logs a fixed placeholder), and every other value is a size, duration, or boolean — never message content, subjects, or addresses. ### Test tax Four existing unit-test classes exercise this code but hadn't been touched by the earlier commits in this branch, so they crashed on the now-hit (but unmocked) `android.util.Log` calls — a throwing no-op stub under plain JVM unit tests: - `MailRepositoryImplCoverageTest` (calls `openMessage`) - `ImapClientBackfillTest` (real `ImapClient` via GreenMail) - `ImapFolderOpenLatencyTest` (real `ImapClient` via GreenMail + a counting proxy) - `ReaderViewModelActionsTest` (constructs `ReaderViewModel`) Each now installs `mockkStatic(Log::class)` in `setUp()`/tears it down in `tearDown()`, following this repo's existing convention (`MailBackfillerTest.kt`'s inline import, or `ImapClientTest.kt`'s fully-qualified `android.util.Log` form for the GreenMail-backed IMAP tests, which deliberately never import `Log`). `detekt.yml` gains two more targeted `ForbiddenImport` excludes (`MailRepositoryImplCoverageTest.kt`, `ReaderViewModelActionsTest.kt`) alongside the existing ones for `MailRepositoryImplTest.kt` / `ReaderViewModelTest.kt`, plus the pre-existing targeted `LargeClass` exclude for the already boundary-sized `MailRepositoryImplTest`. `ImapFolderOpenLatencyTest` specifically asserts IMAP connection/LOGIN *counts* against a real in-process server — those assertions are untouched and still pass, since the new breadcrumbs are pure local timing/counter bookkeeping plus a log call, not additional protocol traffic. Logging behavior itself is covered by unit tests (matching the existing logging-epic precedent: assert on breadcrumb content via `RingLogBuffer`, not by mocking `Log` calls), and the existing reader E2E/instrumented suite continues to exercise the message-open UI path end to end. ## Test plan - [x] `:app:assembleDebug` - [x] `:app:testDebugUnitTest` (all green, including the 4 previously-broken classes — `MailRepositoryImplCoverageTest` 36/36, `ImapClientBackfillTest` 3/3, `ImapFolderOpenLatencyTest` 7/7 with its connection/LOGIN count assertions intact, `ReaderViewModelActionsTest` 10/10) - [x] `:app:compileDebugAndroidTestKotlin` - [x] `:app:lintDebug` - [x] `:app:ktlintCheck` - [x] `:app:detekt` - [ ] CI's full multi-API E2E matrix + API 37 preview (left to CI per this repo's fast-gate convention for logging-only changes) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.