UNPKG

4.99 kBJavaScriptView Raw
1const path = require('path')
2const util = require('util')
3const getConfigDir = require('./config/getConfigDir')
4const strip = require('strip-ansi')
5const chalk = require('chalk')
6const fs = require('fs-extra')
7const gzip = require('./gzip')
8
9const timezonedDate = formatter => {
10 let date = new Date()
11 date.setMinutes(date.getMinutes() + getOffset())
12 return formatter(date.toJSON()).split('.')[0]
13}
14// https://stackoverflow.com/a/44118363
15const isValidTimeZone = tz => {
16 try {
17 Intl.DateTimeFormat(undefined, { timeZone: tz })
18 return true
19 } catch (ex) {
20 return false
21 }
22}
23const getOffset = _ => {
24 let offset = -(new Date()).getTimezoneOffset()
25 if (global.config && config.timezone && isValidTimeZone(config.timezone)) {
26 // https://stackoverflow.com/a/36146278
27 let date = new Date()
28 let arr = date.toLocaleString('ja', { timeZone: config.timezone }).split(/[/\s:]/)
29 arr[1]--
30 return ((Date.UTC.apply(null, arr) - new Date(date).setMilliseconds(0)) / 60 / 1000) || offset
31 }
32 return offset
33}
34const getWritableDate = () => timezonedDate(d => d.replace('T', '_').replace(/:/g, '-'))
35
36const levelBadges = {
37 fatal: chalk.bgWhite.red(' FATAL '),
38 error: chalk.bgRed.white(' ERROR '),
39 warn: chalk.bgYellow.black(' WARN '),
40 info: chalk.bgBlue.black(' INFO '),
41 start: chalk.bgBlue.black(' START '),
42 success: chalk.bgGreen.black(' SUCCESS '),
43 debug: chalk.bgCyan.black(' DEBUG '),
44 trace: chalk.bgWhite.black(' TRACE ')
45}
46
47const levelArrows = {
48 fatal: chalk.white('›'),
49 error: chalk.red('›'),
50 warn: chalk.yellow('›'),
51 info: chalk.blue('›'),
52 start: chalk.green('›'),
53 success: chalk.green('›'),
54 debug: chalk.cyan('›'),
55 trace: chalk.white('›')
56}
57
58const levels = {
59 silent: -1,
60
61 fatal: 0,
62 error: 0,
63
64 warn: 1,
65
66 info: 2,
67 start: 2,
68 success: 2,
69
70 verbose: 3, // deprecated
71 debug: 3,
72
73 silly: 4, // deprecated
74 trace: 4
75}
76
77class Logger {
78 constructor (level, scope) {
79 this.level = typeof level === 'number' ? level : levels[level] || 'info'
80 if (scope) {
81 this.scope = scope
82 } else {
83 this.scopedLoggers = []
84 }
85 }
86
87 withScope (scope) {
88 if (this.scope) throw new Error('Cannot create scoped logger from a scoped logger!')
89 const logger = new Logger(this.level, scope)
90 this.scopedLoggers.push(logger)
91 return logger
92 }
93
94 setLevel (level) {
95 this.level = levels[level]
96 if (!this.scope) this.scopedLoggers.forEach(logger => logger.setLevel(level))
97 }
98
99 getScopeForLevel (level) {
100 return (this.scope || '')
101 .split(':')
102 .map(frag => chalk.reset(frag))
103 .join(` ${levelArrows[level]} `)
104 }
105
106 inspect (object, depth = 2) {
107 return util.inspect(object, { depth, colors: true, breakLength: (process.stdout.columns - 20) })
108 }
109
110 log (logLevel, msg, obj, depth) {
111 if (this.level < levels[logLevel]) return
112 if (typeof msg === 'object') {
113 msg = [
114 chalk.reset(),
115 this.inspect(msg, obj || 2)
116 ]
117 } else {
118 msg = [
119 chalk.reset(msg),
120 obj ? (
121 (typeof obj === 'string' ? '' : '\n') + this.inspect(obj, depth || 2)
122 ) : ''
123 ]
124 }
125 console.log(
126 levelBadges[logLevel],
127 chalk.gray(timezonedDate(d => d.replace('T', ' '))),
128 this.getScopeForLevel(logLevel),
129 levelArrows[logLevel],
130 ...msg
131 )
132 }
133
134 fatal () { this.log('fatal', ...arguments) }
135 error () { this.log('error', ...arguments) }
136 info () { this.log('info', ...arguments) }
137 start () { this.log('start', ...arguments) }
138 success () { this.log('success', ...arguments) }
139 warn () { this.log('warn', ...arguments) }
140 debug () { this.log('debug', ...arguments) }
141 trace () { this.log('trace', ...arguments) }
142}
143
144module.exports = Logger
145
146module.exports.gzipOldLogs = async (configPath = getConfigDir()) => {
147 let files
148 try {
149 files = await fs.readdir(path.join(configPath, 'logs'))
150 } catch (err) {
151 return
152 }
153 await files
154 .filter(file => file.endsWith('.log'))
155 .map(file => path.join(configPath, 'logs', file))
156 .reduce((promise, item) => promise.then(() => gzip(item)), Promise.resolve())
157}
158
159module.exports.inject = (configPath = getConfigDir()) => {
160 const dir = path.join(configPath, 'logs')
161 fs.ensureDirSync(dir)
162
163 const filename = getWritableDate() + '.log'
164 const logStream = fs.createWriteStream(path.join(dir, filename))
165
166 let line = ''
167
168 // https://gist.github.com/pguillory/729616/32aa9dd5b5881f6f2719db835424a7cb96dfdfd6
169 function bindWrite (write, stream) {
170 return (string, encoding, fd) => {
171 if (string === 'close\n') return logStream.end(() => process.exit(1))
172 line += string
173 if (!string.includes('\n')) return
174 write.call(process[stream], line, encoding, fd)
175 logStream.write(strip(line), encoding, fd)
176 line = ''
177 }
178 }
179 process.stdout.write = bindWrite(process.stdout.write, 'stdout')
180 process.stderr.write = bindWrite(process.stderr.write, 'stderr')
181}