Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/logger/runtime-logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ const LOG_LEVEL_PRIORITY: Record<RuntimeLogLevel, number> = {
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;
Expand Down Expand Up @@ -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;
Expand Down
36 changes: 34 additions & 2 deletions tests/logger/console-runtime-logger-level.test.ts
Original file line number Diff line number Diff line change
@@ -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<typeof vi.spyOn>;
let debugSpy: ReturnType<typeof vi.spyOn>;
let warnSpy: ReturnType<typeof vi.spyOn>;
let errorSpy: ReturnType<typeof vi.spyOn>;

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");
Expand Down Expand Up @@ -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"
]);
});
});
66 changes: 66 additions & 0 deletions tests/logger/runtime-logger.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
Loading