Add configurable structured logging
Adds a leveled JSON logger (error/warn/info/verbose/debug, controlled via LOG_LEVEL) wired into the Express API (access log, error middleware, crash handlers, memory heartbeat) and the Next.js server (page-request proxy, instrumentation crash handlers, heartbeat). LOG_LEVEL is exposed through compose.yaml, and both services now rotate their Docker logs (json-file, 20m x 10 files) instead of growing unbounded. Intended to capture long-running diagnostic data for the intermittent 502s seen on the Docker host. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
f26c000007
commit
ea3c02cd6b
12 changed files with 317 additions and 6 deletions
83
tests/logger.test.ts
Normal file
83
tests/logger.test.ts
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { describe, it } from "node:test";
|
||||
import { createLogger, resolveLogLevel } from "../src/shared/logging/logger.js";
|
||||
|
||||
describe("resolveLogLevel", () => {
|
||||
it("defaults to info for missing or unknown values", () => {
|
||||
assert.equal(resolveLogLevel(undefined), "info");
|
||||
assert.equal(resolveLogLevel(""), "info");
|
||||
assert.equal(resolveLogLevel("nonsense"), "info");
|
||||
});
|
||||
|
||||
it("accepts known levels case-insensitively", () => {
|
||||
assert.equal(resolveLogLevel("DEBUG"), "debug");
|
||||
assert.equal(resolveLogLevel(" verbose "), "verbose");
|
||||
assert.equal(resolveLogLevel("warn"), "warn");
|
||||
});
|
||||
});
|
||||
|
||||
describe("createLogger", () => {
|
||||
it("suppresses levels below the configured threshold", () => {
|
||||
const infoLines: string[] = [];
|
||||
const originalLog = console.log;
|
||||
console.log = (line: string) => {
|
||||
infoLines.push(line);
|
||||
};
|
||||
try {
|
||||
const logger = createLogger("test", { level: "warn" });
|
||||
logger.info("should not appear");
|
||||
logger.debug("should not appear");
|
||||
logger.verbose("should not appear");
|
||||
} finally {
|
||||
console.log = originalLog;
|
||||
}
|
||||
assert.equal(infoLines.length, 0);
|
||||
});
|
||||
|
||||
it("emits enabled levels as structured JSON with scope and meta", () => {
|
||||
const errorLines: string[] = [];
|
||||
const originalError = console.error;
|
||||
console.error = (line: string) => {
|
||||
errorLines.push(line);
|
||||
};
|
||||
try {
|
||||
const logger = createLogger("test", { level: "warn" });
|
||||
logger.warn("something happened", { code: 42 });
|
||||
} finally {
|
||||
console.error = originalError;
|
||||
}
|
||||
assert.equal(errorLines.length, 1);
|
||||
const parsed = JSON.parse(errorLines[0]);
|
||||
assert.equal(parsed.level, "warn");
|
||||
assert.equal(parsed.scope, "test");
|
||||
assert.equal(parsed.message, "something happened");
|
||||
assert.equal(parsed.code, 42);
|
||||
assert.equal(typeof parsed.timestamp, "string");
|
||||
});
|
||||
|
||||
it("routes debug/verbose/info to console.log and error/warn to console.error", () => {
|
||||
const logLines: string[] = [];
|
||||
const errorLines: string[] = [];
|
||||
const originalLog = console.log;
|
||||
const originalError = console.error;
|
||||
console.log = (line: string) => {
|
||||
logLines.push(line);
|
||||
};
|
||||
console.error = (line: string) => {
|
||||
errorLines.push(line);
|
||||
};
|
||||
try {
|
||||
const logger = createLogger("test", { level: "debug" });
|
||||
logger.debug("d");
|
||||
logger.verbose("v");
|
||||
logger.info("i");
|
||||
logger.warn("w");
|
||||
logger.error("e");
|
||||
} finally {
|
||||
console.log = originalLog;
|
||||
console.error = originalError;
|
||||
}
|
||||
assert.equal(logLines.length, 3);
|
||||
assert.equal(errorLines.length, 2);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue