From faab0e326096ad5f7b3e1b94ae02c6b618e8e568 Mon Sep 17 00:00:00 2001 From: Jason Ross Date: Sat, 4 Jul 2026 23:02:13 -0500 Subject: [PATCH] feat(logging): detekt guard banning android.util.Log outside AppLog (#331) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a detekt style>ForbiddenImport rule that forbids `import android.util.Log` so all logging flows through org.libremail.reporting.AppLog, which mirrors each line into the debug-report RingLogBuffer. A raw android.util.Log import writes to Logcat only and never reaches a user-reviewed DebugReport (epic #324, strangler final step). Excludes the AppLog facade itself (the one sanctioned wrapper) and the unit tests that mockkStatic(Log) to verify forwarding — AppLog forwards to Log, a throwing stub under plain JVM unit tests, so those tests must mock it; they do not bypass the facade. Closes #331 Part of #324 Co-Authored-By: Claude Opus 4.8 --- config/detekt/detekt.yml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/config/detekt/detekt.yml b/config/detekt/detekt.yml index 2b81111..b9df1c2 100644 --- a/config/detekt/detekt.yml +++ b/config/detekt/detekt.yml @@ -29,6 +29,40 @@ naming: ignoreAnnotated: ['Composable'] style: + ForbiddenImport: + # Strangler guard for epic #324: production code must log through + # org.libremail.reporting.AppLog, which mirrors every line into the debug-report + # RingLogBuffer. A raw android.util.Log import writes to Logcat only and bypasses that + # buffer, so it never reaches a user-reviewed DebugReport. Excludes cover the ONE allowed + # wrapper (AppLog) plus the unit tests that import Log solely to mockkStatic/verify it — + # AppLog forwards to Log, a throwing stub under plain JVM unit tests, so those tests must + # mock it. They do not bypass the facade. Do NOT add production files here; migrate them to + # AppLog instead. + active: true + forbiddenImports: + - value: 'android.util.Log' + reason: >- + Log via org.libremail.reporting.AppLog so lines reach the debug-report RingLogBuffer; + raw android.util.Log bypasses it (epic #324). + excludes: + # The facade itself — the single sanctioned wrapper around android.util.Log. + - '**/reporting/AppLog.kt' + # Facade tests: mockkStatic(Log) to verify AppLog forwards to Logcat. + - '**/reporting/AppLogTest.kt' + - '**/reporting/AppLogUninstalledTest.kt' + - '**/restart/RestartActivityLoggingTest.kt' + # Unit tests that mockkStatic(Log::class) because the code under test logs via AppLog, + # which forwards to android.util.Log (a no-op throwing stub under plain JVM unit tests). + - '**/ui/lock/AppLockViewModelTest.kt' + - '**/ui/accountsetup/AccountSetupViewModelTest.kt' + - '**/data/sync/SyncWorkerTest.kt' + - '**/data/sync/MailSyncerTest.kt' + - '**/data/sync/MailBackfillerTest.kt' + - '**/data/sync/MailPrunerTest.kt' + - '**/data/sync/MailMaintenanceGateTest.kt' + - '**/data/sync/MailSyncConcurrencyTest.kt' + - '**/data/sync/PruneWorkerTest.kt' + - '**/data/sync/BackfillWorkerTest.kt' MagicNumber: # dp / sp / duration literals are idiomatic inline in Compose. ignoreAnnotated: ['Composable']