Spaces:
Paused
Paused
| import crypto from "node:crypto"; | |
| import os from "node:os"; | |
| import path from "node:path"; | |
| import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; | |
| import { | |
| enableConsoleCapture, | |
| resetLogger, | |
| routeLogsToStderr, | |
| setConsoleTimestampPrefix, | |
| setLoggerOverride, | |
| } from "../logging.js"; | |
| import { loggingState } from "./state.js"; | |
| type ConsoleSnapshot = { | |
| log: typeof console.log; | |
| info: typeof console.info; | |
| warn: typeof console.warn; | |
| error: typeof console.error; | |
| debug: typeof console.debug; | |
| trace: typeof console.trace; | |
| }; | |
| let snapshot: ConsoleSnapshot; | |
| beforeEach(() => { | |
| snapshot = { | |
| log: console.log, | |
| info: console.info, | |
| warn: console.warn, | |
| error: console.error, | |
| debug: console.debug, | |
| trace: console.trace, | |
| }; | |
| loggingState.consolePatched = false; | |
| loggingState.forceConsoleToStderr = false; | |
| loggingState.consoleTimestampPrefix = false; | |
| loggingState.rawConsole = null; | |
| resetLogger(); | |
| }); | |
| afterEach(() => { | |
| console.log = snapshot.log; | |
| console.info = snapshot.info; | |
| console.warn = snapshot.warn; | |
| console.error = snapshot.error; | |
| console.debug = snapshot.debug; | |
| console.trace = snapshot.trace; | |
| loggingState.consolePatched = false; | |
| loggingState.forceConsoleToStderr = false; | |
| loggingState.consoleTimestampPrefix = false; | |
| loggingState.rawConsole = null; | |
| resetLogger(); | |
| setLoggerOverride(null); | |
| vi.restoreAllMocks(); | |
| }); | |
| describe("enableConsoleCapture", () => { | |
| it("swallows EIO from stderr writes", () => { | |
| setLoggerOverride({ level: "info", file: tempLogPath() }); | |
| vi.spyOn(process.stderr, "write").mockImplementation(() => { | |
| throw eioError(); | |
| }); | |
| routeLogsToStderr(); | |
| enableConsoleCapture(); | |
| expect(() => console.log("hello")).not.toThrow(); | |
| }); | |
| it("swallows EIO from original console writes", () => { | |
| setLoggerOverride({ level: "info", file: tempLogPath() }); | |
| console.log = () => { | |
| throw eioError(); | |
| }; | |
| enableConsoleCapture(); | |
| expect(() => console.log("hello")).not.toThrow(); | |
| }); | |
| it("prefixes console output with timestamps when enabled", () => { | |
| setLoggerOverride({ level: "info", file: tempLogPath() }); | |
| const now = new Date("2026-01-17T18:01:02.000Z"); | |
| vi.useFakeTimers(); | |
| vi.setSystemTime(now); | |
| const warn = vi.fn(); | |
| console.warn = warn; | |
| setConsoleTimestampPrefix(true); | |
| enableConsoleCapture(); | |
| console.warn("[EventQueue] Slow listener detected"); | |
| expect(warn).toHaveBeenCalledTimes(1); | |
| const firstArg = String(warn.mock.calls[0]?.[0] ?? ""); | |
| expect(firstArg.startsWith("2026-01-17T18:01:02.000Z [EventQueue]")).toBe(true); | |
| vi.useRealTimers(); | |
| }); | |
| it("suppresses discord EventQueue slow listener duplicates", () => { | |
| setLoggerOverride({ level: "info", file: tempLogPath() }); | |
| const warn = vi.fn(); | |
| console.warn = warn; | |
| enableConsoleCapture(); | |
| console.warn( | |
| "[EventQueue] Slow listener detected: DiscordMessageListener took 12.3 seconds for event MESSAGE_CREATE", | |
| ); | |
| expect(warn).not.toHaveBeenCalled(); | |
| }); | |
| it("does not double-prefix timestamps", () => { | |
| setLoggerOverride({ level: "info", file: tempLogPath() }); | |
| const warn = vi.fn(); | |
| console.warn = warn; | |
| setConsoleTimestampPrefix(true); | |
| enableConsoleCapture(); | |
| console.warn("12:34:56 [exec] hello"); | |
| expect(warn).toHaveBeenCalledWith("12:34:56 [exec] hello"); | |
| }); | |
| it("leaves JSON output unchanged when timestamp prefix is enabled", () => { | |
| setLoggerOverride({ level: "info", file: tempLogPath() }); | |
| const log = vi.fn(); | |
| console.log = log; | |
| setConsoleTimestampPrefix(true); | |
| enableConsoleCapture(); | |
| const payload = JSON.stringify({ ok: true }); | |
| console.log(payload); | |
| expect(log).toHaveBeenCalledWith(payload); | |
| }); | |
| }); | |
| function tempLogPath() { | |
| return path.join(os.tmpdir(), `openclaw-log-${crypto.randomUUID()}.log`); | |
| } | |
| function eioError() { | |
| const err = new Error("EIO") as NodeJS.ErrnoException; | |
| err.code = "EIO"; | |
| return err; | |
| } | |