logixia 2.0.10 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/README.md +2 -1
  2. package/dist/{async-context-Dv0bmPC-.js → async-context-Dgl3ZLyb.js} +4 -4
  3. package/dist/{async-context-Dv0bmPC-.js.map → async-context-Dgl3ZLyb.js.map} +1 -1
  4. package/dist/{async-context-COuvmwsS.mjs → async-context-fBqC5lNc.mjs} +3 -3
  5. package/dist/{async-context-COuvmwsS.mjs.map → async-context-fBqC5lNc.mjs.map} +1 -1
  6. package/dist/{azure-monitor.transport-CbSHOnNX.d.mts → azure-monitor.transport-C5VjvREZ.d.mts} +2 -2
  7. package/dist/{azure-monitor.transport-CbSHOnNX.d.mts.map → azure-monitor.transport-C5VjvREZ.d.mts.map} +1 -1
  8. package/dist/{azure-monitor.transport-Bvf7Xegt.d.ts → azure-monitor.transport-CyHDjHS-.d.ts} +2 -2
  9. package/dist/{azure-monitor.transport-Bvf7Xegt.d.ts.map → azure-monitor.transport-CyHDjHS-.d.ts.map} +1 -1
  10. package/dist/{azure-monitor.transport-Coz5Gesu.mjs → azure-monitor.transport-DGiacfvT.mjs} +19 -8
  11. package/dist/{azure-monitor.transport-Coz5Gesu.mjs.map → azure-monitor.transport-DGiacfvT.mjs.map} +1 -1
  12. package/dist/{azure-monitor.transport-D6ryi8rA.js → azure-monitor.transport-DX3fuK2t.js} +19 -8
  13. package/dist/{azure-monitor.transport-D6ryi8rA.js.map → azure-monitor.transport-DX3fuK2t.js.map} +1 -1
  14. package/dist/azure.d.mts +1 -1
  15. package/dist/azure.d.ts +1 -1
  16. package/dist/azure.js +1 -1
  17. package/dist/azure.mjs +1 -1
  18. package/dist/{batch-buffer-CFzKnuNZ.js → batch-buffer-BLmVvJAZ.js} +11 -7
  19. package/dist/{batch-buffer-CFzKnuNZ.js.map → batch-buffer-BLmVvJAZ.js.map} +1 -1
  20. package/dist/{batch-buffer-CQUWDMYq.mjs → batch-buffer-CM-lK_Qy.mjs} +11 -7
  21. package/dist/{batch-buffer-CQUWDMYq.mjs.map → batch-buffer-CM-lK_Qy.mjs.map} +1 -1
  22. package/dist/browser.d.mts +2 -0
  23. package/dist/browser.d.mts.map +1 -1
  24. package/dist/browser.d.ts +2 -0
  25. package/dist/browser.d.ts.map +1 -1
  26. package/dist/browser.js +42 -13
  27. package/dist/browser.js.map +1 -1
  28. package/dist/browser.mjs +42 -13
  29. package/dist/browser.mjs.map +1 -1
  30. package/dist/{builder-Bj3mDeBw.mjs → builder-CA_dokb4.mjs} +246 -101
  31. package/dist/builder-CA_dokb4.mjs.map +1 -0
  32. package/dist/{builder-BLIStsCK.js → builder-jMlVYaQa.js} +256 -111
  33. package/dist/builder-jMlVYaQa.js.map +1 -0
  34. package/dist/cli/index.js +21 -16
  35. package/dist/cloudwatch.d.mts +1 -1
  36. package/dist/cloudwatch.d.ts +1 -1
  37. package/dist/cloudwatch.js +1 -1
  38. package/dist/cloudwatch.mjs +1 -1
  39. package/dist/{cloudwatch.transport-Cr1aeTGj.mjs → cloudwatch.transport-Cr0fEpko.mjs} +24 -9
  40. package/dist/{cloudwatch.transport-Cr1aeTGj.mjs.map → cloudwatch.transport-Cr0fEpko.mjs.map} +1 -1
  41. package/dist/{cloudwatch.transport-CQyT6U91.d.ts → cloudwatch.transport-DdSjUWvu.d.ts} +2 -2
  42. package/dist/{cloudwatch.transport-CQyT6U91.d.ts.map → cloudwatch.transport-DdSjUWvu.d.ts.map} +1 -1
  43. package/dist/{cloudwatch.transport-D4G5xYfG.js → cloudwatch.transport-Lst1Bbmr.js} +24 -9
  44. package/dist/{cloudwatch.transport-D4G5xYfG.js.map → cloudwatch.transport-Lst1Bbmr.js.map} +1 -1
  45. package/dist/{cloudwatch.transport-CjVrI6S4.d.mts → cloudwatch.transport-_hS7Mcc5.d.mts} +2 -2
  46. package/dist/{cloudwatch.transport-CjVrI6S4.d.mts.map → cloudwatch.transport-_hS7Mcc5.d.mts.map} +1 -1
  47. package/dist/correlation.js +3 -3
  48. package/dist/correlation.mjs +3 -3
  49. package/dist/{coerce.utils-C1C5sIX0.js → csv.utils-CbVHY1pd.js} +24 -1
  50. package/dist/csv.utils-CbVHY1pd.js.map +1 -0
  51. package/dist/{coerce.utils-B_rEudCn.mjs → csv.utils-ZdxSod5g.mjs} +19 -2
  52. package/dist/csv.utils-ZdxSod5g.mjs.map +1 -0
  53. package/dist/{entry-fields-CVwoQplO.js → entry-fields-BSLkzw18.js} +3 -3
  54. package/dist/{entry-fields-CVwoQplO.js.map → entry-fields-BSLkzw18.js.map} +1 -1
  55. package/dist/{entry-fields-DNTKQxoU.mjs → entry-fields-J1OBi24o.mjs} +2 -2
  56. package/dist/{entry-fields-DNTKQxoU.mjs.map → entry-fields-J1OBi24o.mjs.map} +1 -1
  57. package/dist/gcp.d.mts +1 -1
  58. package/dist/gcp.d.ts +1 -1
  59. package/dist/gcp.js +1 -1
  60. package/dist/gcp.mjs +1 -1
  61. package/dist/{gcp.transport-CVuRpchl.js → gcp.transport-5-ZIjN8r.js} +18 -9
  62. package/dist/{gcp.transport-CVuRpchl.js.map → gcp.transport-5-ZIjN8r.js.map} +1 -1
  63. package/dist/{gcp.transport-D9SdLPwP.d.mts → gcp.transport-C8VJiEM7.d.mts} +2 -2
  64. package/dist/{gcp.transport-D9SdLPwP.d.mts.map → gcp.transport-C8VJiEM7.d.mts.map} +1 -1
  65. package/dist/{gcp.transport-BCIvdFV-.d.ts → gcp.transport-Ct0BtKFo.d.ts} +2 -2
  66. package/dist/{gcp.transport-BCIvdFV-.d.ts.map → gcp.transport-Ct0BtKFo.d.ts.map} +1 -1
  67. package/dist/{gcp.transport-LLK2bw2-.mjs → gcp.transport-Cw3Qke85.mjs} +18 -9
  68. package/dist/{gcp.transport-LLK2bw2-.mjs.map → gcp.transport-Cw3Qke85.mjs.map} +1 -1
  69. package/dist/{index-BAkPHp5D.d.mts → index-B3ajtXJd.d.mts} +2 -2
  70. package/dist/{index-BAkPHp5D.d.mts.map → index-B3ajtXJd.d.mts.map} +1 -1
  71. package/dist/{index-e7LowmAu.d.ts → index-oqD3WNxf.d.ts} +2 -2
  72. package/dist/{index-e7LowmAu.d.ts.map → index-oqD3WNxf.d.ts.map} +1 -1
  73. package/dist/index.d.mts +8 -7
  74. package/dist/index.d.mts.map +1 -1
  75. package/dist/index.d.ts +8 -7
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.js +44 -26
  78. package/dist/index.js.map +1 -1
  79. package/dist/index.mjs +34 -17
  80. package/dist/index.mjs.map +1 -1
  81. package/dist/{logitron-logger-DVUdgmBY.d.ts → logitron-logger-Csn3e6Qu.d.ts} +2 -2
  82. package/dist/{logitron-logger-DVUdgmBY.d.ts.map → logitron-logger-Csn3e6Qu.d.ts.map} +1 -1
  83. package/dist/{logitron-logger-DoWmMjYB.d.mts → logitron-logger-fMOkXr8H.d.mts} +2 -2
  84. package/dist/{logitron-logger-DoWmMjYB.d.mts.map → logitron-logger-fMOkXr8H.d.mts.map} +1 -1
  85. package/dist/middleware.d.mts +1 -1
  86. package/dist/middleware.d.mts.map +1 -1
  87. package/dist/middleware.d.ts +1 -1
  88. package/dist/middleware.d.ts.map +1 -1
  89. package/dist/middleware.js +12 -7
  90. package/dist/middleware.js.map +1 -1
  91. package/dist/middleware.mjs +11 -6
  92. package/dist/middleware.mjs.map +1 -1
  93. package/dist/nest.d.mts +51 -18
  94. package/dist/nest.d.mts.map +1 -1
  95. package/dist/nest.d.ts +51 -18
  96. package/dist/nest.d.ts.map +1 -1
  97. package/dist/nest.js +207 -65
  98. package/dist/nest.js.map +1 -1
  99. package/dist/nest.mjs +202 -60
  100. package/dist/nest.mjs.map +1 -1
  101. package/dist/{otel-ChnVYRLA.js → otel-8ypFGG19.js} +452 -120
  102. package/dist/otel-8ypFGG19.js.map +1 -0
  103. package/dist/{otel-DOuwGPCd.mjs → otel-b-s2QbY4.mjs} +450 -118
  104. package/dist/otel-b-s2QbY4.mjs.map +1 -0
  105. package/dist/own-property-BWkibepN.js +40 -0
  106. package/dist/own-property-BWkibepN.js.map +1 -0
  107. package/dist/own-property-CjFw9qwZ.mjs +29 -0
  108. package/dist/own-property-CjFw9qwZ.mjs.map +1 -0
  109. package/dist/{request-trace-ByujsUZk.js → request-trace-C7kXMjx5.js} +5 -6
  110. package/dist/{request-trace-ByujsUZk.js.map → request-trace-C7kXMjx5.js.map} +1 -1
  111. package/dist/{request-trace-BgNykH9M.mjs → request-trace-Dekcnenb.mjs} +5 -6
  112. package/dist/{request-trace-BgNykH9M.mjs.map → request-trace-Dekcnenb.mjs.map} +1 -1
  113. package/dist/{safe-stringify--7gbe95n.mjs → safe-stringify-B5p5ktFe.mjs} +2 -2
  114. package/dist/{safe-stringify--7gbe95n.mjs.map → safe-stringify-B5p5ktFe.mjs.map} +1 -1
  115. package/dist/{safe-stringify-CN7icp8S.js → safe-stringify-J3sh-7YM.js} +5 -5
  116. package/dist/{safe-stringify-CN7icp8S.js.map → safe-stringify-J3sh-7YM.js.map} +1 -1
  117. package/dist/{search-DPwTKPve.d.ts → search-B-hJe2N-.d.mts} +9 -8
  118. package/dist/{search-D-8HJADE.d.mts.map → search-B-hJe2N-.d.mts.map} +1 -1
  119. package/dist/{search-D-8HJADE.d.mts → search-BlKBLm5D.d.ts} +9 -8
  120. package/dist/{search-DPwTKPve.d.ts.map → search-BlKBLm5D.d.ts.map} +1 -1
  121. package/dist/{search-DAXKE-P9.mjs → search-CTR4XNHq.mjs} +91 -44
  122. package/dist/search-CTR4XNHq.mjs.map +1 -0
  123. package/dist/{search-CRjhHxe9.js → search-_qx4YzRP.js} +94 -47
  124. package/dist/search-_qx4YzRP.js.map +1 -0
  125. package/dist/search.d.mts +1 -1
  126. package/dist/search.d.ts +1 -1
  127. package/dist/search.js +1 -1
  128. package/dist/search.mjs +1 -1
  129. package/dist/{secret-config-BvFl2dC8.js → secret-config--cVTOWwp.js} +6 -5
  130. package/dist/{secret-config-BvFl2dC8.js.map → secret-config--cVTOWwp.js.map} +1 -1
  131. package/dist/{secret-config-ByGq7kJd.mjs → secret-config-GKmb15Me.mjs} +3 -2
  132. package/dist/{secret-config-ByGq7kJd.mjs.map → secret-config-GKmb15Me.mjs.map} +1 -1
  133. package/dist/{sensitive-names-o__1PUUR.mjs → sensitive-names-BiBM6R1X.mjs} +2 -28
  134. package/dist/sensitive-names-BiBM6R1X.mjs.map +1 -0
  135. package/dist/{sensitive-names-CHMowujD.js → sensitive-names-NiKIRhp5.js} +5 -37
  136. package/dist/sensitive-names-NiKIRhp5.js.map +1 -0
  137. package/dist/{slack.transport-BDyENuR0.d.mts → slack.transport-5OPL7H3u.d.mts} +76 -3
  138. package/dist/slack.transport-5OPL7H3u.d.mts.map +1 -0
  139. package/dist/{slack.transport-C6E0l4kw.d.ts → slack.transport-BFpBD_Ri.d.ts} +76 -3
  140. package/dist/slack.transport-BFpBD_Ri.d.ts.map +1 -0
  141. package/dist/{slack.transport-zLQ9MGxS.js → slack.transport-Cpx5fKga.js} +272 -21
  142. package/dist/slack.transport-Cpx5fKga.js.map +1 -0
  143. package/dist/{slack.transport-CSj1j0_X.mjs → slack.transport-Qwj9WkrJ.mjs} +267 -22
  144. package/dist/slack.transport-Qwj9WkrJ.mjs.map +1 -0
  145. package/dist/testing.d.mts +1 -1
  146. package/dist/testing.d.ts +1 -1
  147. package/dist/{transport.types-CzJq4IrF.d.ts → transport.types-B1ejJJP6.d.mts} +6 -1
  148. package/dist/transport.types-B1ejJJP6.d.mts.map +1 -0
  149. package/dist/{transport.types-CzJq4IrF.d.mts → transport.types-B1ejJJP6.d.ts} +6 -1
  150. package/dist/transport.types-B1ejJJP6.d.ts.map +1 -0
  151. package/dist/transports.d.mts +61 -11
  152. package/dist/transports.d.mts.map +1 -1
  153. package/dist/transports.d.ts +61 -11
  154. package/dist/transports.d.ts.map +1 -1
  155. package/dist/transports.js +7 -6
  156. package/dist/transports.mjs +7 -7
  157. package/dist/worker-entry.js +200 -52
  158. package/dist/worker.d.mts +1 -1
  159. package/dist/worker.d.ts +1 -1
  160. package/dist/worker.js +1 -1
  161. package/dist/worker.mjs +1 -1
  162. package/dist/{worker.transport-B9mCQSM1.d.ts → worker.transport-B7olL1Uh.d.ts} +12 -2
  163. package/dist/worker.transport-B7olL1Uh.d.ts.map +1 -0
  164. package/dist/{worker.transport-D78xhNhB.mjs → worker.transport-BHnWDXPH.mjs} +59 -13
  165. package/dist/worker.transport-BHnWDXPH.mjs.map +1 -0
  166. package/dist/{worker.transport-DQDv1nAJ.js → worker.transport-C7jNGhev.js} +59 -13
  167. package/dist/worker.transport-C7jNGhev.js.map +1 -0
  168. package/dist/{worker.transport-Bl8lFRck.d.mts → worker.transport-DnU0_9Bd.d.mts} +12 -2
  169. package/dist/worker.transport-DnU0_9Bd.d.mts.map +1 -0
  170. package/package.json +10 -3
  171. package/dist/builder-BLIStsCK.js.map +0 -1
  172. package/dist/builder-Bj3mDeBw.mjs.map +0 -1
  173. package/dist/coerce.utils-B_rEudCn.mjs.map +0 -1
  174. package/dist/coerce.utils-C1C5sIX0.js.map +0 -1
  175. package/dist/otel-ChnVYRLA.js.map +0 -1
  176. package/dist/otel-DOuwGPCd.mjs.map +0 -1
  177. package/dist/search-CRjhHxe9.js.map +0 -1
  178. package/dist/search-DAXKE-P9.mjs.map +0 -1
  179. package/dist/sensitive-names-CHMowujD.js.map +0 -1
  180. package/dist/sensitive-names-o__1PUUR.mjs.map +0 -1
  181. package/dist/slack.transport-BDyENuR0.d.mts.map +0 -1
  182. package/dist/slack.transport-C6E0l4kw.d.ts.map +0 -1
  183. package/dist/slack.transport-CSj1j0_X.mjs.map +0 -1
  184. package/dist/slack.transport-zLQ9MGxS.js.map +0 -1
  185. package/dist/transport.types-CzJq4IrF.d.mts.map +0 -1
  186. package/dist/transport.types-CzJq4IrF.d.ts.map +0 -1
  187. package/dist/worker.transport-B9mCQSM1.d.ts.map +0 -1
  188. package/dist/worker.transport-Bl8lFRck.d.mts.map +0 -1
  189. package/dist/worker.transport-D78xhNhB.mjs.map +0 -1
  190. package/dist/worker.transport-DQDv1nAJ.js.map +0 -1
@@ -1,8 +1,9 @@
1
- const require_secret_config = require("./secret-config-BvFl2dC8.js");
1
+ const require_secret_config = require("./secret-config--cVTOWwp.js");
2
2
  const require_internal_log = require("./internal-log-DwqTjhpb.js");
3
- const require_coerce_utils = require("./coerce.utils-C1C5sIX0.js");
4
- const require_safe_stringify = require("./safe-stringify-CN7icp8S.js");
5
- const require_entry_fields = require("./entry-fields-CVwoQplO.js");
3
+ const require_safe_stringify = require("./safe-stringify-J3sh-7YM.js");
4
+ const require_csv_utils = require("./csv.utils-CbVHY1pd.js");
5
+ const require_entry_fields = require("./entry-fields-BSLkzw18.js");
6
+ const require_batch_buffer = require("./batch-buffer-BLmVvJAZ.js");
6
7
  let node_events = require("node:events");
7
8
  let node_util = require("node:util");
8
9
  let node_zlib = require("node:zlib");
@@ -37,12 +38,35 @@ function stripControlChars(value) {
37
38
  * transports: { console: { format: 'json' } }
38
39
  */
39
40
  var ConsoleTransport = class ConsoleTransport {
41
+ config;
42
+ name = "console";
43
+ level;
44
+ filter;
45
+ static COLORS = /* @__PURE__ */ new Map([
46
+ ["black", "\x1B[30m"],
47
+ ["red", "\x1B[31m"],
48
+ ["green", "\x1B[32m"],
49
+ ["yellow", "\x1B[33m"],
50
+ ["blue", "\x1B[34m"],
51
+ ["magenta", "\x1B[35m"],
52
+ ["cyan", "\x1B[36m"],
53
+ ["white", "\x1B[37m"],
54
+ ["gray", "\x1B[90m"],
55
+ ["grey", "\x1B[90m"],
56
+ ["brightred", "\x1B[91m"],
57
+ ["brightgreen", "\x1B[92m"],
58
+ ["brightyellow", "\x1B[93m"],
59
+ ["brightblue", "\x1B[94m"],
60
+ ["brightmagenta", "\x1B[95m"],
61
+ ["brightcyan", "\x1B[96m"],
62
+ ["brightwhite", "\x1B[97m"],
63
+ ["reset", "\x1B[0m"]
64
+ ]);
40
65
  static sanitize(value) {
41
- return stripControlChars(require_coerce_utils.safeToString(value));
66
+ return stripControlChars(require_csv_utils.safeToString(value));
42
67
  }
43
68
  constructor(config = {}) {
44
69
  this.config = config;
45
- this.name = "console";
46
70
  this.level = config.level;
47
71
  if (config.filter) this.filter = config.filter;
48
72
  }
@@ -106,26 +130,6 @@ var ConsoleTransport = class ConsoleTransport {
106
130
  close() {
107
131
  return Promise.resolve();
108
132
  }
109
- static #_ = () => this.COLORS = /* @__PURE__ */ new Map([
110
- ["black", "\x1B[30m"],
111
- ["red", "\x1B[31m"],
112
- ["green", "\x1B[32m"],
113
- ["yellow", "\x1B[33m"],
114
- ["blue", "\x1B[34m"],
115
- ["magenta", "\x1B[35m"],
116
- ["cyan", "\x1B[36m"],
117
- ["white", "\x1B[37m"],
118
- ["gray", "\x1B[90m"],
119
- ["grey", "\x1B[90m"],
120
- ["brightred", "\x1B[91m"],
121
- ["brightgreen", "\x1B[92m"],
122
- ["brightyellow", "\x1B[93m"],
123
- ["brightblue", "\x1B[94m"],
124
- ["brightmagenta", "\x1B[95m"],
125
- ["brightcyan", "\x1B[96m"],
126
- ["brightwhite", "\x1B[97m"],
127
- ["reset", "\x1B[0m"]
128
- ]);
129
133
  };
130
134
  // src/transports/buffer-cap.ts
131
135
  const DEFAULT_BUFFER_CAP = 1e4;
@@ -138,10 +142,12 @@ const DROP_WARN_INTERVAL_MS$1 = 5e3;
138
142
  * A cap below `batchSize` is raised to `batchSize` so one batch always fits.
139
143
  */
140
144
  var BufferCap = class {
145
+ name;
146
+ max;
147
+ dropped = 0;
148
+ lastWarning = 0;
141
149
  constructor(name, requested, batchSize, fallback = DEFAULT_BUFFER_CAP) {
142
150
  this.name = name;
143
- this.dropped = 0;
144
- this.lastWarning = 0;
145
151
  let max = fallback;
146
152
  if (requested !== void 0) {
147
153
  if (Number.isInteger(requested) && requested >= 1) max = requested;
@@ -245,7 +251,9 @@ function isDataError(type, error) {
245
251
  return false;
246
252
  }
247
253
  /** A write that stored part of a batch: only `remaining` still needs to be written. */
254
+ var DatabaseUnavailableError = class extends Error {};
248
255
  var PartialWriteError = class extends Error {
256
+ remaining;
249
257
  constructor(message, remaining, cause) {
250
258
  super(message, { cause });
251
259
  this.remaining = remaining;
@@ -272,11 +280,30 @@ function validateSqlIdentifier(name, label = "table") {
272
280
  * }
273
281
  */
274
282
  var DatabaseTransport = class {
283
+ config;
284
+ name = "database";
285
+ batchSize;
286
+ flushInterval;
287
+ level;
288
+ filter;
289
+ batch = [];
290
+ flushTimer;
291
+ /**
292
+ * The in-flight drain promise, or undefined when idle. Concurrent flush()
293
+ * calls (the un-awaited threshold flush write() fires, plus the interval
294
+ * timer) all await this single promise instead of starting their own drain,
295
+ * so a batch is never snapshotted and written twice. Mirrors the FileTransport
296
+ * fix for the overlapping-batch-flush duplication bug.
297
+ */
298
+ flushPromise;
299
+ connection;
300
+ isConnected = false;
301
+ connectionPromise;
302
+ connectFailures = 0;
303
+ nextConnectAt = 0;
304
+ cap;
275
305
  constructor(config) {
276
306
  this.config = config;
277
- this.name = "database";
278
- this.batch = [];
279
- this.isConnected = false;
280
307
  require_secret_config.protectSecrets(this, ["config", "connection"]);
281
308
  this.batchSize = config.batchSize || 100;
282
309
  this.flushInterval = config.flushInterval || 5e3;
@@ -295,9 +322,12 @@ var DatabaseTransport = class {
295
322
  ];
296
323
  }
297
324
  async write(entry) {
298
- if (!this.isConnected) await this.connect();
299
325
  this.addToBatch(entry);
300
- if (this.batch.length >= this.batchSize) await this.flush();
326
+ if (this.batch.length >= this.batchSize) try {
327
+ await this.flush();
328
+ } catch (error) {
329
+ if (!(error instanceof DatabaseUnavailableError)) throw error;
330
+ }
301
331
  }
302
332
  addToBatch(entry) {
303
333
  this.batch.push(entry);
@@ -322,6 +352,7 @@ var DatabaseTransport = class {
322
352
  */
323
353
  async drain() {
324
354
  while (this.batch.length > 0) {
355
+ await this.ensureConnected();
325
356
  const entries = this.batch.splice(0, this.batchSize);
326
357
  try {
327
358
  await this.writeEntries(entries);
@@ -419,6 +450,38 @@ var DatabaseTransport = class {
419
450
  return false;
420
451
  }
421
452
  }
453
+ async ensureConnected() {
454
+ if (this.isConnected) return;
455
+ const wait = this.nextConnectAt - Date.now();
456
+ if (wait > 0) throw new DatabaseUnavailableError(`Database unavailable; next connect attempt in ${wait}ms`);
457
+ try {
458
+ await this.connect();
459
+ this.connectFailures = 0;
460
+ } catch (error) {
461
+ this.connectFailures += 1;
462
+ this.nextConnectAt = Date.now() + Math.min(3e4, 1e3 * 2 ** (this.connectFailures - 1));
463
+ require_internal_log.internalError("Database connection error", error);
464
+ throw new DatabaseUnavailableError("Database unavailable", { cause: error });
465
+ }
466
+ }
467
+ /**
468
+ * The driver reported the connection gone (idle timeout, server restart,
469
+ * network drop). Forget it so the next flush reconnects; without an 'error'
470
+ * listener the driver's emitter would crash the process.
471
+ */
472
+ onConnectionLost(connection, error) {
473
+ if (this.connection !== connection) return;
474
+ if (error) require_internal_log.internalError("Database connection lost", error);
475
+ this.isConnected = false;
476
+ this.connectionPromise = void 0;
477
+ this.connection = void 0;
478
+ }
479
+ watchConnection(connection) {
480
+ var _connection$on, _connection$on2;
481
+ const lost = (error) => this.onConnectionLost(connection, error);
482
+ (_connection$on = connection.on) === null || _connection$on === void 0 || _connection$on.call(connection, "error", lost);
483
+ (_connection$on2 = connection.on) === null || _connection$on2 === void 0 || _connection$on2.call(connection, "end", () => lost());
484
+ }
422
485
  async connect() {
423
486
  if (this.connectionPromise) return this.connectionPromise;
424
487
  this.connectionPromise = this.establishConnection().catch((error) => {
@@ -455,6 +518,7 @@ var DatabaseTransport = class {
455
518
  });
456
519
  const connectionString = this.config.connectionString || `mongodb://${this.config.host}:${this.config.port}/${this.config.database}`;
457
520
  this.connection = new MongoClient(connectionString);
521
+ this.watchConnection(this.connection);
458
522
  await this.connection.connect();
459
523
  await this.connection.db(this.config.database).admin().ping();
460
524
  } catch (error) {
@@ -476,6 +540,7 @@ var DatabaseTransport = class {
476
540
  if (this.config.password) config.password = this.config.password;
477
541
  if (this.config.ssl) config.ssl = typeof this.config.ssl === "boolean" ? {} : this.config.ssl;
478
542
  this.connection = new Client(config);
543
+ this.watchConnection(this.connection);
479
544
  await this.connection.connect();
480
545
  await this.createPostgreSQLTable();
481
546
  } catch (error) {
@@ -496,6 +561,7 @@ var DatabaseTransport = class {
496
561
  if (this.config.password) config.password = this.config.password;
497
562
  if (this.config.ssl) config.ssl = typeof this.config.ssl === "boolean" ? {} : this.config.ssl;
498
563
  this.connection = await mysql.createConnection(config);
564
+ this.watchConnection(this.connection);
499
565
  await this.createMySQLTable();
500
566
  } catch (error) {
501
567
  throw new Error(`MySQL connection failed: ${error}`, { cause: error });
@@ -718,6 +784,7 @@ var DatabaseTransport = class {
718
784
  this.flushTimer = void 0;
719
785
  }
720
786
  const MAX_CLOSE_FLUSH_ATTEMPTS = 3;
787
+ this.nextConnectAt = 0;
721
788
  if (this.flushPromise) await this.flushPromise.catch(() => {});
722
789
  for (let attempt = 0; attempt < MAX_CLOSE_FLUSH_ATTEMPTS && this.batch.length > 0; attempt += 1) try {
723
790
  await this.flush();
@@ -726,17 +793,19 @@ var DatabaseTransport = class {
726
793
  }
727
794
  if (this.batch.length > 0) require_internal_log.internalError(`Database transport closing with ${this.batch.length} unflushed log entr${this.batch.length === 1 ? "y" : "ies"} after ${MAX_CLOSE_FLUSH_ATTEMPTS} attempts`);
728
795
  this.cap.report(0);
729
- if (this.connection) switch (this.config.type) {
796
+ const connection = this.connection;
797
+ this.connection = void 0;
798
+ if (connection) switch (this.config.type) {
730
799
  case "mongodb":
731
- await this.connection.close();
800
+ await connection.close();
732
801
  break;
733
802
  case "postgresql":
734
- await this.connection.end();
803
+ await connection.end();
735
804
  break;
736
805
  case "mysql":
737
- await this.connection.end();
806
+ await connection.end();
738
807
  break;
739
- case "sqlite": await this.connection.close();
808
+ case "sqlite": await connection.close();
740
809
  }
741
810
  this.isConnected = false;
742
811
  }
@@ -783,6 +852,7 @@ var DatabaseTransport = class {
783
852
  * requeues only `unsent`, so delivered entries are not sent twice.
784
853
  */
785
854
  var PartialBatchError = class extends Error {
855
+ unsent;
786
856
  constructor(message, unsent, options) {
787
857
  super(message, options);
788
858
  this.unsent = unsent;
@@ -790,9 +860,30 @@ var PartialBatchError = class extends Error {
790
860
  }
791
861
  };
792
862
  var AnalyticsTransport = class {
863
+ name;
864
+ level;
865
+ batchSize;
866
+ flushInterval;
867
+ config;
868
+ batch = [];
869
+ batchTimer;
870
+ isReady = false;
871
+ /**
872
+ * The in-flight drain promise, or undefined when idle. addToBatch() fires
873
+ * flush() un-awaited on every Nth entry, so a synchronous burst can trigger
874
+ * many overlapping flushes. Every caller joins this single promise instead of
875
+ * starting its own drain, so a batch is never snapshotted and sent twice —
876
+ * the overlapping-flush duplication that turns N logs into N² delivered events
877
+ * (same class of bug fixed earlier in FileTransport / DatabaseTransport).
878
+ */
879
+ flushPromise;
880
+ /** Set on the first write. Subclass fields don't exist yet while the base constructor runs. */
881
+ initPromise;
882
+ cap;
883
+ /** Consecutive failed drains; drives the backoff that stops a failing provider being re-sent every Nth log. */
884
+ failures = 0;
885
+ retryAt = 0;
793
886
  constructor(name, config) {
794
- this.batch = [];
795
- this.isReady = false;
796
887
  this.name = name;
797
888
  this.config = {
798
889
  batchSize: 50,
@@ -824,12 +915,13 @@ var AnalyticsTransport = class {
824
915
  addToBatch(entry) {
825
916
  this.batch.push(entry);
826
917
  this.cap.enforce(this.batch);
827
- if (this.batch.length >= (this.config.batchSize || 50)) this.flush().catch((err) => require_internal_log.internalError(`${this.name} batch flush failed`, err));
828
- else if (!this.batchTimer && this.config.flushInterval) {
918
+ const backoff = this.retryAt - Date.now();
919
+ if (this.batch.length >= (this.config.batchSize || 50) && backoff <= 0) this.flush().catch((err) => require_internal_log.internalError(`${this.name} batch flush failed`, err));
920
+ else if (!this.batchTimer && (this.config.flushInterval || backoff > 0)) {
829
921
  var _this$batchTimer$unre, _this$batchTimer;
830
922
  this.batchTimer = setTimeout(() => {
831
923
  this.flush().catch((err) => require_internal_log.internalError(`${this.name} interval flush failed`, err));
832
- }, this.config.flushInterval);
924
+ }, Math.max(this.config.flushInterval || 0, backoff));
833
925
  (_this$batchTimer$unre = (_this$batchTimer = this.batchTimer).unref) === null || _this$batchTimer$unre === void 0 || _this$batchTimer$unre.call(_this$batchTimer);
834
926
  }
835
927
  }
@@ -856,7 +948,11 @@ var AnalyticsTransport = class {
856
948
  this.batch = [];
857
949
  try {
858
950
  await this.sendBatch(entriesToSend);
951
+ this.failures = 0;
952
+ this.retryAt = 0;
859
953
  } catch (error) {
954
+ this.failures += 1;
955
+ this.retryAt = Date.now() + Math.min(6e4, 1e3 * 2 ** (this.failures - 1));
860
956
  require_secret_config.scrubError(error, this.secretValues());
861
957
  require_internal_log.internalError(`Analytics transport ${this.name} flush failed`, error);
862
958
  this.batch.unshift(...error instanceof PartialBatchError ? error.unsent : entriesToSend);
@@ -919,6 +1015,28 @@ var AnalyticsTransport = class {
919
1015
  await this.ensureReady();
920
1016
  }
921
1017
  };
1018
+ // src/transports/fetch-timeout.ts
1019
+ /**
1020
+ * fetch() with a hard deadline, so an endpoint that accepts the connection and
1021
+ * never answers cannot hang a flush (and with it close()) forever. Uses an
1022
+ * AbortController rather than AbortSignal.timeout(), which needs Node 17.3+
1023
+ * while `engines` allows 16; the timer is unref'd so it never holds the process.
1024
+ */
1025
+ async function fetchWithTimeout(url, init, timeoutMs = require_batch_buffer.DEFAULT_REQUEST_TIMEOUT_MS) {
1026
+ var _timer$unref;
1027
+ const controller = new AbortController();
1028
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
1029
+ (_timer$unref = timer.unref) === null || _timer$unref === void 0 || _timer$unref.call(timer);
1030
+ try {
1031
+ return await fetch(url, {
1032
+ ...init,
1033
+ signal: controller.signal
1034
+ });
1035
+ } catch (error) {
1036
+ if (controller.signal.aborted) throw new Error(`Request timed out after ${timeoutMs}ms`, { cause: error });
1037
+ throw error;
1038
+ }
1039
+ }
922
1040
  // src/transports/datadog.transport.ts
923
1041
  const gzipAsync = (0, node_util.promisify)(node_zlib.gzip);
924
1042
  const MAX_LOGS_PER_REQUEST = 1e3;
@@ -996,6 +1114,10 @@ function tag(key, value) {
996
1114
  * analytics: { datadog: { apiKey: process.env.DD_API_KEY, service: 'api', env: 'production' } }
997
1115
  */
998
1116
  var DataDogTransport = class extends AnalyticsTransport {
1117
+ datadogConfig;
1118
+ baseUrl;
1119
+ logsUrl;
1120
+ metricsUrl;
999
1121
  constructor(config) {
1000
1122
  super("datadog", config);
1001
1123
  this.datadogConfig = config;
@@ -1154,7 +1276,7 @@ var DataDogTransport = class extends AnalyticsTransport {
1154
1276
  const url = `${this.logsUrl}${endpoint}`;
1155
1277
  try {
1156
1278
  const compress = this.datadogConfig.compress === true;
1157
- const response = await fetch(url, {
1279
+ const response = await fetchWithTimeout(url, {
1158
1280
  method: "POST",
1159
1281
  headers: {
1160
1282
  "Content-Type": "application/json",
@@ -1171,7 +1293,7 @@ var DataDogTransport = class extends AnalyticsTransport {
1171
1293
  async makeMetricsRequest(endpoint, data) {
1172
1294
  const url = `${this.metricsUrl}${endpoint}`;
1173
1295
  try {
1174
- const response = await fetch(url, {
1296
+ const response = await fetchWithTimeout(url, {
1175
1297
  method: "POST",
1176
1298
  headers: {
1177
1299
  "Content-Type": "application/json",
@@ -1216,7 +1338,7 @@ var DataDogTransport = class extends AnalyticsTransport {
1216
1338
  };
1217
1339
  const url = `${this.baseUrl}/api/v1/events`;
1218
1340
  try {
1219
- const response = await fetch(url, {
1341
+ const response = await fetchWithTimeout(url, {
1220
1342
  method: "POST",
1221
1343
  headers: {
1222
1344
  "Content-Type": "application/json",
@@ -1241,7 +1363,7 @@ const DEFAULT_MAX_QUEUE = 1e4;
1241
1363
  const DEFAULT_FILE_MODE = 384;
1242
1364
  const DEFAULT_DIR_MODE = 448;
1243
1365
  function csvField(value) {
1244
- return `"${stripControlChars(require_coerce_utils.safeToString(value)).replace(/"/g, "\"\"")}"`;
1366
+ return require_csv_utils.csvField(stripControlChars(require_csv_utils.safeToString(value)), true);
1245
1367
  }
1246
1368
  const SIZE_UNITS = {
1247
1369
  "": 1,
@@ -1286,21 +1408,51 @@ function formatDatePattern(date, pattern) {
1286
1408
  * transports: { file: { filename: 'app.log', dirname: './logs', maxSize: '50MB' } }
1287
1409
  */
1288
1410
  var FileTransport = class {
1411
+ name = "file";
1412
+ level;
1413
+ batchSize;
1414
+ flushInterval;
1415
+ filter;
1416
+ config;
1417
+ maxBytes;
1418
+ maxFiles;
1419
+ compress;
1420
+ intervalMs;
1421
+ timestampedActive;
1422
+ dir;
1423
+ base;
1424
+ ext;
1425
+ batch = [];
1426
+ batchTimer;
1427
+ lastRotation = /* @__PURE__ */ new Date();
1428
+ currentFilePath;
1429
+ bytesWritten = 0;
1430
+ period;
1431
+ rotateRequested = false;
1432
+ rotationWaiters = [];
1433
+ /**
1434
+ * The one drain loop, or undefined when idle. Only one ever runs, so the batch
1435
+ * is never snapshotted and written twice, and rotation (which runs inside it)
1436
+ * never races a write.
1437
+ */
1438
+ draining;
1439
+ /** Entries queued and entries written (or failed) so far; flush() waits on these. */
1440
+ enqueued = 0;
1441
+ settled = 0;
1442
+ flushWaiters = [];
1443
+ maxQueue;
1444
+ fileMode;
1445
+ dirMode;
1446
+ queuePolicy;
1447
+ spaceWaiters = [];
1448
+ inFlight = 0;
1449
+ dropped = 0;
1450
+ lastDropWarning = 0;
1451
+ /** Gzip and pruning of rotated files run off the write path, one at a time. */
1452
+ housekeeping = Promise.resolve();
1453
+ exitListenerAttached = false;
1454
+ onExit = () => this.flushSyncOnExit();
1289
1455
  constructor(config) {
1290
- this.name = "file";
1291
- this.batch = [];
1292
- this.lastRotation = /* @__PURE__ */ new Date();
1293
- this.bytesWritten = 0;
1294
- this.rotateRequested = false;
1295
- this.rotationWaiters = [];
1296
- this.enqueued = 0;
1297
- this.settled = 0;
1298
- this.flushWaiters = [];
1299
- this.spaceWaiters = [];
1300
- this.inFlight = 0;
1301
- this.dropped = 0;
1302
- this.lastDropWarning = 0;
1303
- this.housekeeping = Promise.resolve();
1304
1456
  this.config = {
1305
1457
  format: "json",
1306
1458
  batchSize: 100,
@@ -1388,6 +1540,7 @@ var FileTransport = class {
1388
1540
  await done;
1389
1541
  }
1390
1542
  enqueue(entry) {
1543
+ this.attachExitListener();
1391
1544
  if (this.queueFull()) {
1392
1545
  if (this.queuePolicy === "drop-oldest" && this.batch.length > 0) {
1393
1546
  this.batch.shift();
@@ -1399,6 +1552,30 @@ var FileTransport = class {
1399
1552
  this.batch.push(entry);
1400
1553
  this.enqueued += 1;
1401
1554
  }
1555
+ attachExitListener() {
1556
+ if (this.exitListenerAttached) return;
1557
+ this.exitListenerAttached = true;
1558
+ process.on("exit", this.onExit);
1559
+ }
1560
+ detachExitListener() {
1561
+ if (!this.exitListenerAttached) return;
1562
+ this.exitListenerAttached = false;
1563
+ process.removeListener("exit", this.onExit);
1564
+ }
1565
+ /**
1566
+ * Last chance on process exit: async writes cannot finish, so whatever is still
1567
+ * batched is appended synchronously to the current file.
1568
+ */
1569
+ flushSyncOnExit() {
1570
+ if (this.batch.length === 0) return;
1571
+ const pending = this.batch;
1572
+ this.batch = [];
1573
+ this.enqueued -= pending.length;
1574
+ try {
1575
+ const content = pending.map((entry) => this.formatEntry(entry)).join("\n") + "\n";
1576
+ node_fs.appendFileSync(this.currentFilePath, content, { mode: this.fileMode });
1577
+ } catch {}
1578
+ }
1402
1579
  queueFull() {
1403
1580
  return this.batch.length + this.inFlight >= this.maxQueue;
1404
1581
  }
@@ -1483,7 +1660,11 @@ var FileTransport = class {
1483
1660
  case "json": return require_safe_stringify.stringifyForLog(require_entry_fields.mergeCoreFields({
1484
1661
  timestamp: entry.timestamp.toISOString(),
1485
1662
  level: entry.level,
1486
- message: entry.message
1663
+ message: entry.message,
1664
+ context: entry.context,
1665
+ traceId: entry.traceId,
1666
+ appName: entry.appName,
1667
+ environment: entry.environment
1487
1668
  }, entry.data));
1488
1669
  case "csv": return [
1489
1670
  entry.timestamp.toISOString(),
@@ -1493,7 +1674,7 @@ var FileTransport = class {
1493
1674
  csvField(entry.traceId),
1494
1675
  csvField(require_safe_stringify.stringifyForLog(entry.data || {}))
1495
1676
  ].join(",");
1496
- default: return `${entry.timestamp.toISOString()} [${stripControlChars(require_coerce_utils.safeToString(entry.level)).toUpperCase()}] ${stripControlChars(require_coerce_utils.safeToString(entry.message))}${entry.data ? " " + require_safe_stringify.stringifyForLog(entry.data) : ""}`;
1677
+ default: return `${entry.timestamp.toISOString()} [${stripControlChars(require_csv_utils.safeToString(entry.level)).toUpperCase()}] ${stripControlChars(require_csv_utils.safeToString(entry.message))}${entry.data ? " " + require_safe_stringify.stringifyForLog(entry.data) : ""}`;
1497
1678
  }
1498
1679
  }
1499
1680
  addToBatch(entry) {
@@ -1505,9 +1686,12 @@ var FileTransport = class {
1505
1686
  this.batchTimer = void 0;
1506
1687
  }
1507
1688
  this.startDrain();
1508
- } else if (!this.batchTimer && this.config.flushInterval) this.batchTimer = setTimeout(() => {
1509
- this.flush().catch((err) => require_internal_log.internalError("FileTransport interval flush failed", err));
1510
- }, this.config.flushInterval);
1689
+ } else if (!this.batchTimer && this.config.flushInterval) {
1690
+ this.batchTimer = setTimeout(() => {
1691
+ this.flush().catch((err) => require_internal_log.internalError("FileTransport interval flush failed", err));
1692
+ }, this.config.flushInterval);
1693
+ this.batchTimer.unref();
1694
+ }
1511
1695
  }
1512
1696
  /** Only ever called from drain(), so writes and rotations never overlap. */
1513
1697
  async writeBatch(entries) {
@@ -1673,6 +1857,7 @@ var FileTransport = class {
1673
1857
  this.batchTimer = void 0;
1674
1858
  }
1675
1859
  await this.flush();
1860
+ this.detachExitListener();
1676
1861
  await this.housekeeping;
1677
1862
  this.reportDropped(0);
1678
1863
  }
@@ -1687,10 +1872,11 @@ var FileTransport = class {
1687
1872
  * }
1688
1873
  */
1689
1874
  var GoogleAnalyticsTransport = class extends AnalyticsTransport {
1875
+ gaConfig;
1876
+ baseUrl = "https://www.google-analytics.com/mp/collect";
1877
+ debugUrl = "https://www.google-analytics.com/debug/mp/collect";
1690
1878
  constructor(config) {
1691
1879
  super("google-analytics", config);
1692
- this.baseUrl = "https://www.google-analytics.com/mp/collect";
1693
- this.debugUrl = "https://www.google-analytics.com/debug/mp/collect";
1694
1880
  this.gaConfig = config;
1695
1881
  require_secret_config.protectSecrets(this, ["gaConfig"]);
1696
1882
  }
@@ -1795,7 +1981,7 @@ var GoogleAnalyticsTransport = class extends AnalyticsTransport {
1795
1981
  api_secret: this.gaConfig.apiSecret
1796
1982
  });
1797
1983
  try {
1798
- const response = await fetch(`${url}?${params}`, {
1984
+ const response = await fetchWithTimeout(`${url}?${params}`, {
1799
1985
  method: "POST",
1800
1986
  headers: { "Content-Type": "application/json" },
1801
1987
  body: require_safe_stringify.stringifyForLog(payload)
@@ -1942,9 +2128,10 @@ function meetsTransportLevel(threshold, level, values) {
1942
2128
  * analytics: { mixpanel: { token: process.env.MIXPANEL_TOKEN } }
1943
2129
  */
1944
2130
  var MixpanelTransport = class extends AnalyticsTransport {
2131
+ mixpanelConfig;
2132
+ baseUrl = "https://api.mixpanel.com";
1945
2133
  constructor(config) {
1946
2134
  super("mixpanel", config);
1947
- this.baseUrl = "https://api.mixpanel.com";
1948
2135
  this.mixpanelConfig = config;
1949
2136
  require_secret_config.protectSecrets(this, ["mixpanelConfig"]);
1950
2137
  }
@@ -2012,7 +2199,7 @@ var MixpanelTransport = class extends AnalyticsTransport {
2012
2199
  async makeRequest(endpoint, data) {
2013
2200
  const url = `${this.mixpanelConfig.endpoint || this.baseUrl}${endpoint}`;
2014
2201
  try {
2015
- const response = await fetch(url, {
2202
+ const response = await fetchWithTimeout(url, {
2016
2203
  method: "POST",
2017
2204
  headers: {
2018
2205
  "Content-Type": "application/json",
@@ -2089,6 +2276,8 @@ const LOGIXIA_VERSION = "1.12.0";
2089
2276
  * analytics: { segment: { writeKey: process.env.SEGMENT_WRITE_KEY } }
2090
2277
  */
2091
2278
  var SegmentTransport = class extends AnalyticsTransport {
2279
+ segmentConfig;
2280
+ baseUrl;
2092
2281
  constructor(config) {
2093
2282
  super("segment", config);
2094
2283
  this.segmentConfig = {
@@ -2179,7 +2368,7 @@ var SegmentTransport = class extends AnalyticsTransport {
2179
2368
  const url = `${this.baseUrl}${endpoint}`;
2180
2369
  const auth = Buffer.from(`${this.segmentConfig.writeKey}:`).toString("base64");
2181
2370
  try {
2182
- const response = await fetch(url, {
2371
+ const response = await fetchWithTimeout(url, {
2183
2372
  method: "POST",
2184
2373
  headers: {
2185
2374
  "Content-Type": "application/json",
@@ -2349,10 +2538,17 @@ var SegmentTransport = class extends AnalyticsTransport {
2349
2538
  }
2350
2539
  };
2351
2540
  // src/transports/transport.manager.ts
2352
- function _sleep(ms) {
2353
- return new Promise((resolve) => setTimeout(resolve, ms));
2354
- }
2541
+ const DEFAULT_MAX_PENDING_RETRIES = 1e3;
2542
+ const RETRY_WARN_INTERVAL_MS = 5e3;
2355
2543
  var TransportManager = class extends node_events.EventEmitter {
2544
+ transports = /* @__PURE__ */ new Map();
2545
+ metrics = /* @__PURE__ */ new Map();
2546
+ isShuttingDown = false;
2547
+ transportLevelPreferences = /* @__PURE__ */ new Map();
2548
+ warnedPromptDeprecated = false;
2549
+ closePromise;
2550
+ retryStates = /* @__PURE__ */ new Map();
2551
+ levelValues;
2356
2552
  /**
2357
2553
  * @param config Transport configuration.
2358
2554
  * @param levels The logger's `levelOptions.levels`; used with the built-in levels
@@ -2360,11 +2556,6 @@ var TransportManager = class extends node_events.EventEmitter {
2360
2556
  */
2361
2557
  constructor(config = {}, levels) {
2362
2558
  super();
2363
- this.transports = /* @__PURE__ */ new Map();
2364
- this.metrics = /* @__PURE__ */ new Map();
2365
- this.isShuttingDown = false;
2366
- this.transportLevelPreferences = /* @__PURE__ */ new Map();
2367
- this.warnedPromptDeprecated = false;
2368
2559
  this.levelValues = buildLevelValues(levels);
2369
2560
  this.setupTransports(config);
2370
2561
  }
@@ -2437,8 +2628,9 @@ var TransportManager = class extends node_events.EventEmitter {
2437
2628
  if (!transport) return false;
2438
2629
  this.transports.delete(id);
2439
2630
  this.metrics.delete(id);
2631
+ this.drainRetries(id, transport);
2440
2632
  if (transport.close) transport.close().catch((error) => {
2441
- this.emit("error", error, id);
2633
+ require_internal_log.internalError(`Transport "${id}" failed to close`, error);
2442
2634
  });
2443
2635
  this.emit("transport:removed", id);
2444
2636
  return true;
@@ -2454,7 +2646,10 @@ var TransportManager = class extends node_events.EventEmitter {
2454
2646
  * sync path.
2455
2647
  */
2456
2648
  write(entry) {
2457
- if (this.isShuttingDown) return;
2649
+ if (this.isShuttingDown) {
2650
+ this.writeAfterClose(entry);
2651
+ return;
2652
+ }
2458
2653
  const transportEntry = {
2459
2654
  timestamp: typeof entry.timestamp === "string" ? new Date(entry.timestamp) : entry.timestamp,
2460
2655
  level: entry.level,
@@ -2492,6 +2687,27 @@ var TransportManager = class extends node_events.EventEmitter {
2492
2687
  }
2493
2688
  });
2494
2689
  }
2690
+ /**
2691
+ * Transports are gone once closed, but the app may still log (its own signal
2692
+ * handler, a late async callback). Emit such lines on stdout/stderr rather
2693
+ * than losing them silently.
2694
+ */
2695
+ writeAfterClose(entry) {
2696
+ const line = `${require_safe_stringify.stringifyForLog({
2697
+ timestamp: entry.timestamp,
2698
+ level: entry.level,
2699
+ message: entry.message,
2700
+ appName: entry.appName,
2701
+ context: entry.context,
2702
+ traceId: entry.traceId,
2703
+ ...entry.payload
2704
+ })}\n`;
2705
+ const level = String(entry.level).toLowerCase();
2706
+ const stream = level === "error" || level === "warn" ? "stderr" : "stdout";
2707
+ try {
2708
+ process[stream].write(line);
2709
+ } catch {}
2710
+ }
2495
2711
  writeToTransport(id, transport, entry) {
2496
2712
  const startTime = Date.now();
2497
2713
  const metrics = this.metrics.get(id);
@@ -2518,43 +2734,86 @@ var TransportManager = class extends node_events.EventEmitter {
2518
2734
  onSuccess();
2519
2735
  return;
2520
2736
  }
2521
- return this._writeWithRetry(transport, entry).then(onSuccess, onError);
2737
+ return this._writeWithRetry(id, transport, entry).then((written) => {
2738
+ if (written) onSuccess();
2739
+ }, onError);
2522
2740
  }
2523
2741
  /**
2524
2742
  * Feature 9: Multi-transport retry + failover.
2525
2743
  *
2526
- * Attempts `transport.write()` up to `retry.maxRetries + 1` times with the
2527
- * configured backoff strategy. If all attempts fail and a fallback transport
2528
- * is configured, routes the entry there instead.
2744
+ * The first attempt is awaited by the caller. If it fails, the entry moves to
2745
+ * a bounded per-transport retry queue and the caller's promise resolves, so a
2746
+ * down transport never holds a log call across backoff. The queue retries up
2747
+ * to `retry.maxRetries` more times with the configured backoff, then routes
2748
+ * the entry to the fallback transport (if any) and calls `onExhausted`.
2749
+ * Returns true when the first attempt succeeded.
2529
2750
  */
2530
- async _writeWithRetry(transport, entry) {
2531
- const retryCfg = transport.retry;
2532
- if (!retryCfg || !retryCfg.maxRetries) {
2751
+ async _writeWithRetry(id, transport, entry) {
2752
+ try {
2533
2753
  await transport.write(entry);
2534
- return;
2754
+ return true;
2755
+ } catch (err) {
2756
+ this.enqueueRetry(id, transport, entry, err instanceof Error ? err : new Error(String(err)));
2757
+ return false;
2535
2758
  }
2536
- const { maxRetries, backoff = "exponential", delay: baseDelay = 500, maxDelay = 3e4, fallback, onExhausted } = retryCfg;
2537
- let lastError = /* @__PURE__ */ new Error("Unknown transport error");
2538
- for (let attempt = 0; attempt <= maxRetries; attempt++) try {
2539
- await transport.write(entry);
2759
+ }
2760
+ enqueueRetry(id, transport, entry, error) {
2761
+ let state = this.retryStates.get(id);
2762
+ if (!state) {
2763
+ state = {
2764
+ queue: [],
2765
+ timer: void 0,
2766
+ running: false,
2767
+ dropped: 0,
2768
+ lastWarn: 0
2769
+ };
2770
+ this.retryStates.set(id, state);
2771
+ }
2772
+ const maxPending = transport.retry.maxPending ?? DEFAULT_MAX_PENDING_RETRIES;
2773
+ while (state.queue.length >= maxPending) {
2774
+ state.queue.shift();
2775
+ state.dropped++;
2776
+ }
2777
+ this.warnDropped(id, state);
2778
+ this.scheduleRetry(id, transport, state, {
2779
+ entry,
2780
+ attempt: 0,
2781
+ due: 0
2782
+ }, error);
2783
+ this.pumpRetries(id, transport, state);
2784
+ }
2785
+ warnDropped(id, state) {
2786
+ const now = Date.now();
2787
+ if (state.dropped > 0 && now - state.lastWarn >= RETRY_WARN_INTERVAL_MS) {
2788
+ require_internal_log.internalWarn(`TransportManager: retry queue for "${id}" is full; dropped ${state.dropped} oldest log entr${state.dropped === 1 ? "y" : "ies"}`);
2789
+ state.dropped = 0;
2790
+ state.lastWarn = now;
2791
+ }
2792
+ }
2793
+ /** Queues `item` for its next attempt, or finishes it when retries ran out. */
2794
+ scheduleRetry(id, transport, state, item, error) {
2795
+ const { maxRetries = 0, backoff = "exponential", delay = 500, maxDelay = 3e4 } = transport.retry;
2796
+ if (item.attempt >= maxRetries) {
2797
+ this.exhaust(id, transport, item.entry, error);
2540
2798
  return;
2541
- } catch (err) {
2542
- lastError = err instanceof Error ? err : new Error(String(err));
2543
- if (attempt < maxRetries) {
2544
- let wait;
2545
- switch (backoff) {
2546
- case "fixed":
2547
- wait = baseDelay;
2548
- break;
2549
- case "linear":
2550
- wait = baseDelay * (attempt + 1);
2551
- break;
2552
- default: wait = Math.min(baseDelay * Math.pow(2, attempt), maxDelay);
2553
- }
2554
- this.emit("transport:retry", transport.name, attempt + 1, maxRetries, wait);
2555
- await _sleep(wait);
2556
- }
2557
2799
  }
2800
+ let wait;
2801
+ switch (backoff) {
2802
+ case "fixed":
2803
+ wait = delay;
2804
+ break;
2805
+ case "linear":
2806
+ wait = delay * (item.attempt + 1);
2807
+ break;
2808
+ default: wait = Math.min(delay * Math.pow(2, item.attempt), maxDelay);
2809
+ }
2810
+ item.attempt++;
2811
+ item.due = Date.now() + wait;
2812
+ state.queue.push(item);
2813
+ this.emit("transport:retry", transport.name, item.attempt, maxRetries, wait);
2814
+ }
2815
+ async exhaust(id, transport, entry, error) {
2816
+ const { fallback, onExhausted } = transport.retry;
2558
2817
  if (fallback) {
2559
2818
  this.emit("transport:fallback", transport.name, fallback.name);
2560
2819
  try {
@@ -2562,8 +2821,65 @@ var TransportManager = class extends node_events.EventEmitter {
2562
2821
  return;
2563
2822
  } catch {}
2564
2823
  }
2565
- onExhausted === null || onExhausted === void 0 || onExhausted(lastError, entry);
2566
- throw lastError;
2824
+ const metrics = this.metrics.get(id);
2825
+ if (metrics) metrics.errors++;
2826
+ try {
2827
+ onExhausted === null || onExhausted === void 0 || onExhausted(error, entry);
2828
+ } catch (hookError) {
2829
+ require_internal_log.internalWarn(`TransportManager: onExhausted for "${id}" threw: ${String(hookError)}`);
2830
+ }
2831
+ if (this.listenerCount("error") > 0) this.emit("error", error, id);
2832
+ }
2833
+ /** One serial worker per transport: the earliest-due entry is retried first. */
2834
+ pumpRetries(id, transport, state) {
2835
+ var _state$timer$unref, _state$timer;
2836
+ if (state.running || state.timer || state.queue.length === 0) return;
2837
+ let next = 0;
2838
+ for (let i = 1; i < state.queue.length; i++) if (state.queue[i].due < state.queue[next].due) next = i;
2839
+ const wait = Math.max(0, state.queue[next].due - Date.now());
2840
+ state.timer = setTimeout(() => {
2841
+ state.timer = void 0;
2842
+ this.runRetry(id, transport, state);
2843
+ }, wait);
2844
+ (_state$timer$unref = (_state$timer = state.timer).unref) === null || _state$timer$unref === void 0 || _state$timer$unref.call(_state$timer);
2845
+ }
2846
+ async runRetry(id, transport, state) {
2847
+ if (state.queue.length === 0) return;
2848
+ let next = 0;
2849
+ for (let i = 1; i < state.queue.length; i++) if (state.queue[i].due < state.queue[next].due) next = i;
2850
+ const [item] = state.queue.splice(next, 1);
2851
+ if (!item) return;
2852
+ state.running = true;
2853
+ try {
2854
+ await transport.write(item.entry);
2855
+ const metrics = this.metrics.get(id);
2856
+ if (metrics) {
2857
+ metrics.logsWritten++;
2858
+ metrics.lastWrite = /* @__PURE__ */ new Date();
2859
+ }
2860
+ this.emit("log", item.entry);
2861
+ } catch (err) {
2862
+ this.scheduleRetry(id, transport, state, item, err instanceof Error ? err : new Error(String(err)));
2863
+ } finally {
2864
+ state.running = false;
2865
+ }
2866
+ if (!this.isShuttingDown) this.pumpRetries(id, transport, state);
2867
+ }
2868
+ /** Stops a transport's retry worker and hands what is still queued to its fallback. */
2869
+ async drainRetries(id, transport) {
2870
+ var _transport$retry;
2871
+ const state = this.retryStates.get(id);
2872
+ if (!state) return;
2873
+ this.retryStates.delete(id);
2874
+ if (state.timer) clearTimeout(state.timer);
2875
+ state.timer = void 0;
2876
+ const fallback = transport === null || transport === void 0 || (_transport$retry = transport.retry) === null || _transport$retry === void 0 ? void 0 : _transport$retry.fallback;
2877
+ if (state.queue.length === 0) return;
2878
+ if (!fallback) {
2879
+ require_internal_log.internalWarn(`TransportManager: "${id}" closed with ${state.queue.length} log entr${state.queue.length === 1 ? "y" : "ies"} still awaiting retry`);
2880
+ return;
2881
+ }
2882
+ await Promise.allSettled(state.queue.map((item) => fallback.write(item.entry)));
2567
2883
  }
2568
2884
  shouldTransportHandle(transport, level, transportId) {
2569
2885
  if (transportId && this.transportLevelPreferences.has(transportId)) return this.transportLevelPreferences.get(transportId).includes(level.toLowerCase());
@@ -2582,19 +2898,35 @@ var TransportManager = class extends node_events.EventEmitter {
2582
2898
  if (readyPromises.length > 0) await Promise.all(readyPromises);
2583
2899
  }
2584
2900
  async flush() {
2901
+ await this.flushTransports(false);
2902
+ }
2903
+ /**
2904
+ * @param skipClosable Leave out transports with a close(): close() flushes
2905
+ * them itself, and a second flush first would double the time a hung
2906
+ * endpoint can hold shutdown.
2907
+ */
2908
+ async flushTransports(skipClosable) {
2585
2909
  const flushPromises = [];
2586
- for (const [id, transport] of this.transports) if (this.isBatchTransport(transport)) {
2587
- const flushPromise = transport.flush().catch((error) => {
2588
- this.emit("error", error, id);
2589
- });
2590
- flushPromises.push(flushPromise);
2910
+ for (const [id, transport] of this.transports) {
2911
+ if (skipClosable && transport.close) continue;
2912
+ if (this.isBatchTransport(transport)) {
2913
+ const flushPromise = transport.flush().catch((error) => {
2914
+ this.emit("error", error, id);
2915
+ });
2916
+ flushPromises.push(flushPromise);
2917
+ }
2591
2918
  }
2592
2919
  await Promise.allSettled(flushPromises);
2593
2920
  this.emit("flush", "all", this.transports.size);
2594
2921
  }
2595
- async close() {
2596
- await this.flush();
2922
+ close() {
2923
+ this.closePromise ??= this.closeOnce();
2924
+ return this.closePromise;
2925
+ }
2926
+ async closeOnce() {
2927
+ await this.flushTransports(true);
2597
2928
  this.isShuttingDown = true;
2929
+ await Promise.allSettled([...this.retryStates.keys()].map((id) => this.drainRetries(id, this.transports.get(id))));
2598
2930
  const closePromises = [];
2599
2931
  for (const [id, transport] of this.transports) if (transport.close) {
2600
2932
  const closePromise = transport.close().catch((error) => {
@@ -2918,4 +3250,4 @@ Object.defineProperty(exports, "stripControlChars", {
2918
3250
  }
2919
3251
  });
2920
3252
 
2921
- //# sourceMappingURL=otel-ChnVYRLA.js.map
3253
+ //# sourceMappingURL=otel-8ypFGG19.js.map