diff --git a/src/logger/runtime-logger.ts b/src/logger/runtime-logger.ts index 1d695c2..2d93592 100644 --- a/src/logger/runtime-logger.ts +++ b/src/logger/runtime-logger.ts @@ -15,6 +15,13 @@ const LOG_LEVEL_PRIORITY: Record = { error: 3 }; +function shouldLog( + level: RuntimeLogLevel, + minimumLevel: RuntimeLogLevel +): boolean { + return LOG_LEVEL_PRIORITY[level] >= LOG_LEVEL_PRIORITY[minimumLevel]; +} + export interface ConsoleRuntimeLoggerOptions { level?: RuntimeLogLevel; redactKeys?: RegExp; @@ -135,6 +142,7 @@ export class InMemoryRuntimeLogger implements RuntimeLogger { private readonly redactKeys: RegExp; public constructor(options: InMemoryRuntimeLoggerOptions = {}) { + this.minimumLevel = options.level ?? "debug"; this.maxEntries = options.maxEntries ?? 5_000; this.minimumLevel = options.level; this.redactKeys = options.redactKeys ?? DEFAULT_REDACT_KEYS; diff --git a/tests/logger/console-runtime-logger-level.test.ts b/tests/logger/console-runtime-logger-level.test.ts index 1797336..9de1db7 100644 --- a/tests/logger/console-runtime-logger-level.test.ts +++ b/tests/logger/console-runtime-logger-level.test.ts @@ -1,17 +1,26 @@ -import { describe, it, expect, vi, beforeEach } from "vitest"; -import { ConsoleRuntimeLogger } from "../../src/logger/runtime-logger.js"; +import { afterEach, describe, it, expect, vi, beforeEach } from "vitest"; +import { + ConsoleRuntimeLogger, + InMemoryRuntimeLogger +} from "../../src/logger/runtime-logger.js"; describe("ConsoleRuntimeLogger level filtering", () => { let infoSpy: ReturnType; + let debugSpy: ReturnType; let warnSpy: ReturnType; let errorSpy: ReturnType; beforeEach(() => { infoSpy = vi.spyOn(console, "info").mockImplementation(() => {}); + debugSpy = vi.spyOn(console, "debug").mockImplementation(() => {}); warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); }); + afterEach(() => { + vi.restoreAllMocks(); + }); + it("logs all levels when level=info (default)", () => { const logger = new ConsoleRuntimeLogger(); logger.info("i"); @@ -41,4 +50,27 @@ describe("ConsoleRuntimeLogger level filtering", () => { expect(warnSpy).toHaveBeenCalledTimes(1); expect(errorSpy).toHaveBeenCalledTimes(1); }); + + it("matches in-memory filtering at the same configured threshold", () => { + const consoleLogger = new ConsoleRuntimeLogger({ level: "warn" }); + const memoryLogger = new InMemoryRuntimeLogger({ level: "warn" }); + + consoleLogger.debug("debug"); + consoleLogger.info("info"); + consoleLogger.warn("warn"); + consoleLogger.error("error"); + memoryLogger.debug("debug"); + memoryLogger.info("info"); + memoryLogger.warn("warn"); + memoryLogger.error("error"); + + expect(debugSpy).not.toHaveBeenCalled(); + expect(infoSpy).not.toHaveBeenCalled(); + expect(warnSpy).toHaveBeenCalledTimes(1); + expect(errorSpy).toHaveBeenCalledTimes(1); + expect(memoryLogger.entries.map((entry) => entry.level)).toEqual([ + "warn", + "error" + ]); + }); }); diff --git a/tests/logger/runtime-logger.test.ts b/tests/logger/runtime-logger.test.ts index c42694b..2674806 100644 --- a/tests/logger/runtime-logger.test.ts +++ b/tests/logger/runtime-logger.test.ts @@ -5,6 +5,72 @@ import { } from "../../src/logger/runtime-logger.js"; describe("InMemoryRuntimeLogger", () => { + it("records all supported levels by default", () => { + const logger = new InMemoryRuntimeLogger(); + + logger.debug("debug"); + logger.info("info"); + logger.warn("warn"); + logger.error("error"); + + expect(logger.entries.map((entry) => entry.level)).toEqual([ + "debug", + "info", + "warn", + "error" + ]); + expect(logger.size()).toBe(4); + }); + + it("filters entries below the configured threshold", () => { + const logger = new InMemoryRuntimeLogger({ level: "warn" }); + + logger.debug("debug"); + logger.info("info"); + logger.warn("warn"); + logger.error("error"); + + expect(logger.entries.map((entry) => entry.level)).toEqual([ + "warn", + "error" + ]); + expect(logger.size()).toBe(2); + }); + + it("preserves retained order and clear semantics after filtering", () => { + const logger = new InMemoryRuntimeLogger({ level: "warn" }); + + logger.info("ignored"); + logger.warn("first"); + logger.debug("also ignored"); + logger.error("second"); + + expect(logger.entries.map((entry) => entry.message)).toEqual([ + "first", + "second" + ]); + expect(logger.size()).toBe(2); + + logger.clear(); + + expect(logger.entries).toEqual([]); + expect(logger.size()).toBe(0); + }); + + it("does not let filtered entries affect maxEntries eviction", () => { + const logger = new InMemoryRuntimeLogger({ level: "warn", maxEntries: 2 }); + + logger.warn("first"); + logger.error("second"); + logger.info("ignored"); + logger.warn("third"); + + expect(logger.entries.map((entry) => entry.message)).toEqual([ + "second", + "third" + ]); + }); + it("records info entries with correct level and message", () => { const logger = new InMemoryRuntimeLogger(); logger.info("test message");