lduck 0.0.1 → 0.0.2
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/dist/index.js +148 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1029,6 +1029,134 @@ class WSHandler {
|
|
|
1029
1029
|
}
|
|
1030
1030
|
}
|
|
1031
1031
|
}
|
|
1032
|
+
function parseRelayArgs(args) {
|
|
1033
|
+
const { values } = parseArgs({
|
|
1034
|
+
args,
|
|
1035
|
+
options: {
|
|
1036
|
+
url: { type: "string", short: "u" },
|
|
1037
|
+
service: { type: "string", short: "s" },
|
|
1038
|
+
"batch-size": { type: "string" },
|
|
1039
|
+
interval: { type: "string" },
|
|
1040
|
+
help: { type: "boolean", short: "h" }
|
|
1041
|
+
},
|
|
1042
|
+
strict: true
|
|
1043
|
+
});
|
|
1044
|
+
return {
|
|
1045
|
+
url: values.url ?? "http://localhost:8080",
|
|
1046
|
+
service: values.service,
|
|
1047
|
+
batchSize: values["batch-size"] !== void 0 ? parseInt(values["batch-size"], 10) : 100,
|
|
1048
|
+
intervalMs: values.interval !== void 0 ? parseInt(values.interval, 10) : 500,
|
|
1049
|
+
help: values.help ?? false
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
const RELAY_USAGE = `
|
|
1053
|
+
Usage: <command> | lduck relay [options]
|
|
1054
|
+
|
|
1055
|
+
Relay stdin JSON logs to a running lduck server via HTTP POST.
|
|
1056
|
+
|
|
1057
|
+
Options:
|
|
1058
|
+
-u, --url <url> Target lduck server URL (default: http://localhost:8080)
|
|
1059
|
+
-s, --service <name> Service name to inject into each log entry
|
|
1060
|
+
--batch-size <n> Lines per HTTP batch (default: 100)
|
|
1061
|
+
--interval <ms> Flush interval in milliseconds (default: 500)
|
|
1062
|
+
-h, --help Show this help message
|
|
1063
|
+
|
|
1064
|
+
Examples:
|
|
1065
|
+
kubectl logs -f deploy/api | lduck relay --service api
|
|
1066
|
+
cat app.log | lduck relay --url http://lduck:9090 --service backend
|
|
1067
|
+
docker logs -f myapp | lduck relay -s myapp -u http://localhost:8080
|
|
1068
|
+
`.trimStart();
|
|
1069
|
+
async function runRelay(args) {
|
|
1070
|
+
const opts = parseRelayArgs(args);
|
|
1071
|
+
if (opts.help) {
|
|
1072
|
+
process.stdout.write(RELAY_USAGE);
|
|
1073
|
+
process.exit(0);
|
|
1074
|
+
}
|
|
1075
|
+
const ingestUrl = opts.url.replace(/\/$/, "") + "/api/ingest";
|
|
1076
|
+
let buffer = [];
|
|
1077
|
+
let flushTimer = null;
|
|
1078
|
+
let totalSent = 0;
|
|
1079
|
+
let totalErrors = 0;
|
|
1080
|
+
let stdinClosed = false;
|
|
1081
|
+
async function flush() {
|
|
1082
|
+
if (buffer.length === 0) return;
|
|
1083
|
+
const batch = buffer;
|
|
1084
|
+
buffer = [];
|
|
1085
|
+
try {
|
|
1086
|
+
const res = await fetch(ingestUrl, {
|
|
1087
|
+
method: "POST",
|
|
1088
|
+
headers: { "Content-Type": "application/json" },
|
|
1089
|
+
body: JSON.stringify(batch)
|
|
1090
|
+
});
|
|
1091
|
+
if (!res.ok) {
|
|
1092
|
+
const text = await res.text();
|
|
1093
|
+
console.error(`[relay] HTTP ${res.status}: ${text}`);
|
|
1094
|
+
totalErrors += batch.length;
|
|
1095
|
+
} else {
|
|
1096
|
+
const result = await res.json();
|
|
1097
|
+
totalSent += result.accepted;
|
|
1098
|
+
}
|
|
1099
|
+
} catch (err) {
|
|
1100
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1101
|
+
console.error(`[relay] Send failed: ${message}`);
|
|
1102
|
+
totalErrors += batch.length;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
function resetFlushTimer() {
|
|
1106
|
+
if (flushTimer !== null) {
|
|
1107
|
+
clearTimeout(flushTimer);
|
|
1108
|
+
}
|
|
1109
|
+
flushTimer = setTimeout(async () => {
|
|
1110
|
+
flushTimer = null;
|
|
1111
|
+
await flush();
|
|
1112
|
+
if (!stdinClosed) {
|
|
1113
|
+
resetFlushTimer();
|
|
1114
|
+
}
|
|
1115
|
+
}, opts.intervalMs);
|
|
1116
|
+
}
|
|
1117
|
+
const rl = createInterface({
|
|
1118
|
+
input: process.stdin,
|
|
1119
|
+
crlfDelay: Infinity
|
|
1120
|
+
});
|
|
1121
|
+
console.error(`[relay] Relaying to ${ingestUrl}${opts.service ? ` (service: ${opts.service})` : ""}`);
|
|
1122
|
+
resetFlushTimer();
|
|
1123
|
+
rl.on("line", (line) => {
|
|
1124
|
+
const trimmed = line.trim();
|
|
1125
|
+
if (trimmed === "") return;
|
|
1126
|
+
let parsed;
|
|
1127
|
+
try {
|
|
1128
|
+
parsed = JSON.parse(trimmed);
|
|
1129
|
+
} catch {
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
if (opts.service !== void 0 && !("service" in parsed)) {
|
|
1136
|
+
parsed.service = opts.service;
|
|
1137
|
+
}
|
|
1138
|
+
buffer.push(parsed);
|
|
1139
|
+
if (buffer.length >= opts.batchSize) {
|
|
1140
|
+
flush();
|
|
1141
|
+
}
|
|
1142
|
+
});
|
|
1143
|
+
await new Promise((resolve) => {
|
|
1144
|
+
rl.on("close", async () => {
|
|
1145
|
+
stdinClosed = true;
|
|
1146
|
+
if (flushTimer !== null) {
|
|
1147
|
+
clearTimeout(flushTimer);
|
|
1148
|
+
flushTimer = null;
|
|
1149
|
+
}
|
|
1150
|
+
await flush();
|
|
1151
|
+
if (totalSent > 0 || totalErrors > 0) {
|
|
1152
|
+
console.error(
|
|
1153
|
+
`[relay] Done. Sent: ${totalSent}, Errors: ${totalErrors}`
|
|
1154
|
+
);
|
|
1155
|
+
}
|
|
1156
|
+
resolve();
|
|
1157
|
+
});
|
|
1158
|
+
});
|
|
1159
|
+
}
|
|
1032
1160
|
function parseCLIArgs(args) {
|
|
1033
1161
|
const { values } = parseArgs({
|
|
1034
1162
|
args,
|
|
@@ -1056,8 +1184,13 @@ function parseCLIArgs(args) {
|
|
|
1056
1184
|
}
|
|
1057
1185
|
const USAGE = `
|
|
1058
1186
|
Usage: <command> | lduck [options]
|
|
1187
|
+
<command> | lduck relay [options]
|
|
1059
1188
|
|
|
1060
|
-
|
|
1189
|
+
Commands:
|
|
1190
|
+
(default) Start the lduck server (Web UI + API)
|
|
1191
|
+
relay Relay stdin logs to a running lduck server via HTTP
|
|
1192
|
+
|
|
1193
|
+
Server Options:
|
|
1061
1194
|
-p, --port <port> Server port (default: 8080)
|
|
1062
1195
|
-m, --max-rows <n> Maximum rows to keep (default: 100000)
|
|
1063
1196
|
--batch-size <n> Batch INSERT size (default: 5000)
|
|
@@ -1069,6 +1202,8 @@ Examples:
|
|
|
1069
1202
|
kubectl logs -f deploy/api | lduck --port 8080
|
|
1070
1203
|
cat app.log | lduck --db ./logs.duckdb
|
|
1071
1204
|
docker logs -f myapp | lduck --no-ui -p 9090
|
|
1205
|
+
kubectl logs -f deploy/api | lduck relay --service api
|
|
1206
|
+
docker logs -f myapp | lduck relay -s myapp -u http://localhost:8080
|
|
1072
1207
|
`.trimStart();
|
|
1073
1208
|
async function main() {
|
|
1074
1209
|
const opts = parseCLIArgs(process.argv.slice(2));
|
|
@@ -1121,10 +1256,18 @@ async function main() {
|
|
|
1121
1256
|
ingester.start(process.stdin);
|
|
1122
1257
|
}
|
|
1123
1258
|
if (!process.env.VITEST) {
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1259
|
+
const args = process.argv.slice(2);
|
|
1260
|
+
if (args[0] === "relay") {
|
|
1261
|
+
runRelay(args.slice(1)).catch((err) => {
|
|
1262
|
+
console.error("Failed to run lduck relay:", err);
|
|
1263
|
+
process.exit(1);
|
|
1264
|
+
});
|
|
1265
|
+
} else {
|
|
1266
|
+
main().catch((err) => {
|
|
1267
|
+
console.error("Failed to start lduck:", err);
|
|
1268
|
+
process.exit(1);
|
|
1269
|
+
});
|
|
1270
|
+
}
|
|
1128
1271
|
}
|
|
1129
1272
|
export {
|
|
1130
1273
|
parseCLIArgs
|