You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
142 lines
4.0 KiB
142 lines
4.0 KiB
export type ConsoleLogEntry = { |
|
type: string |
|
message: string |
|
formattedParts?: Array<{ text: string; style?: string }> |
|
timestamp: number |
|
} |
|
|
|
const MAX_ENTRIES = 1000 |
|
|
|
const buffer: ConsoleLogEntry[] = [] |
|
const listeners = new Set<() => void>() |
|
let initialized = false |
|
/** Same reference between mutations so `useSyncExternalStore` does not loop (React #185). */ |
|
let snapshot: readonly ConsoleLogEntry[] = buffer |
|
|
|
function refreshSnapshot() { |
|
snapshot = buffer.length === 0 ? buffer : [...buffer] |
|
} |
|
|
|
function notifyListeners() { |
|
refreshSnapshot() |
|
for (const listener of listeners) { |
|
listener() |
|
} |
|
} |
|
|
|
function formatArgs(args: unknown[]): { message: string; formattedParts: Array<{ text: string; style?: string }> } { |
|
if (args.length > 0 && typeof args[0] === 'string' && args[0].includes('%c')) { |
|
const formatString = args[0] |
|
const parts = formatString.split(/%c/g) |
|
const formattedParts: Array<{ text: string; style?: string }> = [] |
|
|
|
for (let i = 0; i < parts.length; i++) { |
|
const text = parts[i] |
|
const style = i < args.length - 1 && typeof args[i + 1] === 'string' ? String(args[i + 1]) : undefined |
|
formattedParts.push({ text, style }) |
|
} |
|
|
|
const remainingArgs = args.slice(parts.length) |
|
if (remainingArgs.length > 0) { |
|
const remainingText = remainingArgs |
|
.map((arg) => { |
|
if (typeof arg === 'object') { |
|
try { |
|
return JSON.stringify(arg, null, 2) |
|
} catch { |
|
return String(arg) |
|
} |
|
} |
|
return String(arg) |
|
}) |
|
.join(' ') |
|
if (formattedParts.length > 0) { |
|
formattedParts[formattedParts.length - 1].text += ' ' + remainingText |
|
} else { |
|
formattedParts.push({ text: remainingText }) |
|
} |
|
} |
|
|
|
return { message: formattedParts.map((p) => p.text).join(''), formattedParts } |
|
} |
|
|
|
const message = args |
|
.map((arg) => { |
|
if (typeof arg === 'object') { |
|
try { |
|
return JSON.stringify(arg, null, 2) |
|
} catch { |
|
return String(arg) |
|
} |
|
} |
|
return String(arg) |
|
}) |
|
.join(' ') |
|
|
|
return { message, formattedParts: [{ text: message }] } |
|
} |
|
|
|
function captureLog(type: string, ...args: unknown[]) { |
|
const { message, formattedParts } = formatArgs(args) |
|
// nostr-tools emits relay NOTICE via console.debug; keep buffer useful for real diagnostics. |
|
if (message.includes('NOTICE from')) { |
|
return |
|
} |
|
buffer.push({ type, message, formattedParts, timestamp: Date.now() }) |
|
if (buffer.length > MAX_ENTRIES) { |
|
buffer.splice(0, buffer.length - MAX_ENTRIES) |
|
} |
|
notifyListeners() |
|
} |
|
|
|
/** Ring buffer of recent console output (installed at app startup). */ |
|
export function getConsoleLogBuffer(): readonly ConsoleLogEntry[] { |
|
return snapshot |
|
} |
|
|
|
export function clearConsoleLogBuffer() { |
|
buffer.length = 0 |
|
notifyListeners() |
|
} |
|
|
|
export function subscribeConsoleLogBuffer(listener: () => void): () => void { |
|
listeners.add(listener) |
|
return () => listeners.delete(listener) |
|
} |
|
|
|
/** Wrap console.* after other patches (e.g. error-suppression) so all output is retained. */ |
|
export function initConsoleLogCapture() { |
|
if (initialized || typeof window === 'undefined') return |
|
initialized = true |
|
|
|
const originalLog = console.log.bind(console) |
|
const originalError = console.error.bind(console) |
|
const originalWarn = console.warn.bind(console) |
|
const originalInfo = console.info.bind(console) |
|
const originalDebug = console.debug.bind(console) |
|
|
|
console.log = (...args: unknown[]) => { |
|
captureLog('log', ...args) |
|
originalLog(...args) |
|
} |
|
console.error = (...args: unknown[]) => { |
|
captureLog('error', ...args) |
|
originalError(...args) |
|
} |
|
console.warn = (...args: unknown[]) => { |
|
captureLog('warn', ...args) |
|
originalWarn(...args) |
|
} |
|
console.info = (...args: unknown[]) => { |
|
captureLog('info', ...args) |
|
originalInfo(...args) |
|
} |
|
console.debug = (...args: unknown[]) => { |
|
captureLog('debug', ...args) |
|
originalDebug(...args) |
|
} |
|
} |
|
|
|
if (typeof window !== 'undefined') { |
|
initConsoleLogCapture() |
|
}
|
|
|