1 | const ansistyles = require('ansistyles')
|
2 | const ansicolor = require('ansicolors')
|
3 | const util = require('util')
|
4 |
|
5 | const styles = Object.assign(ansistyles, ansicolor)
|
6 |
|
7 | const TRACE = 10
|
8 | const DEBUG = 20
|
9 | const INFO = 30
|
10 | const WARN = 40
|
11 | const ERROR = 50
|
12 | const FATAL = 60
|
13 |
|
14 | const levelFromName = {
|
15 | 'trace': TRACE,
|
16 | 'debug': DEBUG,
|
17 | 'info': INFO,
|
18 | 'warn': WARN,
|
19 | 'error': ERROR,
|
20 | 'fatal': FATAL
|
21 | }
|
22 |
|
23 | const colorFromLevel = {
|
24 | 10: 'brightBlack',
|
25 | 20: 'brightBlack',
|
26 | 30: 'green',
|
27 | 40: 'magenta',
|
28 | 50: 'red',
|
29 | 60: 'inverse'
|
30 | }
|
31 |
|
32 | const nameFromLevel = {}
|
33 | const upperNameFromLevel = {}
|
34 | const upperPaddedNameFromLevel = {}
|
35 |
|
36 | Object.keys(levelFromName).forEach((name) => {
|
37 | nameFromLevel[levelFromName[name]] = name
|
38 | upperNameFromLevel[levelFromName[name]] = name.toUpperCase()
|
39 | upperPaddedNameFromLevel[levelFromName[name]] = (name.length === 4 ? ' ' : '') + name.toUpperCase()
|
40 | })
|
41 |
|
42 | function isValidRecord (rec) {
|
43 | return !(rec.v === null || rec.level === null || rec.name === null || rec.hostname === null || rec.pid === null || rec.time === null || rec.msg === null)
|
44 | }
|
45 |
|
46 | function indent (s) {
|
47 | return ' ' + s.split(/\r?\n/).join('\n ')
|
48 | }
|
49 |
|
50 | function stylize (s, color) {
|
51 | if (!s) return ''
|
52 | const fn = styles[color]
|
53 | return fn ? fn(s) : s
|
54 | }
|
55 |
|
56 | class LogStream {
|
57 | constructor (locale, stdout, stderr, additionalInfoOnErrorOrAbove) {
|
58 | this.locale = locale || 'en-US'
|
59 | this.out = stdout || process.stdout
|
60 | this.error = stderr || process.stderr
|
61 | this.additionalInfoOnErrorOrAbove = additionalInfoOnErrorOrAbove || false
|
62 | }
|
63 |
|
64 | write (rec) {
|
65 | if (typeof rec !== 'object') return console.error('raw stream got a non-object record: %j', rec)
|
66 | if (!isValidRecord(rec)) return console.error('raw stream got a non-valid-object record: %j', rec)
|
67 |
|
68 | delete rec.v
|
69 |
|
70 | if (rec.level <= 40 || !this.additionalInfoOnErrorOrAbove) {
|
71 |
|
72 | delete rec.hostname
|
73 | delete rec.pid
|
74 | }
|
75 |
|
76 |
|
77 | const time = stylize(rec.time.toLocaleString(this.locale), 'brightBlack')
|
78 |
|
79 |
|
80 | const logLevel = rec.level
|
81 | const level = stylize(upperPaddedNameFromLevel[rec.level] || 'LVL' + rec.level, colorFromLevel[rec.level])
|
82 |
|
83 |
|
84 | const nameStr = rec.component ? `${rec.name}/${rec.component}` : rec.name
|
85 |
|
86 | const extras = []
|
87 | const details = []
|
88 |
|
89 |
|
90 | let onelineMsg = ''
|
91 | if (rec.msg.indexOf('\n') !== -1) {
|
92 | details.push(indent(stylize(rec.msg, 'cyan')))
|
93 | } else {
|
94 | onelineMsg = ' ' + stylize(rec.msg, 'cyan')
|
95 | }
|
96 |
|
97 |
|
98 | if (rec.err && rec.err.stack) details.push(indent(rec.err.stack))
|
99 |
|
100 |
|
101 | delete rec.time
|
102 | delete rec.level
|
103 | delete rec.component
|
104 | delete rec.name
|
105 | delete rec.msg
|
106 | delete rec.err
|
107 |
|
108 |
|
109 | const leftover = Object.keys(rec)
|
110 | for (let i = 0; i < leftover.length; i++) {
|
111 | const key = leftover[i]
|
112 | let value = rec[key]
|
113 | let stringified = false
|
114 | if (typeof (value) !== 'string') {
|
115 | value = JSON.stringify(value, null, 2)
|
116 | stringified = true
|
117 | }
|
118 | if (value.indexOf('\n') !== -1 || value.length > 50) {
|
119 | details.push(indent(key + ': ' + value))
|
120 | } else if (!stringified && (value.indexOf(' ') !== -1 ||
|
121 | value.length === 0)) {
|
122 | extras.push(key + '=' + JSON.stringify(value))
|
123 | } else {
|
124 | extras.push(key + '=' + value)
|
125 | }
|
126 | }
|
127 |
|
128 |
|
129 | const extrasStr = stylize((extras.length ? ' (' + extras.join(', ') + ')' : ''), 'brightBlack')
|
130 | const detailsStr = stylize((details.length ? details.join('\n --\n') + '\n' : ''), 'brightBlack')
|
131 |
|
132 | const output = util.format('%s %s %s:%s%s\n%s', time, level, nameStr, onelineMsg, extrasStr, detailsStr)
|
133 |
|
134 | if (logLevel <= 40) return this.out.write(output)
|
135 | return this.error.write(output)
|
136 | }
|
137 | }
|
138 |
|
139 | module.exports = LogStream
|