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