Debugging the integration build on real iOS 26.5 devices over the air.
- BitLogger DEBUG default minimumLevel now .debug (was .info) so a
tap-launched sideload surfaces .debug decision logs without the
BITCHAT_LOG_LEVEL env var; env still wins; release unchanged (.info,
emits nothing). Extracted defaultMinimumLevel + a test asserting it.
- Greppable bracket decision tags at .info (visible even at .info) via
SecureLogger, ID-prefixes only, sanitized:
[ROUTE] source-route vs flood origination (+reason) + fragment
targeted-resync; [GW] uplink accept/reject, downlink rebroadcast/drop,
loop skips, drain-timer; [WIFI] offer/accept/decline, AWDL connect,
transfer start/complete+bytes, fallback+reason; [PREKEY] seal choice,
consume, unknown-prekey open fail, bundle ingest, re-gossip;
[SYNC] one concise summary per cycle + per-request served counts (no
per-packet spam); [PING]/[TRACE] RTT + computed path.
- New OSLog categories: mesh, gateway, transport.
- Refactored BLESourceRouteOriginationPolicy.route -> decide returning a
Decision enum (flood(reason)/route(hops)) so the flood reason is
greppable and unit-tested.
- DEBUG-only in-memory ring buffer (LogExportBuffer, ~2000 lines/512KB,
thread-safe, off main thread) + App Info "Export Logs" share sheet
(iOS UIActivityViewController / macOS NSSavePanel), VoiceOver labels.
- DEBUG-only UDP LAN log sink (LogNetworkSink): opt-in, off by default,
fire-and-forget, never blocks/throws; each line prefixed with the
device nickname for demux; configured via App Info host:port fields.
Same sanitized formatted line feeds buffer, sink, and export.
All new code is #if DEBUG; release emits nothing and ships no export/sink UI.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Production builds previously still emitted info/warning/error entries
via os_log (content private-redacted, but entries, categories, and
timing metadata were visible, and message strings were constructed).
For a privacy-first app the right posture is silence: every SecureLogger
wrapper and both cores are now gated behind #if DEBUG, so release
builds construct no log strings and emit nothing. Debug builds are
unchanged (public formatting, level threshold via BITCHAT_LOG_LEVEL).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
SecureLogger's public wrappers evaluated their message autoclosure before
the level check inside log(), so every filtered debug message across the
codebase still paid for string interpolation - hundreds of call sites on
the per-packet/per-event hot paths. The wrappers now guard the level
first, and debug() compiles out of release builds entirely. Regression
tests verify filtered messages are never constructed.
The two heaviest per-event debug logs (NostrRelayManager inbound events,
ChatNostrCoordinator geo events) are now sampled every 100th with a
running count, so debug-enabled dev builds stop emitting hundreds of
lines per minute in busy geohashes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>