feat(logging): update logging format to plain text and enhance HTTP diagnostics

This commit is contained in:
HouYunFei
2026-07-29 10:28:24 +08:00
parent b3b02f75a9
commit 92fd0ce129
8 changed files with 123 additions and 93 deletions
+19 -15
View File
@@ -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"),
};
}
+5 -3
View File
@@ -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 || "");
+4 -3
View File
@@ -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}` : "";
}
/** 清理日志内容中的敏感数据和不可序列化引用。 */