mirror of
https://github.com/basketikun/infinite-canvas.git
synced 2026-08-06 09:24:24 +08:00
feat(logging): implement detailed logging for Codex operations and HTTP requests
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
/** 将日期格式化为适合文件名使用的本地日期字符串。 */
|
||||
export function formatDateForFilename(date = new Date()) {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(date.getDate()).padStart(2, "0");
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import {inspect} from "node:util";
|
||||
|
||||
import winston, {format, transports, type Logger as WinstonLogger} from "winston";
|
||||
|
||||
import {formatDateForFilename} from "./date.js";
|
||||
|
||||
/** 管理 Canvas Agent 的终端与文件 Debug 日志。 */
|
||||
export class Logger {
|
||||
readonly enabled = process.argv.includes("--debug");
|
||||
readonly filePath = this.enabled ? path.join(os.homedir(), ".infinite-canvas", "logs", `canvas-agent-${formatDateForFilename()}.log`) : "";
|
||||
private readonly logger: WinstonLogger | null;
|
||||
|
||||
/** 根据命令行 Debug 参数初始化日志输出。 */
|
||||
constructor() {
|
||||
if (!this.enabled) {
|
||||
this.logger = null;
|
||||
return;
|
||||
}
|
||||
fs.mkdirSync(path.dirname(this.filePath), {recursive: true});
|
||||
const line = format.printf(({level, message, timestamp, details}) => `[${level.toUpperCase()}][${timestamp}] ${message}${formatDetails(details)}`);
|
||||
this.logger = winston.createLogger({
|
||||
level: "debug",
|
||||
transports: [
|
||||
new transports.Console({format: format.combine(format.colorize(), format.timestamp({format: "HH:mm:ss"}), line)}),
|
||||
new transports.File({filename: this.filePath, format: format.combine(format.timestamp({format: "HH:mm:ss"}), line)}),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
/** 输出 Debug 级别日志。 */
|
||||
debug(message: string, details?: unknown) {
|
||||
if (details === undefined) this.logger?.debug(message);
|
||||
else this.logger?.debug(message, {details: sanitize(details)});
|
||||
}
|
||||
|
||||
/** 输出 Info 级别日志。 */
|
||||
info(message: string, details?: unknown) {
|
||||
if (details === undefined) this.logger?.info(message);
|
||||
else this.logger?.info(message, {details: sanitize(details)});
|
||||
}
|
||||
|
||||
/** 输出 Warn 级别日志。 */
|
||||
warn(message: string, details?: unknown) {
|
||||
if (details === undefined) this.logger?.warn(message);
|
||||
else this.logger?.warn(message, {details: sanitize(details)});
|
||||
}
|
||||
|
||||
/** 输出 Error 级别日志。 */
|
||||
error(message: string, details?: unknown) {
|
||||
if (details === undefined) this.logger?.error(message);
|
||||
else this.logger?.error(message, {details: sanitize(details)});
|
||||
}
|
||||
}
|
||||
|
||||
/** 将日志详情格式化为紧凑的单行文本。 */
|
||||
function formatDetails(details: unknown) {
|
||||
if (details === undefined) return "";
|
||||
if (!details || typeof details !== "object" || Array.isArray(details)) return ` ${inspect(details, {depth: null, breakLength: Infinity})}`;
|
||||
return ` ${Object.entries(details).map(([key, value]) => `${key}=${inspect(value, {depth: null, breakLength: Infinity})}`).join(" ")}`;
|
||||
}
|
||||
|
||||
/** 清理日志内容中的敏感数据和不可序列化引用。 */
|
||||
function sanitize(value: unknown, key = "", seen = new WeakSet<object>()): unknown {
|
||||
if (/token|authorization|api.?key|dataurl/i.test(key)) return "[REDACTED]";
|
||||
if (typeof value === "string" && value.startsWith("data:")) return `[DATA URL ${value.length} chars]`;
|
||||
if (value instanceof Error) return {name: value.name, message: value.message, stack: value.stack};
|
||||
if (!value || typeof value !== "object") return value;
|
||||
if (seen.has(value)) return "[CIRCULAR]";
|
||||
seen.add(value);
|
||||
if (Array.isArray(value)) return value.map((item) => sanitize(item, key, seen));
|
||||
return Object.fromEntries(Object.entries(value as Record<string, unknown>).map(([field, item]) => [field, sanitize(item, field, seen)]));
|
||||
}
|
||||
|
||||
export const logger = new Logger();
|
||||
Reference in New Issue
Block a user