const STORAGE_KEY = 'app_runtime_logs' const MAX_LOGS = 800 let logs = [] let installed = false let persistTimer = null const nativeConsole = { log: typeof console.log === 'function' ? console.log.bind(console) : () => {}, info: typeof console.info === 'function' ? console.info.bind(console) : null, warn: typeof console.warn === 'function' ? console.warn.bind(console) : null, error: typeof console.error === 'function' ? console.error.bind(console) : null, } const loadFromStorage = () => { try { const raw = uni.getStorageSync(STORAGE_KEY) if (!raw) { logs = [] return } const parsed = typeof raw === 'string' ? JSON.parse(raw) : raw logs = Array.isArray(parsed) ? parsed : [] } catch (error) { logs = [] } } const persistNow = () => { if (persistTimer) { clearTimeout(persistTimer) persistTimer = null } try { uni.setStorageSync(STORAGE_KEY, JSON.stringify(logs)) } catch (error) { // ignore persist errors } } const schedulePersist = () => { if (persistTimer) return persistTimer = setTimeout(() => { persistTimer = null persistNow() }, 300) } const formatArg = (arg) => { if (arg === null) return 'null' if (arg === undefined) return 'undefined' if (typeof arg === 'string') return arg if (typeof arg === 'number' || typeof arg === 'boolean') return String(arg) try { return JSON.stringify(arg) } catch (error) { return String(arg) } } const pad = (n) => String(n).padStart(2, '0') const formatTime = (date = new Date()) => { return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}.${String(date.getMilliseconds()).padStart(3, '0')}` } export const appendLog = (level, args) => { const message = (Array.isArray(args) ? args : [args]) .map(formatArg) .join(' ') logs.push({ time: formatTime(), level: level || 'log', message, }) if (logs.length > MAX_LOGS) { logs = logs.slice(logs.length - MAX_LOGS) } schedulePersist() } /** 业务关键日志:写入本地日志,同时输出到原生 console(避免二次拦截) */ export const appLog = (level, ...args) => { const lv = level || 'info' appendLog(lv, args) const printer = nativeConsole[lv] || nativeConsole.log || (() => {}) try { printer(...args) } catch (error) { // ignore } } export const flushLogs = () => { persistNow() } export const getLogs = () => { if (!logs.length) { loadFromStorage() } return logs.slice() } export const getLogsText = () => { return getLogs() .map((item) => `[${item.time}] [${item.level}] ${item.message}`) .join('\n') } export const clearLogs = () => { logs = [] try { uni.removeStorageSync(STORAGE_KEY) } catch (error) { // ignore } } export const installAppLogger = () => { if (installed) return installed = true loadFromStorage() nativeConsole.log = console.log.bind(console) nativeConsole.info = console.info ? console.info.bind(console) : nativeConsole.log nativeConsole.warn = console.warn.bind(console) nativeConsole.error = console.error.bind(console) const wrap = (level, original) => { return (...args) => { try { appendLog(level, args) } catch (error) { // ignore logger errors } if (typeof original === 'function') { return original.apply(console, args) } } } console.log = wrap('log', nativeConsole.log) console.info = wrap('info', nativeConsole.info) console.warn = wrap('warn', nativeConsole.warn) console.error = wrap('error', nativeConsole.error) appLog('info', '[appLog] logger installed') }