import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; const { readLoggingConfigMock, shouldSkipMutatingLoggingConfigReadMock } = vi.hoisted(() => ({ readLoggingConfigMock: vi.fn(() => undefined), shouldSkipMutatingLoggingConfigReadMock: vi.fn(() => false), })); vi.mock("./config.js", () => ({ readLoggingConfig: readLoggingConfigMock, shouldSkipMutatingLoggingConfigRead: shouldSkipMutatingLoggingConfigReadMock, })); vi.mock("./node-require.js", () => ({ resolveNodeRequireFromMeta: () => () => { throw new Error("config fallback not used"); }, })); let logging: typeof import("../logging.js"); beforeAll(async () => { logging = await import("../logging.js"); }); beforeEach(() => { delete process.env.OPENCLAW_TEST_FILE_LOG; delete process.env.OPENCLAW_LOG_LEVEL; readLoggingConfigMock.mockClear(); shouldSkipMutatingLoggingConfigReadMock.mockReset(); shouldSkipMutatingLoggingConfigReadMock.mockReturnValue(false); logging.resetLogger(); logging.setLoggerOverride(null); }); afterEach(() => { delete process.env.OPENCLAW_TEST_FILE_LOG; delete process.env.OPENCLAW_LOG_LEVEL; logging.resetLogger(); logging.setLoggerOverride(null); vi.restoreAllMocks(); }); describe("isFileLogLevelEnabled", () => { it("returns false for all levels when configured as silent", () => { logging.setLoggerOverride({ level: "silent" }); expect(logging.isFileLogLevelEnabled("fatal")).toBe(false); expect(logging.isFileLogLevelEnabled("error")).toBe(false); expect(logging.isFileLogLevelEnabled("warn")).toBe(false); expect(logging.isFileLogLevelEnabled("info")).toBe(false); expect(logging.isFileLogLevelEnabled("debug")).toBe(false); expect(logging.isFileLogLevelEnabled("trace")).toBe(false); }); it("passes only fatal when configured as fatal", () => { logging.setLoggerOverride({ level: "fatal" }); expect(logging.isFileLogLevelEnabled("fatal")).toBe(true); expect(logging.isFileLogLevelEnabled("error")).toBe(false); expect(logging.isFileLogLevelEnabled("warn")).toBe(false); expect(logging.isFileLogLevelEnabled("info")).toBe(false); expect(logging.isFileLogLevelEnabled("debug")).toBe(false); expect(logging.isFileLogLevelEnabled("trace")).toBe(false); }); it("passes fatal and error when configured as error", () => { logging.setLoggerOverride({ level: "error" }); expect(logging.isFileLogLevelEnabled("fatal")).toBe(true); expect(logging.isFileLogLevelEnabled("error")).toBe(true); expect(logging.isFileLogLevelEnabled("warn")).toBe(false); expect(logging.isFileLogLevelEnabled("info")).toBe(false); expect(logging.isFileLogLevelEnabled("debug")).toBe(false); expect(logging.isFileLogLevelEnabled("trace")).toBe(false); }); it("passes fatal, error, warn, info when configured as info", () => { logging.setLoggerOverride({ level: "info" }); expect(logging.isFileLogLevelEnabled("fatal")).toBe(true); expect(logging.isFileLogLevelEnabled("error")).toBe(true); expect(logging.isFileLogLevelEnabled("warn")).toBe(true); expect(logging.isFileLogLevelEnabled("info")).toBe(true); expect(logging.isFileLogLevelEnabled("debug")).toBe(false); expect(logging.isFileLogLevelEnabled("trace")).toBe(false); }); it("passes all levels when configured as trace", () => { logging.setLoggerOverride({ level: "trace" }); expect(logging.isFileLogLevelEnabled("fatal")).toBe(true); expect(logging.isFileLogLevelEnabled("error")).toBe(true); expect(logging.isFileLogLevelEnabled("warn")).toBe(true); expect(logging.isFileLogLevelEnabled("info")).toBe(true); expect(logging.isFileLogLevelEnabled("debug")).toBe(true); expect(logging.isFileLogLevelEnabled("trace")).toBe(true); }); it("never treats silent as an emittable file level", () => { logging.setLoggerOverride({ level: "info" }); expect(logging.isFileLogLevelEnabled("silent")).toBe(false); }); }); describe("getChildLogger minLevel inheritance", () => { it("child logger inherits parent minLevel when no level is specified", () => { logging.setLoggerOverride({ level: "warn" }); const child = logging.getChildLogger({ component: "test" }); expect(child.settings.minLevel).toBe(logging.levelToMinLevel("warn")); }); it("child logger uses its own level when explicitly specified", () => { logging.setLoggerOverride({ level: "warn" }); const child = logging.getChildLogger({ component: "test" }, { level: "error" }); expect(child.settings.minLevel).toBe(logging.levelToMinLevel("error")); }); it("child logger does not default to minLevel=0 (allow-all) when no level given", () => { logging.setLoggerOverride({ level: "fatal" }); const child = logging.getChildLogger({ component: "test" }); expect(child.settings.minLevel).not.toBe(0); expect(child.settings.minLevel).toBe(logging.levelToMinLevel("fatal")); }); it("pino child logger propagates the parent minLevel", () => { logging.setLoggerOverride({ level: "error" }); const base = logging.getLogger(); const getSubLoggerSpy = vi.spyOn(base, "getSubLogger"); logging.toPinoLikeLogger(base, "info").child({ component: "test" }); expect(getSubLoggerSpy).toHaveBeenCalledWith( expect.objectContaining({ minLevel: logging.levelToMinLevel("error"), }), ); }); });