From a68e4624621b484235f9724dbdef1ad822ebe98c Mon Sep 17 00:00:00 2001 From: Padreug Date: Wed, 23 Sep 2026 23:55:13 +0200 Subject: [PATCH] fix(logging): interpolate log fields instead of passing an object Electron's console bridge stringifies each console argument on its way to the journal, so `console.log('msg:', { a, b })` arrives as `msg: [object Object]` and every field is lost. That cost a debugging session today: a cassette-state publish that had in fact applied an operator refill correctly looked from the journal like nothing had happened, because the d-tag, event id and stamp were all inside the object. Three call sites, the only ones in the renderer passing an object. The publish line now also carries seq and the applied-op count, which are the two things worth knowing when an operation seems not to have landed. Recorded in CLAUDE.md's debugging invariants so it does not come back. --- CLAUDE.md | 1 + apps/machine/src/services/operator-config.ts | 7 +++++-- apps/machine/src/services/operator-fees.ts | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 1b2e8a0..878be52 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -219,6 +219,7 @@ UP Board enumerates its eMMC controller via ACPI, not PCI. `upboard.nix` force-l ## Useful invariants when debugging - The renderer logs prefix every line with a tag: `[Lightning]`, `[ATM]`, `[ATM Service]`, `[LNURL Session]`, `[CLINK]`, `[StateStore]`. `journalctl -u bitspire | grep '\['` is your friend. +- **Never pass an object as a console argument in the renderer.** Electron's console bridge stringifies each argument, so `console.log('msg:', { a, b })` reaches the journal as `msg: [object Object]` and every field is lost. Interpolate instead. Cost a debugging session on 2026-09-23, when a cassette publish that had worked looked like it had done nothing. - `bitspire.service` runs as the `lamassu` user; `/var/lib/bitspire` is its `dataDir` (ReadWritePaths). DB lives at `/var/lib/bitspire/state.db` (we previously had `/var/lib/lamassu-atm` — that path is gone on dev, see commit `9c455d6`). - The `lightning.lightningPub` field on `LightningServices` is a `LightningBackend` *adapter*, not a `LightningPubClient`. Don't try to call LP-only methods on it. diff --git a/apps/machine/src/services/operator-config.ts b/apps/machine/src/services/operator-config.ts index 2f33f73..f81b5d5 100644 --- a/apps/machine/src/services/operator-config.ts +++ b/apps/machine/src/services/operator-config.ts @@ -147,7 +147,7 @@ export async function startOperatorConfigService( }, } ) - console.log('[OperatorConfig] Subscribed:', { dTag, subscriptionId }) + console.log(`[OperatorConfig] Subscribed: d=${dTag} sub=${subscriptionId}`) const heartbeat = setInterval(() => { publishCassettesState(cfg, api, machineId).catch((err) => @@ -342,6 +342,9 @@ async function publishCassettesState( await cfg.nostrClient.publish(event) await api.markStatePublished(createdAt) - console.log('[OperatorConfig] cassettes-state published:', { dTag, eventId: event.id, createdAt }) + console.log( + `[OperatorConfig] cassettes-state published: id=${event.id.slice(0, 12)}… ` + + `created_at=${createdAt} seq=${seq} applied_ops=${appliedOps.length} d=${dTag}` + ) return true } diff --git a/apps/machine/src/services/operator-fees.ts b/apps/machine/src/services/operator-fees.ts index 4e699a3..8afb420 100644 --- a/apps/machine/src/services/operator-fees.ts +++ b/apps/machine/src/services/operator-fees.ts @@ -132,7 +132,7 @@ export async function startOperatorFeesService( }, } ) - console.log('[Fees] Subscribed:', { dTag, subscriptionId }) + console.log(`[Fees] Subscribed: d=${dTag} sub=${subscriptionId}`) return { stop: () => cfg.nostrClient.unsubscribe(subscriptionId), -- 2.55.0