| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import { existsSync, mkdirSync, statSync, renameSync, readdirSync, unlinkSync } from "fs"; |
| import { dirname, join, basename, extname } from "path"; |
| import { |
| getAppLogFilePath, |
| getAppLogMaxFiles, |
| getAppLogMaxFileSize, |
| getAppLogRetentionDays, |
| getAppLogToFile, |
| } from "./logEnv"; |
|
|
| export function getLogConfig() { |
| const logToFile = getAppLogToFile(); |
| const logFilePath = getAppLogFilePath() || join(process.cwd(), "logs/application/app.log"); |
| const maxFileSize = getAppLogMaxFileSize(); |
| const retentionDays = getAppLogRetentionDays(); |
| const maxFiles = getAppLogMaxFiles(); |
|
|
| return { logToFile, logFilePath, maxFileSize, retentionDays, maxFiles }; |
| } |
|
|
| |
| |
| |
| export function ensureLogDir(logFilePath: string): void { |
| const dir = dirname(logFilePath); |
| if (!existsSync(dir)) { |
| mkdirSync(dir, { recursive: true }); |
| } |
| } |
|
|
| |
| |
| |
| |
| export function rotateIfNeeded(logFilePath: string, maxFileSize: number): void { |
| try { |
| if (!existsSync(logFilePath)) return; |
| const stats = statSync(logFilePath); |
| if (stats.size < maxFileSize) return; |
|
|
| const dir = dirname(logFilePath); |
| const ext = extname(logFilePath); |
| const base = basename(logFilePath, ext); |
| const now = new Date(); |
| const ts = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String( |
| now.getDate() |
| ).padStart(2, "0")}_${String(now.getHours()).padStart(2, "0")}${String( |
| now.getMinutes() |
| ).padStart(2, "0")}${String(now.getSeconds()).padStart(2, "0")}`; |
|
|
| const rotatedPath = join(dir, `${base}.${ts}${ext}`); |
| renameSync(logFilePath, rotatedPath); |
| } catch { |
| |
| } |
| } |
|
|
| |
| |
| |
| export function cleanupOldLogs(logFilePath: string, retentionDays: number): void { |
| try { |
| const dir = dirname(logFilePath); |
| if (!existsSync(dir)) return; |
|
|
| const ext = extname(logFilePath); |
| const base = basename(logFilePath, ext); |
| const files = readdirSync(dir); |
| const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1000; |
|
|
| for (const file of files) { |
| |
| if (file.startsWith(base + ".") && file.endsWith(ext) && file !== basename(logFilePath)) { |
| const filePath = join(dir, file); |
| try { |
| const stats = statSync(filePath); |
| if (stats.mtimeMs < cutoff) { |
| unlinkSync(filePath); |
| } |
| } catch { |
| |
| } |
| } |
| } |
| } catch { |
| |
| } |
| } |
|
|
| |
| |
| |
| export function cleanupOverflowLogs(logFilePath: string, maxFiles: number): void { |
| try { |
| const dir = dirname(logFilePath); |
| if (!existsSync(dir) || maxFiles < 1) return; |
|
|
| const ext = extname(logFilePath); |
| const base = basename(logFilePath, ext); |
| const rotatedFiles = readdirSync(dir) |
| .filter( |
| (file) => |
| file !== basename(logFilePath) && file.startsWith(base + ".") && file.endsWith(ext) |
| ) |
| .map((file) => { |
| const filePath = join(dir, file); |
| try { |
| return { filePath, mtimeMs: statSync(filePath).mtimeMs }; |
| } catch { |
| return null; |
| } |
| }) |
| .filter((entry): entry is { filePath: string; mtimeMs: number } => !!entry) |
| .sort((a, b) => b.mtimeMs - a.mtimeMs); |
|
|
| for (const entry of rotatedFiles.slice(maxFiles)) { |
| try { |
| unlinkSync(entry.filePath); |
| } catch { |
| |
| } |
| } |
| } catch { |
| |
| } |
| } |
|
|
| |
| |
| |
| |
| export function initLogRotation(): void { |
| const config = getLogConfig(); |
| if (!config.logToFile) return; |
|
|
| ensureLogDir(config.logFilePath); |
| rotateIfNeeded(config.logFilePath, config.maxFileSize); |
| cleanupOldLogs(config.logFilePath, config.retentionDays); |
| cleanupOverflowLogs(config.logFilePath, config.maxFiles); |
| } |
|
|