Files
iistwin/server/utils/logger.ts
Ильяс Султанов abd5d4b65f chore(ci+logs): eslint в CI, npm audit, logger с уровнями, retention error_logs
Шаги 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
2026-09-08 00:11:38 +03:00

94 lines
3.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Единый серверный 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));
},
};
}