main
ts 82 lines 2.77 KB
Raw
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 }