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
21export interface DebugLogger {
22 readonly isEnabled: () => boolean;
23 readonly log: (event: string, data?: Record<string, unknown>) => void;
24}
25
26export 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
33function globalValue(name: string): unknown {
34 return typeof globalThis === "undefined"
35 ? undefined
36 : (globalThis as Record<string, unknown>)[name];
37}
38
39export 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}