@feizk/logger 1.7.0 → 2.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.
package/dist/index.mjs CHANGED
@@ -1,201 +1,338 @@
1
- // src/utils.ts
2
- import chalk from "chalk";
3
- var TIMESTAMP_TYPES = {
4
- ISO: "iso",
5
- Locale: "locale",
6
- Custom: "custom"
1
+ // src/constants.ts
2
+ var LOG_LEVEL_PRIORITIES = {
3
+ trace: 0,
4
+ debug: 1,
5
+ info: 2,
6
+ warn: 3,
7
+ error: 4,
8
+ fatal: 5
9
+ };
10
+ var ANSI = {
11
+ reset: "\x1B[0m",
12
+ bold: "\x1B[1m",
13
+ dim: "\x1B[2m",
14
+ red: "\x1B[31m",
15
+ green: "\x1B[32m",
16
+ yellow: "\x1B[33m",
17
+ blue: "\x1B[34m",
18
+ magenta: "\x1B[35m",
19
+ cyan: "\x1B[36m",
20
+ gray: "\x1B[90m",
21
+ white: "\x1B[37m",
22
+ bgRed: "\x1B[41m"
23
+ };
24
+ var LEVEL_COLORS = {
25
+ trace: ANSI.gray,
26
+ debug: ANSI.cyan,
27
+ info: ANSI.blue,
28
+ warn: ANSI.yellow,
29
+ error: ANSI.red,
30
+ fatal: `${ANSI.bgRed}${ANSI.white}${ANSI.bold}`
31
+ };
32
+ var LEVEL_LABELS = {
33
+ trace: "[TRACE]",
34
+ debug: "[DEBUG]",
35
+ info: "[INFO]",
36
+ warn: "[WARN]",
37
+ error: "[ERROR]",
38
+ fatal: "[FATAL]"
7
39
  };
8
- function formatTimestamp(formatTimestampFn, types, date = /* @__PURE__ */ new Date()) {
9
- const [, timestamp] = formatTimestampFn(types, date);
10
- return timestamp;
40
+ var CONSOLE_METHODS = {
41
+ trace: "trace",
42
+ debug: "debug",
43
+ info: "log",
44
+ warn: "warn",
45
+ error: "error",
46
+ fatal: "error"
47
+ };
48
+ var TIMESTAMP_PRESETS = {
49
+ iso: (date) => date.toISOString(),
50
+ locale: (date) => date.toLocaleString()
51
+ };
52
+
53
+ // src/utils.ts
54
+ var coloredLabelCache = /* @__PURE__ */ new Map();
55
+ function getColoredLabel(level, enableColors) {
56
+ const cacheKey = `${level}:${enableColors}`;
57
+ const cached = coloredLabelCache.get(cacheKey);
58
+ if (cached !== void 0) return cached;
59
+ const label = LEVEL_LABELS[level];
60
+ if (!enableColors) {
61
+ coloredLabelCache.set(cacheKey, label);
62
+ return label;
63
+ }
64
+ const color = LEVEL_COLORS[level];
65
+ const result = `${color}${label}${ANSI.reset}`;
66
+ coloredLabelCache.set(cacheKey, result);
67
+ return result;
11
68
  }
12
- function getColor(level, enableColors) {
13
- if (!enableColors) return level;
14
- const colors = {
15
- "[INFO]": chalk.blue(level),
16
- "[WARN]": chalk.yellow(level),
17
- "[ERROR]": chalk.red(level),
18
- "[DEBUG]": chalk.gray(level)
19
- };
20
- return colors[level] || level;
69
+ function formatTimestamp(option, date = /* @__PURE__ */ new Date()) {
70
+ if (typeof option === "function") {
71
+ return option(date);
72
+ }
73
+ const preset = TIMESTAMP_PRESETS[option];
74
+ return preset(date);
21
75
  }
22
- function getDiscordColor(level) {
23
- const colors = {
24
- debug: 9807270,
25
- info: 3447003,
26
- warn: 15965202,
27
- error: 15158332
76
+ function formatJson(entry) {
77
+ const message = entry.args.map(formatArg).join(" ");
78
+ const output = {
79
+ level: entry.level,
80
+ timestamp: entry.timestamp,
81
+ message
28
82
  };
29
- return colors[level];
30
- }
31
- function generateId() {
32
- return Math.random().toString(36).substr(2, 8).toUpperCase();
83
+ if (entry.prefix) {
84
+ output.prefix = entry.prefix;
85
+ }
86
+ if (Object.keys(entry.context).length > 0) {
87
+ output.context = entry.context;
88
+ }
89
+ return JSON.stringify(output);
33
90
  }
34
- function formatLog(level, timestamp, args, options) {
35
- const { formatLog: formatLog2, enableColors = true } = options;
36
- const coloredLevel = getColor(level, enableColors);
37
- if (formatLog2) {
38
- return [formatLog2(coloredLevel, timestamp, args)];
91
+ function formatArg(arg) {
92
+ if (arg === null) return "null";
93
+ if (arg === void 0) return "undefined";
94
+ if (typeof arg === "string") return arg;
95
+ if (typeof arg === "number") return String(arg);
96
+ if (typeof arg === "boolean") return String(arg);
97
+ if (typeof arg === "bigint") return `${arg}n`;
98
+ if (typeof arg === "symbol") return arg.toString();
99
+ if (arg instanceof Error) {
100
+ const parts = [arg.message];
101
+ if (arg.name && arg.name !== "Error") parts.unshift(arg.name);
102
+ if (arg.stack) parts.push(arg.stack);
103
+ return parts.join(": ");
104
+ }
105
+ if (Array.isArray(arg)) {
106
+ try {
107
+ return JSON.stringify(arg);
108
+ } catch {
109
+ return String(arg);
110
+ }
111
+ }
112
+ try {
113
+ return JSON.stringify(arg, getCircularReplacer());
114
+ } catch {
115
+ return String(arg);
39
116
  }
40
- return [`${coloredLevel} ${timestamp}`, ...args];
117
+ }
118
+ function getCircularReplacer() {
119
+ const seen = /* @__PURE__ */ new WeakSet();
120
+ return (_key, value) => {
121
+ if (typeof value === "object" && value !== null) {
122
+ if (seen.has(value)) {
123
+ return "[Circular]";
124
+ }
125
+ seen.add(value);
126
+ }
127
+ return value;
128
+ };
129
+ }
130
+ function buildMessage(args) {
131
+ return args.map(formatArg).join(" ");
41
132
  }
42
133
 
43
134
  // src/logger.ts
44
- var defaultFormatTimestamp = (types, date = /* @__PURE__ */ new Date()) => [types.ISO, date.toISOString()];
45
- var LOG_LEVEL_PRIORITIES = {
46
- debug: 0,
47
- info: 1,
48
- warn: 2,
49
- error: 3
50
- };
51
- var Logger = class {
135
+ var Logger = class _Logger {
136
+ /**
137
+ * Create a new Logger instance.
138
+ * @param options - Configuration options
139
+ */
52
140
  constructor(options = {}) {
53
- this.discordQueue = [];
54
- this.isProcessing = false;
55
141
  this.options = {
142
+ level: options.level ?? "debug",
143
+ silent: options.silent ?? false,
56
144
  enableColors: options.enableColors ?? true,
57
- formatTimestamp: options.formatTimestamp ?? defaultFormatTimestamp,
58
- formatLog: options.formatLog,
59
- discord: options.discord
145
+ timestamp: options.timestamp ?? "iso",
146
+ formatter: options.formatter,
147
+ json: options.json ?? false,
148
+ transports: [...options.transports ?? []],
149
+ prefix: options.prefix,
150
+ context: { ...options.context ?? {} }
60
151
  };
61
- this.level = options.level ?? "debug";
152
+ this.transports = this.options.transports;
153
+ this.prefix = this.options.prefix;
154
+ this.context = this.options.context;
155
+ }
156
+ /**
157
+ * Log a trace message (most verbose).
158
+ * @param args - Arguments to log
159
+ */
160
+ trace(...args) {
161
+ this.log("trace", args);
162
+ }
163
+ /**
164
+ * Log a debug message.
165
+ * @param args - Arguments to log
166
+ */
167
+ debug(...args) {
168
+ this.log("debug", args);
62
169
  }
63
170
  /**
64
- * Sets the minimum log level for filtering messages.
65
- * @param level - The log level to set.
171
+ * Log an info message.
172
+ * @param args - Arguments to log
173
+ */
174
+ info(...args) {
175
+ this.log("info", args);
176
+ }
177
+ /**
178
+ * Log a warning message.
179
+ * @param args - Arguments to log
180
+ */
181
+ warn(...args) {
182
+ this.log("warn", args);
183
+ }
184
+ /**
185
+ * Log an error message.
186
+ * @param args - Arguments to log
187
+ */
188
+ error(...args) {
189
+ this.log("error", args);
190
+ }
191
+ /**
192
+ * Log a fatal message (most severe).
193
+ * @param args - Arguments to log
194
+ */
195
+ fatal(...args) {
196
+ this.log("fatal", args);
197
+ }
198
+ /**
199
+ * Set the minimum log level.
200
+ * @param level - The log level to set
66
201
  */
67
202
  setLevel(level) {
68
- this.level = level;
203
+ this.options.level = level;
69
204
  }
70
205
  /**
71
- * Sends a log message to Discord via webhook if configured.
72
- * @param level - The log level.
73
- * @param timestamp - The formatted timestamp.
74
- * @param args - The log arguments.
206
+ * Get the current log level.
207
+ * @returns The current log level
75
208
  */
76
- sendToDiscord(level, timestamp, args) {
77
- const discord = this.options.discord;
78
- if (!discord?.enable) return;
79
- try {
80
- new URL(discord.webhookURL);
81
- } catch {
82
- return;
83
- }
84
- const message = args.map((arg) => typeof arg === "string" ? arg : JSON.stringify(arg)).join(" ");
85
- const title = `${level.toUpperCase()}-${generateId()}`;
86
- const embed = discord.formatEmbed ? discord.formatEmbed(level, timestamp, message) : {
87
- title,
88
- description: message,
89
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
90
- color: getDiscordColor(level)
91
- };
92
- this.discordQueue.push({ embed, retryCount: 0 });
93
- if (!this.isProcessing) {
94
- this.isProcessing = true;
95
- setTimeout(() => this.processQueue(), 0);
96
- }
209
+ getLevel() {
210
+ return this.options.level;
97
211
  }
98
212
  /**
99
- * Processes the Discord queue by sending batches of embeds.
213
+ * Add a transport to the logger.
214
+ * @param transport - The transport to add
100
215
  */
101
- processQueue() {
102
- if (this.discordQueue.length === 0) {
103
- this.isProcessing = false;
104
- return;
216
+ addTransport(transport) {
217
+ this.transports.push(transport);
218
+ }
219
+ /**
220
+ * Remove a transport from the logger.
221
+ * @param transport - The transport to remove
222
+ */
223
+ removeTransport(transport) {
224
+ const index = this.transports.indexOf(transport);
225
+ if (index !== -1) {
226
+ this.transports.splice(index, 1);
105
227
  }
106
- this.isProcessing = true;
107
- const discord = this.options.discord;
108
- const batchSize = discord.batchSize ?? 10;
109
- const batch = this.discordQueue.splice(0, batchSize);
110
- this.sendBatch(batch.map((item) => item.embed)).then(() => {
111
- const delay = discord.batchDelay ?? 2e3;
112
- this.processTimeout = setTimeout(() => this.processQueue(), delay);
113
- }).catch(() => {
114
- const maxRetries = discord.maxRetries ?? 3;
115
- const retryItems = batch.filter((item) => item.retryCount < maxRetries).map((item) => ({
116
- ...item,
117
- retryCount: item.retryCount + 1
118
- }));
119
- this.discordQueue.unshift(...retryItems);
120
- const retryDelayBase = discord.retryDelayBase ?? 1e3;
121
- const delay = retryDelayBase * Math.pow(2, batch[0]?.retryCount ?? 0);
122
- this.processTimeout = setTimeout(() => this.processQueue(), delay);
123
- });
124
228
  }
125
229
  /**
126
- * Sends a batch of embeds to Discord.
127
- * @param embeds - The embeds to send.
230
+ * Create a child logger with additional prefix and context.
231
+ * @param options - Child logger options
232
+ * @returns A new Logger instance
128
233
  */
129
- async sendBatch(embeds) {
130
- const discord = this.options.discord;
131
- await fetch(discord.webhookURL, {
132
- method: "POST",
133
- headers: { "Content-Type": "application/json" },
134
- body: JSON.stringify({ embeds })
234
+ child(options = {}) {
235
+ const combinedPrefix = options.prefix ? this.prefix ? `${this.prefix}:${options.prefix}` : options.prefix : this.prefix;
236
+ const combinedContext = {
237
+ ...this.context,
238
+ ...options.context ?? {}
239
+ };
240
+ return new _Logger({
241
+ level: options.level ?? this.options.level,
242
+ silent: options.silent ?? this.options.silent,
243
+ enableColors: this.options.enableColors,
244
+ timestamp: this.options.timestamp,
245
+ formatter: this.options.formatter,
246
+ json: this.options.json,
247
+ transports: this.transports.slice(),
248
+ prefix: combinedPrefix,
249
+ context: combinedContext
135
250
  });
136
251
  }
137
252
  /**
138
- * Checks if a log level should be output based on the current log level.
139
- * @param level - The log level to check.
140
- * @returns True if the message should be logged.
253
+ * Destroy the logger and all its transports.
254
+ * Calls destroy() on all registered transports.
141
255
  */
142
- shouldLog(level) {
143
- return LOG_LEVEL_PRIORITIES[level] >= LOG_LEVEL_PRIORITIES[this.level];
256
+ async destroy() {
257
+ const destroyPromises = this.transports.map(async (transport) => {
258
+ if (typeof transport.destroy === "function") {
259
+ await transport.destroy();
260
+ }
261
+ });
262
+ await Promise.all(destroyPromises);
263
+ this.transports.length = 0;
144
264
  }
145
265
  /**
146
- * Logs an info message.
147
- * @param args - The arguments to log.
266
+ * Core logging method - all public methods delegate here.
267
+ * @param level - The log level
268
+ * @param args - The arguments to log
148
269
  */
149
- info(...args) {
150
- if (!this.shouldLog("info")) return;
151
- const timestamp = formatTimestamp(
152
- this.options.formatTimestamp,
153
- TIMESTAMP_TYPES
154
- );
155
- console.log(...formatLog("[INFO]", timestamp, args, this.options));
156
- this.sendToDiscord("info", timestamp, args);
270
+ log(level, args) {
271
+ if (!this.shouldLog(level)) return;
272
+ const entry = {
273
+ level,
274
+ timestamp: formatTimestamp(this.options.timestamp),
275
+ args,
276
+ prefix: this.prefix,
277
+ context: this.context
278
+ };
279
+ if (!this.options.silent) {
280
+ this.writeToConsole(level, entry);
281
+ }
282
+ for (const transport of this.transports) {
283
+ this.dispatchToTransport(transport, entry);
284
+ }
157
285
  }
158
286
  /**
159
- * Logs a warning message.
160
- * @param args - The arguments to log.
287
+ * Write a log entry to the console.
288
+ * @param level - The log level
289
+ * @param entry - The log entry
161
290
  */
162
- warn(...args) {
163
- if (!this.shouldLog("warn")) return;
164
- const timestamp = formatTimestamp(
165
- this.options.formatTimestamp,
166
- TIMESTAMP_TYPES
167
- );
168
- console.log(...formatLog("[WARN]", timestamp, args, this.options));
169
- this.sendToDiscord("warn", timestamp, args);
291
+ writeToConsole(level, entry) {
292
+ const method = CONSOLE_METHODS[level];
293
+ if (this.options.formatter) {
294
+ console[method](this.options.formatter(entry));
295
+ return;
296
+ }
297
+ if (this.options.json) {
298
+ console[method](formatJson(entry));
299
+ return;
300
+ }
301
+ const label = getColoredLabel(entry.level, this.options.enableColors);
302
+ const prefixStr = entry.prefix ? ` [${entry.prefix}]` : "";
303
+ const message = buildMessage(entry.args);
304
+ console[method](`${label} ${entry.timestamp}${prefixStr}`, message);
170
305
  }
171
306
  /**
172
- * Logs an error message.
173
- * @param args - The arguments to log.
307
+ * Dispatch a log entry to a transport.
308
+ * @param transport - The transport
309
+ * @param entry - The log entry
174
310
  */
175
- error(...args) {
176
- if (!this.shouldLog("error")) return;
177
- const timestamp = formatTimestamp(
178
- this.options.formatTimestamp,
179
- TIMESTAMP_TYPES
180
- );
181
- console.log(...formatLog("[ERROR]", timestamp, args, this.options));
182
- this.sendToDiscord("error", timestamp, args);
311
+ dispatchToTransport(transport, entry) {
312
+ try {
313
+ const result = transport.log(entry);
314
+ if (result instanceof Promise) {
315
+ result.catch(() => {
316
+ });
317
+ }
318
+ } catch {
319
+ }
183
320
  }
184
321
  /**
185
- * Logs a debug message.
186
- * @param args - The arguments to log.
322
+ * Check if a log level should be output.
323
+ * @param level - The log level to check
324
+ * @returns True if the message should be logged
187
325
  */
188
- debug(...args) {
189
- if (!this.shouldLog("debug")) return;
190
- const timestamp = formatTimestamp(
191
- this.options.formatTimestamp,
192
- TIMESTAMP_TYPES
193
- );
194
- console.log(...formatLog("[DEBUG]", timestamp, args, this.options));
195
- this.sendToDiscord("debug", timestamp, args);
326
+ shouldLog(level) {
327
+ return LOG_LEVEL_PRIORITIES[level] >= LOG_LEVEL_PRIORITIES[this.options.level];
196
328
  }
197
329
  };
198
330
  export {
331
+ LEVEL_LABELS,
332
+ LOG_LEVEL_PRIORITIES,
199
333
  Logger,
200
- TIMESTAMP_TYPES
334
+ buildMessage,
335
+ formatJson,
336
+ formatTimestamp,
337
+ getColoredLabel
201
338
  };
package/package.json CHANGED
@@ -1,14 +1,20 @@
1
1
  {
2
2
  "name": "@feizk/logger",
3
- "version": "1.7.0",
4
- "description": "A simple logger package with colored outputs and timestamps",
5
- "main": "dist/index.js",
3
+ "version": "2.0.1",
4
+ "description": "A lightweight, pluggable logger with colored outputs, structured logging, and transport support",
5
+ "main": "dist/index.cjs",
6
+ "module": "dist/index.js",
6
7
  "types": "dist/index.d.ts",
7
8
  "publishConfig": {
8
9
  "access": "public"
9
10
  },
10
- "dependencies": {
11
- "chalk": "^5.3.0"
11
+ "exports": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "require": "./dist/index.cjs"
15
+ },
16
+ "engines": {
17
+ "node": ">=18"
12
18
  },
13
19
  "devDependencies": {
14
20
  "@types/node": "^20.0.0",
@@ -22,7 +28,9 @@
22
28
  "logger",
23
29
  "console",
24
30
  "colors",
25
- "timestamps"
31
+ "timestamps",
32
+ "structured-logging",
33
+ "transports"
26
34
  ],
27
35
  "author": "feizk",
28
36
  "license": "MIT",