diff --git a/src/app/api/logs/console/route.ts b/src/app/api/logs/console/route.ts index cf12058825..3c7f5a4dbd 100644 --- a/src/app/api/logs/console/route.ts +++ b/src/app/api/logs/console/route.ts @@ -26,10 +26,10 @@ const LEVEL_ORDER: Record = { // Map pino numeric levels to string levels const NUMERIC_LEVEL_MAP: Record = { 10: "trace", - 20: "info", - 30: "warn", - 40: "error", - 50: "fatal", + 20: "debug", + 30: "info", + 40: "warn", + 50: "error", 60: "fatal", }; diff --git a/src/shared/utils/logger.ts b/src/shared/utils/logger.ts index f47f527c75..a5f8f414d9 100644 --- a/src/shared/utils/logger.ts +++ b/src/shared/utils/logger.ts @@ -30,6 +30,14 @@ const baseConfig: pino.LoggerOptions = { }, }; +function getTransportCompatibleConfig(): pino.LoggerOptions { + const { formatters, ...rest } = baseConfig; + if (!formatters) return rest; + + const { level: _levelFormatter, ...safeFormatters } = formatters; + return Object.keys(safeFormatters).length > 0 ? { ...rest, formatters: safeFormatters } : rest; +} + /** * Build the logger with optional file transport. * Uses pino transport targets for all destinations. @@ -37,6 +45,7 @@ const baseConfig: pino.LoggerOptions = { function buildLogger(): pino.Logger { const logConfig = getLogConfig(); const logLevel = (baseConfig.level as string) || "info"; + const transportConfig = getTransportCompatibleConfig(); // If file logging is enabled, set up dual transport (stdout + file) if (logConfig.logToFile) { @@ -50,7 +59,7 @@ function buildLogger(): pino.Logger { if (isDev) { // Dev: pino-pretty → stdout, JSON → file return pino({ - ...baseConfig, + ...transportConfig, transport: { targets: [ { @@ -76,7 +85,7 @@ function buildLogger(): pino.Logger { // Production: JSON → stdout + JSON → file return pino({ - ...baseConfig, + ...transportConfig, transport: { targets: [ { diff --git a/tests/unit/console-log-levels.test.mjs b/tests/unit/console-log-levels.test.mjs new file mode 100644 index 0000000000..d268f7aba0 --- /dev/null +++ b/tests/unit/console-log-levels.test.mjs @@ -0,0 +1,54 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const TEST_LOG_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-console-log-levels-")); +const TEST_LOG_PATH = path.join(TEST_LOG_DIR, "app.log"); + +const originalLogFilePath = process.env.LOG_FILE_PATH; +process.env.LOG_FILE_PATH = TEST_LOG_PATH; + +const route = await import("../../src/app/api/logs/console/route.ts"); + +test.after(() => { + if (originalLogFilePath === undefined) { + delete process.env.LOG_FILE_PATH; + } else { + process.env.LOG_FILE_PATH = originalLogFilePath; + } + fs.rmSync(TEST_LOG_DIR, { recursive: true, force: true }); +}); + +test("console log API normalizes numeric pino levels correctly", async () => { + fs.writeFileSync( + TEST_LOG_PATH, + [ + JSON.stringify({ + timestamp: new Date().toISOString(), + level: 30, + module: "probe", + msg: "info entry", + }), + JSON.stringify({ + timestamp: new Date().toISOString(), + level: 40, + module: "probe", + msg: "warn entry", + }), + ].join("\n") + "\n", + "utf8" + ); + + const response = await route.GET( + new Request("http://localhost/api/logs/console?level=info&limit=10") + ); + const body = await response.json(); + + assert.equal(response.status, 200); + assert.deepEqual( + body.map((entry) => entry.level), + ["info", "warn"] + ); +});