@oxog/log 1.0.0
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/LICENSE +21 -0
- package/README.md +297 -0
- package/dist/index.cjs +1893 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +1784 -0
- package/dist/index.js.map +1 -0
- package/dist/plugins/index.cjs +854 -0
- package/dist/plugins/index.cjs.map +1 -0
- package/dist/plugins/index.js +782 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/transports/index.cjs +853 -0
- package/dist/transports/index.cjs.map +1 -0
- package/dist/transports/index.js +809 -0
- package/dist/transports/index.js.map +1 -0
- package/package.json +96 -0
|
@@ -0,0 +1,809 @@
|
|
|
1
|
+
// src/constants.ts
|
|
2
|
+
var LOG_LEVELS = {
|
|
3
|
+
trace: 10 /* Trace */,
|
|
4
|
+
debug: 20 /* Debug */,
|
|
5
|
+
info: 30 /* Info */,
|
|
6
|
+
warn: 40 /* Warn */,
|
|
7
|
+
error: 50 /* Error */,
|
|
8
|
+
fatal: 60 /* Fatal */
|
|
9
|
+
};
|
|
10
|
+
var LEVEL_NAMES = {
|
|
11
|
+
[10 /* Trace */]: "trace",
|
|
12
|
+
[20 /* Debug */]: "debug",
|
|
13
|
+
[30 /* Info */]: "info",
|
|
14
|
+
[40 /* Warn */]: "warn",
|
|
15
|
+
[50 /* Error */]: "error",
|
|
16
|
+
[60 /* Fatal */]: "fatal"
|
|
17
|
+
};
|
|
18
|
+
var LEVEL_COLORS = {
|
|
19
|
+
trace: "gray",
|
|
20
|
+
debug: "cyan",
|
|
21
|
+
info: "blue",
|
|
22
|
+
warn: "yellow",
|
|
23
|
+
error: "red",
|
|
24
|
+
fatal: "magenta"
|
|
25
|
+
};
|
|
26
|
+
var LEVEL_LABELS = {
|
|
27
|
+
trace: "TRACE",
|
|
28
|
+
debug: "DEBUG",
|
|
29
|
+
info: "INFO ",
|
|
30
|
+
warn: "WARN ",
|
|
31
|
+
error: "ERROR",
|
|
32
|
+
fatal: "FATAL"
|
|
33
|
+
};
|
|
34
|
+
function parseSize(size) {
|
|
35
|
+
const match = size.match(/^(\d+(?:\.\d+)?)\s*(B|KB|MB|GB|TB)?$/i);
|
|
36
|
+
if (!match) {
|
|
37
|
+
throw new Error(`Invalid size format: ${size}`);
|
|
38
|
+
}
|
|
39
|
+
const value = parseFloat(match[1]);
|
|
40
|
+
const unit = (match[2] || "B").toUpperCase();
|
|
41
|
+
const units = {
|
|
42
|
+
B: 1,
|
|
43
|
+
KB: 1024,
|
|
44
|
+
MB: 1024 * 1024,
|
|
45
|
+
GB: 1024 * 1024 * 1024,
|
|
46
|
+
TB: 1024 * 1024 * 1024 * 1024
|
|
47
|
+
};
|
|
48
|
+
return Math.floor(value * (units[unit] ?? 1));
|
|
49
|
+
}
|
|
50
|
+
function parseRotation(rotation) {
|
|
51
|
+
const match = rotation.match(/^(\d+)\s*(s|m|h|d|w)?$/i);
|
|
52
|
+
if (!match) {
|
|
53
|
+
throw new Error(`Invalid rotation format: ${rotation}`);
|
|
54
|
+
}
|
|
55
|
+
const value = parseInt(match[1], 10);
|
|
56
|
+
const unit = (match[2] || "s").toLowerCase();
|
|
57
|
+
const units = {
|
|
58
|
+
s: 1e3,
|
|
59
|
+
m: 60 * 1e3,
|
|
60
|
+
h: 60 * 60 * 1e3,
|
|
61
|
+
d: 24 * 60 * 60 * 1e3,
|
|
62
|
+
w: 7 * 24 * 60 * 60 * 1e3
|
|
63
|
+
};
|
|
64
|
+
return value * (units[unit] ?? 1e3);
|
|
65
|
+
}
|
|
66
|
+
var IS_NODE = typeof process !== "undefined" && process.versions != null && process.versions.node != null;
|
|
67
|
+
var IS_BROWSER = typeof window !== "undefined" && typeof window.document !== "undefined";
|
|
68
|
+
var IS_DEV = IS_NODE && process.env["NODE_ENV"] !== "production";
|
|
69
|
+
var IS_TTY = IS_NODE && process.stdout?.isTTY === true;
|
|
70
|
+
|
|
71
|
+
// src/utils/format.ts
|
|
72
|
+
function formatJson(entry) {
|
|
73
|
+
return JSON.stringify(entry, jsonReplacer);
|
|
74
|
+
}
|
|
75
|
+
function jsonReplacer(_key, value) {
|
|
76
|
+
if (typeof value === "bigint") {
|
|
77
|
+
return value.toString();
|
|
78
|
+
}
|
|
79
|
+
if (value instanceof Error) {
|
|
80
|
+
const errorObj = value;
|
|
81
|
+
const result = {
|
|
82
|
+
name: value.name,
|
|
83
|
+
message: value.message,
|
|
84
|
+
stack: value.stack
|
|
85
|
+
};
|
|
86
|
+
for (const key of Object.keys(errorObj)) {
|
|
87
|
+
if (!(key in result)) {
|
|
88
|
+
result[key] = errorObj[key];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
if (typeof value === "object" && value !== null) {
|
|
94
|
+
if (value instanceof RegExp) {
|
|
95
|
+
return value.toString();
|
|
96
|
+
}
|
|
97
|
+
if (value instanceof Date) {
|
|
98
|
+
return value.toISOString();
|
|
99
|
+
}
|
|
100
|
+
if (value instanceof Map) {
|
|
101
|
+
return Object.fromEntries(value);
|
|
102
|
+
}
|
|
103
|
+
if (value instanceof Set) {
|
|
104
|
+
return Array.from(value);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return value;
|
|
108
|
+
}
|
|
109
|
+
function safeStringify(value, indent) {
|
|
110
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
111
|
+
return JSON.stringify(
|
|
112
|
+
value,
|
|
113
|
+
(_key, val) => {
|
|
114
|
+
const replaced = jsonReplacer(_key, val);
|
|
115
|
+
if (typeof replaced === "object" && replaced !== null) {
|
|
116
|
+
if (seen.has(replaced)) {
|
|
117
|
+
return "[Circular]";
|
|
118
|
+
}
|
|
119
|
+
seen.add(replaced);
|
|
120
|
+
}
|
|
121
|
+
return replaced;
|
|
122
|
+
},
|
|
123
|
+
indent
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
function formatPretty(entry, pigment, options = {}) {
|
|
127
|
+
const parts = [];
|
|
128
|
+
const { timestamp = true, source = true } = options;
|
|
129
|
+
if (timestamp && entry.time) {
|
|
130
|
+
const time = formatTime(entry.time);
|
|
131
|
+
parts.push(pigment ? pigment.gray(`[${time}]`) : `[${time}]`);
|
|
132
|
+
}
|
|
133
|
+
const levelLabel = LEVEL_LABELS[entry.levelName] || entry.levelName.toUpperCase();
|
|
134
|
+
const levelColor = LEVEL_COLORS[entry.levelName] || "white";
|
|
135
|
+
if (pigment) {
|
|
136
|
+
const colorFn = pigment[levelColor];
|
|
137
|
+
parts.push(colorFn ? colorFn(levelLabel) : levelLabel);
|
|
138
|
+
} else {
|
|
139
|
+
parts.push(levelLabel);
|
|
140
|
+
}
|
|
141
|
+
if (source && entry.file) {
|
|
142
|
+
const location = entry.line ? `${entry.file}:${entry.line}` : entry.file;
|
|
143
|
+
parts.push(pigment ? pigment.dim(`(${location})`) : `(${location})`);
|
|
144
|
+
}
|
|
145
|
+
if (entry.msg) {
|
|
146
|
+
parts.push(entry.msg);
|
|
147
|
+
}
|
|
148
|
+
const extra = getExtraFields(entry);
|
|
149
|
+
if (Object.keys(extra).length > 0) {
|
|
150
|
+
const extraStr = safeStringify(extra);
|
|
151
|
+
parts.push(pigment ? pigment.dim(extraStr) : extraStr);
|
|
152
|
+
}
|
|
153
|
+
if (entry.err?.stack) {
|
|
154
|
+
parts.push("\n" + (pigment ? pigment.red(entry.err.stack) : entry.err.stack));
|
|
155
|
+
}
|
|
156
|
+
return parts.join(" ");
|
|
157
|
+
}
|
|
158
|
+
function getExtraFields(entry) {
|
|
159
|
+
const standardFields = /* @__PURE__ */ new Set([
|
|
160
|
+
"level",
|
|
161
|
+
"levelName",
|
|
162
|
+
"time",
|
|
163
|
+
"msg",
|
|
164
|
+
"file",
|
|
165
|
+
"line",
|
|
166
|
+
"column",
|
|
167
|
+
"correlationId",
|
|
168
|
+
"duration",
|
|
169
|
+
"err",
|
|
170
|
+
"name"
|
|
171
|
+
]);
|
|
172
|
+
const extra = {};
|
|
173
|
+
for (const [key, value] of Object.entries(entry)) {
|
|
174
|
+
if (!standardFields.has(key)) {
|
|
175
|
+
extra[key] = value;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return extra;
|
|
179
|
+
}
|
|
180
|
+
function formatTime(timestamp) {
|
|
181
|
+
const date = new Date(timestamp);
|
|
182
|
+
const hours = date.getHours().toString().padStart(2, "0");
|
|
183
|
+
const minutes = date.getMinutes().toString().padStart(2, "0");
|
|
184
|
+
const seconds = date.getSeconds().toString().padStart(2, "0");
|
|
185
|
+
return `${hours}:${minutes}:${seconds}`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// src/utils/env.ts
|
|
189
|
+
function isNode() {
|
|
190
|
+
return typeof process !== "undefined" && process.versions != null && process.versions.node != null;
|
|
191
|
+
}
|
|
192
|
+
function isBrowser() {
|
|
193
|
+
return typeof window !== "undefined" && typeof window.document !== "undefined";
|
|
194
|
+
}
|
|
195
|
+
function isTTY() {
|
|
196
|
+
if (!isNode()) return false;
|
|
197
|
+
return process.stdout?.isTTY === true;
|
|
198
|
+
}
|
|
199
|
+
function shouldUseColors() {
|
|
200
|
+
if (isBrowser()) return true;
|
|
201
|
+
if (!isNode()) return false;
|
|
202
|
+
if (process.env["NO_COLOR"] !== void 0) return false;
|
|
203
|
+
if (process.env["FORCE_COLOR"] !== void 0) return true;
|
|
204
|
+
if (process.env["CI"] !== void 0) return true;
|
|
205
|
+
return isTTY();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// src/transports/console.ts
|
|
209
|
+
var DEFAULT_LEVEL_COLORS = {
|
|
210
|
+
trace: "gray",
|
|
211
|
+
debug: "cyan",
|
|
212
|
+
info: "blue",
|
|
213
|
+
warn: "yellow",
|
|
214
|
+
error: "red",
|
|
215
|
+
fatal: "magenta"
|
|
216
|
+
};
|
|
217
|
+
function consoleTransport(options = {}) {
|
|
218
|
+
const {
|
|
219
|
+
colors = shouldUseColors(),
|
|
220
|
+
timestamp = true,
|
|
221
|
+
levelColors = {}
|
|
222
|
+
} = options;
|
|
223
|
+
const mergedColors = { ...DEFAULT_LEVEL_COLORS, ...levelColors };
|
|
224
|
+
let pigment;
|
|
225
|
+
return {
|
|
226
|
+
name: "console",
|
|
227
|
+
write(entry) {
|
|
228
|
+
let output;
|
|
229
|
+
if (isBrowser()) {
|
|
230
|
+
writeToBrowserConsole(entry, colors);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (colors && pigment) {
|
|
234
|
+
output = formatPretty(entry, pigment, { timestamp, source: true });
|
|
235
|
+
} else if (colors) {
|
|
236
|
+
output = formatWithAnsi(entry, mergedColors, timestamp);
|
|
237
|
+
} else {
|
|
238
|
+
output = formatJson(entry);
|
|
239
|
+
}
|
|
240
|
+
if (entry.level >= 50) {
|
|
241
|
+
process.stderr.write(output + "\n");
|
|
242
|
+
} else {
|
|
243
|
+
process.stdout.write(output + "\n");
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
flush() {
|
|
247
|
+
},
|
|
248
|
+
close() {
|
|
249
|
+
},
|
|
250
|
+
supports(env) {
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
function formatWithAnsi(entry, levelColors, showTimestamp) {
|
|
256
|
+
const parts = [];
|
|
257
|
+
if (showTimestamp && entry.time) {
|
|
258
|
+
const time = formatTime2(entry.time);
|
|
259
|
+
parts.push(`\x1B[90m[${time}]\x1B[0m`);
|
|
260
|
+
}
|
|
261
|
+
const color = getAnsiColor(levelColors[entry.levelName] || "white");
|
|
262
|
+
const levelLabel = entry.levelName.toUpperCase().padEnd(5);
|
|
263
|
+
parts.push(`${color}${levelLabel}\x1B[0m`);
|
|
264
|
+
if (entry.file) {
|
|
265
|
+
const location = entry.line ? `${entry.file}:${entry.line}` : entry.file;
|
|
266
|
+
parts.push(`\x1B[90m(${location})\x1B[0m`);
|
|
267
|
+
}
|
|
268
|
+
if (entry.msg) {
|
|
269
|
+
parts.push(entry.msg);
|
|
270
|
+
}
|
|
271
|
+
const extra = getExtraFields2(entry);
|
|
272
|
+
if (Object.keys(extra).length > 0) {
|
|
273
|
+
parts.push(`\x1B[90m${JSON.stringify(extra)}\x1B[0m`);
|
|
274
|
+
}
|
|
275
|
+
if (entry.err?.stack) {
|
|
276
|
+
parts.push("\n\x1B[31m" + entry.err.stack + "\x1B[0m");
|
|
277
|
+
}
|
|
278
|
+
return parts.join(" ");
|
|
279
|
+
}
|
|
280
|
+
function getAnsiColor(colorName) {
|
|
281
|
+
const colors = {
|
|
282
|
+
black: "\x1B[30m",
|
|
283
|
+
red: "\x1B[31m",
|
|
284
|
+
green: "\x1B[32m",
|
|
285
|
+
yellow: "\x1B[33m",
|
|
286
|
+
blue: "\x1B[34m",
|
|
287
|
+
magenta: "\x1B[35m",
|
|
288
|
+
cyan: "\x1B[36m",
|
|
289
|
+
white: "\x1B[37m",
|
|
290
|
+
gray: "\x1B[90m",
|
|
291
|
+
grey: "\x1B[90m"
|
|
292
|
+
};
|
|
293
|
+
return colors[colorName] || "\x1B[0m";
|
|
294
|
+
}
|
|
295
|
+
function formatTime2(timestamp) {
|
|
296
|
+
const date = new Date(timestamp);
|
|
297
|
+
return date.toTimeString().slice(0, 8);
|
|
298
|
+
}
|
|
299
|
+
function getExtraFields2(entry) {
|
|
300
|
+
const standardFields = /* @__PURE__ */ new Set([
|
|
301
|
+
"level",
|
|
302
|
+
"levelName",
|
|
303
|
+
"time",
|
|
304
|
+
"msg",
|
|
305
|
+
"file",
|
|
306
|
+
"line",
|
|
307
|
+
"column",
|
|
308
|
+
"correlationId",
|
|
309
|
+
"duration",
|
|
310
|
+
"err",
|
|
311
|
+
"name"
|
|
312
|
+
]);
|
|
313
|
+
const extra = {};
|
|
314
|
+
for (const [key, value] of Object.entries(entry)) {
|
|
315
|
+
if (!standardFields.has(key)) {
|
|
316
|
+
extra[key] = value;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return extra;
|
|
320
|
+
}
|
|
321
|
+
function writeToBrowserConsole(entry, styled) {
|
|
322
|
+
const method = getConsoleMethod(entry.levelName);
|
|
323
|
+
const consoleObj = console;
|
|
324
|
+
if (styled) {
|
|
325
|
+
const styles = getBrowserStyles(entry.levelName);
|
|
326
|
+
const label = `%c[${entry.levelName.toUpperCase()}]`;
|
|
327
|
+
const extra = getExtraFields2(entry);
|
|
328
|
+
if (Object.keys(extra).length > 0) {
|
|
329
|
+
consoleObj[method](label, styles, entry.msg, extra);
|
|
330
|
+
} else {
|
|
331
|
+
consoleObj[method](label, styles, entry.msg);
|
|
332
|
+
}
|
|
333
|
+
} else {
|
|
334
|
+
consoleObj[method](`[${entry.levelName.toUpperCase()}]`, entry.msg, entry);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
function getConsoleMethod(levelName) {
|
|
338
|
+
switch (levelName) {
|
|
339
|
+
case "trace":
|
|
340
|
+
case "debug":
|
|
341
|
+
return "debug";
|
|
342
|
+
case "info":
|
|
343
|
+
return "info";
|
|
344
|
+
case "warn":
|
|
345
|
+
return "warn";
|
|
346
|
+
case "error":
|
|
347
|
+
case "fatal":
|
|
348
|
+
return "error";
|
|
349
|
+
default:
|
|
350
|
+
return "log";
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
function getBrowserStyles(levelName) {
|
|
354
|
+
const colors = {
|
|
355
|
+
trace: "#888888",
|
|
356
|
+
debug: "#00bcd4",
|
|
357
|
+
info: "#2196f3",
|
|
358
|
+
warn: "#ff9800",
|
|
359
|
+
error: "#f44336",
|
|
360
|
+
fatal: "#9c27b0"
|
|
361
|
+
};
|
|
362
|
+
return `color: ${colors[levelName]}; font-weight: bold;`;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// src/errors.ts
|
|
366
|
+
var LogError = class extends Error {
|
|
367
|
+
/** Error code for programmatic handling */
|
|
368
|
+
code;
|
|
369
|
+
/** Original error if wrapping another error */
|
|
370
|
+
cause;
|
|
371
|
+
constructor(message, code, cause) {
|
|
372
|
+
super(message);
|
|
373
|
+
this.name = "LogError";
|
|
374
|
+
this.code = code;
|
|
375
|
+
this.cause = cause;
|
|
376
|
+
if (Error.captureStackTrace) {
|
|
377
|
+
Error.captureStackTrace(this, this.constructor);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
var TransportError = class extends LogError {
|
|
382
|
+
/** Name of the transport that caused the error */
|
|
383
|
+
transportName;
|
|
384
|
+
constructor(message, transportName, cause) {
|
|
385
|
+
super(`[Transport: ${transportName}] ${message}`, "TRANSPORT_ERROR", cause);
|
|
386
|
+
this.name = "TransportError";
|
|
387
|
+
this.transportName = transportName;
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
var EnvironmentError = class extends LogError {
|
|
391
|
+
/** Required environment */
|
|
392
|
+
requiredEnv;
|
|
393
|
+
constructor(message, requiredEnv, cause) {
|
|
394
|
+
super(message, "ENVIRONMENT_ERROR", cause);
|
|
395
|
+
this.name = "EnvironmentError";
|
|
396
|
+
this.requiredEnv = requiredEnv;
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
// src/transports/file.ts
|
|
401
|
+
function fileTransport(options) {
|
|
402
|
+
if (!isNode()) {
|
|
403
|
+
throw new EnvironmentError("File transport is only available in Node.js", "node");
|
|
404
|
+
}
|
|
405
|
+
const {
|
|
406
|
+
path: filePath,
|
|
407
|
+
rotate,
|
|
408
|
+
maxSize,
|
|
409
|
+
maxFiles = 5,
|
|
410
|
+
compress = false
|
|
411
|
+
} = options;
|
|
412
|
+
const maxSizeBytes = maxSize ? parseSize(maxSize) : void 0;
|
|
413
|
+
const rotationMs = rotate ? parseRotation(rotate) : void 0;
|
|
414
|
+
let currentSize = 0;
|
|
415
|
+
let lastRotation = Date.now();
|
|
416
|
+
let writeStream = null;
|
|
417
|
+
let fs = null;
|
|
418
|
+
let path = null;
|
|
419
|
+
let zlib = null;
|
|
420
|
+
async function ensureModules() {
|
|
421
|
+
if (!fs) {
|
|
422
|
+
fs = await import("fs");
|
|
423
|
+
path = await import("path");
|
|
424
|
+
if (compress) {
|
|
425
|
+
zlib = await import("zlib");
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
async function ensureStream() {
|
|
430
|
+
await ensureModules();
|
|
431
|
+
if (!writeStream) {
|
|
432
|
+
const dir = path.dirname(filePath);
|
|
433
|
+
if (!fs.existsSync(dir)) {
|
|
434
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
435
|
+
}
|
|
436
|
+
if (fs.existsSync(filePath)) {
|
|
437
|
+
const stats = fs.statSync(filePath);
|
|
438
|
+
currentSize = stats.size;
|
|
439
|
+
}
|
|
440
|
+
writeStream = fs.createWriteStream(filePath, { flags: "a" });
|
|
441
|
+
}
|
|
442
|
+
return writeStream;
|
|
443
|
+
}
|
|
444
|
+
function shouldRotate() {
|
|
445
|
+
if (maxSizeBytes && currentSize >= maxSizeBytes) {
|
|
446
|
+
return true;
|
|
447
|
+
}
|
|
448
|
+
if (rotationMs && Date.now() - lastRotation >= rotationMs) {
|
|
449
|
+
return true;
|
|
450
|
+
}
|
|
451
|
+
return false;
|
|
452
|
+
}
|
|
453
|
+
async function performRotation() {
|
|
454
|
+
await ensureModules();
|
|
455
|
+
if (!writeStream) return;
|
|
456
|
+
await new Promise((resolve, reject) => {
|
|
457
|
+
writeStream.once("finish", resolve);
|
|
458
|
+
writeStream.once("error", reject);
|
|
459
|
+
writeStream.end();
|
|
460
|
+
});
|
|
461
|
+
writeStream = null;
|
|
462
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
463
|
+
const ext = path.extname(filePath);
|
|
464
|
+
const base = filePath.slice(0, -ext.length);
|
|
465
|
+
const rotatedPath = `${base}.${timestamp}${ext}`;
|
|
466
|
+
fs.renameSync(filePath, rotatedPath);
|
|
467
|
+
if (compress && zlib) {
|
|
468
|
+
const gzPath = `${rotatedPath}.gz`;
|
|
469
|
+
const input = fs.createReadStream(rotatedPath);
|
|
470
|
+
const output = fs.createWriteStream(gzPath);
|
|
471
|
+
const gzip = zlib.createGzip();
|
|
472
|
+
await new Promise((resolve, reject) => {
|
|
473
|
+
input.pipe(gzip).pipe(output);
|
|
474
|
+
output.on("finish", () => {
|
|
475
|
+
fs.unlinkSync(rotatedPath);
|
|
476
|
+
resolve();
|
|
477
|
+
});
|
|
478
|
+
output.on("error", reject);
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
await cleanupOldFiles();
|
|
482
|
+
currentSize = 0;
|
|
483
|
+
lastRotation = Date.now();
|
|
484
|
+
}
|
|
485
|
+
async function cleanupOldFiles() {
|
|
486
|
+
await ensureModules();
|
|
487
|
+
const dir = path.dirname(filePath);
|
|
488
|
+
const baseName = path.basename(filePath);
|
|
489
|
+
const ext = path.extname(filePath);
|
|
490
|
+
const prefix = baseName.slice(0, -ext.length);
|
|
491
|
+
const files = fs.readdirSync(dir).filter((f) => f.startsWith(prefix) && f !== baseName).map((f) => ({
|
|
492
|
+
name: f,
|
|
493
|
+
path: path.join(dir, f),
|
|
494
|
+
mtime: fs.statSync(path.join(dir, f)).mtime.getTime()
|
|
495
|
+
})).sort((a, b) => b.mtime - a.mtime);
|
|
496
|
+
while (files.length > maxFiles) {
|
|
497
|
+
const file = files.pop();
|
|
498
|
+
fs.unlinkSync(file.path);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return {
|
|
502
|
+
name: "file",
|
|
503
|
+
async write(entry) {
|
|
504
|
+
if (shouldRotate()) {
|
|
505
|
+
await performRotation();
|
|
506
|
+
}
|
|
507
|
+
const stream = await ensureStream();
|
|
508
|
+
const line = formatJson(entry) + "\n";
|
|
509
|
+
const bytes = Buffer.byteLength(line, "utf8");
|
|
510
|
+
return new Promise((resolve, reject) => {
|
|
511
|
+
stream.write(line, (err) => {
|
|
512
|
+
if (err) {
|
|
513
|
+
reject(new TransportError(`Failed to write to file: ${err.message}`, "file", err));
|
|
514
|
+
} else {
|
|
515
|
+
currentSize += bytes;
|
|
516
|
+
resolve();
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
});
|
|
520
|
+
},
|
|
521
|
+
async flush() {
|
|
522
|
+
if (writeStream && "flush" in writeStream) {
|
|
523
|
+
return new Promise((resolve) => {
|
|
524
|
+
writeStream.flush(resolve);
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
},
|
|
528
|
+
async close() {
|
|
529
|
+
if (writeStream) {
|
|
530
|
+
await new Promise((resolve, reject) => {
|
|
531
|
+
writeStream.once("finish", resolve);
|
|
532
|
+
writeStream.once("error", reject);
|
|
533
|
+
writeStream.end();
|
|
534
|
+
});
|
|
535
|
+
writeStream = null;
|
|
536
|
+
}
|
|
537
|
+
},
|
|
538
|
+
supports(env) {
|
|
539
|
+
return env === "node";
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// src/transports/stream.ts
|
|
545
|
+
function streamTransport(options) {
|
|
546
|
+
if (!isNode()) {
|
|
547
|
+
throw new EnvironmentError("Stream transport is only available in Node.js", "node");
|
|
548
|
+
}
|
|
549
|
+
const { stream } = options;
|
|
550
|
+
if (!stream) {
|
|
551
|
+
throw new TransportError("Stream is required", "stream");
|
|
552
|
+
}
|
|
553
|
+
let closed = false;
|
|
554
|
+
return {
|
|
555
|
+
name: "stream",
|
|
556
|
+
write(entry) {
|
|
557
|
+
if (closed) {
|
|
558
|
+
return Promise.reject(new TransportError("Stream is closed", "stream"));
|
|
559
|
+
}
|
|
560
|
+
const line = formatJson(entry) + "\n";
|
|
561
|
+
return new Promise((resolve, reject) => {
|
|
562
|
+
const canWrite = stream.write(line, (err) => {
|
|
563
|
+
if (err) {
|
|
564
|
+
reject(new TransportError(`Failed to write to stream: ${err.message}`, "stream", err));
|
|
565
|
+
} else {
|
|
566
|
+
resolve();
|
|
567
|
+
}
|
|
568
|
+
});
|
|
569
|
+
if (!canWrite) {
|
|
570
|
+
stream.once("drain", () => {
|
|
571
|
+
resolve();
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
},
|
|
576
|
+
flush() {
|
|
577
|
+
return Promise.resolve();
|
|
578
|
+
},
|
|
579
|
+
async close() {
|
|
580
|
+
if (closed) return;
|
|
581
|
+
closed = true;
|
|
582
|
+
return new Promise((resolve, reject) => {
|
|
583
|
+
stream.once("finish", resolve);
|
|
584
|
+
stream.once("error", (err) => {
|
|
585
|
+
reject(new TransportError(`Failed to close stream: ${err.message}`, "stream", err));
|
|
586
|
+
});
|
|
587
|
+
stream.end();
|
|
588
|
+
});
|
|
589
|
+
},
|
|
590
|
+
supports(env) {
|
|
591
|
+
return env === "node";
|
|
592
|
+
}
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// src/transports/http.ts
|
|
597
|
+
function httpTransport(options) {
|
|
598
|
+
const {
|
|
599
|
+
url,
|
|
600
|
+
method = "POST",
|
|
601
|
+
headers = {},
|
|
602
|
+
batch = 100,
|
|
603
|
+
interval = 5e3,
|
|
604
|
+
retry = 3
|
|
605
|
+
} = options;
|
|
606
|
+
if (!url) {
|
|
607
|
+
throw new TransportError("URL is required", "http");
|
|
608
|
+
}
|
|
609
|
+
let buffer = [];
|
|
610
|
+
let flushTimer = null;
|
|
611
|
+
let isFlushing = false;
|
|
612
|
+
let closed = false;
|
|
613
|
+
function startFlushInterval() {
|
|
614
|
+
if (flushTimer) return;
|
|
615
|
+
if (interval > 0) {
|
|
616
|
+
flushTimer = setInterval(() => {
|
|
617
|
+
flushBuffer().catch(() => {
|
|
618
|
+
});
|
|
619
|
+
}, interval);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
function stopFlushInterval() {
|
|
623
|
+
if (flushTimer) {
|
|
624
|
+
clearInterval(flushTimer);
|
|
625
|
+
flushTimer = null;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
async function flushBuffer() {
|
|
629
|
+
if (isFlushing || buffer.length === 0) return;
|
|
630
|
+
isFlushing = true;
|
|
631
|
+
const entries = buffer;
|
|
632
|
+
buffer = [];
|
|
633
|
+
try {
|
|
634
|
+
await sendWithRetry(entries);
|
|
635
|
+
} catch (err) {
|
|
636
|
+
buffer = [...entries, ...buffer].slice(0, batch * 2);
|
|
637
|
+
throw err;
|
|
638
|
+
} finally {
|
|
639
|
+
isFlushing = false;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
async function sendWithRetry(entries) {
|
|
643
|
+
let lastError = null;
|
|
644
|
+
for (let attempt = 0; attempt <= retry; attempt++) {
|
|
645
|
+
try {
|
|
646
|
+
await sendEntries(entries);
|
|
647
|
+
return;
|
|
648
|
+
} catch (err) {
|
|
649
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
650
|
+
if (attempt < retry) {
|
|
651
|
+
const delay = Math.min(1e3 * Math.pow(2, attempt), 3e4);
|
|
652
|
+
await sleep(delay);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
throw new TransportError(
|
|
657
|
+
`Failed to send logs after ${retry + 1} attempts: ${lastError?.message}`,
|
|
658
|
+
"http",
|
|
659
|
+
lastError || void 0
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
async function sendEntries(entries) {
|
|
663
|
+
const body = JSON.stringify(entries);
|
|
664
|
+
const response = await fetch(url, {
|
|
665
|
+
method,
|
|
666
|
+
headers: {
|
|
667
|
+
"Content-Type": "application/json",
|
|
668
|
+
...headers
|
|
669
|
+
},
|
|
670
|
+
body
|
|
671
|
+
});
|
|
672
|
+
if (!response.ok) {
|
|
673
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
function sleep(ms) {
|
|
677
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
678
|
+
}
|
|
679
|
+
let started = false;
|
|
680
|
+
return {
|
|
681
|
+
name: "http",
|
|
682
|
+
async write(entry) {
|
|
683
|
+
if (closed) {
|
|
684
|
+
throw new TransportError("Transport is closed", "http");
|
|
685
|
+
}
|
|
686
|
+
if (!started) {
|
|
687
|
+
started = true;
|
|
688
|
+
startFlushInterval();
|
|
689
|
+
}
|
|
690
|
+
buffer.push(entry);
|
|
691
|
+
if (buffer.length >= batch) {
|
|
692
|
+
await flushBuffer();
|
|
693
|
+
}
|
|
694
|
+
},
|
|
695
|
+
async flush() {
|
|
696
|
+
await flushBuffer();
|
|
697
|
+
},
|
|
698
|
+
async close() {
|
|
699
|
+
if (closed) return;
|
|
700
|
+
closed = true;
|
|
701
|
+
stopFlushInterval();
|
|
702
|
+
if (buffer.length > 0) {
|
|
703
|
+
try {
|
|
704
|
+
await flushBuffer();
|
|
705
|
+
} catch {
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
},
|
|
709
|
+
supports(_env) {
|
|
710
|
+
return typeof fetch !== "undefined";
|
|
711
|
+
}
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// src/transports/localStorage.ts
|
|
716
|
+
function localStorageTransport(options) {
|
|
717
|
+
if (!isBrowser()) {
|
|
718
|
+
throw new EnvironmentError("LocalStorage transport is only available in browser", "browser");
|
|
719
|
+
}
|
|
720
|
+
const {
|
|
721
|
+
key,
|
|
722
|
+
maxSize = "1MB",
|
|
723
|
+
levels
|
|
724
|
+
} = options;
|
|
725
|
+
if (!key) {
|
|
726
|
+
throw new TransportError("Key is required", "localStorage");
|
|
727
|
+
}
|
|
728
|
+
const maxBytes = parseSize(maxSize);
|
|
729
|
+
const allowedLevels = levels ? new Set(levels.map((l) => LOG_LEVELS[l])) : null;
|
|
730
|
+
function getStorage() {
|
|
731
|
+
return window.localStorage;
|
|
732
|
+
}
|
|
733
|
+
function loadLogs() {
|
|
734
|
+
try {
|
|
735
|
+
const data = getStorage().getItem(key);
|
|
736
|
+
if (!data) return [];
|
|
737
|
+
return JSON.parse(data);
|
|
738
|
+
} catch {
|
|
739
|
+
return [];
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
function saveLogs(logs) {
|
|
743
|
+
const data = JSON.stringify(logs);
|
|
744
|
+
while (data.length > maxBytes && logs.length > 0) {
|
|
745
|
+
logs.shift();
|
|
746
|
+
}
|
|
747
|
+
try {
|
|
748
|
+
getStorage().setItem(key, JSON.stringify(logs));
|
|
749
|
+
} catch (err) {
|
|
750
|
+
if (logs.length > 10) {
|
|
751
|
+
logs.splice(0, Math.floor(logs.length / 2));
|
|
752
|
+
saveLogs(logs);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
function isLevelAllowed(level) {
|
|
757
|
+
if (!allowedLevels) return true;
|
|
758
|
+
return allowedLevels.has(level);
|
|
759
|
+
}
|
|
760
|
+
return {
|
|
761
|
+
name: "localStorage",
|
|
762
|
+
write(entry) {
|
|
763
|
+
if (!isLevelAllowed(entry.level)) {
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
const logs = loadLogs();
|
|
767
|
+
logs.push(entry);
|
|
768
|
+
saveLogs(logs);
|
|
769
|
+
},
|
|
770
|
+
flush() {
|
|
771
|
+
},
|
|
772
|
+
close() {
|
|
773
|
+
},
|
|
774
|
+
supports(env) {
|
|
775
|
+
return env === "browser";
|
|
776
|
+
}
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
function readLogs(key) {
|
|
780
|
+
if (!isBrowser()) return [];
|
|
781
|
+
try {
|
|
782
|
+
const data = window.localStorage.getItem(key);
|
|
783
|
+
if (!data) return [];
|
|
784
|
+
return JSON.parse(data);
|
|
785
|
+
} catch {
|
|
786
|
+
return [];
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
function clearLogs(key) {
|
|
790
|
+
if (!isBrowser()) return;
|
|
791
|
+
window.localStorage.removeItem(key);
|
|
792
|
+
}
|
|
793
|
+
function getStorageUsage(key) {
|
|
794
|
+
if (!isBrowser()) return 0;
|
|
795
|
+
const data = window.localStorage.getItem(key);
|
|
796
|
+
if (!data) return 0;
|
|
797
|
+
return data.length * 2;
|
|
798
|
+
}
|
|
799
|
+
export {
|
|
800
|
+
clearLogs,
|
|
801
|
+
consoleTransport,
|
|
802
|
+
fileTransport,
|
|
803
|
+
getStorageUsage,
|
|
804
|
+
httpTransport,
|
|
805
|
+
localStorageTransport,
|
|
806
|
+
readLogs,
|
|
807
|
+
streamTransport
|
|
808
|
+
};
|
|
809
|
+
//# sourceMappingURL=index.js.map
|