logxpert 1.0.5 → 2.0.3
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 +20 -0
- package/LICENSE +201 -674
- package/README.md +107 -108
- package/index.d.mts +22 -0
- package/index.d.ts +79 -0
- package/main.js +354 -176
- package/main.mjs +14 -0
- package/package.json +55 -6
- package/CODE_OF_CONDUCT.md +0 -128
package/main.js
CHANGED
|
@@ -1,197 +1,375 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
|
-
const
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const dayjs = require('dayjs');
|
|
3
4
|
const { createLogger, format, transports } = require('winston');
|
|
4
5
|
require('winston-daily-rotate-file');
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
if (fmt
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
7
|
+
const VALID_LEVELS = ['error', 'warn', 'info', 'http', 'verbose', 'debug', 'silly'];
|
|
8
|
+
const DEFAULT_CONSOLE_FORMAT = 'YYYY-MM-DD HH:mm:ss';
|
|
9
|
+
|
|
10
|
+
function pad2(n) {
|
|
11
|
+
return String(n).padStart(2, '0');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function formatDefaultTimestamp(d = new Date()) {
|
|
15
|
+
return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())} ${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function splitBrackets(fmt) {
|
|
19
|
+
const parts = [];
|
|
20
|
+
let current = '';
|
|
21
|
+
let i = 0;
|
|
22
|
+
while (i < fmt.length) {
|
|
23
|
+
if (fmt[i] === '[') {
|
|
24
|
+
const close = fmt.indexOf(']', i + 1);
|
|
25
|
+
if (close === -1) {
|
|
26
|
+
current += fmt.slice(i);
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
if (current) {
|
|
30
|
+
parts.push(current);
|
|
31
|
+
current = '';
|
|
32
|
+
}
|
|
33
|
+
parts.push(fmt.slice(i, close + 1));
|
|
34
|
+
i = close + 1;
|
|
35
|
+
} else {
|
|
36
|
+
current += fmt[i];
|
|
37
|
+
i++;
|
|
32
38
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* @typedef {Object} ConsoleOptions
|
|
40
|
-
* @property {boolean} [enableTimestamp=true] - Enable or disable timestamp in console logs.
|
|
41
|
-
* @property {string} [timestampFormat='YYYY-MM-DD HH:mm:ss'] - Custom format for the timestamp.
|
|
42
|
-
* Use square brackets (e.g., [YYYY-MM-DD]) to mark parts that should be formatted.
|
|
43
|
-
* Text outside brackets is treated as literal.
|
|
44
|
-
* @property {string} [timestampPrefix=''] - Optional text to prepend to the timestamp.
|
|
45
|
-
* @property {string} [timestampSuffix=''] - Optional text to append to the timestamp.
|
|
46
|
-
*/
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* @typedef {Object} FilesOptions
|
|
50
|
-
* @property {string} [folder='logs'] - Directory where log files will be stored.
|
|
51
|
-
* @property {string} [filesName='YYYY-MM-DD'] - Date pattern used in the log file name.
|
|
52
|
-
* @property {string} [maxFile='14d'] - Maximum file retention time.
|
|
53
|
-
* @property {string} [maxSize='20m'] - Maximum file size per log file.
|
|
54
|
-
* @property {boolean} [zippedArchive=false] - Whether to archive logs as zip.
|
|
55
|
-
*/
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* @typedef {Object} LogSettingsOptions
|
|
59
|
-
* @property {ConsoleOptions} [console] - Options for customizing console output.
|
|
60
|
-
* @property {FilesOptions} [files] - Options for configuring file logging.
|
|
61
|
-
*/
|
|
62
|
-
|
|
63
|
-
// Custom format for logger output
|
|
64
|
-
const customFormat = format.printf(({ timestamp, level, message }) => {
|
|
65
|
-
return timestamp ? `${timestamp} [${level}]: ${message}` : `[${level}]: ${message}`;
|
|
66
|
-
});
|
|
39
|
+
}
|
|
40
|
+
if (current) parts.push(current);
|
|
41
|
+
return parts;
|
|
42
|
+
}
|
|
67
43
|
|
|
68
|
-
|
|
69
|
-
|
|
44
|
+
function compileTimestampFormatter(fmt) {
|
|
45
|
+
if (typeof fmt !== 'string' || !fmt) return formatDefaultTimestamp;
|
|
46
|
+
if (fmt === DEFAULT_CONSOLE_FORMAT) return formatDefaultTimestamp;
|
|
47
|
+
if (!fmt.includes('[') || !fmt.includes(']')) return () => dayjs().format(fmt);
|
|
48
|
+
const parts = splitBrackets(fmt).map((part) => {
|
|
49
|
+
if (part.startsWith('[') && part.endsWith(']')) {
|
|
50
|
+
const inner = part.slice(1, -1);
|
|
51
|
+
return inner ? () => '[' + dayjs().format(inner) + ']' : () => '[]';
|
|
52
|
+
}
|
|
53
|
+
return () => part;
|
|
54
|
+
});
|
|
55
|
+
return () => parts.map((f) => f()).join('');
|
|
56
|
+
}
|
|
70
57
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
58
|
+
function formatCustomTimestamp(fmt) {
|
|
59
|
+
return compileTimestampFormatter(fmt)();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function formatExtra(meta) {
|
|
63
|
+
const keys = Object.keys(meta).filter((k) => k !== 'splat' && k !== 'ms');
|
|
64
|
+
if (!keys.length) return '';
|
|
65
|
+
try {
|
|
66
|
+
const picked = {};
|
|
67
|
+
for (const k of keys) picked[k] = meta[k];
|
|
68
|
+
return JSON.stringify(picked);
|
|
69
|
+
} catch {
|
|
70
|
+
return '';
|
|
71
|
+
}
|
|
72
|
+
}
|
|
79
73
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
customFormat
|
|
86
|
-
)
|
|
74
|
+
const prettyFormat = format.printf(({ timestamp, level, message, ...meta }) => {
|
|
75
|
+
const msg = typeof message === 'string' ? message : message == null ? '' : JSON.stringify(message);
|
|
76
|
+
const extra = formatExtra(meta);
|
|
77
|
+
const body = [msg, extra].filter(Boolean).join(' ');
|
|
78
|
+
return timestamp ? `${timestamp} [${level}]: ${body}` : `[${level}]: ${body}`;
|
|
87
79
|
});
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
// Remove the current console transport
|
|
122
|
-
logger.remove(consoleTransport);
|
|
123
|
-
|
|
124
|
-
// Build the console format based on the enableTimestamp flag.
|
|
125
|
-
const consoleFormat = enableTimestamp
|
|
126
|
-
? format.combine(
|
|
127
|
-
format.colorize(),
|
|
128
|
-
format.timestamp({ format: () => timestampPrefix + formatCustomTimestamp(timestampFormat) + timestampSuffix }),
|
|
129
|
-
customFormat
|
|
130
|
-
)
|
|
131
|
-
: format.combine(
|
|
132
|
-
format((info) => { delete info.timestamp; return info; })(),
|
|
133
|
-
format.colorize(),
|
|
134
|
-
customFormat
|
|
135
|
-
);
|
|
136
|
-
|
|
137
|
-
consoleTransport = new transports.Console({
|
|
138
|
-
format: consoleFormat
|
|
139
|
-
});
|
|
140
|
-
logger.add(consoleTransport);
|
|
80
|
+
|
|
81
|
+
function assertLevel(level) {
|
|
82
|
+
if (!VALID_LEVELS.includes(level)) throw new Error(`Invalid log level "${level}". Use one of: ${VALID_LEVELS.join(', ')}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function stripAnsi(str) {
|
|
86
|
+
let out = '';
|
|
87
|
+
let i = 0;
|
|
88
|
+
while (i < str.length) {
|
|
89
|
+
if (str[i] !== '\x1b') {
|
|
90
|
+
out += str[i];
|
|
91
|
+
i++;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const next = str[i + 1];
|
|
95
|
+
if (next === ']') {
|
|
96
|
+
let j = i + 2;
|
|
97
|
+
while (j < str.length) {
|
|
98
|
+
if (str[j] === '\x07') { j++; break; }
|
|
99
|
+
if (str[j] === '\x1b' && str[j + 1] === '\\') { j += 2; break; }
|
|
100
|
+
j++;
|
|
101
|
+
}
|
|
102
|
+
i = j;
|
|
103
|
+
} else if (next === '[') {
|
|
104
|
+
let j = i + 2;
|
|
105
|
+
while (j < str.length && str[j] >= ' ' && str[j] <= '?') j++;
|
|
106
|
+
if (j < str.length) j++;
|
|
107
|
+
i = j;
|
|
108
|
+
} else if (next === '(' || next === ')') {
|
|
109
|
+
i += 3;
|
|
110
|
+
} else {
|
|
111
|
+
i += next === undefined ? 1 : 2;
|
|
141
112
|
}
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
const CONTROL_RE = /[\x00-\x08\x0b\x0c\x0d\x0e-\x1f\x7f]/g;
|
|
142
117
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
118
|
+
function sanitizeMessage(msg) {
|
|
119
|
+
if (typeof msg !== 'string') return msg;
|
|
120
|
+
return stripAnsi(msg).replace(CONTROL_RE, '');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function assertFilenameSafe(filename) {
|
|
124
|
+
if (typeof filename !== 'string' || !filename.trim() || filename.includes('\0')) {
|
|
125
|
+
throw new Error('files.filename must be a non-empty string');
|
|
126
|
+
}
|
|
127
|
+
if (path.isAbsolute(filename) || filename.split(/[\\/]/).includes('..')) {
|
|
128
|
+
throw new Error('files.filename must not escape the log folder (.. and absolute paths are rejected)');
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function isSafeChar(c) {
|
|
133
|
+
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c === '.' || c === '_' || c === '-';
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function isPadChar(c) {
|
|
137
|
+
return c === '-' || c === '_' || c === '.';
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function sanitizeAppName(name) {
|
|
141
|
+
if (typeof name !== 'string' || !name.trim()) return null;
|
|
142
|
+
const base = name.trim().slice(0, 200).split('/').pop();
|
|
143
|
+
let out = '';
|
|
144
|
+
for (let i = 0; i < base.length; i++) {
|
|
145
|
+
out += isSafeChar(base[i]) ? base[i] : '-';
|
|
146
|
+
}
|
|
147
|
+
let start = 0;
|
|
148
|
+
while (start < out.length && isPadChar(out[start])) start++;
|
|
149
|
+
let end = out.length;
|
|
150
|
+
while (end > start && isPadChar(out[end - 1])) end--;
|
|
151
|
+
const safe = out.slice(start, end);
|
|
152
|
+
return safe || null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
let cachedAppName = null;
|
|
156
|
+
function getDefaultAppName() {
|
|
157
|
+
if (cachedAppName) return cachedAppName;
|
|
158
|
+
try {
|
|
159
|
+
const pkgPath = path.resolve(process.cwd(), 'package.json');
|
|
160
|
+
const raw = fs.readFileSync(pkgPath, 'utf8');
|
|
161
|
+
cachedAppName = sanitizeAppName(JSON.parse(raw)?.name) ?? 'application';
|
|
162
|
+
} catch {
|
|
163
|
+
cachedAppName = 'application';
|
|
164
|
+
}
|
|
165
|
+
return cachedAppName;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function resolveRunNumber(folder, prefix, opt) {
|
|
169
|
+
let enabled = false;
|
|
170
|
+
let separator = '-';
|
|
171
|
+
let padding = 0;
|
|
172
|
+
let startAt = 1;
|
|
173
|
+
if (opt === true) {
|
|
174
|
+
enabled = true;
|
|
175
|
+
} else if (opt && typeof opt === 'object') {
|
|
176
|
+
enabled = opt.enabled ?? true;
|
|
177
|
+
if (opt.separator !== undefined) {
|
|
178
|
+
if (typeof opt.separator !== 'string' || !opt.separator) throw new Error('files.runNumber.separator must be a non-empty string');
|
|
179
|
+
separator = opt.separator;
|
|
180
|
+
}
|
|
181
|
+
if (opt.padding !== undefined) {
|
|
182
|
+
if (!Number.isInteger(opt.padding) || opt.padding < 0) throw new Error('files.runNumber.padding must be a non-negative integer');
|
|
183
|
+
padding = opt.padding;
|
|
166
184
|
}
|
|
185
|
+
if (opt.startAt !== undefined) {
|
|
186
|
+
if (!Number.isInteger(opt.startAt) || opt.startAt < 0) throw new Error('files.runNumber.startAt must be a non-negative integer');
|
|
187
|
+
startAt = opt.startAt;
|
|
188
|
+
}
|
|
189
|
+
} else if (opt !== undefined && opt !== false) {
|
|
190
|
+
throw new Error('files.runNumber must be a boolean or an object');
|
|
191
|
+
}
|
|
192
|
+
if (!enabled) return null;
|
|
193
|
+
const counterFile = path.join(folder, `.${prefix}.run`);
|
|
194
|
+
let next = startAt;
|
|
195
|
+
try {
|
|
196
|
+
const raw = fs.readFileSync(counterFile, 'utf8').trim();
|
|
197
|
+
const prev = parseInt(raw, 10);
|
|
198
|
+
next = Number.isInteger(prev) ? prev + 1 : startAt;
|
|
199
|
+
} catch {}
|
|
200
|
+
try {
|
|
201
|
+
fs.writeFileSync(counterFile, String(next), 'utf8');
|
|
202
|
+
} catch {}
|
|
203
|
+
return separator + String(next).padStart(padding, '0');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function normalizeFilesOptions(files = {}) {
|
|
207
|
+
const rawFolder = files.folder ?? 'logs';
|
|
208
|
+
if (typeof rawFolder !== 'string' || !rawFolder.trim() || rawFolder.includes('\0')) {
|
|
209
|
+
throw new Error('files.folder must be a non-empty string');
|
|
210
|
+
}
|
|
211
|
+
const folder = path.normalize(rawFolder);
|
|
212
|
+
const datePattern = files.datePattern ?? files.dateFormat ?? files.filesName ?? 'YYYY_MM_DD';
|
|
213
|
+
if (typeof datePattern !== 'string' || !datePattern.trim()) {
|
|
214
|
+
throw new Error('files.datePattern must be a non-empty string');
|
|
215
|
+
}
|
|
216
|
+
const explicitPattern = files.filePattern ?? files.pattern ?? files.filename;
|
|
217
|
+
const explicitPrefix = files.prefix ?? files.appName;
|
|
218
|
+
if (explicitPattern !== undefined && explicitPrefix !== undefined) {
|
|
219
|
+
throw new Error('Do not combine "prefix" with "filePattern", pick one: prefix builds "<prefix>-%DATE%.log" for you, filePattern takes full control');
|
|
220
|
+
}
|
|
221
|
+
let filename = explicitPattern ?? `${sanitizeAppName(explicitPrefix) ?? getDefaultAppName()}-%DATE%.log`;
|
|
222
|
+
filename = filename.split('%datePattern%').join('%DATE%');
|
|
223
|
+
assertFilenameSafe(filename);
|
|
224
|
+
if (!filename.includes('%DATE%')) {
|
|
225
|
+
const ext = path.extname(filename) || '.log';
|
|
226
|
+
const base = path.basename(filename, ext);
|
|
227
|
+
filename = `${base}-%DATE%${ext}`;
|
|
228
|
+
}
|
|
229
|
+
assertFilenameSafe(filename);
|
|
230
|
+
if (!fs.existsSync(folder)) fs.mkdirSync(folder, { recursive: true });
|
|
231
|
+
const rawPrefix = path.basename(filename).split('%DATE%')[0];
|
|
232
|
+
let prefixEnd = rawPrefix.length;
|
|
233
|
+
while (prefixEnd > 0 && isPadChar(rawPrefix[prefixEnd - 1])) prefixEnd--;
|
|
234
|
+
const prefix = rawPrefix.slice(0, prefixEnd) || 'application';
|
|
235
|
+
const runSuffix = resolveRunNumber(folder, sanitizeAppName(prefix) ?? 'application', files.runNumber ?? false);
|
|
236
|
+
if (runSuffix) {
|
|
237
|
+
const ext = path.extname(filename) || '.log';
|
|
238
|
+
const base = filename.slice(0, -ext.length);
|
|
239
|
+
filename = `${base}${runSuffix}${ext}`;
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
folder,
|
|
243
|
+
datePattern,
|
|
244
|
+
filename,
|
|
245
|
+
maxFiles: files.maxFile ?? files.maxFiles ?? '14d',
|
|
246
|
+
maxSize: files.maxSize ?? '20m',
|
|
247
|
+
zippedArchive: files.zippedArchive ?? false,
|
|
248
|
+
level: files.level,
|
|
249
|
+
};
|
|
167
250
|
}
|
|
168
251
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
252
|
+
function buildConsoleTransport({ enableTimestamp = true, timestampFormat = DEFAULT_CONSOLE_FORMAT, timestampPrefix = '', timestampSuffix = '', colorize = true, level } = {}) {
|
|
253
|
+
const parts = [];
|
|
254
|
+
if (colorize) parts.push(format.colorize());
|
|
255
|
+
if (enableTimestamp) {
|
|
256
|
+
const stamp = compileTimestampFormatter(timestampFormat);
|
|
257
|
+
parts.push(format.timestamp({ format: () => `${timestampPrefix}${stamp()}${timestampSuffix}` }));
|
|
258
|
+
} else {
|
|
259
|
+
parts.push(format((info) => { delete info.timestamp; return info; })());
|
|
260
|
+
}
|
|
261
|
+
parts.push(format.errors({ stack: true }), format.splat(), prettyFormat);
|
|
262
|
+
return new transports.Console({ format: format.combine(...parts), level });
|
|
176
263
|
}
|
|
177
264
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
265
|
+
function buildFileTransport({ folder, datePattern, filename, maxFiles, maxSize, zippedArchive, level }) {
|
|
266
|
+
if (!fs.existsSync(folder)) fs.mkdirSync(folder, { recursive: true });
|
|
267
|
+
return new transports.DailyRotateFile({
|
|
268
|
+
filename: path.join(folder, filename),
|
|
269
|
+
datePattern,
|
|
270
|
+
zippedArchive,
|
|
271
|
+
maxSize,
|
|
272
|
+
maxFiles,
|
|
273
|
+
level,
|
|
274
|
+
format: format.combine(format.errors({ stack: true }), format.splat(), format.timestamp(), prettyFormat),
|
|
275
|
+
});
|
|
276
|
+
}
|
|
181
277
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
278
|
+
function createLoggerInstance(initial = {}) {
|
|
279
|
+
const globalLevel = process.env.LOG_LEVEL || initial.level || 'debug';
|
|
280
|
+
assertLevel(globalLevel);
|
|
185
281
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
}
|
|
282
|
+
const logger = createLogger({
|
|
283
|
+
level: globalLevel,
|
|
284
|
+
format: format.combine(format.errors({ stack: true }), format.splat()),
|
|
285
|
+
});
|
|
189
286
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
287
|
+
let consoleTransport = buildConsoleTransport(initial.console);
|
|
288
|
+
consoleTransport.level = initial.console?.level;
|
|
289
|
+
logger.add(consoleTransport);
|
|
193
290
|
|
|
194
|
-
|
|
195
|
-
|
|
291
|
+
let fileTransport = null;
|
|
292
|
+
let consoleOpts = { ...(initial.console || {}) };
|
|
293
|
+
let filesOpts = null;
|
|
294
|
+
|
|
295
|
+
function applySettings(options = {}) {
|
|
296
|
+
if (options.level !== undefined) {
|
|
297
|
+
assertLevel(options.level);
|
|
298
|
+
logger.level = options.level;
|
|
299
|
+
}
|
|
300
|
+
if (options.console) {
|
|
301
|
+
if (options.console.level !== undefined && options.console.level !== null) assertLevel(options.console.level);
|
|
302
|
+
consoleOpts = { ...consoleOpts, ...options.console };
|
|
303
|
+
logger.remove(consoleTransport);
|
|
304
|
+
consoleTransport = buildConsoleTransport(consoleOpts);
|
|
305
|
+
consoleTransport.level = consoleOpts.level;
|
|
306
|
+
logger.add(consoleTransport);
|
|
307
|
+
}
|
|
308
|
+
if (options.files) {
|
|
309
|
+
const normalized = normalizeFilesOptions(options.files);
|
|
310
|
+
if (normalized.level !== undefined && normalized.level !== null) assertLevel(normalized.level);
|
|
311
|
+
filesOpts = normalized;
|
|
312
|
+
if (fileTransport) logger.remove(fileTransport);
|
|
313
|
+
fileTransport = buildFileTransport(normalized);
|
|
314
|
+
logger.add(fileTransport);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function isEnabled(level) {
|
|
319
|
+
const want = logger.levels[level];
|
|
320
|
+
const current = logger.levels[logger.level];
|
|
321
|
+
return want !== undefined && current !== undefined && want <= current;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function write(level, message, ...meta) {
|
|
325
|
+
if (typeof message === 'function') {
|
|
326
|
+
if (!isEnabled(level)) return;
|
|
327
|
+
message = message();
|
|
328
|
+
}
|
|
329
|
+
if (message instanceof Error) {
|
|
330
|
+
logger.log(level, sanitizeMessage(message.message), { stack: message.stack, ...meta[0] });
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
logger.log(level, sanitizeMessage(message), ...meta);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function callable(message, ...meta) {
|
|
337
|
+
write('info', message, ...meta);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
callable.error = (m, ...a) => write('error', m, ...a);
|
|
341
|
+
callable.warn = (m, ...a) => write('warn', m, ...a);
|
|
342
|
+
callable.info = (m, ...a) => write('info', m, ...a);
|
|
343
|
+
callable.debug = (m, ...a) => write('debug', m, ...a);
|
|
344
|
+
callable.http = (m, ...a) => write('http', m, ...a);
|
|
345
|
+
callable.verbose = (m, ...a) => write('verbose', m, ...a);
|
|
346
|
+
callable.silly = (m, ...a) => write('silly', m, ...a);
|
|
347
|
+
callable.log = (level, m, ...a) => { assertLevel(level); write(level, m, ...a); };
|
|
348
|
+
|
|
349
|
+
callable.settings = applySettings;
|
|
350
|
+
callable.setLevel = (level) => { assertLevel(level); logger.level = level; };
|
|
351
|
+
callable.getLevel = () => logger.level;
|
|
352
|
+
callable.child = (meta = {}) => {
|
|
353
|
+
const child = logger.child(meta);
|
|
354
|
+
return {
|
|
355
|
+
error: (m, ...a) => child.error(m, ...a),
|
|
356
|
+
warn: (m, ...a) => child.warn(m, ...a),
|
|
357
|
+
info: (m, ...a) => child.info(m, ...a),
|
|
358
|
+
debug: (m, ...a) => child.debug(m, ...a),
|
|
359
|
+
log: child.log.bind(child),
|
|
360
|
+
};
|
|
361
|
+
};
|
|
362
|
+
callable.close = () => {
|
|
363
|
+
if (fileTransport) { logger.remove(fileTransport); try { fileTransport.close?.(); } catch {} fileTransport = null; }
|
|
364
|
+
};
|
|
365
|
+
callable.createLogger = (opts = {}) => createLoggerInstance(opts);
|
|
366
|
+
callable._winston = logger;
|
|
367
|
+
|
|
368
|
+
if (initial.files) applySettings({ files: initial.files });
|
|
369
|
+
if (initial.console || initial.level) applySettings({ console: initial.console, level: initial.level });
|
|
370
|
+
|
|
371
|
+
return callable;
|
|
372
|
+
}
|
|
196
373
|
|
|
197
|
-
module.exports =
|
|
374
|
+
module.exports = createLoggerInstance();
|
|
375
|
+
module.exports.createLogger = createLoggerInstance;
|
package/main.mjs
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import log from './main.js';
|
|
2
|
+
|
|
3
|
+
export const settings = log.settings;
|
|
4
|
+
export const setLevel = log.setLevel;
|
|
5
|
+
export const getLevel = log.getLevel;
|
|
6
|
+
export const child = log.child;
|
|
7
|
+
export const close = log.close;
|
|
8
|
+
export const createLogger = log.createLogger;
|
|
9
|
+
export const error = log.error;
|
|
10
|
+
export const warn = log.warn;
|
|
11
|
+
export const info = log.info;
|
|
12
|
+
export const debug = log.debug;
|
|
13
|
+
|
|
14
|
+
export default log;
|
package/package.json
CHANGED
|
@@ -1,14 +1,63 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "logxpert",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "A powerful logging library for Node.js",
|
|
3
|
+
"version": "2.0.3",
|
|
4
|
+
"description": "A powerful logging library for Node.js with colorful output and daily file rotation",
|
|
5
5
|
"main": "main.js",
|
|
6
|
-
"
|
|
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
|
+
"README.md",
|
|
26
|
+
"LICENSE",
|
|
27
|
+
"CHANGELOG.md"
|
|
28
|
+
],
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/lighscent/LogXpert.git"
|
|
32
|
+
},
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/lighscent/LogXpert/issues"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://github.com/lighscent/LogXpert#readme",
|
|
7
37
|
"author": "https://github.com/lighscent",
|
|
8
|
-
"license": "
|
|
38
|
+
"license": "Apache-2.0",
|
|
39
|
+
"keywords": [
|
|
40
|
+
"logging",
|
|
41
|
+
"logger",
|
|
42
|
+
"winston",
|
|
43
|
+
"daily-rotate",
|
|
44
|
+
"nodejs"
|
|
45
|
+
],
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=18"
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"test": "node --test test/logger.test.js",
|
|
51
|
+
"check:types": "tsc -p test-d --noEmit",
|
|
52
|
+
"attw": "attw --pack"
|
|
53
|
+
},
|
|
9
54
|
"dependencies": {
|
|
10
|
-
"
|
|
55
|
+
"dayjs": "^1.11.23",
|
|
11
56
|
"winston": "^3.19.0",
|
|
12
57
|
"winston-daily-rotate-file": "^5.0.0"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@arethetypeswrong/cli": "^0.18.5",
|
|
61
|
+
"typescript": "^7.0.2"
|
|
13
62
|
}
|
|
14
|
-
}
|
|
63
|
+
}
|