项目文件夹

文件
wehub-resource-sync 98e40dac97
CLI Smoke Test / smoke-test-linux (20) (push) Has been cancelled
CLI Smoke Test / smoke-test-linux (24) (push) Has been cancelled
CLI Smoke Test / smoke-test-windows (20) (push) Has been cancelled
CLI Smoke Test / smoke-test-windows (24) (push) Has been cancelled
Expo App TypeScript typecheck / typecheck (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:40:49 +08:00

146 行
4.1 KiB
TypeScript

/**
* Console logging bootstrap for React Native
*
* Control flow:
*
* console.log("msg", obj)
* │
* ├─ consoleOutputEnabled = false? (default for prod)
* │ └─ return immediately ⛔ (zero cost, args untouched)
* │
* ├─ consoleOutputEnabled = true? (default for dev/preview, or toggled on)
* │ ├─ call original console method ✅
* │ ├─ capture to in-app buffer ✅
* │ └─ send to remote log server (if configured) ✅
* │
* └─ console.error / console.warn (always, regardless of flag)
* ├─ call original console method ✅
* ├─ capture to in-app buffer ✅
* └─ send to remote log server (if configured) ✅
*/
import { log } from '@/log';
import { MAX_APP_LOG_ENTRIES } from '@/log';
import { getLogServerUrl } from '@/sync/serverConfig';
import { loadLocalSettings } from '@/sync/persistence';
import { loadAppConfig } from '@/sync/appConfig';
import { Platform } from 'react-native';
import { serializeForLogs } from '@/utils/truncateForLogs';
let logBuffer: any[] = []
const MAX_BUFFER_SIZE = MAX_APP_LOG_ENTRIES
let isConsolePatched = false
let remoteLogServerUrl: string | null = null
let consoleOutputEnabled = false
let originalConsole: {
log: typeof console.log,
info: typeof console.info,
warn: typeof console.warn,
error: typeof console.error,
debug: typeof console.debug,
} | null = null
/**
* Toggle console output at runtime (e.g. from Dev screen toggle).
*/
export function setConsoleOutputEnabled(enabled: boolean) {
consoleOutputEnabled = enabled
}
export function initConsoleLogging() {
if (isConsolePatched) {
return
}
remoteLogServerUrl = getLogServerUrl();
// Determine initial state: user setting > build variant default > off
try {
const settings = loadLocalSettings();
const config = loadAppConfig();
consoleOutputEnabled = settings.consoleLoggingEnabled || config.consoleLoggingDefault || false;
} catch {
consoleOutputEnabled = false;
}
originalConsole = {
log: console.log,
info: console.info,
warn: console.warn,
error: console.error,
debug: console.debug,
}
log.setConsoleCaptureEnabled(true)
function formatArgs(args: any[]): string {
return args.map(a => {
if (a === null || a === undefined) return String(a)
if (typeof a !== 'object') return serializeForLogs(a)
try { return serializeForLogs(a) } catch { return String(a) }
}).join(' ')
}
function sendLog(level: string, formatted: string) {
if (!remoteLogServerUrl) {
return
}
void fetch(remoteLogServerUrl + '/logs', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
timestamp: new Date().toISOString(),
level,
message: formatted,
source: 'mobile',
platform: Platform.OS,
})
}).catch(() => {})
}
// Patch console methods
;(['log', 'info', 'warn', 'error', 'debug'] as const).forEach(level => {
const alwaysPassThrough = level === 'error' || level === 'warn'
console[level] = (...args: any[]) => {
// Full short-circuit: when off, skip everything for log/info/debug
if (!consoleOutputEnabled && !alwaysPassThrough) {
return
}
// Pass raw args to native console (preserves interactive object inspection,
// clickable stack traces, and multi-arg formatting in dev tools)
originalConsole![level](...args)
// Serialize once for buffer + remote (but NOT for native console)
const formatted = formatArgs(args)
log.captureFormatted(level, formatted)
logBuffer.push({
timestamp: new Date().toISOString(),
level,
message: formatted
})
if (logBuffer.length > MAX_BUFFER_SIZE) {
logBuffer.shift()
}
sendLog(level, formatted)
}
})
isConsolePatched = true
originalConsole.log('[ConsoleLogging] Initialized', consoleOutputEnabled ? '(output enabled)' : '(output suppressed)')
}
// For developer settings UI
export function getLogBuffer() {
return [...logBuffer]
}
export function clearLogBuffer() {
logBuffer = []
}