mirror of
https://github.com/zadam/trilium.git
synced 2025-10-20 15:19:01 +02:00
feat(logs): cleanup physical log files after 90 days by default
asdf
This commit is contained in:
parent
f00c0d5d73
commit
decfb58142
@ -2,9 +2,11 @@
|
|||||||
|
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from "express";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
|
import path from "path";
|
||||||
import { EOL } from "os";
|
import { EOL } from "os";
|
||||||
import dataDir from "./data_dir.js";
|
import dataDir from "./data_dir.js";
|
||||||
import cls from "./cls.js";
|
import cls from "./cls.js";
|
||||||
|
import optionService from "./options.js";
|
||||||
|
|
||||||
if (!fs.existsSync(dataDir.LOG_DIR)) {
|
if (!fs.existsSync(dataDir.LOG_DIR)) {
|
||||||
fs.mkdirSync(dataDir.LOG_DIR, 0o700);
|
fs.mkdirSync(dataDir.LOG_DIR, 0o700);
|
||||||
@ -17,6 +19,9 @@ const MINUTE = 60 * SECOND;
|
|||||||
const HOUR = 60 * MINUTE;
|
const HOUR = 60 * MINUTE;
|
||||||
const DAY = 24 * HOUR;
|
const DAY = 24 * HOUR;
|
||||||
|
|
||||||
|
const DEFAULT_RETENTION_DAYS = 90;
|
||||||
|
const MINIMUM_FILES_TO_KEEP = 7;
|
||||||
|
|
||||||
let todaysMidnight!: Date;
|
let todaysMidnight!: Date;
|
||||||
|
|
||||||
initLogFile();
|
initLogFile();
|
||||||
@ -27,16 +32,96 @@ function getTodaysMidnight() {
|
|||||||
return new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
return new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function cleanupOldLogFiles() {
|
||||||
|
try {
|
||||||
|
// Get retention days from environment or options
|
||||||
|
const envRetention = process.env.TRILIUM_LOG_RETENTION_DAYS;
|
||||||
|
let retentionDays = DEFAULT_RETENTION_DAYS;
|
||||||
|
|
||||||
|
if (envRetention) {
|
||||||
|
const parsed = parseInt(envRetention, 10);
|
||||||
|
if (!isNaN(parsed) && parsed > 0 && parsed <= 3650) {
|
||||||
|
retentionDays = parsed;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
retentionDays = optionService.getOptionInt("logRetentionDays", DEFAULT_RETENTION_DAYS);
|
||||||
|
}
|
||||||
|
|
||||||
|
const cutoffDate = new Date();
|
||||||
|
cutoffDate.setDate(cutoffDate.getDate() - retentionDays);
|
||||||
|
|
||||||
|
// Read all log files
|
||||||
|
const files = await fs.promises.readdir(dataDir.LOG_DIR);
|
||||||
|
const logFiles: Array<{name: string, mtime: Date, path: string}> = [];
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
// Security: Only process files matching our log pattern
|
||||||
|
if (!/^trilium-\d{4}-\d{2}-\d{2}\.log$/.test(file)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filePath = path.join(dataDir.LOG_DIR, file);
|
||||||
|
|
||||||
|
// Security: Verify path stays within LOG_DIR
|
||||||
|
const resolvedPath = path.resolve(filePath);
|
||||||
|
const resolvedLogDir = path.resolve(dataDir.LOG_DIR);
|
||||||
|
if (!resolvedPath.startsWith(resolvedLogDir + path.sep)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const stats = await fs.promises.stat(filePath);
|
||||||
|
logFiles.push({ name: file, mtime: stats.mtime, path: filePath });
|
||||||
|
} catch (err) {
|
||||||
|
// Skip files we can't stat
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by modification time (oldest first)
|
||||||
|
logFiles.sort((a, b) => a.mtime.getTime() - b.mtime.getTime());
|
||||||
|
|
||||||
|
// Keep minimum number of files
|
||||||
|
if (logFiles.length <= MINIMUM_FILES_TO_KEEP) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete old files, keeping minimum
|
||||||
|
let deletedCount = 0;
|
||||||
|
for (let i = 0; i < logFiles.length - MINIMUM_FILES_TO_KEEP; i++) {
|
||||||
|
const file = logFiles[i];
|
||||||
|
if (file.mtime < cutoffDate) {
|
||||||
|
try {
|
||||||
|
await fs.promises.unlink(file.path);
|
||||||
|
deletedCount++;
|
||||||
|
} catch (err) {
|
||||||
|
// Log deletion failed, but continue with others
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deletedCount > 0) {
|
||||||
|
info(`Log cleanup: deleted ${deletedCount} old log files`);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
// Cleanup failed, but don't crash the log rotation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function initLogFile() {
|
function initLogFile() {
|
||||||
todaysMidnight = getTodaysMidnight();
|
todaysMidnight = getTodaysMidnight();
|
||||||
|
|
||||||
const path = `${dataDir.LOG_DIR}/trilium-${formatDate()}.log`;
|
const logPath = `${dataDir.LOG_DIR}/trilium-${formatDate()}.log`;
|
||||||
|
|
||||||
if (logFile) {
|
if (logFile) {
|
||||||
logFile.end();
|
logFile.end();
|
||||||
|
|
||||||
|
// Clean up old log files when rotating to a new file
|
||||||
|
cleanupOldLogFiles().catch(() => {
|
||||||
|
// Ignore cleanup errors
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
logFile = fs.createWriteStream(path, { flags: "a" });
|
logFile = fs.createWriteStream(logPath, { flags: "a" });
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkDate(millisSinceMidnight: number) {
|
function checkDate(millisSinceMidnight: number) {
|
||||||
|
@ -126,6 +126,7 @@ const defaultOptions: DefaultOption[] = [
|
|||||||
{ name: "disableTray", value: "false", isSynced: false },
|
{ name: "disableTray", value: "false", isSynced: false },
|
||||||
{ name: "eraseUnusedAttachmentsAfterSeconds", value: "2592000", isSynced: true }, // default 30 days
|
{ name: "eraseUnusedAttachmentsAfterSeconds", value: "2592000", isSynced: true }, // default 30 days
|
||||||
{ name: "eraseUnusedAttachmentsAfterTimeScale", value: "86400", isSynced: true }, // default 86400 seconds = Day
|
{ name: "eraseUnusedAttachmentsAfterTimeScale", value: "86400", isSynced: true }, // default 86400 seconds = Day
|
||||||
|
{ name: "logRetentionDays", value: "90", isSynced: false }, // default 90 days
|
||||||
{ name: "customSearchEngineName", value: "DuckDuckGo", isSynced: true },
|
{ name: "customSearchEngineName", value: "DuckDuckGo", isSynced: true },
|
||||||
{ name: "customSearchEngineUrl", value: "https://duckduckgo.com/?q={keyword}", isSynced: true },
|
{ name: "customSearchEngineUrl", value: "https://duckduckgo.com/?q={keyword}", isSynced: true },
|
||||||
{ name: "promotedAttributesOpenInRibbon", value: "true", isSynced: true },
|
{ name: "promotedAttributesOpenInRibbon", value: "true", isSynced: true },
|
||||||
|
@ -85,6 +85,7 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions<KeyboardActi
|
|||||||
minTocHeadings: number;
|
minTocHeadings: number;
|
||||||
eraseUnusedAttachmentsAfterSeconds: number;
|
eraseUnusedAttachmentsAfterSeconds: number;
|
||||||
eraseUnusedAttachmentsAfterTimeScale: number;
|
eraseUnusedAttachmentsAfterTimeScale: number;
|
||||||
|
logRetentionDays: number;
|
||||||
firstDayOfWeek: number;
|
firstDayOfWeek: number;
|
||||||
firstWeekOfYear: number;
|
firstWeekOfYear: number;
|
||||||
minDaysInFirstWeek: number;
|
minDaysInFirstWeek: number;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user