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
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { storage } from "../storage";
|
||||
import { withSuperAdmin } from "../db";
|
||||
import { runAutomationInWorker } from "../routes/automation.routes";
|
||||
import { logger } from "../utils/logger";
|
||||
|
||||
/**
|
||||
* Планировщик schedule-автоматизаций.
|
||||
@@ -13,6 +14,8 @@ import { runAutomationInWorker } from "../routes/automation.routes";
|
||||
* при первой возможности в тот же день (catch-up в пределах суток).
|
||||
*/
|
||||
|
||||
const log = logger("Automation Scheduler");
|
||||
|
||||
const TICK_MS = 60_000;
|
||||
const MSK_OFFSET_MS = 3 * 60 * 60 * 1000; // МСК = UTC+3, перехода на летнее время нет
|
||||
|
||||
@@ -59,19 +62,19 @@ async function tick(): Promise<void> {
|
||||
);
|
||||
await withSuperAdmin(() => storage.markAutomationScheduledRun(automation.id));
|
||||
|
||||
console.log(
|
||||
`[Automation Scheduler] '${automation.name}' (id ${automation.id}, org ${automation.organizationId}): ${error ? "ERROR " + error : "ok"}`
|
||||
log.info(
|
||||
`'${automation.name}' (id ${automation.id}, org ${automation.organizationId}): ${error ? "ERROR " + error : "ok"}`
|
||||
);
|
||||
for (const line of logs) console.log(`[Automation Scheduler] ${line}`);
|
||||
for (const line of logs) log.info(` ${line}`);
|
||||
} catch (err) {
|
||||
console.error(`[Automation Scheduler] Failed automation ${automation.id}:`, err);
|
||||
log.error(`Failed automation ${automation.id}:`, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function startAutomationScheduler(): void {
|
||||
setInterval(() => {
|
||||
tick().catch((err) => console.error("[Automation Scheduler] Tick error:", err));
|
||||
tick().catch((err) => log.error("Tick error:", err));
|
||||
}, TICK_MS);
|
||||
console.log("[Automation Scheduler] started");
|
||||
log.info("started");
|
||||
}
|
||||
|
||||
50
server/workers/error-logs-retention.ts
Normal file
50
server/workers/error-logs-retention.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { db, withSuperAdmin } from "../db";
|
||||
import { sql } from "drizzle-orm";
|
||||
import { logger } from "../utils/logger";
|
||||
|
||||
/**
|
||||
* Retention журнала ошибок API (таблица error_logs, см. shared/schema.ts).
|
||||
*
|
||||
* Раз в сутки удаляет записи старше RETENTION_DAYS дней. Без отдельной
|
||||
* миграции: таблица и индексы уже существуют. Таймер unref'нут, чтобы
|
||||
* не удерживать процесс (и не мешать тестам завершаться).
|
||||
* Первый запуск — сразу при старте, дальше — каждые 24 часа.
|
||||
*/
|
||||
|
||||
const log = logger("error-logs-retention");
|
||||
|
||||
const TICK_MS = 24 * 60 * 60 * 1000; // раз в сутки
|
||||
const RETENTION_DAYS = 30;
|
||||
|
||||
let isRunning = false;
|
||||
|
||||
async function cleanup(): Promise<void> {
|
||||
// withSuperAdmin — единая точка для фоновых задач вне контекста организации
|
||||
const result = await withSuperAdmin(() =>
|
||||
db.execute(sql`
|
||||
DELETE FROM error_logs
|
||||
WHERE created_at < now() - make_interval(days => ${RETENTION_DAYS})
|
||||
`)
|
||||
);
|
||||
const deleted = (result as { rowCount?: number }).rowCount ?? 0;
|
||||
if (deleted > 0) {
|
||||
log.info(`Удалено устаревших записей error_logs: ${deleted} (старше ${RETENTION_DAYS} дней)`);
|
||||
} else {
|
||||
log.debug(`Устаревших записей error_logs нет (старше ${RETENTION_DAYS} дней)`);
|
||||
}
|
||||
}
|
||||
|
||||
export function startErrorLogsRetention(): void {
|
||||
if (isRunning) return;
|
||||
isRunning = true;
|
||||
|
||||
const timer = setInterval(() => {
|
||||
cleanup().catch((err) => log.error("Cleanup error:", err));
|
||||
}, TICK_MS);
|
||||
timer.unref?.();
|
||||
|
||||
// Первый прогон сразу после старта (в фоне, не блокируя listen)
|
||||
cleanup().catch((err) => log.error("Initial cleanup error:", err));
|
||||
|
||||
log.info(`started (retention ${RETENTION_DAYS} дней, тик раз в сутки)`);
|
||||
}
|
||||
Reference in New Issue
Block a user