@subsquid/logger 0.0.1

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,248 @@
1
+ import {toHex} from "@subsquid/util-internal-hex"
2
+ import assert from "assert"
3
+ import {stderr as stderrColor} from "supports-color"
4
+ import {LogLevel} from "../level"
5
+ import {LogRecord} from "../logger"
6
+
7
+
8
+ export class Printer {
9
+ private prefix?: Prefix
10
+ private visited = new Set()
11
+ private style?: {open: string, close: string}
12
+ private seenRecursion = false
13
+
14
+ constructor(private out: (line: string) => void, private hasColor: boolean) {}
15
+
16
+ private line(s: string): void {
17
+ if (s && this.hasColor && this.style) {
18
+ s = this.style.open + s + this.style.close
19
+ }
20
+ if (this.prefix) {
21
+ this.out(this.prefix.prepend(s))
22
+ } else {
23
+ this.out(s)
24
+ }
25
+ }
26
+
27
+ private text(text: string): void {
28
+ for (let line of text.split(/\r?\n/)) {
29
+ this.line(line)
30
+ }
31
+ }
32
+
33
+ private begin(prefix: string, width?: number): void {
34
+ width = width == null ? prefix.length : width
35
+ if (this.hasColor && this.style) {
36
+ prefix = this.style.open + prefix + this.style.close
37
+ }
38
+ this.prefix = new Prefix(prefix, width, this.prefix)
39
+ }
40
+
41
+ private end(): void {
42
+ assert(this.prefix != null)
43
+ this.prefix = this.prefix.prev
44
+ }
45
+
46
+ private property(prefix: string, val: unknown): void {
47
+ switch(typeof val) {
48
+ case "symbol":
49
+ case "string":
50
+ this.begin(prefix)
51
+ this.text(val.toString())
52
+ this.end()
53
+ break
54
+ case "boolean":
55
+ case "bigint":
56
+ case "number":
57
+ this.line(`${prefix} ${val}`)
58
+ break
59
+ case "object":
60
+ if (val instanceof Uint8Array) {
61
+ this.line(`${prefix} ${toHex(val)}`)
62
+ } else if (val instanceof Date) {
63
+ this.line(`${prefix} ${val}`)
64
+ } else if (typeof (val as any)?.toJSON == 'function') {
65
+ this.property(prefix, (val as any).toJSON())
66
+ } else if (Array.isArray(val)) {
67
+ if (val.length == 0) {
68
+ this.line(`${prefix} []`)
69
+ } else {
70
+ if (this.visited.has(val)) {
71
+ this.seenRecursion = true
72
+ return
73
+ } else {
74
+ this.visited.add(val)
75
+ }
76
+ this.line(prefix)
77
+ for (let item of val) {
78
+ this.property(' -', item)
79
+ }
80
+ this.visited.delete(val)
81
+ }
82
+ } else if (val == null) {
83
+ this.line(`${prefix} null`)
84
+ } else {
85
+ if (this.visited.has(val)) {
86
+ this.seenRecursion = true
87
+ return
88
+ } else {
89
+ this.visited.add(val)
90
+ }
91
+ let has = false
92
+ for (let key in val) {
93
+ if (!has) {
94
+ if (prefix == ' -') {
95
+ this.begin(prefix)
96
+ } else {
97
+ this.line(prefix)
98
+ this.begin(' ')
99
+ }
100
+ }
101
+ has = true
102
+ this.property(key + ':', (val as any)[key])
103
+ }
104
+ if (has) {
105
+ this.end()
106
+ }
107
+ this.visited.delete(val)
108
+ }
109
+ break
110
+ }
111
+ }
112
+
113
+ print(rec: LogRecord) {
114
+ this.begin(formatHead(rec, this.hasColor), 14 + (rec.ns ? rec.ns.length + 1 : 0))
115
+ if (rec.msg) {
116
+ this.text(rec.msg)
117
+ }
118
+ this.style = {open: '\u001b[2m', close: '\u001b[22m'} // dim
119
+ if (rec.err instanceof Error) {
120
+ this.text(rec.err.stack || rec.err.toString())
121
+ }
122
+ for (let key in rec) {
123
+ switch(key) {
124
+ case 'time':
125
+ case 'ns':
126
+ case 'level':
127
+ case 'msg':
128
+ break
129
+ default:
130
+ this.property(key + ':', (rec as any)[key])
131
+ }
132
+ }
133
+ this.end()
134
+ if (this.seenRecursion) {
135
+ this.reset()
136
+ this.print({
137
+ ns: 'sys',
138
+ time: Date.now(),
139
+ level: LogLevel.ERROR,
140
+ msg: 'Previous record contained recursive data.\n' +
141
+ 'Serialisation of such records is not supported in production.'
142
+ })
143
+ }
144
+ }
145
+
146
+ reset(): void {
147
+ this.visited.clear()
148
+ this.prefix = undefined
149
+ this.style = undefined
150
+ this.seenRecursion = false
151
+ }
152
+ }
153
+
154
+
155
+ function formatHead(rec: LogRecord, withColor?: boolean): string {
156
+ let time = formatTime(rec.time)
157
+ let level = LogLevel[rec.level].padEnd(5, ' ')
158
+ let ns = rec.ns
159
+ if (withColor) {
160
+ level = `\u001b[1m\u001b[${getLevelColor(rec.level)}m${level}\u001b[0m`
161
+ ns = `\u001b[1m\u001b[34m${ns}\u001b[0m`
162
+ }
163
+ let head = time + ' ' + level
164
+ if (rec.ns) {
165
+ head += ' ' + ns
166
+ }
167
+ return head
168
+ }
169
+
170
+
171
+ function getLevelColor(level: LogLevel): number {
172
+ switch(level) {
173
+ case LogLevel.TRACE:
174
+ return 35
175
+ case LogLevel.DEBUG:
176
+ return 32
177
+ case LogLevel.INFO:
178
+ return 36
179
+ case LogLevel.WARN:
180
+ return 33
181
+ case LogLevel.ERROR:
182
+ case LogLevel.FATAL:
183
+ return 31
184
+ default:
185
+ return 0
186
+ }
187
+ }
188
+
189
+
190
+ function formatTime(time: number): string {
191
+ let date = new Date(time)
192
+ let hour = date.getHours().toString().padStart(2, '0')
193
+ let minutes = date.getMinutes().toString().padStart(2, '0')
194
+ let seconds = date.getSeconds().toString().padStart(2, '0')
195
+ return `${hour}:${minutes}:${seconds}`
196
+ }
197
+
198
+
199
+ class Prefix {
200
+ private indent = ''
201
+ public readonly offset: number
202
+
203
+ constructor(
204
+ private value: string,
205
+ width: number,
206
+ readonly prev?: Prefix
207
+ ) {
208
+ this.offset = (this.prev?.offset || 0) + width + 1
209
+ }
210
+
211
+ prepend(s: string): string {
212
+ if (this.value) {
213
+ let val = this.value
214
+ if (this.prev) {
215
+ val = this.prev.prepend(val)
216
+ }
217
+ this.value = ''
218
+ return s ? val + ' ' + s : val
219
+ } else if (s) {
220
+ this.indent = this.indent || ''.padEnd(this.offset, ' ')
221
+ return this.indent + s
222
+ } else {
223
+ return s
224
+ }
225
+ }
226
+ }
227
+
228
+
229
+ const PRINTER = new Printer(line => {
230
+ process.stderr.write(line + '\n')
231
+ }, !!stderrColor)
232
+
233
+
234
+ export function prettyStderrSink(rec: LogRecord): void {
235
+ try {
236
+ PRINTER.print(rec)
237
+ } catch(e: any) {
238
+ PRINTER.reset()
239
+ PRINTER.print({
240
+ ns: 'sys',
241
+ level: LogLevel.ERROR,
242
+ time: Date.now(),
243
+ msg: e.stack || e.toString()
244
+ })
245
+ } finally {
246
+ PRINTER.reset()
247
+ }
248
+ }