feat(reporting): AppLog seam — record throwables, StackTraceScrubber, accountLogRef (#324) #325

Closed
opened 2026-07-05 00:49:15 +00:00 by JMR-dev · 0 comments
JMR-dev commented 2026-07-05 00:49:15 +00:00 (Migrated from github.com)

⚠ CORRECTION (coordinator): StackTraceScrubber ALREADY EXISTS — app/src/main/kotlin/org/libremail/reporting/StackTraceScrubber.kt, added by #316. Do NOT create it. This seam = (a) add throwable-recording overloads to AppLog.w/d/e that push the scrubbed throwable (via the existing StackTraceScrubber) into RingLogBuffer, and (b) add the accountLogRef non-reversible-hash helper. Disregard any "create StackTraceScrubber" text below.

Part of #324 (strangler-migrate debug logging to AppLog).

Sequencing: FIRST — blocks every other migration area. The auth/lock, DB/keystore,
connectivity/send, sync-engine, and straggler tickets all call the throwable-carrying
AppLog overloads, the StackTraceScrubber, and accountLogRef(...) introduced here. Land
this before starting them. The guard-rule ticket is sequenced LAST.

Why

AppLog.e(tag, message, throwable) currently records only the message into the
RingLogBuffer (AppLog.kt:37) — the throwable never reaches a DebugReport. Several call
sites also pass a throwable to Log.w/Log.d, but AppLog has no throwable-carrying w/d.
And DiagnosticsCollector.build serializes the crash throwable via raw
throwable.stackTraceToString() (DiagnosticsCollector.kt:70) with no PII scrubbing —
exception messages embedded in a trace can carry an email/host. There is no
StackTraceScrubber in the repo today
(the epic assumed one); this ticket creates it.

Scope (files)

  • app/src/main/kotlin/org/libremail/reporting/AppLog.kt — edit.
  • app/src/main/kotlin/org/libremail/reporting/StackTraceScrubber.kt — new.
  • app/src/main/kotlin/org/libremail/reporting/AccountLogRef.kt — new (or fold the
    helper into an existing reporting file).
  • app/src/main/kotlin/org/libremail/reporting/DiagnosticsCollector.kt — wire stackTrace
    through the scrubber.
  • Tests: extend AppLogTest; new StackTraceScrubberTest, AccountLogRefTest; extend
    DiagnosticsCollectorTest.

Changes

  1. AppLog.e records the throwable. Keep forwarding to Log.e(tag, message, throwable);
    change the buffer line to include the scrubbed throwable, e.g.
    buffer?.record('E', tag, if (throwable == null) message else "$message\n" + StackTraceScrubber.scrub(throwable)).
  2. Add throwable overloads AppLog.w(tag, message, throwable) and
    AppLog.d(tag, message, throwable) mirroring e: forward to Log.w/Log.d (3-arg) and
    record message + scrubbed throwable to the buffer. Keep the existing 2-arg forms. (These
    overloads are required by the auth/lock, DB/keystore, and connectivity/send sites, which
    currently pass a throwable to Log.w/Log.d.)
  3. StackTraceScrubber (stateless object): fun scrub(t: Throwable): String = scrub(t.stackTraceToString()) and fun scrub(raw: String): String that redacts
    email addresses (e.g. [\w.+-]+@[\w.-]+\.\w+ -> <redacted-email>) and URL/scheme://host
    substrings, preserving exception class names + stack frames. Conservative and pure.
  4. accountLogRef(accountId: String): String — a short, stable, non-reversible token
    for correlating log lines per account without leaking identity, e.g.
    "acct-" + sha256Hex(accountId).take(8). NOTE: Account.id embeds the raw email
    (Account.kt:31 -> "outlook:user@domain.com"), so it is PII and must never be logged
    directly
    — downstream tickets log accountLogRef(account.id) instead (ties to #297).
  5. DiagnosticsCollector.build: stackTrace = throwable?.let { StackTraceScrubber.scrub(it) }
    so the crash-report stack trace is scrubbed too.

PII

No new PII. This ticket adds the tools other areas use to stay PII-safe (scrubber +
accountLogRef).

Test expectation (unit)

  • AppLogTest: AppLog.e("T","msg", IllegalStateException("boom")) -> the buffer's last
    entry now contains boom (today it is only msg). Add coverage for the new
    w/d throwable overloads (buffer contains message + throwable summary; Logcat forwarded).
  • StackTraceScrubberTest: a throwable whose message is "auth failed for user@example.com"
    scrubs to output containing no user@example.com while retaining the exception class name.
  • AccountLogRefTest: deterministic + stable across calls; output contains no @ and is not
    equal to the raw id.
  • DiagnosticsCollectorTest: a crash whose throwable message carries an email produces a
    report whose stackTrace contains no email.
  • Per repo DoD, also add/extend an instrumented assertion only if a runtime surface exists;
    this seam is fully JVM-testable, so unit tests are the primary gate.

Parallelism: none before it — this is the seam. All other area tickets parallelize once
this merges.

> **⚠ CORRECTION (coordinator):** `StackTraceScrubber` ALREADY EXISTS — `app/src/main/kotlin/org/libremail/reporting/StackTraceScrubber.kt`, added by #316. Do NOT create it. This seam = (a) add throwable-recording overloads to `AppLog.w/d/e` that push the **scrubbed** throwable (via the existing `StackTraceScrubber`) into `RingLogBuffer`, and (b) add the `accountLogRef` non-reversible-hash helper. Disregard any "create StackTraceScrubber" text below. Part of #324 (strangler-migrate debug logging to AppLog). **Sequencing: FIRST — blocks every other migration area.** The auth/lock, DB/keystore, connectivity/send, sync-engine, and straggler tickets all call the throwable-carrying `AppLog` overloads, the `StackTraceScrubber`, and `accountLogRef(...)` introduced here. Land this before starting them. The guard-rule ticket is sequenced LAST. ## Why `AppLog.e(tag, message, throwable)` currently records only the **message** into the `RingLogBuffer` (`AppLog.kt:37`) — the throwable never reaches a `DebugReport`. Several call sites also pass a throwable to `Log.w`/`Log.d`, but `AppLog` has no throwable-carrying `w`/`d`. And `DiagnosticsCollector.build` serializes the crash throwable via raw `throwable.stackTraceToString()` (`DiagnosticsCollector.kt:70`) with **no PII scrubbing** — exception *messages* embedded in a trace can carry an email/host. There is **no `StackTraceScrubber` in the repo today** (the epic assumed one); this ticket creates it. ## Scope (files) - `app/src/main/kotlin/org/libremail/reporting/AppLog.kt` — edit. - `app/src/main/kotlin/org/libremail/reporting/StackTraceScrubber.kt` — **new**. - `app/src/main/kotlin/org/libremail/reporting/AccountLogRef.kt` — **new** (or fold the helper into an existing reporting file). - `app/src/main/kotlin/org/libremail/reporting/DiagnosticsCollector.kt` — wire `stackTrace` through the scrubber. - Tests: extend `AppLogTest`; new `StackTraceScrubberTest`, `AccountLogRefTest`; extend `DiagnosticsCollectorTest`. ## Changes 1. **`AppLog.e` records the throwable.** Keep forwarding to `Log.e(tag, message, throwable)`; change the buffer line to include the scrubbed throwable, e.g. `buffer?.record('E', tag, if (throwable == null) message else "$message\n" + StackTraceScrubber.scrub(throwable))`. 2. **Add throwable overloads** `AppLog.w(tag, message, throwable)` and `AppLog.d(tag, message, throwable)` mirroring `e`: forward to `Log.w`/`Log.d` (3-arg) and record `message + scrubbed throwable` to the buffer. Keep the existing 2-arg forms. (These overloads are required by the auth/lock, DB/keystore, and connectivity/send sites, which currently pass a throwable to `Log.w`/`Log.d`.) 3. **`StackTraceScrubber`** (stateless `object`): `fun scrub(t: Throwable): String = scrub(t.stackTraceToString())` and `fun scrub(raw: String): String` that redacts email addresses (e.g. `[\w.+-]+@[\w.-]+\.\w+` -> `<redacted-email>`) and URL/`scheme://host` substrings, preserving exception class names + stack frames. Conservative and pure. 4. **`accountLogRef(accountId: String): String`** — a short, stable, **non-reversible** token for correlating log lines per account without leaking identity, e.g. `"acct-" + sha256Hex(accountId).take(8)`. NOTE: `Account.id` embeds the raw email (`Account.kt:31` -> `"outlook:user@domain.com"`), so it is **PII and must never be logged directly** — downstream tickets log `accountLogRef(account.id)` instead (ties to #297). 5. **`DiagnosticsCollector.build`**: `stackTrace = throwable?.let { StackTraceScrubber.scrub(it) }` so the crash-report stack trace is scrubbed too. ## PII No new PII. This ticket adds the *tools* other areas use to stay PII-safe (scrubber + `accountLogRef`). ## Test expectation (unit) - `AppLogTest`: `AppLog.e("T","msg", IllegalStateException("boom"))` -> the buffer's last entry now contains `boom` (today it is only `msg`). Add coverage for the new `w`/`d` throwable overloads (buffer contains message + throwable summary; Logcat forwarded). - `StackTraceScrubberTest`: a throwable whose message is `"auth failed for user@example.com"` scrubs to output containing **no** `user@example.com` while retaining the exception class name. - `AccountLogRefTest`: deterministic + stable across calls; output contains no `@` and is not equal to the raw id. - `DiagnosticsCollectorTest`: a crash whose throwable message carries an email produces a report whose `stackTrace` contains no email. - Per repo DoD, also add/extend an instrumented assertion only if a runtime surface exists; this seam is fully JVM-testable, so unit tests are the primary gate. **Parallelism:** none before it — this is the seam. All other area tickets parallelize once this merges.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: JMR-dev/LibreMail#325