feat: add backend logger matching frontend text format
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -13,17 +13,14 @@ import { schema } from "./graphql/index";
|
|||||||
import { buildContext } from "./graphql/context";
|
import { buildContext } from "./graphql/context";
|
||||||
import { db } from "./db/connection";
|
import { db } from "./db/connection";
|
||||||
import { redis } from "./lib/auth";
|
import { redis } from "./lib/auth";
|
||||||
|
import { logger } from "./lib/logger";
|
||||||
|
|
||||||
const PORT = parseInt(process.env.PORT || "4000");
|
const PORT = parseInt(process.env.PORT || "4000");
|
||||||
const UPLOAD_DIR = process.env.UPLOAD_DIR || "/data/uploads";
|
const UPLOAD_DIR = process.env.UPLOAD_DIR || "/data/uploads";
|
||||||
const CORS_ORIGIN = process.env.CORS_ORIGIN || "http://localhost:3000";
|
const CORS_ORIGIN = process.env.CORS_ORIGIN || "http://localhost:3000";
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const fastify = Fastify({
|
const fastify = Fastify({ loggerInstance: logger });
|
||||||
logger: {
|
|
||||||
level: process.env.LOG_LEVEL || "info",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await fastify.register(fastifyCookie, {
|
await fastify.register(fastifyCookie, {
|
||||||
secret: process.env.COOKIE_SECRET || "change-me-in-production",
|
secret: process.env.COOKIE_SECRET || "change-me-in-production",
|
||||||
|
|||||||
91
packages/backend/src/lib/logger.ts
Normal file
91
packages/backend/src/lib/logger.ts
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
type LogLevel = "trace" | "debug" | "info" | "warn" | "error" | "fatal";
|
||||||
|
|
||||||
|
const LEVEL_VALUES: Record<LogLevel, number> = {
|
||||||
|
trace: 10,
|
||||||
|
debug: 20,
|
||||||
|
info: 30,
|
||||||
|
warn: 40,
|
||||||
|
error: 50,
|
||||||
|
fatal: 60,
|
||||||
|
};
|
||||||
|
|
||||||
|
function createLogger(bindings: Record<string, unknown> = {}, initialLevel: LogLevel = "info") {
|
||||||
|
let currentLevel = initialLevel;
|
||||||
|
|
||||||
|
function shouldLog(level: LogLevel): boolean {
|
||||||
|
return LEVEL_VALUES[level] >= LEVEL_VALUES[currentLevel];
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatMessage(level: LogLevel, arg: unknown, msg?: string): string {
|
||||||
|
const timestamp = new Date().toISOString();
|
||||||
|
|
||||||
|
let message: string;
|
||||||
|
const meta: Record<string, unknown> = { ...bindings };
|
||||||
|
|
||||||
|
if (typeof arg === "string") {
|
||||||
|
message = arg;
|
||||||
|
} else if (arg !== null && typeof arg === "object") {
|
||||||
|
// Pino-style: log(obj, msg?) — strip internal pino keys
|
||||||
|
const { msg: m, level: _l, time: _t, pid: _p, hostname: _h, req: _req, res: _res, reqId, ...rest } = arg as Record<string, unknown>;
|
||||||
|
message = msg || (typeof m === "string" ? m : "");
|
||||||
|
if (reqId) meta.reqId = reqId;
|
||||||
|
Object.assign(meta, rest);
|
||||||
|
} else {
|
||||||
|
message = String(arg ?? "");
|
||||||
|
}
|
||||||
|
|
||||||
|
const parts = [`[${timestamp}]`, `[${level.toUpperCase()}]`, message];
|
||||||
|
let result = parts.join(" ");
|
||||||
|
|
||||||
|
const metaEntries = Object.entries(meta).filter(([k]) => k !== "reqId");
|
||||||
|
const reqId = meta.reqId;
|
||||||
|
if (reqId) result = `[${timestamp}] [${level.toUpperCase()}] [${reqId}] ${message}`;
|
||||||
|
|
||||||
|
if (metaEntries.length > 0) {
|
||||||
|
result += " " + JSON.stringify(Object.fromEntries(metaEntries));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function write(level: LogLevel, arg: unknown, msg?: string) {
|
||||||
|
if (!shouldLog(level)) return;
|
||||||
|
const formatted = formatMessage(level, arg, msg);
|
||||||
|
switch (level) {
|
||||||
|
case "trace":
|
||||||
|
case "debug":
|
||||||
|
console.debug(formatted);
|
||||||
|
break;
|
||||||
|
case "info":
|
||||||
|
console.info(formatted);
|
||||||
|
break;
|
||||||
|
case "warn":
|
||||||
|
console.warn(formatted);
|
||||||
|
break;
|
||||||
|
case "error":
|
||||||
|
case "fatal":
|
||||||
|
console.error(formatted);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
get level() {
|
||||||
|
return currentLevel;
|
||||||
|
},
|
||||||
|
set level(l: string) {
|
||||||
|
currentLevel = l as LogLevel;
|
||||||
|
},
|
||||||
|
trace: (arg: unknown, msg?: string) => write("trace", arg, msg),
|
||||||
|
debug: (arg: unknown, msg?: string) => write("debug", arg, msg),
|
||||||
|
info: (arg: unknown, msg?: string) => write("info", arg, msg),
|
||||||
|
warn: (arg: unknown, msg?: string) => write("warn", arg, msg),
|
||||||
|
error: (arg: unknown, msg?: string) => write("error", arg, msg),
|
||||||
|
fatal: (arg: unknown, msg?: string) => write("fatal", arg, msg),
|
||||||
|
silent: () => {},
|
||||||
|
child: (newBindings: Record<string, unknown>) =>
|
||||||
|
createLogger({ ...bindings, ...newBindings }, currentLevel),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const logger = createLogger({}, (process.env.LOG_LEVEL as LogLevel) || "info");
|
||||||
Reference in New Issue
Block a user