⚠ 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.
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
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)).
Add throwable overloadsAppLog.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.)
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.
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).
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 nouser@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.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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
AppLogoverloads, theStackTraceScrubber, andaccountLogRef(...)introduced here. Landthis before starting them. The guard-rule ticket is sequenced LAST.
Why
AppLog.e(tag, message, throwable)currently records only the message into theRingLogBuffer(AppLog.kt:37) — the throwable never reaches aDebugReport. Several callsites also pass a throwable to
Log.w/Log.d, butAppLoghas no throwable-carryingw/d.And
DiagnosticsCollector.buildserializes the crash throwable via rawthrowable.stackTraceToString()(DiagnosticsCollector.kt:70) with no PII scrubbing —exception messages embedded in a trace can carry an email/host. There is no
StackTraceScrubberin 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 thehelper into an existing reporting file).
app/src/main/kotlin/org/libremail/reporting/DiagnosticsCollector.kt— wirestackTracethrough the scrubber.
AppLogTest; newStackTraceScrubberTest,AccountLogRefTest; extendDiagnosticsCollectorTest.Changes
AppLog.erecords the throwable. Keep forwarding toLog.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)).AppLog.w(tag, message, throwable)andAppLog.d(tag, message, throwable)mirroringe: forward toLog.w/Log.d(3-arg) andrecord
message + scrubbed throwableto the buffer. Keep the existing 2-arg forms. (Theseoverloads are required by the auth/lock, DB/keystore, and connectivity/send sites, which
currently pass a throwable to
Log.w/Log.d.)StackTraceScrubber(statelessobject):fun scrub(t: Throwable): String = scrub(t.stackTraceToString())andfun scrub(raw: String): Stringthat redactsemail addresses (e.g.
[\w.+-]+@[\w.-]+\.\w+-><redacted-email>) and URL/scheme://hostsubstrings, preserving exception class names + stack frames. Conservative and pure.
accountLogRef(accountId: String): String— a short, stable, non-reversible tokenfor correlating log lines per account without leaking identity, e.g.
"acct-" + sha256Hex(accountId).take(8). NOTE:Account.idembeds the raw email(
Account.kt:31->"outlook:user@domain.com"), so it is PII and must never be loggeddirectly — downstream tickets log
accountLogRef(account.id)instead (ties to #297).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 lastentry now contains
boom(today it is onlymsg). Add coverage for the neww/dthrowable 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.comwhile retaining the exception class name.AccountLogRefTest: deterministic + stable across calls; output contains no@and is notequal to the raw id.
DiagnosticsCollectorTest: a crash whose throwable message carries an email produces areport whose
stackTracecontains no email.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.