- streamer/windows: vash-broadcast.exe (setup/login/start/stop/status/doctor) with per-instance OBS, tosu and FFmpeg, a supervised worker, OCR room selection and OBS scene reconciliation; built and tested on Linux only - streamer/relay.ts shared with Vash TV; Cloudflare Access proxy for dev/staging - worker status validation and live score helpers for the site - STREAMING_HANDOFF.md and streamer/ALPHA_TEST.md record what is unverified Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
41 lines
1.3 KiB
TypeScript
41 lines
1.3 KiB
TypeScript
/** Appends timestamped lines to the instance's log, keeping the last few files. */
|
|
import { appendFileSync, existsSync, mkdirSync, renameSync, rmSync, statSync } from 'node:fs';
|
|
import { dirname } from 'node:path';
|
|
|
|
const MAX_BYTES = 10 * 1024 * 1024;
|
|
const KEEP = 5;
|
|
|
|
/** Rotation keeps a multi-hour broadcast's log bounded: worker.log, worker.1.log, … */
|
|
function rotate(path: string) {
|
|
if (!existsSync(path) || statSync(path).size < MAX_BYTES) return;
|
|
const numbered = (n: number) => path.replace(/\.log$/, `.${n}.log`);
|
|
rmSync(numbered(KEEP), { force: true });
|
|
for (let n = KEEP - 1; n >= 1; n--)
|
|
if (existsSync(numbered(n))) renameSync(numbered(n), numbered(n + 1));
|
|
renameSync(path, numbered(1));
|
|
}
|
|
|
|
export function fileLogger(path: string, alsoConsole = false) {
|
|
mkdirSync(dirname(path), { recursive: true });
|
|
let lines = 0;
|
|
return (...parts: unknown[]) => {
|
|
const text = parts
|
|
.map((part) =>
|
|
part instanceof Error
|
|
? part.message
|
|
: typeof part === 'string'
|
|
? part
|
|
: JSON.stringify(part)
|
|
)
|
|
.join(' ');
|
|
const line = `${new Date().toISOString()} ${text}\n`;
|
|
if (alsoConsole) process.stdout.write(line);
|
|
try {
|
|
if (++lines % 200 === 0) rotate(path);
|
|
appendFileSync(path, line);
|
|
} catch {
|
|
/* a full or locked disk must not stop the broadcast */
|
|
}
|
|
};
|
|
}
|