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.
- package/README.md +81 -16
- package/dist/{async-context-CwBNMm2E.mjs → async-context-BzJrbfQs.mjs} +8 -4
- package/dist/async-context-BzJrbfQs.mjs.map +1 -0
- package/dist/{async-context-CSHWoHMB.js → async-context-DyQQYq3I.js} +8 -4
- package/dist/async-context-DyQQYq3I.js.map +1 -0
- package/dist/{azure-monitor.transport-CG7ClkYj.d.ts → azure-monitor.transport-Cn_3Usvu.d.ts} +2 -2
- package/dist/{azure-monitor.transport-CG7ClkYj.d.ts.map → azure-monitor.transport-Cn_3Usvu.d.ts.map} +1 -1
- package/dist/{azure-monitor.transport-Cm74kXZS.d.mts → azure-monitor.transport-D7Da4RAb.d.mts} +2 -2
- package/dist/{azure-monitor.transport-Cm74kXZS.d.mts.map → azure-monitor.transport-D7Da4RAb.d.mts.map} +1 -1
- package/dist/azure.d.mts +2 -2
- package/dist/azure.d.ts +2 -2
- package/dist/browser.d.mts +9 -0
- package/dist/browser.d.mts.map +1 -1
- package/dist/browser.d.ts +9 -0
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +43 -12
- package/dist/browser.js.map +1 -1
- package/dist/browser.mjs +43 -12
- package/dist/browser.mjs.map +1 -1
- package/dist/{logitron-logger.module-Bs3XYZgF.mjs → builder-0gEzRopq.mjs} +383 -1021
- package/dist/builder-0gEzRopq.mjs.map +1 -0
- package/dist/{logitron-logger.module-BsDZR5BH.js → builder-D44QktIo.js} +411 -1155
- package/dist/builder-D44QktIo.js.map +1 -0
- package/dist/cli/index.js +130 -65
- package/dist/cloudwatch.d.mts +2 -2
- package/dist/cloudwatch.d.ts +2 -2
- package/dist/cloudwatch.js +2 -1
- package/dist/cloudwatch.mjs +2 -1
- package/dist/{cloudwatch.transport-pNBNWJLt.js → cloudwatch.transport-BO1ahxw-.js} +26 -19
- package/dist/cloudwatch.transport-BO1ahxw-.js.map +1 -0
- package/dist/{cloudwatch.transport-yBwm4OeO.mjs → cloudwatch.transport-DZ6ies7g.mjs} +26 -19
- package/dist/cloudwatch.transport-DZ6ies7g.mjs.map +1 -0
- package/dist/{cloudwatch.transport-CGHY4buE.d.mts → cloudwatch.transport-DsEq633o.d.ts} +9 -2
- package/dist/cloudwatch.transport-DsEq633o.d.ts.map +1 -0
- package/dist/{cloudwatch.transport-DK_8cKd3.d.ts → cloudwatch.transport-UlyZFWMn.d.mts} +9 -2
- package/dist/cloudwatch.transport-UlyZFWMn.d.mts.map +1 -0
- package/dist/{coerce.utils-BN980Y9V.js → coerce.utils-B-LilukN.js} +1 -1
- package/dist/{coerce.utils-BN980Y9V.js.map → coerce.utils-B-LilukN.js.map} +1 -1
- package/dist/{coerce.utils-Dxv4_ikC.mjs → coerce.utils-Cu6IUTgX.mjs} +1 -1
- package/dist/{coerce.utils-Dxv4_ikC.mjs.map → coerce.utils-Cu6IUTgX.mjs.map} +1 -1
- package/dist/correlation.d.mts +24 -6
- package/dist/correlation.d.mts.map +1 -1
- package/dist/correlation.d.ts +24 -6
- package/dist/correlation.d.ts.map +1 -1
- package/dist/correlation.js +41 -16
- package/dist/correlation.js.map +1 -1
- package/dist/correlation.mjs +41 -16
- package/dist/correlation.mjs.map +1 -1
- package/dist/entry-fields-BJkuPnIg.mjs +30 -0
- package/dist/entry-fields-BJkuPnIg.mjs.map +1 -0
- package/dist/entry-fields-BvOLFlJ4.js +36 -0
- package/dist/entry-fields-BvOLFlJ4.js.map +1 -0
- package/dist/gcp.d.mts +2 -2
- package/dist/gcp.d.ts +2 -2
- package/dist/gcp.js +2 -1
- package/dist/gcp.mjs +2 -1
- package/dist/{gcp.transport-BOhkVKlI.d.ts → gcp.transport-B7oc97sG.d.ts} +2 -2
- package/dist/gcp.transport-B7oc97sG.d.ts.map +1 -0
- package/dist/{gcp.transport-SHdtc1n0.js → gcp.transport-CHV8MGgz.js} +5 -5
- package/dist/gcp.transport-CHV8MGgz.js.map +1 -0
- package/dist/{gcp.transport-BJuNRFe_.mjs → gcp.transport-DBX5TPIy.mjs} +5 -5
- package/dist/gcp.transport-DBX5TPIy.mjs.map +1 -0
- package/dist/{gcp.transport-Bi6CUqZR.d.mts → gcp.transport-GrJurKo8.d.mts} +2 -2
- package/dist/gcp.transport-GrJurKo8.d.mts.map +1 -0
- package/dist/{index-BPKwwERo.d.mts → index-3mw5gaCw.d.mts} +2 -2
- package/dist/{index-BPKwwERo.d.mts.map → index-3mw5gaCw.d.mts.map} +1 -1
- package/dist/{index-C1X-TE5I.d.ts → index-DhIoploR.d.ts} +2 -2
- package/dist/{index-C1X-TE5I.d.ts.map → index-DhIoploR.d.ts.map} +1 -1
- package/dist/index.d.mts +99 -137
- package/dist/index.d.mts.map +1 -1
- package/dist/index.d.ts +99 -137
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +146 -510
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +99 -431
- package/dist/index.mjs.map +1 -1
- package/dist/logitron-logger-BFDSO8Io.d.mts +257 -0
- package/dist/logitron-logger-BFDSO8Io.d.mts.map +1 -0
- package/dist/logitron-logger-UVeN-mQm.d.ts +257 -0
- package/dist/logitron-logger-UVeN-mQm.d.ts.map +1 -0
- package/dist/middleware.d.mts +11 -4
- package/dist/middleware.d.mts.map +1 -1
- package/dist/middleware.d.ts +11 -4
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +148 -24
- package/dist/middleware.js.map +1 -1
- package/dist/middleware.mjs +148 -24
- package/dist/middleware.mjs.map +1 -1
- package/dist/nest.d.mts +481 -6
- package/dist/nest.d.mts.map +1 -1
- package/dist/nest.d.ts +481 -6
- package/dist/nest.d.ts.map +1 -1
- package/dist/nest.js +884 -13
- package/dist/nest.js.map +1 -0
- package/dist/nest.mjs +863 -6
- package/dist/nest.mjs.map +1 -0
- package/dist/{search-CFQRISUv.mjs → search-BFbu_oVn.mjs} +35 -12
- package/dist/search-BFbu_oVn.mjs.map +1 -0
- package/dist/{search-njTI2hdo.js → search-C4V0OMEn.js} +35 -12
- package/dist/search-C4V0OMEn.js.map +1 -0
- package/dist/{search-DujhR94O.d.ts → search-CNh9uVwR.d.ts} +2 -3
- package/dist/search-CNh9uVwR.d.ts.map +1 -0
- package/dist/{search-CQOo-Iq6.d.mts → search-DXANsQ2n.d.mts} +2 -3
- package/dist/search-DXANsQ2n.d.mts.map +1 -0
- package/dist/search.d.mts +3 -3
- package/dist/search.d.ts +3 -3
- package/dist/search.js +2 -2
- package/dist/search.mjs +2 -2
- package/dist/slack.transport-BZzoXDPn.d.mts +112 -0
- package/dist/slack.transport-BZzoXDPn.d.mts.map +1 -0
- package/dist/slack.transport-BfJRouB2.js +654 -0
- package/dist/slack.transport-BfJRouB2.js.map +1 -0
- package/dist/slack.transport-Bzw7sucI.mjs +643 -0
- package/dist/slack.transport-Bzw7sucI.mjs.map +1 -0
- package/dist/slack.transport-CXjQYOes.d.ts +112 -0
- package/dist/slack.transport-CXjQYOes.d.ts.map +1 -0
- package/dist/testing.d.mts +2 -2
- package/dist/testing.d.ts +2 -2
- package/dist/trace.utils-CvXgaiQT.mjs +278 -0
- package/dist/trace.utils-CvXgaiQT.mjs.map +1 -0
- package/dist/trace.utils-DpBOLDtq.js +358 -0
- package/dist/trace.utils-DpBOLDtq.js.map +1 -0
- package/dist/{transport.manager-D6KL3t9V.mjs → transport.manager-BiuCAJd2.mjs} +526 -160
- package/dist/transport.manager-BiuCAJd2.mjs.map +1 -0
- package/dist/{transport.manager-BivyUTtU.js → transport.manager-CXbKKdVE.js} +537 -159
- package/dist/transport.manager-CXbKKdVE.js.map +1 -0
- package/dist/{transport.types-B5vcyv2k.d.mts → transport.types-BMnurPcc.d.mts} +23 -3
- package/dist/transport.types-BMnurPcc.d.mts.map +1 -0
- package/dist/{transport.types-BuY84TwO.d.ts → transport.types-CGoHpWQR.d.ts} +23 -3
- package/dist/transport.types-CGoHpWQR.d.ts.map +1 -0
- package/dist/transports.d.mts +50 -20
- package/dist/transports.d.mts.map +1 -1
- package/dist/transports.d.ts +50 -20
- package/dist/transports.d.ts.map +1 -1
- package/dist/transports.js +9 -5
- package/dist/transports.mjs +8 -6
- package/dist/worker-entry.js +307 -55
- package/dist/worker.d.mts +2 -2
- package/dist/worker.d.ts +2 -2
- package/dist/worker.js +1 -1
- package/dist/worker.mjs +1 -1
- package/dist/{worker.transport-CfH6z37Z.js → worker.transport-BdPxu3PN.js} +1 -1
- package/dist/{worker.transport-CfH6z37Z.js.map → worker.transport-BdPxu3PN.js.map} +1 -1
- package/dist/{worker.transport-mKXgNK0L.d.ts → worker.transport-CdzSZJmw.d.ts} +2 -2
- package/dist/{worker.transport-mKXgNK0L.d.ts.map → worker.transport-CdzSZJmw.d.ts.map} +1 -1
- package/dist/{worker.transport-BP8Q28Z2.d.mts → worker.transport-JvGPog3C.d.mts} +2 -2
- package/dist/{worker.transport-BP8Q28Z2.d.mts.map → worker.transport-JvGPog3C.d.mts.map} +1 -1
- package/dist/{worker.transport-BhMyvqQ2.mjs → worker.transport-lPxO2MVL.mjs} +1 -1
- package/dist/{worker.transport-BhMyvqQ2.mjs.map → worker.transport-lPxO2MVL.mjs.map} +1 -1
- package/package.json +27 -14
- package/dist/.tsbuildinfo +0 -1
- package/dist/async-context-CSHWoHMB.js.map +0 -1
- package/dist/async-context-CwBNMm2E.mjs.map +0 -1
- package/dist/cloudwatch.transport-CGHY4buE.d.mts.map +0 -1
- package/dist/cloudwatch.transport-DK_8cKd3.d.ts.map +0 -1
- package/dist/cloudwatch.transport-pNBNWJLt.js.map +0 -1
- package/dist/cloudwatch.transport-yBwm4OeO.mjs.map +0 -1
- package/dist/gcp.transport-BJuNRFe_.mjs.map +0 -1
- package/dist/gcp.transport-BOhkVKlI.d.ts.map +0 -1
- package/dist/gcp.transport-Bi6CUqZR.d.mts.map +0 -1
- package/dist/gcp.transport-SHdtc1n0.js.map +0 -1
- package/dist/logitron-logger.module-BdG1Jnpk.d.mts +0 -618
- package/dist/logitron-logger.module-BdG1Jnpk.d.mts.map +0 -1
- package/dist/logitron-logger.module-Bs3XYZgF.mjs.map +0 -1
- package/dist/logitron-logger.module-BsDZR5BH.js.map +0 -1
- package/dist/logitron-logger.module-CbXy2gfW.d.ts +0 -618
- package/dist/logitron-logger.module-CbXy2gfW.d.ts.map +0 -1
- package/dist/search-CFQRISUv.mjs.map +0 -1
- package/dist/search-CQOo-Iq6.d.mts.map +0 -1
- package/dist/search-DujhR94O.d.ts.map +0 -1
- package/dist/search-njTI2hdo.js.map +0 -1
- package/dist/transport.manager-BivyUTtU.js.map +0 -1
- package/dist/transport.manager-D6KL3t9V.mjs.map +0 -1
- package/dist/transport.types-B5vcyv2k.d.mts.map +0 -1
- package/dist/transport.types-BuY84TwO.d.ts.map +0 -1
- 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
|
|
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(
|
|
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
|
|
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 =
|
|
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 =
|
|
831
|
+
const whereTokenIdx = maskedUpper.indexOf("WHERE ");
|
|
782
832
|
if (whereTokenIdx !== -1) {
|
|
783
833
|
const afterWhere = remainder.slice(whereTokenIdx + 6).trimStart();
|
|
784
|
-
const upperAfter =
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
968
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
1060
|
-
|
|
1061
|
-
if (
|
|
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
|
|
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"
|
|
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
|
|
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").
|
|
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 [
|
|
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(
|
|
1323
|
+
function highlightLine(rawLine, highlight) {
|
|
1324
|
+
const line = sanitizeForTerminal(rawLine);
|
|
1260
1325
|
if (!highlight) return line;
|
|
1261
1326
|
try {
|
|
1262
|
-
const level = (JSON.parse(
|
|
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
|
}
|
package/dist/cloudwatch.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import "./transport.types-
|
|
2
|
-
import { n as CloudWatchTransportConfig, t as CloudWatchTransport } from "./cloudwatch.transport-
|
|
1
|
+
import "./transport.types-BMnurPcc.mjs";
|
|
2
|
+
import { n as CloudWatchTransportConfig, t as CloudWatchTransport } from "./cloudwatch.transport-UlyZFWMn.mjs";
|
|
3
3
|
export { CloudWatchTransport, CloudWatchTransportConfig };
|
package/dist/cloudwatch.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import "./transport.types-
|
|
2
|
-
import { n as CloudWatchTransportConfig, t as CloudWatchTransport } from "./cloudwatch.transport-
|
|
1
|
+
import "./transport.types-CGoHpWQR.js";
|
|
2
|
+
import { n as CloudWatchTransportConfig, t as CloudWatchTransport } from "./cloudwatch.transport-DsEq633o.js";
|
|
3
3
|
export { CloudWatchTransport, CloudWatchTransportConfig };
|
package/dist/cloudwatch.js
CHANGED
|
@@ -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-
|
|
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;
|
package/dist/cloudwatch.mjs
CHANGED
|
@@ -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-
|
|
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
|
|
281
|
-
|
|
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
|
-
|
|
297
|
-
|
|
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-
|
|
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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"}
|