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
package/dist/cli/index.js CHANGED
@@ -31,6 +31,25 @@ commander = __toESM(commander);
31
31
  let picocolors = require("picocolors");
32
32
  picocolors = __toESM(picocolors);
33
33
 
34
+ //#region src/utils/csv.utils.ts
35
+ const CSV_FORMULA_PREFIX = /^[=+\-@\t\r]/;
36
+ /**
37
+ * Render one CSV field. String values that a spreadsheet would treat as a
38
+ * formula (leading `=`, `+`, `-`, `@`, tab or CR) are prefixed with `'`.
39
+ * Objects are JSON-encoded. Fields containing a comma, quote, CR or LF are quoted;
40
+ * `alwaysQuote` quotes every field. `null` / `undefined` render as empty.
41
+ */
42
+ function csvField(value, alwaysQuote = false) {
43
+ if (value === void 0 || value === null) return alwaysQuote ? "\"\"" : "";
44
+ let str;
45
+ if (typeof value === "string") str = CSV_FORMULA_PREFIX.test(value) ? `'${value}` : value;
46
+ else if (typeof value === "object") str = JSON.stringify(value);
47
+ else str = String(value);
48
+ if (alwaysQuote || /[",\r\n]/.test(str)) return `"${str.replace(/"/g, "\"\"")}"`;
49
+ return str;
50
+ }
51
+
52
+ //#endregion
34
53
  //#region src/cli/utils.ts
35
54
  /**
36
55
  * Render a single cell value. Uses a null/undefined check rather than `||` so
@@ -38,12 +57,33 @@ picocolors = __toESM(picocolors);
38
57
  * `statusCode=0` field must not silently display as blank.
39
58
  */
40
59
  function cellText(value) {
41
- return value === null || value === void 0 ? "" : String(value);
60
+ return value === null || value === void 0 ? "" : sanitizeInline(String(value));
61
+ }
62
+ const TERMINAL_UNSAFE = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f\u2028\u2029]/g;
63
+ function escapeChar(ch) {
64
+ const code = ch.charCodeAt(0);
65
+ return code <= 255 ? `\\x${code.toString(16).padStart(2, "0")}` : `\\u${code.toString(16).padStart(4, "0")}`;
66
+ }
67
+ /**
68
+ * Make untrusted log text safe to print: control characters (ESC, CR, BEL, C1
69
+ * codes, ...) are replaced by a visible `\xNN` escape. Tabs and newlines are kept
70
+ * so multi-line output the CLI formats itself is unaffected.
71
+ */
72
+ function sanitizeForTerminal(text) {
73
+ return text.replace(TERMINAL_UNSAFE, escapeChar);
74
+ }
75
+ /** Like {@link sanitizeForTerminal} but also escapes newlines and tabs (single-line cells). */
76
+ function sanitizeInline(text) {
77
+ return sanitizeForTerminal(text).replace(/\n/g, "\\n").replace(/\t/g, "\\t");
78
+ }
79
+ /** Escape regular-expression metacharacters so `text` matches literally. */
80
+ function escapeRegExp(text) {
81
+ return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
42
82
  }
43
83
  function formatAsTable(rows, columns) {
44
- const colWidths = columns.map((c) => Math.max(c.length, ...rows.map((r) => cellText(r[c]).length)));
84
+ const colWidths = columns.map((c) => Math.max(sanitizeInline(c).length, ...rows.map((r) => cellText(r[c]).length)));
45
85
  return [
46
- columns.map((c, i) => c.padEnd(colWidths[i] ?? 0)).join(" | "),
86
+ columns.map((c, i) => sanitizeInline(c).padEnd(colWidths[i] ?? 0)).join(" | "),
47
87
  colWidths.map((w) => "-".repeat(w)).join("-|-"),
48
88
  rows.map((r) => columns.map((c, i) => cellText(r[c]).padEnd(colWidths[i] ?? 0)).join(" | ")).join("\n")
49
89
  ].join("\n");
@@ -90,7 +130,7 @@ async function analyzeFileContents(raw, opts = {}) {
90
130
  });
91
131
  }
92
132
  if (opts.level) filtered = filtered.filter((e) => (e["level"] ?? "").toLowerCase() === opts.level.toLowerCase());
93
- const byLevel = {};
133
+ const byLevel = Object.create(null);
94
134
  for (const e of filtered) {
95
135
  const l = (e.level || "info").toUpperCase();
96
136
  byLevel[l] = (byLevel[l] || 0) + 1;
@@ -112,7 +152,7 @@ const analyzeCommand = new commander.Command("analyze").description("Analyze log
112
152
  }
113
153
  const result = await analyzeFileContents(node_fs.default.readFileSync(full, "utf8"), opts);
114
154
  if (opts.format === "json") {
115
- console.log(JSON.stringify(result, null, 2));
155
+ console.log(sanitizeForTerminal(JSON.stringify(result, null, 2)));
116
156
  return;
117
157
  }
118
158
  console.log(result);
@@ -178,10 +218,10 @@ function formatTime(ts) {
178
218
  if (!ts) return " ";
179
219
  try {
180
220
  const d = new Date(String(ts));
181
- if (Number.isNaN(d.getTime())) return String(ts).slice(0, 12).padEnd(12);
221
+ if (Number.isNaN(d.getTime())) return sanitizeInline(String(ts).slice(0, 12)).padEnd(12);
182
222
  return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}:${String(d.getSeconds()).padStart(2, "0")}.${String(d.getMilliseconds()).padStart(3, "0")}`;
183
223
  } catch {
184
- return String(ts).slice(0, 12).padEnd(12);
224
+ return sanitizeInline(String(ts).slice(0, 12)).padEnd(12);
185
225
  }
186
226
  }
187
227
  function syntaxColorJson(line) {
@@ -204,12 +244,12 @@ function buildDetailLines(entry) {
204
244
  if (!entry) return [picocolors.default.dim(" No entry selected")];
205
245
  const lines = [];
206
246
  const { stack,...rest } = entry;
207
- const jsonText = JSON.stringify(rest, null, 2);
247
+ const jsonText = sanitizeForTerminal(JSON.stringify(rest, null, 2));
208
248
  for (const l of jsonText.split("\n")) lines.push(" " + syntaxColorJson(l));
209
249
  if (stack) {
210
250
  lines.push("");
211
251
  lines.push(picocolors.default.bold(picocolors.default.red(" ▼ STACK TRACE")));
212
- for (const sl of String(stack).split("\n")) {
252
+ for (const sl of sanitizeForTerminal(String(stack)).split("\n")) {
213
253
  if (!sl.trim()) continue;
214
254
  if (sl.trim().startsWith("at ")) lines.push(coloriseStackFrame(sl));
215
255
  else lines.push(" " + picocolors.default.red(sl.trim()));
@@ -367,18 +407,18 @@ var TUIExplorer = class {
367
407
  }
368
408
  const entry = this.filteredEntries[entryIdx];
369
409
  const isSelected = entryIdx === this.selectedIndex;
370
- const level = normalizeLevel(entry.level);
410
+ const level = sanitizeInline(normalizeLevel(entry.level));
371
411
  const time = formatTime(entry.timestamp);
372
- const msg = String(entry.message ?? "");
373
- const extraFields = Object.entries(entry).filter(([k]) => ![
412
+ const msg = sanitizeInline(String(entry.message ?? ""));
413
+ const extraFieldsSafe = sanitizeInline(Object.entries(entry).filter(([k]) => ![
374
414
  "timestamp",
375
415
  "level",
376
416
  "message",
377
417
  "stack"
378
- ].includes(k)).map(([k, v]) => `${k}=${typeof v === "object" ? JSON.stringify(v) : String(v)}`).join(" ");
418
+ ].includes(k)).map(([k, v]) => `${k}=${typeof v === "object" ? JSON.stringify(v) : String(v)}`).join(" "));
379
419
  const msgSlot = Math.floor(msgWidth * .65);
380
420
  const msgPart = this.truncate(msg, msgSlot);
381
- const extraPart = this.truncate(extraFields, msgWidth - msgPart.length - 2);
421
+ const extraPart = this.truncate(extraFieldsSafe, msgWidth - msgPart.length - 2);
382
422
  const msgDisplay = msgPart + (extraPart ? picocolors.default.dim(" " + extraPart) : "");
383
423
  const line = ` ${time} ` + levelBadge(level) + " " + this.pad(msgDisplay, msgWidth);
384
424
  if (isSelected) {
@@ -550,10 +590,10 @@ var TUIExplorer = class {
550
590
  if (ext === ".csv") {
551
591
  const entries = this.filteredEntries;
552
592
  const allKeys = [...new Set(entries.flatMap((e) => Object.keys(e)))];
553
- const header = allKeys.join(",");
593
+ const header = allKeys.map((k) => csvField(k)).join(",");
554
594
  const rows = entries.map((e) => allKeys.map((k) => {
555
595
  const v = e[k];
556
- return `"${(typeof v === "string" ? v : JSON.stringify(v ?? "")).replace(/"/g, "\"\"")}"`;
596
+ return csvField(v, true);
557
597
  }).join(","));
558
598
  node_fs.default.writeFileSync(resolved, [header, ...rows].join("\n"), "utf8");
559
599
  } else if (ext === ".ndjson" || ext === ".jsonl") node_fs.default.writeFileSync(resolved, this.filteredEntries.map((e) => JSON.stringify(e)).join("\n"), "utf8");
@@ -653,15 +693,9 @@ function exportAsCSV(entries, fields) {
653
693
  if (entries.length === 0) return "";
654
694
  const columns = fields || Object.keys(entries[0] || {});
655
695
  const lines = [];
656
- lines.push(columns.join(","));
696
+ lines.push(columns.map((c) => csvField(c)).join(","));
657
697
  for (const entry of entries) {
658
- const row = columns.map((col) => {
659
- const value = entry[col];
660
- if (value === void 0 || value === null) return "";
661
- const str = String(value);
662
- if (str.includes(",") || str.includes("\"") || str.includes("\n")) return `"${str.replace(/"/g, "\"\"")}"`;
663
- return str;
664
- });
698
+ const row = columns.map((col) => csvField(entry[col]));
665
699
  lines.push(row.join(","));
666
700
  }
667
701
  return lines.join("\n");
@@ -728,6 +762,20 @@ function parseTimeRange(value) {
728
762
  const parsed = new Date(value);
729
763
  return Number.isNaN(parsed.getTime()) ? void 0 : parsed;
730
764
  }
765
+ /** Replace the contents of quoted literals with `_` (same length) so clause keywords inside them are ignored. */
766
+ function maskQuoted(sql) {
767
+ let out = "";
768
+ let quote = null;
769
+ for (const ch of sql) if (quote !== null) if (ch === quote) {
770
+ quote = null;
771
+ out += ch;
772
+ } else out += "_";
773
+ else {
774
+ if (ch === "'" || ch === "\"") quote = ch;
775
+ out += ch;
776
+ }
777
+ return out;
778
+ }
731
779
  function parseSQL(sql) {
732
780
  const result = {
733
781
  select: "*",
@@ -737,6 +785,8 @@ function parseSQL(sql) {
737
785
  limit: null
738
786
  };
739
787
  const upper = sql.trim().toUpperCase();
788
+ const masked = maskQuoted(sql.trim());
789
+ const maskedUpper = masked.toUpperCase();
740
790
  {
741
791
  const m = /^COUNT\s+BY\s+(\w+)/i.exec(sql.trim());
742
792
  if (m) {
@@ -771,22 +821,22 @@ function parseSQL(sql) {
771
821
  }
772
822
  }
773
823
  if (upper.startsWith("SELECT")) {
774
- const fromIdx = upper.indexOf(" FROM ");
824
+ const fromIdx = maskedUpper.indexOf(" FROM ");
775
825
  if (fromIdx !== -1) {
776
826
  const selectPart = sql.slice(6, fromIdx + sql.length - upper.length).trim();
777
827
  if (selectPart !== "*") result.select = selectPart.split(",").map((s) => s.trim()).filter(Boolean);
778
828
  }
779
829
  }
780
830
  const remainder = sql.trim();
781
- const whereTokenIdx = remainder.toUpperCase().indexOf("WHERE ");
831
+ const whereTokenIdx = maskedUpper.indexOf("WHERE ");
782
832
  if (whereTokenIdx !== -1) {
783
833
  const afterWhere = remainder.slice(whereTokenIdx + 6).trimStart();
784
- const upperAfter = afterWhere.toUpperCase();
834
+ const upperAfter = maskedUpper.slice(whereTokenIdx + 6).trimStart();
785
835
  const candidates = [upperAfter.indexOf(" ORDER BY "), upperAfter.indexOf(" LIMIT ")].filter((i) => i !== -1);
786
836
  const endIdx = candidates.length > 0 ? Math.min(...candidates) : afterWhere.length;
787
837
  result.where = parseWhereClause(afterWhere.slice(0, endIdx).trim());
788
838
  }
789
- const orderMatch = /ORDER\s+BY\s+(\w+)(?:\s+(ASC|DESC))?/i.exec(remainder);
839
+ const orderMatch = /ORDER\s+BY\s+(\w+)(?:\s+(ASC|DESC))?/i.exec(masked);
790
840
  if (orderMatch) {
791
841
  var _orderMatch$;
792
842
  result.orderBy = {
@@ -794,7 +844,7 @@ function parseSQL(sql) {
794
844
  dir: ((_orderMatch$ = orderMatch[2]) === null || _orderMatch$ === void 0 ? void 0 : _orderMatch$.toUpperCase()) ?? "ASC"
795
845
  };
796
846
  }
797
- const limitMatch = /LIMIT\s+(\d+)/i.exec(remainder);
847
+ const limitMatch = /LIMIT\s+(\d+)/i.exec(masked);
798
848
  if (limitMatch) result.limit = Number.parseInt(limitMatch[1], 10);
799
849
  return result;
800
850
  }
@@ -922,6 +972,21 @@ function parseCondition(expr) {
922
972
  }
923
973
  return null;
924
974
  }
975
+ /** Build a case-insensitive regex for a SQL LIKE pattern; only `%` and `_` are special. */
976
+ function likeToRegExp(pattern) {
977
+ let source = "";
978
+ let lastWasWildcard = false;
979
+ for (const ch of pattern) {
980
+ if (ch === "%") {
981
+ if (!lastWasWildcard) source += ".*";
982
+ lastWasWildcard = true;
983
+ continue;
984
+ }
985
+ lastWasWildcard = false;
986
+ source += ch === "_" ? "." : escapeRegExp(ch);
987
+ }
988
+ return new RegExp(`^${source}$`, "i");
989
+ }
925
990
  function matchesCondition(row, cond) {
926
991
  const rawFieldValue = row[cond.field];
927
992
  if (rawFieldValue === void 0) return false;
@@ -934,14 +999,8 @@ function matchesCondition(row, cond) {
934
999
  case ">=": return Number(rawFieldValue) >= Number(cond.value);
935
1000
  case "<": return Number(rawFieldValue) < Number(cond.value);
936
1001
  case "<=": return Number(rawFieldValue) <= Number(cond.value);
937
- case "LIKE": {
938
- const pattern = condStr.replace(/%/g, ".*").replace(/_/g, ".");
939
- return new RegExp(`^${pattern}$`, "i").test(fieldStr);
940
- }
941
- case "NOT LIKE": {
942
- const pattern = condStr.replace(/%/g, ".*").replace(/_/g, ".");
943
- return !new RegExp(`^${pattern}$`, "i").test(fieldStr);
944
- }
1002
+ case "LIKE": return likeToRegExp(condStr).test(fieldStr);
1003
+ case "NOT LIKE": return !likeToRegExp(condStr).test(fieldStr);
945
1004
  case "IN": return cond.value.some((v) => String(rawFieldValue) === v);
946
1005
  case "NOT IN": return !cond.value.some((v) => String(rawFieldValue) === v);
947
1006
  default: return false;
@@ -961,18 +1020,19 @@ function matchesNode(row, node) {
961
1020
  function runAggregation(entries, agg) {
962
1021
  const { fn, field, groupBy } = agg;
963
1022
  if (!groupBy) return picocolors.default.yellow("Aggregation requires a GROUP BY field");
964
- const groups = {};
1023
+ const groups = /* @__PURE__ */ new Map();
965
1024
  for (const entry of entries) {
966
1025
  const key = String(entry[groupBy] ?? "(empty)");
967
- if (!groups[key]) groups[key] = [];
968
- groups[key].push(entry);
1026
+ const bucket = groups.get(key);
1027
+ if (bucket) bucket.push(entry);
1028
+ else groups.set(key, [entry]);
969
1029
  }
970
- if (fn === "COUNT" || fn === "GROUP") return formatAsTable(Object.entries(groups).map(([k, v]) => ({
1030
+ if (fn === "COUNT" || fn === "GROUP") return formatAsTable([...groups.entries()].map(([k, v]) => ({
971
1031
  [groupBy]: k,
972
1032
  count: v.length
973
1033
  })).sort((a, b) => b.count - a.count), [groupBy, "count"]);
974
1034
  if (!field) return picocolors.default.yellow("Numeric aggregation requires a field: AVG(field) BY group");
975
- return formatAsTable(Object.entries(groups).map(([k, v]) => {
1035
+ return formatAsTable([...groups.entries()].map(([k, v]) => {
976
1036
  const nums = v.map((e) => Number(e[field])).filter((n) => !Number.isNaN(n));
977
1037
  let result;
978
1038
  switch (fn) {
@@ -1012,12 +1072,12 @@ function projectRow(row, select) {
1012
1072
  }
1013
1073
  function formatResults(results, format) {
1014
1074
  if (results.length === 0) return picocolors.default.yellow("No results found");
1015
- if (format === "json") return JSON.stringify(results, null, 2);
1016
- if (format === "ndjson") return results.map((r) => JSON.stringify(r)).join("\n");
1075
+ if (format === "json") return sanitizeForTerminal(JSON.stringify(results, null, 2));
1076
+ if (format === "ndjson") return results.map((r) => sanitizeForTerminal(JSON.stringify(r))).join("\n");
1017
1077
  if (format === "table") return formatAsTable(results, Object.keys(results[0] || {}).slice(0, 6));
1018
1078
  return results.map((r) => {
1019
1079
  const level = (r.level || "").toUpperCase();
1020
- const line = JSON.stringify(r);
1080
+ const line = sanitizeForTerminal(JSON.stringify(r));
1021
1081
  if (level === "ERROR" || level === "CRITICAL") return picocolors.default.red(line);
1022
1082
  if (level === "WARN") return picocolors.default.yellow(line);
1023
1083
  if (level === "DEBUG" || level === "VERBOSE") return picocolors.default.gray(line);
@@ -1048,36 +1108,39 @@ function executeQuery(entries, opts) {
1048
1108
  results: [],
1049
1109
  aggregationOutput: runAggregation(rows, query.aggregation)
1050
1110
  };
1051
- rows = rows.map((row) => projectRow(row, query.select));
1052
1111
  const orderField = ((_query$orderBy = query.orderBy) === null || _query$orderBy === void 0 ? void 0 : _query$orderBy.field) ?? opts.orderBy;
1053
1112
  if (orderField) {
1054
1113
  var _query$orderBy2;
1055
1114
  const dir = ((_query$orderBy2 = query.orderBy) === null || _query$orderBy2 === void 0 ? void 0 : _query$orderBy2.dir) ?? "ASC";
1056
- rows.sort((a, b) => {
1115
+ rows = [...rows].sort((a, b) => {
1057
1116
  const av = a[orderField];
1058
1117
  const bv = b[orderField];
1059
- if (av === void 0 && bv === void 0) return 0;
1060
- if (av === void 0) return dir === "ASC" ? 1 : -1;
1061
- if (bv === void 0) return dir === "ASC" ? -1 : 1;
1118
+ const aMissing = av === void 0 || av === null;
1119
+ const bMissing = bv === void 0 || bv === null;
1120
+ if (aMissing && bMissing) return 0;
1121
+ if (aMissing) return 1;
1122
+ if (bMissing) return -1;
1062
1123
  if (typeof av === "number" && typeof bv === "number") return dir === "ASC" ? av - bv : bv - av;
1063
1124
  return dir === "ASC" ? String(av).localeCompare(String(bv)) : String(bv).localeCompare(String(av));
1064
1125
  });
1065
1126
  }
1066
1127
  const limitN = query.limit ?? opts.limit;
1067
- if (limitN && limitN > 0) rows = rows.slice(0, limitN);
1128
+ if (typeof limitN === "number" && limitN >= 0) rows = rows.slice(0, limitN);
1129
+ rows = rows.map((row) => projectRow(row, query.select));
1068
1130
  return {
1069
1131
  results: rows,
1070
1132
  aggregationOutput: null
1071
1133
  };
1072
1134
  }
1073
- const queryCommand = new commander.Command("query").description("Query log files with SQL-like syntax\n\n Examples:\n logixia query app.log --sql \"SELECT * FROM logs WHERE level='error'\"\n logixia query app.log --sql \"COUNT BY level\"\n logixia query app.log --sql \"AVG(duration) BY endpoint\"\n logixia query app.log --since \"last 2 hours\" --format table\n logixia query app.log --follow --sql \"WHERE level='error'\"\n").argument("<file>", "Path to NDJSON or JSON log file").option("--sql <query>", "SQL-like query string").option("--since <time>", "Only include entries after this time (e.g. \"last 2 hours\", \"today\")").option("--until <time>", "Only include entries before this time").option("--limit <n>", "Maximum number of results to return", "0").option("--order-by <field>", "Sort results by this field").option("--format <fmt>", "Output format: line, table, json, ndjson", "line").option("--follow", "Follow file for new entries matching the query (tail mode)", false).action(async (file, opts) => {
1135
+ const queryCommand = new commander.Command("query").description("Query log files with SQL-like syntax\n\n Examples:\n logixia query app.log --sql \"SELECT * FROM logs WHERE level='error'\"\n logixia query app.log --sql \"COUNT BY level\"\n logixia query app.log --sql \"AVG(duration) BY endpoint\"\n logixia query app.log --since \"last 2 hours\" --format table\n logixia query app.log --follow --sql \"WHERE level='error'\"\n").argument("<file>", "Path to NDJSON or JSON log file").option("--sql <query>", "SQL-like query string").option("--since <time>", "Only include entries after this time (e.g. \"last 2 hours\", \"today\")").option("--until <time>", "Only include entries before this time").option("--limit <n>", "Maximum number of results to return").option("--order-by <field>", "Sort results by this field").option("--format <fmt>", "Output format: line, table, json, ndjson", "line").option("--follow", "Follow file for new entries matching the query (tail mode)", false).action(async (file, opts) => {
1074
1136
  const full = node_path.default.resolve(process.cwd(), file);
1075
1137
  if (!node_fs.default.existsSync(full)) {
1076
1138
  console.error(picocolors.default.red(`File not found: ${full}`));
1077
1139
  process.exit(2);
1078
1140
  }
1079
1141
  const entries = safeParseLogs(node_fs.default.readFileSync(full, "utf8"));
1080
- const limitN = opts.limit ? Number.parseInt(opts.limit, 10) : 0;
1142
+ const parsedLimit = opts.limit === void 0 ? NaN : Number.parseInt(opts.limit, 10);
1143
+ const limitN = Number.isNaN(parsedLimit) ? void 0 : parsedLimit;
1081
1144
  const { results, aggregationOutput } = executeQuery(entries, {
1082
1145
  sql: opts.sql,
1083
1146
  since: opts.since,
@@ -1115,7 +1178,7 @@ const queryCommand = new commander.Command("query").description("Query log files
1115
1178
  });
1116
1179
  if (tailResults.length > 0) console.log(formatResults(tailResults, opts.format || "line"));
1117
1180
  } catch {
1118
- if (!opts.sql) console.log(line);
1181
+ if (!opts.sql) console.log(sanitizeForTerminal(line));
1119
1182
  }
1120
1183
  });
1121
1184
  rs.on("end", () => {
@@ -1148,14 +1211,14 @@ async function searchLogs(raw, opts = {}) {
1148
1211
  });
1149
1212
  }
1150
1213
  function formatSearchResults(results, format, _context) {
1151
- if (format === "json") return JSON.stringify(results, null, 2);
1214
+ if (format === "json") return sanitizeForTerminal(JSON.stringify(results, null, 2));
1152
1215
  if (format === "table") {
1153
1216
  if (results.length === 0) return picocolors.default.yellow("No results found");
1154
1217
  return formatAsTable(results, Object.keys(results[0] || {}).slice(0, 4));
1155
1218
  }
1156
1219
  if (results.length === 0) return picocolors.default.yellow("No results found");
1157
1220
  return results.map((r, idx) => {
1158
- const line = JSON.stringify(r);
1221
+ const line = sanitizeForTerminal(JSON.stringify(r));
1159
1222
  return `${picocolors.default.gray(String(idx + 1) + ":")} ${line}`;
1160
1223
  }).join("\n");
1161
1224
  }
@@ -1179,11 +1242,11 @@ const searchCommand = new commander.Command("search").description("Search log fi
1179
1242
  async function generateStats(raw, opts = {}) {
1180
1243
  const entries = safeParseLogs(raw);
1181
1244
  const group = opts.groupBy || "level";
1182
- const agg = {};
1245
+ const agg = Object.create(null);
1183
1246
  let minTime = null;
1184
1247
  let maxTime = null;
1185
1248
  for (const e of entries) {
1186
- const key = (e[group] || e.level || "unknown").toString().toUpperCase();
1249
+ const key = String(e[group] || e.level || "unknown").toUpperCase();
1187
1250
  agg[key] = (agg[key] || 0) + 1;
1188
1251
  if (e.timestamp) {
1189
1252
  const ts = new Date(e.timestamp);
@@ -1204,14 +1267,15 @@ async function generateStats(raw, opts = {}) {
1204
1267
  function formatStatsOutput(stats, filename) {
1205
1268
  const s = stats;
1206
1269
  const lines = [];
1207
- lines.push(picocolors.default.bold(`\nLog Statistics for ${node_path.default.basename(filename)}`));
1270
+ lines.push(picocolors.default.bold(`\nLog Statistics for ${sanitizeInline(node_path.default.basename(filename))}`));
1208
1271
  lines.push(picocolors.default.bold("=".repeat(50)));
1209
1272
  lines.push(picocolors.default.cyan(`Total Entries: ${s["total"].toLocaleString()}`));
1210
1273
  if (s["timeRange"]) lines.push(picocolors.default.cyan(`Time Range: ${s["timeRange"]["start"].toISOString()} - ${s["timeRange"]["end"].toISOString()}`));
1211
1274
  lines.push("");
1212
- lines.push(picocolors.default.bold(`${s["groupBy"].charAt(0).toUpperCase() + s["groupBy"].slice(1)} Distribution:`));
1275
+ lines.push(picocolors.default.bold(`${sanitizeInline(s["groupBy"].charAt(0).toUpperCase() + s["groupBy"].slice(1))} Distribution:`));
1213
1276
  const sorted = Object.entries(s["distribution"]).sort(([, a], [, b]) => b - a);
1214
- for (const [key, count] of sorted) {
1277
+ for (const [rawKey, count] of sorted) {
1278
+ const key = sanitizeInline(rawKey);
1215
1279
  const percentage = (count / s["total"] * 100).toFixed(1);
1216
1280
  const bar = "█".repeat(Math.floor(count / s["total"] * 30));
1217
1281
  let colorFn = picocolors.default.white;
@@ -1231,7 +1295,7 @@ const statsCommand = new commander.Command("stats").description("Show statistics
1231
1295
  process.exit(2);
1232
1296
  }
1233
1297
  const stats = await generateStats(node_fs.default.readFileSync(full, "utf8"), opts);
1234
- if (opts.format === "json") console.log(JSON.stringify(stats, null, 2));
1298
+ if (opts.format === "json") console.log(sanitizeForTerminal(JSON.stringify(stats, null, 2)));
1235
1299
  else console.log(formatStatsOutput(stats, file));
1236
1300
  });
1237
1301
 
@@ -1256,17 +1320,18 @@ function applyFilter(line, filter) {
1256
1320
  return line.toLowerCase().includes(filter.value.toLowerCase());
1257
1321
  }
1258
1322
  }
1259
- function highlightLine(line, highlight) {
1323
+ function highlightLine(rawLine, highlight) {
1324
+ const line = sanitizeForTerminal(rawLine);
1260
1325
  if (!highlight) return line;
1261
1326
  try {
1262
- const level = (JSON.parse(line).level || "").toUpperCase();
1327
+ const level = (JSON.parse(rawLine).level || "").toUpperCase();
1263
1328
  if (level === "ERROR") return picocolors.default.red(line);
1264
1329
  if (level === "WARN") return picocolors.default.yellow(line);
1265
1330
  if (level === "INFO") return picocolors.default.blue(line);
1266
1331
  if (level === "DEBUG") return picocolors.default.gray(line);
1267
1332
  return line;
1268
1333
  } catch {
1269
- const regex = new RegExp(highlight, "gi");
1334
+ const regex = new RegExp(escapeRegExp(sanitizeForTerminal(highlight)), "gi");
1270
1335
  return line.replace(regex, (match) => picocolors.default.bgYellow(picocolors.default.black(match)));
1271
1336
  }
1272
1337
  }
@@ -1,3 +1,3 @@
1
- import "./transport.types-B5vcyv2k.mjs";
2
- import { n as CloudWatchTransportConfig, t as CloudWatchTransport } from "./cloudwatch.transport-CGHY4buE.mjs";
1
+ import "./transport.types-BMnurPcc.mjs";
2
+ import { n as CloudWatchTransportConfig, t as CloudWatchTransport } from "./cloudwatch.transport-UlyZFWMn.mjs";
3
3
  export { CloudWatchTransport, CloudWatchTransportConfig };
@@ -1,3 +1,3 @@
1
- import "./transport.types-BuY84TwO.js";
2
- import { n as CloudWatchTransportConfig, t as CloudWatchTransport } from "./cloudwatch.transport-DK_8cKd3.js";
1
+ import "./transport.types-CGoHpWQR.js";
2
+ import { n as CloudWatchTransportConfig, t as CloudWatchTransport } from "./cloudwatch.transport-DsEq633o.js";
3
3
  export { CloudWatchTransport, CloudWatchTransportConfig };
@@ -1,6 +1,7 @@
1
1
  require('./internal-log-BUJYMZbE.js');
2
2
  require('./safe-stringify-DWo3aZp_.js');
3
3
  require('./batch-buffer-BY-mRWIK.js');
4
- const require_cloudwatch_transport = require('./cloudwatch.transport-pNBNWJLt.js');
4
+ const require_cloudwatch_transport = require('./cloudwatch.transport-BO1ahxw-.js');
5
+ require('./entry-fields-BvOLFlJ4.js');
5
6
 
6
7
  exports.CloudWatchTransport = require_cloudwatch_transport.CloudWatchTransport;
@@ -1,6 +1,7 @@
1
1
  import "./internal-log-Cm8SOWSV.mjs";
2
2
  import "./safe-stringify-DJ66tW5I.mjs";
3
3
  import "./batch-buffer-PGvSgbmd.mjs";
4
- import { t as CloudWatchTransport } from "./cloudwatch.transport-yBwm4OeO.mjs";
4
+ import { t as CloudWatchTransport } from "./cloudwatch.transport-DZ6ies7g.mjs";
5
+ import "./entry-fields-BJkuPnIg.mjs";
5
6
 
6
7
  export { CloudWatchTransport };
@@ -2,6 +2,7 @@ const require_chunk = require('./chunk-nOFOJqeH.js');
2
2
  const require_internal_log = require('./internal-log-BUJYMZbE.js');
3
3
  const require_safe_stringify = require('./safe-stringify-DWo3aZp_.js');
4
4
  const require_batch_buffer = require('./batch-buffer-BY-mRWIK.js');
5
+ const require_entry_fields = require('./entry-fields-BvOLFlJ4.js');
5
6
  let node_fs = require("node:fs");
6
7
  node_fs = require_chunk.__toESM(node_fs);
7
8
  let node_os = require("node:os");
@@ -202,6 +203,7 @@ function signV4(opts) {
202
203
  //#endregion
203
204
  //#region src/transports/cloudwatch.transport.ts
204
205
  const MAX_EVENTS_PER_PUT = 1e4;
206
+ const EMF_MAX_METRICS = 100;
205
207
  /** Error types worth retrying even though CloudWatch reports them as HTTP 400. */
206
208
  const RETRYABLE_TYPES = new Set([
207
209
  "ThrottlingException",
@@ -237,6 +239,7 @@ var CloudWatchTransport = class {
237
239
  this.emfNamespace = config.emfNamespace ?? "Logixia";
238
240
  this.flushInterval = config.flushIntervalMs ?? 5e3;
239
241
  this.emf = config.emf ?? false;
242
+ this.emfMetrics = config.emfMetrics ? new Set(config.emfMetrics) : void 0;
240
243
  this.level = config.level;
241
244
  this.requestTimeoutMs = config.requestTimeoutMs ?? require_batch_buffer.DEFAULT_REQUEST_TIMEOUT_MS;
242
245
  this.buffer = new require_batch_buffer.BatchBuffer({
@@ -277,25 +280,29 @@ var CloudWatchTransport = class {
277
280
  }
278
281
  toEmf(entry) {
279
282
  const { level, message, data: payload, timestamp } = entry;
280
- const numericFields = {};
281
- for (const [k, v] of Object.entries(payload ?? {})) if (typeof v === "number") numericFields[k] = v;
282
- return require_safe_stringify.stringifyForLog({
283
- _aws: {
284
- Timestamp: timestamp ? new Date(timestamp).getTime() : Date.now(),
285
- CloudWatchMetrics: Object.keys(numericFields).length > 0 ? [{
286
- Namespace: this.emfNamespace,
287
- Dimensions: [["level"]],
288
- Metrics: Object.keys(numericFields).map((n) => ({
289
- Name: n,
290
- Unit: "None"
291
- }))
292
- }] : []
293
- },
283
+ const fields = require_entry_fields.mergeCoreFields({
284
+ _aws: void 0,
294
285
  level,
295
- message,
296
- ...payload,
297
- ...numericFields
298
- });
286
+ message
287
+ }, payload);
288
+ const metricNames = [];
289
+ for (const [k, v] of Object.entries(fields)) {
290
+ if (k === "_aws" || k === "level" || k === "message" || typeof v !== "number") continue;
291
+ if (this.emfMetrics && !this.emfMetrics.has(k)) continue;
292
+ if (metricNames.length < EMF_MAX_METRICS) metricNames.push(k);
293
+ }
294
+ fields["_aws"] = {
295
+ Timestamp: timestamp ? new Date(timestamp).getTime() : Date.now(),
296
+ CloudWatchMetrics: metricNames.length > 0 ? [{
297
+ Namespace: this.emfNamespace,
298
+ Dimensions: [["level"]],
299
+ Metrics: metricNames.map((n) => ({
300
+ Name: n,
301
+ Unit: "None"
302
+ }))
303
+ }] : []
304
+ };
305
+ return require_safe_stringify.stringifyForLog(fields);
299
306
  }
300
307
  async putLogEvents(events) {
301
308
  await this.ensureStream();
@@ -367,4 +374,4 @@ Object.defineProperty(exports, 'CloudWatchTransport', {
367
374
  return CloudWatchTransport;
368
375
  }
369
376
  });
370
- //# sourceMappingURL=cloudwatch.transport-pNBNWJLt.js.map
377
+ //# sourceMappingURL=cloudwatch.transport-BO1ahxw-.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloudwatch.transport-BO1ahxw-.js","names":["out: Record<string, Record<string, string>>","section: Record<string, string> | undefined","path","os","text: string","fs","headers: Record<string, string>","httpRequest","crypto","headers: Record<string, string>","HttpError","DEFAULT_REQUEST_TIMEOUT_MS","BatchBuffer","DEFAULT_MAX_BUFFER_SIZE","isPermanentHttpError","stringifyForLog","mergeCoreFields","metricNames: string[]","httpRequest"],"sources":["../src/transports/aws-credentials.ts","../src/transports/aws-sigv4.ts","../src/transports/cloudwatch.transport.ts"],"sourcesContent":["/**\n * AWS credential resolution without the SDK.\n *\n * Order: explicit config, environment variables, shared credentials file,\n * ECS/container credentials, EC2 instance metadata (IMDSv2).\n * Not supported: SSO, assume-role profiles, credential_process, web identity.\n */\n\nimport * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\n\nimport { internalWarn } from '../utils/internal-log';\nimport { type AwsCredentials } from './aws-sigv4';\nimport { httpRequest } from './batch-buffer';\n\nconst REFRESH_MARGIN_MS = 5 * 60 * 1000;\nconst FAILURE_BACKOFF_MS = 30 * 1000;\nconst METADATA_TIMEOUT_MS = 2000;\n\nexport interface AwsCredentialConfig {\n accessKeyId?: string | undefined;\n secretAccessKey?: string | undefined;\n sessionToken?: string | undefined;\n}\n\n/** Parse the sections of an AWS ini file into `{ section: { key: value } }`. */\nexport function parseIni(text: string): Record<string, Record<string, string>> {\n const out: Record<string, Record<string, string>> = {};\n let section: Record<string, string> | undefined;\n for (const raw of text.split(/\\r?\\n/)) {\n const line = raw.trim();\n if (!line || line.startsWith('#') || line.startsWith(';')) continue;\n const header = /^\\[(.+)\\]$/.exec(line);\n if (header) {\n section = out[header[1]!.trim()] = out[header[1]!.trim()] ?? {};\n continue;\n }\n const eq = line.indexOf('=');\n if (eq > 0 && section) section[line.slice(0, eq).trim()] = line.slice(eq + 1).trim();\n }\n return out;\n}\n\nfunction fromEnv(): AwsCredentials | undefined {\n const accessKeyId = process.env['AWS_ACCESS_KEY_ID'];\n const secretAccessKey = process.env['AWS_SECRET_ACCESS_KEY'];\n if (!accessKeyId || !secretAccessKey) return undefined;\n return { accessKeyId, secretAccessKey, sessionToken: process.env['AWS_SESSION_TOKEN'] };\n}\n\nfunction fromSharedFile(): AwsCredentials | undefined {\n const file =\n process.env['AWS_SHARED_CREDENTIALS_FILE'] ?? path.join(os.homedir(), '.aws', 'credentials');\n const profile = process.env['AWS_PROFILE'] ?? 'default';\n let text: string;\n try {\n text = fs.readFileSync(file, 'utf8');\n } catch {\n return undefined;\n }\n const section = parseIni(text)[profile];\n const accessKeyId = section?.['aws_access_key_id'];\n const secretAccessKey = section?.['aws_secret_access_key'];\n if (!accessKeyId || !secretAccessKey) return undefined;\n return { accessKeyId, secretAccessKey, sessionToken: section?.['aws_session_token'] };\n}\n\ninterface CredentialDocument {\n AccessKeyId?: string;\n SecretAccessKey?: string;\n Token?: string;\n Expiration?: string;\n}\n\nfunction fromDocument(raw: string): AwsCredentials | undefined {\n const doc = JSON.parse(raw) as CredentialDocument;\n if (!doc.AccessKeyId || !doc.SecretAccessKey) return undefined;\n const expiresAt = doc.Expiration ? Date.parse(doc.Expiration) : Number.NaN;\n return {\n accessKeyId: doc.AccessKeyId,\n secretAccessKey: doc.SecretAccessKey,\n sessionToken: doc.Token,\n expiresAt: Number.isNaN(expiresAt) ? undefined : expiresAt,\n };\n}\n\nconst CONTAINER_HOSTS = new Set([\n // eslint-disable-next-line sonarjs/no-hardcoded-ip -- fixed ECS credentials endpoint\n '169.254.170.2',\n // eslint-disable-next-line sonarjs/no-hardcoded-ip -- fixed EKS pod identity endpoint\n '169.254.170.23',\n '[fd00:ec2::23]',\n 'localhost',\n '[::1]',\n]);\n\n/** Same rule as the AWS SDKs: never send the container token to an arbitrary http host. */\nfunction isAllowedContainerHost(url: URL): boolean {\n if (url.protocol === 'https:') return true;\n if (url.protocol !== 'http:') return false;\n return CONTAINER_HOSTS.has(url.hostname) || /^127(?:\\.\\d{1,3}){3}$/.test(url.hostname);\n}\n\nasync function fromContainer(): Promise<AwsCredentials | undefined> {\n const relative = process.env['AWS_CONTAINER_CREDENTIALS_RELATIVE_URI'];\n const full = process.env['AWS_CONTAINER_CREDENTIALS_FULL_URI'];\n if (!relative && !full) return undefined;\n const url = new URL(relative ? `http://169.254.170.2${relative}` : (full as string));\n if (!relative && !isAllowedContainerHost(url)) {\n internalWarn(\n `ignoring AWS_CONTAINER_CREDENTIALS_FULL_URI: ${url.host} is not https or a loopback / ECS / EKS address`\n );\n return undefined;\n }\n const headers: Record<string, string> = {};\n const tokenFile = process.env['AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE'];\n const token = tokenFile\n ? fs.readFileSync(tokenFile, 'utf8').trim()\n : process.env['AWS_CONTAINER_AUTHORIZATION_TOKEN'];\n if (token) headers['Authorization'] = token;\n const res = await httpRequest({\n url,\n method: 'GET',\n headers,\n timeoutMs: METADATA_TIMEOUT_MS,\n label: 'AWS container credentials',\n });\n return fromDocument(res.body);\n}\n\nasync function fromInstanceMetadata(): Promise<AwsCredentials | undefined> {\n if (process.env['AWS_EC2_METADATA_DISABLED'] === 'true') return undefined;\n const base = process.env['AWS_EC2_METADATA_SERVICE_ENDPOINT'] ?? 'http://169.254.169.254';\n const get = (p: string, method: string, headers: Record<string, string>) =>\n httpRequest({\n url: new URL(p, base),\n method,\n headers,\n timeoutMs: METADATA_TIMEOUT_MS,\n label: 'EC2 instance metadata',\n });\n const session = await get('/latest/api/token', 'PUT', {\n 'X-aws-ec2-metadata-token-ttl-seconds': '21600',\n });\n const auth = { 'X-aws-ec2-metadata-token': session.body.trim() };\n const roles = await get('/latest/meta-data/iam/security-credentials/', 'GET', auth);\n const role = roles.body.split('\\n')[0]?.trim();\n if (!role) return undefined;\n const creds = await get(\n `/latest/meta-data/iam/security-credentials/${encodeURIComponent(role)}`,\n 'GET',\n auth\n );\n return fromDocument(creds.body);\n}\n\nexport class AwsCredentialProvider {\n private cached: AwsCredentials | undefined;\n private failedAt = 0;\n private readonly explicit: AwsCredentials | undefined;\n\n constructor(config: AwsCredentialConfig) {\n if (config.accessKeyId && config.secretAccessKey) {\n this.explicit = {\n accessKeyId: config.accessKeyId,\n secretAccessKey: config.secretAccessKey,\n sessionToken: config.sessionToken,\n };\n }\n }\n\n /** Throws when no source yields credentials, so the caller keeps its buffer. */\n async get(): Promise<AwsCredentials> {\n if (this.explicit) return this.explicit;\n const env = fromEnv();\n if (env) return env;\n if (this.cached && !this.isExpiring(this.cached)) return this.cached;\n if (Date.now() - this.failedAt < FAILURE_BACKOFF_MS) {\n throw new Error('no AWS credentials found (retrying shortly)');\n }\n\n let found = fromSharedFile();\n for (const source of [fromContainer, fromInstanceMetadata]) {\n if (found) break;\n try {\n found = await source();\n } catch {\n /* source not available here; try the next */\n }\n }\n if (!found) {\n this.failedAt = Date.now();\n throw new Error(\n 'no AWS credentials found: set AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY, a shared credentials file, container credentials, or run on EC2 with an instance role'\n );\n }\n this.cached = found;\n return found;\n }\n\n invalidate(): void {\n this.cached = undefined;\n this.failedAt = 0;\n }\n\n private isExpiring(c: AwsCredentials): boolean {\n return c.expiresAt !== undefined && c.expiresAt - Date.now() < REFRESH_MARGIN_MS;\n }\n}\n","/**\n * AWS Signature Version 4 request signing, dependency-free.\n */\n\nimport * as crypto from 'node:crypto';\n\nexport interface AwsCredentials {\n accessKeyId: string;\n secretAccessKey: string;\n sessionToken?: string | undefined;\n /** Epoch ms after which the credentials are no longer valid. */\n expiresAt?: number | undefined;\n}\n\nexport interface SignV4Options {\n method: string;\n path: string;\n query?: string;\n /** Must include `host`. Names are lowercased before signing. */\n headers: Record<string, string>;\n body: string;\n region: string;\n service: string;\n credentials: AwsCredentials;\n date?: Date;\n}\n\nconst sha256Hex = (data: string): string => crypto.createHash('sha256').update(data).digest('hex');\n\nconst hmac = (key: Buffer | string, msg: string): Buffer =>\n crypto.createHmac('sha256', key).update(msg).digest();\n\n/** Returns the request headers (lowercase names) with `x-amz-date` and `authorization` added. */\nexport function signV4(opts: SignV4Options): Record<string, string> {\n const {\n method,\n path,\n query = '',\n body,\n region,\n service,\n credentials,\n headers: input,\n date,\n } = opts;\n const amzDate = (date ?? new Date()).toISOString().replace(/[:-]|\\.\\d{3}/g, '');\n const dateStamp = amzDate.slice(0, 8);\n\n const headers: Record<string, string> = {};\n for (const [k, v] of Object.entries(input)) headers[k.toLowerCase()] = v;\n headers['x-amz-date'] = amzDate;\n if (credentials.sessionToken) headers['x-amz-security-token'] = credentials.sessionToken;\n\n const names = Object.keys(headers).sort();\n const canonicalHeaders = names\n .map((n) => `${n}:${headers[n]!.trim().replace(/\\s+/g, ' ')}\\n`)\n .join('');\n const signedHeaders = names.join(';');\n\n const canonicalRequest = [\n method,\n path,\n query,\n canonicalHeaders,\n signedHeaders,\n sha256Hex(body),\n ].join('\\n');\n const scope = `${dateStamp}/${region}/${service}/aws4_request`;\n const stringToSign = ['AWS4-HMAC-SHA256', amzDate, scope, sha256Hex(canonicalRequest)].join('\\n');\n\n const signingKey = hmac(\n hmac(hmac(hmac(`AWS4${credentials.secretAccessKey}`, dateStamp), region), service),\n 'aws4_request'\n );\n const signature = crypto.createHmac('sha256', signingKey).update(stringToSign).digest('hex');\n\n headers['authorization'] =\n `AWS4-HMAC-SHA256 Credential=${credentials.accessKeyId}/${scope}, ` +\n `SignedHeaders=${signedHeaders}, Signature=${signature}`;\n return headers;\n}\n","/**\n * logixia — AWS CloudWatch Logs transport\n *\n * Sends log entries to AWS CloudWatch Logs via the PutLogEvents API.\n * Supports EMF (Embedded Metric Format) for automatic metric extraction.\n *\n * Auth, in order: explicit config, environment variables, the shared\n * credentials file (AWS_PROFILE, AWS_SHARED_CREDENTIALS_FILE), ECS container\n * credentials, then EC2 instance metadata (IMDSv2). SSO, assume-role profiles\n * and credential_process are not supported. No SDK dependency — raw HTTPS with\n * AWS Signature Version 4.\n *\n * The log group and stream are created on first use if they do not exist.\n *\n * @example\n * ```ts\n * import { CloudWatchTransport } from 'logixia';\n *\n * const logger = createLogger({\n * transports: {\n * cloudwatch: new CloudWatchTransport({\n * region: 'us-east-1',\n * logGroupName: '/myapp/production',\n * logStreamName: `api-${process.env.HOSTNAME}`,\n * batchSize: 100,\n * flushIntervalMs: 5000,\n * }),\n * },\n * });\n * ```\n */\n\nimport type { IAsyncTransport, TransportLogEntry } from '../types/transport.types';\nimport { internalWarn } from '../utils/internal-log';\nimport { stringifyForLog } from '../utils/safe-stringify';\nimport { AwsCredentialProvider } from './aws-credentials';\nimport { signV4 } from './aws-sigv4';\nimport {\n BatchBuffer,\n DEFAULT_MAX_BUFFER_SIZE,\n DEFAULT_REQUEST_TIMEOUT_MS,\n HttpError,\n httpRequest,\n isPermanentHttpError,\n} from './batch-buffer';\nimport { mergeCoreFields } from './entry-fields';\n\nexport interface CloudWatchTransportConfig {\n /** AWS region, e.g. `'us-east-1'`. Falls back to `AWS_REGION` env var. */\n region?: string;\n /** CloudWatch Logs group name. Created automatically if it does not exist. */\n logGroupName: string;\n /** CloudWatch Logs stream name. Defaults to hostname + process PID. */\n logStreamName?: string;\n /** Override the Logs endpoint, e.g. for LocalStack. Default: `https://logs.<region>.amazonaws.com`. */\n endpoint?: string;\n /** AWS access key ID. Falls back to the credential chain described above. */\n accessKeyId?: string;\n /** AWS secret access key. Used together with `accessKeyId`. */\n secretAccessKey?: string;\n /** AWS session token for temporary credentials. Used together with `accessKeyId`. */\n sessionToken?: string;\n /**\n * Number of log events to batch before flushing.\n * CloudWatch allows max 10,000 events per PutLogEvents call; larger values are capped.\n * @default 100\n */\n batchSize?: number;\n /**\n * Max ms between flushes even if batchSize is not reached.\n * @default 5000\n */\n flushIntervalMs?: number;\n /**\n * Enable EMF (Embedded Metric Format) — wraps log entries as CloudWatch\n * metric namespaces so numeric fields become CloudWatch Metrics automatically.\n * @default false\n */\n emf?: boolean;\n /** CloudWatch Metrics namespace used by EMF. @default 'Logixia' */\n emfNamespace?: string;\n /**\n * Payload fields published as EMF metrics. When set, only these numeric\n * fields become metrics; when unset every numeric payload field does (up to\n * the EMF limit of 100 per entry).\n */\n emfMetrics?: string[];\n /** Abort a request that has not finished after this many ms. @default 10000 */\n requestTimeoutMs?: number;\n /** Max buffered events; the oldest are dropped past this. @default 10000 */\n maxBufferSize?: number;\n /** Minimum log level to forward. */\n level?: string;\n}\n\nconst MAX_EVENTS_PER_PUT = 10_000;\nconst EMF_MAX_METRICS = 100;\n\n/** Error types worth retrying even though CloudWatch reports them as HTTP 400. */\nconst RETRYABLE_TYPES = new Set([\n 'ThrottlingException',\n 'ThrottledException',\n 'LimitExceededException',\n 'ServiceUnavailableException',\n 'ExpiredTokenException',\n 'ResourceNotFoundException',\n]);\n\nfunction awsErrorType(err: unknown): string | undefined {\n if (!(err instanceof HttpError)) return undefined;\n try {\n const type = (JSON.parse(err.body) as { __type?: string }).__type;\n return type?.split('#').pop();\n } catch {\n return undefined;\n }\n}\n\ninterface CwLogEvent {\n timestamp: number;\n message: string;\n}\n\n/**\n * AWS CloudWatch Logs transport with EMF support and batched PutLogEvents calls.\n */\nexport class CloudWatchTransport implements IAsyncTransport {\n public readonly name = 'cloudwatch';\n public readonly batchSize: number;\n public readonly flushInterval: number;\n public readonly level: string | undefined;\n\n private readonly buffer: BatchBuffer<CwLogEvent>;\n private readonly requestTimeoutMs: number;\n private flushTimer: NodeJS.Timeout | null = null;\n private readonly credentials: AwsCredentialProvider;\n private readonly endpoint: URL;\n private streamReady = false;\n\n private readonly region: string;\n private readonly logGroupName: string;\n private readonly logStreamName: string;\n private readonly emf: boolean;\n private readonly emfNamespace: string;\n private readonly emfMetrics: ReadonlySet<string> | undefined;\n\n constructor(config: CloudWatchTransportConfig) {\n this.region =\n config.region ??\n process.env['AWS_REGION'] ??\n process.env['AWS_DEFAULT_REGION'] ??\n 'us-east-1';\n this.logGroupName = config.logGroupName;\n this.logStreamName =\n config.logStreamName ?? `${process.env['HOSTNAME'] ?? 'node'}-${process.pid}`;\n this.credentials = new AwsCredentialProvider(config);\n this.endpoint = new URL(config.endpoint ?? `https://logs.${this.region}.amazonaws.com`);\n this.batchSize = Math.min(config.batchSize ?? 100, MAX_EVENTS_PER_PUT);\n this.emfNamespace = config.emfNamespace ?? 'Logixia';\n this.flushInterval = config.flushIntervalMs ?? 5000;\n this.emf = config.emf ?? false;\n this.emfMetrics = config.emfMetrics ? new Set(config.emfMetrics) : undefined;\n this.level = config.level;\n this.requestTimeoutMs = config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n this.buffer = new BatchBuffer<CwLogEvent>({\n name: 'CloudWatchTransport',\n batchSize: this.batchSize,\n maxBufferSize: config.maxBufferSize ?? DEFAULT_MAX_BUFFER_SIZE,\n send: (events) => this.putLogEvents(events),\n shouldRetry: (err) =>\n !isPermanentHttpError(err) || RETRYABLE_TYPES.has(awsErrorType(err) ?? ''),\n });\n\n this.flushTimer = setInterval(() => {\n this.flush().catch(() => {});\n }, this.flushInterval);\n if (this.flushTimer.unref) this.flushTimer.unref();\n }\n\n write(entry: TransportLogEntry): void {\n const timestamp = entry.timestamp ? new Date(entry.timestamp).getTime() : Date.now();\n const message = this.emf ? this.toEmf(entry) : stringifyForLog(entry);\n this.buffer.push({ timestamp, message });\n this.buffer.flushIfFull();\n }\n\n flush(): Promise<void> {\n return this.buffer.flush();\n }\n\n /**\n * Flush remaining events and stop the interval timer on shutdown. Without this\n * the manager's close() skips the transport (close was undefined) and any\n * buffered events were lost on deploy. Waits for any in-flight send first.\n */\n async close(): Promise<void> {\n if (this.flushTimer) {\n clearInterval(this.flushTimer);\n this.flushTimer = null;\n }\n await this.buffer.close();\n }\n\n private toEmf(entry: TransportLogEntry): string {\n const { level, message, data: payload, timestamp } = entry;\n const fields = mergeCoreFields({ _aws: undefined, level, message }, payload);\n const metricNames: string[] = [];\n for (const [k, v] of Object.entries(fields)) {\n if (k === '_aws' || k === 'level' || k === 'message' || typeof v !== 'number') continue;\n if (this.emfMetrics && !this.emfMetrics.has(k)) continue;\n if (metricNames.length < EMF_MAX_METRICS) metricNames.push(k);\n }\n fields['_aws'] = {\n Timestamp: timestamp ? new Date(timestamp).getTime() : Date.now(),\n CloudWatchMetrics:\n metricNames.length > 0\n ? [\n {\n Namespace: this.emfNamespace,\n Dimensions: [['level']],\n Metrics: metricNames.map((n) => ({ Name: n, Unit: 'None' })),\n },\n ]\n : [],\n };\n return stringifyForLog(fields);\n }\n\n private async putLogEvents(events: CwLogEvent[]): Promise<void> {\n await this.ensureStream();\n const logEvents = [...events].sort((a, b) => a.timestamp - b.timestamp);\n try {\n await this.call('PutLogEvents', {\n logGroupName: this.logGroupName,\n logStreamName: this.logStreamName,\n logEvents,\n });\n } catch (err) {\n const type = awsErrorType(err);\n if (type === 'DataAlreadyAcceptedException') return;\n if (type === 'ResourceNotFoundException') this.streamReady = false;\n if (type === 'ExpiredTokenException') this.credentials.invalidate();\n throw err;\n }\n }\n\n private async ensureStream(): Promise<void> {\n if (this.streamReady) return;\n try {\n await this.call('CreateLogGroup', { logGroupName: this.logGroupName });\n } catch (err) {\n if (awsErrorType(err) === 'ExpiredTokenException') this.credentials.invalidate();\n if (!(err instanceof HttpError)) throw err;\n if (awsErrorType(err) !== 'ResourceAlreadyExistsException') {\n internalWarn(`CloudWatchTransport: CreateLogGroup failed, continuing (${err.message})`);\n }\n }\n try {\n await this.call('CreateLogStream', {\n logGroupName: this.logGroupName,\n logStreamName: this.logStreamName,\n });\n } catch (err) {\n if (awsErrorType(err) !== 'ResourceAlreadyExistsException') throw err;\n }\n this.streamReady = true;\n }\n\n private async call(action: string, payload: Record<string, unknown>): Promise<void> {\n const credentials = await this.credentials.get();\n const body = stringifyForLog(payload);\n const { host } = this.endpoint;\n const headers = signV4({\n method: 'POST',\n path: '/',\n headers: {\n host,\n 'content-type': 'application/x-amz-json-1.1',\n 'x-amz-target': `Logs_20140328.${action}`,\n },\n body,\n region: this.region,\n service: 'logs',\n credentials,\n });\n await httpRequest({\n url: this.endpoint,\n headers,\n body,\n timeoutMs: this.requestTimeoutMs,\n label: `CloudWatch ${action}`,\n });\n }\n}\n"],"mappings":"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"}