@innei/pretty-logger-core 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,409 @@
1
+ import { defu } from 'defu'
2
+ import type { LogLevel, LogType } from './constants'
3
+ import type {
4
+ ConsolaOptions,
5
+ ConsolaReporter,
6
+ InputLogObject,
7
+ LogObject,
8
+ } from './types'
9
+
10
+ import { LogTypes } from './constants'
11
+ import { isLogObj } from './utils/log'
12
+
13
+ let paused = false
14
+ const queue: any[] = []
15
+
16
+ export class Consola {
17
+ options: ConsolaOptions
18
+
19
+ _lastLog: {
20
+ serialized?: string
21
+ object?: LogObject
22
+ count?: number
23
+ time?: Date
24
+ timeout?: ReturnType<typeof setTimeout>
25
+ }
26
+
27
+ _mockFn?: ConsolaOptions['mockFn']
28
+
29
+ constructor(options: Partial<ConsolaOptions> = {}) {
30
+ // Options
31
+ const types = options.types || LogTypes
32
+ this.options = defu(
33
+ <ConsolaOptions>{
34
+ ...options,
35
+ defaults: { ...options.defaults },
36
+ level: _normalizeLogLevel(options.level, types),
37
+ reporters: [...(options.reporters || [])],
38
+ },
39
+ <Partial<ConsolaOptions>>{
40
+ types: LogTypes,
41
+ throttle: 1000,
42
+ throttleMin: 5,
43
+ formatOptions: {
44
+ date: true,
45
+ colors: false,
46
+ compact: true,
47
+ },
48
+ },
49
+ )
50
+
51
+ // Create logger functions for current instance
52
+ for (const type in types) {
53
+ const defaults: InputLogObject = {
54
+ type: type as LogType,
55
+ ...this.options.defaults,
56
+ ...types[type as LogType],
57
+ }
58
+ // @ts-expect-error
59
+ ;(this as unknown as ConsolaInstance)[type as LogType] =
60
+ this._wrapLogFn(defaults)
61
+ // @ts-ignore
62
+ ;(this as unknown as ConsolaInstance)[type].raw = this._wrapLogFn(
63
+ defaults,
64
+ true,
65
+ )
66
+ }
67
+
68
+ // Use _mockFn if is set
69
+ if (this.options.mockFn) {
70
+ this.mockTypes()
71
+ }
72
+
73
+ // Track of last log
74
+ this._lastLog = {}
75
+ }
76
+
77
+ get level() {
78
+ return this.options.level
79
+ }
80
+
81
+ set level(level) {
82
+ this.options.level = _normalizeLogLevel(
83
+ level,
84
+ this.options.types,
85
+ this.options.level,
86
+ )
87
+ }
88
+
89
+ create(options: Partial<ConsolaOptions>): ConsolaInstance {
90
+ const instance = new Consola({
91
+ ...this.options,
92
+ ...options,
93
+ }) as ConsolaInstance
94
+
95
+ if (this._mockFn) {
96
+ instance.mockTypes(this._mockFn)
97
+ }
98
+
99
+ return instance
100
+ }
101
+
102
+ withDefaults(defaults: InputLogObject): ConsolaInstance {
103
+ return this.create({
104
+ ...this.options,
105
+ defaults: {
106
+ ...this.options.defaults,
107
+ ...defaults,
108
+ },
109
+ })
110
+ }
111
+
112
+ withTag(tag: string): ConsolaInstance {
113
+ return this.withDefaults({
114
+ tag: this.options.defaults.tag
115
+ ? `${this.options.defaults.tag}:${tag}`
116
+ : tag,
117
+ })
118
+ }
119
+
120
+ addReporter(reporter: ConsolaReporter) {
121
+ this.options.reporters.push(reporter)
122
+ return this
123
+ }
124
+
125
+ removeReporter(reporter: ConsolaReporter) {
126
+ if (reporter) {
127
+ const i = this.options.reporters.indexOf(reporter)
128
+ if (i >= 0) {
129
+ return this.options.reporters.splice(i, 1)
130
+ }
131
+ } else {
132
+ this.options.reporters.splice(0)
133
+ }
134
+ return this
135
+ }
136
+
137
+ setReporters(reporters: ConsolaReporter[]) {
138
+ this.options.reporters = Array.isArray(reporters) ? reporters : [reporters]
139
+ return this
140
+ }
141
+
142
+ wrapAll() {
143
+ this.wrapConsole()
144
+ this.wrapStd()
145
+ }
146
+
147
+ restoreAll() {
148
+ this.restoreConsole()
149
+ this.restoreStd()
150
+ }
151
+
152
+ wrapConsole() {
153
+ for (const type in this.options.types) {
154
+ // Backup original value
155
+ if (!(console as any)[`__${type}`]) {
156
+ // eslint-disable-line no-console
157
+ ;(console as any)[`__${type}`] = (console as any)[type] // eslint-disable-line no-console
158
+ }
159
+ // Override
160
+ ;(console as any)[type] = (this as unknown as ConsolaInstance)[
161
+ type as LogType
162
+ ].raw // eslint-disable-line no-console
163
+ }
164
+ }
165
+
166
+ restoreConsole() {
167
+ for (const type in this.options.types) {
168
+ // Restore if backup is available
169
+ if ((console as any)[`__${type}`]) {
170
+ // eslint-disable-line no-console
171
+ ;(console as any)[type] = (console as any)[`__${type}`] // eslint-disable-line no-console
172
+ delete (console as any)[`__${type}`] // eslint-disable-line no-console
173
+ }
174
+ }
175
+ }
176
+
177
+ wrapStd() {
178
+ this._wrapStream(this.options.stdout, 'log')
179
+ this._wrapStream(this.options.stderr, 'log')
180
+ }
181
+
182
+ _wrapStream(stream: NodeJS.WriteStream | undefined, type: LogType) {
183
+ if (!stream) {
184
+ return
185
+ }
186
+
187
+ // Backup original value
188
+ if (!(stream as any).__write) {
189
+ ;(stream as any).__write = stream.write
190
+ }
191
+
192
+ // Override
193
+ ;(stream as any).write = (data: any) => {
194
+ ;(this as unknown as ConsolaInstance)[type].raw(String(data).trim())
195
+ }
196
+ }
197
+
198
+ restoreStd() {
199
+ this._restoreStream(this.options.stdout)
200
+ this._restoreStream(this.options.stderr)
201
+ }
202
+
203
+ _restoreStream(stream?: NodeJS.WriteStream) {
204
+ if (!stream) {
205
+ return
206
+ }
207
+
208
+ if ((stream as any).__write) {
209
+ stream.write = (stream as any).__write
210
+ delete (stream as any).__write
211
+ }
212
+ }
213
+
214
+ pauseLogs() {
215
+ paused = true
216
+ }
217
+
218
+ resumeLogs() {
219
+ paused = false
220
+
221
+ // Process queue
222
+ const _queue = queue.splice(0)
223
+ for (const item of _queue) {
224
+ item[0]._logFn(item[1], item[2])
225
+ }
226
+ }
227
+
228
+ mockTypes(mockFn?: ConsolaOptions['mockFn']) {
229
+ const _mockFn = mockFn || this.options.mockFn
230
+
231
+ this._mockFn = _mockFn
232
+
233
+ if (typeof _mockFn !== 'function') {
234
+ return
235
+ }
236
+
237
+ for (const type in this.options.types) {
238
+ // @ts-expect-error
239
+ ;(this as unknown as ConsolaInstance)[type as LogType] =
240
+ _mockFn(type as LogType, this.options.types[type as LogType]) ||
241
+ (this as unknown as ConsolaInstance)[type as LogType]
242
+ ;(this as unknown as ConsolaInstance)[type as LogType].raw = (
243
+ this as unknown as ConsolaInstance
244
+ )[type as LogType]
245
+ }
246
+ }
247
+
248
+ _wrapLogFn(defaults: InputLogObject, isRaw?: boolean) {
249
+ return (...args: any[]) => {
250
+ if (paused) {
251
+ queue.push([this, defaults, args, isRaw])
252
+ return
253
+ }
254
+ return this._logFn(defaults, args, isRaw)
255
+ }
256
+ }
257
+
258
+ _logFn(defaults: InputLogObject, args: any[], isRaw?: boolean) {
259
+ if (((defaults.level as number) || 0) > this.level) {
260
+ return false
261
+ }
262
+
263
+ // Construct a new log object
264
+ const logObj: Partial<LogObject> = {
265
+ date: new Date(),
266
+ args: [],
267
+ ...defaults,
268
+ level: _normalizeLogLevel(defaults.level, this.options.types),
269
+ }
270
+
271
+ // Consume arguments
272
+ if (!isRaw && args.length === 1 && isLogObj(args[0])) {
273
+ Object.assign(logObj, args[0])
274
+ } else {
275
+ logObj.args = [...args]
276
+ }
277
+
278
+ // Aliases
279
+ if (logObj.message) {
280
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
281
+ logObj.args!.unshift(logObj.message)
282
+ delete logObj.message
283
+ }
284
+ if (logObj.additional) {
285
+ if (!Array.isArray(logObj.additional)) {
286
+ logObj.additional = logObj.additional.split('\n')
287
+ }
288
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
289
+ logObj.args!.push(`\n${logObj.additional.join('\n')}`)
290
+ delete logObj.additional
291
+ }
292
+
293
+ // Normalize type to lowercase
294
+ logObj.type = (
295
+ typeof logObj.type === 'string' ? logObj.type.toLowerCase() : 'log'
296
+ ) as LogType
297
+ logObj.tag = typeof logObj.tag === 'string' ? logObj.tag : ''
298
+
299
+ // Resolve log
300
+ /**
301
+ * @param newLog false if the throttle expired and
302
+ * we don't want to log a duplicate
303
+ */
304
+ const resolveLog = (newLog = false) => {
305
+ const repeated = (this._lastLog.count || 0) - this.options.throttleMin
306
+ if (this._lastLog.object && repeated > 0) {
307
+ const args = [...this._lastLog.object.args]
308
+ if (repeated > 1) {
309
+ args.push(`(repeated ${repeated} times)`)
310
+ }
311
+ this._log({ ...this._lastLog.object, args })
312
+ this._lastLog.count = 1
313
+ }
314
+
315
+ // Log
316
+ if (newLog) {
317
+ this._lastLog.object = logObj as LogObject
318
+ this._log(logObj as LogObject)
319
+ }
320
+ }
321
+
322
+ // Throttle
323
+ clearTimeout(this._lastLog.timeout)
324
+ const diffTime =
325
+ this._lastLog.time && logObj.date
326
+ ? logObj.date.getTime() - this._lastLog.time.getTime()
327
+ : 0
328
+ this._lastLog.time = logObj.date
329
+ if (diffTime < this.options.throttle) {
330
+ try {
331
+ const serializedLog = JSON.stringify([
332
+ logObj.type,
333
+ logObj.tag,
334
+ logObj.args,
335
+ ])
336
+ const isSameLog = this._lastLog.serialized === serializedLog
337
+ this._lastLog.serialized = serializedLog
338
+ if (isSameLog) {
339
+ this._lastLog.count = (this._lastLog.count || 0) + 1
340
+ if (this._lastLog.count > this.options.throttleMin) {
341
+ // Auto-resolve when throttle is timed out
342
+ this._lastLog.timeout = setTimeout(
343
+ resolveLog,
344
+ this.options.throttle,
345
+ )
346
+ return // SPAM!
347
+ }
348
+ }
349
+ } catch {
350
+ // Circular References
351
+ }
352
+ }
353
+
354
+ resolveLog(true)
355
+ }
356
+
357
+ _log(logObj: LogObject) {
358
+ for (const reporter of this.options.reporters) {
359
+ reporter.log(logObj, {
360
+ options: this.options,
361
+ })
362
+ }
363
+ }
364
+ }
365
+
366
+ function _normalizeLogLevel(
367
+ input: LogLevel | LogType | undefined,
368
+ types: any = {},
369
+ defaultLevel = 3,
370
+ ) {
371
+ if (input === undefined) {
372
+ return defaultLevel
373
+ }
374
+ if (typeof input === 'number') {
375
+ return input
376
+ }
377
+ if (types[input] && types[input].level !== undefined) {
378
+ return types[input].level
379
+ }
380
+ return defaultLevel
381
+ }
382
+
383
+ export interface LogFn {
384
+ (message: InputLogObject | any, ...args: any[]): void
385
+ raw: (...args: any[]) => void
386
+ }
387
+ export type ConsolaInstance = Consola & Record<LogType, LogFn>
388
+
389
+ // Legacy support
390
+ // @ts-expect-error
391
+ Consola.prototype.add = Consola.prototype.addReporter
392
+ // @ts-expect-error
393
+ Consola.prototype.remove = Consola.prototype.removeReporter
394
+ // @ts-expect-error
395
+ Consola.prototype.clear = Consola.prototype.removeReporter
396
+ // @ts-expect-error
397
+ Consola.prototype.withScope = Consola.prototype.withTag
398
+ // @ts-expect-error
399
+ Consola.prototype.mock = Consola.prototype.mockTypes
400
+ // @ts-expect-error
401
+ Consola.prototype.pause = Consola.prototype.pauseLogs
402
+ // @ts-expect-error
403
+ Consola.prototype.resume = Consola.prototype.resumeLogs
404
+
405
+ export function createConsola(
406
+ options: Partial<ConsolaOptions> = {},
407
+ ): ConsolaInstance {
408
+ return new Consola(options) as ConsolaInstance
409
+ }
@@ -0,0 +1,109 @@
1
+ import type { LogObject } from './types'
2
+
3
+ // eslint-disable-next-line @typescript-eslint/ban-types
4
+ export type LogLevel = 0 | 1 | 2 | 3 | 4 | 5 | (number & {})
5
+
6
+ export const LogLevels: Record<LogType, number> = {
7
+ silent: Number.NEGATIVE_INFINITY,
8
+
9
+ fatal: 0,
10
+ error: 0,
11
+
12
+ warn: 1,
13
+
14
+ log: 2,
15
+ info: 3,
16
+
17
+ success: 3,
18
+ fail: 3,
19
+ ready: 3,
20
+ start: 3,
21
+ box: 3,
22
+
23
+ debug: 4,
24
+
25
+ trace: 5,
26
+
27
+ verbose: Number.POSITIVE_INFINITY,
28
+ }
29
+
30
+ export type LogType =
31
+ // 0
32
+ | 'silent'
33
+ | 'fatal'
34
+ | 'error'
35
+ // 1
36
+ | 'warn'
37
+ // 2
38
+ | 'log'
39
+ // 3
40
+ | 'info'
41
+ | 'success'
42
+ | 'fail'
43
+ | 'ready'
44
+ | 'start'
45
+ | 'box'
46
+ // Verbose
47
+ | 'debug'
48
+ | 'trace'
49
+ | 'verbose'
50
+
51
+ export const LogTypes: Record<LogType, Partial<LogObject>> = {
52
+ // Silent
53
+ silent: {
54
+ level: -1,
55
+ },
56
+
57
+ // Level 0
58
+ fatal: {
59
+ level: LogLevels.fatal,
60
+ },
61
+ error: {
62
+ level: LogLevels.error,
63
+ },
64
+
65
+ // Level 1
66
+ warn: {
67
+ level: LogLevels.warn,
68
+ },
69
+
70
+ // Level 2
71
+ log: {
72
+ level: LogLevels.log,
73
+ },
74
+
75
+ // Level 3
76
+ info: {
77
+ level: LogLevels.info,
78
+ },
79
+ success: {
80
+ level: LogLevels.success,
81
+ },
82
+ fail: {
83
+ level: LogLevels.fail,
84
+ },
85
+ ready: {
86
+ level: LogLevels.info,
87
+ },
88
+ start: {
89
+ level: LogLevels.info,
90
+ },
91
+ box: {
92
+ level: LogLevels.info,
93
+ },
94
+
95
+ // Level 4
96
+ debug: {
97
+ level: LogLevels.debug,
98
+ },
99
+
100
+ // Level 5
101
+ trace: {
102
+ level: LogLevels.trace,
103
+ },
104
+
105
+ // Verbose
106
+ verbose: {
107
+ level: LogLevels.verbose,
108
+ },
109
+ }
@@ -0,0 +1,2 @@
1
+ export { createConsola } from './consola'
2
+ export * from './shared'
@@ -0,0 +1,50 @@
1
+ import { isCI, isDebug, isTest } from 'std-env'
2
+ import type { ConsolaInstance } from './consola'
3
+ import type { LogLevel } from './constants'
4
+ import type { ConsolaOptions } from './types'
5
+
6
+ import { createConsola as _createConsola } from './consola'
7
+ import { LogLevels } from './constants'
8
+ import { BasicReporter } from './reporters/basic'
9
+ import { FancyReporter } from './reporters/fancy'
10
+
11
+ export * from './shared'
12
+
13
+ export function createConsola(
14
+ options: Partial<ConsolaOptions & { fancy: boolean }> = {},
15
+ ): ConsolaInstance {
16
+ // Log level
17
+ let level = _getDefaultLogLevel()
18
+ if (process.env.CONSOLA_LEVEL) {
19
+ level = Number.parseInt(process.env.CONSOLA_LEVEL) ?? level
20
+ }
21
+
22
+ // Create new consola instance
23
+ const consola = _createConsola({
24
+ level: level as LogLevel,
25
+ defaults: { level },
26
+ stdout: process.stdout,
27
+ stderr: process.stderr,
28
+
29
+ reporters: options.reporters || [
30
+ options.fancy ?? !(isCI || isTest)
31
+ ? new FancyReporter()
32
+ : new BasicReporter(),
33
+ ],
34
+ ...options,
35
+ })
36
+
37
+ return consola
38
+ }
39
+
40
+ function _getDefaultLogLevel() {
41
+ if (isDebug) {
42
+ return LogLevels.debug
43
+ }
44
+ if (isTest) {
45
+ return LogLevels.warn
46
+ }
47
+ return LogLevels.info
48
+ }
49
+
50
+ export const consola = createConsola()
@@ -0,0 +1,74 @@
1
+ import { formatWithOptions } from 'node:util'
2
+ import type {
3
+ ConsolaOptions,
4
+ ConsolaReporter,
5
+ FormatOptions,
6
+ LogObject,
7
+ } from '../types'
8
+
9
+ import { parseStack } from '../utils/error'
10
+ import { writeStream } from '../utils/stream'
11
+
12
+ const bracket = (x: string) => (x ? `[${x}]` : '')
13
+
14
+ export class BasicReporter implements ConsolaReporter {
15
+ formatStack(stack: string, opts: FormatOptions) {
16
+ return ` ${parseStack(stack).join('\n ')}`
17
+ }
18
+
19
+ formatArgs(args: any[], opts: FormatOptions) {
20
+ const _args = args.map((arg) => {
21
+ if (arg && typeof arg.stack === 'string') {
22
+ return `${arg.message}\n${this.formatStack(arg.stack, opts)}`
23
+ }
24
+ return arg
25
+ })
26
+
27
+ // Only supported with Node >= 10
28
+ // https://nodejs.org/api/util.html#util_util_inspect_object_options
29
+ return formatWithOptions(opts, ..._args)
30
+ }
31
+
32
+ formatDate(date: Date, opts: FormatOptions) {
33
+ return opts.date ? date.toLocaleTimeString() : ''
34
+ }
35
+
36
+ filterAndJoin(arr: any[]) {
37
+ return arr.filter(Boolean).join(' ')
38
+ }
39
+
40
+ formatLogObj(logObj: LogObject, opts: FormatOptions) {
41
+ const message = this.formatArgs(logObj.args, opts)
42
+
43
+ if (logObj.type === 'box') {
44
+ return `\n${[
45
+ bracket(logObj.tag),
46
+ logObj.title && logObj.title,
47
+ ...message.split('\n'),
48
+ ]
49
+ .filter(Boolean)
50
+ .map((l) => ` > ${l}`)
51
+ .join('\n')}\n`
52
+ }
53
+
54
+ return this.filterAndJoin([
55
+ bracket(logObj.type),
56
+ bracket(logObj.tag),
57
+ message,
58
+ ])
59
+ }
60
+
61
+ log(logObj: LogObject, ctx: { options: ConsolaOptions }) {
62
+ const line = this.formatLogObj(logObj, {
63
+ columns: (ctx.options.stdout as any).columns || 0,
64
+ ...ctx.options.formatOptions,
65
+ })
66
+
67
+ return writeStream(
68
+ `${line}\n`,
69
+ logObj.level < 2
70
+ ? ctx.options.stderr || process.stderr
71
+ : ctx.options.stdout || process.stdout,
72
+ )
73
+ }
74
+ }
@@ -0,0 +1,70 @@
1
+ import type { LogObject } from '../types'
2
+
3
+ export class BrowserReporter {
4
+ options: any
5
+ defaultColor: string
6
+ levelColorMap: Record<number, string>
7
+ typeColorMap: Record<string, string>
8
+
9
+ constructor(options: any) {
10
+ this.options = { ...options }
11
+
12
+ this.defaultColor = '#7f8c8d' // Gray
13
+ this.levelColorMap = {
14
+ 0: '#c0392b', // Red
15
+ 1: '#f39c12', // Yellow
16
+ 3: '#00BCD4', // Cyan
17
+ }
18
+ this.typeColorMap = {
19
+ success: '#2ecc71', // Green
20
+ }
21
+ }
22
+
23
+ _getLogFn(level: number) {
24
+ if (level < 1) {
25
+ return (console as any).__error || console.error
26
+ }
27
+ if (level === 1) {
28
+ return (console as any).__warn || console.warn
29
+ }
30
+ return (console as any).__log || console.log
31
+ }
32
+
33
+ log(logObj: LogObject) {
34
+ const consoleLogFn = this._getLogFn(logObj.level)
35
+
36
+ // Type
37
+ const type = logObj.type === 'log' ? '' : logObj.type
38
+
39
+ // Tag
40
+ const tag = logObj.tag || ''
41
+
42
+ // Styles
43
+ const color =
44
+ this.typeColorMap[logObj.type] ||
45
+ this.levelColorMap[logObj.level] ||
46
+ this.defaultColor
47
+ const style = `
48
+ background: ${color};
49
+ border-radius: 0.5em;
50
+ color: white;
51
+ font-weight: bold;
52
+ padding: 2px 0.5em;
53
+ `
54
+
55
+ const badge = `%c${[tag, type].filter(Boolean).join(':')}`
56
+
57
+ // Log to the console
58
+ if (typeof logObj.args[0] === 'string') {
59
+ consoleLogFn(
60
+ `${badge}%c ${logObj.args[0]}`,
61
+ style,
62
+ // Empty string as style resets to default console style
63
+ '',
64
+ ...logObj.args.slice(1),
65
+ )
66
+ } else {
67
+ consoleLogFn(badge, style, ...logObj.args)
68
+ }
69
+ }
70
+ }