UNPKG

4.42 kBPlain TextView Raw
1// Imports
2import { LogEntry } from '../logger.js'
3import { Transform } from '../transform.js'
4import { inspect } from 'util'
5import * as ansi from '@bevry/ansi'
6
7/**
8 * Return the given argument.
9 * Used for when there is no formatter.
10 */
11function ansiNoop(a: string): string {
12 return a
13}
14
15/** A mapping of log level numbers to their intended colours */
16interface LevelsToColorsMap {
17 [logLevelNumber: string]: ansi.ANSIApplier
18}
19
20/** Configuration options for the Caterpillar Human Transform */
21export interface HumanOptions {
22 /** Use to override the default value of {@link Human.color} */
23 color?: boolean
24
25 /** Use to override the default value of {@link Human.colors} */
26 colors?: LevelsToColorsMap
27}
28
29/**
30 * Convert Logger entries into human readable format.
31 * @extends Transform
32 * @example
33 * ``` javascript
34 * import { Logger, Human } from 'caterpillar'
35 * const logger = new Logger()
36 * const human = new Human()
37 * logger.pipe(human).pipe(process.stdout)
38 * logger.log('info', 'some', {data: 'oh yeah'}, 42)
39 * ```
40 */
41export class Human extends Transform {
42 /** Whether or not to use colors? */
43 public color: boolean = true
44
45 /** Mapping of which log level numbers correspond to which colours */
46 public colors: LevelsToColorsMap = {
47 '0': 'red',
48 '1': 'red',
49 '2': 'red',
50 '3': 'red',
51 '4': 'yellow',
52 '5': 'yellow',
53 '6': 'green',
54 '7': 'green',
55 }
56
57 /** Create our instance and apply our configuration options. */
58 constructor(opts?: HumanOptions) {
59 super()
60
61 // options
62 if (opts?.color != null) this.color = opts.color
63 if (opts?.colors != null) this.colors = opts.colors
64 }
65
66 /** Get the color for the log level */
67 getColor(levelNumber: number): ansi.ANSIApplier | false {
68 // Determine
69 const color = this.colors[levelNumber] || false
70
71 // Return
72 return color
73 }
74
75 /** Pad the left of some content if need be with the specified padding to make the content reach a certain size */
76 padLeft(padding: string, size: number, content: string | number): string {
77 // Prepare
78 padding = String(padding)
79 content = String(content)
80
81 // Handle
82 if (content.length < size) {
83 for (let i = 0, n = size - content.length; i < n; ++i) {
84 content = padding + content
85 }
86 }
87
88 // Return
89 return content
90 }
91
92 /** Convert logger entry arguments into a human readable string */
93 formatArguments(args: any[]): string {
94 return args
95 .map((value) =>
96 typeof value === 'string'
97 ? value
98 : inspect(value, {
99 showHidden: false,
100 depth: 10,
101 colors: this.color,
102 })
103 )
104 .join(' ')
105 }
106
107 /** Convert a datetime into a human readable format */
108 formatDate(datetime: Date | number | string): string {
109 // Prepare
110 const now = new Date(datetime)
111 const year = now.getFullYear()
112 const month = this.padLeft('0', 2, now.getMonth() + 1)
113 const date = this.padLeft('0', 2, now.getDate())
114 const hours = this.padLeft('0', 2, now.getHours())
115 const minutes = this.padLeft('0', 2, now.getMinutes())
116 const seconds = this.padLeft('0', 2, now.getSeconds())
117 const ms = this.padLeft('0', 3, now.getMilliseconds())
118
119 // Apply
120 const result = `${year}-${month}-${date} ${hours}:${minutes}:${seconds}.${ms}`
121
122 // Return
123 return result
124 }
125
126 /** Convert a logger entry into a human readable format */
127 format(entry: LogEntry): string {
128 // Prepare
129 const { color } = this
130 const useLine = entry.line !== -1
131 let result: string
132
133 // Format
134 const format = {
135 color: this.getColor(entry.levelNumber),
136 timestamp: this.formatDate(entry.date),
137 text: this.formatArguments(entry.args),
138 }
139
140 // Check
141 if (format.text) {
142 // Formatters
143 const levelFormatter =
144 (color && format.color && ansi[format.color]) || ansiNoop
145 const lineFormatter = (useLine && color && ansi.dim) || ansiNoop
146
147 // Message
148 // @ts-ignore
149 const levelString = levelFormatter(`${entry.levelName}:`)
150 const entryString = format.text
151 const messageString = `${levelString} ${entryString}`
152
153 // Format
154 if (useLine) {
155 // Line Information
156 const seperator = '\n '
157 const debugString = lineFormatter(
158 `→ [${format.timestamp}] [${entry.file}:${entry.line}:${entry.char}] [${entry.method}]`
159 )
160
161 // Result
162 result = `${messageString}${seperator}${debugString}\n`
163 } else {
164 // Result
165 result = `${messageString}\n`
166 }
167 } else {
168 result = format.text
169 }
170
171 // Return
172 return result
173 }
174}
175
176export default Human