logxpert 2.0.0 → 2.1.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/CHANGELOG.md +6 -0
- package/LICENSE +201 -674
- package/README.md +66 -45
- package/index.d.mts +23 -0
- package/index.d.ts +77 -51
- package/lib/files.js +102 -0
- package/lib/format.js +38 -0
- package/lib/mini.js +134 -0
- package/lib/rotate.js +144 -0
- package/lib/sanitize.js +89 -0
- package/lib/time.js +64 -0
- package/main.js +43 -95
- package/package.json +63 -51
package/lib/rotate.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const zlib = require('zlib');
|
|
4
|
+
const dayjs = require('dayjs');
|
|
5
|
+
|
|
6
|
+
function parseSize(value, fallback) {
|
|
7
|
+
if (value == null) return fallback;
|
|
8
|
+
const m = String(value).trim().match(/^(\d+(?:\.\d+)?)\s*([kmg])?$/i);
|
|
9
|
+
if (!m) return fallback;
|
|
10
|
+
const mult = { k: 1024, m: 1024 ** 2, g: 1024 ** 3 }[(m[2] || '').toLowerCase()] ?? 1;
|
|
11
|
+
return Math.floor(parseFloat(m[1]) * mult);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function parseDays(value) {
|
|
15
|
+
if (value == null) return null;
|
|
16
|
+
const m = String(value).trim().match(/^(\d+)\s*d$/i);
|
|
17
|
+
return m ? parseInt(m[1], 10) : null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
class FileTransport {
|
|
21
|
+
constructor({ folder, filename, datePattern, maxSize = '20m', maxFiles = '14d', zippedArchive = false, level, render }) {
|
|
22
|
+
this.dirname = folder;
|
|
23
|
+
this.filename = filename;
|
|
24
|
+
this.options = { datePattern, maxSize, maxFiles, zippedArchive };
|
|
25
|
+
this.level = level;
|
|
26
|
+
this.render = render;
|
|
27
|
+
this.datePattern = datePattern;
|
|
28
|
+
this.maxBytes = parseSize(maxSize, Infinity);
|
|
29
|
+
this.maxFiles = maxFiles;
|
|
30
|
+
this.maxDays = parseDays(maxFiles);
|
|
31
|
+
this.zippedArchive = zippedArchive;
|
|
32
|
+
this.currentKey = null;
|
|
33
|
+
this.currentFile = null;
|
|
34
|
+
this.stream = null;
|
|
35
|
+
this.streamSize = 0;
|
|
36
|
+
this.streamErr = null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
fileNameFor(dateStr, n = 0) {
|
|
40
|
+
let name = this.filename.split('%DATE%').join(dateStr);
|
|
41
|
+
if (n > 0) {
|
|
42
|
+
const ext = path.extname(name) || '.log';
|
|
43
|
+
name = name.slice(0, -ext.length) + '-' + n + ext;
|
|
44
|
+
}
|
|
45
|
+
return name;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
open(dateStr, startN = 0) {
|
|
49
|
+
if (!fs.existsSync(this.dirname)) fs.mkdirSync(this.dirname, { recursive: true });
|
|
50
|
+
let n = startN;
|
|
51
|
+
let full = path.join(this.dirname, this.fileNameFor(dateStr, n));
|
|
52
|
+
try {
|
|
53
|
+
let size = fs.existsSync(full) ? fs.statSync(full).size : 0;
|
|
54
|
+
while (size >= this.maxBytes && n < 1000) {
|
|
55
|
+
n += 1;
|
|
56
|
+
full = path.join(this.dirname, this.fileNameFor(dateStr, n));
|
|
57
|
+
size = fs.existsSync(full) ? fs.statSync(full).size : 0;
|
|
58
|
+
}
|
|
59
|
+
} catch {}
|
|
60
|
+
const stream = fs.createWriteStream(full, { flags: 'a' });
|
|
61
|
+
stream.on('error', (err) => { this.streamErr = err; });
|
|
62
|
+
this.stream = stream;
|
|
63
|
+
this.streamSize = 0;
|
|
64
|
+
try {
|
|
65
|
+
this.streamSize = fs.existsSync(full) ? fs.statSync(full).size : 0;
|
|
66
|
+
} catch {}
|
|
67
|
+
this.currentKey = dateStr + '#' + n;
|
|
68
|
+
this.currentFile = full;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
rotateIfNeeded(dateStr, incoming) {
|
|
72
|
+
if (this.currentKey !== null && !this.currentKey.startsWith(dateStr + '#')) {
|
|
73
|
+
const previous = this.currentFile;
|
|
74
|
+
this.closeStream();
|
|
75
|
+
this.onDateChanged(previous);
|
|
76
|
+
this.currentKey = null;
|
|
77
|
+
}
|
|
78
|
+
if (this.stream === null) {
|
|
79
|
+
this.open(dateStr);
|
|
80
|
+
} else if (this.streamSize + incoming > this.maxBytes) {
|
|
81
|
+
const cur = parseInt(String(this.currentKey || '#0').split('#')[1] || '0', 10);
|
|
82
|
+
this.closeStream();
|
|
83
|
+
this.open(dateStr, cur + 1);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
onDateChanged(previousFile) {
|
|
88
|
+
if (!previousFile) return;
|
|
89
|
+
try {
|
|
90
|
+
if (this.zippedArchive && fs.existsSync(previousFile)) {
|
|
91
|
+
const gz = previousFile + '.gz';
|
|
92
|
+
const data = fs.readFileSync(previousFile);
|
|
93
|
+
fs.writeFileSync(gz, zlib.gzipSync(data));
|
|
94
|
+
fs.rmSync(previousFile, { force: true });
|
|
95
|
+
}
|
|
96
|
+
} catch {}
|
|
97
|
+
this.cleanupOld();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
cleanupOld() {
|
|
101
|
+
if (this.maxDays == null) return;
|
|
102
|
+
let entries;
|
|
103
|
+
try {
|
|
104
|
+
entries = fs.readdirSync(this.dirname);
|
|
105
|
+
} catch {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const cutoff = Date.now() - this.maxDays * 86400000;
|
|
109
|
+
for (const entry of entries) {
|
|
110
|
+
if (!entry.endsWith('.log') && !entry.endsWith('.log.gz') && !entry.endsWith('.gz')) continue;
|
|
111
|
+
const full = path.join(this.dirname, entry);
|
|
112
|
+
try {
|
|
113
|
+
if (fs.statSync(full).mtimeMs < cutoff) fs.rmSync(full, { force: true });
|
|
114
|
+
} catch {}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
write(entry) {
|
|
119
|
+
const line = this.render(entry);
|
|
120
|
+
const buf = Buffer.byteLength(line + '\n');
|
|
121
|
+
const dateStr = dayjs().format(this.datePattern);
|
|
122
|
+
this.rotateIfNeeded(dateStr, buf);
|
|
123
|
+
if (this.stream) {
|
|
124
|
+
this.stream.write(line + '\n');
|
|
125
|
+
this.streamSize += buf;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
closeStream() {
|
|
130
|
+
if (this.stream) {
|
|
131
|
+
try {
|
|
132
|
+
this.stream.end();
|
|
133
|
+
} catch {}
|
|
134
|
+
this.stream = null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
close() {
|
|
139
|
+
this.closeStream();
|
|
140
|
+
this.currentKey = null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
module.exports = { FileTransport, parseSize, parseDays };
|
package/lib/sanitize.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
|
|
3
|
+
const VALID_LEVELS = ['error', 'warn', 'info', 'http', 'verbose', 'debug', 'silly'];
|
|
4
|
+
|
|
5
|
+
function assertLevel(level) {
|
|
6
|
+
if (!VALID_LEVELS.includes(level)) throw new Error(`Invalid log level "${level}". Use one of: ${VALID_LEVELS.join(', ')}`);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function stripAnsi(str) {
|
|
10
|
+
let out = '';
|
|
11
|
+
let i = 0;
|
|
12
|
+
while (i < str.length) {
|
|
13
|
+
if (str[i] !== '\x1b') {
|
|
14
|
+
out += str[i];
|
|
15
|
+
i++;
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
const next = str[i + 1];
|
|
19
|
+
if (next === ']') {
|
|
20
|
+
let j = i + 2;
|
|
21
|
+
while (j < str.length) {
|
|
22
|
+
if (str[j] === '\x07') { j++; break; }
|
|
23
|
+
if (str[j] === '\x1b' && str[j + 1] === '\\') { j += 2; break; }
|
|
24
|
+
j++;
|
|
25
|
+
}
|
|
26
|
+
i = j;
|
|
27
|
+
} else if (next === '[') {
|
|
28
|
+
let j = i + 2;
|
|
29
|
+
while (j < str.length && str[j] >= ' ' && str[j] <= '?') j++;
|
|
30
|
+
if (j < str.length) j++;
|
|
31
|
+
i = j;
|
|
32
|
+
} else if (next === '(' || next === ')') {
|
|
33
|
+
i += 3;
|
|
34
|
+
} else {
|
|
35
|
+
i += next === undefined ? 1 : 2;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
const CONTROL_RE = /[\x00-\x08\x0b\x0c\x0d\x0e-\x1f\x7f]/g;
|
|
41
|
+
|
|
42
|
+
function sanitizeMessage(msg) {
|
|
43
|
+
if (typeof msg !== 'string') return msg;
|
|
44
|
+
return stripAnsi(msg).replace(CONTROL_RE, '');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function assertFilenameSafe(filename) {
|
|
48
|
+
if (typeof filename !== 'string' || !filename.trim() || filename.includes('\0')) {
|
|
49
|
+
throw new Error('files.filename must be a non-empty string');
|
|
50
|
+
}
|
|
51
|
+
if (path.isAbsolute(filename) || filename.split(/[\\/]/).includes('..')) {
|
|
52
|
+
throw new Error('files.filename must not escape the log folder (.. and absolute paths are rejected)');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isSafeChar(c) {
|
|
57
|
+
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c === '.' || c === '_' || c === '-';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function isPadChar(c) {
|
|
61
|
+
return c === '-' || c === '_' || c === '.';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function sanitizeAppName(name) {
|
|
65
|
+
if (typeof name !== 'string' || !name.trim()) return null;
|
|
66
|
+
const base = name.trim().slice(0, 200).split('/').pop();
|
|
67
|
+
let out = '';
|
|
68
|
+
for (let i = 0; i < base.length; i++) {
|
|
69
|
+
out += isSafeChar(base[i]) ? base[i] : '-';
|
|
70
|
+
}
|
|
71
|
+
let start = 0;
|
|
72
|
+
while (start < out.length && isPadChar(out[start])) start++;
|
|
73
|
+
let end = out.length;
|
|
74
|
+
while (end > start && isPadChar(out[end - 1])) end--;
|
|
75
|
+
const safe = out.slice(start, end);
|
|
76
|
+
return safe || null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = {
|
|
80
|
+
VALID_LEVELS,
|
|
81
|
+
assertLevel,
|
|
82
|
+
stripAnsi,
|
|
83
|
+
CONTROL_RE,
|
|
84
|
+
sanitizeMessage,
|
|
85
|
+
assertFilenameSafe,
|
|
86
|
+
isSafeChar,
|
|
87
|
+
isPadChar,
|
|
88
|
+
sanitizeAppName,
|
|
89
|
+
};
|
package/lib/time.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const dayjs = require('dayjs');
|
|
2
|
+
|
|
3
|
+
const DEFAULT_CONSOLE_FORMAT = 'YYYY-MM-DD HH:mm:ss';
|
|
4
|
+
|
|
5
|
+
function pad2(n) {
|
|
6
|
+
return String(n).padStart(2, '0');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function formatDefaultTimestamp(d = new Date()) {
|
|
10
|
+
return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())} ${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function splitBrackets(fmt) {
|
|
14
|
+
const parts = [];
|
|
15
|
+
let current = '';
|
|
16
|
+
let i = 0;
|
|
17
|
+
while (i < fmt.length) {
|
|
18
|
+
if (fmt[i] === '[') {
|
|
19
|
+
const close = fmt.indexOf(']', i + 1);
|
|
20
|
+
if (close === -1) {
|
|
21
|
+
current += fmt.slice(i);
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
if (current) {
|
|
25
|
+
parts.push(current);
|
|
26
|
+
current = '';
|
|
27
|
+
}
|
|
28
|
+
parts.push(fmt.slice(i, close + 1));
|
|
29
|
+
i = close + 1;
|
|
30
|
+
} else {
|
|
31
|
+
current += fmt[i];
|
|
32
|
+
i++;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (current) parts.push(current);
|
|
36
|
+
return parts;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function compileTimestampFormatter(fmt) {
|
|
40
|
+
if (typeof fmt !== 'string' || !fmt) return formatDefaultTimestamp;
|
|
41
|
+
if (fmt === DEFAULT_CONSOLE_FORMAT) return formatDefaultTimestamp;
|
|
42
|
+
if (!fmt.includes('[') || !fmt.includes(']')) return () => dayjs().format(fmt);
|
|
43
|
+
const parts = splitBrackets(fmt).map((part) => {
|
|
44
|
+
if (part.startsWith('[') && part.endsWith(']')) {
|
|
45
|
+
const inner = part.slice(1, -1);
|
|
46
|
+
return inner ? () => '[' + dayjs().format(inner) + ']' : () => '[]';
|
|
47
|
+
}
|
|
48
|
+
return () => part;
|
|
49
|
+
});
|
|
50
|
+
return () => parts.map((f) => f()).join('');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function formatCustomTimestamp(fmt) {
|
|
54
|
+
return compileTimestampFormatter(fmt)();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = {
|
|
58
|
+
DEFAULT_CONSOLE_FORMAT,
|
|
59
|
+
pad2,
|
|
60
|
+
formatDefaultTimestamp,
|
|
61
|
+
splitBrackets,
|
|
62
|
+
compileTimestampFormatter,
|
|
63
|
+
formatCustomTimestamp,
|
|
64
|
+
};
|
package/main.js
CHANGED
|
@@ -1,100 +1,40 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const
|
|
4
|
-
const {
|
|
5
|
-
require('
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
function
|
|
11
|
-
if (
|
|
12
|
-
|
|
13
|
-
return fmt.split(/(\[[^\]]*\])/).map((part) => {
|
|
14
|
-
if (part.startsWith('[') && part.endsWith(']')) {
|
|
15
|
-
const inner = part.slice(1, -1);
|
|
16
|
-
return '[' + (inner ? dayjs().format(inner) : '') + ']';
|
|
17
|
-
}
|
|
18
|
-
return part;
|
|
19
|
-
}).join('');
|
|
20
|
-
}
|
|
21
|
-
return dayjs().format(fmt);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function formatExtra(meta) {
|
|
25
|
-
const keys = Object.keys(meta).filter((k) => k !== 'splat' && k !== 'ms');
|
|
26
|
-
if (!keys.length) return '';
|
|
27
|
-
try {
|
|
28
|
-
const picked = {};
|
|
29
|
-
for (const k of keys) picked[k] = meta[k];
|
|
30
|
-
return JSON.stringify(picked);
|
|
31
|
-
} catch {
|
|
32
|
-
return '';
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const prettyFormat = format.printf(({ timestamp, level, message, ...meta }) => {
|
|
37
|
-
const msg = typeof message === 'string' ? message : message == null ? '' : JSON.stringify(message);
|
|
38
|
-
const extra = formatExtra(meta);
|
|
39
|
-
const body = [msg, extra].filter(Boolean).join(' ');
|
|
40
|
-
return timestamp ? `${timestamp} [${level}]: ${body}` : `[${level}]: ${body}`;
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
function assertLevel(level) {
|
|
44
|
-
if (!VALID_LEVELS.includes(level)) throw new Error(`Invalid log level "${level}". Use one of: ${VALID_LEVELS.join(', ')}`);
|
|
3
|
+
const { MiniLogger, ConsoleTransport, NullTransport, colorizeLevel, resolveLevelColors } = require('./lib/mini');
|
|
4
|
+
const { FileTransport } = require('./lib/rotate');
|
|
5
|
+
const { DEFAULT_CONSOLE_FORMAT, compileTimestampFormatter } = require('./lib/time');
|
|
6
|
+
const { buildLine } = require('./lib/format');
|
|
7
|
+
const { assertLevel, sanitizeMessage } = require('./lib/sanitize');
|
|
8
|
+
const { normalizeFilesOptions } = require('./lib/files');
|
|
9
|
+
|
|
10
|
+
function normalizeConsoleOpts(input) {
|
|
11
|
+
if (input === false) return { enabled: false };
|
|
12
|
+
return input || {};
|
|
45
13
|
}
|
|
46
14
|
|
|
47
|
-
function
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
let filename = files.filename ?? `application-%DATE%.log`;
|
|
57
|
-
if (typeof filename !== 'string' || !filename.trim()) {
|
|
58
|
-
throw new Error('files.filename must be a non-empty string');
|
|
59
|
-
}
|
|
60
|
-
if (!filename.includes('%DATE%')) {
|
|
61
|
-
const ext = path.extname(filename) || '.log';
|
|
62
|
-
const base = path.basename(filename, ext);
|
|
63
|
-
filename = `${base}-%DATE%${ext}`;
|
|
64
|
-
}
|
|
65
|
-
return {
|
|
66
|
-
folder: path.normalize(folder),
|
|
67
|
-
datePattern,
|
|
68
|
-
filename,
|
|
69
|
-
maxFiles: files.maxFile ?? files.maxFiles ?? '14d',
|
|
70
|
-
maxSize: files.maxSize ?? '20m',
|
|
71
|
-
zippedArchive: files.zippedArchive ?? false,
|
|
72
|
-
level: files.level,
|
|
15
|
+
function buildConsoleTransport({ enabled = true, enableTimestamp = true, timestampFormat = DEFAULT_CONSOLE_FORMAT, timestampPrefix = '', timestampSuffix = '', colorize = true, colors, level } = {}) {
|
|
16
|
+
if (!enabled) return new NullTransport();
|
|
17
|
+
const stamp = enableTimestamp ? compileTimestampFormatter(timestampFormat) : null;
|
|
18
|
+
const palette = resolveLevelColors(colors);
|
|
19
|
+
const render = ({ level, message, meta }) => {
|
|
20
|
+
const timestamp = stamp ? `${timestampPrefix}${stamp()}${timestampSuffix}` : '';
|
|
21
|
+
const tag = colorize ? colorizeLevel(level, `[${level}]`, palette) : `[${level}]`;
|
|
22
|
+
return buildLine(timestamp, level, message, meta, tag);
|
|
73
23
|
};
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function buildConsoleTransport({ enableTimestamp = true, timestampFormat = DEFAULT_CONSOLE_FORMAT, timestampPrefix = '', timestampSuffix = '', colorize = true, level } = {}) {
|
|
77
|
-
const parts = [];
|
|
78
|
-
if (colorize) parts.push(format.colorize());
|
|
79
|
-
if (enableTimestamp) {
|
|
80
|
-
parts.push(format.timestamp({ format: () => `${timestampPrefix}${formatCustomTimestamp(timestampFormat)}${timestampSuffix}` }));
|
|
81
|
-
} else {
|
|
82
|
-
parts.push(format((info) => { delete info.timestamp; return info; })());
|
|
83
|
-
}
|
|
84
|
-
parts.push(format.errors({ stack: true }), format.splat(), prettyFormat);
|
|
85
|
-
return new transports.Console({ format: format.combine(...parts), level });
|
|
24
|
+
return new ConsoleTransport({ level, render });
|
|
86
25
|
}
|
|
87
26
|
|
|
88
27
|
function buildFileTransport({ folder, datePattern, filename, maxFiles, maxSize, zippedArchive, level }) {
|
|
89
28
|
if (!fs.existsSync(folder)) fs.mkdirSync(folder, { recursive: true });
|
|
90
|
-
return new
|
|
91
|
-
|
|
29
|
+
return new FileTransport({
|
|
30
|
+
folder: path.normalize(folder),
|
|
31
|
+
filename,
|
|
92
32
|
datePattern,
|
|
93
33
|
zippedArchive,
|
|
94
34
|
maxSize,
|
|
95
35
|
maxFiles,
|
|
96
36
|
level,
|
|
97
|
-
|
|
37
|
+
render: ({ level, message, meta }) => buildLine(new Date().toISOString(), level, message, meta),
|
|
98
38
|
});
|
|
99
39
|
}
|
|
100
40
|
|
|
@@ -102,17 +42,14 @@ function createLoggerInstance(initial = {}) {
|
|
|
102
42
|
const globalLevel = process.env.LOG_LEVEL || initial.level || 'debug';
|
|
103
43
|
assertLevel(globalLevel);
|
|
104
44
|
|
|
105
|
-
const logger =
|
|
106
|
-
level: globalLevel,
|
|
107
|
-
format: format.combine(format.errors({ stack: true }), format.splat()),
|
|
108
|
-
});
|
|
45
|
+
const logger = new MiniLogger({ level: globalLevel });
|
|
109
46
|
|
|
110
|
-
let consoleTransport = buildConsoleTransport(initial.console);
|
|
111
|
-
consoleTransport.level = initial.console
|
|
47
|
+
let consoleTransport = buildConsoleTransport(normalizeConsoleOpts(initial.console));
|
|
48
|
+
consoleTransport.level = normalizeConsoleOpts(initial.console).level;
|
|
112
49
|
logger.add(consoleTransport);
|
|
113
50
|
|
|
114
51
|
let fileTransport = null;
|
|
115
|
-
let consoleOpts = { ...(initial.console
|
|
52
|
+
let consoleOpts = { ...normalizeConsoleOpts(initial.console) };
|
|
116
53
|
let filesOpts = null;
|
|
117
54
|
|
|
118
55
|
function applySettings(options = {}) {
|
|
@@ -120,9 +57,10 @@ function createLoggerInstance(initial = {}) {
|
|
|
120
57
|
assertLevel(options.level);
|
|
121
58
|
logger.level = options.level;
|
|
122
59
|
}
|
|
123
|
-
if (options.console) {
|
|
124
|
-
|
|
125
|
-
|
|
60
|
+
if (options.console !== undefined) {
|
|
61
|
+
const next = normalizeConsoleOpts(options.console);
|
|
62
|
+
if (next.level !== undefined && next.level !== null) assertLevel(next.level);
|
|
63
|
+
consoleOpts = { ...consoleOpts, ...next };
|
|
126
64
|
logger.remove(consoleTransport);
|
|
127
65
|
consoleTransport = buildConsoleTransport(consoleOpts);
|
|
128
66
|
consoleTransport.level = consoleOpts.level;
|
|
@@ -138,12 +76,22 @@ function createLoggerInstance(initial = {}) {
|
|
|
138
76
|
}
|
|
139
77
|
}
|
|
140
78
|
|
|
79
|
+
function isEnabled(level) {
|
|
80
|
+
const want = logger.levels[level];
|
|
81
|
+
const current = logger.levels[logger.level];
|
|
82
|
+
return want !== undefined && current !== undefined && want <= current;
|
|
83
|
+
}
|
|
84
|
+
|
|
141
85
|
function write(level, message, ...meta) {
|
|
86
|
+
if (typeof message === 'function') {
|
|
87
|
+
if (!isEnabled(level)) return;
|
|
88
|
+
message = message();
|
|
89
|
+
}
|
|
142
90
|
if (message instanceof Error) {
|
|
143
|
-
logger.log(level, message.message, { stack: message.stack, ...meta[0] });
|
|
91
|
+
logger.log(level, sanitizeMessage(message.message), { stack: message.stack, ...meta[0] });
|
|
144
92
|
return;
|
|
145
93
|
}
|
|
146
|
-
logger.log(level, message, ...meta);
|
|
94
|
+
logger.log(level, sanitizeMessage(message), ...meta);
|
|
147
95
|
}
|
|
148
96
|
|
|
149
97
|
function callable(message, ...meta) {
|
package/package.json
CHANGED
|
@@ -1,51 +1,63 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "logxpert",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "A powerful logging library for Node.js with colorful output and daily file rotation",
|
|
5
|
-
"main": "main.js",
|
|
6
|
-
"types": "index.d.ts",
|
|
7
|
-
"exports": {
|
|
8
|
-
".": {
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"
|
|
29
|
-
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "logxpert",
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"description": "A powerful logging library for Node.js with colorful output and daily file rotation",
|
|
5
|
+
"main": "main.js",
|
|
6
|
+
"types": "index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": {
|
|
10
|
+
"types": "./index.d.mts",
|
|
11
|
+
"default": "./main.mjs"
|
|
12
|
+
},
|
|
13
|
+
"require": {
|
|
14
|
+
"types": "./index.d.ts",
|
|
15
|
+
"default": "./main.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"./package.json": "./package.json"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"main.js",
|
|
22
|
+
"main.mjs",
|
|
23
|
+
"index.d.ts",
|
|
24
|
+
"index.d.mts",
|
|
25
|
+
"lib/",
|
|
26
|
+
"README.md",
|
|
27
|
+
"LICENSE",
|
|
28
|
+
"CHANGELOG.md"
|
|
29
|
+
],
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/lighscent/LogXpert.git"
|
|
33
|
+
},
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/lighscent/LogXpert/issues"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/lighscent/LogXpert#readme",
|
|
38
|
+
"author": "https://github.com/lighscent",
|
|
39
|
+
"license": "Apache-2.0",
|
|
40
|
+
"keywords": [
|
|
41
|
+
"logging",
|
|
42
|
+
"logger",
|
|
43
|
+
"winston",
|
|
44
|
+
"daily-rotate",
|
|
45
|
+
"nodejs"
|
|
46
|
+
],
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=18"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"test": "node --test --test-reporter=./test/reporter.js test/logger.test.js test/console.test.js test/files.test.js test/lib.test.js",
|
|
52
|
+
"test:demo": "node --test --test-reporter=./test/reporter.js demo/reporter.demo.test.js",
|
|
53
|
+
"check:types": "tsc -p test-d --noEmit && echo TYPES-OK",
|
|
54
|
+
"attw": "attw --pack"
|
|
55
|
+
},
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"dayjs": "^1.11.23"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@arethetypeswrong/cli": "^0.18.5",
|
|
61
|
+
"typescript": "^7.0.2"
|
|
62
|
+
}
|
|
63
|
+
}
|