| 1 | import events from './events' |
| 2 | import { formatTime, formatTimestamp } from './cross' |
| 3 | import { createWriteStream } from 'fs' |
| 4 | import { argv } from './argv' |
| 5 | |
| 6 | export const consoleLog: Array<{ ts: Date, k: string, msg: string }> = [] |
| 7 | const originalConsoleLog = console.log |
| 8 | const f = argv.consoleFile ? createWriteStream(argv.consoleFile, { flags: 'a', encoding: 'utf8' }) : null |
| 9 | let terminalOutputBroken = false |
| 10 | for (const stream of [process.stdout, process.stderr]) |
| 11 | stream.on('error', err => { |
| 12 | if (!isBrokenTerminalOutput(err)) |
| 13 | throw err |
| 14 | // after the terminal/pipe is gone, further console writes would just re-emit the same process-level error |
| 15 | terminalOutputBroken = true |
| 16 | }) |
| 17 | for (const k of ['log','warn','error','debug'] as const) { |
| 18 | const original = console[k] |
| 19 | console[k as 'log'] = (...args: any[]) => { |
| 20 | const ts = new Date() |
| 21 | if (k === 'debug') |
| 22 | args.unshift('DBG') |
| 23 | else { |
| 24 | const msg = safeJoin(args) // if args contains a symbol, join will throw |
| 25 | const rec = { ts, k, msg } |
| 26 | consoleLog.push(rec) |
| 27 | if (consoleLog.length > 100_000) // limit to avoid infinite space |
| 28 | consoleLog.splice(0, 1_000) |
| 29 | events.emit('console', rec) |
| 30 | f?.write(`${formatTimestamp(ts)} [${k}] ${msg}\n`) |
| 31 | if (k !== 'log') |
| 32 | args.unshift('!') |
| 33 | } |
| 34 | if (!terminalOutputBroken) { |
| 35 | try { return original(formatTime(ts), ...args) } // bundled nodejs doesn't have locales (and apparently uses en-US) |
| 36 | catch (err) { |
| 37 | if (!isBrokenTerminalOutput(err)) |
| 38 | throw err |
| 39 | terminalOutputBroken = true |
| 40 | } |
| 41 | } |
| 42 | } |
| 43 | Object.assign(console[k], { original }) |
| 44 | } |
| 45 | |
| 46 | const over = console.log |
| 47 | for (const k of ['table'] as const) { |
| 48 | const original = console[k] |
| 49 | console[k] = (...args: any[]) => { |
| 50 | console.log = originalConsoleLog |
| 51 | // @ts-ignore |
| 52 | try { return original(...args) } |
| 53 | finally { console.log = over } |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | function safeJoin(a: unknown[]): string { |
| 58 | try { return a.join(' ') } |
| 59 | catch { |
| 60 | return a.map(x => { |
| 61 | if (x == null) |
| 62 | return '' |
| 63 | try { return String(x) } |
| 64 | catch { |
| 65 | if (Array.isArray(x)) |
| 66 | return `[${safeJoin(x)}]` |
| 67 | try { return JSON.stringify(x) } |
| 68 | catch { return 'N/A' } |
| 69 | } |
| 70 | }).join(' ') |
| 71 | } |
| 72 | } |
| 73 | |
| 74 | function isBrokenTerminalOutput(err: unknown) { |
| 75 | const code = (err as NodeJS.ErrnoException)?.code |
| 76 | return code === 'EPIPE' || code === 'EIO' |
| 77 | || code === 'ERR_STREAM_DESTROYED' || code === 'ERR_STREAM_WRITE_AFTER_END' |
| 78 | } |
| 79 | |
| 80 | export function consoleHint(msg: string) { |
| 81 | console.log("HINT: "+ msg) |
| 82 | } |