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

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()
}