From 05b124f624e26f00fb40c596ff4aec2d64270e4d Mon Sep 17 00:00:00 2001 From: Louis Date: Sun, 9 Feb 2025 19:36:56 +0700 Subject: [PATCH] chore: add back app logging function (#4614) --- electron/main.ts | 2 + electron/utils/logger.ts | 167 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+) create mode 100644 electron/utils/logger.ts diff --git a/electron/main.ts b/electron/main.ts index 6ce7f476a..42d16bb74 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -28,6 +28,7 @@ import { setupReactDevTool } from './utils/dev' import { trayManager } from './managers/tray' import { logSystemInfo } from './utils/system' import { registerGlobalShortcuts } from './utils/shortcut' +import { registerLogger } from './utils/logger' const preloadPath = join(__dirname, 'preload.js') const rendererPath = join(__dirname, '..', 'renderer') @@ -79,6 +80,7 @@ app }) .then(setupCore) .then(createUserSpace) + .then(registerLogger) .then(migrate) .then(setupExtensions) .then(setupMenu) diff --git a/electron/utils/logger.ts b/electron/utils/logger.ts new file mode 100644 index 000000000..48af0b93a --- /dev/null +++ b/electron/utils/logger.ts @@ -0,0 +1,167 @@ +import { + createWriteStream, + existsSync, + mkdirSync, + readdir, + stat, + unlink, + writeFileSync, +} from 'fs' +import util from 'util' +import { + getAppConfigurations, + getJanDataFolderPath, + Logger, + LoggerManager, +} from '@janhq/core/node' +import path, { join } from 'path' + +/** + * File Logger + */ +export class FileLogger implements Logger { + name = 'file' + logCleaningInterval: number = 120000 + timeout: NodeJS.Timeout | undefined + appLogPath: string = './' + logEnabled: boolean = true + + constructor( + logEnabled: boolean = true, + logCleaningInterval: number = 120000 + ) { + this.logEnabled = logEnabled + if (logCleaningInterval) this.logCleaningInterval = logCleaningInterval + + const appConfigurations = getAppConfigurations() + const logFolderPath = join(appConfigurations.data_folder, 'logs') + if (!existsSync(logFolderPath)) { + mkdirSync(logFolderPath, { recursive: true }) + } + + this.appLogPath = join(logFolderPath, 'app.log') + } + + log(args: any) { + if (!this.logEnabled) return + let message = args[0] + const scope = args[1] + if (!message) return + const path = this.appLogPath + if (!scope && !message.startsWith('[')) { + message = `[APP]::${message}` + } else if (scope) { + message = `${scope}::${message}` + } + + message = `${new Date().toISOString()} ${message}` + + writeLog(message, path) + } + + cleanLogs( + maxFileSizeBytes?: number | undefined, + daysToKeep?: number | undefined + ): void { + // clear existing timeout + // in case we rerun it with different values + if (this.timeout) clearTimeout(this.timeout) + this.timeout = undefined + + if (!this.logEnabled) return + + console.log( + 'Validating app logs. Next attempt in ', + this.logCleaningInterval + ) + + const size = maxFileSizeBytes ?? 1 * 1024 * 1024 // 1 MB + const days = daysToKeep ?? 7 // 7 days + const logDirectory = path.join(getJanDataFolderPath(), 'logs') + // Perform log cleaning + const currentDate = new Date() + if (existsSync(logDirectory)) + readdir(logDirectory, (err, files) => { + if (err) { + console.error('Error reading log directory:', err) + return + } + + files.forEach((file) => { + const filePath = path.join(logDirectory, file) + stat(filePath, (err, stats) => { + if (err) { + console.error('Error getting file stats:', err) + return + } + + // Check size + if (stats.size > size) { + unlink(filePath, (err) => { + if (err) { + console.error('Error deleting log file:', err) + return + } + console.debug( + `Deleted log file due to exceeding size limit: ${filePath}` + ) + }) + } else { + // Check age + const creationDate = new Date(stats.ctime) + const daysDifference = Math.floor( + (currentDate.getTime() - creationDate.getTime()) / + (1000 * 3600 * 24) + ) + if (daysDifference > days) { + unlink(filePath, (err) => { + if (err) { + console.error('Error deleting log file:', err) + return + } + console.debug(`Deleted old log file: ${filePath}`) + }) + } + } + }) + }) + }) + + // Schedule the next execution with doubled delays + this.timeout = setTimeout( + () => this.cleanLogs(maxFileSizeBytes, daysToKeep), + this.logCleaningInterval + ) + } +} + +/** + * Write log function implementation + * @param message + * @param logPath + */ +const writeLog = (message: string, logPath: string) => { + if (!existsSync(logPath)) { + const logDirectory = path.join(getJanDataFolderPath(), 'logs') + if (!existsSync(logDirectory)) { + mkdirSync(logDirectory) + } + writeFileSync(logPath, message) + } else { + const logFile = createWriteStream(logPath, { + flags: 'a', + }) + logFile.write(util.format(message) + '\n') + logFile.close() + console.debug(message) + } +} + +/** + * Register logger for global application logging + */ +export const registerLogger = () => { + const logger = new FileLogger() + LoggerManager.instance().register(logger) + logger.cleanLogs() +}