Files
rdv/src/lib/server/logger.ts
2026-01-05 22:40:27 +02:00

144 lines
3.8 KiB
TypeScript

import { env } from "$env/dynamic/private";
import path from "path";
import util from "util";
import winston from "winston";
import DailyRotateFile from "winston-daily-rotate-file";
import type { Err } from "./result";
process.on("warning", (warning) => {
const msg = String(warning?.message || "");
const name = String((warning as any)?.name || "");
// Ignore the noisy timer warning from Node/kafkajs interplay
if (
name === "TimeoutNegativeWarning" ||
msg.includes("TimeoutNegativeWarning") ||
msg.includes("Timeout duration was set to 1")
) {
return;
}
// Keep other warnings visible
console.warn(warning);
});
const levels = {
error: 0,
warn: 1,
info: 2,
http: 3,
debug: 4,
};
const colors = {
error: "red",
warn: "yellow",
info: "green",
http: "magenta",
debug: "white",
};
const level = () => {
const envLevel = env.LOG_LEVEL?.toLowerCase();
if (envLevel && envLevel in levels) {
return envLevel;
}
return env.NODE_ENV === "development" ? "debug" : "warn";
};
// Console format with colors
const consoleFormat = winston.format.combine(
winston.format.errors({ stack: true }),
winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss:ms" }),
winston.format.colorize({ all: true }),
winston.format.printf((info: any) => {
const { level, message, timestamp, ...extra } = info;
let formattedMessage = "";
if (message instanceof Error) {
formattedMessage = message.stack || message.message;
} else if (typeof message === "object") {
formattedMessage = util.inspect(message, {
depth: null,
colors: true,
});
} else {
formattedMessage = message as any as string;
}
// Handle extra fields (if any)
const formattedExtra =
Object.keys(extra).length > 0
? `\n${util.inspect(extra, { depth: null, colors: true })}`
: "";
return `[${level}] ${timestamp}: ${formattedMessage}${formattedExtra}`;
}),
);
// JSON format for file logging
const fileFormat = winston.format.combine(
winston.format.errors({ stack: true }),
winston.format.timestamp(),
winston.format.json(),
);
// Log directory - use logs folder in project root
const logDir = path.join(process.cwd(), "logs");
// Daily rotate file transport for all logs
const dailyRotateFileTransport = new DailyRotateFile({
filename: path.join(logDir, "app-%DATE%.log"),
datePattern: "YYYY-MM-DD",
zippedArchive: true,
maxSize: "20m",
maxFiles: "14d", // Keep logs for 14 days
format: fileFormat,
});
// Daily rotate file transport for errors only
const dailyRotateErrorTransport = new DailyRotateFile({
filename: path.join(logDir, "error-%DATE%.log"),
datePattern: "YYYY-MM-DD",
zippedArchive: true,
maxSize: "20m",
maxFiles: "30d", // Keep error logs for 30 days
level: "error",
format: fileFormat,
});
// Build transports
const transports: winston.transport[] = [
new winston.transports.Console({ format: consoleFormat }),
dailyRotateFileTransport,
dailyRotateErrorTransport,
];
winston.addColors(colors);
const logger = winston.createLogger({
level: level(),
levels,
transports,
format: fileFormat, // Default format for all transports
exceptionHandlers: [dailyRotateFileTransport, dailyRotateErrorTransport],
rejectionHandlers: [dailyRotateFileTransport, dailyRotateErrorTransport],
});
const stream = { write: (message: string) => logger.http(message.trim()) };
function getError(payload: Err, error?: any) {
logger.error(JSON.stringify({ payload, error }, null, 2));
console.error(error);
return {
code: payload.code,
message: payload.message,
description: payload.description,
detail: payload.detail,
error: error instanceof Error ? error.message : error,
actionable: payload.actionable,
} as Err;
}
export { getError, logger, stream };