@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,152 @@
1
+ import _stringWidth from 'string-width'
2
+ import type { LogLevel, LogType } from '../constants'
3
+ import type { FormatOptions, LogObject } from '../types'
4
+ import type { BoxOpts } from '../utils/box'
5
+
6
+ import { stripAnsi } from '../utils'
7
+ import { box } from '../utils/box'
8
+ import { colors } from '../utils/color'
9
+ import { parseStack } from '../utils/error'
10
+ import { isUnicodeSupported } from '../utils/tester'
11
+ import { BasicReporter } from './basic'
12
+
13
+ export const TYPE_COLOR_MAP: { [k in LogType]?: string } = {
14
+ info: 'cyan',
15
+ fail: 'red',
16
+ success: 'green',
17
+ ready: 'green',
18
+ start: 'magenta',
19
+ }
20
+
21
+ export const LEVEL_COLOR_MAP: { [k in LogLevel]?: string } = {
22
+ 0: 'red',
23
+ 1: 'yellow',
24
+ }
25
+
26
+ const unicode = isUnicodeSupported()
27
+ const s = (c: string, fallback: string) => (unicode ? c : fallback)
28
+ const TYPE_ICONS: { [k in LogType]?: string } = {
29
+ error: s('✖', '×'),
30
+ fatal: s('✖', '×'),
31
+ ready: s('✔', '√'),
32
+ warn: s('⚠', '‼'),
33
+ info: s('ℹ', 'i'),
34
+ success: s('✔', '√'),
35
+ debug: s('⚙', 'D'),
36
+ trace: s('→', '→'),
37
+ fail: s('✖', '×'),
38
+ start: s('◐', 'o'),
39
+ log: '',
40
+ }
41
+
42
+ function stringWidth(str: string) {
43
+ // https://github.com/unjs/consola/issues/204
44
+ if (!(Intl as any).Segmenter) {
45
+ return stripAnsi(str).length
46
+ }
47
+ return _stringWidth(str)
48
+ }
49
+
50
+ export class FancyReporter extends BasicReporter {
51
+ formatStack(stack: string) {
52
+ return `\n${parseStack(stack)
53
+ .map(
54
+ (line) =>
55
+ ` ${line
56
+ .replace(/^at +/, (m) => colors.gray(m))
57
+ .replace(/\((.+)\)/, (_, m) => `(${colors.cyan(m)})`)}`,
58
+ )
59
+ .join('\n')}`
60
+ }
61
+
62
+ formatType(logObj: LogObject, isBadge: boolean, opts: FormatOptions) {
63
+ const typeColor =
64
+ (TYPE_COLOR_MAP as any)[logObj.type] ||
65
+ (LEVEL_COLOR_MAP as any)[logObj.level] ||
66
+ 'gray'
67
+
68
+ if (isBadge) {
69
+ return getBgColor(typeColor)(
70
+ colors.black(` ${logObj.type.toUpperCase()} `),
71
+ )
72
+ }
73
+
74
+ const _type =
75
+ typeof (TYPE_ICONS as any)[logObj.type] === 'string'
76
+ ? (TYPE_ICONS as any)[logObj.type]
77
+ : (logObj as any).icon || logObj.type
78
+
79
+ return _type ? getColor(typeColor)(_type) : ''
80
+ }
81
+
82
+ formatLogObj(logObj: LogObject, opts: FormatOptions) {
83
+ const [message, ...additional] = this.formatArgs(logObj.args, opts).split(
84
+ '\n',
85
+ )
86
+
87
+ if (logObj.type === 'box') {
88
+ return box(
89
+ characterFormat(
90
+ message + (additional.length > 0 ? `\n${additional.join('\n')}` : ''),
91
+ ),
92
+ {
93
+ title: logObj.title
94
+ ? characterFormat(logObj.title as string)
95
+ : undefined,
96
+ style: logObj.style as BoxOpts['style'],
97
+ },
98
+ )
99
+ }
100
+
101
+ const date = this.formatDate(logObj.date, opts)
102
+ const coloredDate = date && colors.gray(date)
103
+
104
+ const isBadge = (logObj.badge as boolean) ?? logObj.level < 2
105
+ const type = this.formatType(logObj, isBadge, opts)
106
+
107
+ const tag = logObj.tag ? colors.gray(logObj.tag) : ''
108
+
109
+ let line
110
+ const left = this.filterAndJoin([type, characterFormat(message)])
111
+ const right = this.filterAndJoin(opts.columns ? [tag, coloredDate] : [tag])
112
+ const space =
113
+ (opts.columns || 0) - stringWidth(left) - stringWidth(right) - 2
114
+
115
+ line =
116
+ space > 0 && (opts.columns || 0) >= 80
117
+ ? left + ' '.repeat(space) + right
118
+ : (right ? `${colors.gray(`[${right}]`)} ` : '') + left
119
+
120
+ line += characterFormat(
121
+ additional.length > 0 ? `\n${additional.join('\n')}` : '',
122
+ )
123
+
124
+ if (logObj.type === 'trace') {
125
+ const _err = new Error(`Trace: ${logObj.message}`)
126
+ line += this.formatStack(_err.stack || '')
127
+ }
128
+
129
+ return isBadge ? `\n${line}\n` : line
130
+ }
131
+ }
132
+
133
+ function characterFormat(str: string) {
134
+ return (
135
+ str
136
+ // highlight backticks
137
+ .replace(/`([^`]+)`/gm, (_, m) => colors.cyan(m))
138
+ // underline underscores
139
+ .replace(/\s+_([^_]+)_\s+/gm, (_, m) => ` ${colors.underline(m)} `)
140
+ )
141
+ }
142
+
143
+ function getColor(color = 'white') {
144
+ return (colors as any)[color] || colors.white
145
+ }
146
+
147
+ function getBgColor(color = 'bgWhite') {
148
+ return (
149
+ (colors as any)[`bg${color[0].toUpperCase()}${color.slice(1)}`] ||
150
+ colors.bgWhite
151
+ )
152
+ }
@@ -0,0 +1,126 @@
1
+ import { createWriteStream } from 'fs'
2
+ import * as fs from 'fs'
3
+ import { dirname } from 'path'
4
+ import { CronJob } from 'cron'
5
+ import type { WriteStream } from 'fs'
6
+ import type { ConsolaOptions, LogObject } from '../types'
7
+
8
+ import { getLogFilePath } from '../../tool.util'
9
+ import { writeStream } from '../utils/stream'
10
+ import { LoggerReporter } from './logger'
11
+
12
+ export interface FileReporterConfig {
13
+ loggerDir: string
14
+ /**
15
+ * @default 'stdout_%d%.log'
16
+ */
17
+ stdoutFileFormat?: string
18
+ /**
19
+ * @default 'error.log'
20
+ */
21
+ stderrFileFormat?: string
22
+
23
+ /**
24
+ * refresh logger file stream
25
+ * @default '0 0 * * *'
26
+ */
27
+ cron?: string
28
+
29
+ /**
30
+ * Error log will be written to stdout and stderr
31
+ * @default false
32
+ */
33
+ errWriteToStdout?: boolean
34
+ }
35
+
36
+ export class FileReporter extends LoggerReporter {
37
+ constructor(private readonly configs: FileReporterConfig) {
38
+ super()
39
+ this.refreshWriteStream()
40
+
41
+ this.scheduleRefreshWriteStream()
42
+ }
43
+
44
+ private stdoutStream?: WriteStream
45
+ private stderrStream?: WriteStream
46
+
47
+ private __job?: CronJob
48
+
49
+ private scheduleRefreshWriteStream() {
50
+ const { cron = '0 0 * * *' } = this.configs
51
+ const job = new CronJob(cron, this.refreshWriteStream.bind(this))
52
+ job.start()
53
+
54
+ this.__job = job
55
+ }
56
+
57
+ teardown() {
58
+ this.__job?.stop()
59
+ this.stdoutStream?.end()
60
+ this.stderrStream?.end()
61
+ }
62
+
63
+ private refreshWriteStream() {
64
+ const {
65
+ loggerDir,
66
+ stderrFileFormat = 'error.log',
67
+ stdoutFileFormat = 'stdout_%d.log',
68
+ } = this.configs
69
+
70
+ const stdoutPath = getLogFilePath(loggerDir, stdoutFileFormat)
71
+ const stderrPath = getLogFilePath(loggerDir, stderrFileFormat)
72
+
73
+ createLoggerFileIfNotExist(stdoutPath)
74
+ createLoggerFileIfNotExist(stderrPath)
75
+
76
+ const options = {
77
+ encoding: 'utf-8',
78
+ flags: 'a+',
79
+ } as const
80
+
81
+ ;[this.stderrStream, this.stdoutStream].forEach((stream) => {
82
+ stream?.end()
83
+ })
84
+
85
+ this.stdoutStream = createWriteStream(stdoutPath, options)
86
+ this.stderrStream = createWriteStream(stderrPath, options)
87
+ ;[this.stderrStream, this.stdoutStream].forEach((stream) => {
88
+ writeStream(
89
+ '\n========================================================\n',
90
+ stream,
91
+ )
92
+ })
93
+ }
94
+
95
+ log(logObj: LogObject, ctx: { options: ConsolaOptions }) {
96
+ if (!this.stdoutStream || !this.stderrStream) {
97
+ return
98
+ }
99
+ const finalStdout = this.stdoutStream
100
+ const finalStderr = this.stderrStream || this.stdoutStream
101
+ const line = super.formatLogObj(logObj, {
102
+ ...ctx.options.formatOptions,
103
+ columns: undefined,
104
+ })
105
+
106
+ if (this.configs.errWriteToStdout && logObj.level < 2) {
107
+ writeStream(`${line}\n`, finalStdout)
108
+ }
109
+ return writeStream(
110
+ `${line}\n`,
111
+ logObj.level < 2 ? finalStderr : finalStdout,
112
+ )
113
+ }
114
+ }
115
+
116
+ const createLoggerFileIfNotExist = (path: string) => {
117
+ const dirPath = dirname(path)
118
+
119
+ if (!fs.existsSync(dirPath)) {
120
+ fs.mkdirSync(dirPath, { recursive: true })
121
+ }
122
+
123
+ if (!fs.existsSync(path)) {
124
+ fs.writeFileSync(path, '', { flag: 'wx' })
125
+ }
126
+ }
@@ -0,0 +1,6 @@
1
+ export * from './basic'
2
+ export * from './browser'
3
+ export * from './fancy'
4
+ export * from './file'
5
+ export * from './logger'
6
+ export * from './subscriber'
@@ -0,0 +1,31 @@
1
+ /* eslint-disable prefer-rest-params */
2
+ import picocolors from 'picocolors'
3
+ import { isDevelopment } from 'std-env'
4
+ import type { FormatOptions, LogObject } from '../types'
5
+
6
+ import { getShortTime } from '../../tool.util'
7
+ import { FancyReporter } from './fancy'
8
+
9
+ export class LoggerReporter extends FancyReporter {
10
+ private latestLogTime: number = Date.now()
11
+ public formatDate(date: Date, opts: FormatOptions): string {
12
+ const isInVirtualTerminal = typeof opts.columns === 'undefined'
13
+ if (isDevelopment) {
14
+ const now = Date.now()
15
+ const delta = now - this.latestLogTime
16
+ this.latestLogTime = now
17
+ return `+${delta | 0}ms ${super.formatDate(date, opts)}`
18
+ }
19
+
20
+ return isInVirtualTerminal ? '' : super.formatDate(date, opts)
21
+ }
22
+
23
+ public formatLogObj(logObj: LogObject, opts: FormatOptions): string {
24
+ const isInVirtualTerminal = typeof opts.columns === 'undefined'
25
+ return isInVirtualTerminal
26
+ ? `${picocolors.gray(getShortTime(new Date()))} ${super
27
+ .formatLogObj(logObj, opts)
28
+ .replace(/^\n/, '')}`.trimEnd()
29
+ : super.formatLogObj(logObj, opts)
30
+ }
31
+ }
@@ -0,0 +1,30 @@
1
+ import EventEmitter from 'events'
2
+ import type { ConsolaInstance } from '../consola'
3
+ import type { ConsolaOptions, LogObject, WrappedConsola } from '../types'
4
+
5
+ import { LoggerReporter } from './logger'
6
+
7
+ export const wrapperSubscribers = (
8
+ consola: ConsolaInstance,
9
+ ): WrappedConsola => {
10
+ Object.assign(consola, {
11
+ onData: (handler: (data: string) => any) =>
12
+ SubscriberReporter.subscriber.on('log', handler),
13
+ onStdOut: (handler: (data: string) => any) =>
14
+ SubscriberReporter.subscriber.on('stdout', handler),
15
+ onStdErr: (handler: (data: string) => any) =>
16
+ SubscriberReporter.subscriber.on('stderr', handler),
17
+ })
18
+ // @ts-expect-error
19
+ return consola
20
+ }
21
+
22
+ export class SubscriberReporter extends LoggerReporter {
23
+ static subscriber = new EventEmitter()
24
+ log(logObj: LogObject, ctx: { options: ConsolaOptions }) {
25
+ const line = super.formatLogObj(logObj, ctx)
26
+ const event = logObj.level < 2 ? 'stderr' : 'stdout'
27
+ SubscriberReporter.subscriber.emit(event, line)
28
+ SubscriberReporter.subscriber.emit('log', line)
29
+ }
30
+ }
@@ -0,0 +1,6 @@
1
+ export { LogLevels, LogTypes } from './constants'
2
+ export { Consola } from './consola'
3
+
4
+ export type * from './types'
5
+ export type { ConsolaInstance } from './consola'
6
+ export type { LogLevel, LogType } from './constants'
@@ -0,0 +1,60 @@
1
+ import type { ConsolaInstance } from './consola'
2
+ import type { LogLevel, LogType } from './constants'
3
+
4
+ export interface ConsolaOptions {
5
+ reporters: ConsolaReporter[]
6
+ types: Record<LogType, InputLogObject>
7
+ level: LogLevel
8
+ defaults: InputLogObject
9
+ throttle: number
10
+ throttleMin: number
11
+ stdout?: NodeJS.WriteStream
12
+ stderr?: NodeJS.WriteStream
13
+ mockFn?: (type: LogType, defaults: InputLogObject) => (...args: any) => void
14
+ formatOptions: FormatOptions
15
+ }
16
+
17
+ /**
18
+ * @see https://nodejs.org/api/util.html#util_util_inspect_object_showhidden_depth_colors
19
+ */
20
+ export interface FormatOptions {
21
+ columns?: number
22
+ date?: boolean
23
+ colors?: boolean
24
+ compact?: boolean | number
25
+ [key: string]: unknown
26
+ }
27
+
28
+ export interface InputLogObject {
29
+ level?: LogLevel
30
+ tag?: string
31
+ type?: LogType
32
+ message?: string
33
+ additional?: string | string[]
34
+ args?: any[]
35
+ date?: Date
36
+ }
37
+
38
+ export interface LogObject extends InputLogObject {
39
+ level: LogLevel
40
+ type: LogType
41
+ tag: string
42
+ args: any[]
43
+ date: Date
44
+ [key: string]: unknown
45
+ }
46
+
47
+ export interface ConsolaReporter {
48
+ log: (
49
+ logObj: LogObject,
50
+ ctx: {
51
+ options: ConsolaOptions
52
+ },
53
+ ) => void
54
+ }
55
+
56
+ export interface WrappedConsola extends ConsolaInstance {
57
+ onData: (handler: (data: string) => any) => WrappedConsola
58
+ onStdOut: (handler: (data: string) => any) => WrappedConsola
59
+ onStdErr: (handler: (data: string) => any) => WrappedConsola
60
+ }