Files
kickkingsapi/server_log.ts
T
2026-05-02 20:22:35 +00:00

115 lines
3.8 KiB
TypeScript

import * as fs from 'fs';
import * as path from 'path';
import * as util from 'util';
let logFilePath = '';
let historyFilePath = '';
export function initServerLog(logDir: string): void {
const dir = path.resolve(logDir);
fs.mkdirSync(dir, { recursive: true });
logFilePath = path.join(dir, 'matchmaker.log');
historyFilePath = path.join(dir, 'history.log');
}
function formatArg(msg: unknown): string {
if (typeof msg === 'string') return msg;
try {
return util.inspect(msg, { depth: 8, colors: false, maxArrayLength: 200 });
} catch {
return String(msg);
}
}
function appendFileSafe(filePath: string, line: string): void {
try {
fs.appendFileSync(filePath, line, 'utf8');
} catch (e) {
console.error('server_log: append failed', filePath, e);
}
}
/** Same visibility rules as console: always. */
export function logInfo(...parts: unknown[]): void {
const ts = new Date().toISOString();
const text = parts.map(formatArg).join(' ');
const line = `[${ts}] ${text}\n`;
console.log(text);
if (logFilePath) appendFileSafe(logFilePath, line);
}
/** Same visibility rules as console: when logLevel > 0. */
export function logDebug(logLevel: number, ...parts: unknown[]): void {
if (logLevel <= 0) return;
const ts = new Date().toISOString();
const text = parts.map(formatArg).join(' ');
const line = `[${ts}] ${text}\n`;
console.log(text);
if (logFilePath) appendFileSafe(logFilePath, line);
}
/** Same visibility rules as console: when logLevel > 1. */
export function logVerbose(logLevel: number, ...parts: unknown[]): void {
if (logLevel <= 1) return;
const ts = new Date().toISOString();
const text = parts.map(formatArg).join(' ');
const line = `[${ts}] ${text}\n`;
console.log(text);
if (logFilePath) appendFileSafe(logFilePath, line);
}
/** Short human-readable timeline (always appended when called). */
export function recordHistory(message: string): void {
const safe = message.replace(/\r?\n/g, ' ').trim();
if (!safe) return;
const ts = new Date().toISOString();
const line = `${ts}\t${safe}\n`;
if (historyFilePath) appendFileSafe(historyFilePath, line);
}
export function readLogTail(maxLines: number, maxBytes: number): string {
if (!logFilePath || !fs.existsSync(logFilePath)) return '';
return tailTextFile(logFilePath, maxLines, maxBytes);
}
export function readHistoryTail(maxLines: number, maxBytes: number): string[] {
if (!historyFilePath || !fs.existsSync(historyFilePath)) return [];
const text = tailTextFile(historyFilePath, maxLines, maxBytes);
return text
.split('\n')
.map((l) => l.trimEnd())
.filter((l) => l.length > 0);
}
function tailTextFile(filePath: string, maxLines: number, maxBytes: number): string {
const st = fs.statSync(filePath);
if (st.size === 0) return '';
const readSize = Math.min(st.size, maxBytes);
const fd = fs.openSync(filePath, 'r');
try {
const buf = Buffer.alloc(readSize);
fs.readSync(fd, buf, 0, readSize, st.size - readSize);
let s = buf.toString('utf8');
if (st.size > readSize && !s.startsWith('\n')) {
const firstNl = s.indexOf('\n');
if (firstNl !== -1) s = s.slice(firstNl + 1);
}
const lines = s.split('\n');
if (lines.length > maxLines) {
return lines.slice(-maxLines).join('\n');
}
return s;
} finally {
fs.closeSync(fd);
}
}
export function logGameStderr(port: number, chunk: Buffer): void {
logInfo(`[game:${port}] stderr: ${chunk.toString('utf8').trimEnd()}`);
}
export function logGameExit(port: number, code: number | null): void {
logInfo(`Game instance with port ${port} exited with code ${code}`);
recordHistory(`Game on port ${port} exited (code ${code == null ? 'unknown' : code})`);
}