Шаги 1.6 и 1.7 плана production-готовности: - eslint flat-config (баг-ловушки, легаси warn), lint блокирующий в pr-check - npm audit --audit-level=high в CI (отчёт) - фикс реального бага: условный useRef в TaskTitleInline - server/utils/logger.ts (LOG_LEVEL/LOG_FORMAT) в 5 горячих местах - error_logs retention 30 дней (worker), redactSensitive в captureErrorLog - 0 errors lint, vitest 96/96
94 lines
3.2 KiB
TypeScript
94 lines
3.2 KiB
TypeScript
/**
|
||
* Единый серверный logger.
|
||
*
|
||
* НОВЫЕ модули сервера должны использовать этот logger вместо console.*,
|
||
* чтобы уровни и формат логов управлялись централизованно:
|
||
*
|
||
* import { logger } from "./utils/logger";
|
||
* const log = logger("billing");
|
||
* log.info("Payment created", { id: 123 });
|
||
*
|
||
* Уровень логирования — env LOG_LEVEL (debug|info|warn|error), по умолчанию info.
|
||
* Формат — env LOG_FORMAT=json включает JSON-строки (для сборщиков логов),
|
||
* иначе вывод в историческом формате сервера: "HH:MM:SS AM [source] message",
|
||
* совместимом с log() из server/vite.ts.
|
||
*
|
||
* Массовая замена console.* по существующему коду сознательно не делается —
|
||
* logger подключается точечно в новых/горячих местах.
|
||
*/
|
||
|
||
type LogLevel = "debug" | "info" | "warn" | "error";
|
||
|
||
const LEVEL_ORDER: Record<LogLevel, number> = {
|
||
debug: 10,
|
||
info: 20,
|
||
warn: 30,
|
||
error: 40,
|
||
};
|
||
|
||
function resolveMinLevel(): number {
|
||
const raw = (process.env.LOG_LEVEL || "info").toLowerCase();
|
||
return LEVEL_ORDER[raw as LogLevel] ?? LEVEL_ORDER.info;
|
||
}
|
||
|
||
const MIN_LEVEL = resolveMinLevel();
|
||
const JSON_FORMAT = process.env.LOG_FORMAT === "json";
|
||
|
||
// Формат времени как в server/vite.ts log() — чтобы строки не отличались
|
||
function formattedTime(): string {
|
||
return new Date().toLocaleTimeString("en-US", {
|
||
hour: "numeric",
|
||
minute: "2-digit",
|
||
second: "2-digit",
|
||
hour12: true,
|
||
});
|
||
}
|
||
|
||
function serialize(level: LogLevel, source: string, message: string, meta?: unknown): string {
|
||
if (JSON_FORMAT) {
|
||
return JSON.stringify({
|
||
time: new Date().toISOString(),
|
||
level,
|
||
source,
|
||
message,
|
||
...(meta !== undefined ? { meta } : {}),
|
||
});
|
||
}
|
||
// Ошибки и объекты разворачиваем в читаемую строку
|
||
let suffix = "";
|
||
if (meta instanceof Error) {
|
||
suffix = ` ${meta.stack || meta.message}`;
|
||
} else if (meta !== undefined) {
|
||
try {
|
||
suffix = ` ${JSON.stringify(meta)}`;
|
||
} catch {
|
||
suffix = ` [unserializable meta]`;
|
||
}
|
||
}
|
||
return `${formattedTime()} [${source}] ${message}${suffix}`;
|
||
}
|
||
|
||
export interface Logger {
|
||
debug(message: string, meta?: unknown): void;
|
||
info(message: string, meta?: unknown): void;
|
||
warn(message: string, meta?: unknown): void;
|
||
error(message: string, meta?: unknown): void;
|
||
}
|
||
|
||
export function logger(source: string): Logger {
|
||
return {
|
||
debug(message, meta) {
|
||
if (MIN_LEVEL <= LEVEL_ORDER.debug) console.debug(serialize("debug", source, message, meta));
|
||
},
|
||
info(message, meta) {
|
||
if (MIN_LEVEL <= LEVEL_ORDER.info) console.log(serialize("info", source, message, meta));
|
||
},
|
||
warn(message, meta) {
|
||
if (MIN_LEVEL <= LEVEL_ORDER.warn) console.warn(serialize("warn", source, message, meta));
|
||
},
|
||
error(message, meta) {
|
||
if (MIN_LEVEL <= LEVEL_ORDER.error) console.error(serialize("error", source, message, meta));
|
||
},
|
||
};
|
||
}
|