196 lines
4.6 KiB
TypeScript
196 lines
4.6 KiB
TypeScript
import { Config } from "lib/setup/config";
|
|
import SafeJSONStringify from "safe-json-stringify";
|
|
import { EscapeStringRegexp } from "utils/misc";
|
|
import winston, { format, transports } from "winston";
|
|
import "winston-daily-rotate-file";
|
|
|
|
const level = process.env.LOG_LEVEL ?? Config.LOGGER_CONFIG.LOG_LEVEL;
|
|
|
|
const formatExcessProperties = (meta: Record<string, unknown>, limit = false) => {
|
|
let i = 0;
|
|
|
|
for (const [key, val] of Object.entries(meta)) {
|
|
// this is probably fine
|
|
// eslint-disable-next-line cadence/no-instanceof
|
|
if (val instanceof Error) {
|
|
meta[key] = { message: val.message, stack: val.stack };
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
if (!i) {
|
|
return "";
|
|
}
|
|
|
|
const content = SafeJSONStringify(meta);
|
|
|
|
return ` ${limit ? StrCap(content) : content}`;
|
|
};
|
|
|
|
const formatExcessPropertiesNoStack = (
|
|
meta: Record<string, unknown>,
|
|
omitKeys: Array<string> = [],
|
|
limit = false
|
|
) => {
|
|
const realMeta: Record<string, unknown> = {};
|
|
|
|
for (const [key, val] of Object.entries(meta)) {
|
|
if (omitKeys.includes(key)) {
|
|
continue;
|
|
}
|
|
|
|
// this is probably fine
|
|
// eslint-disable-next-line cadence/no-instanceof
|
|
if (val instanceof Error) {
|
|
realMeta[key] = { message: val.message };
|
|
} else if (!key.startsWith("__") && !key.startsWith("!")) {
|
|
realMeta[key] = val;
|
|
}
|
|
}
|
|
|
|
if (Object.keys(realMeta).length === 0) {
|
|
return "";
|
|
}
|
|
|
|
const content = SafeJSONStringify(realMeta);
|
|
|
|
return ` ${limit ? StrCap(content) : content}`;
|
|
};
|
|
|
|
function StrCap(string: string) {
|
|
if (string.length > 300) {
|
|
return `${string.slice(0, 297)}...`;
|
|
}
|
|
|
|
return string;
|
|
}
|
|
|
|
const printf = format.printf(
|
|
({ level, message, context = "toa-root", timestamp, ...meta }) =>
|
|
`${timestamp} [${
|
|
Array.isArray(context) ? context.join(" | ") : context
|
|
}] ${level}: ${message}${formatExcessProperties(meta)}`
|
|
);
|
|
|
|
const consolePrintf = format.printf(
|
|
({ level, message, context = "toa-root", timestamp, hideFromConsole, ...meta }) =>
|
|
`${timestamp} [${
|
|
Array.isArray(context) ? context.join(" | ") : context
|
|
}] ${level}: ${message}${formatExcessPropertiesNoStack(
|
|
meta,
|
|
hideFromConsole as Array<string>,
|
|
true
|
|
)}`
|
|
);
|
|
|
|
winston.addColors({
|
|
crit: ["bgRed", "black"],
|
|
error: ["red"],
|
|
warn: ["yellow"],
|
|
info: ["blue"],
|
|
verbose: ["cyan"],
|
|
debug: ["white"],
|
|
});
|
|
|
|
const baseFormatter = format.combine(
|
|
format.timestamp({
|
|
format: "YYYY-MM-DD HH:mm:ss",
|
|
})
|
|
);
|
|
|
|
const defaultFormatter = format.combine(baseFormatter, format.errors({ stack: false }), printf);
|
|
|
|
const consoleFormatter = format.combine(
|
|
baseFormatter,
|
|
format.errors({ stack: false }),
|
|
consolePrintf,
|
|
format.colorize({
|
|
all: true,
|
|
})
|
|
);
|
|
|
|
const tports: Array<winston.transport> = [];
|
|
|
|
if (Config.LOGGER_CONFIG.CONSOLE || process.env.FORCE_CONSOLE_LOG) {
|
|
tports.push(
|
|
new transports.Console({
|
|
format: consoleFormatter,
|
|
})
|
|
);
|
|
}
|
|
|
|
if (Config.LOGGER_CONFIG.FOLDER) {
|
|
tports.push(
|
|
new transports.DailyRotateFile({
|
|
filename: `${Config.LOGGER_CONFIG.FOLDER}/toa-%DATE%.log`,
|
|
datePattern: "YYYY-MM-DD-HH",
|
|
zippedArchive: true,
|
|
maxSize: "20m",
|
|
maxFiles: "14d",
|
|
createSymlink: true,
|
|
symlinkName: "toa.log",
|
|
format: defaultFormatter,
|
|
})
|
|
);
|
|
}
|
|
|
|
export const rootLogger = winston.createLogger({
|
|
level,
|
|
format: defaultFormatter,
|
|
transports: tports,
|
|
defaultMeta: {
|
|
__ServerName: Config.NAME,
|
|
},
|
|
});
|
|
|
|
if (tports.length === 0) {
|
|
// eslint-disable-next-line no-console
|
|
console.warn(
|
|
"You have no transports set. Absolutely no logs will be saved. This is a terrible idea!"
|
|
);
|
|
}
|
|
|
|
export default function CreateLogCtx(filename: string, lg = rootLogger): winston.Logger {
|
|
const replacedFilename = filename.replace(
|
|
new RegExp(`^${EscapeStringRegexp(process.cwd())}[\\\\/]((js|src)[\\\\/])?`, "u"),
|
|
""
|
|
);
|
|
|
|
const logger = lg.child({
|
|
context: [replacedFilename],
|
|
});
|
|
|
|
// @hack, defaultMeta isn't reactive -- won't be updated unless we do this.
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
logger.defaultMeta = { ...(logger.defaultMeta ?? {}), context: [replacedFilename] };
|
|
|
|
return logger;
|
|
}
|
|
|
|
export function AppendLogCtx(context: string, lg: winston.Logger): winston.Logger {
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
|
|
const newContext = [...lg.defaultMeta.context, context];
|
|
|
|
return lg.child({ context: newContext });
|
|
}
|
|
|
|
export function ChangeRootLogLevel(
|
|
level: "crit" | "debug" | "error" | "info" | "severe" | "verbose" | "warn"
|
|
) {
|
|
rootLogger.info(`Changing log level to ${level}.`);
|
|
|
|
for (const tp of tports) {
|
|
tp.level = level;
|
|
}
|
|
}
|
|
|
|
export function GetLogLevel() {
|
|
return (
|
|
tports.map((e) => e.level).find((e) => typeof e === "string") ??
|
|
Config.LOGGER_CONFIG.LOG_LEVEL
|
|
);
|
|
}
|
|
|
|
export const Transports = tports;
|