106 lines
3.1 KiB
TypeScript
106 lines
3.1 KiB
TypeScript
/**
|
|
* Logging utilities — stderr only, stdout is the MCP JSON-RPC channel.
|
|
*
|
|
* When the environment variable MCP_PROXY_LOG_FILE is set, log lines are
|
|
* also appended to that file so the Electron host can tail them into main.log.
|
|
*
|
|
* Log rotation: file is named by date (e.g. mcp-proxy-2026-03-09.log).
|
|
* A new file is created each day. Old files beyond MAX_LOG_FILES are deleted.
|
|
*/
|
|
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
|
|
const logFilePath = process.env.MCP_PROXY_LOG_FILE;
|
|
const MAX_LOG_FILES = 7;
|
|
|
|
let logStream: fs.WriteStream | null = null;
|
|
let logDir = '';
|
|
let logBaseName = '';
|
|
let logExt = '';
|
|
let currentDateStr = '';
|
|
|
|
function dateStr(): string {
|
|
const d = new Date();
|
|
const pad2 = (n: number) => String(n).padStart(2, '0');
|
|
return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`;
|
|
}
|
|
|
|
function openLogFile(): void {
|
|
if (!logFilePath) return;
|
|
|
|
const today = dateStr();
|
|
if (today === currentDateStr && logStream) return;
|
|
|
|
// Close previous stream
|
|
if (logStream) {
|
|
try { logStream.end(); } catch { /* ignore */ }
|
|
}
|
|
|
|
currentDateStr = today;
|
|
const dated = path.join(logDir, `${logBaseName}-${today}${logExt}`);
|
|
try {
|
|
logStream = fs.createWriteStream(dated, { flags: 'a' });
|
|
} catch {
|
|
logStream = null;
|
|
}
|
|
|
|
// Cleanup old log files
|
|
cleanupOldLogs();
|
|
}
|
|
|
|
function cleanupOldLogs(): void {
|
|
if (!logDir || !logBaseName) return;
|
|
try {
|
|
const prefix = `${logBaseName}-`;
|
|
const files = fs.readdirSync(logDir)
|
|
.filter(f => f.startsWith(prefix) && f.endsWith(logExt))
|
|
.sort()
|
|
.reverse();
|
|
for (let i = MAX_LOG_FILES; i < files.length; i++) {
|
|
try { fs.unlinkSync(path.join(logDir, files[i])); } catch { /* ignore */ }
|
|
}
|
|
} catch { /* ignore */ }
|
|
}
|
|
|
|
if (logFilePath) {
|
|
try {
|
|
logDir = path.dirname(logFilePath);
|
|
if (logDir && !fs.existsSync(logDir)) {
|
|
fs.mkdirSync(logDir, { recursive: true });
|
|
}
|
|
const fullName = path.basename(logFilePath);
|
|
const dotIdx = fullName.lastIndexOf('.');
|
|
if (dotIdx > 0) {
|
|
logBaseName = fullName.substring(0, dotIdx);
|
|
logExt = fullName.substring(dotIdx);
|
|
} else {
|
|
logBaseName = fullName;
|
|
logExt = '.log';
|
|
}
|
|
openLogFile();
|
|
} catch {
|
|
// If file creation fails, continue without file logging
|
|
}
|
|
}
|
|
|
|
function timestamp(): string {
|
|
const d = new Date();
|
|
const pad2 = (n: number) => String(n).padStart(2, '0');
|
|
const pad3 = (n: number) => String(n).padStart(3, '0');
|
|
return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())} ${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}.${pad3(d.getMilliseconds())}`;
|
|
}
|
|
|
|
export function log(level: string, msg: string): void {
|
|
const line = `[${timestamp()}] [${level.toLowerCase()}] [qiming-mcp-proxy] ${msg}\n`;
|
|
process.stderr.write(line);
|
|
if (logFilePath) {
|
|
openLogFile(); // Rotate if date changed
|
|
logStream?.write(line);
|
|
}
|
|
}
|
|
|
|
export const logInfo = (msg: string) => log('INFO', msg);
|
|
export const logWarn = (msg: string) => log('WARN', msg);
|
|
export const logError = (msg: string) => log('ERROR', msg);
|