metalog 4.0.2-prerelease → 4.0.3-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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2017-2025 Metarhia
3
+ Copyright (c) 2017-2026 Metarhia
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -361,6 +361,6 @@ logger.console.debug('Debug information', { data: 'value' });
361
361
 
362
362
  ## License & Contributors
363
363
 
364
- Copyright (c) 2017-2025 [Metarhia contributors](https://github.com/metarhia/metalog/graphs/contributors).
364
+ Copyright (c) 2017-2026 [Metarhia contributors](https://github.com/metarhia/metalog/graphs/contributors).
365
365
  Metalog is [MIT licensed](./LICENSE).\
366
366
  Metalog is a part of [Metarhia](https://github.com/metarhia) technology stack.
package/metalog.d.ts CHANGED
@@ -79,22 +79,6 @@ export class Logger extends EventEmitter {
79
79
  rotate(): Promise<void>;
80
80
  write(tag: string, indent: number, args: unknown[]): void;
81
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;
98
82
  }
99
83
 
100
84
  export function nowDays(): number;
package/metalog.js CHANGED
@@ -23,6 +23,16 @@ const TIME_END = TIME_START + 'HH:MM:SS'.length;
23
23
 
24
24
  const LOG_TAGS = ['log', 'info', 'warn', 'debug', 'error'];
25
25
 
26
+ const CRASH_EVENTS = [
27
+ 'SIGTERM',
28
+ 'SIGINT',
29
+ 'SIGUSR1',
30
+ 'SIGUSR2',
31
+ 'uncaughtException',
32
+ 'unhandledRejection',
33
+ 'exit',
34
+ ];
35
+
26
36
  const TAG_COLOR = concolor({
27
37
  log: 'b,black/white',
28
38
  info: 'b,white/blue',
@@ -55,11 +65,10 @@ const logTags = (tags) => {
55
65
 
56
66
  const nowDays = () => {
57
67
  const now = new Date();
58
- const year = now.getUTCFullYear();
59
- const month = now.getUTCMonth();
60
- const day = now.getUTCDate();
61
- const date = new Date(year, month, day, 0, 0, 0, 0);
62
- return Math.floor(date.getTime() / DAY_MILLISECONDS);
68
+ const y = now.getUTCFullYear();
69
+ const m = now.getUTCMonth();
70
+ const d = now.getUTCDate();
71
+ return Math.floor(Date.UTC(y, m, d) / DAY_MILLISECONDS);
63
72
  };
64
73
 
65
74
  const nameToDays = (fileName = '') => {
@@ -68,12 +77,11 @@ const nameToDays = (fileName = '') => {
68
77
  }
69
78
  const date = fileName.substring(0, DATE_LEN);
70
79
  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)) {
80
+ const utc = Date.UTC(year, month - 1, day);
81
+ if (isNaN(utc)) {
74
82
  throw new Error(`Invalid filename: ${fileName}`);
75
83
  }
76
- return Math.floor(fileTime / DAY_MILLISECONDS);
84
+ return Math.floor(utc / DAY_MILLISECONDS);
77
85
  };
78
86
 
79
87
  const getNextReopen = () => {
@@ -165,7 +173,7 @@ class Formatter {
165
173
  const markColor = TAG_COLOR[tag];
166
174
  const time = normalColor(dateTime.substring(TIME_START, TIME_END));
167
175
  const id = normalColor(this.#worker);
168
- const mark = markColor(' ' + tag.padEnd(TAG_LENGTH));
176
+ const mark = markColor(` ${tag.padEnd(TAG_LENGTH)}`);
169
177
  const msg = normalColor(message);
170
178
  return `${time} ${id} ${mark} ${msg}`;
171
179
  }
@@ -173,30 +181,31 @@ class Formatter {
173
181
  formatFile(tag, indent, args) {
174
182
  const dateTime = new Date().toISOString();
175
183
  const message = this.format(tag, indent, args);
176
- const msg = metautil.replace(message, '\n', LINE_SEPARATOR);
184
+ const msg = message.replaceAll('\n', LINE_SEPARATOR);
177
185
  return `${dateTime} [${tag}] ${msg}`;
178
186
  }
179
187
 
180
188
  formatJson(tag, indent, args) {
181
189
  const timestamp = new Date().toISOString();
182
190
  const json = { timestamp, worker: this.#worker, tag, message: null };
183
- let data = args.slice();
184
- const head = data[0];
191
+ const head = args[0];
192
+ let start = 0;
185
193
  if (metautil.isError(head)) {
186
194
  json.error = this.expandError(head);
187
- data = data.slice(1);
195
+ start = 1;
188
196
  } else if (typeof head === 'object') {
189
197
  Object.assign(json, head);
190
- data = data.slice(1);
198
+ start = 1;
191
199
  }
192
- json.message = util.format(...data);
200
+ const rest = start === 0 ? args : args.slice(start);
201
+ json.message = util.format(...rest);
193
202
  return JSON.stringify(json);
194
203
  }
195
204
 
196
205
  normalizeStack(stack) {
197
206
  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, '');
207
+ let res = stack.replaceAll(STACK_AT, '');
208
+ if (this.#home) res = res.replaceAll(this.#home, '');
200
209
  return res;
201
210
  }
202
211
 
@@ -312,15 +321,20 @@ class Console {
312
321
  }
313
322
 
314
323
  time(label = 'default') {
315
- this.#times.set(label, process.hrtime());
324
+ this.#times.set(label, process.hrtime.bigint());
316
325
  }
317
326
 
318
327
  timeEnd(label = 'default') {
319
328
  const startTime = this.#times.get(label);
320
- const totalTime = process.hrtime(startTime);
321
- const totalTimeMs = totalTime[0] * 1e3 + totalTime[1] / 1e6;
322
- this.timeLog(label, `${label}: ${totalTimeMs}ms`);
329
+ if (startTime === undefined) {
330
+ const msg = `Warning: No such label '${label}'`;
331
+ this.#logger.write('warn', this.#groupIndent, [msg]);
332
+ return;
333
+ }
323
334
  this.#times.delete(label);
335
+ const elapsed = Number(process.hrtime.bigint() - startTime) / 1e6;
336
+ const output = `${label}: ${elapsed}ms`;
337
+ this.#logger.write('debug', this.#groupIndent, [output]);
324
338
  }
325
339
 
326
340
  timeLog(label = 'default', ...data) {
@@ -330,10 +344,10 @@ class Console {
330
344
  this.#logger.write('warn', this.#groupIndent, [msg]);
331
345
  return;
332
346
  }
333
- const totalTime = process.hrtime(startTime);
334
- const totalTimeMs = totalTime[0] * 1e3 + totalTime[1] / 1e6;
347
+ const elapsed = Number(process.hrtime.bigint() - startTime) / 1e6;
335
348
  const message = data.length > 0 ? util.format(...data) : '';
336
- const output = `${label}: ${totalTimeMs}ms${message ? ' ' + message : ''}`;
349
+ const suffix = message ? ` ${message}` : '';
350
+ const output = `${label}: ${elapsed}ms${suffix}`;
337
351
  this.#logger.write('debug', this.#groupIndent, [output]);
338
352
  }
339
353
  }
@@ -352,6 +366,7 @@ class Logger extends EventEmitter {
352
366
  #toStdout = null;
353
367
  #buffer = null;
354
368
  #formatter = null;
369
+ #exitHandler = null;
355
370
 
356
371
  constructor(options) {
357
372
  super();
@@ -383,7 +398,7 @@ class Logger extends EventEmitter {
383
398
  this.active = true;
384
399
  if (!this.#fsEnabled) return this;
385
400
  await this.#createDir();
386
- const fileName = metautil.nowDate() + '-' + this.#worker + '.log';
401
+ const fileName = `${metautil.nowDate()}-${this.#worker}.log`;
387
402
  this.#file = path.join(this.path, fileName);
388
403
  const nextReopen = getNextReopen();
389
404
  this.#rotationTimer = setTimeout(() => {
@@ -408,36 +423,26 @@ class Logger extends EventEmitter {
408
423
  }
409
424
 
410
425
  async close() {
411
- if (!this.active) return Promise.resolve();
426
+ if (!this.active) return;
427
+ this.#removeCrashHandling();
412
428
  if (!this.#fsEnabled) {
413
429
  this.active = false;
414
430
  this.emit('close');
415
- return Promise.resolve();
431
+ return;
416
432
  }
417
433
  const stream = this.#stream;
418
- if (stream.destroyed || stream.closed) return Promise.resolve();
434
+ if (stream.destroyed || stream.closed) return;
419
435
  clearTimeout(this.#rotationTimer);
420
436
  this.#rotationTimer = null;
421
- return new Promise((resolve, reject) => {
422
- this.flush((error) => {
423
- if (error) return void reject(error);
424
- this.active = false;
425
- this.#buffer
426
- .close()
427
- .then(() => {
428
- const fileName = this.#file;
429
- this.emit('close');
430
- fs.stat(fileName, (error, stats) => {
431
- if (error || stats.size > 0) {
432
- return void resolve();
433
- }
434
- fsp.unlink(fileName).catch(() => {});
435
- resolve();
436
- });
437
- })
438
- .catch(reject);
439
- });
440
- });
437
+ this.active = false;
438
+ await this.#buffer.close();
439
+ this.emit('close');
440
+ try {
441
+ const stats = await fsp.stat(this.#file);
442
+ if (stats.size === 0) await fsp.unlink(this.#file).catch(() => {});
443
+ } catch {
444
+ this.emit('error', new Error(`Can't delete log file: ${this.#file}`));
445
+ }
441
446
  }
442
447
 
443
448
  async rotate() {
@@ -461,36 +466,35 @@ class Logger extends EventEmitter {
461
466
  }
462
467
  }
463
468
 
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
- });
469
+ async #createDir() {
470
+ try {
471
+ await fsp.mkdir(this.path, { recursive: true });
472
+ } catch (cause) {
473
+ const error = new Error(`Can not create directory: ${this.path}`, {
474
+ cause,
477
475
  });
478
- });
476
+ this.emit('error', error);
477
+ throw error;
478
+ }
479
479
  }
480
480
 
481
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);
486
- process.stdout.write(line + '\n');
487
- }
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);
492
- const buffer = Buffer.from(line + '\n');
493
- this.#buffer.write(buffer);
482
+ const toStdout = this.#toStdout[tag];
483
+ const toFile = this.#toFile[tag];
484
+ if (!toStdout && !toFile) return;
485
+ if (this.#options.json) {
486
+ const line = `${this.#formatter.formatJson(tag, indent, args)}\n`;
487
+ if (toStdout) process.stdout.write(line);
488
+ if (toFile) this.#buffer.write(Buffer.from(line));
489
+ } else {
490
+ if (toStdout) {
491
+ const pretty = this.#formatter.formatPretty(tag, indent, args);
492
+ process.stdout.write(`${pretty}\n`);
493
+ }
494
+ if (toFile) {
495
+ const file = this.#formatter.formatFile(tag, indent, args);
496
+ this.#buffer.write(Buffer.from(`${file}\n`));
497
+ }
494
498
  }
495
499
  }
496
500
 
@@ -510,16 +514,20 @@ class Logger extends EventEmitter {
510
514
  }
511
515
 
512
516
  #setupCrashHandling() {
513
- const exitHandler = () => {
517
+ this.#exitHandler = () => {
514
518
  if (this.active) this.flush();
515
519
  };
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);
520
+ for (const event of CRASH_EVENTS) {
521
+ process.on(event, this.#exitHandler);
522
+ }
523
+ }
524
+
525
+ #removeCrashHandling() {
526
+ if (!this.#exitHandler) return;
527
+ for (const event of CRASH_EVENTS) {
528
+ process.removeListener(event, this.#exitHandler);
529
+ }
530
+ this.#exitHandler = null;
523
531
  }
524
532
  }
525
533
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metalog",
3
- "version": "4.0.2-prerelease",
3
+ "version": "4.0.3-prerelease",
4
4
  "author": "Timur Shemsedinov <timur.shemsedinov@gmail.com>",
5
5
  "description": "Logger for Metarhia",
6
6
  "license": "MIT",
@@ -12,7 +12,7 @@
12
12
  "impress",
13
13
  "server",
14
14
  "jstp",
15
- "globalstorege",
15
+ "globalstorage",
16
16
  "highload",
17
17
  "cloud",
18
18
  "api",
@@ -39,7 +39,7 @@
39
39
  "types": "metalog.d.ts",
40
40
  "readmeFilename": "README.md",
41
41
  "files": [
42
- "types/",
42
+ "metalog.js",
43
43
  "metalog.d.ts"
44
44
  ],
45
45
  "scripts": {
@@ -53,12 +53,12 @@
53
53
  },
54
54
  "dependencies": {
55
55
  "concolor": "^1.1.4",
56
- "metautil": "^5.4.0"
56
+ "metautil": "^5.5.1"
57
57
  },
58
58
  "devDependencies": {
59
- "@types/node": "^25.0.3",
60
- "eslint": "^9.39.2",
61
- "eslint-config-metarhia": "^9.1.5",
59
+ "@types/node": "^25.4.0",
60
+ "eslint": "^9.39.4",
61
+ "eslint-config-metarhia": "^9.1.8",
62
62
  "prettier": "^3.7.4",
63
63
  "typescript": "^5.9.2"
64
64
  }