audiobookshelf/server/managers/LogManager.js

178 lines
4.8 KiB
JavaScript

const Path = require('path')
const fs = require('../libs/fsExtra')
const Logger = require('../Logger')
const DailyLog = require('../objects/DailyLog')
const { LogLevel } = require('../utils/constants')
const TAG = '[LogManager]'
/**
* @typedef LogObject
* @property {string} timestamp
* @property {string} source
* @property {string} message
* @property {string} levelName
* @property {number} level
*/
class LogManager {
constructor() {
this.DailyLogPath = Path.posix.join(global.MetadataPath, 'logs', 'daily')
this.ScanLogPath = Path.posix.join(global.MetadataPath, 'logs', 'scans')
/** @type {DailyLog} */
this.currentDailyLog = null
/** @type {LogObject[]} */
this.dailyLogBuffer = []
/** @type {string[]} */
this.dailyLogFiles = []
}
get loggerDailyLogsToKeep() {
return global.ServerSettings.loggerDailyLogsToKeep || 7
}
async ensureLogDirs() {
await fs.ensureDir(this.DailyLogPath)
await fs.ensureDir(this.ScanLogPath)
}
/**
* 1. Ensure log directories exist
* 2. Load daily log files
* 3. Remove old daily log files
* 4. Create/set current daily log file
*/
async init() {
await this.ensureLogDirs()
// 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}`)
this.currentDailyLog = new DailyLog(this.DailyLogPath)
if (this.dailyLogFiles.includes(currentDailyLogFilename)) {
Logger.debug(TAG, `Daily log file already exists - set in Logger`)
await this.currentDailyLog.loadLogs()
} else {
this.dailyLogFiles.push(this.currentDailyLog.filename)
}
// Log buffered daily logs
if (this.dailyLogBuffer.length) {
this.dailyLogBuffer.forEach((logObj) => {
this.currentDailyLog.appendLog(logObj)
})
this.dailyLogBuffer = []
}
}
/**
* Load all daily log filenames in /metadata/logs/daily
*/
async scanLogFiles() {
const dailyFiles = await fs.readdir(this.DailyLogPath)
if (dailyFiles?.length) {
dailyFiles.forEach((logFile) => {
if (Path.extname(logFile) === '.txt') {
Logger.debug('Daily Log file found', logFile)
this.dailyLogFiles.push(logFile)
} else {
Logger.debug(TAG, 'Unknown File in Daily log files dir', logFile)
}
})
}
this.dailyLogFiles.sort()
}
/**
*
* @param {string} filename
*/
async removeLogFile(filename) {
const fullPath = Path.join(this.DailyLogPath, filename)
const exists = await fs.pathExists(fullPath)
if (!exists) {
Logger.error(TAG, 'Invalid log dne ' + fullPath)
this.dailyLogFiles = this.dailyLogFiles.filter(dlf => dlf !== filename)
} else {
try {
await fs.unlink(fullPath)
Logger.info(TAG, 'Removed daily log: ' + filename)
this.dailyLogFiles = this.dailyLogFiles.filter(dlf => dlf !== filename)
} catch (error) {
Logger.error(TAG, 'Failed to unlink log file ' + fullPath)
}
}
}
/**
*
* @param {LogObject} logObj
*/
async logToFile(logObj) {
// Fatal crashes get logged to a separate file
if (logObj.level === LogLevel.FATAL) {
await this.logCrashToFile(logObj)
}
// Buffer when logging before daily logs have been initialized
if (!this.currentDailyLog) {
this.dailyLogBuffer.push(logObj)
return
}
// 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])
}
}
// Append log line to log file
return this.currentDailyLog.appendLog(logObj)
}
/**
*
* @param {LogObject} logObj
*/
async logCrashToFile(logObj) {
const line = JSON.stringify(logObj) + '\n'
const logsDir = Path.join(global.MetadataPath, 'logs')
await fs.ensureDir(logsDir)
const crashLogPath = Path.join(logsDir, 'crash_logs.txt')
return fs.writeFile(crashLogPath, line, { flag: "a+" }).catch((error) => {
console.log('[LogManager] Appended crash log', error)
})
}
/**
* Most recent 5000 daily logs
*
* @returns {string}
*/
getMostRecentCurrentDailyLogs() {
return this.currentDailyLog?.logs.slice(-5000) || ''
}
}
module.exports = LogManager