mirror of
https://github.com/basketikun/infinite-canvas.git
synced 2026-08-04 16:14:22 +08:00
feat(logging): update logging format to plain text and enhance HTTP diagnostics
This commit is contained in:
+19
-15
@@ -40,7 +40,6 @@ export function interruptCodexTurn(threadId?: string) {
|
||||
async function runCodexTurnNow(prompt: string, emit: AgentEmit, attachments: AgentAttachment[], options: CodexRunOptions) {
|
||||
let files: string[] = [];
|
||||
try {
|
||||
logger.info("Preparing Codex turn", { threadId: options.threadId, cwd: options.cwd, prompt, attachmentCount: attachments.length });
|
||||
options.onStart?.();
|
||||
files = await writeAttachmentFiles(attachments);
|
||||
const app = await getCodexApp(options.appEmit || emit);
|
||||
@@ -147,7 +146,6 @@ class CodexAppClient {
|
||||
private nextId = 1;
|
||||
private buffer = "";
|
||||
private textByItem = new Map<string, string>();
|
||||
private deltaCount = 0;
|
||||
private lastUsage: unknown = null;
|
||||
private pending = new Map<number, PendingRequest>();
|
||||
private activeTurns = new Map<string, PendingRequest>();
|
||||
@@ -245,7 +243,9 @@ class CodexAppClient {
|
||||
}
|
||||
|
||||
private write(value: unknown) {
|
||||
logger.debug("Codex app-server request", value);
|
||||
const method = String(field(value, "method") || "");
|
||||
const params = field(value, "params");
|
||||
if (method) logger.debug(`Codex ${method}`, { id: field(value, "id"), threadId: field(params, "threadId") });
|
||||
this.child.stdin?.write(`${JSON.stringify(value)}\n`);
|
||||
}
|
||||
|
||||
@@ -264,9 +264,12 @@ class CodexAppClient {
|
||||
}
|
||||
|
||||
private handle(message: Json) {
|
||||
logger.debug("Codex app-server response", message);
|
||||
const id = Number(message.id);
|
||||
if (message.error && this.pending.has(id)) return this.reject(id, String(field(message.error, "message") || "Codex request failed"));
|
||||
if (message.error && this.pending.has(id)) {
|
||||
const error = String(field(message.error, "message") || "Codex request failed");
|
||||
logger.warn("Codex request failed", { id, error });
|
||||
return this.reject(id, error);
|
||||
}
|
||||
if (this.pending.has(id)) return this.resolve(id, message.result);
|
||||
if (typeof message.method === "string" && "id" in message) return this.answerServerRequest(message);
|
||||
if (typeof message.method === "string") this.handleNotification(message.method, (message.params || {}) as Json);
|
||||
@@ -274,7 +277,11 @@ class CodexAppClient {
|
||||
|
||||
private handleNotification(method: string, params: Json) {
|
||||
if (method === "item/agentMessage/delta") return this.emitDelta(params);
|
||||
if (method === "thread/tokenUsage/updated") this.lastUsage = normalizeUsage(params);
|
||||
if (method === "thread/tokenUsage/updated") {
|
||||
this.lastUsage = normalizeUsage(params);
|
||||
this.emit("agent_event", { agent: "codex", type: "usage.updated", usage: this.lastUsage, ...codexEventScope(params) });
|
||||
return;
|
||||
}
|
||||
const event = normalizeCodexNotification(method, params);
|
||||
if (!event) return;
|
||||
if (event.type === "item.completed") {
|
||||
@@ -296,8 +303,6 @@ class CodexAppClient {
|
||||
} else if (turnId) {
|
||||
this.completedTurns.set(turnId, error ? new Error(String(field(error, "message") || "Codex turn failed")) : null);
|
||||
}
|
||||
this.emit("agent_event", { agent: "codex", type: "stream.summary", delta_count: this.deltaCount, ...codexEventScope(params) });
|
||||
this.deltaCount = 0;
|
||||
this.emit("agent_done", { agent: "codex", usage: event.usage, ...codexEventScope(params) });
|
||||
}
|
||||
}
|
||||
@@ -305,7 +310,6 @@ class CodexAppClient {
|
||||
private emitDelta(params: Json) {
|
||||
const id = String(field(params, "itemId") || "");
|
||||
const text = `${this.textByItem.get(id) || ""}${String(field(params, "delta") || "")}`;
|
||||
this.deltaCount += 1;
|
||||
this.textByItem.set(id, text);
|
||||
this.emit("agent_event", { agent: "codex", type: "item.updated", item: { id, type: "agent_message", text }, ...codexEventScope(params) });
|
||||
}
|
||||
@@ -353,7 +357,7 @@ function normalizeCodexNotification(method: string, params: Json): AgentEvent |
|
||||
const scope = codexEventScope(params);
|
||||
if (method === "thread/started") return { type: "thread.started", ...scope };
|
||||
if (method === "turn/started") return { type: "turn.started", ...scope };
|
||||
if (method === "turn/completed") return { type: "turn.completed", usage: null, ...scope };
|
||||
if (method === "turn/completed") return { type: "turn.completed", usage: null, duration_ms: field(field(params, "turn"), "durationMs"), ...scope };
|
||||
if (method === "item/started") return { type: "item.started", item: normalizeItem(field(params, "item")), ...scope };
|
||||
if (method === "item/completed") return { type: "item.completed", item: normalizeItem(field(params, "item")), ...scope };
|
||||
if (method === "error") return { type: "error", message: field(params, "message"), ...scope };
|
||||
@@ -405,12 +409,12 @@ function normalizeItem(item: unknown) {
|
||||
}
|
||||
|
||||
function normalizeUsage(params: Json) {
|
||||
const total = field(field(params, "tokenUsage"), "total") as Json | undefined;
|
||||
const last = field(field(params, "tokenUsage"), "last") as Json | undefined;
|
||||
return {
|
||||
input_tokens: field(total, "inputTokens"),
|
||||
cached_input_tokens: field(total, "cachedInputTokens"),
|
||||
output_tokens: field(total, "outputTokens"),
|
||||
reasoning_output_tokens: field(total, "reasoningOutputTokens"),
|
||||
input_tokens: field(last, "inputTokens"),
|
||||
cached_input_tokens: field(last, "cachedInputTokens"),
|
||||
output_tokens: field(last, "outputTokens"),
|
||||
reasoning_output_tokens: field(last, "reasoningOutputTokens"),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ export function startHttpServer() {
|
||||
const emit = (type: string, payload: unknown) => {
|
||||
const data = payload && typeof payload === "object" && !Array.isArray(payload) ? payload as Record<string, unknown> : { value: payload };
|
||||
const threadId = String(data.threadId || data.thread_id || ensureSiteWorkspace(config).activeThreadId || "");
|
||||
logger.debug("Agent event", { type, threadId, payload: data });
|
||||
threadId ? session.emitThread(type, threadId, data) : session.emitAll(type, data);
|
||||
};
|
||||
const setActiveThread = (activeThreadId: string, payload: Record<string, unknown> = {}) => {
|
||||
@@ -31,7 +30,10 @@ export function startHttpServer() {
|
||||
if (!logger.enabled) return next();
|
||||
const startedAt = Date.now();
|
||||
const url = requestUrl(req, config);
|
||||
res.on("finish", () => logger.debug("HTTP request", { method: req.method, path: url.pathname, status: res.statusCode, durationMs: Date.now() - startedAt, origin: req.headers.origin, clientId: url.searchParams.get("clientId") }));
|
||||
res.on("finish", () => {
|
||||
if (req.method === "OPTIONS" || (res.statusCode < 400 && ["/health", "/canvas/state", "/canvas/activate"].includes(url.pathname))) return;
|
||||
logger.debug(`HTTP ${req.method} ${url.pathname}`, { status: res.statusCode, durationMs: Date.now() - startedAt });
|
||||
});
|
||||
next();
|
||||
});
|
||||
app.use((req, res, next) => {
|
||||
@@ -117,7 +119,7 @@ export function startHttpServer() {
|
||||
const prompt = String(req.body?.prompt || "");
|
||||
if (!prompt.trim()) return res.status(400).json({ ok: false, error: "请输入任务内容" });
|
||||
const clientId = String(req.body?.clientId || "");
|
||||
logger.info("Codex turn accepted", { clientId, threadId: req.body?.threadId, prompt, attachments: attachments.map(({ id, name, type, size, width, height }) => ({ id, name, type, size, width, height })) });
|
||||
logger.info("Codex turn accepted", { threadId: req.body?.threadId, promptLength: prompt.length, attachmentCount: attachments.length });
|
||||
session.setCodexState({ busy: true, threadId: String(req.body?.threadId || workspace.activeThreadId || ""), turnId: "" });
|
||||
try {
|
||||
let threadId = String(req.body?.threadId || workspace.activeThreadId || "");
|
||||
|
||||
@@ -20,11 +20,11 @@ export class Logger {
|
||||
return;
|
||||
}
|
||||
fs.mkdirSync(path.dirname(this.filePath), {recursive: true});
|
||||
const line = format.printf(({level, message, timestamp, details}) => `[${level.toUpperCase()}][${timestamp}] ${message}${formatDetails(details)}`);
|
||||
const line = format.printf(({level, message, timestamp, details}) => `${timestamp} ${level.toUpperCase()} ${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.Console({format: format.combine(format.timestamp({format: "HH:mm:ss"}), line)}),
|
||||
new transports.File({filename: this.filePath, format: format.combine(format.timestamp({format: "HH:mm:ss"}), line)}),
|
||||
],
|
||||
});
|
||||
@@ -59,7 +59,8 @@ export class Logger {
|
||||
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(" ")}`;
|
||||
const text = Object.entries(details).filter(([, value]) => value !== undefined).map(([key, value]) => `${key}=${inspect(value, {depth: null, breakLength: Infinity})}`).join(" ");
|
||||
return text ? ` ${text}` : "";
|
||||
}
|
||||
|
||||
/** 清理日志内容中的敏感数据和不可序列化引用。 */
|
||||
|
||||
Reference in New Issue
Block a user