From 8f8acc2271c903c5fe9bbe669c8ed1da4bd23638 Mon Sep 17 00:00:00 2001 From: sir-wilhelm Date: Tue, 27 Jan 2026 22:16:12 -0600 Subject: [PATCH 1/4] Added scanner log purging. It'll use whatever value is set in the database/default to 2. I noticed this was making sure the scan dir exists, but `LogManager.js` should already do that. Since it's not hurting anything I'll flip it to use `fs.ensureDir`. --- server/scanner/LibraryScan.js | 42 ++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/server/scanner/LibraryScan.js b/server/scanner/LibraryScan.js index e77f30ba6..75a998304 100644 --- a/server/scanner/LibraryScan.js +++ b/server/scanner/LibraryScan.js @@ -30,6 +30,10 @@ class LibraryScan { this.logs = [] } + get loggerScannerLogsToKeep() { + return global.ServerSettings?.loggerScannerLogsToKeep || 2 + } + get libraryId() { return this.library.id } @@ -125,9 +129,7 @@ class LibraryScan { async saveLog() { const scanLogDir = Path.join(global.MetadataPath, 'logs', 'scans') - if (!(await fs.pathExists(scanLogDir))) { - await fs.mkdir(scanLogDir) - } + await fs.ensureDir(scanLogDir) const outputPath = Path.join(scanLogDir, this.logFilename) const logLines = [JSON.stringify(this.toJSON())] @@ -137,6 +139,40 @@ class LibraryScan { await fs.writeFile(outputPath, logLines.join('\n') + '\n') Logger.info(`[LibraryScan] Scan log saved "${outputPath}"`) + + await this.purgeOldScanLogs(scanLogDir) + } + + /** + * Keep the most recent N scan logs in metadata/logs/scans. + * Where N is the server setting `loggerScannerLogsToKeep`. + * + * @param {string} scanLogDir + */ + async purgeOldScanLogs(scanLogDir) { + const scanLogsToKeep = this.loggerScannerLogsToKeep + + let scanFiles + try { + scanFiles = await fs.readdir(scanLogDir) + } catch (error) { + Logger.warn(`[LibraryScan] Failed to read scan log dir "${scanLogDir}": ${error.message}`) + return + } + + const scanLogFiles = (scanFiles || []).filter((f) => Path.extname(f) === '.txt').sort() + if (scanLogFiles.length <= scanLogsToKeep) return + + const filesToRemove = scanLogFiles.slice(0, scanLogFiles.length - scanLogsToKeep) + for (const file of filesToRemove) { + const fullPath = Path.join(scanLogDir, file) + try { + await fs.unlink(fullPath) + Logger.info(`[LibraryScan] Removed scan log "${fullPath}"`) + } catch (error) { + Logger.warn(`[LibraryScan] Failed to remove scan log "${fullPath}": ${error.message}`) + } + } } } module.exports = LibraryScan From ead270abbe3048ef6626ce1703d11e58303c44a0 Mon Sep 17 00:00:00 2001 From: sir-wilhelm Date: Tue, 27 Jan 2026 22:48:02 -0600 Subject: [PATCH 2/4] Purge daily logs folder whenever a new one is created. --- server/managers/LogManager.js | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/server/managers/LogManager.js b/server/managers/LogManager.js index 07bb7d401..756070365 100644 --- a/server/managers/LogManager.js +++ b/server/managers/LogManager.js @@ -36,6 +36,12 @@ class LogManager { return global.ServerSettings.loggerDailyLogsToKeep || 7 } + async enforceDailyLogRetention() { + while (this.dailyLogFiles.length > this.loggerDailyLogsToKeep) { + await this.removeLogFile(this.dailyLogFiles[0]) + } + } + async ensureLogDirs() { try { await fs.ensureDir(this.DailyLogPath) @@ -49,8 +55,9 @@ class LogManager { /** * 1. Ensure log directories exist * 2. Load daily log files - * 3. Remove old daily log files - * 4. Create/set current daily log file + * 3. Create/set current daily log file + * 4. Remove old daily log files + * 5. Log any buffered daily logs */ async init() { await this.ensureLogDirs() @@ -58,14 +65,6 @@ class LogManager { // Load daily logs await this.scanLogFiles() - // Check remove extra daily logs - if (this.dailyLogFiles.length > this.loggerDailyLogsToKeep) { - const dailyLogFilesCopy = [...this.dailyLogFiles] - for (let i = 0; i < dailyLogFilesCopy.length - this.loggerDailyLogsToKeep; i++) { - await this.removeLogFile(dailyLogFilesCopy[i]) - } - } - // set current daily log file or create if does not exist const currentDailyLogFilename = DailyLog.getCurrentDailyLogFilename() Logger.info(TAG, `Init current daily log filename: ${currentDailyLogFilename}`) @@ -79,6 +78,9 @@ class LogManager { this.dailyLogFiles.push(this.currentDailyLog.filename) } + // Check remove extra daily logs + await this.enforceDailyLogRetention() + // Log buffered daily logs if (this.dailyLogBuffer.length) { this.dailyLogBuffer.forEach((logObj) => { @@ -146,10 +148,13 @@ class LogManager { // Check log rolls to next day if (this.currentDailyLog.id !== DailyLog.getCurrentDateString()) { this.currentDailyLog = new DailyLog(this.DailyLogPath) - if (this.dailyLogFiles.length > this.loggerDailyLogsToKeep) { - // Remove oldest log - this.removeLogFile(this.dailyLogFiles[0]) + + // Track the new daily log file so retention works for long-running servers + if (!this.dailyLogFiles.includes(this.currentDailyLog.filename)) { + this.dailyLogFiles.push(this.currentDailyLog.filename) } + + await this.enforceDailyLogRetention() } // Append log line to log file From f069475a4e0b88da853df9663a9d8b2f803bcf77 Mon Sep 17 00:00:00 2001 From: sir-wilhelm Date: Mon, 16 Feb 2026 14:45:54 -0600 Subject: [PATCH 3/4] Move scanner log purge into the LogManager. --- server/managers/LogManager.js | 36 ++++++++++++++++++++++++++++++ server/scanner/LibraryScan.js | 42 ++--------------------------------- 2 files changed, 38 insertions(+), 40 deletions(-) diff --git a/server/managers/LogManager.js b/server/managers/LogManager.js index 756070365..5583c5dfd 100644 --- a/server/managers/LogManager.js +++ b/server/managers/LogManager.js @@ -36,6 +36,10 @@ class LogManager { return global.ServerSettings.loggerDailyLogsToKeep || 7 } + get loggerScannerLogsToKeep() { + return global.ServerSettings.loggerScannerLogsToKeep || 2 + } + async enforceDailyLogRetention() { while (this.dailyLogFiles.length > this.loggerDailyLogsToKeep) { await this.removeLogFile(this.dailyLogFiles[0]) @@ -184,5 +188,37 @@ class LogManager { getMostRecentCurrentDailyLogs() { return this.currentDailyLog?.logs.slice(-5000) || '' } + + /** + * Keep the most recent N scan logs in metadata/logs/scans. + * Where N is the server setting `loggerScannerLogsToKeep`. + * + * @param {string} [logDir] + */ + async purgeOldScanLogs(logDir = this.ScanLogPath) { + const scanLogsToKeep = this.loggerScannerLogsToKeep + + let scanFiles + try { + scanFiles = await fs.readdir(logDir) + } catch (error) { + Logger.warn(TAG, `Failed to read scan log dir "${logDir}": ${error.message}`) + return + } + + const scanLogFiles = (scanFiles || []).filter((f) => Path.extname(f) === '.txt').sort() + if (scanLogFiles.length <= scanLogsToKeep) return + + const filesToRemove = scanLogFiles.slice(0, scanLogFiles.length - scanLogsToKeep) + for (const file of filesToRemove) { + const fullPath = Path.join(logDir, file) + try { + await fs.unlink(fullPath) + Logger.info(TAG, `Removed scan log "${fullPath}"`) + } catch (error) { + Logger.warn(TAG, `Failed to remove scan log "${fullPath}": ${error.message}`) + } + } + } } module.exports = LogManager diff --git a/server/scanner/LibraryScan.js b/server/scanner/LibraryScan.js index 75a998304..4f9044162 100644 --- a/server/scanner/LibraryScan.js +++ b/server/scanner/LibraryScan.js @@ -30,10 +30,6 @@ class LibraryScan { this.logs = [] } - get loggerScannerLogsToKeep() { - return global.ServerSettings?.loggerScannerLogsToKeep || 2 - } - get libraryId() { return this.library.id } @@ -127,9 +123,7 @@ class LibraryScan { } async saveLog() { - const scanLogDir = Path.join(global.MetadataPath, 'logs', 'scans') - - await fs.ensureDir(scanLogDir) + const scanLogDir = Logger.logManager.ScanLogPath const outputPath = Path.join(scanLogDir, this.logFilename) const logLines = [JSON.stringify(this.toJSON())] @@ -140,39 +134,7 @@ class LibraryScan { Logger.info(`[LibraryScan] Scan log saved "${outputPath}"`) - await this.purgeOldScanLogs(scanLogDir) - } - - /** - * Keep the most recent N scan logs in metadata/logs/scans. - * Where N is the server setting `loggerScannerLogsToKeep`. - * - * @param {string} scanLogDir - */ - async purgeOldScanLogs(scanLogDir) { - const scanLogsToKeep = this.loggerScannerLogsToKeep - - let scanFiles - try { - scanFiles = await fs.readdir(scanLogDir) - } catch (error) { - Logger.warn(`[LibraryScan] Failed to read scan log dir "${scanLogDir}": ${error.message}`) - return - } - - const scanLogFiles = (scanFiles || []).filter((f) => Path.extname(f) === '.txt').sort() - if (scanLogFiles.length <= scanLogsToKeep) return - - const filesToRemove = scanLogFiles.slice(0, scanLogFiles.length - scanLogsToKeep) - for (const file of filesToRemove) { - const fullPath = Path.join(scanLogDir, file) - try { - await fs.unlink(fullPath) - Logger.info(`[LibraryScan] Removed scan log "${fullPath}"`) - } catch (error) { - Logger.warn(`[LibraryScan] Failed to remove scan log "${fullPath}": ${error.message}`) - } - } + await Logger.logManager?.purgeOldScanLogs() } } module.exports = LibraryScan From f819d661e86957b15d4f1741deb62c78f4fe4ede Mon Sep 17 00:00:00 2001 From: sir-wilhelm Date: Mon, 16 Feb 2026 14:49:58 -0600 Subject: [PATCH 4/4] If scanLogsToKeep is <= 0 don't purge. For now this would be a manual database update to configure this. It does help future proof the setting being set in the UI. --- server/managers/LogManager.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/managers/LogManager.js b/server/managers/LogManager.js index 5583c5dfd..9bacfeeba 100644 --- a/server/managers/LogManager.js +++ b/server/managers/LogManager.js @@ -41,6 +41,8 @@ class LogManager { } async enforceDailyLogRetention() { + if (this.loggerDailyLogsToKeep <= 0) return + while (this.dailyLogFiles.length > this.loggerDailyLogsToKeep) { await this.removeLogFile(this.dailyLogFiles[0]) } @@ -197,6 +199,7 @@ class LogManager { */ async purgeOldScanLogs(logDir = this.ScanLogPath) { const scanLogsToKeep = this.loggerScannerLogsToKeep + if (scanLogsToKeep <= 0) return let scanFiles try {