logixia 1.13.0 → 2.0.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 (176) hide show
  1. package/README.md +81 -16
  2. package/dist/{async-context-CwBNMm2E.mjs → async-context-BzJrbfQs.mjs} +8 -4
  3. package/dist/async-context-BzJrbfQs.mjs.map +1 -0
  4. package/dist/{async-context-CSHWoHMB.js → async-context-DyQQYq3I.js} +8 -4
  5. package/dist/async-context-DyQQYq3I.js.map +1 -0
  6. package/dist/{azure-monitor.transport-CG7ClkYj.d.ts → azure-monitor.transport-Cn_3Usvu.d.ts} +2 -2
  7. package/dist/{azure-monitor.transport-CG7ClkYj.d.ts.map → azure-monitor.transport-Cn_3Usvu.d.ts.map} +1 -1
  8. package/dist/{azure-monitor.transport-Cm74kXZS.d.mts → azure-monitor.transport-D7Da4RAb.d.mts} +2 -2
  9. package/dist/{azure-monitor.transport-Cm74kXZS.d.mts.map → azure-monitor.transport-D7Da4RAb.d.mts.map} +1 -1
  10. package/dist/azure.d.mts +2 -2
  11. package/dist/azure.d.ts +2 -2
  12. package/dist/browser.d.mts +9 -0
  13. package/dist/browser.d.mts.map +1 -1
  14. package/dist/browser.d.ts +9 -0
  15. package/dist/browser.d.ts.map +1 -1
  16. package/dist/browser.js +43 -12
  17. package/dist/browser.js.map +1 -1
  18. package/dist/browser.mjs +43 -12
  19. package/dist/browser.mjs.map +1 -1
  20. package/dist/{logitron-logger.module-Bs3XYZgF.mjs → builder-0gEzRopq.mjs} +383 -1021
  21. package/dist/builder-0gEzRopq.mjs.map +1 -0
  22. package/dist/{logitron-logger.module-BsDZR5BH.js → builder-D44QktIo.js} +411 -1155
  23. package/dist/builder-D44QktIo.js.map +1 -0
  24. package/dist/cli/index.js +130 -65
  25. package/dist/cloudwatch.d.mts +2 -2
  26. package/dist/cloudwatch.d.ts +2 -2
  27. package/dist/cloudwatch.js +2 -1
  28. package/dist/cloudwatch.mjs +2 -1
  29. package/dist/{cloudwatch.transport-pNBNWJLt.js → cloudwatch.transport-BO1ahxw-.js} +26 -19
  30. package/dist/cloudwatch.transport-BO1ahxw-.js.map +1 -0
  31. package/dist/{cloudwatch.transport-yBwm4OeO.mjs → cloudwatch.transport-DZ6ies7g.mjs} +26 -19
  32. package/dist/cloudwatch.transport-DZ6ies7g.mjs.map +1 -0
  33. package/dist/{cloudwatch.transport-CGHY4buE.d.mts → cloudwatch.transport-DsEq633o.d.ts} +9 -2
  34. package/dist/cloudwatch.transport-DsEq633o.d.ts.map +1 -0
  35. package/dist/{cloudwatch.transport-DK_8cKd3.d.ts → cloudwatch.transport-UlyZFWMn.d.mts} +9 -2
  36. package/dist/cloudwatch.transport-UlyZFWMn.d.mts.map +1 -0
  37. package/dist/{coerce.utils-BN980Y9V.js → coerce.utils-B-LilukN.js} +1 -1
  38. package/dist/{coerce.utils-BN980Y9V.js.map → coerce.utils-B-LilukN.js.map} +1 -1
  39. package/dist/{coerce.utils-Dxv4_ikC.mjs → coerce.utils-Cu6IUTgX.mjs} +1 -1
  40. package/dist/{coerce.utils-Dxv4_ikC.mjs.map → coerce.utils-Cu6IUTgX.mjs.map} +1 -1
  41. package/dist/correlation.d.mts +24 -6
  42. package/dist/correlation.d.mts.map +1 -1
  43. package/dist/correlation.d.ts +24 -6
  44. package/dist/correlation.d.ts.map +1 -1
  45. package/dist/correlation.js +41 -16
  46. package/dist/correlation.js.map +1 -1
  47. package/dist/correlation.mjs +41 -16
  48. package/dist/correlation.mjs.map +1 -1
  49. package/dist/entry-fields-BJkuPnIg.mjs +30 -0
  50. package/dist/entry-fields-BJkuPnIg.mjs.map +1 -0
  51. package/dist/entry-fields-BvOLFlJ4.js +36 -0
  52. package/dist/entry-fields-BvOLFlJ4.js.map +1 -0
  53. package/dist/gcp.d.mts +2 -2
  54. package/dist/gcp.d.ts +2 -2
  55. package/dist/gcp.js +2 -1
  56. package/dist/gcp.mjs +2 -1
  57. package/dist/{gcp.transport-BOhkVKlI.d.ts → gcp.transport-B7oc97sG.d.ts} +2 -2
  58. package/dist/gcp.transport-B7oc97sG.d.ts.map +1 -0
  59. package/dist/{gcp.transport-SHdtc1n0.js → gcp.transport-CHV8MGgz.js} +5 -5
  60. package/dist/gcp.transport-CHV8MGgz.js.map +1 -0
  61. package/dist/{gcp.transport-BJuNRFe_.mjs → gcp.transport-DBX5TPIy.mjs} +5 -5
  62. package/dist/gcp.transport-DBX5TPIy.mjs.map +1 -0
  63. package/dist/{gcp.transport-Bi6CUqZR.d.mts → gcp.transport-GrJurKo8.d.mts} +2 -2
  64. package/dist/gcp.transport-GrJurKo8.d.mts.map +1 -0
  65. package/dist/{index-BPKwwERo.d.mts → index-3mw5gaCw.d.mts} +2 -2
  66. package/dist/{index-BPKwwERo.d.mts.map → index-3mw5gaCw.d.mts.map} +1 -1
  67. package/dist/{index-C1X-TE5I.d.ts → index-DhIoploR.d.ts} +2 -2
  68. package/dist/{index-C1X-TE5I.d.ts.map → index-DhIoploR.d.ts.map} +1 -1
  69. package/dist/index.d.mts +99 -137
  70. package/dist/index.d.mts.map +1 -1
  71. package/dist/index.d.ts +99 -137
  72. package/dist/index.d.ts.map +1 -1
  73. package/dist/index.js +146 -510
  74. package/dist/index.js.map +1 -1
  75. package/dist/index.mjs +99 -431
  76. package/dist/index.mjs.map +1 -1
  77. package/dist/logitron-logger-BFDSO8Io.d.mts +257 -0
  78. package/dist/logitron-logger-BFDSO8Io.d.mts.map +1 -0
  79. package/dist/logitron-logger-UVeN-mQm.d.ts +257 -0
  80. package/dist/logitron-logger-UVeN-mQm.d.ts.map +1 -0
  81. package/dist/middleware.d.mts +11 -4
  82. package/dist/middleware.d.mts.map +1 -1
  83. package/dist/middleware.d.ts +11 -4
  84. package/dist/middleware.d.ts.map +1 -1
  85. package/dist/middleware.js +148 -24
  86. package/dist/middleware.js.map +1 -1
  87. package/dist/middleware.mjs +148 -24
  88. package/dist/middleware.mjs.map +1 -1
  89. package/dist/nest.d.mts +481 -6
  90. package/dist/nest.d.mts.map +1 -1
  91. package/dist/nest.d.ts +481 -6
  92. package/dist/nest.d.ts.map +1 -1
  93. package/dist/nest.js +884 -13
  94. package/dist/nest.js.map +1 -0
  95. package/dist/nest.mjs +863 -6
  96. package/dist/nest.mjs.map +1 -0
  97. package/dist/{search-CFQRISUv.mjs → search-BFbu_oVn.mjs} +35 -12
  98. package/dist/search-BFbu_oVn.mjs.map +1 -0
  99. package/dist/{search-njTI2hdo.js → search-C4V0OMEn.js} +35 -12
  100. package/dist/search-C4V0OMEn.js.map +1 -0
  101. package/dist/{search-DujhR94O.d.ts → search-CNh9uVwR.d.ts} +2 -3
  102. package/dist/search-CNh9uVwR.d.ts.map +1 -0
  103. package/dist/{search-CQOo-Iq6.d.mts → search-DXANsQ2n.d.mts} +2 -3
  104. package/dist/search-DXANsQ2n.d.mts.map +1 -0
  105. package/dist/search.d.mts +3 -3
  106. package/dist/search.d.ts +3 -3
  107. package/dist/search.js +2 -2
  108. package/dist/search.mjs +2 -2
  109. package/dist/slack.transport-BZzoXDPn.d.mts +112 -0
  110. package/dist/slack.transport-BZzoXDPn.d.mts.map +1 -0
  111. package/dist/slack.transport-BfJRouB2.js +654 -0
  112. package/dist/slack.transport-BfJRouB2.js.map +1 -0
  113. package/dist/slack.transport-Bzw7sucI.mjs +643 -0
  114. package/dist/slack.transport-Bzw7sucI.mjs.map +1 -0
  115. package/dist/slack.transport-CXjQYOes.d.ts +112 -0
  116. package/dist/slack.transport-CXjQYOes.d.ts.map +1 -0
  117. package/dist/testing.d.mts +2 -2
  118. package/dist/testing.d.ts +2 -2
  119. package/dist/trace.utils-CvXgaiQT.mjs +278 -0
  120. package/dist/trace.utils-CvXgaiQT.mjs.map +1 -0
  121. package/dist/trace.utils-DpBOLDtq.js +358 -0
  122. package/dist/trace.utils-DpBOLDtq.js.map +1 -0
  123. package/dist/{transport.manager-D6KL3t9V.mjs → transport.manager-BiuCAJd2.mjs} +526 -160
  124. package/dist/transport.manager-BiuCAJd2.mjs.map +1 -0
  125. package/dist/{transport.manager-BivyUTtU.js → transport.manager-CXbKKdVE.js} +537 -159
  126. package/dist/transport.manager-CXbKKdVE.js.map +1 -0
  127. package/dist/{transport.types-B5vcyv2k.d.mts → transport.types-BMnurPcc.d.mts} +23 -3
  128. package/dist/transport.types-BMnurPcc.d.mts.map +1 -0
  129. package/dist/{transport.types-BuY84TwO.d.ts → transport.types-CGoHpWQR.d.ts} +23 -3
  130. package/dist/transport.types-CGoHpWQR.d.ts.map +1 -0
  131. package/dist/transports.d.mts +50 -20
  132. package/dist/transports.d.mts.map +1 -1
  133. package/dist/transports.d.ts +50 -20
  134. package/dist/transports.d.ts.map +1 -1
  135. package/dist/transports.js +9 -5
  136. package/dist/transports.mjs +8 -6
  137. package/dist/worker-entry.js +307 -55
  138. package/dist/worker.d.mts +2 -2
  139. package/dist/worker.d.ts +2 -2
  140. package/dist/worker.js +1 -1
  141. package/dist/worker.mjs +1 -1
  142. package/dist/{worker.transport-CfH6z37Z.js → worker.transport-BdPxu3PN.js} +1 -1
  143. package/dist/{worker.transport-CfH6z37Z.js.map → worker.transport-BdPxu3PN.js.map} +1 -1
  144. package/dist/{worker.transport-mKXgNK0L.d.ts → worker.transport-CdzSZJmw.d.ts} +2 -2
  145. package/dist/{worker.transport-mKXgNK0L.d.ts.map → worker.transport-CdzSZJmw.d.ts.map} +1 -1
  146. package/dist/{worker.transport-BP8Q28Z2.d.mts → worker.transport-JvGPog3C.d.mts} +2 -2
  147. package/dist/{worker.transport-BP8Q28Z2.d.mts.map → worker.transport-JvGPog3C.d.mts.map} +1 -1
  148. package/dist/{worker.transport-BhMyvqQ2.mjs → worker.transport-lPxO2MVL.mjs} +1 -1
  149. package/dist/{worker.transport-BhMyvqQ2.mjs.map → worker.transport-lPxO2MVL.mjs.map} +1 -1
  150. package/package.json +27 -14
  151. package/dist/.tsbuildinfo +0 -1
  152. package/dist/async-context-CSHWoHMB.js.map +0 -1
  153. package/dist/async-context-CwBNMm2E.mjs.map +0 -1
  154. package/dist/cloudwatch.transport-CGHY4buE.d.mts.map +0 -1
  155. package/dist/cloudwatch.transport-DK_8cKd3.d.ts.map +0 -1
  156. package/dist/cloudwatch.transport-pNBNWJLt.js.map +0 -1
  157. package/dist/cloudwatch.transport-yBwm4OeO.mjs.map +0 -1
  158. package/dist/gcp.transport-BJuNRFe_.mjs.map +0 -1
  159. package/dist/gcp.transport-BOhkVKlI.d.ts.map +0 -1
  160. package/dist/gcp.transport-Bi6CUqZR.d.mts.map +0 -1
  161. package/dist/gcp.transport-SHdtc1n0.js.map +0 -1
  162. package/dist/logitron-logger.module-BdG1Jnpk.d.mts +0 -618
  163. package/dist/logitron-logger.module-BdG1Jnpk.d.mts.map +0 -1
  164. package/dist/logitron-logger.module-Bs3XYZgF.mjs.map +0 -1
  165. package/dist/logitron-logger.module-BsDZR5BH.js.map +0 -1
  166. package/dist/logitron-logger.module-CbXy2gfW.d.ts +0 -618
  167. package/dist/logitron-logger.module-CbXy2gfW.d.ts.map +0 -1
  168. package/dist/search-CFQRISUv.mjs.map +0 -1
  169. package/dist/search-CQOo-Iq6.d.mts.map +0 -1
  170. package/dist/search-DujhR94O.d.ts.map +0 -1
  171. package/dist/search-njTI2hdo.js.map +0 -1
  172. package/dist/transport.manager-BivyUTtU.js.map +0 -1
  173. package/dist/transport.manager-D6KL3t9V.mjs.map +0 -1
  174. package/dist/transport.types-B5vcyv2k.d.mts.map +0 -1
  175. package/dist/transport.types-BuY84TwO.d.ts.map +0 -1
  176. package/scripts/postinstall.js +0 -61
@@ -1,14 +1,33 @@
1
1
  import { t as __require } from "./chunk-DLSkYcPk.mjs";
2
2
  import { n as internalLog, r as internalWarn, t as internalError } from "./internal-log-Cm8SOWSV.mjs";
3
3
  import { i as stringifyForLog } from "./safe-stringify-DJ66tW5I.mjs";
4
- import { t as safeToString } from "./coerce.utils-Dxv4_ikC.mjs";
4
+ import { t as mergeCoreFields } from "./entry-fields-BJkuPnIg.mjs";
5
+ import { t as safeToString } from "./coerce.utils-Cu6IUTgX.mjs";
5
6
  import * as fs from "node:fs";
6
7
  import * as path$1 from "node:path";
7
8
  import { EventEmitter } from "node:events";
8
- import { pipeline } from "node:stream/promises";
9
9
  import { promisify } from "node:util";
10
- import { createGzip } from "node:zlib";
10
+ import { createGzip, gzip } from "node:zlib";
11
+ import { pipeline } from "node:stream/promises";
11
12
 
13
+ //#region src/utils/sanitize.utils.ts
14
+ /**
15
+ * Control-character stripping for text-mode log output.
16
+ *
17
+ * Line breaks become the visible escapes `\\n` / `\\r`; other C0 characters
18
+ * (except tab), DEL and C1 characters are removed, so user-supplied text cannot
19
+ * inject ANSI escapes, overwrite lines, or forge extra log entries.
20
+ */
21
+ const CONTROL_CHARS_RE = /[\x00-\x08\x0A-\x1F\x7F-\x9F]/g;
22
+ const VISIBLE_ESCAPES = {
23
+ "\n": "\\n",
24
+ "\r": "\\r"
25
+ };
26
+ function stripControlChars(value) {
27
+ return value.replace(CONTROL_CHARS_RE, (ch) => VISIBLE_ESCAPES[ch] ?? "");
28
+ }
29
+
30
+ //#endregion
12
31
  //#region src/transports/console.transport.ts
13
32
  /**
14
33
  * Writes log entries to stdout (info/debug/verbose) and stderr (error/warn).
@@ -39,9 +58,8 @@ var ConsoleTransport = class ConsoleTransport {
39
58
  ["brightwhite", "\x1B[97m"],
40
59
  ["reset", "\x1B[0m"]
41
60
  ]);
42
- static #_2 = this.CONTROL_CHARS_RE = /[\x00-\x08\x0B-\x1F\x7F-\x9F]/g;
43
61
  static sanitize(value) {
44
- return safeToString(value).replace(ConsoleTransport.CONTROL_CHARS_RE, "");
62
+ return stripControlChars(safeToString(value));
45
63
  }
46
64
  constructor(config = {}) {
47
65
  this.config = config;
@@ -54,16 +72,15 @@ var ConsoleTransport = class ConsoleTransport {
54
72
  (lvl === "error" || lvl === "warn" ? process.stderr : process.stdout).write(formatted);
55
73
  }
56
74
  formatEntry(entry) {
57
- if (this.config.format === "json") return stringifyForLog({
75
+ if (this.config.format === "json") return stringifyForLog(mergeCoreFields({
58
76
  timestamp: this.config.timestamp !== false ? entry.timestamp.toISOString() : void 0,
59
77
  level: entry.level,
60
78
  message: entry.message,
61
- ...entry.data || {},
62
79
  context: entry.context,
63
80
  traceId: entry.traceId,
64
81
  appName: entry.appName,
65
82
  environment: entry.environment
66
- });
83
+ }, entry.data));
67
84
  const parts = [];
68
85
  if (this.config.timestamp !== false) {
69
86
  const timestamp = entry.timestamp.toISOString();
@@ -111,6 +128,47 @@ var ConsoleTransport = class ConsoleTransport {
111
128
  }
112
129
  };
113
130
 
131
+ //#endregion
132
+ //#region src/transports/buffer-cap.ts
133
+ const DEFAULT_BUFFER_CAP = 1e4;
134
+ const DROP_WARN_INTERVAL_MS$1 = 5e3;
135
+ /**
136
+ * Keeps an in-memory retry buffer bounded. Past `max` the oldest items are
137
+ * dropped, counted, and reported through internalWarn at most every few seconds.
138
+ *
139
+ * `requested` must be an integer of at least 1, otherwise the default is used.
140
+ * A cap below `batchSize` is raised to `batchSize` so one batch always fits.
141
+ */
142
+ var BufferCap = class {
143
+ constructor(name, requested, batchSize, fallback = DEFAULT_BUFFER_CAP) {
144
+ this.name = name;
145
+ this.dropped = 0;
146
+ this.lastWarning = 0;
147
+ let max = fallback;
148
+ if (requested !== void 0) if (Number.isInteger(requested) && requested >= 1) max = requested;
149
+ else internalWarn(`${name}: maxBufferSize ${String(requested)} is not a positive integer; using ${fallback}`);
150
+ this.max = Math.max(max, batchSize);
151
+ }
152
+ /** Drops the oldest items beyond the cap, in place. */
153
+ enforce(items) {
154
+ const excess = items.length - this.max;
155
+ if (excess <= 0) return;
156
+ items.splice(0, excess);
157
+ this.note(excess);
158
+ }
159
+ note(count) {
160
+ this.dropped += count;
161
+ this.report(DROP_WARN_INTERVAL_MS$1);
162
+ }
163
+ report(minGapMs) {
164
+ const now = Date.now();
165
+ if (this.dropped === 0 || now - this.lastWarning < minGapMs) return;
166
+ internalWarn(`${this.name}: buffer full (${this.max}), dropped ${this.dropped} oldest entr${this.dropped === 1 ? "y" : "ies"}`);
167
+ this.dropped = 0;
168
+ this.lastWarning = now;
169
+ }
170
+ };
171
+
114
172
  //#endregion
115
173
  //#region src/transports/database.transport.ts
116
174
  /**
@@ -121,6 +179,80 @@ const POSTGRES_MAX_PARAMS = 65535;
121
179
  const POSTGRES_COLUMNS = 8;
122
180
  const POSTGRES_ROWS_PER_INSERT = Math.floor(POSTGRES_MAX_PARAMS / POSTGRES_COLUMNS);
123
181
  const MYSQL_ROWS_PER_INSERT = 5e3;
182
+ /** Postgres rejects NUL in TEXT and JSONB. */
183
+ function stripNul(value) {
184
+ return typeof value === "string" ? value.split("\0").join("") : value;
185
+ }
186
+ /** JSONB also rejects the \u0000 escape, which JSON.stringify writes for a NUL. */
187
+ function stripJsonNulEscape(value) {
188
+ if (typeof value !== "string") return value;
189
+ return value.replace(/(?<!\\)((?:\\\\)*)\\u0000/g, "$1");
190
+ }
191
+ const PG_DATA_CLASSES = [
192
+ "22",
193
+ "23",
194
+ "54"
195
+ ];
196
+ const MYSQL_DATA_CODES = new Set([
197
+ "ER_DATA_TOO_LONG",
198
+ "ER_TRUNCATED_WRONG_VALUE",
199
+ "ER_TRUNCATED_WRONG_VALUE_FOR_FIELD",
200
+ "ER_BAD_NULL_ERROR",
201
+ "ER_WARN_DATA_OUT_OF_RANGE",
202
+ "ER_DUP_ENTRY",
203
+ "ER_INVALID_JSON_TEXT",
204
+ "ER_INVALID_CHARACTER_STRING"
205
+ ]);
206
+ const MYSQL_DATA_ERRNOS = new Set([
207
+ 1048,
208
+ 1062,
209
+ 1264,
210
+ 1292,
211
+ 1366,
212
+ 1406,
213
+ 3140
214
+ ]);
215
+ const SQLITE_DATA_CODES = new Set([
216
+ "SQLITE_CONSTRAINT",
217
+ "SQLITE_TOOBIG",
218
+ "SQLITE_MISMATCH"
219
+ ]);
220
+ const MONGO_DATA_CODES = new Set([
221
+ 2,
222
+ 121,
223
+ 10334,
224
+ 11e3
225
+ ]);
226
+ function isDataErrorLayer(type, e) {
227
+ const code = e.code;
228
+ switch (type) {
229
+ case "postgresql": return typeof code === "string" && PG_DATA_CLASSES.includes(code.slice(0, 2));
230
+ case "mysql": return typeof code === "string" && MYSQL_DATA_CODES.has(code) || typeof e.errno === "number" && MYSQL_DATA_ERRNOS.has(e.errno);
231
+ case "sqlite": return typeof code === "string" && (SQLITE_DATA_CODES.has(code) || code.startsWith("SQLITE_CONSTRAINT_"));
232
+ case "mongodb": return e.name === "BSONObjectTooLarge" || typeof code === "number" && MONGO_DATA_CODES.has(code);
233
+ default: return false;
234
+ }
235
+ }
236
+ /**
237
+ * True when the database refused the data itself (bad value, constraint, size),
238
+ * which retrying cannot fix. Connection, timeout, auth and unknown errors are
239
+ * not data errors and must be retried, never dropped.
240
+ */
241
+ function isDataError(type, error) {
242
+ let current = error;
243
+ for (let depth = 0; depth < 3 && current && typeof current === "object"; depth += 1) {
244
+ if (isDataErrorLayer(type, current)) return true;
245
+ current = current.cause;
246
+ }
247
+ return false;
248
+ }
249
+ /** A write that stored part of a batch: only `remaining` still needs to be written. */
250
+ var PartialWriteError = class extends Error {
251
+ constructor(message, remaining, cause) {
252
+ super(message, { cause });
253
+ this.remaining = remaining;
254
+ }
255
+ };
124
256
  function chunk(items, size) {
125
257
  const chunks = [];
126
258
  for (let i = 0; i < items.length; i += size) chunks.push(items.slice(i, i + size));
@@ -149,6 +281,7 @@ var DatabaseTransport = class {
149
281
  this.isConnected = false;
150
282
  this.batchSize = config.batchSize || 100;
151
283
  this.flushInterval = config.flushInterval || 5e3;
284
+ this.cap = new BufferCap("DatabaseTransport", config.maxBufferSize, this.batchSize);
152
285
  if (config.filter) this.filter = config.filter;
153
286
  if (config.table) validateSqlIdentifier(config.table, "table");
154
287
  this.setupFlushTimer();
@@ -160,6 +293,7 @@ var DatabaseTransport = class {
160
293
  }
161
294
  addToBatch(entry) {
162
295
  this.batch.push(entry);
296
+ this.cap.enforce(this.batch);
163
297
  }
164
298
  async flush() {
165
299
  if (!this.flushPromise) this.flushPromise = this.drain().finally(() => {
@@ -168,37 +302,97 @@ var DatabaseTransport = class {
168
302
  await this.flushPromise;
169
303
  }
170
304
  /**
171
- * Drains the batch to the database one snapshot at a time. The current batch
172
- * is detached SYNCHRONOUSLY before awaiting the write, so entries appended by
173
- * concurrent write() calls land in a fresh array and are never written twice.
174
- * On write failure the snapshot is restored to the front of the batch for the
175
- * next flush cycle and the loop stops, so a persistently failing DB does not
176
- * hot-spin retrying the same entries.
305
+ * Drains the buffer to the database one batch at a time. A batch is detached
306
+ * SYNCHRONOUSLY before awaiting the write, so entries appended by concurrent
307
+ * write() calls land in a fresh array and are never written twice.
308
+ *
309
+ * A failure that is not a data error (connection, timeout, auth, unknown) puts
310
+ * the batch back at the front of the buffer, bounded by `maxBufferSize`, and
311
+ * stops the loop so a down database is not hammered. A data error means the
312
+ * database refused some entry itself, so that batch alone is split to find and
313
+ * drop the refused entries; see isolate().
177
314
  */
178
315
  async drain() {
179
316
  while (this.batch.length > 0) {
180
- const entriesToFlush = this.batch;
181
- this.batch = [];
317
+ const entries = this.batch.splice(0, this.batchSize);
182
318
  try {
183
- switch (this.config.type) {
184
- case "mongodb":
185
- await this.flushToMongoDB(entriesToFlush);
186
- break;
187
- case "postgresql":
188
- await this.flushToPostgreSQL(entriesToFlush);
189
- break;
190
- case "mysql":
191
- await this.flushToMySQL(entriesToFlush);
192
- break;
193
- case "sqlite":
194
- await this.flushToSQLite(entriesToFlush);
195
- break;
196
- default: throw new Error(`Unsupported database type: ${this.config.type}`);
197
- }
319
+ await this.writeEntries(entries);
198
320
  } catch (error) {
199
321
  internalError("Database flush error", error);
200
- this.batch.unshift(...entriesToFlush);
201
- throw error;
322
+ if (error instanceof PartialWriteError) {
323
+ this.requeue(error.remaining);
324
+ throw error.cause;
325
+ }
326
+ if (!isDataError(this.config.type, error)) {
327
+ this.requeue(entries);
328
+ throw error;
329
+ }
330
+ const state = {
331
+ budget: 2 * this.batchSize,
332
+ rejected: 0,
333
+ requeue: [],
334
+ stopped: false
335
+ };
336
+ await this.isolate(entries, state);
337
+ if (state.rejected > 0) internalWarn(`DatabaseTransport: dropped ${state.rejected} log entr${state.rejected === 1 ? "y" : "ies"} the database rejected`);
338
+ this.requeue(state.requeue);
339
+ if (state.stopped) throw state.error;
340
+ }
341
+ }
342
+ }
343
+ requeue(entries) {
344
+ if (entries.length === 0) return;
345
+ this.batch = entries.concat(this.batch);
346
+ this.cap.enforce(this.batch);
347
+ }
348
+ async writeEntries(entries) {
349
+ switch (this.config.type) {
350
+ case "mongodb":
351
+ await this.flushToMongoDB(entries);
352
+ break;
353
+ case "postgresql":
354
+ await this.flushToPostgreSQL(entries);
355
+ break;
356
+ case "mysql":
357
+ await this.flushToMySQL(entries);
358
+ break;
359
+ case "sqlite":
360
+ await this.flushToSQLite(entries);
361
+ break;
362
+ default: throw new Error(`Unsupported database type: ${this.config.type}`);
363
+ }
364
+ }
365
+ /**
366
+ * `entries` failed with a data error. Splits them in half until the refused
367
+ * entries are alone and drops those. A write that fails with anything but a
368
+ * data error, or running out of the per-flush write budget, stops the search:
369
+ * the entries not yet written go back to the buffer.
370
+ */
371
+ async isolate(entries, state) {
372
+ if (entries.length === 1) {
373
+ state.rejected += 1;
374
+ return;
375
+ }
376
+ const mid = Math.ceil(entries.length / 2);
377
+ for (const half of [entries.slice(0, mid), entries.slice(mid)]) {
378
+ if (state.stopped || state.budget <= 0) {
379
+ state.requeue.push(...half);
380
+ continue;
381
+ }
382
+ state.budget -= 1;
383
+ try {
384
+ await this.writeEntries(half);
385
+ } catch (error) {
386
+ if (error instanceof PartialWriteError) {
387
+ state.requeue.push(...error.remaining);
388
+ state.stopped = true;
389
+ state.error = error.cause;
390
+ } else if (isDataError(this.config.type, error)) await this.isolate(half, state);
391
+ else {
392
+ state.requeue.push(...half);
393
+ state.stopped = true;
394
+ state.error = error;
395
+ }
202
396
  }
203
397
  }
204
398
  }
@@ -323,7 +517,26 @@ var DatabaseTransport = class {
323
517
  appName: entry.appName,
324
518
  environment: entry.environment
325
519
  }));
326
- await collection.insertMany(documents);
520
+ try {
521
+ await collection.insertMany(documents, { ordered: false });
522
+ } catch (error) {
523
+ const raw = error.writeErrors;
524
+ let list = [];
525
+ if (Array.isArray(raw)) list = raw;
526
+ else if (raw) list = [raw];
527
+ const writeErrors = list;
528
+ if (writeErrors.length === 0) throw error;
529
+ const remaining = [];
530
+ let rejected = 0;
531
+ for (const writeError of writeErrors) {
532
+ const entry = typeof writeError.index === "number" ? entries[writeError.index] : void 0;
533
+ if (!entry) throw error;
534
+ if (isDataError("mongodb", writeError)) rejected += 1;
535
+ else remaining.push(entry);
536
+ }
537
+ if (rejected > 0) internalWarn(`DatabaseTransport: dropped ${rejected} log entr${rejected === 1 ? "y" : "ies"} the database rejected`);
538
+ if (remaining.length > 0) throw new PartialWriteError("MongoDB insert partially failed", remaining, error);
539
+ }
327
540
  }
328
541
  async flushToPostgreSQL(entries) {
329
542
  const tableName = this.config.table || "logs";
@@ -336,7 +549,7 @@ var DatabaseTransport = class {
336
549
  entry.traceId,
337
550
  entry.appName,
338
551
  entry.environment
339
- ]), POSTGRES_ROWS_PER_INSERT);
552
+ ].map((value, column) => stripNul(column === 3 ? stripJsonNulEscape(value) : value))), POSTGRES_ROWS_PER_INSERT);
340
553
  const insertChunk = async (rows) => {
341
554
  const query = `
342
555
  INSERT INTO ${tableName}
@@ -496,6 +709,7 @@ var DatabaseTransport = class {
496
709
  internalError("Database flush during close failed; entries remain buffered", error);
497
710
  }
498
711
  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`);
712
+ this.cap.report(0);
499
713
  if (this.connection) switch (this.config.type) {
500
714
  case "mongodb":
501
715
  await this.connection.close();
@@ -552,6 +766,17 @@ var DatabaseTransport = class {
552
766
 
553
767
  //#endregion
554
768
  //#region src/transports/analytics.transport.ts
769
+ /**
770
+ * Thrown by sendBatch() when part of a batch was already delivered. drain()
771
+ * requeues only `unsent`, so delivered entries are not sent twice.
772
+ */
773
+ var PartialBatchError = class extends Error {
774
+ constructor(message, unsent, options) {
775
+ super(message, options);
776
+ this.unsent = unsent;
777
+ this.name = "PartialBatchError";
778
+ }
779
+ };
555
780
  var AnalyticsTransport = class {
556
781
  constructor(name, config) {
557
782
  this.batch = [];
@@ -567,6 +792,7 @@ var AnalyticsTransport = class {
567
792
  this.level = config.level;
568
793
  this.batchSize = this.config.batchSize || 50;
569
794
  this.flushInterval = this.config.flushInterval || 1e4;
795
+ this.cap = new BufferCap(name, config.maxBufferSize, this.batchSize);
570
796
  }
571
797
  async write(entry) {
572
798
  if (!this.isReady) await this.ensureReady();
@@ -576,6 +802,7 @@ var AnalyticsTransport = class {
576
802
  }
577
803
  addToBatch(entry) {
578
804
  this.batch.push(entry);
805
+ this.cap.enforce(this.batch);
579
806
  if (this.batch.length >= (this.config.batchSize || 50)) this.flush().catch((err) => internalError(`${this.name} batch flush failed`, err));
580
807
  else if (!this.batchTimer && this.config.flushInterval) {
581
808
  var _this$batchTimer$unre, _this$batchTimer;
@@ -610,7 +837,8 @@ var AnalyticsTransport = class {
610
837
  await this.sendBatch(entriesToSend);
611
838
  } catch (error) {
612
839
  internalError(`Analytics transport ${this.name} flush failed`, error);
613
- this.batch.unshift(...entriesToSend);
840
+ this.batch.unshift(...error instanceof PartialBatchError ? error.unsent : entriesToSend);
841
+ this.cap.enforce(this.batch);
614
842
  return;
615
843
  }
616
844
  }
@@ -623,25 +851,36 @@ var AnalyticsTransport = class {
623
851
  const MAX_CLOSE_FLUSH_ATTEMPTS = 3;
624
852
  for (let attempt = 0; attempt < MAX_CLOSE_FLUSH_ATTEMPTS && this.batch.length > 0; attempt += 1) await this.flush();
625
853
  if (this.batch.length > 0) internalError(`Analytics transport ${this.name} closing with ${this.batch.length} unflushed entr${this.batch.length === 1 ? "y" : "ies"} after ${MAX_CLOSE_FLUSH_ATTEMPTS} attempts`);
854
+ this.cap.report(0);
626
855
  await this.cleanup();
627
856
  }
628
857
  shouldSkipEntry(entry) {
629
858
  return ["debug", "trace"].includes(entry.level.toLowerCase());
630
859
  }
631
860
  transformEntry(entry) {
632
- const transformed = {
861
+ const core = {
633
862
  timestamp: entry.timestamp.toISOString(),
634
863
  level: entry.level,
635
864
  message: entry.message,
636
- ...entry.data
865
+ context: entry.context,
866
+ traceId: entry.traceId,
867
+ appName: entry.appName,
868
+ environment: entry.environment
637
869
  };
638
- if (entry.context) transformed.context = entry.context;
639
- if (entry.traceId) transformed.traceId = entry.traceId;
640
- if (entry.appName) transformed.appName = entry.appName;
641
- if (entry.environment) transformed.environment = entry.environment;
870
+ const transformed = mergeCoreFields(core, entry.data, this.reservedKeys());
871
+ for (const key of [
872
+ "context",
873
+ "traceId",
874
+ "appName",
875
+ "environment"
876
+ ]) if (!core[key]) delete transformed[key];
642
877
  if (this.config.customProperties) Object.assign(transformed, this.config.customProperties);
643
878
  return transformed;
644
879
  }
880
+ /** Provider-specific field names a payload key must not replace. */
881
+ reservedKeys() {
882
+ return [];
883
+ }
645
884
  /**
646
885
  * Runs initialize() once. A bad config (say, a missing API key) fails every
647
886
  * write with the same error instead of an unhandled rejection at startup.
@@ -661,8 +900,71 @@ var AnalyticsTransport = class {
661
900
 
662
901
  //#endregion
663
902
  //#region src/transports/datadog.transport.ts
903
+ const gzipAsync = promisify(gzip);
904
+ const MAX_LOGS_PER_REQUEST = 1e3;
905
+ const MAX_LOGS_PAYLOAD_BYTES = 5e6;
906
+ const MAX_LOG_BYTES = 1e6;
907
+ const MAX_TRUNCATED_MESSAGE_CHARS = 15e4;
908
+ const TRUNCATED_LOG_KEYS = [
909
+ "ddsource",
910
+ "ddtags",
911
+ "hostname",
912
+ "service",
913
+ "status",
914
+ "timestamp",
915
+ "level",
916
+ "context",
917
+ "traceId",
918
+ "appName",
919
+ "environment",
920
+ "env",
921
+ "version",
922
+ "logger",
923
+ "dd.trace_id",
924
+ "dd.span_id"
925
+ ];
926
+ const STATUS_MAP = {
927
+ fatal: "critical",
928
+ critical: "critical",
929
+ error: "error",
930
+ warn: "warn",
931
+ warning: "warn",
932
+ info: "info",
933
+ debug: "debug",
934
+ trace: "debug",
935
+ verbose: "debug"
936
+ };
937
+ const MAX_UINT64 = (1n << 64n) - 1n;
938
+ const DASHED_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
939
+ const HEX = /^[0-9a-f]+$/i;
940
+ const DIGITS = /^\d+$/;
664
941
  /**
665
- * Sends log entries and metrics to Datadog via the Logs and Metrics HTTP APIs.
942
+ * Datadog correlates on a decimal 64-bit id. Accepts:
943
+ * - a dashed UUID (logixia's default trace id): dashes stripped, then 32-hex;
944
+ * - 32 hex chars (W3C trace id) or 16 hex chars containing a-f: low 64 bits as decimal;
945
+ * - an all-digit string: already decimal (dd-trace's native format), passed through.
946
+ * An all-digit string is ambiguous when it is 16 or 32 long: 32 is read as hex (a
947
+ * decimal id has at most 20 digits), 16 is read as decimal.
948
+ * Anything else yields the raw string for trace ids (`passthrough`) and nothing for span ids.
949
+ */
950
+ function toDatadogId(id, passthrough) {
951
+ if (typeof id !== "string" || id === "") return void 0;
952
+ const fallback = passthrough ? id : void 0;
953
+ const compact = DASHED_UUID.test(id) ? id.replaceAll("-", "") : id;
954
+ const isHexId = HEX.test(compact) && (compact.length === 32 || compact.length === 16);
955
+ if (DIGITS.test(compact) && compact.length !== 32) {
956
+ const value = BigInt(compact);
957
+ return value <= MAX_UINT64 ? value.toString() : fallback;
958
+ }
959
+ if (isHexId) return BigInt(`0x${compact.slice(-16)}`).toString();
960
+ return fallback;
961
+ }
962
+ /** Datadog tags: no commas or whitespace, lowercase, at most 200 chars. */
963
+ function tag(key, value) {
964
+ return `${key}:${value}`.replaceAll(/[\s,]+/g, "_").toLowerCase().slice(0, 200);
965
+ }
966
+ /**
967
+ * Sends log entries and metrics to Datadog via the Logs (v2) and Metrics HTTP APIs.
666
968
  *
667
969
  * The API key is transmitted via the `DD-API-KEY` request header — never in the URL.
668
970
  *
@@ -688,29 +990,68 @@ var DataDogTransport = class extends AnalyticsTransport {
688
990
  }
689
991
  }
690
992
  async sendEntry(entry) {
691
- const promises = [];
692
- if (this.datadogConfig.enableLogs !== false) promises.push(this.sendLog(entry));
693
- if (this.datadogConfig.enableMetrics) promises.push(this.sendMetric(entry));
694
- if (this.datadogConfig.enableTraces && entry.traceId) promises.push(this.sendTrace(entry));
695
- await Promise.all(promises);
993
+ await this.sendBatch([entry]);
696
994
  }
995
+ /**
996
+ * Logs go first, chunk by chunk; metrics follow once every log chunk is out.
997
+ * A failure part-way throws PartialBatchError naming only the entries that were
998
+ * not delivered, so a retry never re-posts a chunk Datadog already accepted.
999
+ */
697
1000
  async sendBatch(entries) {
698
- const promises = [];
699
- if (this.datadogConfig.enableLogs !== false) promises.push(this.sendLogBatch(entries));
700
- if (this.datadogConfig.enableMetrics) promises.push(this.sendMetricBatch(entries));
701
- if (this.datadogConfig.enableTraces) {
702
- const entriesWithTraces = entries.filter((e) => e.traceId);
703
- if (entriesWithTraces.length > 0) promises.push(this.sendTraceBatch(entriesWithTraces));
1001
+ if (this.datadogConfig.enableLogs !== false) await this.sendLogBatch(entries);
1002
+ if (this.datadogConfig.enableMetrics) try {
1003
+ await this.sendMetricBatch(entries);
1004
+ } catch (error) {
1005
+ if (this.datadogConfig.enableLogs === false) throw error;
1006
+ throw new PartialBatchError(error.message, [], { cause: error });
704
1007
  }
705
- await Promise.all(promises);
706
1008
  }
707
1009
  async cleanup() {}
708
- async sendLog(entry) {
709
- await this.sendLogBatch([entry]);
710
- }
711
1010
  async sendLogBatch(entries) {
712
- const payload = { logs: entries.map((entry) => this.transformToDataDogLog(entry)) };
713
- await this.makeLogsRequest("/v1/input", payload);
1011
+ const items = entries.map((entry) => ({
1012
+ entry,
1013
+ json: this.serializeLog(entry)
1014
+ }));
1015
+ const chunks = this.chunkLogs(items);
1016
+ for (let i = 0; i < chunks.length; i += 1) {
1017
+ const chunk$1 = chunks[i];
1018
+ try {
1019
+ await this.makeLogsRequest("/api/v2/logs", `[${chunk$1.map((item) => item.json).join(",")}]`);
1020
+ } catch (error) {
1021
+ if (i === 0) throw error;
1022
+ const unsent = chunks.slice(i).flatMap((c) => c.map((item) => item.entry));
1023
+ throw new PartialBatchError(error.message, unsent, { cause: error });
1024
+ }
1025
+ }
1026
+ }
1027
+ /** Splits into requests of at most 1000 entries and about 5MB of JSON. */
1028
+ chunkLogs(items) {
1029
+ const chunks = [];
1030
+ let current = [];
1031
+ let currentBytes = 2;
1032
+ for (const item of items) {
1033
+ const bytes = Buffer.byteLength(item.json) + 1;
1034
+ if (current.length > 0 && (current.length >= MAX_LOGS_PER_REQUEST || currentBytes + bytes > MAX_LOGS_PAYLOAD_BYTES)) {
1035
+ chunks.push(current);
1036
+ current = [];
1037
+ currentBytes = 2;
1038
+ }
1039
+ current.push(item);
1040
+ currentBytes += bytes;
1041
+ }
1042
+ if (current.length > 0) chunks.push(current);
1043
+ return chunks;
1044
+ }
1045
+ /** Serializes once. A log over 1MB keeps its identity and correlation fields but loses its attributes and most of its message. */
1046
+ serializeLog(entry) {
1047
+ const log = this.transformToDataDogLog(entry);
1048
+ const json = stringifyForLog(log);
1049
+ if (Buffer.byteLength(json) <= MAX_LOG_BYTES) return json;
1050
+ const kept = {};
1051
+ for (const key of TRUNCATED_LOG_KEYS) if (log[key] !== void 0) kept[key] = log[key];
1052
+ kept["message"] = String(log["message"]).slice(0, MAX_TRUNCATED_MESSAGE_CHARS);
1053
+ kept["truncated"] = true;
1054
+ return stringifyForLog(kept);
714
1055
  }
715
1056
  async sendMetric(entry) {
716
1057
  await this.sendMetricBatch([entry]);
@@ -719,32 +1060,53 @@ var DataDogTransport = class extends AnalyticsTransport {
719
1060
  const payload = { series: entries.map((entry) => this.transformToDataDogMetric(entry)) };
720
1061
  await this.makeMetricsRequest("/api/v1/series", payload);
721
1062
  }
722
- async sendTrace(entry) {
723
- await this.sendLog(entry);
724
- }
725
- async sendTraceBatch(entries) {
726
- await this.sendLogBatch(entries);
727
- }
728
1063
  transformToDataDogLog(entry) {
1064
+ var _entry$data, _entry$data2;
729
1065
  const transformed = this.transformEntry(entry);
1066
+ const service = this.datadogConfig.service || "logixia";
1067
+ const env = this.datadogConfig.env || "production";
1068
+ const version = this.datadogConfig.version;
1069
+ const tags = [
1070
+ tag("env", env),
1071
+ tag("service", service),
1072
+ ...version ? [tag("version", version)] : []
1073
+ ];
1074
+ const spanId = toDatadogId(((_entry$data = entry.data) === null || _entry$data === void 0 ? void 0 : _entry$data["spanId"]) ?? ((_entry$data2 = entry.data) === null || _entry$data2 === void 0 ? void 0 : _entry$data2["span_id"]), false);
1075
+ const traceId = toDatadogId(entry.traceId, true);
730
1076
  return {
731
- timestamp: entry.timestamp.toISOString(),
732
- level: this.mapLogLevel(entry.level),
1077
+ ...transformed,
1078
+ ddsource: "nodejs",
1079
+ ddtags: tags.join(","),
1080
+ hostname: this.getHostname(),
1081
+ service,
1082
+ status: this.mapLogLevel(entry.level),
733
1083
  message: entry.message,
734
- service: this.datadogConfig.service || "logixia",
735
- version: this.datadogConfig.version,
736
- env: this.datadogConfig.env || "production",
1084
+ env,
1085
+ version,
737
1086
  logger: {
738
1087
  name: "logixia",
739
1088
  thread_name: "main"
740
1089
  },
741
- ...transformed,
742
- "dd.trace_id": entry.traceId,
743
- "dd.span_id": this.generateSpanId(),
744
- host: this.getHostname(),
745
- source: "nodejs"
1090
+ ...traceId && { "dd.trace_id": traceId },
1091
+ ...traceId && spanId && { "dd.span_id": spanId }
746
1092
  };
747
1093
  }
1094
+ reservedKeys() {
1095
+ return [
1096
+ "status",
1097
+ "ddtags",
1098
+ "ddsource",
1099
+ "hostname",
1100
+ "service",
1101
+ "version",
1102
+ "env",
1103
+ "logger",
1104
+ "host",
1105
+ "source",
1106
+ "dd.trace_id",
1107
+ "dd.span_id"
1108
+ ];
1109
+ }
748
1110
  transformToDataDogMetric(entry) {
749
1111
  return {
750
1112
  metric: `logitron.logs.${entry.level}`,
@@ -752,58 +1114,29 @@ var DataDogTransport = class extends AnalyticsTransport {
752
1114
  type: "count",
753
1115
  host: this.getHostname(),
754
1116
  tags: [
755
- `level:${entry.level}`,
756
- `service:${this.datadogConfig.service || "logixia"}`,
757
- `env:${this.datadogConfig.env || "production"}`,
758
- ...entry.context ? [`context:${entry.context}`] : [],
759
- ...entry.appName ? [`app:${entry.appName}`] : []
1117
+ tag("level", entry.level),
1118
+ tag("service", this.datadogConfig.service || "logixia"),
1119
+ tag("env", this.datadogConfig.env || "production"),
1120
+ ...entry.context ? [tag("context", entry.context)] : [],
1121
+ ...entry.appName ? [tag("app", entry.appName)] : []
760
1122
  ]
761
1123
  };
762
1124
  }
763
- transformToDataDogTrace(entry) {
764
- return {
765
- trace_id: entry.traceId,
766
- span_id: this.generateSpanId(),
767
- parent_id: null,
768
- name: "log.entry",
769
- service: this.datadogConfig.service || "logixia",
770
- resource: entry.message,
771
- type: "log",
772
- start: entry.timestamp.getTime() * 1e6,
773
- duration: 1e6,
774
- meta: {
775
- "log.level": entry.level,
776
- "log.message": entry.message,
777
- ...entry.context && { "log.context": entry.context },
778
- ...entry.data && Object.keys(entry.data).reduce((acc, key) => {
779
- acc[`log.${key}`] = String(entry.data[key]);
780
- return acc;
781
- }, {})
782
- },
783
- metrics: { _sampling_priority_v1: 1 }
784
- };
785
- }
786
1125
  mapLogLevel(level) {
787
- return {
788
- error: "error",
789
- warn: "warn",
790
- warning: "warn",
791
- info: "info",
792
- debug: "debug",
793
- trace: "trace",
794
- verbose: "debug"
795
- }[level.toLowerCase()] || "info";
796
- }
797
- async makeLogsRequest(endpoint, data) {
1126
+ return STATUS_MAP[level.toLowerCase()] || "info";
1127
+ }
1128
+ async makeLogsRequest(endpoint, json) {
798
1129
  const url = `${this.logsUrl}${endpoint}`;
799
1130
  try {
1131
+ const compress = this.datadogConfig.compress === true;
800
1132
  const response = await fetch(url, {
801
1133
  method: "POST",
802
1134
  headers: {
803
1135
  "Content-Type": "application/json",
804
- "DD-API-KEY": this.datadogConfig.apiKey
1136
+ "DD-API-KEY": this.datadogConfig.apiKey,
1137
+ ...compress && { "Content-Encoding": "gzip" }
805
1138
  },
806
- body: stringifyForLog(data)
1139
+ body: compress ? await gzipAsync(json) : json
807
1140
  });
808
1141
  if (!response.ok) throw new Error(`DataDog Logs API error: ${response.status} ${response.statusText}`);
809
1142
  } catch (error) {
@@ -826,9 +1159,6 @@ var DataDogTransport = class extends AnalyticsTransport {
826
1159
  throw new Error(`Failed to send metrics to DataDog: ${error.message}`, { cause: error });
827
1160
  }
828
1161
  }
829
- generateSpanId() {
830
- return Math.floor(Math.random() * 0xffffffffffff).toString(16);
831
- }
832
1162
  getHostname() {
833
1163
  try {
834
1164
  return __require("node:os").hostname();
@@ -884,6 +1214,12 @@ const unlink = promisify(fs.unlink);
884
1214
  const readdir = promisify(fs.readdir);
885
1215
  const stat = promisify(fs.stat);
886
1216
  const DROP_WARN_INTERVAL_MS = 5e3;
1217
+ const DEFAULT_MAX_QUEUE = 1e4;
1218
+ const DEFAULT_FILE_MODE = 384;
1219
+ const DEFAULT_DIR_MODE = 448;
1220
+ function csvField(value) {
1221
+ return `"${stripControlChars(safeToString(value)).replace(/"/g, "\"\"")}"`;
1222
+ }
887
1223
  const SIZE_UNITS = {
888
1224
  "": 1,
889
1225
  k: 1024,
@@ -919,6 +1255,10 @@ function formatDatePattern(date, pattern) {
919
1255
  * rotated files are renamed beside it (`app-2026-10-22T09-30-00-000.log`).
920
1256
  * With a `rotation` block every file, the live one included, is timestamped.
921
1257
  *
1258
+ * Files are created with mode 0o600 and directories with 0o700; override with
1259
+ * `fileMode` and `dirMode`. At most `maxQueue` (default 10000) entries wait in
1260
+ * memory; the rest are dropped per `queuePolicy` and counted in a warning.
1261
+ *
922
1262
  * @example
923
1263
  * transports: { file: { filename: 'app.log', dirname: './logs', maxSize: '50MB' } }
924
1264
  */
@@ -953,8 +1293,10 @@ var FileTransport = class {
953
1293
  this.maxBytes = parseSize(rawMaxSize);
954
1294
  if (rawMaxSize !== void 0 && this.maxBytes === void 0) internalWarn(`FileTransport: maxSize ${JSON.stringify(rawMaxSize)} is not a valid size (try '50MB'); size rotation is off`);
955
1295
  const maxQueue = config.maxQueue;
956
- if (maxQueue !== void 0 && !(Number.isInteger(maxQueue) && maxQueue > 0)) internalWarn(`FileTransport: maxQueue ${String(maxQueue)} is not a positive integer; ignored`);
957
- else this.maxQueue = maxQueue;
1296
+ if (maxQueue !== void 0 && !(Number.isInteger(maxQueue) && maxQueue > 0)) internalWarn(`FileTransport: maxQueue ${String(maxQueue)} is not a positive integer; using ${DEFAULT_MAX_QUEUE}`);
1297
+ this.maxQueue = maxQueue !== void 0 && Number.isInteger(maxQueue) && maxQueue > 0 ? maxQueue : DEFAULT_MAX_QUEUE;
1298
+ this.fileMode = config.fileMode ?? DEFAULT_FILE_MODE;
1299
+ this.dirMode = config.dirMode ?? DEFAULT_DIR_MODE;
958
1300
  this.queuePolicy = config.queuePolicy ?? "drop-oldest";
959
1301
  this.maxFiles = (rotation === null || rotation === void 0 ? void 0 : rotation.maxFiles) ?? config.maxFiles;
960
1302
  this.compress = (rotation === null || rotation === void 0 ? void 0 : rotation.compress) ?? config.zippedArchive ?? false;
@@ -964,7 +1306,10 @@ var FileTransport = class {
964
1306
  this.ext = path$1.extname(config.filename);
965
1307
  this.base = path$1.basename(config.filename, this.ext);
966
1308
  try {
967
- fs.mkdirSync(this.dir, { recursive: true });
1309
+ fs.mkdirSync(this.dir, {
1310
+ recursive: true,
1311
+ mode: this.dirMode
1312
+ });
968
1313
  } catch (error) {
969
1314
  internalError(`FileTransport could not create ${this.dir}`, error);
970
1315
  }
@@ -1032,7 +1377,7 @@ var FileTransport = class {
1032
1377
  this.enqueued += 1;
1033
1378
  }
1034
1379
  queueFull() {
1035
- return this.maxQueue !== void 0 && this.batch.length + this.inFlight >= this.maxQueue;
1380
+ return this.batch.length + this.inFlight >= this.maxQueue;
1036
1381
  }
1037
1382
  roomAvailable() {
1038
1383
  const room = new Promise((resolve) => this.spaceWaiters.push(resolve));
@@ -1112,30 +1457,26 @@ var FileTransport = class {
1112
1457
  }
1113
1458
  formatEntry(entry) {
1114
1459
  switch (this.config.format) {
1115
- case "json": return stringifyForLog({
1460
+ case "json": return stringifyForLog(mergeCoreFields({
1116
1461
  timestamp: entry.timestamp.toISOString(),
1117
1462
  level: entry.level,
1118
- message: entry.message,
1119
- ...entry.data || {}
1120
- });
1121
- case "csv": {
1122
- const safeMessage = safeToString(entry.message);
1123
- return [
1124
- entry.timestamp.toISOString(),
1125
- entry.level,
1126
- `"${safeMessage.replace(/"/g, "\"\"")}"`,
1127
- safeToString(entry.context),
1128
- safeToString(entry.traceId),
1129
- stringifyForLog(entry.data || {})
1130
- ].join(",");
1131
- }
1463
+ message: entry.message
1464
+ }, entry.data));
1465
+ case "csv": return [
1466
+ entry.timestamp.toISOString(),
1467
+ csvField(entry.level),
1468
+ csvField(entry.message),
1469
+ csvField(entry.context),
1470
+ csvField(entry.traceId),
1471
+ csvField(stringifyForLog(entry.data || {}))
1472
+ ].join(",");
1132
1473
  case "text":
1133
- default: return `${entry.timestamp.toISOString()} [${entry.level.toUpperCase()}] ${safeToString(entry.message)}${entry.data ? " " + stringifyForLog(entry.data) : ""}`;
1474
+ default: return `${entry.timestamp.toISOString()} [${stripControlChars(safeToString(entry.level)).toUpperCase()}] ${stripControlChars(safeToString(entry.message))}${entry.data ? " " + stringifyForLog(entry.data) : ""}`;
1134
1475
  }
1135
1476
  }
1136
1477
  addToBatch(entry) {
1137
1478
  this.enqueue(entry);
1138
- const threshold = Math.min(this.config.batchSize || 100, this.maxQueue ?? Infinity);
1479
+ const threshold = Math.min(this.config.batchSize || 100, this.maxQueue);
1139
1480
  if (this.batch.length >= threshold && !this.draining) {
1140
1481
  if (this.batchTimer) {
1141
1482
  clearTimeout(this.batchTimer);
@@ -1173,7 +1514,7 @@ var FileTransport = class {
1173
1514
  if (chunk$1) await this.append(chunk$1, chunkBytes);
1174
1515
  }
1175
1516
  async append(content, bytes = Buffer.byteLength(content)) {
1176
- await appendFile(this.currentFilePath, content);
1517
+ await appendFile(this.currentFilePath, content, { mode: this.fileMode });
1177
1518
  this.bytesWritten += bytes;
1178
1519
  }
1179
1520
  timeToRotate() {
@@ -1267,7 +1608,7 @@ var FileTransport = class {
1267
1608
  async compressFile(filePath) {
1268
1609
  const gzPath = `${filePath}.gz`;
1269
1610
  try {
1270
- await pipeline(fs.createReadStream(filePath), createGzip(), fs.createWriteStream(gzPath));
1611
+ await pipeline(fs.createReadStream(filePath), createGzip(), fs.createWriteStream(gzPath, { mode: this.fileMode }));
1271
1612
  await unlink(filePath);
1272
1613
  } catch (error) {
1273
1614
  internalError("Failed to compress rotated log file", error);
@@ -1351,12 +1692,27 @@ var GoogleAnalyticsTransport = class extends AnalyticsTransport {
1351
1692
  await this.sendEvents(events);
1352
1693
  }
1353
1694
  async cleanup() {}
1695
+ reservedKeys() {
1696
+ return [
1697
+ "event_category",
1698
+ "event_label",
1699
+ "log_level",
1700
+ "log_message",
1701
+ "trace_id",
1702
+ "app_name",
1703
+ "custom_dimension_1",
1704
+ "custom_dimension_2",
1705
+ "custom_dimension_3"
1706
+ ];
1707
+ }
1354
1708
  transformToGAEvent(entry) {
1355
1709
  const transformed = this.transformEntry(entry);
1710
+ const eventCategory = this.mapLogLevelToCategory(entry.level);
1356
1711
  return {
1357
1712
  name: "log_event",
1358
1713
  params: {
1359
- event_category: this.mapLogLevelToCategory(entry.level),
1714
+ ...this.flattenData(transformed),
1715
+ event_category: eventCategory,
1360
1716
  event_label: entry.message,
1361
1717
  log_level: entry.level,
1362
1718
  log_message: entry.message,
@@ -1365,7 +1721,6 @@ var GoogleAnalyticsTransport = class extends AnalyticsTransport {
1365
1721
  ...entry.traceId && { trace_id: entry.traceId },
1366
1722
  ...entry.appName && { app_name: entry.appName },
1367
1723
  ...entry.environment && { environment: entry.environment },
1368
- ...this.flattenData(transformed),
1369
1724
  custom_dimension_1: entry.level,
1370
1725
  custom_dimension_2: entry.context || "default",
1371
1726
  custom_dimension_3: entry.environment || "production"
@@ -1561,12 +1916,21 @@ var MixpanelTransport = class extends AnalyticsTransport {
1561
1916
  await this.trackEvents(events);
1562
1917
  }
1563
1918
  async cleanup() {}
1919
+ reservedKeys() {
1920
+ return [
1921
+ "token",
1922
+ "distinct_id",
1923
+ "time",
1924
+ "$insert_id"
1925
+ ];
1926
+ }
1564
1927
  transformToMixpanelEvent(entry) {
1565
1928
  const transformed = this.transformEntry(entry);
1566
1929
  return {
1567
1930
  name: `log_${entry.level}`,
1568
1931
  properties: {
1569
1932
  ...transformed,
1933
+ token: this.mixpanelConfig.token,
1570
1934
  distinct_id: this.mixpanelConfig.distinct_id || "anonymous",
1571
1935
  time: entry.timestamp.getTime(),
1572
1936
  $insert_id: this.generateInsertId(entry),
@@ -1579,8 +1943,8 @@ var MixpanelTransport = class extends AnalyticsTransport {
1579
1943
  const payload = {
1580
1944
  event: event.name,
1581
1945
  properties: {
1582
- token: this.mixpanelConfig.token,
1583
- ...event.properties
1946
+ ...event.properties,
1947
+ token: this.mixpanelConfig.token
1584
1948
  }
1585
1949
  };
1586
1950
  await this.makeRequest("/track", [payload]);
@@ -1589,8 +1953,8 @@ var MixpanelTransport = class extends AnalyticsTransport {
1589
1953
  const payload = events.map((event) => ({
1590
1954
  event: event.name,
1591
1955
  properties: {
1592
- token: this.mixpanelConfig.token,
1593
- ...event.properties
1956
+ ...event.properties,
1957
+ token: this.mixpanelConfig.token
1594
1958
  }
1595
1959
  }));
1596
1960
  await this.makeRequest("/track", payload);
@@ -1648,11 +2012,11 @@ var MixpanelTransport = class extends AnalyticsTransport {
1648
2012
  const payload = {
1649
2013
  event: eventName,
1650
2014
  properties: {
2015
+ ...properties,
2016
+ ...this.mixpanelConfig.enableSuperProperties ? this.mixpanelConfig.superProperties : {},
1651
2017
  token: this.mixpanelConfig.token,
1652
2018
  distinct_id: this.mixpanelConfig.distinct_id || "anonymous",
1653
- time: Date.now(),
1654
- ...properties,
1655
- ...this.mixpanelConfig.enableSuperProperties ? this.mixpanelConfig.superProperties : {}
2019
+ time: Date.now()
1656
2020
  }
1657
2021
  };
1658
2022
  await this.makeRequest("/track", [payload]);
@@ -1712,6 +2076,9 @@ var SegmentTransport = class extends AnalyticsTransport {
1712
2076
  await this.batchTrack(segmentEvents);
1713
2077
  }
1714
2078
  async cleanup() {}
2079
+ reservedKeys() {
2080
+ return ["logger"];
2081
+ }
1715
2082
  transformToSegmentEvent(entry) {
1716
2083
  const transformed = this.transformEntry(entry);
1717
2084
  return {
@@ -1721,10 +2088,10 @@ var SegmentTransport = class extends AnalyticsTransport {
1721
2088
  anonymousId: this.generateAnonymousId(),
1722
2089
  timestamp: entry.timestamp.toISOString(),
1723
2090
  properties: {
2091
+ ...transformed,
1724
2092
  level: entry.level,
1725
2093
  message: entry.message,
1726
2094
  logger: "logixia",
1727
- ...transformed,
1728
2095
  ...entry.context && { context: entry.context },
1729
2096
  ...entry.traceId && { traceId: entry.traceId },
1730
2097
  ...entry.appName && { appName: entry.appName },
@@ -1780,11 +2147,10 @@ var SegmentTransport = class extends AnalyticsTransport {
1780
2147
  }
1781
2148
  }
1782
2149
  extractUserId(entry) {
1783
- if (entry.data) {
1784
- const d = entry.data;
1785
- const id = d["userId"] ?? d["user_id"] ?? d["id"];
1786
- return typeof id === "string" ? id : void 0;
1787
- }
2150
+ var _entry$data;
2151
+ const field = this.segmentConfig.userIdField ?? "userId";
2152
+ const id = (_entry$data = entry.data) === null || _entry$data === void 0 ? void 0 : _entry$data[field];
2153
+ return typeof id === "string" ? id : void 0;
1788
2154
  }
1789
2155
  generateAnonymousId() {
1790
2156
  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
@@ -2285,5 +2651,5 @@ var TransportManager = class extends EventEmitter {
2285
2651
  };
2286
2652
 
2287
2653
  //#endregion
2288
- export { FileTransport as a, DatabaseTransport as c, GoogleAnalyticsTransport as i, ConsoleTransport as l, SegmentTransport as n, DataDogTransport as o, MixpanelTransport as r, AnalyticsTransport as s, TransportManager as t };
2289
- //# sourceMappingURL=transport.manager-D6KL3t9V.mjs.map
2654
+ export { FileTransport as a, DatabaseTransport as c, stripControlChars as d, GoogleAnalyticsTransport as i, BufferCap as l, SegmentTransport as n, DataDogTransport as o, MixpanelTransport as r, AnalyticsTransport as s, TransportManager as t, ConsoleTransport as u };
2655
+ //# sourceMappingURL=transport.manager-BiuCAJd2.mjs.map