UNPKG

4 kBJavaScriptView Raw
1const ansistyles = require('ansistyles')
2const ansicolor = require('ansicolors')
3const util = require('util')
4
5const styles = Object.assign(ansistyles, ansicolor)
6
7const TRACE = 10
8const DEBUG = 20
9const INFO = 30
10const WARN = 40
11const ERROR = 50
12const FATAL = 60
13
14const levelFromName = {
15 'trace': TRACE,
16 'debug': DEBUG,
17 'info': INFO,
18 'warn': WARN,
19 'error': ERROR,
20 'fatal': FATAL
21}
22
23const colorFromLevel = {
24 10: 'brightBlack', // TRACE
25 20: 'brightBlack', // DEBUG
26 30: 'green', // INFO
27 40: 'magenta', // WARN
28 50: 'red', // ERROR
29 60: 'inverse' // FATAL
30}
31
32const nameFromLevel = {}
33const upperNameFromLevel = {}
34const upperPaddedNameFromLevel = {}
35
36Object.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
42function 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
46function indent (s) {
47 return ' ' + s.split(/\r?\n/).join('\n ')
48}
49
50function stylize (s, color) {
51 if (!s) return ''
52 const fn = styles[color]
53 return fn ? fn(s) : s
54}
55
56class 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 // No need if not ERROR or FATAL
72 delete rec.hostname
73 delete rec.pid
74 }
75
76 // Set time
77 const time = stylize(rec.time.toLocaleString(this.locale), 'brightBlack')
78
79 // Set level
80 const logLevel = rec.level
81 const level = stylize(upperPaddedNameFromLevel[rec.level] || 'LVL' + rec.level, colorFromLevel[rec.level])
82
83 // Set name
84 const nameStr = rec.component ? `${rec.name}/${rec.component}` : rec.name
85
86 const extras = []
87 const details = []
88
89 // Set message
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 // Add error stack to details if present
98 if (rec.err && rec.err.stack) details.push(indent(rec.err.stack))
99
100 // remove used properties
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 // Add all remaining keys in extras
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 // Create extras and details string
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
139module.exports = LogStream