Files
zerobyte/app/server/utils/logger.ts
Nico 825d46c934 refactor: react-router -> tanstack start (#498)
* refactor: move to tanstack start

* refactor: auth flow & volumes

* refactor: repo & snapshot details

* refactor: backups, create repo, volumes

* refactor: create volume & restore snapshot

* refactor: notifications

* refactor: settings

* refactor: breadcrumbs

* fix: ts issues

* refactor: prod deployment

* fix: import css production

* refactor: nitro build

* refactor: winston -> consola

* fix: memory leak is sse events cleanup

* fix: cli usage

* chore: remove rr routes file

* refactor: pr feedbacks

* refactor: patch api client to have a global client per call

* refactor: pr feedbacks

* fix(dockerfile): add explicit port

* fix(e2e): healthcheck under /api
2026-02-11 21:41:06 +01:00

106 lines
3.1 KiB
TypeScript

import { format } from "date-fns";
import { createConsola, type ConsolaReporter } from "consola";
import { formatWithOptions } from "node:util";
import { sanitizeSensitiveData } from "./sanitize";
type LogLevel = "debug" | "info" | "warn" | "error";
const getDefaultLevel = () => {
const isProd = process.env.NODE_ENV === "production";
return isProd ? "info" : "debug";
};
const resolveLogLevel = (): LogLevel => {
const raw = (process.env.LOG_LEVEL || getDefaultLevel()).toLowerCase();
if (raw === "debug" || raw === "info" || raw === "warn" || raw === "error") {
return raw;
}
return getDefaultLevel();
};
const consolaLevel: Record<LogLevel, number> = {
error: 0,
warn: 1,
info: 3,
debug: 4,
};
const useColor = (() => {
if (process.env.NO_COLOR !== undefined) return false;
if (process.env.FORCE_COLOR === "1" || process.env.FORCE_COLOR === "true") return true;
return true;
})();
const levelStyles = {
debug: { label: "debug", color: "\x1b[34m" },
info: { label: "info", color: "\x1b[32m" },
warn: { label: "warn", color: "\x1b[33m" },
error: { label: "error", color: "\x1b[31m" },
} as const;
const colorize = (color: string, text: string) => (useColor ? `${color}${text}\x1b[0m` : text);
const resolveLevel = (type: string | undefined): LogLevel => {
if (type === "debug") return "debug";
if (type === "warn") return "warn";
if (type === "error" || type === "fatal") return "error";
return "info";
};
const reporter: ConsolaReporter = {
log(logObj, ctx) {
const level = resolveLevel(logObj.type);
const timestamp = colorize("\x1b[90m", format(new Date(), "HH:mm:ss"));
const style = levelStyles[level];
const prefix = colorize(style.color, style.label);
const tag = logObj.tag ? `[${logObj.tag}]` : "";
const message = formatWithOptions(
{
...ctx.options.formatOptions,
colors: useColor,
},
...logObj.args,
);
const line = [timestamp, prefix, tag, message].filter(Boolean).join(" ");
const stream = logObj.level < 2 ? (ctx.options.stderr ?? process.stderr) : (ctx.options.stdout ?? process.stdout);
stream.write(line + "\n");
},
};
const consola = createConsola({
level: consolaLevel[resolveLogLevel()],
formatOptions: {
colors: true,
},
reporters: [reporter],
});
const safeStringify = (value: unknown) => {
try {
return JSON.stringify(value, null, 2);
} catch {
return "[Unserializable object]";
}
};
const formatMessages = (messages: unknown[]) =>
messages.flatMap((m) => {
if (m instanceof Error) {
return [sanitizeSensitiveData(m.message), m.stack ? sanitizeSensitiveData(m.stack) : undefined].filter(Boolean);
}
if (typeof m === "object") {
return sanitizeSensitiveData(safeStringify(m));
}
return sanitizeSensitiveData(String(m as string));
});
export const logger = {
debug: (...messages: unknown[]) => consola.debug(formatMessages(messages).join(" ")),
info: (...messages: unknown[]) => consola.info(formatMessages(messages).join(" ")),
warn: (...messages: unknown[]) => consola.warn(formatMessages(messages).join(" ")),
error: (...messages: unknown[]) => consola.error(formatMessages(messages).join(" ")),
};