POST /api/log stores a log snapshot in Neon table miner_logs over the HTTP SQL endpoint with no dependencies. upload-log.bat posts the last 256 KB of a log from Windows with the curl.exe that ships with it. tools/logs.mjs lists runs and prints the latest lines on the Mac. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
53 lines
2.6 KiB
JavaScript
Executable file
53 lines
2.6 KiB
JavaScript
Executable file
#!/usr/bin/env node
|
|
// Reader for the miner log intake (site/api/log.mjs). Runs on the Mac.
|
|
// node tools/logs.mjs list runs: label, machine, run_id, last received, total bytes
|
|
// node tools/logs.mjs <run_id> print the latest upload for that run
|
|
// node tools/logs.mjs <run_id> --all print every upload for that run, oldest first
|
|
// Reads DATABASE_URL from ~/.config/igneum/env. No dependencies: Neon HTTP SQL over fetch.
|
|
import { readFileSync } from 'node:fs';
|
|
import { homedir } from 'node:os';
|
|
|
|
process.stdout.on('error', e => { if (e.code === 'EPIPE') process.exit(0); throw e; });
|
|
|
|
const env = readFileSync(`${homedir()}/.config/igneum/env`, 'utf8');
|
|
const m = /^DATABASE_URL=(.*)$/m.exec(env);
|
|
if (!m) { console.error('DATABASE_URL not found in ~/.config/igneum/env'); process.exit(1); }
|
|
const url = m[1].trim().replace(/^['"]|['"]$/g, '');
|
|
const host = new URL(url).hostname.replace('-pooler', '');
|
|
|
|
async function sql(query, params = []) {
|
|
const r = await fetch(`https://${host}/sql`, {
|
|
method: 'POST',
|
|
headers: { 'Neon-Connection-String': url, 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ query, params }),
|
|
});
|
|
const j = await r.json();
|
|
if (!r.ok) throw new Error(j.message || JSON.stringify(j));
|
|
return j.rows;
|
|
}
|
|
|
|
const [runId, flag] = process.argv.slice(2);
|
|
|
|
if (!runId) {
|
|
const rows = await sql(`
|
|
SELECT run_id, max(label) AS label, max(machine) AS machine, count(*)::int AS uploads,
|
|
max(received_at) AS last_received, sum(bytes)::bigint AS total_bytes
|
|
FROM miner_logs GROUP BY run_id ORDER BY max(received_at) DESC LIMIT 100`);
|
|
if (!rows.length) { console.log('No runs yet.'); process.exit(0); }
|
|
const fmt = rows.map(r => ({
|
|
run_id: r.run_id, label: r.label, machine: r.machine, uploads: r.uploads,
|
|
last_received: new Date(r.last_received).toISOString().replace('T', ' ').slice(0, 19) + ' UTC',
|
|
total_bytes: Number(r.total_bytes),
|
|
}));
|
|
console.table(fmt);
|
|
process.exit(0);
|
|
}
|
|
|
|
const rows = flag === '--all'
|
|
? await sql('SELECT id, received_at, label, machine, bytes, lines FROM miner_logs WHERE run_id = $1 ORDER BY received_at ASC', [runId])
|
|
: await sql('SELECT id, received_at, label, machine, bytes, lines FROM miner_logs WHERE run_id = $1 ORDER BY received_at DESC LIMIT 1', [runId]);
|
|
if (!rows.length) { console.error(`No uploads for run_id ${runId}`); process.exit(1); }
|
|
for (const r of rows) {
|
|
console.log(`===== id ${r.id} | ${r.label} | ${r.machine} | ${new Date(r.received_at).toISOString()} | ${r.bytes} bytes =====`);
|
|
process.stdout.write(r.lines.endsWith('\n') ? r.lines : r.lines + '\n');
|
|
}
|