| 1 | /** |
| 2 | * Namespaced, filterable debug logging shared by mobile subsystems. |
| 3 | * |
| 4 | * Ordinary, expected conditions — a queued send failing while the device is |
| 5 | * offline, for example — go through a debug logger instead of `console.warn` |
| 6 | * so warning output stays reserved for failures someone can act on. Output |
| 7 | * uses `console.log` with a `[t3-<namespace>]` prefix, matching the existing |
| 8 | * cloud and terminal debug logs. (client-runtime cannot host this: its |
| 9 | * tooling bans `console.*` in favor of Effect logging.) |
| 10 | * |
| 11 | * A logger is silent in every build, including development, unless enabled. |
| 12 | * Toggle it from a JS debugger or the Metro console, including on release/TestFlight builds: |
| 13 | * - `globalThis.__T3_DEBUG__ = true` enables every namespace; |
| 14 | * - `globalThis.__T3_DEBUG__ = ["thread-outbox"]` enables only listed ones. |
| 15 | * |
| 16 | * Subsystems whose traces are useful by default in development (`__DEV__`) |
| 17 | * opt in with `enabledInDev`; `legacyGlobalFlag` keeps an older |
| 18 | * subsystem-specific global (e.g. `__T3_CLOUD_DEBUG__`) working. |
| 19 | */ |
| 20 | |
| 21 | export interface DebugLogger { |
| 22 | readonly isEnabled: () => boolean; |
| 23 | readonly log: (event: string, data?: Record<string, unknown>) => void; |
| 24 | } |
| 25 | |
| 26 | export interface DebugLoggerOptions { |
| 27 | /** Log whenever `__DEV__` is true, without the global filter. Defaults to false. */ |
| 28 | readonly enabledInDev?: boolean; |
| 29 | /** Name of a legacy subsystem-specific global boolean, e.g. `"__T3_CLOUD_DEBUG__"`. */ |
| 30 | readonly legacyGlobalFlag?: string; |
| 31 | } |
| 32 | |
| 33 | function globalValue(name: string): unknown { |
| 34 | return typeof globalThis === "undefined" |
| 35 | ? undefined |
| 36 | : (globalThis as Record<string, unknown>)[name]; |
| 37 | } |
| 38 | |
| 39 | export function createDebugLogger( |
| 40 | namespace: string, |
| 41 | options: DebugLoggerOptions = {}, |
| 42 | ): DebugLogger { |
| 43 | const isEnabled = () => { |
| 44 | if (options.enabledInDev === true && typeof __DEV__ !== "undefined" && __DEV__) { |
| 45 | return true; |
| 46 | } |
| 47 | if (options.legacyGlobalFlag !== undefined && globalValue(options.legacyGlobalFlag) === true) { |
| 48 | return true; |
| 49 | } |
| 50 | const filter = globalValue("__T3_DEBUG__"); |
| 51 | return filter === true || (Array.isArray(filter) && filter.includes(namespace)); |
| 52 | }; |
| 53 | const log = (event: string, data?: Record<string, unknown>) => { |
| 54 | if (!isEnabled()) { |
| 55 | return; |
| 56 | } |
| 57 | if (data === undefined) { |
| 58 | console.log(`[t3-${namespace}] ${event}`); |
| 59 | } else { |
| 60 | console.log(`[t3-${namespace}] ${event}`, data); |
| 61 | } |
| 62 | }; |
| 63 | return { isEnabled, log }; |
| 64 | } |