项目文件夹

文件
wehub-resource-sync 9119799461
构建测试 / 构建 Linux (ARM64) (push) Failing after 2s
构建测试 / 构建 Linux (x64) (push) Failing after 2s
构建测试 / 构建 Windows (ARM64) (push) Has been cancelled
构建测试 / 构建 Windows (x64) (push) Has been cancelled
构建测试 / 构建 macOS (Apple Silicon) (push) Has been cancelled
构建测试 / 构建 macOS (Intel) (push) Has been cancelled
构建测试 / 构建摘要 (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:24:21 +08:00

153 行
5.0 KiB
TypeScript

import * as fs from "fs";
import * as path from "path";
import getPath from "@/utils/getPath";
type LogLevel = "log" | "info" | "warn" | "error" | "debug";
type ConsoleMethod = (...args: unknown[]) => void;
const LOG_DIR = getPath("logs");
const LOG_FILE = path.join(LOG_DIR, "app.log");
const MAX_SIZE = 1000 * 1024 * 1024;
const LEVELS: LogLevel[] = ["log", "info", "warn", "error", "debug"];
class Logger {
private stream: fs.WriteStream | null = null;
private originalConsole: Partial<Record<LogLevel, ConsoleMethod>> = {};
private originalStdoutWrite: typeof process.stdout.write | null = null;
private originalStderrWrite: typeof process.stderr.write | null = null;
private isHijacked = false;
init(): this {
if (!fs.existsSync(LOG_DIR)) fs.mkdirSync(LOG_DIR, { recursive: true });
this.stream = fs.createWriteStream(LOG_FILE, { flags: "a" });
this.hijack();
return this;
}
private formatTime(): string {
const d = new Date();
const p = (n: number, l = 2) => String(n).padStart(l, "0");
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}.${p(
d.getMilliseconds(),
3,
)}`;
}
private stringify(arg: unknown): string {
if (arg == null) return String(arg);
if (arg instanceof Error) return `${arg.message}\n${arg.stack || ""}`;
if (typeof arg === "object") {
try {
return JSON.stringify(arg);
} catch {
return String(arg);
}
}
return String(arg);
}
private writing = false;
private write(level: LogLevel, args: unknown[]): void {
const line = `[${this.formatTime()}] [${level.toUpperCase()}] ${args.map((a) => this.stringify(a)).join(" ")}\n`;
if (this.stream && !this.stream.destroyed) this.stream.write(line);
this.checkRotate();
}
private writeRaw(chunk: any): void {
if (this.writing) return;
this.writing = true;
try {
let str = typeof chunk === "string" ? chunk : chunk?.toString?.("utf-8") ?? "";
str = str.replace(/\x1B\[\d*m/g, ""); // 去除 ANSI 颜色码
if (str.trim() && this.stream && !this.stream.destroyed) this.stream.write(str.endsWith("\n") ? str : str + "\n");
} finally {
this.writing = false;
}
}
private checkRotate(): void {
try {
if (!fs.existsSync(LOG_FILE) || fs.statSync(LOG_FILE).size < MAX_SIZE) return;
this.stream?.end();
// 单文件轮转:保留后半部分日志
const content = fs.readFileSync(LOG_FILE, "utf-8");
const half = content.slice(content.length >>> 1);
const firstNewline = half.indexOf("\n");
fs.writeFileSync(LOG_FILE, firstNewline >= 0 ? half.slice(firstNewline + 1) : half);
this.stream = fs.createWriteStream(LOG_FILE, { flags: "a" });
} catch {}
}
private hijack(): void {
if (this.isHijacked) return;
// 劫持 console 方法
for (const level of LEVELS) {
const original = console[level];
if (typeof original !== "function") continue;
this.originalConsole[level] = original.bind(console);
(console as any)[level] = (...args: unknown[]) => {
this.writing = true;
try {
this.write(level, args);
} catch (err) {
this.originalConsole.error?.("[Logger Error]", err);
}
this.writing = false;
this.originalConsole[level]!(...args);
};
}
// 劫持 stdout/stderr(捕获 morgan 等直接写 stdout 的输出)
this.originalStdoutWrite = process.stdout.write.bind(process.stdout);
this.originalStderrWrite = process.stderr.write.bind(process.stderr);
process.stdout.write = ((chunk: any, ...rest: any[]) => {
this.writeRaw(chunk);
return this.originalStdoutWrite!(chunk, ...rest);
}) as typeof process.stdout.write;
process.stderr.write = ((chunk: any, ...rest: any[]) => {
this.writeRaw(chunk);
return this.originalStderrWrite!(chunk, ...rest);
}) as typeof process.stderr.write;
this.isHijacked = true;
}
/** 导出日志内容 */
exportLogs(): string {
if (!fs.existsSync(LOG_FILE)) return "";
return fs.readFileSync(LOG_FILE, "utf-8");
}
/** 清空日志 */
clear(): void {
this.stream?.end();
if (fs.existsSync(LOG_FILE)) fs.unlinkSync(LOG_FILE);
this.stream = fs.createWriteStream(LOG_FILE, { flags: "a" });
}
/** 关闭日志 */
close(): void {
if (this.isHijacked) {
for (const level of LEVELS) {
const original = this.originalConsole[level];
if (original) (console as any)[level] = original;
}
this.originalConsole = {};
if (this.originalStdoutWrite) process.stdout.write = this.originalStdoutWrite;
if (this.originalStderrWrite) process.stderr.write = this.originalStderrWrite;
this.originalStdoutWrite = null;
this.originalStderrWrite = null;
this.isHijacked = false;
}
this.stream?.end();
this.stream = null;
}
}
const logger = new Logger().init();
export default logger;