metalog 3.1.16 → 4.0.0-prerelease
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 +335 -5
- package/metalog.d.ts +79 -24
- package/metalog.js +315 -205
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -14,21 +14,351 @@
|
|
|
14
14
|
## Usage
|
|
15
15
|
|
|
16
16
|
```js
|
|
17
|
-
const logger = await
|
|
17
|
+
const logger = await Logger.create({
|
|
18
18
|
path: './log', // absolute or relative path
|
|
19
19
|
workerId: 7, // mark for process or thread
|
|
20
|
-
|
|
21
|
-
writeBuffer: 64 * 1024, // buffer size (default 64kb)
|
|
22
|
-
keepDays: 5, // delete after N days, 0 - disable
|
|
20
|
+
flushInterval: 3000, // flush log to disk interval (default: 3s)
|
|
21
|
+
writeBuffer: 64 * 1024, // buffer size (default: 64kb)
|
|
22
|
+
keepDays: 5, // delete after N days, 0 - disable (default: 1)
|
|
23
23
|
home: process.cwd(), // remove substring from paths
|
|
24
|
-
json: false, // print logs in JSON format
|
|
24
|
+
json: false, // print logs in JSON format (default: false)
|
|
25
|
+
toFile: ['log', 'info', 'warn', 'error'], // tags to write to file (default: all)
|
|
26
|
+
toStdout: ['log', 'info', 'warn', 'error'], // tags to write to stdout (default: all)
|
|
27
|
+
createStream: () => fs.createWriteStream, // custom stream factory (optional)
|
|
28
|
+
crash: 'flush', // crash handling: 'flush' to flush buffer on exit (optional)
|
|
25
29
|
});
|
|
26
30
|
|
|
27
31
|
const { console } = logger;
|
|
32
|
+
|
|
28
33
|
console.log('Test message');
|
|
34
|
+
console.info('Info message');
|
|
35
|
+
console.warn('Warning message');
|
|
36
|
+
console.error('Error message');
|
|
37
|
+
console.debug('Debug message');
|
|
38
|
+
|
|
39
|
+
console.assert(true, 'Assertion passed');
|
|
40
|
+
console.assert(false, 'Assertion failed');
|
|
41
|
+
console.count('counter');
|
|
42
|
+
console.count('counter');
|
|
43
|
+
console.countReset('counter');
|
|
44
|
+
|
|
45
|
+
console.time('operation');
|
|
46
|
+
// ... some operation ...
|
|
47
|
+
console.timeEnd('operation');
|
|
48
|
+
console.timeLog('operation', 'Checkpoint');
|
|
49
|
+
|
|
50
|
+
console.group('Group 1');
|
|
51
|
+
console.log('Nested message');
|
|
52
|
+
console.groupCollapsed('Group 2');
|
|
53
|
+
console.log('Collapsed group message');
|
|
54
|
+
console.groupEnd();
|
|
55
|
+
console.groupEnd();
|
|
56
|
+
|
|
57
|
+
console.dir({ key: 'value' });
|
|
58
|
+
console.dirxml('<div>HTML content</div>');
|
|
59
|
+
console.table([
|
|
60
|
+
{ name: 'John', age: 30 },
|
|
61
|
+
{ name: 'Jane', age: 25 },
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
console.trace('Trace message');
|
|
65
|
+
|
|
29
66
|
await logger.close();
|
|
30
67
|
```
|
|
31
68
|
|
|
69
|
+
## Console API Compatibility
|
|
70
|
+
|
|
71
|
+
Metalog provides a fully compatible console implementation that supports all Node.js console methods:
|
|
72
|
+
|
|
73
|
+
- `console.log([data][, ...args])` - General logging
|
|
74
|
+
- `console.info([data][, ...args])` - Informational messages
|
|
75
|
+
- `console.warn([data][, ...args])` - Warning messages
|
|
76
|
+
- `console.error([data][, ...args])` - Error messages
|
|
77
|
+
- `console.debug([data][, ...args])` - Debug messages
|
|
78
|
+
- `console.assert(value[, ...message])` - Assertion testing
|
|
79
|
+
- `console.clear()` - Clear the console
|
|
80
|
+
- `console.count([label])` - Count occurrences
|
|
81
|
+
- `console.countReset([label])` - Reset counter
|
|
82
|
+
- `console.dir(obj[, options])` - Object inspection
|
|
83
|
+
- `console.dirxml(...data)` - XML/HTML inspection
|
|
84
|
+
- `console.group([...label])` - Start group
|
|
85
|
+
- `console.groupCollapsed()` - Start collapsed group
|
|
86
|
+
- `console.groupEnd()` - End group
|
|
87
|
+
- `console.table(tabularData[, properties])` - Table display
|
|
88
|
+
- `console.time([label])` - Start timer
|
|
89
|
+
- `console.timeEnd([label])` - End timer
|
|
90
|
+
- `console.timeLog([label][, ...data])` - Log timer value
|
|
91
|
+
- `console.trace([message][, ...args])` - Stack trace
|
|
92
|
+
|
|
93
|
+
All methods maintain the same behavior as Node.js native console, with output routed through the metalog system for consistent formatting and file logging.
|
|
94
|
+
|
|
95
|
+
## Configuration Options
|
|
96
|
+
|
|
97
|
+
### LoggerOptions
|
|
98
|
+
|
|
99
|
+
| Option | Type | Default | Description |
|
|
100
|
+
| --------------- | ---------- | ------------------------------------------- | --------------------------------------------------- |
|
|
101
|
+
| `path` | `string` | **required** | Directory path for log files (absolute or relative) |
|
|
102
|
+
| `home` | `string` | **required** | Base path to remove from stack traces |
|
|
103
|
+
| `workerId` | `number` | `undefined` | Worker/process identifier (appears as W0, W1, etc.) |
|
|
104
|
+
| `flushInterval` | `number` | `3000` | Flush buffer to disk interval in milliseconds |
|
|
105
|
+
| `writeBuffer` | `number` | `65536` | Buffer size threshold before flushing (64KB) |
|
|
106
|
+
| `keepDays` | `number` | `1` | Days to keep log files (0 = disable rotation) |
|
|
107
|
+
| `json` | `boolean` | `false` | Output logs in JSON format |
|
|
108
|
+
| `toFile` | `string[]` | `['log', 'info', 'warn', 'debug', 'error']` | Log tags to write to file |
|
|
109
|
+
| `toStdout` | `string[]` | `['log', 'info', 'warn', 'debug', 'error']` | Log tags to write to stdout |
|
|
110
|
+
| `createStream` | `function` | `fs.createWriteStream` | Custom stream factory function |
|
|
111
|
+
| `crash` | `string` | `undefined` | Crash handling mode ('flush' to flush on exit) |
|
|
112
|
+
|
|
113
|
+
### Log Tags
|
|
114
|
+
|
|
115
|
+
Metalog supports five log tags that can be filtered independently for file and console output:
|
|
116
|
+
|
|
117
|
+
- `log` - General logging
|
|
118
|
+
- `info` - Informational messages
|
|
119
|
+
- `warn` - Warning messages
|
|
120
|
+
- `debug` - Debug messages
|
|
121
|
+
- `error` - Error messages
|
|
122
|
+
|
|
123
|
+
## Advanced Usage
|
|
124
|
+
|
|
125
|
+
### Custom Stream Factory
|
|
126
|
+
|
|
127
|
+
```js
|
|
128
|
+
const logger = await Logger.create({
|
|
129
|
+
path: './log',
|
|
130
|
+
home: process.cwd(),
|
|
131
|
+
createStream: (filePath) => {
|
|
132
|
+
// Custom compression stream
|
|
133
|
+
const fs = require('fs');
|
|
134
|
+
const zlib = require('zlib');
|
|
135
|
+
const gzip = zlib.createGzip();
|
|
136
|
+
const writeStream = fs.createWriteStream(filePath + '.gz');
|
|
137
|
+
return gzip.pipe(writeStream);
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Selective Logging
|
|
143
|
+
|
|
144
|
+
```js
|
|
145
|
+
// Only log errors to file, all tags to console
|
|
146
|
+
const logger = await Logger.create({
|
|
147
|
+
path: './log',
|
|
148
|
+
home: process.cwd(),
|
|
149
|
+
toFile: ['error'],
|
|
150
|
+
toStdout: ['log', 'info', 'warn', 'debug', 'error'],
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Only log info and above tags to both file and console
|
|
154
|
+
const logger = await Logger.create({
|
|
155
|
+
path: './log',
|
|
156
|
+
home: process.cwd(),
|
|
157
|
+
toFile: ['info', 'warn', 'error'],
|
|
158
|
+
toStdout: ['info', 'warn', 'error'],
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### JSON Logging
|
|
163
|
+
|
|
164
|
+
```js
|
|
165
|
+
const logger = await Logger.create({
|
|
166
|
+
path: './log',
|
|
167
|
+
home: process.cwd(),
|
|
168
|
+
json: true,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
logger.console.info('User action', { userId: 123, action: 'login' });
|
|
172
|
+
// Output: {"timestamp":"2025-01-07T10:30:00.000Z","worker":"W0","tag":"info","message":"User action","userId":123,"action":"login"}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Log Rotation and Cleanup
|
|
176
|
+
|
|
177
|
+
```js
|
|
178
|
+
const logger = await Logger.create({
|
|
179
|
+
path: './log',
|
|
180
|
+
home: process.cwd(),
|
|
181
|
+
keepDays: 7, // Keep logs for 7 days
|
|
182
|
+
workerId: 1,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// Manual rotation
|
|
186
|
+
await logger.rotate();
|
|
187
|
+
|
|
188
|
+
// Log files are automatically rotated daily
|
|
189
|
+
// Old files are cleaned up based on keepDays setting
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Error Handling
|
|
193
|
+
|
|
194
|
+
```js
|
|
195
|
+
const logger = await Logger.create({
|
|
196
|
+
path: './log',
|
|
197
|
+
home: process.cwd(),
|
|
198
|
+
crash: 'flush', // Flush buffer on process exit
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
logger.on('error', (error) => {
|
|
202
|
+
console.error('Logger error:', error.message);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// Graceful shutdown
|
|
206
|
+
process.on('SIGTERM', async () => {
|
|
207
|
+
await logger.close();
|
|
208
|
+
process.exit(0);
|
|
209
|
+
});
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## API Reference
|
|
213
|
+
|
|
214
|
+
### Logger Class
|
|
215
|
+
|
|
216
|
+
#### Constructor
|
|
217
|
+
|
|
218
|
+
```js
|
|
219
|
+
new Logger(options: LoggerOptions): Promise<Logger>
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
#### Static Methods
|
|
223
|
+
|
|
224
|
+
- `Logger.create(options: LoggerOptions): Promise<Logger>` - Create and open logger
|
|
225
|
+
|
|
226
|
+
#### Instance Methods
|
|
227
|
+
|
|
228
|
+
- `open(): Promise<Logger>` - Open log file and start logging
|
|
229
|
+
- `close(): Promise<void>` - Close logger and flush remaining data
|
|
230
|
+
- `rotate(): Promise<void>` - Manually trigger log rotation
|
|
231
|
+
- `write(tag: string, indent: number, args: unknown[]): void` - Low-level write method
|
|
232
|
+
- `flush(callback?: (error?: Error) => void): void` - Flush buffer to disk
|
|
233
|
+
|
|
234
|
+
#### Properties
|
|
235
|
+
|
|
236
|
+
- `active: boolean` - Whether logger is currently active
|
|
237
|
+
- `path: string` - Log directory path
|
|
238
|
+
- `home: string` - Home directory for path normalization
|
|
239
|
+
- `console: Console` - Console instance for logging
|
|
240
|
+
|
|
241
|
+
### BufferedStream Class
|
|
242
|
+
|
|
243
|
+
```js
|
|
244
|
+
const stream = new BufferedStream({
|
|
245
|
+
stream: fs.createWriteStream('output.log'),
|
|
246
|
+
writeBuffer: 32 * 1024, // 32KB buffer
|
|
247
|
+
flushInterval: 5000, // 5 second flush interval
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
stream.write(Buffer.from('data'));
|
|
251
|
+
stream.flush();
|
|
252
|
+
await stream.close();
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Formatter Class
|
|
256
|
+
|
|
257
|
+
```js
|
|
258
|
+
const formatter = new Formatter({
|
|
259
|
+
worker: 'W1',
|
|
260
|
+
home: '/app',
|
|
261
|
+
json: false,
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
const formatted = formatter.formatPretty('info', 0, ['Message']);
|
|
265
|
+
const jsonOutput = formatter.formatJson('error', 0, [error]);
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Best Practices
|
|
269
|
+
|
|
270
|
+
### Performance Optimization
|
|
271
|
+
|
|
272
|
+
1. **Buffer Size**: Adjust `writeBuffer` based on your log volume
|
|
273
|
+
- High volume: 128KB or larger
|
|
274
|
+
- Low volume: 16KB or smaller
|
|
275
|
+
|
|
276
|
+
2. **Flush Interval**: Balance between performance and data safety
|
|
277
|
+
- Production: 3-10 seconds
|
|
278
|
+
- Development: 1-3 seconds
|
|
279
|
+
|
|
280
|
+
3. **Selective Logging**: Use `toFile` and `toStdout` to reduce I/O
|
|
281
|
+
```js
|
|
282
|
+
// Production: Only errors to file, warnings+ to console
|
|
283
|
+
toFile: ['error'],
|
|
284
|
+
toStdout: ['warn', 'error']
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Log Management
|
|
288
|
+
|
|
289
|
+
1. **Rotation Strategy**: Set appropriate `keepDays` based on storage
|
|
290
|
+
2. **Path Organization**: Use structured paths for multi-service deployments
|
|
291
|
+
|
|
292
|
+
```js
|
|
293
|
+
path: `/var/log/app/${process.env.NODE_ENV}/${process.env.SERVICE_NAME}`;
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
3. **Error Handling**: Always handle logger errors
|
|
297
|
+
```js
|
|
298
|
+
logger.on('error', (error) => {
|
|
299
|
+
// Fallback logging or alerting
|
|
300
|
+
});
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Development vs Production
|
|
304
|
+
|
|
305
|
+
```js
|
|
306
|
+
const isDevelopment = process.env.NODE_ENV === 'development';
|
|
307
|
+
|
|
308
|
+
const logger = await Logger.create({
|
|
309
|
+
path: './log',
|
|
310
|
+
home: process.cwd(),
|
|
311
|
+
json: !isDevelopment, // JSON in production, pretty in dev
|
|
312
|
+
toFile: isDevelopment ? ['log', 'info', 'warn', 'error'] : ['error'],
|
|
313
|
+
toStdout: isDevelopment
|
|
314
|
+
? ['log', 'info', 'warn', 'debug', 'error']
|
|
315
|
+
: ['warn', 'error'],
|
|
316
|
+
flushInterval: isDevelopment ? 1000 : 5000,
|
|
317
|
+
keepDays: isDevelopment ? 1 : 30,
|
|
318
|
+
});
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
## Troubleshooting
|
|
322
|
+
|
|
323
|
+
### Common Issues
|
|
324
|
+
|
|
325
|
+
**Logs not appearing in files:**
|
|
326
|
+
|
|
327
|
+
- Check `path` directory exists and is writable
|
|
328
|
+
- Verify `toFile` array includes desired log tags
|
|
329
|
+
- Ensure logger is properly opened with `await logger.open()`
|
|
330
|
+
|
|
331
|
+
**High memory usage:**
|
|
332
|
+
|
|
333
|
+
- Reduce `writeBuffer` size
|
|
334
|
+
- Increase `flushInterval` frequency
|
|
335
|
+
- Use selective logging with `toFile`/`toStdout`
|
|
336
|
+
|
|
337
|
+
**Missing logs on crash:**
|
|
338
|
+
|
|
339
|
+
- Set `crash: 'flush'` option
|
|
340
|
+
- Handle process signals properly
|
|
341
|
+
- Use try/catch around critical operations
|
|
342
|
+
|
|
343
|
+
**Performance issues:**
|
|
344
|
+
|
|
345
|
+
- Use JSON format for high-volume logging
|
|
346
|
+
- Disable file logging for debug tags in production
|
|
347
|
+
- Consider using separate loggers for different components
|
|
348
|
+
|
|
349
|
+
### Debug Mode
|
|
350
|
+
|
|
351
|
+
```js
|
|
352
|
+
// Enable debug logging
|
|
353
|
+
const logger = await Logger.create({
|
|
354
|
+
path: './log',
|
|
355
|
+
home: process.cwd(),
|
|
356
|
+
toStdout: ['debug', 'info', 'warn', 'error'],
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
logger.console.debug('Debug information', { data: 'value' });
|
|
360
|
+
```
|
|
361
|
+
|
|
32
362
|
## License & Contributors
|
|
33
363
|
|
|
34
364
|
Copyright (c) 2017-2025 [Metarhia contributors](https://github.com/metarhia/metalog/graphs/contributors).
|
package/metalog.d.ts
CHANGED
|
@@ -5,42 +5,97 @@ interface LoggerOptions {
|
|
|
5
5
|
home: string;
|
|
6
6
|
workerId?: number;
|
|
7
7
|
createStream?: () => NodeJS.WritableStream;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
keepDays
|
|
8
|
+
writeBuffer?: number;
|
|
9
|
+
flushInterval?: number;
|
|
10
|
+
keepDays?: number;
|
|
11
11
|
json?: boolean;
|
|
12
12
|
toFile?: Array<string>;
|
|
13
13
|
toStdout?: Array<string>;
|
|
14
|
+
crash?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface BufferedStreamOptions {
|
|
18
|
+
stream?: NodeJS.WritableStream;
|
|
19
|
+
writeBuffer?: number;
|
|
20
|
+
flushInterval?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface FormatterOptions {
|
|
24
|
+
json?: boolean;
|
|
25
|
+
worker?: string;
|
|
26
|
+
home?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class BufferedStream extends EventEmitter {
|
|
30
|
+
constructor(options?: BufferedStreamOptions);
|
|
31
|
+
write(buffer: Buffer): void;
|
|
32
|
+
flush(callback?: (error?: Error) => void): void;
|
|
33
|
+
close(): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class Formatter {
|
|
37
|
+
constructor(options?: FormatterOptions);
|
|
38
|
+
format(tag: string, indent: number, args: unknown[]): string;
|
|
39
|
+
formatPretty(tag: string, indent: number, args: unknown[]): string;
|
|
40
|
+
formatFile(tag: string, indent: number, args: unknown[]): string;
|
|
41
|
+
formatJson(tag: string, indent: number, args: unknown[]): string;
|
|
42
|
+
normalizeStack(stack: string): string;
|
|
43
|
+
expandError(error: Error): unknown;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export class Console {
|
|
47
|
+
constructor(logger: Logger);
|
|
48
|
+
assert(value: unknown, ...message: unknown[]): void;
|
|
49
|
+
clear(): void;
|
|
50
|
+
count(label?: string): void;
|
|
51
|
+
countReset(label?: string): void;
|
|
52
|
+
debug(data: unknown, ...args: unknown[]): void;
|
|
53
|
+
dir(obj: unknown, options?: unknown): void;
|
|
54
|
+
dirxml(...data: unknown[]): void;
|
|
55
|
+
error(data?: unknown, ...args: unknown[]): void;
|
|
56
|
+
group(...label: unknown[]): void;
|
|
57
|
+
groupCollapsed(...label: unknown[]): void;
|
|
58
|
+
groupEnd(): void;
|
|
59
|
+
info(data?: unknown, ...args: unknown[]): void;
|
|
60
|
+
log(data?: unknown, ...args: unknown[]): void;
|
|
61
|
+
table(tabularData: unknown, properties?: string[]): void;
|
|
62
|
+
time(label?: string): void;
|
|
63
|
+
timeEnd(label?: string): void;
|
|
64
|
+
timeLog(label?: string, ...data: unknown[]): void;
|
|
65
|
+
trace(message?: unknown, ...args: unknown[]): void;
|
|
66
|
+
warn(data?: unknown, ...args: unknown[]): void;
|
|
14
67
|
}
|
|
15
68
|
|
|
16
69
|
export class Logger extends EventEmitter {
|
|
17
70
|
active: boolean;
|
|
18
71
|
path: string;
|
|
19
|
-
workerId: string;
|
|
20
|
-
createStream: () => NodeJS.WritableStream;
|
|
21
|
-
writeInterval: number;
|
|
22
|
-
writeBuffer: number;
|
|
23
|
-
keepDays: number;
|
|
24
72
|
home: string;
|
|
25
|
-
stream: NodeJS.WritableStream;
|
|
26
|
-
reopenTimer: NodeJS.Timer;
|
|
27
|
-
flushTimer: NodeJS.Timer;
|
|
28
|
-
lock: boolean;
|
|
29
|
-
buffer: Array<Buffer>;
|
|
30
|
-
bufferLength: number;
|
|
31
|
-
file: string;
|
|
32
|
-
toFile: Record<string, boolean>;
|
|
33
|
-
fsEnabled: boolean;
|
|
34
|
-
toStdout: Record<string, boolean>;
|
|
35
73
|
console: Console;
|
|
36
|
-
|
|
37
|
-
|
|
74
|
+
|
|
75
|
+
constructor(options: LoggerOptions);
|
|
76
|
+
static create(options: LoggerOptions): Promise<Logger>;
|
|
38
77
|
open(): Promise<Logger>;
|
|
39
78
|
close(): Promise<void>;
|
|
40
79
|
rotate(): Promise<void>;
|
|
41
|
-
write(
|
|
42
|
-
flush(callback
|
|
43
|
-
|
|
80
|
+
write(tag: string, indent: number, args: unknown[]): void;
|
|
81
|
+
flush(callback?: (error?: Error) => void): void;
|
|
82
|
+
|
|
83
|
+
#options: LoggerOptions;
|
|
84
|
+
#worker: string;
|
|
85
|
+
#createStream: () => NodeJS.WritableStream;
|
|
86
|
+
#keepDays: number;
|
|
87
|
+
#stream: NodeJS.WritableStream | null;
|
|
88
|
+
#rotationTimer: NodeJS.Timer | null;
|
|
89
|
+
#file: string;
|
|
90
|
+
#fsEnabled: boolean;
|
|
91
|
+
#toFile: Record<string, boolean> | null;
|
|
92
|
+
#toStdout: Record<string, boolean> | null;
|
|
93
|
+
#buffer: BufferedStream | null;
|
|
94
|
+
#formatter: Formatter;
|
|
95
|
+
|
|
96
|
+
#createDir(): Promise<void>;
|
|
97
|
+
#setupCrashHandling(): void;
|
|
44
98
|
}
|
|
45
99
|
|
|
46
|
-
export function
|
|
100
|
+
export function nowDays(): number;
|
|
101
|
+
export function nameToDays(fileName: string): number;
|
package/metalog.js
CHANGED
|
@@ -4,26 +4,26 @@ const fs = require('node:fs');
|
|
|
4
4
|
const fsp = fs.promises;
|
|
5
5
|
const path = require('node:path');
|
|
6
6
|
const util = require('node:util');
|
|
7
|
-
const
|
|
7
|
+
const EventEmitter = require('node:events');
|
|
8
8
|
const readline = require('node:readline');
|
|
9
9
|
const metautil = require('metautil');
|
|
10
10
|
const concolor = require('concolor');
|
|
11
11
|
|
|
12
12
|
const DAY_MILLISECONDS = metautil.duration('1d');
|
|
13
|
-
const
|
|
14
|
-
const
|
|
13
|
+
const DEFAULT_FLUSH_INTERVAL = metautil.duration('3s');
|
|
14
|
+
const DEFAULT_BUFFER_THRESHOLD = 64 * 1024;
|
|
15
15
|
const DEFAULT_KEEP_DAYS = 1;
|
|
16
16
|
const STACK_AT = ' at ';
|
|
17
|
-
const
|
|
17
|
+
const TAG_LENGTH = 6;
|
|
18
18
|
const LINE_SEPARATOR = ';';
|
|
19
19
|
const INDENT = 2;
|
|
20
20
|
const DATE_LEN = 'YYYY-MM-DD'.length;
|
|
21
21
|
const TIME_START = DATE_LEN + 1;
|
|
22
22
|
const TIME_END = TIME_START + 'HH:MM:SS'.length;
|
|
23
23
|
|
|
24
|
-
const
|
|
24
|
+
const LOG_TAGS = ['log', 'info', 'warn', 'debug', 'error'];
|
|
25
25
|
|
|
26
|
-
const
|
|
26
|
+
const TAG_COLOR = concolor({
|
|
27
27
|
log: 'b,black/white',
|
|
28
28
|
info: 'b,white/blue',
|
|
29
29
|
warn: 'b,black/yellow',
|
|
@@ -47,11 +47,9 @@ const DEFAULT_FLAGS = {
|
|
|
47
47
|
error: false,
|
|
48
48
|
};
|
|
49
49
|
|
|
50
|
-
const
|
|
50
|
+
const logTags = (tags) => {
|
|
51
51
|
const flags = { ...DEFAULT_FLAGS };
|
|
52
|
-
for (const
|
|
53
|
-
flags[type] = true;
|
|
54
|
-
}
|
|
52
|
+
for (const tag of tags) flags[tag] = true;
|
|
55
53
|
return flags;
|
|
56
54
|
};
|
|
57
55
|
|
|
@@ -64,9 +62,17 @@ const nowDays = () => {
|
|
|
64
62
|
return Math.floor(date.getTime() / DAY_MILLISECONDS);
|
|
65
63
|
};
|
|
66
64
|
|
|
67
|
-
const nameToDays = (fileName) => {
|
|
65
|
+
const nameToDays = (fileName = '') => {
|
|
66
|
+
if (fileName.length < DATE_LEN) {
|
|
67
|
+
throw new Error(`Invalid filename: ${fileName}`);
|
|
68
|
+
}
|
|
68
69
|
const date = fileName.substring(0, DATE_LEN);
|
|
69
|
-
const
|
|
70
|
+
const [year, month, day] = date.split('-').map(Number);
|
|
71
|
+
const fileDate = new Date(year, month - 1, day, 0, 0, 0, 0);
|
|
72
|
+
const fileTime = fileDate.getTime();
|
|
73
|
+
if (isNaN(fileTime)) {
|
|
74
|
+
throw new Error(`Invalid filename: ${fileName}`);
|
|
75
|
+
}
|
|
70
76
|
return Math.floor(fileTime / DAY_MILLISECONDS);
|
|
71
77
|
};
|
|
72
78
|
|
|
@@ -77,22 +83,148 @@ const getNextReopen = () => {
|
|
|
77
83
|
return nextDate - curTime + DAY_MILLISECONDS;
|
|
78
84
|
};
|
|
79
85
|
|
|
86
|
+
class BufferedStream extends EventEmitter {
|
|
87
|
+
#writable = null;
|
|
88
|
+
#buffers = [];
|
|
89
|
+
#size = 0;
|
|
90
|
+
#threshold = DEFAULT_BUFFER_THRESHOLD;
|
|
91
|
+
#flushing = false;
|
|
92
|
+
#flushTimer = null;
|
|
93
|
+
|
|
94
|
+
constructor(options = {}) {
|
|
95
|
+
super();
|
|
96
|
+
const { stream, writeBuffer, flushInterval } = options;
|
|
97
|
+
if (!stream) throw new Error('Stream is required');
|
|
98
|
+
this.#writable = stream;
|
|
99
|
+
if (writeBuffer) this.#threshold = writeBuffer;
|
|
100
|
+
const interval = flushInterval || DEFAULT_FLUSH_INTERVAL;
|
|
101
|
+
this.#flushTimer = setInterval(() => void this.flush(), interval);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
write(buffer) {
|
|
105
|
+
this.#buffers.push(buffer);
|
|
106
|
+
this.#size += buffer.length;
|
|
107
|
+
if (this.#size >= this.#threshold) this.flush();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
flush(callback) {
|
|
111
|
+
if (this.#flushing) {
|
|
112
|
+
if (callback) this.once('drain', callback);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (this.#size === 0) {
|
|
116
|
+
if (callback) callback();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (this.#writable.destroyed || this.#writable.closed) {
|
|
120
|
+
if (callback) callback();
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
this.#flushing = true;
|
|
124
|
+
const buffer = Buffer.concat(this.#buffers);
|
|
125
|
+
this.#buffers.length = 0;
|
|
126
|
+
this.#size = 0;
|
|
127
|
+
this.#writable.write(buffer, (error) => {
|
|
128
|
+
this.#flushing = false;
|
|
129
|
+
this.emit('drain');
|
|
130
|
+
if (callback) callback(error);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async close() {
|
|
135
|
+
clearInterval(this.#flushTimer);
|
|
136
|
+
return new Promise((resolve, reject) => {
|
|
137
|
+
this.flush((error) => {
|
|
138
|
+
if (error) return void reject(error);
|
|
139
|
+
this.#writable.end(resolve);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
class Formatter {
|
|
146
|
+
#worker = 'W0';
|
|
147
|
+
#home = './';
|
|
148
|
+
|
|
149
|
+
constructor(options = {}) {
|
|
150
|
+
const { worker, home } = options;
|
|
151
|
+
if (worker) this.#worker = worker;
|
|
152
|
+
if (home) this.#home = home;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
format(tag, indent, args) {
|
|
156
|
+
let line = util.format(...args);
|
|
157
|
+
if (tag === 'error' || tag === 'debug') line = this.normalizeStack(line);
|
|
158
|
+
return ' '.repeat(indent) + line;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
formatPretty(tag, indent, args) {
|
|
162
|
+
const dateTime = new Date().toISOString();
|
|
163
|
+
const message = this.format(tag, indent, args);
|
|
164
|
+
const normalColor = TEXT_COLOR[tag];
|
|
165
|
+
const markColor = TAG_COLOR[tag];
|
|
166
|
+
const time = normalColor(dateTime.substring(TIME_START, TIME_END));
|
|
167
|
+
const id = normalColor(this.#worker);
|
|
168
|
+
const mark = markColor(' ' + tag.padEnd(TAG_LENGTH));
|
|
169
|
+
const msg = normalColor(message);
|
|
170
|
+
return `${time} ${id} ${mark} ${msg}`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
formatFile(tag, indent, args) {
|
|
174
|
+
const dateTime = new Date().toISOString();
|
|
175
|
+
const message = this.format(tag, indent, args);
|
|
176
|
+
const msg = metautil.replace(message, '\n', LINE_SEPARATOR);
|
|
177
|
+
return `${dateTime} [${tag}] ${msg}`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
formatJson(tag, indent, args) {
|
|
181
|
+
const timestamp = new Date().toISOString();
|
|
182
|
+
const json = { timestamp, worker: this.#worker, tag, message: null };
|
|
183
|
+
let data = args.slice();
|
|
184
|
+
const head = data[0];
|
|
185
|
+
if (metautil.isError(head)) {
|
|
186
|
+
json.error = this.expandError(head);
|
|
187
|
+
data = data.slice(1);
|
|
188
|
+
} else if (typeof head === 'object') {
|
|
189
|
+
Object.assign(json, head);
|
|
190
|
+
data = data.slice(1);
|
|
191
|
+
}
|
|
192
|
+
json.message = util.format(...data);
|
|
193
|
+
return JSON.stringify(json);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
normalizeStack(stack) {
|
|
197
|
+
if (!stack) return 'No stack trace to log';
|
|
198
|
+
let res = metautil.replace(stack, STACK_AT, '');
|
|
199
|
+
if (this.#home) res = metautil.replace(res, this.#home, '');
|
|
200
|
+
return res;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
expandError(error) {
|
|
204
|
+
return {
|
|
205
|
+
message: error.message,
|
|
206
|
+
stack: this.normalizeStack(error.stack),
|
|
207
|
+
...error,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
80
212
|
class Console {
|
|
81
|
-
#
|
|
213
|
+
#logger;
|
|
82
214
|
#groupIndent = 0;
|
|
83
215
|
#counts = new Map();
|
|
84
216
|
#times = new Map();
|
|
85
217
|
#readline = readline;
|
|
86
218
|
|
|
87
|
-
constructor(
|
|
88
|
-
this.#
|
|
219
|
+
constructor(logger) {
|
|
220
|
+
this.#logger = logger;
|
|
89
221
|
}
|
|
90
222
|
|
|
91
223
|
assert(assertion, ...args) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
this.#write('error', this.#groupIndent,
|
|
224
|
+
if (!assertion) {
|
|
225
|
+
const noArgs = args.length === 0;
|
|
226
|
+
const message = noArgs ? 'Assertion failed' : util.format(...args);
|
|
227
|
+
this.#logger.write('error', this.#groupIndent, [message]);
|
|
96
228
|
}
|
|
97
229
|
}
|
|
98
230
|
|
|
@@ -105,7 +237,7 @@ class Console {
|
|
|
105
237
|
let cnt = this.#counts.get(label) || 0;
|
|
106
238
|
cnt++;
|
|
107
239
|
this.#counts.set(label, cnt);
|
|
108
|
-
this.#write('debug', this.#groupIndent, `${label}: ${cnt}`);
|
|
240
|
+
this.#logger.write('debug', this.#groupIndent, [`${label}: ${cnt}`]);
|
|
109
241
|
}
|
|
110
242
|
|
|
111
243
|
countReset(label = 'default') {
|
|
@@ -113,33 +245,38 @@ class Console {
|
|
|
113
245
|
}
|
|
114
246
|
|
|
115
247
|
debug(...args) {
|
|
116
|
-
this.#write('debug', this.#groupIndent,
|
|
248
|
+
this.#logger.write('debug', this.#groupIndent, args);
|
|
117
249
|
}
|
|
118
250
|
|
|
119
|
-
dir(
|
|
120
|
-
|
|
251
|
+
dir(obj, options) {
|
|
252
|
+
const inspected = util.inspect(obj, options);
|
|
253
|
+
this.#logger.write('debug', this.#groupIndent, [inspected]);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
dirxml(...data) {
|
|
257
|
+
this.#logger.write('debug', this.#groupIndent, data);
|
|
121
258
|
}
|
|
122
259
|
|
|
123
260
|
trace(...args) {
|
|
124
261
|
const msg = util.format(...args);
|
|
125
262
|
const err = new Error(msg);
|
|
126
|
-
this.#write('debug', this.#groupIndent, `Trace${err.stack}`);
|
|
263
|
+
this.#logger.write('debug', this.#groupIndent, [`Trace${err.stack}`]);
|
|
127
264
|
}
|
|
128
265
|
|
|
129
266
|
info(...args) {
|
|
130
|
-
this.#write('info', this.#groupIndent,
|
|
267
|
+
this.#logger.write('info', this.#groupIndent, args);
|
|
131
268
|
}
|
|
132
269
|
|
|
133
270
|
log(...args) {
|
|
134
|
-
this.#write('log', this.#groupIndent,
|
|
271
|
+
this.#logger.write('log', this.#groupIndent, args);
|
|
135
272
|
}
|
|
136
273
|
|
|
137
274
|
warn(...args) {
|
|
138
|
-
this.#write('warn', this.#groupIndent,
|
|
275
|
+
this.#logger.write('warn', this.#groupIndent, args);
|
|
139
276
|
}
|
|
140
277
|
|
|
141
278
|
error(...args) {
|
|
142
|
-
this.#write('error', this.#groupIndent,
|
|
279
|
+
this.#logger.write('error', this.#groupIndent, args);
|
|
143
280
|
}
|
|
144
281
|
|
|
145
282
|
group(...args) {
|
|
@@ -152,12 +289,26 @@ class Console {
|
|
|
152
289
|
}
|
|
153
290
|
|
|
154
291
|
groupEnd() {
|
|
155
|
-
if (this.#groupIndent
|
|
292
|
+
if (this.#groupIndent === 0) return;
|
|
156
293
|
this.#groupIndent -= INDENT;
|
|
157
294
|
}
|
|
158
295
|
|
|
159
|
-
table(tabularData) {
|
|
160
|
-
|
|
296
|
+
table(tabularData, properties) {
|
|
297
|
+
const opts = { showHidden: false, depth: null, colors: false };
|
|
298
|
+
let data = tabularData;
|
|
299
|
+
if (properties) {
|
|
300
|
+
if (!Array.isArray(data)) data = [data];
|
|
301
|
+
data = data.map((item) => {
|
|
302
|
+
const record = {};
|
|
303
|
+
for (const prop of properties) {
|
|
304
|
+
if (Object.prototype.hasOwnProperty.call(item, prop)) {
|
|
305
|
+
record[prop] = item[prop];
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return record;
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
this.#logger.write('log', 0, [util.inspect(data, opts)]);
|
|
161
312
|
}
|
|
162
313
|
|
|
163
314
|
time(label = 'default') {
|
|
@@ -172,130 +323,124 @@ class Console {
|
|
|
172
323
|
this.#times.delete(label);
|
|
173
324
|
}
|
|
174
325
|
|
|
175
|
-
timeLog(label, ...
|
|
326
|
+
timeLog(label = 'default', ...data) {
|
|
176
327
|
const startTime = this.#times.get(label);
|
|
177
328
|
if (startTime === undefined) {
|
|
178
329
|
const msg = `Warning: No such label '${label}'`;
|
|
179
|
-
this.#write('warn', this.#groupIndent, msg);
|
|
330
|
+
this.#logger.write('warn', this.#groupIndent, [msg]);
|
|
180
331
|
return;
|
|
181
332
|
}
|
|
182
|
-
|
|
333
|
+
const totalTime = process.hrtime(startTime);
|
|
334
|
+
const totalTimeMs = totalTime[0] * 1e3 + totalTime[1] / 1e6;
|
|
335
|
+
const message = data.length > 0 ? util.format(...data) : '';
|
|
336
|
+
const output = `${label}: ${totalTimeMs}ms${message ? ' ' + message : ''}`;
|
|
337
|
+
this.#logger.write('debug', this.#groupIndent, [output]);
|
|
183
338
|
}
|
|
184
339
|
}
|
|
185
340
|
|
|
186
|
-
class Logger extends
|
|
187
|
-
|
|
341
|
+
class Logger extends EventEmitter {
|
|
342
|
+
active = false;
|
|
343
|
+
#worker = 'W0';
|
|
344
|
+
#createStream = fs.createWriteStream;
|
|
345
|
+
#options = null;
|
|
346
|
+
#keepDays = DEFAULT_KEEP_DAYS;
|
|
347
|
+
#stream = null;
|
|
348
|
+
#rotationTimer = null;
|
|
349
|
+
#file = '';
|
|
350
|
+
#fsEnabled = false;
|
|
351
|
+
#toFile = null;
|
|
352
|
+
#toStdout = null;
|
|
353
|
+
#buffer = null;
|
|
354
|
+
#formatter = null;
|
|
355
|
+
|
|
356
|
+
constructor(options) {
|
|
188
357
|
super();
|
|
189
|
-
|
|
190
|
-
const {
|
|
191
|
-
const { toFile =
|
|
192
|
-
this.
|
|
193
|
-
this.path = args.path;
|
|
194
|
-
this.workerId = `W${workerId}`;
|
|
195
|
-
this.createStream = createStream;
|
|
196
|
-
this.writeInterval = writeInterval || DEFAULT_WRITE_INTERVAL;
|
|
197
|
-
this.writeBuffer = writeBuffer || DEFAULT_BUFFER_SIZE;
|
|
198
|
-
this.keepDays = keepDays || DEFAULT_KEEP_DAYS;
|
|
358
|
+
this.#options = options;
|
|
359
|
+
const { workerId, createStream, keepDays, home, crash, json } = options;
|
|
360
|
+
const { toFile = LOG_TAGS, toStdout = LOG_TAGS } = options;
|
|
361
|
+
this.path = options.path;
|
|
199
362
|
this.home = home;
|
|
200
|
-
this.
|
|
201
|
-
this
|
|
202
|
-
this
|
|
203
|
-
this
|
|
204
|
-
this
|
|
205
|
-
this
|
|
206
|
-
|
|
207
|
-
this
|
|
208
|
-
this
|
|
209
|
-
this
|
|
210
|
-
this.toStdout = logTypes(toStdout);
|
|
211
|
-
this.console = new Console((...args) => this.write(...args));
|
|
363
|
+
this.console = new Console(this);
|
|
364
|
+
if (workerId) this.#worker = `W${workerId}`;
|
|
365
|
+
if (toFile) this.#toFile = logTags(toFile);
|
|
366
|
+
if (toStdout) this.#toStdout = logTags(toStdout);
|
|
367
|
+
if (createStream) this.#createStream = createStream;
|
|
368
|
+
if (keepDays) this.#keepDays = keepDays;
|
|
369
|
+
if (crash === 'flush') this.#setupCrashHandling();
|
|
370
|
+
this.#fsEnabled = toFile.length !== 0;
|
|
371
|
+
this.#buffer = null;
|
|
372
|
+
this.#formatter = new Formatter({ json, worker: this.#worker, home });
|
|
212
373
|
return this.open();
|
|
213
374
|
}
|
|
214
375
|
|
|
215
|
-
|
|
216
|
-
return new
|
|
217
|
-
fs.access(this.path, (err) => {
|
|
218
|
-
if (!err) resolve();
|
|
219
|
-
fs.mkdir(this.path, (err) => {
|
|
220
|
-
if (!err || err.code === 'EEXIST') return void resolve();
|
|
221
|
-
const error = new Error(`Can not create directory: ${this.path}\n`);
|
|
222
|
-
this.emit('error', error);
|
|
223
|
-
reject();
|
|
224
|
-
});
|
|
225
|
-
});
|
|
226
|
-
});
|
|
376
|
+
static async create(options) {
|
|
377
|
+
return new Logger(options);
|
|
227
378
|
}
|
|
228
379
|
|
|
229
380
|
async open() {
|
|
230
381
|
if (this.active) return this;
|
|
231
382
|
this.active = true;
|
|
232
|
-
if (!this
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
await this.createLogDir();
|
|
237
|
-
const fileName = metautil.nowDate() + '-' + this.workerId + '.log';
|
|
238
|
-
this.file = path.join(this.path, fileName);
|
|
383
|
+
if (!this.#fsEnabled) return this;
|
|
384
|
+
await this.#createDir();
|
|
385
|
+
const fileName = metautil.nowDate() + '-' + this.#worker + '.log';
|
|
386
|
+
this.#file = path.join(this.path, fileName);
|
|
239
387
|
const nextReopen = getNextReopen();
|
|
240
|
-
this
|
|
388
|
+
this.#rotationTimer = setTimeout(() => {
|
|
241
389
|
this.once('close', () => {
|
|
242
390
|
this.open();
|
|
243
391
|
});
|
|
244
|
-
this.close().catch((
|
|
245
|
-
|
|
246
|
-
this.emit('error', err);
|
|
392
|
+
this.close().catch((error) => {
|
|
393
|
+
this.emit('error', error);
|
|
247
394
|
});
|
|
248
395
|
}, nextReopen);
|
|
249
|
-
if (this
|
|
250
|
-
|
|
251
|
-
this
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
this.
|
|
396
|
+
if (this.#keepDays) await this.rotate();
|
|
397
|
+
const stream = this.#createStream(this.#file, { flags: 'a' });
|
|
398
|
+
this.#stream = stream;
|
|
399
|
+
const { writeBuffer, flushInterval } = this.#options;
|
|
400
|
+
this.#buffer = new BufferedStream({ writeBuffer, stream, flushInterval });
|
|
401
|
+
stream.on('error', (error) => {
|
|
402
|
+
const errorMsg = `Can't open log file: ${this.#file}, ${error.message}`;
|
|
403
|
+
this.emit('error', new Error(errorMsg));
|
|
256
404
|
});
|
|
257
|
-
|
|
258
|
-
this.emit('error', new Error(`Can't open log file: ${this.file}`));
|
|
259
|
-
});
|
|
260
|
-
await events.once(this, 'open');
|
|
405
|
+
await EventEmitter.once(stream, 'open');
|
|
261
406
|
return this;
|
|
262
407
|
}
|
|
263
408
|
|
|
264
409
|
async close() {
|
|
265
410
|
if (!this.active) return Promise.resolve();
|
|
266
|
-
if (!this
|
|
411
|
+
if (!this.#fsEnabled) {
|
|
267
412
|
this.active = false;
|
|
268
413
|
this.emit('close');
|
|
269
414
|
return Promise.resolve();
|
|
270
415
|
}
|
|
271
|
-
const
|
|
272
|
-
if (
|
|
273
|
-
|
|
274
|
-
|
|
416
|
+
const stream = this.#stream;
|
|
417
|
+
if (stream.destroyed || stream.closed) return Promise.resolve();
|
|
418
|
+
clearTimeout(this.#rotationTimer);
|
|
419
|
+
this.#rotationTimer = null;
|
|
275
420
|
return new Promise((resolve, reject) => {
|
|
276
|
-
this.flush((
|
|
277
|
-
if (
|
|
421
|
+
this.flush((error) => {
|
|
422
|
+
if (error) return void reject(error);
|
|
278
423
|
this.active = false;
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
424
|
+
this.#buffer
|
|
425
|
+
.close()
|
|
426
|
+
.then(() => {
|
|
427
|
+
const fileName = this.#file;
|
|
428
|
+
this.emit('close');
|
|
429
|
+
fs.stat(fileName, (error, stats) => {
|
|
430
|
+
if (error || stats.size > 0) {
|
|
431
|
+
return void resolve();
|
|
432
|
+
}
|
|
288
433
|
fsp.unlink(fileName).catch(() => {});
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
})
|
|
292
|
-
|
|
434
|
+
resolve();
|
|
435
|
+
});
|
|
436
|
+
})
|
|
437
|
+
.catch(reject);
|
|
293
438
|
});
|
|
294
439
|
});
|
|
295
440
|
}
|
|
296
441
|
|
|
297
442
|
async rotate() {
|
|
298
|
-
if (!this
|
|
443
|
+
if (!this.#keepDays) return;
|
|
299
444
|
const now = nowDays();
|
|
300
445
|
const finish = [];
|
|
301
446
|
try {
|
|
@@ -303,121 +448,86 @@ class Logger extends events.EventEmitter {
|
|
|
303
448
|
for (const fileName of files) {
|
|
304
449
|
if (metautil.fileExt(fileName) !== 'log') continue;
|
|
305
450
|
const fileAge = now - nameToDays(fileName);
|
|
306
|
-
if (fileAge < this
|
|
307
|
-
|
|
451
|
+
if (fileAge < this.#keepDays) continue;
|
|
452
|
+
const promise = fsp
|
|
453
|
+
.unlink(path.join(this.path, fileName))
|
|
454
|
+
.catch(() => {});
|
|
455
|
+
finish.push(promise);
|
|
308
456
|
}
|
|
309
457
|
await Promise.all(finish);
|
|
310
|
-
} catch (
|
|
311
|
-
process.stdout.write(`${
|
|
312
|
-
this.emit('error',
|
|
458
|
+
} catch (error) {
|
|
459
|
+
process.stdout.write(`${error.stack}\n`);
|
|
460
|
+
this.emit('error', error);
|
|
313
461
|
}
|
|
314
462
|
}
|
|
315
463
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
return `${time} ${id} ${mark} ${msg}`;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
formatFile(type, indent, ...args) {
|
|
335
|
-
const dateTime = new Date().toISOString();
|
|
336
|
-
const message = this.format(type, indent, ...args);
|
|
337
|
-
const msg = metautil.replace(message, '\n', LINE_SEPARATOR);
|
|
338
|
-
return `${dateTime} [${type}] ${msg}`;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
formatJson(type, indent, ...args) {
|
|
342
|
-
const log = {
|
|
343
|
-
timestamp: new Date().toISOString(),
|
|
344
|
-
workerId: this.workerId,
|
|
345
|
-
level: type,
|
|
346
|
-
message: null,
|
|
347
|
-
};
|
|
348
|
-
if (metautil.isError(args[0])) {
|
|
349
|
-
log.err = this.expandError(args[0]);
|
|
350
|
-
args = args.slice(1);
|
|
351
|
-
} else if (typeof args[0] === 'object') {
|
|
352
|
-
Object.assign(log, args[0]);
|
|
353
|
-
if (metautil.isError(log.err)) log.err = this.expandError(log.err);
|
|
354
|
-
if (metautil.isError(log.error)) log.error = this.expandError(log.error);
|
|
355
|
-
args = args.slice(1);
|
|
356
|
-
}
|
|
357
|
-
log.message = util.format(...args);
|
|
358
|
-
return JSON.stringify(log);
|
|
464
|
+
#createDir() {
|
|
465
|
+
return new Promise((resolve, reject) => {
|
|
466
|
+
fs.access(this.path, (error) => {
|
|
467
|
+
if (!error) resolve();
|
|
468
|
+
fs.mkdir(this.path, (error) => {
|
|
469
|
+
if (!error || error.code === 'EEXIST') {
|
|
470
|
+
return void resolve();
|
|
471
|
+
} else {
|
|
472
|
+
const error = new Error(`Can not create directory: ${this.path}`);
|
|
473
|
+
this.emit('error', error);
|
|
474
|
+
reject(error);
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
});
|
|
478
|
+
});
|
|
359
479
|
}
|
|
360
480
|
|
|
361
|
-
write(
|
|
362
|
-
if (this
|
|
363
|
-
const line = this.json
|
|
364
|
-
? this.formatJson(
|
|
365
|
-
: this.formatPretty(
|
|
481
|
+
write(tag, indent, args) {
|
|
482
|
+
if (this.#toStdout[tag]) {
|
|
483
|
+
const line = this.#options.json
|
|
484
|
+
? this.#formatter.formatJson(tag, indent, args)
|
|
485
|
+
: this.#formatter.formatPretty(tag, indent, args);
|
|
366
486
|
process.stdout.write(line + '\n');
|
|
367
487
|
}
|
|
368
|
-
if (this
|
|
369
|
-
const line = this.json
|
|
370
|
-
? this.formatJson(
|
|
371
|
-
: this.formatFile(
|
|
488
|
+
if (this.#toFile[tag]) {
|
|
489
|
+
const line = this.#options.json
|
|
490
|
+
? this.#formatter.formatJson(tag, indent, args)
|
|
491
|
+
: this.#formatter.formatFile(tag, indent, args);
|
|
372
492
|
const buffer = Buffer.from(line + '\n');
|
|
373
|
-
this
|
|
374
|
-
this.bufferLength += buffer.length;
|
|
375
|
-
if (this.bufferLength >= this.writeBuffer) this.flush();
|
|
493
|
+
this.#buffer.write(buffer);
|
|
376
494
|
}
|
|
377
495
|
}
|
|
378
496
|
|
|
379
497
|
flush(callback) {
|
|
380
|
-
if (this.
|
|
381
|
-
if (callback) this.once('unlocked', callback);
|
|
382
|
-
return;
|
|
383
|
-
}
|
|
384
|
-
if (this.buffer.length === 0) {
|
|
498
|
+
if (!this.active) {
|
|
385
499
|
if (callback) callback();
|
|
386
500
|
return;
|
|
387
501
|
}
|
|
388
|
-
if (!this
|
|
389
|
-
|
|
390
|
-
this.emit('error', err);
|
|
391
|
-
if (callback) callback(err);
|
|
502
|
+
if (!this.#buffer) {
|
|
503
|
+
if (callback) callback();
|
|
392
504
|
return;
|
|
393
505
|
}
|
|
394
|
-
this.
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
this.bufferLength = 0;
|
|
398
|
-
this.stream.write(buffer, () => {
|
|
399
|
-
this.lock = false;
|
|
400
|
-
this.emit('unlocked');
|
|
401
|
-
if (callback) callback();
|
|
506
|
+
this.#buffer.flush((error) => {
|
|
507
|
+
if (error) this.emit('error', error);
|
|
508
|
+
if (callback) callback(error);
|
|
402
509
|
});
|
|
403
510
|
}
|
|
404
511
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
if (this.home) res = metautil.replace(res, this.home, '');
|
|
409
|
-
return res;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
expandError(err) {
|
|
413
|
-
return {
|
|
414
|
-
message: err.message,
|
|
415
|
-
stack: this.normalizeStack(err.stack),
|
|
416
|
-
...err,
|
|
512
|
+
#setupCrashHandling() {
|
|
513
|
+
const exitHandler = () => {
|
|
514
|
+
if (this.active) this.flush();
|
|
417
515
|
};
|
|
516
|
+
process.on('SIGTERM', exitHandler);
|
|
517
|
+
process.on('SIGINT', exitHandler);
|
|
518
|
+
process.on('SIGUSR1', exitHandler);
|
|
519
|
+
process.on('SIGUSR2', exitHandler);
|
|
520
|
+
process.on('uncaughtException', exitHandler);
|
|
521
|
+
process.on('unhandledRejection', exitHandler);
|
|
522
|
+
process.on('exit', exitHandler);
|
|
418
523
|
}
|
|
419
524
|
}
|
|
420
525
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
526
|
+
module.exports = {
|
|
527
|
+
Logger,
|
|
528
|
+
Console,
|
|
529
|
+
BufferedStream,
|
|
530
|
+
Formatter,
|
|
531
|
+
nowDays,
|
|
532
|
+
nameToDays,
|
|
533
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "metalog",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0-prerelease",
|
|
4
4
|
"author": "Timur Shemsedinov <timur.shemsedinov@gmail.com>",
|
|
5
5
|
"description": "Logger for Metarhia",
|
|
6
6
|
"license": "MIT",
|
|
@@ -49,17 +49,17 @@
|
|
|
49
49
|
"fix": "eslint . --fix && prettier --write \"**/*.js\" \"**/*.json\" \"**/*.md\" \"**/*.ts\""
|
|
50
50
|
},
|
|
51
51
|
"engines": {
|
|
52
|
-
"node": "18
|
|
52
|
+
"node": ">=18"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"concolor": "^1.1.
|
|
56
|
-
"metautil": "^5.
|
|
55
|
+
"concolor": "^1.1.3",
|
|
56
|
+
"metautil": "^5.3.0"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
|
-
"@types/node": "^
|
|
60
|
-
"eslint": "^9.
|
|
61
|
-
"eslint-config-metarhia": "^9.1.
|
|
62
|
-
"prettier": "^3.
|
|
63
|
-
"typescript": "^5.
|
|
59
|
+
"@types/node": "^24.3.1",
|
|
60
|
+
"eslint": "^9.35.0",
|
|
61
|
+
"eslint-config-metarhia": "^9.1.3",
|
|
62
|
+
"prettier": "^3.6.2",
|
|
63
|
+
"typescript": "^5.9.2"
|
|
64
64
|
}
|
|
65
65
|
}
|