logixia 1.10.2 → 1.10.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.
Files changed (40) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/index.d.mts +5 -2
  3. package/dist/index.d.mts.map +1 -1
  4. package/dist/index.d.ts +5 -2
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +75 -33
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +75 -33
  9. package/dist/index.mjs.map +1 -1
  10. package/dist/logitron-logger.module-B8NklSC4.d.mts.map +1 -1
  11. package/dist/{logitron-logger.module-Bt_Jei1V.mjs → logitron-logger.module-BBC9nO5q.mjs} +118 -37
  12. package/dist/logitron-logger.module-BBC9nO5q.mjs.map +1 -0
  13. package/dist/logitron-logger.module-BLT1y5Iq.d.ts.map +1 -1
  14. package/dist/{logitron-logger.module-bJ1hGhaL.js → logitron-logger.module-Dlf5GwJ9.js} +118 -37
  15. package/dist/logitron-logger.module-Dlf5GwJ9.js.map +1 -0
  16. package/dist/middleware.d.mts.map +1 -1
  17. package/dist/middleware.d.ts.map +1 -1
  18. package/dist/middleware.js +4 -3
  19. package/dist/middleware.js.map +1 -1
  20. package/dist/middleware.mjs +4 -3
  21. package/dist/middleware.mjs.map +1 -1
  22. package/dist/nest.d.mts.map +1 -1
  23. package/dist/nest.d.ts.map +1 -1
  24. package/dist/nest.js +2 -2
  25. package/dist/nest.mjs +2 -2
  26. package/dist/{transport.manager-zgEZCJhR.js → transport.manager-B9LF9uDd.js} +130 -56
  27. package/dist/transport.manager-B9LF9uDd.js.map +1 -0
  28. package/dist/{transport.manager-CaL4XuLD.mjs → transport.manager-Cij_sA-b.mjs} +128 -56
  29. package/dist/transport.manager-Cij_sA-b.mjs.map +1 -0
  30. package/dist/transports.d.mts +41 -2
  31. package/dist/transports.d.mts.map +1 -1
  32. package/dist/transports.d.ts +41 -2
  33. package/dist/transports.d.ts.map +1 -1
  34. package/dist/transports.js +1 -1
  35. package/dist/transports.mjs +1 -1
  36. package/package.json +1 -1
  37. package/dist/logitron-logger.module-Bt_Jei1V.mjs.map +0 -1
  38. package/dist/logitron-logger.module-bJ1hGhaL.js.map +0 -1
  39. package/dist/transport.manager-CaL4XuLD.mjs.map +0 -1
  40. package/dist/transport.manager-zgEZCJhR.js.map +0 -1
@@ -3,7 +3,9 @@ import { EventEmitter } from "node:events";
3
3
  import * as readline from "node:readline";
4
4
  import * as fs from "node:fs";
5
5
  import * as path from "node:path";
6
+ import { pipeline } from "node:stream/promises";
6
7
  import { promisify } from "node:util";
8
+ import { createGzip } from "node:zlib";
7
9
 
8
10
  //#region rolldown:runtime
9
11
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
@@ -21,28 +23,36 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
21
23
  * helpers instead so that internal output can be silenced in tests by setting
22
24
  * the LOGIXIA_SILENT_INTERNAL=1 environment variable.
23
25
  */
24
- const silent = process.env.LOGIXIA_SILENT_INTERNAL === "1";
26
+ /**
27
+ * Read the silence flag on each call rather than caching it at import time, so
28
+ * setting LOGIXIA_SILENT_INTERNAL=1 AFTER the module is first imported (e.g. in
29
+ * a test setup file) still takes effect — the documented test-silencing
30
+ * behavior was previously unreliable because the value was frozen at load.
31
+ */
32
+ function isSilent() {
33
+ return process.env.LOGIXIA_SILENT_INTERNAL === "1";
34
+ }
25
35
  /**
26
36
  * Emit an internal debug/info message to stderr.
27
37
  * Use for field-enable/disable notifications that a developer might want to see
28
38
  * during development but should not appear in production log streams.
29
39
  */
30
40
  function internalLog(message) {
31
- if (!silent) process.stderr.write(`[logixia] ${message}\n`);
41
+ if (!isSilent()) process.stderr.write(`[logixia] ${message}\n`);
32
42
  }
33
43
  /**
34
44
  * Emit an internal warning to stderr.
35
45
  * Use when something is misconfigured but logixia can continue operating.
36
46
  */
37
47
  function internalWarn(message) {
38
- if (!silent) process.stderr.write(`[logixia:warn] ${message}\n`);
48
+ if (!isSilent()) process.stderr.write(`[logixia:warn] ${message}\n`);
39
49
  }
40
50
  /**
41
51
  * Emit an internal error to stderr.
42
52
  * Use when a transport write fails or a serious internal error occurs.
43
53
  */
44
54
  function internalError(message, error) {
45
- if (!silent) {
55
+ if (!isSilent()) {
46
56
  let errStr = "";
47
57
  if (error instanceof Error) errStr = ` — ${error.message}`;
48
58
  else if (error != null) errStr = ` — ${String(error)}`;
@@ -72,6 +82,7 @@ function internalError(message, error) {
72
82
  * `String(value)` for circular refs / values without a stringifier.
73
83
  */
74
84
  function safeToString(value) {
85
+ var _constructor;
75
86
  if (typeof value === "string") return value;
76
87
  if (value === void 0 || value === null) return "";
77
88
  if (value instanceof Error) return value.message;
@@ -79,11 +90,10 @@ function safeToString(value) {
79
90
  if (typeof value === "symbol") return value.toString();
80
91
  if (typeof value === "function") return `[Function: ${value.name || "anonymous"}]`;
81
92
  try {
82
- return JSON.stringify(value);
83
- } catch {
84
- var _constructor;
85
- return `[${((_constructor = value.constructor) === null || _constructor === void 0 ? void 0 : _constructor.name) ?? "object"}]`;
86
- }
93
+ const json = JSON.stringify(value);
94
+ if (json !== void 0) return json;
95
+ } catch {}
96
+ return `[${((_constructor = value.constructor) === null || _constructor === void 0 ? void 0 : _constructor.name) ?? "object"}]`;
87
97
  }
88
98
 
89
99
  //#endregion
@@ -216,7 +226,6 @@ var DatabaseTransport = class {
216
226
  this.config = config;
217
227
  this.name = "database";
218
228
  this.batch = [];
219
- this.isFlushing = false;
220
229
  this.isConnected = false;
221
230
  this.batchSize = config.batchSize || 100;
222
231
  this.flushInterval = config.flushInterval || 5e3;
@@ -233,33 +242,44 @@ var DatabaseTransport = class {
233
242
  this.batch.push(entry);
234
243
  }
235
244
  async flush() {
236
- if (this.batch.length === 0) return;
237
- if (this.isFlushing) return;
238
- this.isFlushing = true;
239
- const entriesToFlush = [...this.batch];
240
- this.batch = [];
241
- try {
242
- switch (this.config.type) {
243
- case "mongodb":
244
- await this.flushToMongoDB(entriesToFlush);
245
- break;
246
- case "postgresql":
247
- await this.flushToPostgreSQL(entriesToFlush);
248
- break;
249
- case "mysql":
250
- await this.flushToMySQL(entriesToFlush);
251
- break;
252
- case "sqlite":
253
- await this.flushToSQLite(entriesToFlush);
254
- break;
255
- default: throw new Error(`Unsupported database type: ${this.config.type}`);
245
+ if (!this.flushPromise) this.flushPromise = this.drain().finally(() => {
246
+ this.flushPromise = void 0;
247
+ });
248
+ await this.flushPromise;
249
+ }
250
+ /**
251
+ * Drains the batch to the database one snapshot at a time. The current batch
252
+ * is detached SYNCHRONOUSLY before awaiting the write, so entries appended by
253
+ * concurrent write() calls land in a fresh array and are never written twice.
254
+ * On write failure the snapshot is restored to the front of the batch for the
255
+ * next flush cycle and the loop stops, so a persistently failing DB does not
256
+ * hot-spin retrying the same entries.
257
+ */
258
+ async drain() {
259
+ while (this.batch.length > 0) {
260
+ const entriesToFlush = this.batch;
261
+ this.batch = [];
262
+ try {
263
+ switch (this.config.type) {
264
+ case "mongodb":
265
+ await this.flushToMongoDB(entriesToFlush);
266
+ break;
267
+ case "postgresql":
268
+ await this.flushToPostgreSQL(entriesToFlush);
269
+ break;
270
+ case "mysql":
271
+ await this.flushToMySQL(entriesToFlush);
272
+ break;
273
+ case "sqlite":
274
+ await this.flushToSQLite(entriesToFlush);
275
+ break;
276
+ default: throw new Error(`Unsupported database type: ${this.config.type}`);
277
+ }
278
+ } catch (error) {
279
+ internalError("Database flush error", error);
280
+ this.batch.unshift(...entriesToFlush);
281
+ throw error;
256
282
  }
257
- } catch (error) {
258
- internalError("Database flush error", error);
259
- this.batch.unshift(...entriesToFlush);
260
- throw error;
261
- } finally {
262
- this.isFlushing = false;
263
283
  }
264
284
  }
265
285
  async isReady() {
@@ -272,7 +292,10 @@ var DatabaseTransport = class {
272
292
  }
273
293
  async connect() {
274
294
  if (this.connectionPromise) return this.connectionPromise;
275
- this.connectionPromise = this.establishConnection();
295
+ this.connectionPromise = this.establishConnection().catch((error) => {
296
+ this.connectionPromise = void 0;
297
+ throw error;
298
+ });
276
299
  return this.connectionPromise;
277
300
  }
278
301
  async establishConnection() {
@@ -503,8 +526,17 @@ var DatabaseTransport = class {
503
526
  }, this.flushInterval);
504
527
  }
505
528
  async close() {
506
- if (this.flushTimer) clearInterval(this.flushTimer);
507
- await this.flush();
529
+ if (this.flushTimer) {
530
+ clearInterval(this.flushTimer);
531
+ this.flushTimer = void 0;
532
+ }
533
+ const MAX_CLOSE_FLUSH_ATTEMPTS = 3;
534
+ for (let attempt = 0; attempt < MAX_CLOSE_FLUSH_ATTEMPTS && this.batch.length > 0; attempt += 1) try {
535
+ await this.flush();
536
+ } catch (error) {
537
+ internalError("Database flush during close failed; entries remain buffered", error);
538
+ }
539
+ if (this.batch.length > 0) internalError(`Database transport closing with ${this.batch.length} unflushed log entr${this.batch.length === 1 ? "y" : "ies"} after ${MAX_CLOSE_FLUSH_ATTEMPTS} attempts`);
508
540
  if (this.connection) switch (this.config.type) {
509
541
  case "mongodb":
510
542
  await this.connection.close();
@@ -540,7 +572,8 @@ var DatabaseTransport = class {
540
572
  var _result$rows, _result$;
541
573
  const tableName = this.config.table || "logs";
542
574
  const result = await this.connection.query(`SELECT COUNT(*) as count FROM ${tableName}`);
543
- return ((_result$rows = result.rows) === null || _result$rows === void 0 || (_result$rows = _result$rows[0]) === null || _result$rows === void 0 ? void 0 : _result$rows.count) || ((_result$ = result[0]) === null || _result$ === void 0 ? void 0 : _result$.count) || 0;
575
+ const rawCount = ((_result$rows = result.rows) === null || _result$rows === void 0 || (_result$rows = _result$rows[0]) === null || _result$rows === void 0 ? void 0 : _result$rows.count) ?? ((_result$ = result[0]) === null || _result$ === void 0 ? void 0 : _result$.count) ?? 0;
576
+ return Number(rawCount) || 0;
544
577
  }
545
578
  default: return 0;
546
579
  }
@@ -583,23 +616,43 @@ var AnalyticsTransport = class {
583
616
  }, this.config.flushInterval);
584
617
  }
585
618
  async flush() {
586
- if (this.batch.length === 0) return;
587
- const entriesToSend = [...this.batch];
588
- this.batch = [];
589
619
  if (this.batchTimer) {
590
620
  clearTimeout(this.batchTimer);
591
- delete this.batchTimer;
621
+ this.batchTimer = void 0;
592
622
  }
593
- try {
594
- await this.sendBatch(entriesToSend);
595
- } catch (error) {
596
- internalError(`Analytics transport ${this.name} flush failed`, error);
597
- this.batch.unshift(...entriesToSend);
623
+ if (!this.flushPromise) this.flushPromise = this.drain().finally(() => {
624
+ this.flushPromise = void 0;
625
+ });
626
+ await this.flushPromise;
627
+ }
628
+ /**
629
+ * Drains the batch to the provider one snapshot at a time. The batch is
630
+ * detached SYNCHRONOUSLY before awaiting sendBatch(), so entries appended by
631
+ * concurrent writes land in a fresh array and are never sent twice. On failure
632
+ * the snapshot is restored to the front of the batch for the next flush and
633
+ * the loop stops, so a failing provider does not hot-spin.
634
+ */
635
+ async drain() {
636
+ while (this.batch.length > 0) {
637
+ const entriesToSend = this.batch;
638
+ this.batch = [];
639
+ try {
640
+ await this.sendBatch(entriesToSend);
641
+ } catch (error) {
642
+ internalError(`Analytics transport ${this.name} flush failed`, error);
643
+ this.batch.unshift(...entriesToSend);
644
+ return;
645
+ }
598
646
  }
599
647
  }
600
648
  async close() {
601
- await this.flush();
602
- if (this.batchTimer) clearTimeout(this.batchTimer);
649
+ if (this.batchTimer) {
650
+ clearTimeout(this.batchTimer);
651
+ this.batchTimer = void 0;
652
+ }
653
+ const MAX_CLOSE_FLUSH_ATTEMPTS = 3;
654
+ for (let attempt = 0; attempt < MAX_CLOSE_FLUSH_ATTEMPTS && this.batch.length > 0; attempt += 1) await this.flush();
655
+ if (this.batch.length > 0) internalError(`Analytics transport ${this.name} closing with ${this.batch.length} unflushed entr${this.batch.length === 1 ? "y" : "ies"} after ${MAX_CLOSE_FLUSH_ATTEMPTS} attempts`);
603
656
  await this.cleanup();
604
657
  }
605
658
  shouldSkipEntry(entry) {
@@ -1025,15 +1078,34 @@ var FileTransport = class {
1025
1078
  await mkdir(dir, { recursive: true });
1026
1079
  } catch {}
1027
1080
  }
1028
- async compressFile(_filePath) {}
1081
+ /**
1082
+ * Gzip the rotated file to `<file>.gz` and remove the original. Streams the
1083
+ * data so large log files don't have to be buffered in memory. Best-effort:
1084
+ * any failure is logged and leaves the original file intact rather than
1085
+ * throwing out of the rotation path.
1086
+ */
1087
+ async compressFile(filePath) {
1088
+ const gzPath = `${filePath}.gz`;
1089
+ try {
1090
+ await pipeline(fs.createReadStream(filePath), createGzip(), fs.createWriteStream(gzPath));
1091
+ await unlink(filePath);
1092
+ } catch (error) {
1093
+ internalError("Failed to compress rotated log file", error);
1094
+ try {
1095
+ await unlink(gzPath);
1096
+ } catch {}
1097
+ }
1098
+ }
1029
1099
  async cleanupOldFiles() {
1030
1100
  var _this$config$rotation4;
1031
1101
  if (!((_this$config$rotation4 = this.config.rotation) === null || _this$config$rotation4 === void 0 ? void 0 : _this$config$rotation4.maxFiles)) return;
1032
1102
  try {
1033
1103
  const dir = this.resolveDir();
1034
1104
  const files = await readdir(dir);
1035
- const base = path.basename(this.config.filename, path.extname(this.config.filename));
1036
- const logFiles = files.filter((file) => file.startsWith(base)).map(async (file) => {
1105
+ const ext = path.extname(this.config.filename);
1106
+ const base = path.basename(this.config.filename, ext);
1107
+ const isOwnRotatedFile = (file) => file.startsWith(`${base}-`) && (file.endsWith(ext) || file.endsWith(`${ext}.gz`));
1108
+ const logFiles = files.filter(isOwnRotatedFile).map(async (file) => {
1037
1109
  const filePath = path.join(dir, file);
1038
1110
  return {
1039
1111
  path: filePath,
@@ -1834,7 +1906,7 @@ var TransportManager = class extends EventEmitter {
1834
1906
  const writeTime = Date.now() - startTime;
1835
1907
  metrics.logsWritten++;
1836
1908
  metrics.lastWrite = new Date(startTime);
1837
- metrics.averageWriteTime = (metrics.averageWriteTime + writeTime) / 2;
1909
+ metrics.averageWriteTime += (writeTime - metrics.averageWriteTime) / metrics.logsWritten;
1838
1910
  this.emit("log", entry);
1839
1911
  } catch (error) {
1840
1912
  metrics.errors++;
@@ -2062,4 +2134,4 @@ var TransportManager = class extends EventEmitter {
2062
2134
 
2063
2135
  //#endregion
2064
2136
  export { FileTransport as a, DatabaseTransport as c, internalError as d, internalLog as f, GoogleAnalyticsTransport as i, ConsoleTransport as l, __require as m, SegmentTransport as n, DataDogTransport as o, internalWarn as p, MixpanelTransport as r, AnalyticsTransport as s, TransportManager as t, safeToString as u };
2065
- //# sourceMappingURL=transport.manager-CaL4XuLD.mjs.map
2137
+ //# sourceMappingURL=transport.manager-Cij_sA-b.mjs.map