walking-log 0.0.2
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.
- package/README.md +41 -0
- package/index.js +272 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# 🧟 walking-log
|
|
2
|
+
|
|
3
|
+
<p>
|
|
4
|
+
<div align="center" style="margin-bottom: 1rem;">
|
|
5
|
+
<a href="https://www.npmjs.com/package/walking-log" style="display: flex; align-items: endx; justify-content: center;">
|
|
6
|
+
<img src="https://koboyo.com/icons/svg/zombie.svg" width="200" alt="cartoon zombie from https://koboyo.com/icons?q=zombie" />
|
|
7
|
+
|
|
8
|
+
<img src="https://koboyo.com/icons/svg/cartoon-terminal-error-line.svg" alt="cartoon terminal error line from https://koboyo.com/icons?q=terminal" width="100" />
|
|
9
|
+
</a>
|
|
10
|
+
</div>
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
> **walking-log** is a colorful yet minimal console log for **Node.js only**.
|
|
14
|
+
|
|
15
|
+
> <img src="https://github.com/legend80s/my-npm-dashboard/raw/refs/heads/main/src/backend/utils/logger/assets/languages.svg" width="16" height="16" alt="Chinese:" /> 就是一个 Node.js 控制台彩色 console.log 而已,不涉及浏览器,仅为 Node.js CLI 提供纯粹、简单的信息输出。
|
|
16
|
+
|
|
17
|
+
## Features
|
|
18
|
+
|
|
19
|
+
Zero dependencies, just one file with 120 LOC. Copy or install however you like.
|
|
20
|
+
|
|
21
|
+
<img src="https://github.com/legend80s/my-npm-dashboard/raw/refs/heads/main/src/backend/utils/logger/assets/languages.svg" width="16" height="16" alt="Chinese:" /> 零依赖,仅 120 行代码的单文件。复制或安装随意。
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install walking-log
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```javascript
|
|
30
|
+
import { createLogger } from 'walking-log'
|
|
31
|
+
|
|
32
|
+
const logger = createLogger({ verbose: true })
|
|
33
|
+
|
|
34
|
+
logger.debug("I'm not the good guy anymore.")
|
|
35
|
+
logger.info("We are the walking dead.")
|
|
36
|
+
logger.warn("If you don't fight, you die.")
|
|
37
|
+
logger.success("We survive this by pulling together, not apart.")
|
|
38
|
+
logger.error("I didn't ask for this. I killed my best friend for you people.")
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
[Want a more tailored one? 想要阅读更多?](./README-more.md)
|
package/index.js
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
// import { consola } from "consola";
|
|
2
|
+
/** @typedef {number} int */
|
|
3
|
+
|
|
4
|
+
import { styleText } from "node:util"
|
|
5
|
+
|
|
6
|
+
const BASH_COLORS = {
|
|
7
|
+
reset: "\x1b[0m",
|
|
8
|
+
bold: "\x1b[1m",
|
|
9
|
+
gray: "\x1b[90m",
|
|
10
|
+
red: "\x1b[31m",
|
|
11
|
+
green: "\x1b[32m",
|
|
12
|
+
yellow: "\x1b[33m",
|
|
13
|
+
blue: "\x1b[34m",
|
|
14
|
+
brightBlue: "\x1b[94m",
|
|
15
|
+
magenta: "\x1b[35m",
|
|
16
|
+
cyan: "\x1b[36m",
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* `trace (最低) < debug < info < warn < error < fatal (最高)`
|
|
21
|
+
*
|
|
22
|
+
* 当你设置一个日志级别(例如 info),只有优先级 ≥ info 的日志(即 info、warn、error、fatal)会被输出;而 trace 和 debug 级别的日志会被忽略。这正是日志级别配置的核心作用。
|
|
23
|
+
* none: 用于关闭所有日志输出(静默模式)。
|
|
24
|
+
*/
|
|
25
|
+
export const LEVEL = /** @type {const} */ ({
|
|
26
|
+
DEBUG: 1,
|
|
27
|
+
INFO: 2,
|
|
28
|
+
get SUCCESS() {
|
|
29
|
+
return LEVEL.INFO
|
|
30
|
+
},
|
|
31
|
+
WARN: 3,
|
|
32
|
+
ERROR: 4,
|
|
33
|
+
NONE: 5,
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @satisfies { { [key in LevelKey | 'success']: { emoji: string, color: string } } }
|
|
38
|
+
*/
|
|
39
|
+
const decorations = /** @type {const} */ ({
|
|
40
|
+
debug: { emoji: "🐞", color: "" },
|
|
41
|
+
info: { emoji: styleText("blueBright", "ℹ"), color: BASH_COLORS.brightBlue },
|
|
42
|
+
warn: { emoji: "🟡", color: BASH_COLORS.yellow },
|
|
43
|
+
error: { emoji: "🔴", color: BASH_COLORS.red },
|
|
44
|
+
success: { emoji: styleText("green", "✔"), color: BASH_COLORS.green },
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
/** @typedef { typeof LEVEL[keyof typeof LEVEL] } LevelNumber */
|
|
48
|
+
/** @typedef { Exclude<Lowercase<keyof typeof LEVEL>, 'none'> } LevelKey */
|
|
49
|
+
/** @import { LoggerOptions } from './logger.type.js' */
|
|
50
|
+
|
|
51
|
+
export class Logger {
|
|
52
|
+
/** @type {int | null} */
|
|
53
|
+
#now = null
|
|
54
|
+
/**
|
|
55
|
+
*
|
|
56
|
+
* @param {LoggerOptions} opts
|
|
57
|
+
*/
|
|
58
|
+
constructor(opts) {
|
|
59
|
+
this.level = opts.level
|
|
60
|
+
this.showTime = opts.showTime ?? true
|
|
61
|
+
this.showDiff = opts.showDiff ?? false
|
|
62
|
+
this.diffToHumanTime = opts.diffToHumanTime
|
|
63
|
+
this.#now = null
|
|
64
|
+
this.formatTimestamp = opts.formatTime ?? ((date) => date.toISOString())
|
|
65
|
+
this.color = opts.color ?? false
|
|
66
|
+
this.emoji = opts.emoji ?? false
|
|
67
|
+
this.formatLevel =
|
|
68
|
+
opts.formatLevel ??
|
|
69
|
+
// to format `[emoji level]`
|
|
70
|
+
((level) => {
|
|
71
|
+
let emoji = this.emoji ? this.pickEmoji(level) : ""
|
|
72
|
+
emoji = emoji ? `${emoji} ` : ""
|
|
73
|
+
const levelWithEmoji = `[${emoji}${this.#makeColorLevel(level)}]`
|
|
74
|
+
|
|
75
|
+
return levelWithEmoji
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
/** @type {NonNullable<LoggerOptions['pickEmoji']>} */
|
|
79
|
+
this.pickEmoji = opts.pickEmoji ?? ((level) => decorations[level].emoji)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
*
|
|
84
|
+
* @param {LevelKey} level
|
|
85
|
+
* @returns {string}
|
|
86
|
+
*/
|
|
87
|
+
#makeColorLevel(level) {
|
|
88
|
+
const upperCasedLevel = level.toUpperCase()
|
|
89
|
+
if (!this.color) {
|
|
90
|
+
return upperCasedLevel
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** @type {import('node:util').InspectColor[] | null} */
|
|
94
|
+
const color =
|
|
95
|
+
level === "success"
|
|
96
|
+
? ["white", "bgGreen"]
|
|
97
|
+
: level === "warn"
|
|
98
|
+
? ["black", "bgYellow"]
|
|
99
|
+
: level === "error"
|
|
100
|
+
? ["white", "bgRed"]
|
|
101
|
+
: null
|
|
102
|
+
|
|
103
|
+
if (!color) {
|
|
104
|
+
return upperCasedLevel
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// console.log("level:", { upperCasedLevel, color })
|
|
108
|
+
|
|
109
|
+
const colorLevel = styleText(color, ` ${upperCasedLevel} `)
|
|
110
|
+
return colorLevel
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* @param {unknown[]} args
|
|
115
|
+
*/
|
|
116
|
+
debug = (...args) => {
|
|
117
|
+
if (this.level <= LEVEL.DEBUG) {
|
|
118
|
+
this.#dispatch("debug", args)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* @param {...unknown} args
|
|
124
|
+
*/
|
|
125
|
+
info(...args) {
|
|
126
|
+
if (this.level <= LEVEL.INFO) {
|
|
127
|
+
this.#dispatch("info", args)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* @param {...unknown} args
|
|
133
|
+
*/
|
|
134
|
+
warn(...args) {
|
|
135
|
+
if (this.level <= LEVEL.WARN) {
|
|
136
|
+
this.#dispatch("warn", args)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* @param {...unknown} args
|
|
142
|
+
*/
|
|
143
|
+
error(...args) {
|
|
144
|
+
if (this.level <= LEVEL.ERROR) {
|
|
145
|
+
this.#dispatch("error", args)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* @param {unknown[]} args
|
|
151
|
+
*/
|
|
152
|
+
success(...args) {
|
|
153
|
+
if (this.level <= LEVEL.INFO) {
|
|
154
|
+
this.#dispatch("success", args)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
*
|
|
160
|
+
* @param {LevelKey} level
|
|
161
|
+
* @param {unknown[]} args
|
|
162
|
+
* @param {Pick<LoggerOptions, 'formatLevel'>} pickEmoji
|
|
163
|
+
*/
|
|
164
|
+
#dispatch(level, args, { formatLevel = this.formatLevel } = {}) {
|
|
165
|
+
const leadings = [
|
|
166
|
+
this.showTime && this.formatTimestamp(new Date()),
|
|
167
|
+
// showDiff
|
|
168
|
+
this.showDiff && this.#formatDiff(),
|
|
169
|
+
formatLevel(level),
|
|
170
|
+
]
|
|
171
|
+
.filter(Boolean)
|
|
172
|
+
.join(" ")
|
|
173
|
+
|
|
174
|
+
if (!this.color) {
|
|
175
|
+
return console[level !== "success" ? level : "info"](leadings, ...args)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const color = decorations[level].color
|
|
179
|
+
|
|
180
|
+
return console[level !== "success" ? level : "info"](leadings + color, ...args, BASH_COLORS.reset)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
#formatDiff() {
|
|
184
|
+
const diff = this.#now ? Date.now() - this.#now : 0
|
|
185
|
+
this.#now = Date.now()
|
|
186
|
+
|
|
187
|
+
const humanTime = this.#diffToHumanTime(diff)
|
|
188
|
+
|
|
189
|
+
return `${BASH_COLORS.yellow}+${humanTime}${BASH_COLORS.reset}`
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
*
|
|
194
|
+
* @param {int} diff
|
|
195
|
+
* @returns
|
|
196
|
+
*/
|
|
197
|
+
#diffToHumanTime(diff) {
|
|
198
|
+
if (typeof this.diffToHumanTime === "function") {
|
|
199
|
+
return this.diffToHumanTime(diff)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return `${diff.toLocaleString("en")}ms`
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
*
|
|
208
|
+
* @param {{ verbose: undefined | boolean }} param0
|
|
209
|
+
*/
|
|
210
|
+
export function createLogger({ verbose }) {
|
|
211
|
+
return new Logger({
|
|
212
|
+
level: verbose ? LEVEL.DEBUG : LEVEL.INFO,
|
|
213
|
+
showTime: true,
|
|
214
|
+
formatTime: (date) => date.toLocaleString(),
|
|
215
|
+
diffToHumanTime: (diff) => {
|
|
216
|
+
if (diff < 1000) {
|
|
217
|
+
return `${diff}ms`
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return `${Math.floor(diff / 1000)}.${String(diff % 1000).padStart(3, "0")}s`
|
|
221
|
+
},
|
|
222
|
+
// formatLevel: (level) => `[${level.toUpperCase()}]`,
|
|
223
|
+
color: true,
|
|
224
|
+
// emoji: true,
|
|
225
|
+
showDiff: true,
|
|
226
|
+
})
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const isMain = () => {
|
|
230
|
+
try {
|
|
231
|
+
return import.meta.main
|
|
232
|
+
} catch {
|
|
233
|
+
return false
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (isMain()) {
|
|
238
|
+
const logger = createLogger({ verbose: true })
|
|
239
|
+
|
|
240
|
+
logger.info("Hello from the wasteland.")
|
|
241
|
+
logger.warn("Watch out for walkers.")
|
|
242
|
+
logger.error("We lost another one.")
|
|
243
|
+
|
|
244
|
+
const { setTimeout: sleep } = await import("node:timers/promises")
|
|
245
|
+
|
|
246
|
+
logger.debug("Using consola 3.0.0")
|
|
247
|
+
|
|
248
|
+
await sleep(100)
|
|
249
|
+
logger.debug("Using consola", "3.0.0")
|
|
250
|
+
|
|
251
|
+
await sleep(100)
|
|
252
|
+
logger.debug("Using consola", "v", 3)
|
|
253
|
+
|
|
254
|
+
await sleep(100)
|
|
255
|
+
logger.info("Using consola", {
|
|
256
|
+
string: "3.0.0",
|
|
257
|
+
boolean: true,
|
|
258
|
+
number: 123,
|
|
259
|
+
array: [1, 2, 3],
|
|
260
|
+
object: { a: 1, b: 2 },
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
await sleep(100)
|
|
264
|
+
logger.warn("A new version of consola is available: 3.0.1")
|
|
265
|
+
|
|
266
|
+
await sleep(1000)
|
|
267
|
+
logger.success("Project built!")
|
|
268
|
+
|
|
269
|
+
setTimeout(() => {
|
|
270
|
+
logger.error(new Error("This is an example error. Everything is fine!"))
|
|
271
|
+
}, 1000)
|
|
272
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "walking-log",
|
|
3
|
+
"description": "A simple console logger only for printing messages in Node.js console with colors time, time diff and emojis. I use it in CLI app usually",
|
|
4
|
+
"private": false,
|
|
5
|
+
"version": "0.0.2",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"files": [
|
|
8
|
+
"index.js",
|
|
9
|
+
"logger.type.js"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"test": "node --test",
|
|
13
|
+
"typecheck": "tsgo --noEmit",
|
|
14
|
+
"pub:patch": "npm version patch",
|
|
15
|
+
"pub:minor": "npm version minor",
|
|
16
|
+
"pub:major": "npm version major",
|
|
17
|
+
"preversion": "npm test && npm run typecheck",
|
|
18
|
+
"postversion": "npm publish && git push && git push --tags"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "https://github.com/legend80s/my-npm-dashboard/blob/main/src/backend/utils/logger"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://github.com/legend80s/my-npm-dashboard/blob/main/src/backend/utils/logger/README.md",
|
|
25
|
+
"devDependencies": {},
|
|
26
|
+
"dependencies": {},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"consola",
|
|
29
|
+
"console",
|
|
30
|
+
"log",
|
|
31
|
+
"logger",
|
|
32
|
+
"logging",
|
|
33
|
+
"debug",
|
|
34
|
+
"info",
|
|
35
|
+
"warn",
|
|
36
|
+
"error",
|
|
37
|
+
"fatal",
|
|
38
|
+
"node",
|
|
39
|
+
"nodejs"
|
|
40
|
+
]
|
|
41
|
+
}
|