From 3868ab5006d99b3081628fe5faf64329b9d306a5 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 23 Feb 2026 20:39:23 -0500 Subject: [PATCH] refactor: replace all println/printStackTrace logging with Kermit Adopt Kermit (co.touchlab:kermit:2.0.8) as the KMP logging framework across the entire codebase, replacing ad-hoc println(), printStackTrace(), and System.err.println() calls with structured, level-filtered logging. - Add kermit dependency to gradle/libs.versions.toml and base/build.gradle.kts (as api() so all downstream modules inherit it) - Replace ~50 println() calls with Logger.withTag("Tag").x { } using appropriate levels (d/i/w/e) - Replace all 5 printStackTrace() calls with log.e(throwable) { } - Add logging to ~15 silent catch blocks (catch (_: Exception)) - Remove manual DEBUG flag in Usb4JavaPN533Transport.kt (Kermit handles log level filtering) - Add CLAUDE.md rule #10 documenting Kermit logging conventions Scope: all production Kotlin code (commonMain, androidMain, iosMain, wasmJsMain, jvmMain). Excluded: tools/mdst/ CLI utilities and test files. Co-Authored-By: Claude Opus 4.6 --- CLAUDE.md | 33 +++++++++++++++++++ .../farebot/desktop/DesktopCardScanner.kt | 13 +++++--- .../com/codebutler/farebot/desktop/Main.kt | 21 ++++++++++++ .../farebot/desktop/PN533ReaderBackend.kt | 2 +- .../farebot/desktop/PN53xReaderBackend.kt | 24 +++++++++----- .../farebot/desktop/PcscReaderBackend.kt | 20 ++++++----- .../farebot/desktop/RCS956ReaderBackend.kt | 2 +- .../farebot/shared/nfc/ISO7816Dispatcher.kt | 5 ++- .../shared/serialize/MetrodroidJsonParser.kt | 5 ++- .../farebot/shared/viewmodel/CardViewModel.kt | 6 ++-- .../shared/viewmodel/FlipperViewModel.kt | 7 ++-- .../shared/viewmodel/HistoryViewModel.kt | 6 ++-- .../farebot/shared/viewmodel/HomeViewModel.kt | 9 ++--- .../farebot/shared/viewmodel/KeysViewModel.kt | 6 ++-- .../web/LocalStorageCardKeysPersister.kt | 7 ++-- .../farebot/web/LocalStorageCardPersister.kt | 5 ++- .../codebutler/farebot/web/WebCardScanner.kt | 30 ++++++++++------- base/build.gradle.kts | 1 + .../farebot/base/mdst/ResourceAccessor.kt | 4 ++- .../base/mdst/MdstStationTableReader.kt | 7 ++-- .../farebot/base/mdst/ResourceAccessor.kt | 6 +++- .../farebot/base/mdst/ResourceAccessor.kt | 7 +++- .../farebot/base/mdst/ResourceAccessor.kt | 4 ++- .../farebot/card/cepas/CEPASProtocol.kt | 9 +++-- .../card/felica/AndroidFeliCaTagAdapter.kt | 10 ++++-- .../card/felica/PN533FeliCaTagAdapter.kt | 5 ++- .../card/felica/PCSCFeliCaTagAdapter.kt | 5 ++- .../farebot/card/iso7816/ISO7816CardReader.kt | 19 ++++++----- .../farebot/card/iso7816/ISO7816Protocol.kt | 5 ++- .../farebot/card/iso7816/ISO7816TLV.kt | 7 ++-- .../farebot/card/ksx6924/KSX6924Utils.kt | 6 ++-- .../card/nfc/pn533/PN533ClassicTechnology.kt | 6 +++- .../card/nfc/pn533/Usb4JavaPN533Transport.kt | 11 ++++--- .../card/ultralight/UltralightCardReader.kt | 7 ++-- .../card/ultralight/UltralightProtocol.kt | 15 +++++---- .../card/ultralight/UltralightTypeRaw.kt | 27 ++++++++------- .../card/vicinity/VicinityCardReader.kt | 5 ++- flipper/build.gradle.kts | 1 + .../flipper/AndroidUsbSerialTransport.kt | 6 +++- gradle/libs.versions.toml | 4 +++ .../codebutler/farebot/transit/bip/BipUtil.kt | 5 ++- .../transit/calypso/CalypsoTransitFactory.kt | 6 +++- .../intercode/IntercodeTransitFactory.kt | 12 +++++-- .../transit/calypso/mobib/MobibTransitInfo.kt | 7 ++-- .../farebot/transit/china/ChinaTransitData.kt | 3 +- .../farebot/transit/erg/ErgTransitInfo.kt | 3 +- .../pilet/KievDigitalTransitFactory.kt | 6 +++- .../transit/pilet/TartuTransitFactory.kt | 6 +++- .../transit/serialonly/NorticTransitInfo.kt | 4 ++- .../smartrider/SmartRiderTransitFactory.kt | 7 ++-- .../transit/tfileap/LeapTransitFactory.kt | 5 ++- .../transit/troika/TroikaTransitFactory.kt | 12 +++++-- 52 files changed, 326 insertions(+), 128 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 11d45ba9b..90028dd41 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -112,6 +112,39 @@ Do NOT claim work is complete without verification. When continuing from a previous session, check for implementation plans and session transcripts in `~/.claude/` to recover context rather than starting from scratch. +### 10. Use Kermit for all logging + +Use `co.touchlab.kermit.Logger` for all logging. Never use `println()`, `e.printStackTrace()`, `android.util.Log`, `NSLog`, or `console.log` in Kotlin code. + +```kotlin +import co.touchlab.kermit.Logger + +// Create a tagged logger (use class/module name as tag) +private val log = Logger.withTag("MyClass") + +// Log levels (least to most severe): v, d, i, w, e, a +log.d { "Debug message with $variable" } // Use lambda syntax for lazy eval +log.w(exception) { "Warning with context" } // Attach throwable +log.e(exception) { "Error description" } // Errors — replaces printStackTrace() + +// In catch blocks — NEVER swallow exceptions silently: +catch (e: Exception) { + log.w(e) { "Descriptive message about what failed" } + // ... handle gracefully +} + +// For expected/benign exceptions, still log at debug level: +catch (e: SpecificException) { + log.d { "Expected: description" } +} +``` + +Do NOT: +- Use `println()` for logging (except in CLI tools under `tools/`) +- Call `e.printStackTrace()` — use `log.e(e) { "msg" }` instead +- Catch exceptions without logging them (no silent swallowing) +- Use `catch (_: Exception)` without at least a `log.d` call + ## Build Commands ```bash diff --git a/app/desktop/src/jvmMain/kotlin/com/codebutler/farebot/desktop/DesktopCardScanner.kt b/app/desktop/src/jvmMain/kotlin/com/codebutler/farebot/desktop/DesktopCardScanner.kt index 86d63e6ac..215bd4fc6 100644 --- a/app/desktop/src/jvmMain/kotlin/com/codebutler/farebot/desktop/DesktopCardScanner.kt +++ b/app/desktop/src/jvmMain/kotlin/com/codebutler/farebot/desktop/DesktopCardScanner.kt @@ -22,6 +22,7 @@ package com.codebutler.farebot.desktop +import co.touchlab.kermit.Logger import com.codebutler.farebot.card.RawCard import com.codebutler.farebot.card.nfc.pn533.PN533 import com.codebutler.farebot.card.nfc.pn533.PN533Device @@ -48,6 +49,8 @@ import kotlinx.coroutines.launch * the error is logged and the other backends continue scanning. * Results from any backend are emitted to the shared [scannedCards] flow. */ +private val log = Logger.withTag("DesktopCardScanner") + class DesktopCardScanner : CardScanner { override val requiresActiveScan: Boolean = true @@ -80,7 +83,7 @@ class DesktopCardScanner : CardScanner { val backendJobs = backends.map { backend -> launch { - println("[DesktopCardScanner] Starting ${backend.name} backend") + log.i { "Starting ${backend.name} backend" } try { backend.scanLoop( onCardDetected = { tag -> @@ -100,11 +103,11 @@ class DesktopCardScanner : CardScanner { ) } catch (e: Exception) { if (isActive) { - println("[DesktopCardScanner] ${backend.name} backend failed: ${e.message}") + log.w(e) { "${backend.name} backend failed" } } } catch (e: Error) { // Catch LinkageError / UnsatisfiedLinkError from native libs - println("[DesktopCardScanner] ${backend.name} backend unavailable: ${e.message}") + log.w(e) { "${backend.name} backend unavailable" } } } } @@ -132,7 +135,7 @@ class DesktopCardScanner : CardScanner { try { PN533Device.openAll() } catch (e: UnsatisfiedLinkError) { - println("[DesktopCardScanner] libusb not available: ${e.message}") + log.w(e) { "libusb not available" } emptyList() } if (transports.isEmpty()) { @@ -144,7 +147,7 @@ class DesktopCardScanner : CardScanner { val probe = PN533(transport) val fw = probe.getFirmwareVersion() val label = "PN53x #${index + 1}" - println("[DesktopCardScanner] $label firmware: $fw") + log.i { "$label firmware: $fw" } if (fw.version >= 2) { backends.add(PN533ReaderBackend(transport)) } else { diff --git a/app/desktop/src/jvmMain/kotlin/com/codebutler/farebot/desktop/Main.kt b/app/desktop/src/jvmMain/kotlin/com/codebutler/farebot/desktop/Main.kt index 4151843ec..334882af7 100644 --- a/app/desktop/src/jvmMain/kotlin/com/codebutler/farebot/desktop/Main.kt +++ b/app/desktop/src/jvmMain/kotlin/com/codebutler/farebot/desktop/Main.kt @@ -13,6 +13,9 @@ import androidx.compose.ui.window.MenuBar import androidx.compose.ui.window.Window import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState +import co.touchlab.kermit.LogWriter +import co.touchlab.kermit.Logger +import co.touchlab.kermit.Severity import com.codebutler.farebot.card.CardType import com.codebutler.farebot.shared.FareBotApp import com.codebutler.farebot.shared.di.LocalAppGraph @@ -26,6 +29,24 @@ import javax.imageio.ImageIO private const val ICON_PATH = "composeResources/farebot.app.generated.resources/drawable/ic_launcher.png" fun main() { + Logger.setLogWriters( + object : LogWriter() { + override fun log( + severity: Severity, + message: String, + tag: String, + throwable: Throwable?, + ) { + val ts = java.time.LocalTime.now() + val prefix = "$ts ${severity.name[0]}/$tag: " + println("$prefix$message") + throwable?.stackTraceToString()?.lineSequence()?.forEach { line -> + println("$prefix$line") + } + } + }, + ) + System.setProperty("apple.awt.application.appearance", "system") val desktop = Desktop.getDesktop() diff --git a/app/desktop/src/jvmMain/kotlin/com/codebutler/farebot/desktop/PN533ReaderBackend.kt b/app/desktop/src/jvmMain/kotlin/com/codebutler/farebot/desktop/PN533ReaderBackend.kt index ab2f67a1c..3088304c5 100644 --- a/app/desktop/src/jvmMain/kotlin/com/codebutler/farebot/desktop/PN533ReaderBackend.kt +++ b/app/desktop/src/jvmMain/kotlin/com/codebutler/farebot/desktop/PN533ReaderBackend.kt @@ -35,7 +35,7 @@ class PN533ReaderBackend( override suspend fun initDevice(pn533: PN533) { val fw = pn533.getFirmwareVersion() - println("[$name] Firmware: $fw") + log.i { "Firmware: $fw" } pn533.samConfiguration() pn533.setMaxRetries(passiveActivation = 0x02) } diff --git a/app/desktop/src/jvmMain/kotlin/com/codebutler/farebot/desktop/PN53xReaderBackend.kt b/app/desktop/src/jvmMain/kotlin/com/codebutler/farebot/desktop/PN53xReaderBackend.kt index 4a472440d..280dc5641 100644 --- a/app/desktop/src/jvmMain/kotlin/com/codebutler/farebot/desktop/PN53xReaderBackend.kt +++ b/app/desktop/src/jvmMain/kotlin/com/codebutler/farebot/desktop/PN53xReaderBackend.kt @@ -22,6 +22,7 @@ package com.codebutler.farebot.desktop +import co.touchlab.kermit.Logger import com.codebutler.farebot.card.CardType import com.codebutler.farebot.card.RawCard import com.codebutler.farebot.card.cepas.CEPASCardReader @@ -51,6 +52,8 @@ import kotlinx.coroutines.delay abstract class PN53xReaderBackend( private val preOpenedTransport: Usb4JavaPN533Transport? = null, ) : NfcReaderBackend { + protected val log by lazy { Logger.withTag(name) } + protected abstract suspend fun initDevice(pn533: PN533) protected open fun createTransceiver( @@ -87,7 +90,7 @@ abstract class PN53xReaderBackend( onProgress: (suspend (current: Int, total: Int) -> Unit)?, ) { while (true) { - println("[$name] Polling for cards...") + log.i { "Polling for cards..." } // Try ISO 14443-A (106 kbps) first — covers Classic, Ultralight, DESFire var target = pn533.inListPassiveTarget(baudRate = PN533.BAUD_RATE_106_ISO14443A) @@ -122,20 +125,21 @@ abstract class PN53xReaderBackend( try { val rawCard = readTarget(pn533, target, onProgress) onCardRead(rawCard) - println("[$name] Card read successfully") + log.i { "Card read successfully" } } catch (e: Exception) { - println("[$name] Read error: ${e.message}") + log.e(e) { "Read error" } onError(e) } // Release target try { pn533.inRelease(target.tg) - } catch (_: PN533Exception) { + } catch (e: PN533Exception) { + log.d(e) { "inRelease failed (expected)" } } // Wait for card removal by polling until no target detected - println("[$name] Waiting for card removal...") + log.i { "Waiting for card removal..." } waitForRemoval(pn533) } } @@ -157,7 +161,7 @@ abstract class PN53xReaderBackend( ): RawCard<*> { val info = PN533CardInfo.fromTypeA(target) val tagId = target.uid - println("[$name] Type A card: type=${info.cardType}, SAK=0x%02X, UID=${tagId.hex()}".format(target.sak)) + log.i { "Type A card: type=${info.cardType}, SAK=0x%02X, UID=${tagId.hex()}".format(target.sak) } return when (info.cardType) { CardType.MifareDesfire, CardType.ISO7816 -> { @@ -193,7 +197,7 @@ abstract class PN53xReaderBackend( onProgress: (suspend (current: Int, total: Int) -> Unit)?, ): RawCard<*> { val tagId = target.idm - println("[$name] FeliCa card: IDm=${tagId.hex()}") + log.i { "FeliCa card: IDm=${tagId.hex()}" } val adapter = PN533FeliCaTagAdapter(pn533, target.idm) return FeliCaReader.readTag(tagId, adapter, onProgress = onProgress) } @@ -208,7 +212,8 @@ abstract class PN53xReaderBackend( baudRate = PN533.BAUD_RATE_212_FELICA, initiatorData = SENSF_REQ, ) - } catch (_: PN533Exception) { + } catch (e: PN533Exception) { + log.d(e) { "Poll during removal check failed" } null } if (target == null) { @@ -217,7 +222,8 @@ abstract class PN53xReaderBackend( // Card still present, release and keep waiting try { pn533.inRelease(target.tg) - } catch (_: PN533Exception) { + } catch (e: PN533Exception) { + log.d(e) { "inRelease during removal wait failed" } } } } diff --git a/app/desktop/src/jvmMain/kotlin/com/codebutler/farebot/desktop/PcscReaderBackend.kt b/app/desktop/src/jvmMain/kotlin/com/codebutler/farebot/desktop/PcscReaderBackend.kt index 37240c486..1854eafdd 100644 --- a/app/desktop/src/jvmMain/kotlin/com/codebutler/farebot/desktop/PcscReaderBackend.kt +++ b/app/desktop/src/jvmMain/kotlin/com/codebutler/farebot/desktop/PcscReaderBackend.kt @@ -22,6 +22,7 @@ package com.codebutler.farebot.desktop +import co.touchlab.kermit.Logger import com.codebutler.farebot.card.CardType import com.codebutler.farebot.card.RawCard import com.codebutler.farebot.card.cepas.CEPASCardReader @@ -41,6 +42,8 @@ import javax.smartcardio.CardException import javax.smartcardio.CommandAPDU import javax.smartcardio.TerminalFactory +private val log = Logger.withTag("PcscReaderBackend") + /** * PC/SC reader backend using javax.smartcardio. * @@ -69,10 +72,10 @@ class PcscReaderBackend : NfcReaderBackend { } val terminal = terminals.first() - println("[PC/SC] Using reader: ${terminal.name}") + log.i { "Using reader: ${terminal.name}" } while (true) { - println("[PC/SC] Waiting for card...") + log.i { "Waiting for card..." } terminal.waitForCardPresent(0) try { @@ -80,7 +83,7 @@ class PcscReaderBackend : NfcReaderBackend { try { val atr = card.atr.bytes val info = PCSCCardInfo.fromATR(atr) - println("[PC/SC] Card detected: type=${info.cardType}, ATR=${atr.hex()}") + log.i { "Card detected: type=${info.cardType}, ATR=${atr.hex()}" } val channel = card.basicChannel @@ -93,24 +96,25 @@ class PcscReaderBackend : NfcReaderBackend { } else { byteArrayOf() } - println("[PC/SC] Tag ID: ${tagId.hex()}") + log.i { "Tag ID: ${tagId.hex()}" } onCardDetected(ScannedTag(id = tagId, techList = listOf(info.cardType.name))) val rawCard = readCard(info, channel, tagId, onProgress) onCardRead(rawCard) - println("[PC/SC] Card read successfully") + log.i { "Card read successfully" } } finally { try { card.disconnect(false) - } catch (_: Exception) { + } catch (e: Exception) { + log.d(e) { "Card disconnect failed" } } } } catch (e: Exception) { - println("[PC/SC] Read error: ${e.message}") + log.e(e) { "Read error" } onError(e) } - println("[PC/SC] Waiting for card removal...") + log.i { "Waiting for card removal..." } terminal.waitForCardAbsent(0) } } diff --git a/app/desktop/src/jvmMain/kotlin/com/codebutler/farebot/desktop/RCS956ReaderBackend.kt b/app/desktop/src/jvmMain/kotlin/com/codebutler/farebot/desktop/RCS956ReaderBackend.kt index 7c28e91ba..e34ff01de 100644 --- a/app/desktop/src/jvmMain/kotlin/com/codebutler/farebot/desktop/RCS956ReaderBackend.kt +++ b/app/desktop/src/jvmMain/kotlin/com/codebutler/farebot/desktop/RCS956ReaderBackend.kt @@ -62,7 +62,7 @@ class RCS956ReaderBackend( pn533.resetMode() val fw = pn533.getFirmwareVersion() - println("[$name] Firmware: $fw (RC-S956)") + log.i { "Firmware: $fw (RC-S956)" } // mute() = resetMode + super().mute() pn533.resetMode() diff --git a/app/src/commonMain/kotlin/com/codebutler/farebot/shared/nfc/ISO7816Dispatcher.kt b/app/src/commonMain/kotlin/com/codebutler/farebot/shared/nfc/ISO7816Dispatcher.kt index 79f5aaee9..25b862523 100644 --- a/app/src/commonMain/kotlin/com/codebutler/farebot/shared/nfc/ISO7816Dispatcher.kt +++ b/app/src/commonMain/kotlin/com/codebutler/farebot/shared/nfc/ISO7816Dispatcher.kt @@ -22,6 +22,7 @@ package com.codebutler.farebot.shared.nfc +import co.touchlab.kermit.Logger import com.codebutler.farebot.card.RawCard import com.codebutler.farebot.card.china.ChinaRegistry import com.codebutler.farebot.card.desfire.DesfireCardReader @@ -36,6 +37,8 @@ import com.codebutler.farebot.card.nfc.CardTransceiver * then falls back to the DESFire protocol if no known application is found. */ object ISO7816Dispatcher { + private val log = Logger.withTag("ISO7816Dispatcher") + suspend fun readCard( tagId: ByteArray, transceiver: CardTransceiver, @@ -59,7 +62,7 @@ object ISO7816Dispatcher { return try { ISO7816CardReader.readCard(tagId, transceiver, appConfigs, onProgress) } catch (e: Exception) { - println("[ISO7816Dispatcher] ISO7816 read attempt failed: $e") + log.w(e) { "ISO7816 read attempt failed" } null } } diff --git a/app/src/commonMain/kotlin/com/codebutler/farebot/shared/serialize/MetrodroidJsonParser.kt b/app/src/commonMain/kotlin/com/codebutler/farebot/shared/serialize/MetrodroidJsonParser.kt index f210f2c7d..40b3618f3 100644 --- a/app/src/commonMain/kotlin/com/codebutler/farebot/shared/serialize/MetrodroidJsonParser.kt +++ b/app/src/commonMain/kotlin/com/codebutler/farebot/shared/serialize/MetrodroidJsonParser.kt @@ -22,6 +22,7 @@ package com.codebutler.farebot.shared.serialize +import co.touchlab.kermit.Logger import com.codebutler.farebot.base.util.ByteUtils import com.codebutler.farebot.card.RawCard import com.codebutler.farebot.card.classic.raw.RawClassicBlock @@ -60,6 +61,8 @@ import kotlin.time.Instant * Metrodroid JSON tree, similar to how FlipperNfcParser handles Flipper NFC dumps. */ object MetrodroidJsonParser { + private val log = Logger.withTag("MetrodroidJsonParser") + fun parse(obj: JsonObject): RawCard<*>? { val tagId = parseTagId(obj) val scannedAt = parseScannedAt(obj) @@ -398,7 +401,7 @@ object MetrodroidJsonParser { return try { ByteUtils.hexStringToByteArray(hex) } catch (e: Exception) { - println("[MetrodroidJsonParser] Failed to parse hex string: $e") + log.w(e) { "Failed to parse hex string" } ByteArray(0) } } diff --git a/app/src/commonMain/kotlin/com/codebutler/farebot/shared/viewmodel/CardViewModel.kt b/app/src/commonMain/kotlin/com/codebutler/farebot/shared/viewmodel/CardViewModel.kt index 197c4bde4..b8ceab541 100644 --- a/app/src/commonMain/kotlin/com/codebutler/farebot/shared/viewmodel/CardViewModel.kt +++ b/app/src/commonMain/kotlin/com/codebutler/farebot/shared/viewmodel/CardViewModel.kt @@ -2,6 +2,7 @@ package com.codebutler.farebot.shared.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import co.touchlab.kermit.Logger import com.codebutler.farebot.base.ui.HeaderListItem import com.codebutler.farebot.base.util.DateFormatStyle import com.codebutler.farebot.base.util.formatDate @@ -39,6 +40,8 @@ class CardViewModel( private val cardSerializer: CardSerializer, private val cardPersister: CardPersister, ) : ViewModel() { + private val log = Logger.withTag("CardViewModel") + private val _uiState = MutableStateFlow(CardUiState()) val uiState: StateFlow = _uiState.asStateFlow() @@ -154,8 +157,7 @@ class CardViewModel( ) } } catch (ex: Exception) { - println("[FareBot] Card load error: ${ex::class.simpleName}: ${ex.message}") - ex.printStackTrace() + log.e(ex) { "Card load error: ${ex::class.simpleName}: ${ex.message}" } _uiState.value = CardUiState( isLoading = false, diff --git a/app/src/commonMain/kotlin/com/codebutler/farebot/shared/viewmodel/FlipperViewModel.kt b/app/src/commonMain/kotlin/com/codebutler/farebot/shared/viewmodel/FlipperViewModel.kt index 2d48a955d..a357db864 100644 --- a/app/src/commonMain/kotlin/com/codebutler/farebot/shared/viewmodel/FlipperViewModel.kt +++ b/app/src/commonMain/kotlin/com/codebutler/farebot/shared/viewmodel/FlipperViewModel.kt @@ -2,6 +2,7 @@ package com.codebutler.farebot.shared.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import co.touchlab.kermit.Logger import com.codebutler.farebot.base.util.hex import com.codebutler.farebot.card.serialize.CardSerializer import com.codebutler.farebot.flipper.FlipperKeyDictParser @@ -33,6 +34,8 @@ class FlipperViewModel( private val cardSerializer: CardSerializer, private val transportFactory: FlipperTransportFactory, ) : ViewModel() { + private val log = Logger.withTag("FlipperViewModel") + private val _uiState = MutableStateFlow(FlipperUiState()) val uiState: StateFlow = _uiState.asStateFlow() @@ -129,7 +132,7 @@ class FlipperViewModel( try { transport?.close() } catch (e: Exception) { - println("[FlipperViewModel] Error closing transport: ${e.message}") + log.w(e) { "Error closing transport" } } rpcClient = null transport = null @@ -240,7 +243,7 @@ class FlipperViewModel( } } } catch (e: Exception) { - println("[FlipperViewModel] Failed to import $path: ${e.message}") + log.w(e) { "Failed to import $path" } } } diff --git a/app/src/commonMain/kotlin/com/codebutler/farebot/shared/viewmodel/HistoryViewModel.kt b/app/src/commonMain/kotlin/com/codebutler/farebot/shared/viewmodel/HistoryViewModel.kt index 16bfb54fb..4f7fd7eff 100644 --- a/app/src/commonMain/kotlin/com/codebutler/farebot/shared/viewmodel/HistoryViewModel.kt +++ b/app/src/commonMain/kotlin/com/codebutler/farebot/shared/viewmodel/HistoryViewModel.kt @@ -2,6 +2,7 @@ package com.codebutler.farebot.shared.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import co.touchlab.kermit.Logger import com.codebutler.farebot.base.util.formatHumanDate import com.codebutler.farebot.base.util.formatTimeShort import com.codebutler.farebot.base.util.hex @@ -42,6 +43,8 @@ class HistoryViewModel( private val versionCode: Int = 1, private val versionName: String = "1.0.0", ) : ViewModel() { + private val log = Logger.withTag("HistoryViewModel") + private val _uiState = MutableStateFlow(HistoryUiState()) val uiState: StateFlow = _uiState.asStateFlow() @@ -171,8 +174,7 @@ class HistoryViewModel( groupedItems = groupedItems, ) } catch (e: Throwable) { - println("[HistoryViewModel] Failed to load cards: $e") - e.printStackTrace() + log.e(e) { "Failed to load cards" } _uiState.value = _uiState.value.copy(isLoading = false, allItems = emptyList(), groupedItems = emptyList()) } diff --git a/app/src/commonMain/kotlin/com/codebutler/farebot/shared/viewmodel/HomeViewModel.kt b/app/src/commonMain/kotlin/com/codebutler/farebot/shared/viewmodel/HomeViewModel.kt index 6c4fb4313..34d0e31f1 100644 --- a/app/src/commonMain/kotlin/com/codebutler/farebot/shared/viewmodel/HomeViewModel.kt +++ b/app/src/commonMain/kotlin/com/codebutler/farebot/shared/viewmodel/HomeViewModel.kt @@ -2,6 +2,7 @@ package com.codebutler.farebot.shared.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import co.touchlab.kermit.Logger import com.codebutler.farebot.base.util.hex import com.codebutler.farebot.card.CardType import com.codebutler.farebot.card.RawCard @@ -41,6 +42,8 @@ class HomeViewModel( private val navDataHolder: NavDataHolder, private val analytics: Analytics, ) : ViewModel() { + private val log = Logger.withTag("HomeViewModel") + private val _uiState = MutableStateFlow( HomeUiState( @@ -98,8 +101,7 @@ class HomeViewModel( viewModelScope.launch { cardScanner.scanErrors.collect { error -> _uiState.value = _uiState.value.copy(isReadingCard = false, readingProgress = null) - println("[FareBot] Scan error: ${error::class.simpleName}: ${error.message}") - error.printStackTrace() + log.e(error) { "Scan error: ${error::class.simpleName}: ${error.message}" } val scanError = categorizeError(error) analytics.logEvent( "scan_card_error", @@ -167,8 +169,7 @@ class HomeViewModel( val key = navDataHolder.put(rawCard) _navigateToCard.emit(key) } catch (e: Exception) { - println("[FareBot] Process card error: ${e::class.simpleName}: ${e.message}") - e.printStackTrace() + log.e(e) { "Process card error: ${e::class.simpleName}: ${e.message}" } _errorMessage.value = ScanError( title = getString(Res.string.error), diff --git a/app/src/commonMain/kotlin/com/codebutler/farebot/shared/viewmodel/KeysViewModel.kt b/app/src/commonMain/kotlin/com/codebutler/farebot/shared/viewmodel/KeysViewModel.kt index 5b78e9f6b..f771b0340 100644 --- a/app/src/commonMain/kotlin/com/codebutler/farebot/shared/viewmodel/KeysViewModel.kt +++ b/app/src/commonMain/kotlin/com/codebutler/farebot/shared/viewmodel/KeysViewModel.kt @@ -2,6 +2,7 @@ package com.codebutler.farebot.shared.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import co.touchlab.kermit.Logger import com.codebutler.farebot.persist.CardKeysPersister import com.codebutler.farebot.shared.ui.screen.KeyItem import com.codebutler.farebot.shared.ui.screen.KeysUiState @@ -11,6 +12,8 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch +private val log = Logger.withTag("KeysViewModel") + @Inject class KeysViewModel( private val keysPersister: CardKeysPersister, @@ -37,8 +40,7 @@ class KeysViewModel( } _uiState.value = KeysUiState(keys = keys, isLoading = false) } catch (e: Throwable) { - println("[KeysViewModel] Failed to load keys: $e") - e.printStackTrace() + log.e(e) { "Failed to load keys" } _uiState.value = KeysUiState(isLoading = false) } } diff --git a/app/web/src/wasmJsMain/kotlin/com/codebutler/farebot/web/LocalStorageCardKeysPersister.kt b/app/web/src/wasmJsMain/kotlin/com/codebutler/farebot/web/LocalStorageCardKeysPersister.kt index 50275b6d2..1abd20eeb 100644 --- a/app/web/src/wasmJsMain/kotlin/com/codebutler/farebot/web/LocalStorageCardKeysPersister.kt +++ b/app/web/src/wasmJsMain/kotlin/com/codebutler/farebot/web/LocalStorageCardKeysPersister.kt @@ -2,6 +2,7 @@ package com.codebutler.farebot.web +import co.touchlab.kermit.Logger import com.codebutler.farebot.card.CardType import com.codebutler.farebot.persist.CardKeysPersister import com.codebutler.farebot.persist.db.model.SavedKey @@ -47,6 +48,8 @@ private fun lsSetItem( js("localStorage.setItem(key, value)") } +private val log = Logger.withTag("LocalStorage") + class LocalStorageCardKeysPersister( private val json: Json, ) : CardKeysPersister { @@ -89,7 +92,7 @@ class LocalStorageCardKeysPersister( return try { json.decodeFromString>(raw).map { it.hexToByteArray() } } catch (e: Exception) { - println("[LocalStorage] Failed to load global keys: $e") + log.w(e) { "Failed to load global keys" } emptyList() } } @@ -114,7 +117,7 @@ class LocalStorageCardKeysPersister( return try { json.decodeFromString>(raw).map { it.toSavedKey() } } catch (e: Exception) { - println("[LocalStorage] Failed to load saved keys: $e") + log.w(e) { "Failed to load saved keys" } emptyList() } } diff --git a/app/web/src/wasmJsMain/kotlin/com/codebutler/farebot/web/LocalStorageCardPersister.kt b/app/web/src/wasmJsMain/kotlin/com/codebutler/farebot/web/LocalStorageCardPersister.kt index 75001ca2a..57721bb58 100644 --- a/app/web/src/wasmJsMain/kotlin/com/codebutler/farebot/web/LocalStorageCardPersister.kt +++ b/app/web/src/wasmJsMain/kotlin/com/codebutler/farebot/web/LocalStorageCardPersister.kt @@ -2,6 +2,7 @@ package com.codebutler.farebot.web +import co.touchlab.kermit.Logger import com.codebutler.farebot.card.CardType import com.codebutler.farebot.persist.CardPersister import com.codebutler.farebot.persist.db.model.SavedCard @@ -47,6 +48,8 @@ private fun lsSetItem( js("localStorage.setItem(key, value)") } +private val log = Logger.withTag("LocalStorage") + class LocalStorageCardPersister( private val json: Json, ) : CardPersister { @@ -87,7 +90,7 @@ class LocalStorageCardPersister( return try { json.decodeFromString>(raw).map { it.toSavedCard() } } catch (e: Exception) { - println("[LocalStorage] Failed to load saved cards: $e") + log.w(e) { "Failed to load saved cards" } emptyList() } } diff --git a/app/web/src/wasmJsMain/kotlin/com/codebutler/farebot/web/WebCardScanner.kt b/app/web/src/wasmJsMain/kotlin/com/codebutler/farebot/web/WebCardScanner.kt index 960cabd41..2b42f2593 100644 --- a/app/web/src/wasmJsMain/kotlin/com/codebutler/farebot/web/WebCardScanner.kt +++ b/app/web/src/wasmJsMain/kotlin/com/codebutler/farebot/web/WebCardScanner.kt @@ -1,5 +1,6 @@ package com.codebutler.farebot.web +import co.touchlab.kermit.Logger import com.codebutler.farebot.card.CardType import com.codebutler.farebot.card.RawCard import com.codebutler.farebot.card.cepas.CEPASCardReader @@ -46,6 +47,8 @@ import kotlinx.coroutines.launch * interfaces are suspend-compatible, allowing WebUSB's async API to be * used seamlessly through Kotlin coroutines. */ +private val log = Logger.withTag("WebCardScanner") + class WebCardScanner : CardScanner { override val requiresActiveScan: Boolean = true @@ -121,7 +124,7 @@ class WebCardScanner : CardScanner { // Initialize PN533 pn533.sendAck() val fw = pn533.getFirmwareVersion() - println("[WebUSB] PN53x firmware: $fw") + log.i { "PN53x firmware: $fw" } pn533.samConfiguration() // Use finite ATR retries on WebUSB. WebUSB's transferIn cannot be // cancelled, so InListPassiveTarget must self-resolve within its own @@ -164,21 +167,22 @@ class WebCardScanner : CardScanner { val rawCard = readTarget(pn533, target) _readingProgress.value = null _scannedCards.tryEmit(rawCard) - println("[WebUSB] Card read successfully") + log.i { "Card read successfully" } } catch (e: Exception) { _readingProgress.value = null - println("[WebUSB] Read error: ${e.message}") + log.e(e) { "Read error" } _scanErrors.tryEmit(e) } // Release target try { pn533.inRelease(target.tg) - } catch (_: PN533Exception) { + } catch (e: PN533Exception) { + log.d(e) { "inRelease failed (expected)" } } // Wait for card removal - println("[WebUSB] Waiting for card removal...") + log.i { "Waiting for card removal..." } waitForRemoval(pn533) } } @@ -202,11 +206,11 @@ class WebCardScanner : CardScanner { ): RawCard<*> { val info = PN533CardInfo.fromTypeA(target) val tagId = target.uid - println( - "[WebUSB] Type A card: type=${info.cardType}, SAK=0x${(target.sak.toInt() and 0xFF).toString( + log.i { + "Type A card: type=${info.cardType}, SAK=0x${(target.sak.toInt() and 0xFF).toString( 16, - ).padStart(2, '0')}, UID=${tagId.hex()}", - ) + ).padStart(2, '0')}, UID=${tagId.hex()}" + } return when (info.cardType) { CardType.MifareDesfire, CardType.ISO7816 -> { @@ -241,7 +245,7 @@ class WebCardScanner : CardScanner { target: PN533.TargetInfo.FeliCa, ): RawCard<*> { val tagId = target.idm - println("[WebUSB] FeliCa card: IDm=${tagId.hex()}") + log.i { "FeliCa card: IDm=${tagId.hex()}" } val adapter = PN533FeliCaTagAdapter(pn533, tagId) return FeliCaReader.readTag(tagId, adapter, onProgress = onProgress) } @@ -256,13 +260,15 @@ class WebCardScanner : CardScanner { baudRate = PN533.BAUD_RATE_212_FELICA, initiatorData = SENSF_REQ, ) - } catch (_: PN533Exception) { + } catch (e: PN533Exception) { + log.d(e) { "Poll during removal check failed" } null } if (target == null) break try { pn533.inRelease(target.tg) - } catch (_: PN533Exception) { + } catch (e: PN533Exception) { + log.d(e) { "inRelease during removal wait failed" } } } } diff --git a/base/build.gradle.kts b/base/build.gradle.kts index d45a3e915..d99dbe954 100644 --- a/base/build.gradle.kts +++ b/base/build.gradle.kts @@ -36,6 +36,7 @@ kotlin { implementation(libs.kotlinx.serialization.json) implementation(libs.kotlinx.serialization.protobuf) implementation(libs.kotlincrypto.hash.md) + api(libs.kermit) api(libs.kotlinx.datetime) api(libs.sqldelight.runtime) } diff --git a/base/src/androidMain/kotlin/com/codebutler/farebot/base/mdst/ResourceAccessor.kt b/base/src/androidMain/kotlin/com/codebutler/farebot/base/mdst/ResourceAccessor.kt index 4d76c4723..4966b3fe3 100644 --- a/base/src/androidMain/kotlin/com/codebutler/farebot/base/mdst/ResourceAccessor.kt +++ b/base/src/androidMain/kotlin/com/codebutler/farebot/base/mdst/ResourceAccessor.kt @@ -19,6 +19,7 @@ package com.codebutler.farebot.base.mdst +import co.touchlab.kermit.Logger import farebot.base.generated.resources.Res import kotlinx.coroutines.runBlocking import org.jetbrains.compose.resources.ExperimentalResourceApi @@ -30,7 +31,8 @@ actual object ResourceAccessor { runBlocking { Res.readBytes("files/$dbName.mdst") } - } catch (_: Exception) { + } catch (e: Exception) { + Logger.withTag("ResourceAccessor").w(e) { "Failed to load resource: $dbName" } null } } diff --git a/base/src/commonMain/kotlin/com/codebutler/farebot/base/mdst/MdstStationTableReader.kt b/base/src/commonMain/kotlin/com/codebutler/farebot/base/mdst/MdstStationTableReader.kt index e987ab1ad..cb72ec281 100644 --- a/base/src/commonMain/kotlin/com/codebutler/farebot/base/mdst/MdstStationTableReader.kt +++ b/base/src/commonMain/kotlin/com/codebutler/farebot/base/mdst/MdstStationTableReader.kt @@ -23,10 +23,13 @@ package com.codebutler.farebot.base.mdst +import co.touchlab.kermit.Logger import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.decodeFromByteArray import kotlinx.serialization.protobuf.ProtoBuf +private val log = Logger.withTag("MdST") + /** * Metrodroid Station Table (MdST) file reader. * @@ -88,7 +91,7 @@ class MdstStationTableReader private constructor( val (bytes, _) = readDelimitedBytes(data, absoluteOffset) ProtoBuf.decodeFromByteArray(bytes) } catch (e: Exception) { - println("[MdST] Failed to decode station $id: $e") + log.w(e) { "Failed to decode station $id" } null } } @@ -130,7 +133,7 @@ class MdstStationTableReader private constructor( readers[dbName] = reader reader } catch (e: Exception) { - println("[MdST] Failed to parse $dbName: $e") + log.w(e) { "Failed to parse $dbName" } null } } diff --git a/base/src/iosMain/kotlin/com/codebutler/farebot/base/mdst/ResourceAccessor.kt b/base/src/iosMain/kotlin/com/codebutler/farebot/base/mdst/ResourceAccessor.kt index 53a7e32cb..e38e1ac5c 100644 --- a/base/src/iosMain/kotlin/com/codebutler/farebot/base/mdst/ResourceAccessor.kt +++ b/base/src/iosMain/kotlin/com/codebutler/farebot/base/mdst/ResourceAccessor.kt @@ -19,16 +19,20 @@ package com.codebutler.farebot.base.mdst +import co.touchlab.kermit.Logger import farebot.base.generated.resources.Res import kotlinx.coroutines.runBlocking +private val log = Logger.withTag("ResourceAccessor") + actual object ResourceAccessor { actual fun openMdstFile(dbName: String): ByteArray? = try { runBlocking { Res.readBytes("files/$dbName.mdst") } - } catch (_: Exception) { + } catch (e: Exception) { + log.w(e) { "Failed to load resource: $dbName" } null } } diff --git a/base/src/jvmMain/kotlin/com/codebutler/farebot/base/mdst/ResourceAccessor.kt b/base/src/jvmMain/kotlin/com/codebutler/farebot/base/mdst/ResourceAccessor.kt index 59c33c23b..f95bcd4e7 100644 --- a/base/src/jvmMain/kotlin/com/codebutler/farebot/base/mdst/ResourceAccessor.kt +++ b/base/src/jvmMain/kotlin/com/codebutler/farebot/base/mdst/ResourceAccessor.kt @@ -1,5 +1,9 @@ package com.codebutler.farebot.base.mdst +import co.touchlab.kermit.Logger + +private val log = Logger.withTag("ResourceAccessor") + actual object ResourceAccessor { actual fun openMdstFile(dbName: String): ByteArray? = try { @@ -9,7 +13,8 @@ actual object ResourceAccessor { .contextClassLoader ?.getResourceAsStream(path) ?.use { it.readBytes() } - } catch (_: Exception) { + } catch (e: Exception) { + log.w(e) { "Failed to load resource: $dbName" } null } } diff --git a/base/src/wasmJsMain/kotlin/com/codebutler/farebot/base/mdst/ResourceAccessor.kt b/base/src/wasmJsMain/kotlin/com/codebutler/farebot/base/mdst/ResourceAccessor.kt index e4cc0de37..22a9a3588 100644 --- a/base/src/wasmJsMain/kotlin/com/codebutler/farebot/base/mdst/ResourceAccessor.kt +++ b/base/src/wasmJsMain/kotlin/com/codebutler/farebot/base/mdst/ResourceAccessor.kt @@ -2,6 +2,7 @@ package com.codebutler.farebot.base.mdst +import co.touchlab.kermit.Logger import kotlin.io.encoding.Base64 import kotlin.io.encoding.ExperimentalEncodingApi import kotlin.js.ExperimentalWasmJsInterop @@ -52,7 +53,8 @@ actual object ResourceAccessor { if (base64.isEmpty()) return null return try { Base64.decode(base64) - } catch (_: Exception) { + } catch (e: Exception) { + Logger.withTag("ResourceAccessor").w(e) { "Failed to load resource: $dbName" } null } } diff --git a/card/cepas/src/commonMain/kotlin/com/codebutler/farebot/card/cepas/CEPASProtocol.kt b/card/cepas/src/commonMain/kotlin/com/codebutler/farebot/card/cepas/CEPASProtocol.kt index c95abe9ae..7bc65f701 100644 --- a/card/cepas/src/commonMain/kotlin/com/codebutler/farebot/card/cepas/CEPASProtocol.kt +++ b/card/cepas/src/commonMain/kotlin/com/codebutler/farebot/card/cepas/CEPASProtocol.kt @@ -24,9 +24,12 @@ package com.codebutler.farebot.card.cepas +import co.touchlab.kermit.Logger import com.codebutler.farebot.card.iso7816.ISO7816Exception import com.codebutler.farebot.card.iso7816.ISO7816Protocol +private val log = Logger.withTag("CEPASProtocol") + internal class CEPASProtocol( private val protocol: ISO7816Protocol, ) { @@ -42,7 +45,7 @@ internal class CEPASProtocol( ) if (result.isEmpty()) null else result } catch (ex: ISO7816Exception) { - println("[CEPAS] Failed to read purse $purseId: $ex") + log.w(ex) { "Failed to read purse $purseId" } null } @@ -59,7 +62,7 @@ internal class CEPASProtocol( byteArrayOf(0.toByte()), ) } catch (ex: ISO7816Exception) { - println("[CEPAS] Failed to read purse history: $ex") + log.w(ex) { "Failed to read purse history" } return null } @@ -75,7 +78,7 @@ internal class CEPASProtocol( ) historyBuff = historyBuff + historyBuff2 } catch (ex: ISO7816Exception) { - println("[CEPAS] Failed to read 2nd purse history: $ex") + log.w(ex) { "Failed to read 2nd purse history" } } return historyBuff diff --git a/card/felica/src/androidMain/kotlin/com/codebutler/farebot/card/felica/AndroidFeliCaTagAdapter.kt b/card/felica/src/androidMain/kotlin/com/codebutler/farebot/card/felica/AndroidFeliCaTagAdapter.kt index 7fbe13344..4b19d377a 100644 --- a/card/felica/src/androidMain/kotlin/com/codebutler/farebot/card/felica/AndroidFeliCaTagAdapter.kt +++ b/card/felica/src/androidMain/kotlin/com/codebutler/farebot/card/felica/AndroidFeliCaTagAdapter.kt @@ -26,6 +26,7 @@ package com.codebutler.farebot.card.felica import android.nfc.Tag import android.nfc.TagLostException import android.nfc.tech.NfcF +import co.touchlab.kermit.Logger import java.io.IOException /** @@ -34,6 +35,8 @@ import java.io.IOException * Builds raw NFC-F command packets inline and parses responses directly, * replacing the old FeliCaTag/FeliCaLibAndroid wrapper classes. */ +private val log = Logger.withTag("AndroidFeliCaTagAdapter") + class AndroidFeliCaTagAdapter( private val tag: Tag, ) : FeliCaTagAdapter { @@ -177,8 +180,8 @@ class AndroidFeliCaTagAdapter( fun close() { try { nfcF?.close() - } catch (_: IOException) { - // ignore + } catch (e: IOException) { + log.d(e) { "Failed to close NfcF" } } nfcF = null } @@ -187,7 +190,8 @@ class AndroidFeliCaTagAdapter( try { val f = ensureConnected() return f.transceive(data) - } catch (_: TagLostException) { + } catch (e: TagLostException) { + log.d(e) { "Tag lost during transceive" } return null } catch (e: IOException) { throw Exception("NFC transceive failed", e) diff --git a/card/felica/src/commonMain/kotlin/com/codebutler/farebot/card/felica/PN533FeliCaTagAdapter.kt b/card/felica/src/commonMain/kotlin/com/codebutler/farebot/card/felica/PN533FeliCaTagAdapter.kt index 15cca377f..d43af5cbc 100644 --- a/card/felica/src/commonMain/kotlin/com/codebutler/farebot/card/felica/PN533FeliCaTagAdapter.kt +++ b/card/felica/src/commonMain/kotlin/com/codebutler/farebot/card/felica/PN533FeliCaTagAdapter.kt @@ -22,8 +22,11 @@ package com.codebutler.farebot.card.felica +import co.touchlab.kermit.Logger import com.codebutler.farebot.card.nfc.pn533.PN533 +private val log = Logger.withTag("PN533FeliCaTagAdapter") + /** * PN533 implementation of [FeliCaTagAdapter] for FeliCa (NFC-F) cards. * @@ -147,7 +150,7 @@ class PN533FeliCaTagAdapter( try { pn533.inCommunicateThru(felicaFrame) } catch (e: Exception) { - println("[PN533FeliCa] Transceive failed: $e") + log.w(e) { "Transceive failed" } null } } diff --git a/card/felica/src/jvmMain/kotlin/com/codebutler/farebot/card/felica/PCSCFeliCaTagAdapter.kt b/card/felica/src/jvmMain/kotlin/com/codebutler/farebot/card/felica/PCSCFeliCaTagAdapter.kt index 763c3d99a..27226985f 100644 --- a/card/felica/src/jvmMain/kotlin/com/codebutler/farebot/card/felica/PCSCFeliCaTagAdapter.kt +++ b/card/felica/src/jvmMain/kotlin/com/codebutler/farebot/card/felica/PCSCFeliCaTagAdapter.kt @@ -22,10 +22,13 @@ package com.codebutler.farebot.card.felica +import co.touchlab.kermit.Logger import javax.smartcardio.CardChannel import javax.smartcardio.CommandAPDU import javax.smartcardio.ResponseAPDU +private val log = Logger.withTag("PCSCFeliCaTagAdapter") + /** * PC/SC implementation of [FeliCaTagAdapter] for FeliCa (NFC-F) cards. * @@ -172,7 +175,7 @@ class PCSCFeliCaTagAdapter( null } } catch (e: Exception) { - println("[PCSCFeliCa] Transceive failed: $e") + log.w(e) { "Transceive failed" } null } } diff --git a/card/iso7816/src/commonMain/kotlin/com/codebutler/farebot/card/iso7816/ISO7816CardReader.kt b/card/iso7816/src/commonMain/kotlin/com/codebutler/farebot/card/iso7816/ISO7816CardReader.kt index bc3d3ca39..0b0196185 100644 --- a/card/iso7816/src/commonMain/kotlin/com/codebutler/farebot/card/iso7816/ISO7816CardReader.kt +++ b/card/iso7816/src/commonMain/kotlin/com/codebutler/farebot/card/iso7816/ISO7816CardReader.kt @@ -23,10 +23,13 @@ package com.codebutler.farebot.card.iso7816 +import co.touchlab.kermit.Logger import com.codebutler.farebot.card.iso7816.raw.RawISO7816Card import com.codebutler.farebot.card.nfc.CardTransceiver import kotlin.time.Clock +private val log = Logger.withTag("ISO7816CardReader") + /** * Reads ISO 7816 cards by trying to SELECT BY NAME known application identifiers, * then reading their files, records, and balance data. @@ -175,7 +178,7 @@ object ISO7816CardReader { } catch (e: ISO7816Exception) { break } catch (e: Exception) { - println("[ISO7816] Record read failed at SFI $sfi, record $recordNum: $e") + log.w(e) { "Record read failed at SFI $sfi, record $recordNum" } break } } @@ -184,7 +187,7 @@ object ISO7816CardReader { try { binaryData = protocol.readBinary(sfi) } catch (e: Exception) { - println("[ISO7816] Binary read failed for SFI $sfi: $e") + log.w(e) { "Binary read failed for SFI $sfi" } } return if (records.isNotEmpty() || binaryData != null) { @@ -225,7 +228,7 @@ object ISO7816CardReader { } catch (e: ISOEOFException) { break } catch (e: Exception) { - println("[ISO7816] File record read failed: $e") + log.w(e) { "File record read failed" } break } } @@ -235,7 +238,7 @@ object ISO7816CardReader { try { protocol.readBinary() } catch (e: Exception) { - println("[ISO7816] File binary read failed: $e") + log.w(e) { "File binary read failed" } null } @@ -243,7 +246,7 @@ object ISO7816CardReader { return ISO7816File(binaryData = binaryData, records = records, fci = fci) } catch (e: Exception) { - println("[ISO7816] File read failed for app: $e") + log.w(e) { "File read failed for app" } return null } } @@ -266,7 +269,7 @@ object ISO7816CardReader { ) balances[i] = balance } catch (e: Exception) { - println("[ISO7816] Balance read failed: $e") + log.w(e) { "Balance read failed" } } } return balances @@ -286,7 +289,7 @@ object ISO7816CardReader { 4, // BALANCE_RESP_LEN ) } catch (e: Exception) { - println("[ISO7816] KSX6924 purse info failed: $e") + log.w(e) { "KSX6924 purse info failed" } null } @@ -309,7 +312,7 @@ object ISO7816CardReader { records.add(record) } } catch (e: Exception) { - println("[ISO7816] KSX6924 transaction record read failed: $e") + log.w(e) { "KSX6924 transaction record read failed" } } return records } diff --git a/card/iso7816/src/commonMain/kotlin/com/codebutler/farebot/card/iso7816/ISO7816Protocol.kt b/card/iso7816/src/commonMain/kotlin/com/codebutler/farebot/card/iso7816/ISO7816Protocol.kt index ec98394cf..b19ca25e0 100644 --- a/card/iso7816/src/commonMain/kotlin/com/codebutler/farebot/card/iso7816/ISO7816Protocol.kt +++ b/card/iso7816/src/commonMain/kotlin/com/codebutler/farebot/card/iso7816/ISO7816Protocol.kt @@ -23,8 +23,11 @@ package com.codebutler.farebot.card.iso7816 +import co.touchlab.kermit.Logger import com.codebutler.farebot.card.nfc.CardTransceiver +private val log = Logger.withTag("ISO7816Protocol") + /** * Implements communication with cards that talk over ISO7816-4 APDUs. * @@ -172,7 +175,7 @@ class ISO7816Protocol( } catch (e: ISO7816Exception) { null } catch (e: Exception) { - println("[ISO7816Protocol] Unexpected error in selectByName: $e") + log.w(e) { "Unexpected error in selectByName" } null } diff --git a/card/iso7816/src/commonMain/kotlin/com/codebutler/farebot/card/iso7816/ISO7816TLV.kt b/card/iso7816/src/commonMain/kotlin/com/codebutler/farebot/card/iso7816/ISO7816TLV.kt index 295cbe2cd..84a635e47 100644 --- a/card/iso7816/src/commonMain/kotlin/com/codebutler/farebot/card/iso7816/ISO7816TLV.kt +++ b/card/iso7816/src/commonMain/kotlin/com/codebutler/farebot/card/iso7816/ISO7816TLV.kt @@ -23,6 +23,7 @@ package com.codebutler.farebot.card.iso7816 +import co.touchlab.kermit.Logger import com.codebutler.farebot.base.ui.HeaderListItem import com.codebutler.farebot.base.ui.ListItem import com.codebutler.farebot.base.ui.ListItemInterface @@ -44,7 +45,7 @@ import farebot.card.iso7816.generated.resources.* * Reference: https://en.wikipedia.org/wiki/X.690#BER_encoding */ object ISO7816TLV { - private const val TAG = "ISO7816TLV" + private val log = Logger.withTag("ISO7816TLV") private const val MAX_TLV_FIELD_LENGTH = 0xffff /** @@ -306,7 +307,7 @@ object ISO7816TLV { infoBerTLV(header + data, multihead), ) } catch (e: Exception) { - println("[ISO7816TLV] Failed to build TLV items: $e") + log.w(e) { "Failed to build TLV items" } ListItem(id.toHexDump(), data.toHexDump()) } } else { @@ -320,7 +321,7 @@ object ISO7816TLV { try { ListItemRecursive(FormattedString("TLV"), null, infoBerTLV(buf)) } catch (e: Exception) { - println("[ISO7816TLV] Failed to decode TLV node: $e") + log.w(e) { "Failed to decode TLV node" } null }, ) diff --git a/card/ksx6924/src/commonMain/kotlin/com/codebutler/farebot/card/ksx6924/KSX6924Utils.kt b/card/ksx6924/src/commonMain/kotlin/com/codebutler/farebot/card/ksx6924/KSX6924Utils.kt index 489575945..55436e818 100644 --- a/card/ksx6924/src/commonMain/kotlin/com/codebutler/farebot/card/ksx6924/KSX6924Utils.kt +++ b/card/ksx6924/src/commonMain/kotlin/com/codebutler/farebot/card/ksx6924/KSX6924Utils.kt @@ -22,6 +22,7 @@ */ package com.codebutler.farebot.card.ksx6924 +import co.touchlab.kermit.Logger import com.codebutler.farebot.base.util.NumberUtils import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDateTime @@ -30,6 +31,7 @@ import kotlinx.datetime.toInstant import kotlin.time.Instant object KSX6924Utils { + private val log = Logger.withTag("KSX6924Utils") const val INVALID_DATETIME = 0xffffffffffffffL private const val INVALID_DATE = 0xffffffffL @@ -70,7 +72,7 @@ object KSX6924Utils { second = second, ).toInstant(tz) } catch (e: Exception) { - println("[KSX6924] Failed to parse hex datetime: $e") + log.w(e) { "Failed to parse hex datetime" } null } } @@ -95,7 +97,7 @@ object KSX6924Utils { return try { LocalDate(year = year, month = month, day = day) } catch (e: Exception) { - println("[KSX6924] Failed to parse hex date: $e") + log.w(e) { "Failed to parse hex date" } null } } diff --git a/card/src/commonMain/kotlin/com/codebutler/farebot/card/nfc/pn533/PN533ClassicTechnology.kt b/card/src/commonMain/kotlin/com/codebutler/farebot/card/nfc/pn533/PN533ClassicTechnology.kt index 567cdcf89..b2f8efd02 100644 --- a/card/src/commonMain/kotlin/com/codebutler/farebot/card/nfc/pn533/PN533ClassicTechnology.kt +++ b/card/src/commonMain/kotlin/com/codebutler/farebot/card/nfc/pn533/PN533ClassicTechnology.kt @@ -22,6 +22,7 @@ package com.codebutler.farebot.card.nfc.pn533 +import co.touchlab.kermit.Logger import com.codebutler.farebot.card.nfc.ClassicTechnology /** @@ -34,6 +35,8 @@ import com.codebutler.farebot.card.nfc.ClassicTechnology * The PN533 handles the MIFARE Classic crypto1 cipher internally * after a successful authentication. */ +private val log = Logger.withTag("PN533ClassicTechnology") + class PN533ClassicTechnology( private val pn533: PN533, private val tg: Int, @@ -92,7 +95,8 @@ class PN533ClassicTechnology( val data = byteArrayOf(authCommand, block.toByte()) + key + uidBytes pn533.inDataExchange(tg, data) true - } catch (_: PN533Exception) { + } catch (e: PN533Exception) { + log.d(e) { "Authentication failed for sector $sectorIndex" } false } diff --git a/card/src/jvmMain/kotlin/com/codebutler/farebot/card/nfc/pn533/Usb4JavaPN533Transport.kt b/card/src/jvmMain/kotlin/com/codebutler/farebot/card/nfc/pn533/Usb4JavaPN533Transport.kt index f8dbbf31e..840651438 100644 --- a/card/src/jvmMain/kotlin/com/codebutler/farebot/card/nfc/pn533/Usb4JavaPN533Transport.kt +++ b/card/src/jvmMain/kotlin/com/codebutler/farebot/card/nfc/pn533/Usb4JavaPN533Transport.kt @@ -22,6 +22,7 @@ package com.codebutler.farebot.card.nfc.pn533 +import co.touchlab.kermit.Logger import org.usb4java.DeviceHandle import org.usb4java.LibUsb import java.nio.ByteBuffer @@ -39,6 +40,8 @@ import java.nio.IntBuffer class Usb4JavaPN533Transport( private val handle: DeviceHandle, ) : PN533Transport { + private val log = Logger.withTag("Usb4JavaPN533Transport") + /** * Drain any stale data from the USB read buffer. * Call after opening the device to clear leftovers from previous sessions. @@ -62,12 +65,12 @@ class Usb4JavaPN533Transport( val payload = byteArrayOf(TFI_HOST_TO_PN533, code) + data val frame = buildFrame(payload) - if (DEBUG) println("[PN53x TX] cmd=0x%02X frame=${frame.hex()}".format(code)) + log.d { "TX cmd=0x%02X frame=${frame.hex()}".format(code) } bulkWrite(frame) val staleResponse = readAck() // If readAck got a response frame instead of an ACK, use it directly if (staleResponse != null) { - if (DEBUG) println("[PN53x RX] (stale) ${staleResponse.hex()}") + log.d { "RX (stale) ${staleResponse.hex()}" } return parseFrame(staleResponse) } return readResponse(timeoutMs) @@ -153,7 +156,7 @@ class Usb4JavaPN533Transport( val bytes = ByteArray(count) buf.rewind() buf.get(bytes) - if (DEBUG) println("[PN53x RX] ${bytes.hex()}") + log.d { "RX ${bytes.hex()}" } return parseFrame(bytes) } @@ -230,8 +233,6 @@ class Usb4JavaPN533Transport( 0x00, ) - const val DEBUG = false - private fun ByteArray.hex(): String = joinToString("") { "%02X".format(it) } } } diff --git a/card/ultralight/src/commonMain/kotlin/com/codebutler/farebot/card/ultralight/UltralightCardReader.kt b/card/ultralight/src/commonMain/kotlin/com/codebutler/farebot/card/ultralight/UltralightCardReader.kt index c4179058e..f8c6b902c 100644 --- a/card/ultralight/src/commonMain/kotlin/com/codebutler/farebot/card/ultralight/UltralightCardReader.kt +++ b/card/ultralight/src/commonMain/kotlin/com/codebutler/farebot/card/ultralight/UltralightCardReader.kt @@ -22,10 +22,13 @@ package com.codebutler.farebot.card.ultralight +import co.touchlab.kermit.Logger import com.codebutler.farebot.card.nfc.UltralightTechnology import com.codebutler.farebot.card.ultralight.raw.RawUltralightCard import kotlin.time.Clock +private val log = Logger.withTag("UltralightCardReader") + object UltralightCardReader { @Throws(Exception::class) suspend fun readCard( @@ -35,7 +38,7 @@ object UltralightCardReader { ): RawUltralightCard { // Detect card type using protocol commands (GET_VERSION, AUTH_1) val detectedType = detectCardType(tech) - println("UltralightCardReader: Detected card type: $detectedType") + log.d { "Detected card type: $detectedType" } // Determine page count based on detected type val pageCount = detectedType.pageCount @@ -107,7 +110,7 @@ object UltralightCardReader { val rawType = protocol.getCardType() rawType.parse() } catch (e: Exception) { - println("UltralightCardReader: Card type detection failed, falling back to UNKNOWN: $e") + log.w(e) { "Card type detection failed, falling back to UNKNOWN" } UltralightCard.UltralightType.UNKNOWN } } diff --git a/card/ultralight/src/commonMain/kotlin/com/codebutler/farebot/card/ultralight/UltralightProtocol.kt b/card/ultralight/src/commonMain/kotlin/com/codebutler/farebot/card/ultralight/UltralightProtocol.kt index 8b46d90db..ae53a5f34 100644 --- a/card/ultralight/src/commonMain/kotlin/com/codebutler/farebot/card/ultralight/UltralightProtocol.kt +++ b/card/ultralight/src/commonMain/kotlin/com/codebutler/farebot/card/ultralight/UltralightProtocol.kt @@ -22,9 +22,12 @@ package com.codebutler.farebot.card.ultralight +import co.touchlab.kermit.Logger import com.codebutler.farebot.base.util.hex import com.codebutler.farebot.card.nfc.UltralightTechnology +private val log = Logger.withTag("UltralightProtocol") + /** * Low level commands for MIFARE Ultralight. * @@ -64,7 +67,7 @@ internal class UltralightProtocol( try { return UltralightTypeRaw(versionCmd = getVersion()) } catch (e: Exception) { - println("UltralightProtocol: getVersion returned error, not EV1: $e") + log.d(e) { "getVersion returned error, not EV1" } } // Reconnect the tag @@ -73,10 +76,10 @@ internal class UltralightProtocol( // Try to get a nonce for 3DES authentication with Ultralight C. try { val b2 = auth1() - println("UltralightProtocol: auth1 said = ${b2.hex()}") + log.d { "auth1 said = ${b2.hex()}" } } catch (e: Exception) { // Non-C cards will disconnect here. - println("UltralightProtocol: auth1 returned error, not Ultralight C: $e") + log.d(e) { "auth1 returned error, not Ultralight C" } // TODO: PM3 says NTAG 203 (with different memory size) also looks like this. @@ -120,19 +123,17 @@ internal class UltralightProtocol( } catch (e: Exception) { // When the card halts, the tag may report an error up through the stack. This is fine. // Unfortunately we can't tell if the card was removed or we need to reset it. - println("UltralightProtocol: Discarding exception in halt, this probably expected: $e") + log.d(e) { "Discarding exception in halt, this is probably expected" } } } private suspend fun sendRequest(vararg data: Byte): ByteArray { - println("UltralightProtocol: sent card: ${data.hex()}") + log.d { "sent card: ${data.hex()}" } return mTagTech.transceive(data) } companion object { - private const val TAG = "UltralightProtocol" - // Commands private const val GET_VERSION = 0x60.toByte() private const val AUTH_1 = 0x1a.toByte() diff --git a/card/ultralight/src/commonMain/kotlin/com/codebutler/farebot/card/ultralight/UltralightTypeRaw.kt b/card/ultralight/src/commonMain/kotlin/com/codebutler/farebot/card/ultralight/UltralightTypeRaw.kt index 2db169252..790f95b10 100644 --- a/card/ultralight/src/commonMain/kotlin/com/codebutler/farebot/card/ultralight/UltralightTypeRaw.kt +++ b/card/ultralight/src/commonMain/kotlin/com/codebutler/farebot/card/ultralight/UltralightTypeRaw.kt @@ -22,8 +22,11 @@ package com.codebutler.farebot.card.ultralight +import co.touchlab.kermit.Logger import com.codebutler.farebot.base.util.hex +private val log = Logger.withTag("UltralightTypeRaw") + /** * Raw card type detection data from protocol commands. * @@ -40,9 +43,9 @@ internal data class UltralightTypeRaw( fun parse(): UltralightCard.UltralightType { if (versionCmd != null) { if (versionCmd.size != 8) { - println( - "UltralightTypeRaw: getVersion didn't return 8 bytes, got (${versionCmd.size} instead): ${versionCmd.hex()}", - ) + log.w { + "getVersion didn't return 8 bytes, got (${versionCmd.size} instead): ${versionCmd.hex()}" + } return UltralightCard.UltralightType.UNKNOWN } @@ -55,9 +58,9 @@ internal data class UltralightTypeRaw( 0x11 -> UltralightCard.UltralightType.NTAG215 0x13 -> UltralightCard.UltralightType.NTAG216 else -> { - println( - "UltralightTypeRaw: getVersion returned unknown storage size (${versionCmd[6]}): ${versionCmd.hex()}", - ) + log.w { + "getVersion returned unknown NTAG storage size (${versionCmd[6]}): ${versionCmd.hex()}" + } UltralightCard.UltralightType.UNKNOWN } } @@ -65,9 +68,9 @@ internal data class UltralightTypeRaw( if (versionCmd[2].toInt() != 0x03) { // TODO: PM3 notes that there are a number of NTAG which respond to this command, and look similar to EV1. - println( - "UltralightTypeRaw: getVersion got a tag response with non-EV1 product code (${versionCmd[2]}): ${versionCmd.hex()}", - ) + log.w { + "getVersion got a tag response with non-EV1 product code (${versionCmd[2]}): ${versionCmd.hex()}" + } return UltralightCard.UltralightType.UNKNOWN } @@ -80,9 +83,9 @@ internal data class UltralightTypeRaw( 0x0b -> UltralightCard.UltralightType.EV1_MF0UL11 0x0e -> UltralightCard.UltralightType.EV1_MF0UL21 else -> { - println( - "UltralightTypeRaw: getVersion returned unknown storage size (${versionCmd[6]}): ${versionCmd.hex()}", - ) + log.w { + "getVersion returned unknown EV1 storage size (${versionCmd[6]}): ${versionCmd.hex()}" + } UltralightCard.UltralightType.UNKNOWN } } diff --git a/card/vicinity/src/commonMain/kotlin/com/codebutler/farebot/card/vicinity/VicinityCardReader.kt b/card/vicinity/src/commonMain/kotlin/com/codebutler/farebot/card/vicinity/VicinityCardReader.kt index f4cad34c5..b512351b9 100644 --- a/card/vicinity/src/commonMain/kotlin/com/codebutler/farebot/card/vicinity/VicinityCardReader.kt +++ b/card/vicinity/src/commonMain/kotlin/com/codebutler/farebot/card/vicinity/VicinityCardReader.kt @@ -23,10 +23,13 @@ package com.codebutler.farebot.card.vicinity +import co.touchlab.kermit.Logger import com.codebutler.farebot.card.nfc.VicinityTechnology import com.codebutler.farebot.card.vicinity.raw.RawVicinityCard import kotlin.time.Clock +private val log = Logger.withTag("VicinityCardReader") + /** * Shared card-reading algorithm for NFC-V (ISO 15693) Vicinity cards. * @@ -63,7 +66,7 @@ object VicinityCardReader { null } } catch (e: Exception) { - println("[VicinityCardReader] Failed to read system info: $e") + log.w(e) { "Failed to read system info" } null } diff --git a/flipper/build.gradle.kts b/flipper/build.gradle.kts index 2f094aed0..0b2eccb5c 100644 --- a/flipper/build.gradle.kts +++ b/flipper/build.gradle.kts @@ -23,6 +23,7 @@ kotlin { sourceSets { commonMain.dependencies { + implementation(libs.kermit) implementation(libs.kotlinx.serialization.protobuf) implementation(libs.kotlinx.coroutines.core) } diff --git a/flipper/src/androidMain/kotlin/com/codebutler/farebot/flipper/AndroidUsbSerialTransport.kt b/flipper/src/androidMain/kotlin/com/codebutler/farebot/flipper/AndroidUsbSerialTransport.kt index 4bbb44532..2054e3cd6 100644 --- a/flipper/src/androidMain/kotlin/com/codebutler/farebot/flipper/AndroidUsbSerialTransport.kt +++ b/flipper/src/androidMain/kotlin/com/codebutler/farebot/flipper/AndroidUsbSerialTransport.kt @@ -12,6 +12,7 @@ import android.hardware.usb.UsbEndpoint import android.hardware.usb.UsbInterface import android.hardware.usb.UsbManager import android.os.Build +import co.touchlab.kermit.Logger import kotlinx.coroutines.suspendCancellableCoroutine import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException @@ -22,6 +23,8 @@ import kotlin.coroutines.resumeWithException * * Flipper Zero USB identifiers: VID 0x0483 (STMicroelectronics), PID 0x5740. */ +private val log = Logger.withTag("AndroidUsbSerialTransport") + class AndroidUsbSerialTransport( private val context: Context, ) : FlipperTransport { @@ -187,7 +190,8 @@ class AndroidUsbSerialTransport( cont.invokeOnCancellation { try { context.unregisterReceiver(receiver) - } catch (_: Exception) { + } catch (e: Exception) { + log.d(e) { "unregisterReceiver on cancel" } } } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1434adaf6..acd65ab8b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,6 +14,7 @@ activity = "1.10.1" material = "1.13.0" appcompat = "1.7.1" kotlincrypto-hash = "0.8.0" +kermit = "2.0.8" metro = "0.10.4" usb4java = "1.3.5" usb4java-native = "1.3.3" @@ -43,6 +44,9 @@ sqldelight-web-worker-driver = { module = "app.cash.sqldelight:web-worker-driver material = { module = "com.google.android.material:material", version.ref = "material" } appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" } +# Kermit (logging) +kermit = { module = "co.touchlab:kermit", version.ref = "kermit" } + # KotlinCrypto kotlincrypto-hash-md = { module = "org.kotlincrypto.hash:md", version.ref = "kotlincrypto-hash" } diff --git a/transit/bip/src/commonMain/kotlin/com/codebutler/farebot/transit/bip/BipUtil.kt b/transit/bip/src/commonMain/kotlin/com/codebutler/farebot/transit/bip/BipUtil.kt index 910be3b6f..c339e3da8 100644 --- a/transit/bip/src/commonMain/kotlin/com/codebutler/farebot/transit/bip/BipUtil.kt +++ b/transit/bip/src/commonMain/kotlin/com/codebutler/farebot/transit/bip/BipUtil.kt @@ -22,12 +22,14 @@ package com.codebutler.farebot.transit.bip +import co.touchlab.kermit.Logger import com.codebutler.farebot.base.util.getBitsFromBufferLeBits import kotlinx.datetime.LocalDateTime import kotlinx.datetime.TimeZone import kotlinx.datetime.toInstant import kotlin.time.Instant +private val log = Logger.withTag("BipUtil") private val TZ = TimeZone.of("America/Santiago") internal fun parseTimestamp(raw: ByteArray): Instant? { @@ -41,7 +43,8 @@ internal fun parseTimestamp(raw: ByteArray): Instant? { return try { LocalDateTime(year, month, day, hour, minute, second) .toInstant(TZ) - } catch (_: Exception) { + } catch (e: Exception) { + log.d(e) { "Failed to parse timestamp" } null } } diff --git a/transit/calypso/src/commonMain/kotlin/com/codebutler/farebot/transit/calypso/CalypsoTransitFactory.kt b/transit/calypso/src/commonMain/kotlin/com/codebutler/farebot/transit/calypso/CalypsoTransitFactory.kt index 47a09e924..c529a260f 100644 --- a/transit/calypso/src/commonMain/kotlin/com/codebutler/farebot/transit/calypso/CalypsoTransitFactory.kt +++ b/transit/calypso/src/commonMain/kotlin/com/codebutler/farebot/transit/calypso/CalypsoTransitFactory.kt @@ -22,6 +22,7 @@ package com.codebutler.farebot.transit.calypso +import co.touchlab.kermit.Logger import com.codebutler.farebot.base.util.FormattedString import com.codebutler.farebot.card.iso7816.ISO7816Application import com.codebutler.farebot.card.iso7816.ISO7816Card @@ -35,6 +36,8 @@ import com.codebutler.farebot.transit.en1545.CalypsoConstants * Base class for Calypso ISO 7816 transit system factories. * Subclasses implement [checkTenv] to match on the ticket environment data. */ +private val log = Logger.withTag("CalypsoTransitFactory") + abstract class CalypsoTransitFactory : TransitFactory { override val allCards: List = emptyList() @@ -63,7 +66,8 @@ abstract class CalypsoTransitFactory : TransitFactory ?.value ?: return false return try { checkTenv(tenv) - } catch (_: Exception) { + } catch (e: Exception) { + log.d(e) { "Failed to check ticket environment" } false } } diff --git a/transit/calypso/src/commonMain/kotlin/com/codebutler/farebot/transit/calypso/intercode/IntercodeTransitFactory.kt b/transit/calypso/src/commonMain/kotlin/com/codebutler/farebot/transit/calypso/intercode/IntercodeTransitFactory.kt index 5640377e6..34a9a5316 100644 --- a/transit/calypso/src/commonMain/kotlin/com/codebutler/farebot/transit/calypso/intercode/IntercodeTransitFactory.kt +++ b/transit/calypso/src/commonMain/kotlin/com/codebutler/farebot/transit/calypso/intercode/IntercodeTransitFactory.kt @@ -22,6 +22,7 @@ package com.codebutler.farebot.transit.calypso.intercode +import co.touchlab.kermit.Logger import com.codebutler.farebot.base.util.FormattedString import com.codebutler.farebot.card.CardType import com.codebutler.farebot.card.iso7816.ISO7816Application @@ -39,6 +40,8 @@ import com.codebutler.farebot.transit.en1545.En1545TransitData import com.codebutler.farebot.transit.en1545.getBitsFromBuffer import farebot.transit.calypso.generated.resources.* +private val log = Logger.withTag("IntercodeTransitFactory") + class IntercodeTransitFactory : CalypsoTransitFactory() { override val allCards: List get() = ALL_CARDS @@ -60,7 +63,8 @@ class IntercodeTransitFactory : CalypsoTransitFactory() { val netId = try { tenv.getBitsFromBuffer(13, 24) - } catch (_: Exception) { + } catch (e: Exception) { + log.d(e) { "Failed to parse netId for identity" } 0 } val cardName = IntercodeTransitInfo.getCardName(netId, tenv) @@ -71,7 +75,8 @@ class IntercodeTransitFactory : CalypsoTransitFactory() { try { val netId = tenv.getBitsFromBuffer(13, 24) IntercodeTransitInfo.isIntercode(netId) - } catch (_: Exception) { + } catch (e: Exception) { + log.d(e) { "Failed to check Intercode tenv" } false } @@ -88,7 +93,8 @@ class IntercodeTransitFactory : CalypsoTransitFactory() { val netId = try { tenv.getBitsFromBuffer(13, 24) - } catch (_: Exception) { + } catch (e: Exception) { + log.d(e) { "Failed to parse netId for serial" } 0 } diff --git a/transit/calypso/src/commonMain/kotlin/com/codebutler/farebot/transit/calypso/mobib/MobibTransitInfo.kt b/transit/calypso/src/commonMain/kotlin/com/codebutler/farebot/transit/calypso/mobib/MobibTransitInfo.kt index 46b8d25ad..d97c71623 100644 --- a/transit/calypso/src/commonMain/kotlin/com/codebutler/farebot/transit/calypso/mobib/MobibTransitInfo.kt +++ b/transit/calypso/src/commonMain/kotlin/com/codebutler/farebot/transit/calypso/mobib/MobibTransitInfo.kt @@ -22,6 +22,7 @@ package com.codebutler.farebot.transit.calypso.mobib +import co.touchlab.kermit.Logger import com.codebutler.farebot.base.ui.ListItem import com.codebutler.farebot.base.ui.ListItemInterface import com.codebutler.farebot.base.util.DateFormatStyle @@ -133,7 +134,8 @@ class MobibTransitInfo internal constructor( NumberUtils.zeroPad(NumberUtils.convertBCDtoInteger(holder.getBitsFromBuffer(82 + 80, 8)), 2) + " / " + NumberUtils.convertBCDtoInteger(holder.getBitsFromBuffer(90 + 80, 4)).toString() - } catch (_: Exception) { + } catch (e: Exception) { + Logger.withTag("MobibTransitInfo").d(e) { "Failed to parse serial" } null } } @@ -214,7 +216,8 @@ class MobibTransitInfo internal constructor( epLoadLog?.records?.get(1)?.let { try { it.getBitsFromBuffer(2, 14) - } catch (_: Exception) { + } catch (e: Exception) { + Logger.withTag("MobibTransitInfo").d(e) { "Failed to parse purchase date" } 0 } } ?: 0 diff --git a/transit/china/src/commonMain/kotlin/com/codebutler/farebot/transit/china/ChinaTransitData.kt b/transit/china/src/commonMain/kotlin/com/codebutler/farebot/transit/china/ChinaTransitData.kt index 7922660d9..3780decec 100644 --- a/transit/china/src/commonMain/kotlin/com/codebutler/farebot/transit/china/ChinaTransitData.kt +++ b/transit/china/src/commonMain/kotlin/com/codebutler/farebot/transit/china/ChinaTransitData.kt @@ -22,6 +22,7 @@ package com.codebutler.farebot.transit.china +import co.touchlab.kermit.Logger import com.codebutler.farebot.base.util.NumberUtils import com.codebutler.farebot.base.util.getBitsFromBufferSigned import com.codebutler.farebot.card.china.ChinaCard @@ -114,7 +115,7 @@ object ChinaTransitData { second = 0, ).toInstant(TZ) } catch (e: Exception) { - println("[ChinaTransit] Failed to parse hex date from $value: $e") + Logger.withTag("ChinaTransit").w(e) { "Failed to parse hex date from $value" } null } } diff --git a/transit/erg/src/commonMain/kotlin/com/codebutler/farebot/transit/erg/ErgTransitInfo.kt b/transit/erg/src/commonMain/kotlin/com/codebutler/farebot/transit/erg/ErgTransitInfo.kt index e392bde4f..c259a45eb 100644 --- a/transit/erg/src/commonMain/kotlin/com/codebutler/farebot/transit/erg/ErgTransitInfo.kt +++ b/transit/erg/src/commonMain/kotlin/com/codebutler/farebot/transit/erg/ErgTransitInfo.kt @@ -22,6 +22,7 @@ package com.codebutler.farebot.transit.erg +import co.touchlab.kermit.Logger import com.codebutler.farebot.base.util.FormattedString import com.codebutler.farebot.card.classic.ClassicCard import com.codebutler.farebot.card.classic.DataClassicSector @@ -92,7 +93,7 @@ open class ErgTransitInfo( return try { ErgMetadataRecord.recordFromBytes(sector0.getBlock(2).data) } catch (e: Exception) { - println("[ERG] Failed to parse metadata record: $e") + Logger.withTag("ERG").w(e) { "Failed to parse metadata record" } null } } diff --git a/transit/pilet/src/commonMain/kotlin/com/codebutler/farebot/transit/pilet/KievDigitalTransitFactory.kt b/transit/pilet/src/commonMain/kotlin/com/codebutler/farebot/transit/pilet/KievDigitalTransitFactory.kt index b2815bbed..e6eb2727e 100644 --- a/transit/pilet/src/commonMain/kotlin/com/codebutler/farebot/transit/pilet/KievDigitalTransitFactory.kt +++ b/transit/pilet/src/commonMain/kotlin/com/codebutler/farebot/transit/pilet/KievDigitalTransitFactory.kt @@ -22,6 +22,7 @@ package com.codebutler.farebot.transit.pilet +import co.touchlab.kermit.Logger import com.codebutler.farebot.base.util.FormattedString import com.codebutler.farebot.base.util.byteArrayToInt import com.codebutler.farebot.base.util.readASCII @@ -40,6 +41,8 @@ import farebot.transit.pilet.generated.resources.* * * This is a serial-only reader; the card data is stored as NDEF with pilet.ee TLV records. */ +private val log = Logger.withTag("KievDigitalTransitFactory") + class KievDigitalTransitFactory : TransitFactory { companion object { private const val NDEF_TYPE = "pilet.ee:ekaart:5" @@ -138,7 +141,8 @@ class KievDigitalTransitFactory : TransitFactory } } if (allData.isEmpty()) null else allData.toByteArray() - } catch (_: Exception) { + } catch (e: Exception) { + log.d(e) { "Failed to read NDEF data" } null } diff --git a/transit/pilet/src/commonMain/kotlin/com/codebutler/farebot/transit/pilet/TartuTransitFactory.kt b/transit/pilet/src/commonMain/kotlin/com/codebutler/farebot/transit/pilet/TartuTransitFactory.kt index ebccc5cd2..542ea93d0 100644 --- a/transit/pilet/src/commonMain/kotlin/com/codebutler/farebot/transit/pilet/TartuTransitFactory.kt +++ b/transit/pilet/src/commonMain/kotlin/com/codebutler/farebot/transit/pilet/TartuTransitFactory.kt @@ -22,6 +22,7 @@ package com.codebutler.farebot.transit.pilet +import co.touchlab.kermit.Logger import com.codebutler.farebot.base.util.FormattedString import com.codebutler.farebot.base.util.byteArrayToInt import com.codebutler.farebot.base.util.readASCII @@ -43,6 +44,8 @@ import farebot.transit.pilet.generated.resources.* * * Documentation of format: https://github.com/micolous/metrodroid/wiki/TartuBus */ +private val log = Logger.withTag("TartuTransitFactory") + class TartuTransitFactory : TransitFactory { companion object { private const val NDEF_TYPE = "pilet.ee:ekaart:2" @@ -142,7 +145,8 @@ class TartuTransitFactory : TransitFactory { } } if (allData.isEmpty()) null else allData.toByteArray() - } catch (_: Exception) { + } catch (e: Exception) { + log.d(e) { "Failed to read NDEF data" } null } diff --git a/transit/serialonly/src/commonMain/kotlin/com/codebutler/farebot/transit/serialonly/NorticTransitInfo.kt b/transit/serialonly/src/commonMain/kotlin/com/codebutler/farebot/transit/serialonly/NorticTransitInfo.kt index a437d7a25..5d43e06a2 100644 --- a/transit/serialonly/src/commonMain/kotlin/com/codebutler/farebot/transit/serialonly/NorticTransitInfo.kt +++ b/transit/serialonly/src/commonMain/kotlin/com/codebutler/farebot/transit/serialonly/NorticTransitInfo.kt @@ -10,6 +10,7 @@ package com.codebutler.farebot.transit.serialonly +import co.touchlab.kermit.Logger import com.codebutler.farebot.base.ui.ListItem import com.codebutler.farebot.base.ui.ListItemInterface import com.codebutler.farebot.base.util.FormattedString @@ -42,7 +43,8 @@ class NorticTransitInfo( val epoch = LocalDate(1997, 1, 1) val expiryDate = epoch.toEpochDays() + mValidityEndDate LocalDate.fromEpochDays(expiryDate).toString() - } catch (_: Exception) { + } catch (e: Exception) { + Logger.withTag("NorticTransitInfo").d(e) { "Failed to parse expiry date" } "Day $mValidityEndDate since 1997-01-01" } diff --git a/transit/smartrider/src/commonMain/kotlin/com/codebutler/farebot/transit/smartrider/SmartRiderTransitFactory.kt b/transit/smartrider/src/commonMain/kotlin/com/codebutler/farebot/transit/smartrider/SmartRiderTransitFactory.kt index 81232f47e..4eee44ad4 100644 --- a/transit/smartrider/src/commonMain/kotlin/com/codebutler/farebot/transit/smartrider/SmartRiderTransitFactory.kt +++ b/transit/smartrider/src/commonMain/kotlin/com/codebutler/farebot/transit/smartrider/SmartRiderTransitFactory.kt @@ -22,6 +22,7 @@ package com.codebutler.farebot.transit.smartrider +import co.touchlab.kermit.Logger import com.codebutler.farebot.base.util.FormattedString import com.codebutler.farebot.base.util.HashUtils import com.codebutler.farebot.base.util.byteArrayToIntReversed @@ -45,6 +46,8 @@ import farebot.transit.smartrider.generated.resources.* * https://github.com/micolous/metrodroid/wiki/SmartRider * https://github.com/micolous/metrodroid/wiki/MyWay */ +private val log = Logger.withTag("SmartRiderTransitFactory") + class SmartRiderTransitFactory : TransitFactory { override val allCards: List get() = listOf(CARD_INFO) @@ -109,8 +112,8 @@ class SmartRiderTransitFactory : TransitFactory { override val allCards: List get() = listOf(CARD_INFO) @@ -48,7 +51,7 @@ class LeapTransitFactory : TransitFactory { val file6 = (app.getFile(6) as StandardDesfireFile).data TransitIdentity(FormattedString(Res.string.transit_leap_card_name), LeapTransitInfo.getSerial(file2, file6)) } catch (e: Exception) { - println("[Leap] Failed to parse identity: $e") + log.w(e) { "Failed to parse identity" } TransitIdentity(FormattedString(Res.string.transit_leap_card_name), null) } diff --git a/transit/troika/src/commonMain/kotlin/com/codebutler/farebot/transit/troika/TroikaTransitFactory.kt b/transit/troika/src/commonMain/kotlin/com/codebutler/farebot/transit/troika/TroikaTransitFactory.kt index e112eb448..0cd9603f4 100644 --- a/transit/troika/src/commonMain/kotlin/com/codebutler/farebot/transit/troika/TroikaTransitFactory.kt +++ b/transit/troika/src/commonMain/kotlin/com/codebutler/farebot/transit/troika/TroikaTransitFactory.kt @@ -23,6 +23,7 @@ package com.codebutler.farebot.transit.troika +import co.touchlab.kermit.Logger import com.codebutler.farebot.base.util.FormattedString import com.codebutler.farebot.base.util.HashUtils import com.codebutler.farebot.card.classic.ClassicCard @@ -39,6 +40,8 @@ import farebot.transit.troika.generated.resources.* * Detection uses key-hash matching on sector 1 keys, with fallback to * checking the header magic bytes on sector 8 and sector 4. */ +private val log = Logger.withTag("TroikaTransitFactory") + class TroikaTransitFactory : TransitFactory { // TroikaHybridTransitFactory is the registered factory; this is an internal helper. override val allCards: List @@ -66,7 +69,8 @@ class TroikaTransitFactory : TransitFactory { val sector = card.getSector(idx) as? DataClassicSector ?: return@any false try { TroikaBlock.check(sector.getBlock(0).data) - } catch (_: Exception) { + } catch (e: Exception) { + log.d(e) { "Failed to check Troika header on sector $idx" } false } } @@ -80,7 +84,8 @@ class TroikaTransitFactory : TransitFactory { try { val data = sector.readBlocks(0, 3) if (TroikaBlock.check(data)) data else null - } catch (_: Exception) { + } catch (e: Exception) { + log.d(e) { "Failed to read Troika sector $idx for identity" } null } } ?: throw RuntimeException("No valid Troika sector found") @@ -131,7 +136,8 @@ class TroikaTransitFactory : TransitFactory { val sector = card.getSector(idx) as? DataClassicSector ?: return null val data = sector.readBlocks(0, 3) if (!TroikaBlock.check(data)) null else TroikaBlock.parseBlock(data) - } catch (_: Exception) { + } catch (e: Exception) { + log.d(e) { "Failed to decode Troika sector $idx" } null } }