@warlock.js/logger 4.0.171 → 4.1.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/README.md +145 -422
- package/cjs/index.cjs +1003 -0
- package/cjs/index.cjs.map +1 -0
- package/esm/channels/console-log.d.mts +40 -0
- package/esm/channels/console-log.d.mts.map +1 -0
- package/esm/channels/console-log.mjs +51 -0
- package/esm/channels/console-log.mjs.map +1 -0
- package/esm/channels/file-log.d.mts +194 -0
- package/esm/channels/file-log.d.mts.map +1 -0
- package/esm/channels/file-log.mjs +267 -0
- package/esm/channels/file-log.mjs.map +1 -0
- package/esm/channels/index.mjs +5 -0
- package/esm/channels/json-file-log.d.mts +33 -0
- package/esm/channels/json-file-log.d.mts.map +1 -0
- package/esm/channels/json-file-log.mjs +137 -0
- package/esm/channels/json-file-log.mjs.map +1 -0
- package/esm/index.d.mts +11 -0
- package/esm/index.mjs +13 -0
- package/esm/log-channel.d.mts +78 -0
- package/esm/log-channel.d.mts.map +1 -0
- package/esm/log-channel.mjs +75 -0
- package/esm/log-channel.mjs.map +1 -0
- package/esm/logger.d.mts +184 -0
- package/esm/logger.d.mts.map +1 -0
- package/esm/logger.mjs +282 -0
- package/esm/logger.mjs.map +1 -0
- package/esm/redact/redact.d.mts +25 -0
- package/esm/redact/redact.d.mts.map +1 -0
- package/esm/redact/redact.mjs +109 -0
- package/esm/redact/redact.mjs.map +1 -0
- package/esm/types.d.mts +129 -0
- package/esm/types.d.mts.map +1 -0
- package/esm/utils/capture-unhandled-errors.d.mts +16 -0
- package/esm/utils/capture-unhandled-errors.d.mts.map +1 -0
- package/esm/utils/capture-unhandled-errors.mjs +26 -0
- package/esm/utils/capture-unhandled-errors.mjs.map +1 -0
- package/esm/utils/clear-message.d.mts +8 -0
- package/esm/utils/clear-message.d.mts.map +1 -0
- package/esm/utils/clear-message.mjs +12 -0
- package/esm/utils/clear-message.mjs.map +1 -0
- package/esm/utils/index.mjs +5 -0
- package/esm/utils/safe-json-stringify.d.mts +14 -0
- package/esm/utils/safe-json-stringify.d.mts.map +1 -0
- package/esm/utils/safe-json-stringify.mjs +35 -0
- package/esm/utils/safe-json-stringify.mjs.map +1 -0
- package/llms-full.txt +1296 -0
- package/llms.txt +19 -0
- package/package.json +39 -39
- package/skills/capture-unhandled-errors/SKILL.md +103 -0
- package/skills/configure-logger/SKILL.md +105 -0
- package/skills/filter-log-entries/SKILL.md +120 -0
- package/skills/flush-logs-on-shutdown/SKILL.md +91 -0
- package/skills/logger-basics/SKILL.md +85 -0
- package/skills/overview/SKILL.md +86 -0
- package/skills/pick-log-channel/SKILL.md +139 -0
- package/skills/redact-sensitive-log-fields/SKILL.md +122 -0
- package/skills/test-logging-code/SKILL.md +169 -0
- package/skills/use-log-helpers/SKILL.md +66 -0
- package/skills/write-custom-log-channel/SKILL.md +160 -0
- package/cjs/channels/console-log.d.ts +0 -17
- package/cjs/channels/console-log.d.ts.map +0 -1
- package/cjs/channels/console-log.js +0 -47
- package/cjs/channels/console-log.js.map +0 -1
- package/cjs/channels/file-log.d.ts +0 -171
- package/cjs/channels/file-log.d.ts.map +0 -1
- package/cjs/channels/file-log.js +0 -293
- package/cjs/channels/file-log.js.map +0 -1
- package/cjs/channels/index.d.ts +0 -4
- package/cjs/channels/index.d.ts.map +0 -1
- package/cjs/channels/json-file-log.d.ts +0 -33
- package/cjs/channels/json-file-log.d.ts.map +0 -1
- package/cjs/channels/json-file-log.js +0 -164
- package/cjs/channels/json-file-log.js.map +0 -1
- package/cjs/index.d.ts +0 -6
- package/cjs/index.d.ts.map +0 -1
- package/cjs/index.js +0 -1
- package/cjs/index.js.map +0 -1
- package/cjs/log-channel.d.ts +0 -67
- package/cjs/log-channel.d.ts.map +0 -1
- package/cjs/log-channel.js +0 -88
- package/cjs/log-channel.js.map +0 -1
- package/cjs/logger.d.ts +0 -62
- package/cjs/logger.d.ts.map +0 -1
- package/cjs/logger.js +0 -124
- package/cjs/logger.js.map +0 -1
- package/cjs/types.d.ts +0 -104
- package/cjs/types.d.ts.map +0 -1
- package/cjs/utils/capture-unhandled-errors.d.ts +0 -2
- package/cjs/utils/capture-unhandled-errors.d.ts.map +0 -1
- package/cjs/utils/capture-unhandled-errors.js +0 -12
- package/cjs/utils/capture-unhandled-errors.js.map +0 -1
- package/cjs/utils/clear-message.d.ts +0 -5
- package/cjs/utils/clear-message.d.ts.map +0 -1
- package/cjs/utils/clear-message.js +0 -9
- package/cjs/utils/clear-message.js.map +0 -1
- package/cjs/utils/index.d.ts +0 -3
- package/cjs/utils/index.d.ts.map +0 -1
- package/esm/channels/console-log.d.ts +0 -17
- package/esm/channels/console-log.d.ts.map +0 -1
- package/esm/channels/console-log.js +0 -47
- package/esm/channels/console-log.js.map +0 -1
- package/esm/channels/file-log.d.ts +0 -171
- package/esm/channels/file-log.d.ts.map +0 -1
- package/esm/channels/file-log.js +0 -293
- package/esm/channels/file-log.js.map +0 -1
- package/esm/channels/index.d.ts +0 -4
- package/esm/channels/index.d.ts.map +0 -1
- package/esm/channels/json-file-log.d.ts +0 -33
- package/esm/channels/json-file-log.d.ts.map +0 -1
- package/esm/channels/json-file-log.js +0 -164
- package/esm/channels/json-file-log.js.map +0 -1
- package/esm/index.d.ts +0 -6
- package/esm/index.d.ts.map +0 -1
- package/esm/index.js +0 -1
- package/esm/index.js.map +0 -1
- package/esm/log-channel.d.ts +0 -67
- package/esm/log-channel.d.ts.map +0 -1
- package/esm/log-channel.js +0 -88
- package/esm/log-channel.js.map +0 -1
- package/esm/logger.d.ts +0 -62
- package/esm/logger.d.ts.map +0 -1
- package/esm/logger.js +0 -124
- package/esm/logger.js.map +0 -1
- package/esm/types.d.ts +0 -104
- package/esm/types.d.ts.map +0 -1
- package/esm/utils/capture-unhandled-errors.d.ts +0 -2
- package/esm/utils/capture-unhandled-errors.d.ts.map +0 -1
- package/esm/utils/capture-unhandled-errors.js +0 -12
- package/esm/utils/capture-unhandled-errors.js.map +0 -1
- package/esm/utils/clear-message.d.ts +0 -5
- package/esm/utils/clear-message.d.ts.map +0 -1
- package/esm/utils/clear-message.js +0 -9
- package/esm/utils/clear-message.js.map +0 -1
- package/esm/utils/index.d.ts +0 -3
- package/esm/utils/index.d.ts.map +0 -1
package/cjs/index.cjs
ADDED
|
@@ -0,0 +1,1003 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
12
|
+
key = keys[i];
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
14
|
+
__defProp(to, key, {
|
|
15
|
+
get: ((k) => from[k]).bind(null, key),
|
|
16
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
24
|
+
value: mod,
|
|
25
|
+
enumerable: true
|
|
26
|
+
}) : target, mod));
|
|
27
|
+
|
|
28
|
+
//#endregion
|
|
29
|
+
let _mongez_copper = require("@mongez/copper");
|
|
30
|
+
let util = require("util");
|
|
31
|
+
let _warlock_js_fs = require("@warlock.js/fs");
|
|
32
|
+
let dayjs = require("dayjs");
|
|
33
|
+
dayjs = __toESM(dayjs, 1);
|
|
34
|
+
let fs = require("fs");
|
|
35
|
+
fs = __toESM(fs, 1);
|
|
36
|
+
let os = require("os");
|
|
37
|
+
let path = require("path");
|
|
38
|
+
path = __toESM(path, 1);
|
|
39
|
+
let safe_stable_stringify = require("safe-stable-stringify");
|
|
40
|
+
let _mongez_reinforcements = require("@mongez/reinforcements");
|
|
41
|
+
|
|
42
|
+
//#region ../../@warlock.js/logger/src/log-channel.ts
|
|
43
|
+
var LogChannel = class {
|
|
44
|
+
/**
|
|
45
|
+
* Constructor
|
|
46
|
+
*/
|
|
47
|
+
constructor(configurations) {
|
|
48
|
+
this.terminal = false;
|
|
49
|
+
this.defaultConfigurations = {};
|
|
50
|
+
this.channelConfigurations = {};
|
|
51
|
+
this.isInitialized = false;
|
|
52
|
+
if (configurations) this.setConfigurations(configurations);
|
|
53
|
+
setTimeout(async () => {
|
|
54
|
+
if (this.init) await this.init();
|
|
55
|
+
this.isInitialized = true;
|
|
56
|
+
}, 0);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Get config value
|
|
60
|
+
*/
|
|
61
|
+
config(key) {
|
|
62
|
+
return this.channelConfigurations[key] ?? (this.defaultConfigurations ?? {})[key];
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Set configurations
|
|
66
|
+
*/
|
|
67
|
+
setConfigurations(configurations) {
|
|
68
|
+
this.channelConfigurations = {
|
|
69
|
+
...this.channelConfigurations,
|
|
70
|
+
...configurations
|
|
71
|
+
};
|
|
72
|
+
return this;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Determine if the message should be logged
|
|
76
|
+
*/
|
|
77
|
+
shouldBeLogged(data) {
|
|
78
|
+
const allowedLevels = this.config("levels");
|
|
79
|
+
if (allowedLevels?.length && !allowedLevels.includes(data.type)) return false;
|
|
80
|
+
const filter = this.config("filter");
|
|
81
|
+
if (filter) return filter(data);
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Read the channel's redact config (if any). Used by `Logger` to apply
|
|
86
|
+
* per-channel additive redaction on top of the logger-wide floor.
|
|
87
|
+
* Subclasses normally don't override this — set `redact` in your channel
|
|
88
|
+
* configuration instead.
|
|
89
|
+
*/
|
|
90
|
+
getRedactConfig() {
|
|
91
|
+
return this.config("redact");
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Get date and time formats
|
|
95
|
+
*/
|
|
96
|
+
getDateAndTimeFormat() {
|
|
97
|
+
const dateFormat = this.config("dateFormat");
|
|
98
|
+
return {
|
|
99
|
+
date: dateFormat?.date ?? "DD-MM-YYYY",
|
|
100
|
+
time: dateFormat?.time ?? "HH:mm:ss"
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* get basic configurations with the given ones
|
|
105
|
+
*/
|
|
106
|
+
withBasicConfigurations(configurations) {
|
|
107
|
+
return {
|
|
108
|
+
filter: () => true,
|
|
109
|
+
...configurations
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
//#endregion
|
|
115
|
+
//#region ../../@warlock.js/logger/src/channels/console-log.ts
|
|
116
|
+
var ConsoleLog = class extends LogChannel {
|
|
117
|
+
constructor(..._args) {
|
|
118
|
+
super(..._args);
|
|
119
|
+
this.name = "console";
|
|
120
|
+
this.terminal = true;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* {@inheritdoc}
|
|
124
|
+
*/
|
|
125
|
+
log(data) {
|
|
126
|
+
const { module, action, message, type: level } = data;
|
|
127
|
+
if (!this.shouldBeLogged(data)) return;
|
|
128
|
+
const date = (/* @__PURE__ */ new Date()).toISOString();
|
|
129
|
+
switch (level) {
|
|
130
|
+
case "debug":
|
|
131
|
+
console.log(_mongez_copper.colors.magentaBright("⚙"), _mongez_copper.colors.yellow(`(${date})`), _mongez_copper.colors.cyan(`[${module}]`), _mongez_copper.colors.magenta(`[${action}]`), _mongez_copper.colors.magentaBright(message));
|
|
132
|
+
break;
|
|
133
|
+
case "info":
|
|
134
|
+
console.log(_mongez_copper.colors.blueBright("ℹ"), _mongez_copper.colors.yellow(`(${date})`), _mongez_copper.colors.cyan(`[${module}]`), _mongez_copper.colors.magenta(`[${action}]`), _mongez_copper.colors.blueBright(message));
|
|
135
|
+
break;
|
|
136
|
+
case "warn":
|
|
137
|
+
console.log(_mongez_copper.colors.yellow("⚠"), _mongez_copper.colors.yellow(`(${date})`), _mongez_copper.colors.cyan(`[${module}]`), _mongez_copper.colors.magenta(`[${action}]`), _mongez_copper.colors.yellowBright(message));
|
|
138
|
+
break;
|
|
139
|
+
case "error":
|
|
140
|
+
console.log(_mongez_copper.colors.red("✗"), _mongez_copper.colors.yellow(`(${date})`), _mongez_copper.colors.cyan(`[${module}]`), _mongez_copper.colors.magenta(`[${action}]`), _mongez_copper.colors.redBright(message));
|
|
141
|
+
break;
|
|
142
|
+
case "success":
|
|
143
|
+
console.log(_mongez_copper.colors.green("✓"), _mongez_copper.colors.yellow(`(${date})`), _mongez_copper.colors.cyan(`[${module}]`), _mongez_copper.colors.magenta(`[${action}]`), _mongez_copper.colors.greenBright(message));
|
|
144
|
+
break;
|
|
145
|
+
default: console.log("[log]", _mongez_copper.colors.yellow(`(${date})`), _mongez_copper.colors.cyan(`[${module}]`), _mongez_copper.colors.magenta(`[${action}]`), message);
|
|
146
|
+
}
|
|
147
|
+
if (typeof message === "object") console.log(message);
|
|
148
|
+
if (this.config("showContext") && data.context && Object.keys(data.context).length > 0) {
|
|
149
|
+
const depth = this.config("contextDepth") ?? 4;
|
|
150
|
+
console.log(_mongez_copper.colors.gray(" ↳"), (0, util.inspect)(data.context, {
|
|
151
|
+
colors: true,
|
|
152
|
+
depth,
|
|
153
|
+
breakLength: 80
|
|
154
|
+
}));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
//#endregion
|
|
160
|
+
//#region ../../@warlock.js/logger/src/channels/file-log.ts
|
|
161
|
+
var FileLog = class extends LogChannel {
|
|
162
|
+
constructor(..._args) {
|
|
163
|
+
super(..._args);
|
|
164
|
+
this.name = "file";
|
|
165
|
+
this.messages = [];
|
|
166
|
+
this.groupedMessages = {};
|
|
167
|
+
this.defaultConfigurations = {
|
|
168
|
+
storagePath: process.cwd() + "/storage/logs",
|
|
169
|
+
rotate: true,
|
|
170
|
+
name: "app",
|
|
171
|
+
extension: "log",
|
|
172
|
+
chunk: "single",
|
|
173
|
+
maxMessagesToWrite: 100,
|
|
174
|
+
filter: () => true,
|
|
175
|
+
maxFileSize: 10 * 1024 * 1024,
|
|
176
|
+
get rotateFileName() {
|
|
177
|
+
return (0, dayjs.default)().format("DD-MM-YYYY");
|
|
178
|
+
},
|
|
179
|
+
dateFormat: {
|
|
180
|
+
date: "DD-MM-YYYY",
|
|
181
|
+
time: "HH:mm:ss"
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
this.lastWriteTime = Date.now();
|
|
185
|
+
this.isWriting = false;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Check file size for file rotation
|
|
189
|
+
*/
|
|
190
|
+
async checkAndRotateFile(filePath = this.filePath) {
|
|
191
|
+
if (!this.config("rotate")) return;
|
|
192
|
+
try {
|
|
193
|
+
if ((await fs.default.promises.stat(filePath)).size >= this.config("maxFileSize")) await this.rotateLogFile();
|
|
194
|
+
} catch (error) {
|
|
195
|
+
if (error.code !== "ENOENT") console.error("Error checking log file:", error);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Rotate log file
|
|
200
|
+
*/
|
|
201
|
+
async rotateLogFile() {
|
|
202
|
+
const fileName = `${this.fileName}-${this.config("rotateFileName")}-${Date.now()}`;
|
|
203
|
+
const extension = this.extension;
|
|
204
|
+
const rotatedFilePath = path.default.join(this.storagePath, `${fileName}.${extension}`);
|
|
205
|
+
await fs.default.promises.rename(this.filePath, rotatedFilePath).catch((error) => {
|
|
206
|
+
console.error("Error rotating file:", error);
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Flush messages
|
|
211
|
+
*
|
|
212
|
+
* Starts a periodic re-check so low-traffic channels don't sit on buffered
|
|
213
|
+
* entries indefinitely. The handle is stored on the instance so `dispose()`
|
|
214
|
+
* can stop it — without this, every channel leaks a timer for the lifetime
|
|
215
|
+
* of the process.
|
|
216
|
+
*/
|
|
217
|
+
initMessageFlush() {
|
|
218
|
+
this.flushIntervalHandle = setInterval(() => {
|
|
219
|
+
if (this.messages.length > 0 && (this.messages.length >= this.maxMessagesToWrite || Date.now() - this.lastWriteTime > 5e3)) this.writeMessagesToFile();
|
|
220
|
+
}, 5e3);
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Stop the background flush interval and drain any buffered entries.
|
|
224
|
+
*
|
|
225
|
+
* Call this when discarding a channel (e.g. reconfiguring the logger at
|
|
226
|
+
* runtime) so the 5-second timer doesn't keep the event loop alive. Safe to
|
|
227
|
+
* call more than once.
|
|
228
|
+
*/
|
|
229
|
+
dispose() {
|
|
230
|
+
if (this.flushIntervalHandle) {
|
|
231
|
+
clearInterval(this.flushIntervalHandle);
|
|
232
|
+
this.flushIntervalHandle = void 0;
|
|
233
|
+
}
|
|
234
|
+
this.flushSync();
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Get file path
|
|
238
|
+
*/
|
|
239
|
+
get filePath() {
|
|
240
|
+
const fileName = this.fileName;
|
|
241
|
+
const extension = this.extension;
|
|
242
|
+
return path.default.join(this.storagePath, `${fileName}.${extension}`);
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Get max messages
|
|
246
|
+
*/
|
|
247
|
+
get maxMessagesToWrite() {
|
|
248
|
+
return this.config("maxMessagesToWrite");
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Get file name
|
|
252
|
+
*/
|
|
253
|
+
get fileName() {
|
|
254
|
+
switch (this.config("chunk")) {
|
|
255
|
+
case "single":
|
|
256
|
+
default: return this.config("name");
|
|
257
|
+
case "daily": return (0, dayjs.default)().format("DD-MM-YYYY");
|
|
258
|
+
case "hourly": return (0, dayjs.default)().format("DD-MM-YYYY-HH-00-00-a");
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Get file extension
|
|
263
|
+
*/
|
|
264
|
+
get extension() {
|
|
265
|
+
return this.config("extension");
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Get content
|
|
269
|
+
*/
|
|
270
|
+
get content() {
|
|
271
|
+
return this.messages.map((message) => message.content).join(os.EOL) + os.EOL;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Get storage path
|
|
275
|
+
*/
|
|
276
|
+
get storagePath() {
|
|
277
|
+
return this.config("storagePath");
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* {@inheritdoc}
|
|
281
|
+
*/
|
|
282
|
+
async init() {
|
|
283
|
+
const logsDirectory = this.storagePath;
|
|
284
|
+
await (0, _warlock_js_fs.ensureDirectoryAsync)(logsDirectory);
|
|
285
|
+
this.initMessageFlush();
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Synchronously flush messages
|
|
289
|
+
*/
|
|
290
|
+
flushSync() {
|
|
291
|
+
if (this.messages.length === 0 && Object.keys(this.groupedMessages).length === 0) return;
|
|
292
|
+
if (this.messagedShouldBeGrouped) {
|
|
293
|
+
this.prepareGroupedMessages();
|
|
294
|
+
for (const key in this.groupedMessages) {
|
|
295
|
+
const directoryPath = path.default.join(this.storagePath, key);
|
|
296
|
+
fs.default.mkdirSync(directoryPath, { recursive: true });
|
|
297
|
+
const filePath = path.default.join(directoryPath, `${this.fileName}.${this.extension}`);
|
|
298
|
+
const content = this.groupedMessages[key].map((message) => message.content).join(os.EOL) + os.EOL;
|
|
299
|
+
fs.default.appendFileSync(filePath, content);
|
|
300
|
+
}
|
|
301
|
+
} else {
|
|
302
|
+
fs.default.mkdirSync(this.storagePath, { recursive: true });
|
|
303
|
+
fs.default.appendFileSync(this.filePath, this.content);
|
|
304
|
+
}
|
|
305
|
+
this.onSave();
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* {@inheritdoc}
|
|
309
|
+
*/
|
|
310
|
+
async log(data) {
|
|
311
|
+
const { module, action, message, type: level, context } = data;
|
|
312
|
+
if (!this.shouldBeLogged(data)) return;
|
|
313
|
+
const { date: dateFormat, time } = this.getDateAndTimeFormat();
|
|
314
|
+
const date = (0, dayjs.default)().format(dateFormat + " " + time);
|
|
315
|
+
let content = `[${date}] [${level}] [${module}][${action}]: `;
|
|
316
|
+
let stack;
|
|
317
|
+
if (message instanceof Error) {
|
|
318
|
+
content += message.message + os.EOL;
|
|
319
|
+
content += `[trace]` + os.EOL;
|
|
320
|
+
content += message.stack;
|
|
321
|
+
stack = message.stack;
|
|
322
|
+
} else content += message;
|
|
323
|
+
this.messages.push({
|
|
324
|
+
content,
|
|
325
|
+
level,
|
|
326
|
+
date,
|
|
327
|
+
module,
|
|
328
|
+
action,
|
|
329
|
+
stack,
|
|
330
|
+
context,
|
|
331
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
332
|
+
});
|
|
333
|
+
await this.checkIfMessagesShouldBeWritten();
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Check if messages should be written
|
|
337
|
+
*/
|
|
338
|
+
async checkIfMessagesShouldBeWritten() {
|
|
339
|
+
if (this.messages.length >= this.maxMessagesToWrite || Date.now() - this.lastWriteTime > 5e3) await this.writeMessagesToFile();
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Should be called after messages are saved
|
|
343
|
+
*/
|
|
344
|
+
onSave() {
|
|
345
|
+
this.messages = [];
|
|
346
|
+
this.groupedMessages = {};
|
|
347
|
+
this.isWriting = false;
|
|
348
|
+
this.lastWriteTime = Date.now();
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Check if messages should be grouped
|
|
352
|
+
*/
|
|
353
|
+
get messagedShouldBeGrouped() {
|
|
354
|
+
return Number(this.config("groupBy")?.length) > 0;
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Write messages to the file
|
|
358
|
+
*/
|
|
359
|
+
async writeMessagesToFile() {
|
|
360
|
+
if (this.messages.length === 0 || this.isWriting || !this.isInitialized) return;
|
|
361
|
+
this.isWriting = true;
|
|
362
|
+
if (this.messagedShouldBeGrouped) return await this.writeGroupedMessagesToFile();
|
|
363
|
+
await this.checkAndRotateFile();
|
|
364
|
+
try {
|
|
365
|
+
await this.write(this.filePath, this.content);
|
|
366
|
+
this.onSave();
|
|
367
|
+
} catch (error) {
|
|
368
|
+
console.error("Failed to write log:", error);
|
|
369
|
+
this.isWriting = false;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Write grouped messages to the file
|
|
374
|
+
*/
|
|
375
|
+
async writeGroupedMessagesToFile() {
|
|
376
|
+
this.prepareGroupedMessages();
|
|
377
|
+
for (const key in this.groupedMessages) {
|
|
378
|
+
const directoryPath = path.default.join(this.storagePath, key);
|
|
379
|
+
await (0, _warlock_js_fs.ensureDirectoryAsync)(directoryPath);
|
|
380
|
+
const filePath = path.default.join(directoryPath, `${this.fileName}.${this.extension}`);
|
|
381
|
+
await this.checkAndRotateFile(filePath);
|
|
382
|
+
const content = this.groupedMessages[key].map((message) => message.content).join(os.EOL) + os.EOL;
|
|
383
|
+
try {
|
|
384
|
+
await this.write(filePath, content);
|
|
385
|
+
} catch (error) {
|
|
386
|
+
console.error("Failed to write log:", error);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
this.onSave();
|
|
390
|
+
this.isWriting = false;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Prepare grouped messages
|
|
394
|
+
*/
|
|
395
|
+
prepareGroupedMessages() {
|
|
396
|
+
this.messages.forEach((message) => {
|
|
397
|
+
const key = this.config("groupBy").map((groupKey) => encodeURIComponent(message[groupKey])).join("/");
|
|
398
|
+
this.groupedMessages[key] = this.groupedMessages[key] || [];
|
|
399
|
+
this.groupedMessages[key].push(message);
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Start writing to the file
|
|
404
|
+
*/
|
|
405
|
+
async write(filePath, content) {
|
|
406
|
+
return new Promise((resolve, reject) => {
|
|
407
|
+
const writer = fs.default.createWriteStream(filePath, { flags: "a" });
|
|
408
|
+
writer.write(content, (error) => {
|
|
409
|
+
writer.end();
|
|
410
|
+
if (error) reject(error);
|
|
411
|
+
else resolve(true);
|
|
412
|
+
});
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
//#endregion
|
|
418
|
+
//#region ../../@warlock.js/logger/src/utils/safe-json-stringify.ts
|
|
419
|
+
/**
|
|
420
|
+
* Replacer that surfaces Error data — `name`, `message`, and `stack` are
|
|
421
|
+
* non-enumerable on `Error`, so neither default JSON serialization nor an
|
|
422
|
+
* object spread captures them (both produce `{}`). They are copied explicitly;
|
|
423
|
+
* the trailing spread then captures any additional enumerable props the caller
|
|
424
|
+
* (or a subclass) attached, such as a `code` field.
|
|
425
|
+
*/
|
|
426
|
+
function errorReplacer(_key, value) {
|
|
427
|
+
if (value instanceof Error) return {
|
|
428
|
+
...value,
|
|
429
|
+
name: value.name,
|
|
430
|
+
message: value.message,
|
|
431
|
+
stack: value.stack
|
|
432
|
+
};
|
|
433
|
+
return value;
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* JSON-serialize log payloads safely. Circular refs, BigInt, and repeated
|
|
437
|
+
* non-tree references are handled by `safe-stable-stringify`; functions and
|
|
438
|
+
* symbols are dropped (standard JSON behavior); Errors are expanded via
|
|
439
|
+
* `errorReplacer`. Class instances serialize as their enumerable props.
|
|
440
|
+
*
|
|
441
|
+
* @example
|
|
442
|
+
* await fs.promises.writeFile(filePath, safeJsonStringify(payload, 2));
|
|
443
|
+
*/
|
|
444
|
+
function safeJsonStringify(value, space) {
|
|
445
|
+
return (0, safe_stable_stringify.stringify)(value, errorReplacer, space) ?? "";
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
//#endregion
|
|
449
|
+
//#region ../../@warlock.js/logger/src/channels/json-file-log.ts
|
|
450
|
+
var JSONFileLog = class extends FileLog {
|
|
451
|
+
constructor(..._args) {
|
|
452
|
+
super(..._args);
|
|
453
|
+
this.name = "fileJson";
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Get file extension
|
|
457
|
+
*/
|
|
458
|
+
get extension() {
|
|
459
|
+
return "json";
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Synchronously flush messages
|
|
463
|
+
*/
|
|
464
|
+
flushSync() {
|
|
465
|
+
if (this.messages.length === 0 && Object.keys(this.groupedMessages).length === 0) return;
|
|
466
|
+
if (this.messagedShouldBeGrouped) {
|
|
467
|
+
this.prepareGroupedMessages();
|
|
468
|
+
for (const key in this.groupedMessages) {
|
|
469
|
+
const directoryPath = path.default.join(this.storagePath, key);
|
|
470
|
+
fs.default.mkdirSync(directoryPath, { recursive: true });
|
|
471
|
+
const filePath = path.default.join(directoryPath, `${this.fileName}.${this.extension}`);
|
|
472
|
+
let fileContents = { messages: [] };
|
|
473
|
+
if (fs.default.existsSync(filePath)) try {
|
|
474
|
+
fileContents = JSON.parse(fs.default.readFileSync(filePath, "utf-8"));
|
|
475
|
+
if (!Array.isArray(fileContents.messages)) fileContents.messages = [];
|
|
476
|
+
} catch (e) {
|
|
477
|
+
fileContents = { messages: [] };
|
|
478
|
+
}
|
|
479
|
+
fileContents.messages.push(...this.groupedMessages[key]);
|
|
480
|
+
fs.default.writeFileSync(filePath, safeJsonStringify(fileContents, 2));
|
|
481
|
+
}
|
|
482
|
+
} else {
|
|
483
|
+
fs.default.mkdirSync(this.storagePath, { recursive: true });
|
|
484
|
+
let fileContents = { messages: [] };
|
|
485
|
+
if (fs.default.existsSync(this.filePath)) try {
|
|
486
|
+
fileContents = JSON.parse(fs.default.readFileSync(this.filePath, "utf-8"));
|
|
487
|
+
if (!Array.isArray(fileContents.messages)) fileContents.messages = [];
|
|
488
|
+
} catch (e) {
|
|
489
|
+
fileContents = { messages: [] };
|
|
490
|
+
}
|
|
491
|
+
fileContents.messages.push(...this.messages);
|
|
492
|
+
fs.default.writeFileSync(this.filePath, safeJsonStringify(fileContents, 2));
|
|
493
|
+
}
|
|
494
|
+
this.onSave();
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* {@inheritdoc}
|
|
498
|
+
*/
|
|
499
|
+
async log(data) {
|
|
500
|
+
let stack;
|
|
501
|
+
if (data.message instanceof Error) {
|
|
502
|
+
stack = data.message.stack?.split("\n");
|
|
503
|
+
data.message = data.message.message;
|
|
504
|
+
}
|
|
505
|
+
const { module, action, message, type: level, context } = data;
|
|
506
|
+
if (!this.shouldBeLogged(data)) return;
|
|
507
|
+
const { date: dateFormat, time } = this.getDateAndTimeFormat();
|
|
508
|
+
const date = (0, dayjs.default)().format(dateFormat + " " + time);
|
|
509
|
+
this.messages.push({
|
|
510
|
+
content: message,
|
|
511
|
+
level,
|
|
512
|
+
date,
|
|
513
|
+
module,
|
|
514
|
+
action,
|
|
515
|
+
stack,
|
|
516
|
+
context,
|
|
517
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
518
|
+
});
|
|
519
|
+
await this.checkIfMessagesShouldBeWritten();
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Write messages to the file
|
|
523
|
+
*/
|
|
524
|
+
async writeMessagesToFile() {
|
|
525
|
+
if (this.messages.length === 0 || this.isWriting || !this.isInitialized) return;
|
|
526
|
+
this.isWriting = true;
|
|
527
|
+
if (this.messagedShouldBeGrouped) return await this.writeGroupedMessagesToFile();
|
|
528
|
+
await this.checkAndRotateFile();
|
|
529
|
+
let fileContents = { messages: [] };
|
|
530
|
+
if (await (0, _warlock_js_fs.fileExistsAsync)(this.filePath)) try {
|
|
531
|
+
fileContents = await (0, _warlock_js_fs.getJsonFileAsync)(this.filePath);
|
|
532
|
+
} catch (error) {
|
|
533
|
+
console.error("Error reading log file, reinitializing:", error);
|
|
534
|
+
fileContents = { messages: [] };
|
|
535
|
+
}
|
|
536
|
+
else fileContents = { messages: [] };
|
|
537
|
+
fileContents.messages.push(...this.messages);
|
|
538
|
+
try {
|
|
539
|
+
await fs.default.promises.writeFile(this.filePath, safeJsonStringify(fileContents, 2));
|
|
540
|
+
this.onSave();
|
|
541
|
+
} catch (error) {
|
|
542
|
+
console.error("Failed to write log:", error);
|
|
543
|
+
this.isWriting = false;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Write grouped messages to the file
|
|
548
|
+
*/
|
|
549
|
+
async writeGroupedMessagesToFile() {
|
|
550
|
+
this.prepareGroupedMessages();
|
|
551
|
+
for (const key in this.groupedMessages) {
|
|
552
|
+
const directoryPath = path.default.join(this.storagePath, key);
|
|
553
|
+
await (0, _warlock_js_fs.ensureDirectoryAsync)(directoryPath);
|
|
554
|
+
const filePath = path.default.join(directoryPath, `${this.fileName}.${this.extension}`);
|
|
555
|
+
await this.checkAndRotateFile(filePath);
|
|
556
|
+
let fileContents = { messages: [] };
|
|
557
|
+
if (await (0, _warlock_js_fs.fileExistsAsync)(filePath)) try {
|
|
558
|
+
fileContents = await (0, _warlock_js_fs.getJsonFileAsync)(filePath);
|
|
559
|
+
} catch (error) {
|
|
560
|
+
console.error("Error reading log file, reinitializing:", error);
|
|
561
|
+
fileContents = { messages: [] };
|
|
562
|
+
}
|
|
563
|
+
else fileContents = { messages: [] };
|
|
564
|
+
fileContents.messages.push(...this.groupedMessages[key]);
|
|
565
|
+
try {
|
|
566
|
+
await fs.default.promises.writeFile(filePath, safeJsonStringify(fileContents, 2));
|
|
567
|
+
} catch (error) {
|
|
568
|
+
console.error("Failed to write log:", error);
|
|
569
|
+
this.isWriting = false;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
this.onSave();
|
|
573
|
+
}
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
//#endregion
|
|
577
|
+
//#region ../../@warlock.js/logger/src/redact/redact.ts
|
|
578
|
+
/**
|
|
579
|
+
* Deep-clone a value with structural fidelity for log entries — handles plain
|
|
580
|
+
* objects, arrays, `Date`, `Error`, and primitives. Anything else is copied
|
|
581
|
+
* by reference (we only redact paths through plain objects/arrays anyway,
|
|
582
|
+
* and rebuilding e.g. a `Buffer` would change semantics).
|
|
583
|
+
*
|
|
584
|
+
* Purpose-built rather than reaching for `structuredClone`: `Error` instances
|
|
585
|
+
* lose their `message`/`stack` under `structuredClone` in some Node versions,
|
|
586
|
+
* and the logger pipeline carries them often.
|
|
587
|
+
*/
|
|
588
|
+
function cloneEntry(value, seen = /* @__PURE__ */ new WeakMap()) {
|
|
589
|
+
if (value === null || typeof value !== "object") return value;
|
|
590
|
+
if (seen.has(value)) return seen.get(value);
|
|
591
|
+
if (value instanceof Date) return new Date(value.getTime());
|
|
592
|
+
if (value instanceof Error) {
|
|
593
|
+
const copy = new value.constructor(value.message);
|
|
594
|
+
copy.stack = value.stack;
|
|
595
|
+
copy.name = value.name;
|
|
596
|
+
return copy;
|
|
597
|
+
}
|
|
598
|
+
if (Array.isArray(value)) {
|
|
599
|
+
const arr = [];
|
|
600
|
+
seen.set(value, arr);
|
|
601
|
+
for (const item of value) arr.push(cloneEntry(item, seen));
|
|
602
|
+
return arr;
|
|
603
|
+
}
|
|
604
|
+
const out = {};
|
|
605
|
+
seen.set(value, out);
|
|
606
|
+
for (const key of Object.keys(value)) out[key] = cloneEntry(value[key], seen);
|
|
607
|
+
return out;
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Apply a single censor decision to a value. String censors are returned
|
|
611
|
+
* verbatim; function censors receive the original value plus the dotted
|
|
612
|
+
* path so callers can implement value-aware redaction (mask all but the
|
|
613
|
+
* last 4 chars, hash, etc.).
|
|
614
|
+
*/
|
|
615
|
+
function applyCensor(value, censor, path) {
|
|
616
|
+
if (typeof censor === "function") return censor(value, path.join("."));
|
|
617
|
+
return censor;
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Walk `target` following the remaining `segments` of a path pattern,
|
|
621
|
+
* replacing matched leaves via `censor`. Operates in place — the caller
|
|
622
|
+
* is responsible for cloning before calling.
|
|
623
|
+
*
|
|
624
|
+
* Wildcards:
|
|
625
|
+
* - `*` matches exactly one segment (any key on a plain object, any index
|
|
626
|
+
* on an array — stringified for the path that's passed to a function
|
|
627
|
+
* censor).
|
|
628
|
+
* - `**` matches zero or more segments greedily; the rest of the pattern
|
|
629
|
+
* is then attempted at the current level and at every descendant.
|
|
630
|
+
*/
|
|
631
|
+
function redactAtPath(target, segments, censor, pathTrail) {
|
|
632
|
+
if (target === null || typeof target !== "object") return;
|
|
633
|
+
if (segments.length === 0) return;
|
|
634
|
+
const [head, ...rest] = segments;
|
|
635
|
+
if (head === "**") {
|
|
636
|
+
if (rest.length > 0) redactAtPath(target, rest, censor, pathTrail);
|
|
637
|
+
const keys = Array.isArray(target) ? target.map((_, index) => String(index)) : Object.keys(target);
|
|
638
|
+
for (const key of keys) redactAtPath(target[key], segments, censor, [...pathTrail, key]);
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
const keysToVisit = head === "*" ? Array.isArray(target) ? target.map((_, index) => String(index)) : Object.keys(target) : Array.isArray(target) ? /^\d+$/.test(head) && Number(head) < target.length ? [head] : [] : Object.prototype.hasOwnProperty.call(target, head) ? [head] : [];
|
|
642
|
+
for (const key of keysToVisit) if (rest.length === 0) target[key] = applyCensor(target[key], censor, [...pathTrail, key]);
|
|
643
|
+
else redactAtPath(target[key], rest, censor, [...pathTrail, key]);
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Produce a new `LoggingData` with every path in `config.paths` replaced
|
|
647
|
+
* by `config.censor`. The original entry is never mutated — channels and
|
|
648
|
+
* other call sites can hold references to the input safely.
|
|
649
|
+
*
|
|
650
|
+
* No-op (returns the input by reference) when `config` is `undefined` or
|
|
651
|
+
* its `paths` array is empty, so the fast path stays fast.
|
|
652
|
+
*/
|
|
653
|
+
function applyRedact(data, config) {
|
|
654
|
+
if (!config || config.paths.length === 0) return data;
|
|
655
|
+
const censor = config.censor ?? "[REDACTED]";
|
|
656
|
+
const cloned = cloneEntry(data);
|
|
657
|
+
for (const pattern of config.paths) {
|
|
658
|
+
const segments = pattern.split(".").filter((segment) => segment.length > 0);
|
|
659
|
+
if (segments.length === 0) continue;
|
|
660
|
+
redactAtPath(cloned, segments, censor, []);
|
|
661
|
+
}
|
|
662
|
+
return cloned;
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Combine two redact configs into one effective config. Used to merge a
|
|
666
|
+
* channel's additive paths on top of the logger-wide floor.
|
|
667
|
+
*
|
|
668
|
+
* - `paths` are concatenated; duplicates are kept (the matcher tolerates
|
|
669
|
+
* them, and de-duping cross-config would mask a developer typo).
|
|
670
|
+
* - `censor` from the channel wins; falls back to the logger's; falls back
|
|
671
|
+
* to the default `"[REDACTED]"`.
|
|
672
|
+
*/
|
|
673
|
+
function mergeRedact(base, extra) {
|
|
674
|
+
if (!base && !extra) return void 0;
|
|
675
|
+
if (!base) return extra;
|
|
676
|
+
if (!extra) return base;
|
|
677
|
+
return {
|
|
678
|
+
paths: [...base.paths, ...extra.paths],
|
|
679
|
+
censor: extra.censor ?? base.censor
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
//#endregion
|
|
684
|
+
//#region ../../@warlock.js/logger/src/utils/clear-message.ts
|
|
685
|
+
/**
|
|
686
|
+
* Clear message from any terminal codes
|
|
687
|
+
*/
|
|
688
|
+
function clearMessage(message) {
|
|
689
|
+
if (typeof message !== "string") return message;
|
|
690
|
+
return message.replace(/\u001b[^m]*?m/g, "");
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
//#endregion
|
|
694
|
+
//#region ../../@warlock.js/logger/src/logger.ts
|
|
695
|
+
const SIGNAL_EVENTS = new Set([
|
|
696
|
+
"SIGINT",
|
|
697
|
+
"SIGTERM",
|
|
698
|
+
"SIGHUP",
|
|
699
|
+
"SIGBREAK",
|
|
700
|
+
"SIGUSR2"
|
|
701
|
+
]);
|
|
702
|
+
/**
|
|
703
|
+
* Severity ranks used by `setMinLevel`. Higher number = more severe. The
|
|
704
|
+
* ordering matches conventional log-level hierarchies: `debug` is noisiest
|
|
705
|
+
* and easiest to drop; `error` is the loudest and never dropped by the
|
|
706
|
+
* minimum-level filter. `success` sits beside `info` — it's an informational
|
|
707
|
+
* outcome, not a warning.
|
|
708
|
+
*/
|
|
709
|
+
const LEVEL_RANK = {
|
|
710
|
+
debug: 0,
|
|
711
|
+
info: 1,
|
|
712
|
+
success: 1,
|
|
713
|
+
warn: 2,
|
|
714
|
+
error: 3
|
|
715
|
+
};
|
|
716
|
+
var Logger = class {
|
|
717
|
+
constructor() {
|
|
718
|
+
this.channels = [];
|
|
719
|
+
this.id = "logger-" + _mongez_reinforcements.Random.string(32);
|
|
720
|
+
this.autoFlushHandlers = /* @__PURE__ */ new Map();
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* Add a new channel
|
|
724
|
+
*/
|
|
725
|
+
addChannel(channel) {
|
|
726
|
+
this.channels.push(channel);
|
|
727
|
+
return this;
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Set base configurations
|
|
731
|
+
*/
|
|
732
|
+
configure(config) {
|
|
733
|
+
if (config.channels) this.channels = config.channels;
|
|
734
|
+
if (config.autoFlushOn) this.enableAutoFlush(config.autoFlushOn);
|
|
735
|
+
if (config.minLevel !== void 0) this.setMinLevel(config.minLevel);
|
|
736
|
+
if (config.redact !== void 0) this.setRedact(config.redact);
|
|
737
|
+
return this;
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* Set the logger-wide redaction floor. Applied to every entry before
|
|
741
|
+
* fan-out; channel configs add more paths on top, never fewer. Pass
|
|
742
|
+
* `undefined` to clear.
|
|
743
|
+
*
|
|
744
|
+
* @example
|
|
745
|
+
* log.setRedact({
|
|
746
|
+
* paths: ["context.password", "context.*.token"],
|
|
747
|
+
* censor: "[REDACTED]",
|
|
748
|
+
* });
|
|
749
|
+
*/
|
|
750
|
+
setRedact(config) {
|
|
751
|
+
this.redactConfig = config;
|
|
752
|
+
return this;
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* Read the active logger-wide redact config (or `undefined`).
|
|
756
|
+
*/
|
|
757
|
+
getRedact() {
|
|
758
|
+
return this.redactConfig;
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Drop every entry whose severity is below `level` before fan-out. Cheaper
|
|
762
|
+
* than per-channel `levels` filters because the loop never runs and no
|
|
763
|
+
* channel receives the entry. Pass `undefined` to clear and accept all
|
|
764
|
+
* levels again.
|
|
765
|
+
*
|
|
766
|
+
* @example
|
|
767
|
+
* // production: silence debug noise everywhere at once
|
|
768
|
+
* logger.setMinLevel("info");
|
|
769
|
+
*/
|
|
770
|
+
setMinLevel(level) {
|
|
771
|
+
this.minLevel = level;
|
|
772
|
+
return this;
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* Read the active minimum severity (or `undefined` when none is set).
|
|
776
|
+
*/
|
|
777
|
+
getMinLevel() {
|
|
778
|
+
return this.minLevel;
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* Set channels
|
|
782
|
+
*/
|
|
783
|
+
setChannels(channels) {
|
|
784
|
+
this.channels = channels;
|
|
785
|
+
return this;
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Normalize log data to a single object
|
|
789
|
+
*/
|
|
790
|
+
normalizeLogData(dataOrModule, action, message = "", level, context) {
|
|
791
|
+
if (typeof dataOrModule === "object") return {
|
|
792
|
+
type: level || dataOrModule.type || "info",
|
|
793
|
+
module: dataOrModule.module,
|
|
794
|
+
action: dataOrModule.action,
|
|
795
|
+
message: dataOrModule.message,
|
|
796
|
+
...context ? { context } : dataOrModule.context ? { context: dataOrModule.context } : {}
|
|
797
|
+
};
|
|
798
|
+
return {
|
|
799
|
+
type: level || "info",
|
|
800
|
+
module: dataOrModule,
|
|
801
|
+
action,
|
|
802
|
+
message,
|
|
803
|
+
...context ? { context } : {}
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Make log
|
|
808
|
+
*
|
|
809
|
+
* Fans out a single log entry to every registered channel. Non-terminal
|
|
810
|
+
* channels receive a copy whose `message` has had ANSI color codes stripped
|
|
811
|
+
* — each channel sees its own shallow clone so one channel cannot observe
|
|
812
|
+
* another's mutations (e.g. a later terminal channel still sees the original
|
|
813
|
+
* colored message).
|
|
814
|
+
*/
|
|
815
|
+
async log(data) {
|
|
816
|
+
if (this.minLevel && LEVEL_RANK[data.type] < LEVEL_RANK[this.minLevel]) return this;
|
|
817
|
+
const baseEntry = applyRedact(data, this.redactConfig);
|
|
818
|
+
for (const channel of this.channels) {
|
|
819
|
+
const channelRedact = channel.getRedactConfig?.();
|
|
820
|
+
const effectiveRedact = channelRedact ? mergeRedact(this.redactConfig, channelRedact) : void 0;
|
|
821
|
+
let payload = effectiveRedact ? applyRedact(data, effectiveRedact) : baseEntry;
|
|
822
|
+
if (channel.terminal === false) payload = {
|
|
823
|
+
...payload,
|
|
824
|
+
message: clearMessage(payload.message)
|
|
825
|
+
};
|
|
826
|
+
channel.log(payload);
|
|
827
|
+
}
|
|
828
|
+
return this;
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
831
|
+
* Make debug log
|
|
832
|
+
*/
|
|
833
|
+
debug(dataOrModule, action, message = "", context) {
|
|
834
|
+
const data = this.normalizeLogData(dataOrModule, action, message, "debug", context);
|
|
835
|
+
return this.log(data);
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* Make info log
|
|
839
|
+
*/
|
|
840
|
+
info(dataOrModule, action, message = "", context) {
|
|
841
|
+
const data = this.normalizeLogData(dataOrModule, action, message, "info", context);
|
|
842
|
+
return this.log(data);
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* Make warn log
|
|
846
|
+
*/
|
|
847
|
+
warn(dataOrModule, action, message = "", context) {
|
|
848
|
+
const data = this.normalizeLogData(dataOrModule, action, message, "warn", context);
|
|
849
|
+
return this.log(data);
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* Make error log
|
|
853
|
+
*/
|
|
854
|
+
error(dataOrModule, action, message = "", context) {
|
|
855
|
+
const data = this.normalizeLogData(dataOrModule, action, message, "error", context);
|
|
856
|
+
return this.log(data);
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* Make success log
|
|
860
|
+
*/
|
|
861
|
+
success(dataOrModule, action, message = "", context) {
|
|
862
|
+
const data = this.normalizeLogData(dataOrModule, action, message, "success", context);
|
|
863
|
+
return this.log(data);
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* Log an `error` entry when `condition` is falsy. No-op otherwise — the
|
|
867
|
+
* entry is never built and channels are not invoked, so this is genuinely
|
|
868
|
+
* free in the happy path. Mirrors the spirit of `console.assert` but routes
|
|
869
|
+
* through the logger pipeline so persistent channels capture failures.
|
|
870
|
+
*
|
|
871
|
+
* @example
|
|
872
|
+
* log.assert(user !== null, "auth", "session", "user vanished mid-flight", { sessionId });
|
|
873
|
+
*/
|
|
874
|
+
assert(condition, module, action, message, context) {
|
|
875
|
+
if (condition) return this;
|
|
876
|
+
return this.error(module, action, message, context);
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* Start a duration timer. The returned function emits an `info` entry
|
|
880
|
+
* with `completed in <ms>ms` and a `durationMs` field in `context` when
|
|
881
|
+
* called. Pass an object to `end()` to merge extra fields into context.
|
|
882
|
+
*
|
|
883
|
+
* @example
|
|
884
|
+
* const end = log.timer("db", "users.findById");
|
|
885
|
+
* const user = await usersRepo.findById(id);
|
|
886
|
+
* end({ id, found: !!user });
|
|
887
|
+
*/
|
|
888
|
+
timer(module, action) {
|
|
889
|
+
const startedAt = Date.now();
|
|
890
|
+
return (extra) => {
|
|
891
|
+
const durationMs = Date.now() - startedAt;
|
|
892
|
+
return this.info(module, action, `completed in ${durationMs}ms`, {
|
|
893
|
+
durationMs,
|
|
894
|
+
...extra ?? {}
|
|
895
|
+
});
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* Get channel by name
|
|
900
|
+
*/
|
|
901
|
+
channel(name) {
|
|
902
|
+
return this.channels.find((channel) => channel.name === name);
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* Synchronously flush logs
|
|
906
|
+
*/
|
|
907
|
+
flushSync() {
|
|
908
|
+
for (const channel of this.channels) if (channel.flushSync) channel.flushSync();
|
|
909
|
+
}
|
|
910
|
+
/**
|
|
911
|
+
* Register one process-level handler per event that calls `flushSync()`
|
|
912
|
+
* before the process terminates.
|
|
913
|
+
*
|
|
914
|
+
* For signal events (`SIGINT`, `SIGTERM`, `SIGHUP`, `SIGBREAK`, `SIGUSR2`)
|
|
915
|
+
* the handler flushes and then re-raises the signal so Node's default exit
|
|
916
|
+
* behavior runs. For `beforeExit`, the handler flushes in place — Node exits
|
|
917
|
+
* naturally afterwards.
|
|
918
|
+
*
|
|
919
|
+
* Idempotent: calling with the same events replaces the previous handlers.
|
|
920
|
+
* Call `disableAutoFlush()` to unregister.
|
|
921
|
+
*
|
|
922
|
+
* @example
|
|
923
|
+
* log.configure({
|
|
924
|
+
* channels: [new ConsoleLog(), new FileLog()],
|
|
925
|
+
* autoFlushOn: ["SIGINT", "SIGTERM", "beforeExit"],
|
|
926
|
+
* });
|
|
927
|
+
*/
|
|
928
|
+
enableAutoFlush(events) {
|
|
929
|
+
this.disableAutoFlush();
|
|
930
|
+
for (const event of events) {
|
|
931
|
+
const handler = SIGNAL_EVENTS.has(event) ? () => {
|
|
932
|
+
this.flushSync();
|
|
933
|
+
process.off(event, handler);
|
|
934
|
+
process.kill(process.pid, event);
|
|
935
|
+
} : () => {
|
|
936
|
+
this.flushSync();
|
|
937
|
+
};
|
|
938
|
+
process.on(event, handler);
|
|
939
|
+
this.autoFlushHandlers.set(event, handler);
|
|
940
|
+
}
|
|
941
|
+
return this;
|
|
942
|
+
}
|
|
943
|
+
/**
|
|
944
|
+
* Remove every handler previously registered by `enableAutoFlush`.
|
|
945
|
+
* Safe to call when no handlers are registered.
|
|
946
|
+
*/
|
|
947
|
+
disableAutoFlush() {
|
|
948
|
+
for (const [event, handler] of this.autoFlushHandlers) process.off(event, handler);
|
|
949
|
+
this.autoFlushHandlers.clear();
|
|
950
|
+
return this;
|
|
951
|
+
}
|
|
952
|
+
};
|
|
953
|
+
/**
|
|
954
|
+
* The package singleton. Use this for everyday logging — `log.info(...)`,
|
|
955
|
+
* `log.error(...)`, `log.configure(...)`. Custom logger instances can be
|
|
956
|
+
* created by instantiating `Logger` directly.
|
|
957
|
+
*
|
|
958
|
+
* The name is intentionally short: `log` reads naturally at the call site
|
|
959
|
+
* (`log.info("auth", "login", "ok")`) and matches the convention used in
|
|
960
|
+
* pino, bunyan, and most JS logging tutorials.
|
|
961
|
+
*
|
|
962
|
+
* Note that `log` is a `Logger` instance, **not** a function — the bare
|
|
963
|
+
* callable form was removed when the dual `log` / `logger` exports were
|
|
964
|
+
* collapsed into a single name. Use `log.info(...)` (or any other level
|
|
965
|
+
* shortcut) to emit entries.
|
|
966
|
+
*/
|
|
967
|
+
const log = new Logger();
|
|
968
|
+
|
|
969
|
+
//#endregion
|
|
970
|
+
//#region ../../@warlock.js/logger/src/utils/capture-unhandled-errors.ts
|
|
971
|
+
/**
|
|
972
|
+
* Route Node's process-level failure events through the logger so they land in
|
|
973
|
+
* every configured channel with full stack context. Registers one listener for
|
|
974
|
+
* `unhandledRejection` and one for `uncaughtException`; call once at startup
|
|
975
|
+
* after channels are configured. Pair with `autoFlushOn: ["beforeExit"]` so the
|
|
976
|
+
* final entry survives the process exit that follows an uncaught exception.
|
|
977
|
+
*
|
|
978
|
+
* @example
|
|
979
|
+
* log.configure({ channels: [new ConsoleLog(), new FileLog()] });
|
|
980
|
+
* captureAnyUnhandledRejection();
|
|
981
|
+
*/
|
|
982
|
+
function captureAnyUnhandledRejection() {
|
|
983
|
+
process.on("unhandledRejection", (reason) => {
|
|
984
|
+
log.error("app", "unhandledRejection", reason);
|
|
985
|
+
});
|
|
986
|
+
process.on("uncaughtException", (error) => {
|
|
987
|
+
log.error("app", "uncaughtException", error);
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
//#endregion
|
|
992
|
+
exports.ConsoleLog = ConsoleLog;
|
|
993
|
+
exports.FileLog = FileLog;
|
|
994
|
+
exports.JSONFileLog = JSONFileLog;
|
|
995
|
+
exports.LogChannel = LogChannel;
|
|
996
|
+
exports.Logger = Logger;
|
|
997
|
+
exports.applyRedact = applyRedact;
|
|
998
|
+
exports.captureAnyUnhandledRejection = captureAnyUnhandledRejection;
|
|
999
|
+
exports.clearMessage = clearMessage;
|
|
1000
|
+
exports.log = log;
|
|
1001
|
+
exports.mergeRedact = mergeRedact;
|
|
1002
|
+
exports.safeJsonStringify = safeJsonStringify;
|
|
1003
|
+
//# sourceMappingURL=index.cjs.map
|