diff --git a/spec/childWindowHandle.spec.ts b/spec/childWindowHandle.spec.ts index 04e49866..cc5d5ab8 100644 --- a/spec/childWindowHandle.spec.ts +++ b/spec/childWindowHandle.spec.ts @@ -22,8 +22,6 @@ jest.mock('fs', () => ({ readFileSync: jest.fn(() => '{"configVersion": "4.0.0"}'), })); -jest.mock('electron-log'); - jest.mock('../src/common/env', () => { return { isWindowsOS: true, @@ -58,20 +56,6 @@ jest.mock('../src/app/window-actions', () => { }; }); -jest.mock('../src/common/logger', () => { - return { - logger: { - setLoggerWindow: jest.fn(), - error: jest.fn(), - warn: jest.fn(), - info: jest.fn(), - verbose: jest.fn(), - debug: jest.fn(), - silly: jest.fn(), - }, - }; -}); - jest.mock('../src/app/auto-update-handler', () => { return {}; }); diff --git a/spec/log.spec.ts b/spec/log.spec.ts index 06d18bde..d6c9dba2 100644 --- a/spec/log.spec.ts +++ b/spec/log.spec.ts @@ -12,8 +12,6 @@ jest.mock('../src/common/env', () => { }; }); -jest.mock('electron-log'); - describe('logger', () => { let instance; beforeEach(() => { diff --git a/src/app/c9-pipe-handler.ts b/src/app/c9-pipe-handler.ts index 7a28ef32..795a85c0 100644 --- a/src/app/c9-pipe-handler.ts +++ b/src/app/c9-pipe-handler.ts @@ -1,6 +1,6 @@ import { WebContents } from 'electron'; import { createConnection, Socket } from 'net'; -import { logger } from '../common/logger'; +import { logger } from '../common/c9-logger'; class C9PipeHandler { private _socket: Socket | undefined; diff --git a/src/app/c9-shell-handler.ts b/src/app/c9-shell-handler.ts index e40f189b..7f556b29 100644 --- a/src/app/c9-shell-handler.ts +++ b/src/app/c9-shell-handler.ts @@ -1,6 +1,6 @@ import { app, powerMonitor, WebContents } from 'electron'; +import { logger } from '../common/c9-logger'; import { isDevEnv, isWindowsOS } from '../common/env'; -import { logger } from '../common/logger'; import { ChildProcess, spawn } from 'child_process'; import * as path from 'path'; diff --git a/src/common/c9-logger.ts b/src/common/c9-logger.ts new file mode 100644 index 00000000..077bd40e --- /dev/null +++ b/src/common/c9-logger.ts @@ -0,0 +1,5 @@ +import { Logger } from './loggerBase'; + +const logger = new Logger('iv'); + +export { logger }; diff --git a/src/common/logger.ts b/src/common/logger.ts index 94a0696b..207eda34 100644 --- a/src/common/logger.ts +++ b/src/common/logger.ts @@ -1,292 +1,5 @@ -import { app, WebContents } from 'electron'; -import electronLog, { LogLevel, transports } from 'electron-log'; -import * as fs from 'fs'; -import * as path from 'path'; -import * as util from 'util'; +import { Logger } from './loggerBase'; -import { isElectronQA, isWindowsOS } from './env'; -import { getCommandLineArgs } from './utils'; - -export interface ILogMsg { - level: LogLevel; - details: any; - showInConsole: boolean; - startTime: number; -} - -interface IClientLogMsg { - msgs?: ILogMsg[]; - logLevel?: LogLevel; - showInConsole?: boolean; -} - -const MAX_LOG_QUEUE_LENGTH = 100; - -// Force log path to local path in Windows rather than roaming -if (isWindowsOS && process.env.LOCALAPPDATA) { - app.setPath('appData', process.env.LOCALAPPDATA); - app.setPath('userData', path.join(app.getPath('appData'), app.getName())); -} - -// Electron wants this to be called initially before calling -// app.getPath('logs') -app.setAppLogsPath(); - -class Logger { - private readonly showInConsole: boolean = false; - private readonly desiredLogLevel?: LogLevel; - private readonly logQueue: ILogMsg[]; - private readonly logPath: string; - private loggerWindow: WebContents | null; - - constructor() { - this.loggerWindow = null; - this.logQueue = []; - // If the user has specified a custom log path use it. - const customLogPathArg = getCommandLineArgs( - process.argv, - '--logPath=', - false, - ); - const customLogsFolder = - customLogPathArg && - customLogPathArg.substring(customLogPathArg.indexOf('=') + 1); - if (customLogsFolder) { - if (!fs.existsSync(customLogsFolder)) { - fs.mkdirSync(customLogsFolder, { recursive: true }); - } - app.setPath('logs', customLogsFolder); - } - - this.logPath = app.getPath('logs'); - - if (app.isPackaged) { - transports.file.file = path.join(this.logPath, `app_${Date.now()}.log`); - transports.file.level = 'debug'; - transports.file.format = - '{y}-{m}-{d} {h}:{i}:{s}:{ms} {z} | {level} | {text}'; - transports.file.appName = 'Symphony'; - } - - const logLevel = getCommandLineArgs(process.argv, '--logLevel=', false); - if (logLevel) { - const level = logLevel.split('=')[1]; - if (level) { - this.desiredLogLevel = level as LogLevel; - } - } - - if (getCommandLineArgs(process.argv, '--enableConsoleLogging', false)) { - this.showInConsole = true; - } - - // cleans up old logs if there are any - if (app.isPackaged) { - this.cleanupOldLogs(); - } - } - - /** - * get instance of logQueue - */ - public getLogQueue(): ILogMsg[] { - return this.logQueue; - } - - /** - * Log error - * - * @param message {string} - message to be logged - * @param data {any} - extra data that needs to be logged - */ - public error(message: string, ...data: any[]): void { - this.log('error', message, data); - } - - /** - * Log warn - * - * @param message {string} - message to be logged - * @param data {any} - extra data that needs to be logged - */ - public warn(message: string, ...data: any[]): void { - this.log('warn', message, data); - } - - /** - * Log info - * - * @param message {string} - message to be logged - * @param data {any} - extra data that needs to be logged - */ - public info(message: string, ...data: any[]): void { - this.log('info', message, data); - } - - /** - * Log verbose - * - * @param message {string} - message to be logged - * @param data {array} - extra data that needs to be logged - */ - public verbose(message: string, ...data: any[]): void { - this.log('verbose', message, data); - } - - /** - * Log debug - * - * @param message {string} - message to be logged - * @param data {any} - extra data that needs to be logged - */ - public debug(message: string, ...data: any[]): void { - this.log('debug', message, data); - } - - /** - * Log silly - * - * @param message {string} - message to be logged - * @param data {any} - extra data that needs to be logged - */ - public silly(message: string, ...data: any[]): void { - this.log('silly', message, data); - } - - /** - * Sets the renderer window for sending logs to the client - * - * @param window {WebContents} - renderer window - */ - public setLoggerWindow(window: WebContents): void { - this.loggerWindow = window; - - if (this.loggerWindow) { - const logMsgs: IClientLogMsg = {}; - if (this.logQueue.length) { - logMsgs.msgs = this.logQueue; - } - if (this.desiredLogLevel) { - logMsgs.logLevel = this.desiredLogLevel; - } - if (Object.keys(logMsgs).length) { - this.loggerWindow.send('log', logMsgs); - } - } - } - - /** - * Main instance of the logger method - * - * @param logLevel {LogLevel} - Different type of log levels - * @param message {string} - Log message - * @param data {array} - extra data to be logged - * @param sendToCloud {boolean} - wehether to send the logs on to cloud - */ - public log( - logLevel: LogLevel, - message: string, - data: any[] = [], - sendToCloud: boolean = true, - ): void { - if (data && data.length > 0) { - data.forEach((param) => { - message += `, '${param && typeof param}': ${JSON.stringify(param)}`; - }); - } - if (!isElectronQA) { - switch (logLevel) { - case 'error': - electronLog.error(message); - break; - case 'warn': - electronLog.warn(message); - break; - case 'info': - electronLog.info(message); - break; - case 'verbose': - electronLog.verbose(message); - break; - case 'debug': - electronLog.debug(message); - break; - case 'silly': - electronLog.silly(message); - break; - default: - electronLog.info(message); - } - } - if (sendToCloud) { - this.sendToCloud(this.formatLogMsg(logLevel, message)); - } - } - - /** - * Formats the logs in the format that required - * to send to the client - * - * @param level {LogLevel} - Different type of log levels - * @param details {any} - log format that required to send to client - */ - private formatLogMsg(level: LogLevel, details: any): ILogMsg { - return { - details, - level, - showInConsole: this.showInConsole, - startTime: Date.now(), - }; - } - - /** - * This will send the logs to the client if loggerWindow - * else adds the logs to a Queue - * - * @param logMsg {ILogMsg} - */ - private sendToCloud(logMsg: ILogMsg): void { - // don't send logs if it is not desired by the user - if (this.desiredLogLevel && this.desiredLogLevel !== logMsg.level) { - return; - } - - if (this.loggerWindow && !this.loggerWindow.isDestroyed()) { - this.loggerWindow.send('log', { - msgs: [logMsg], - logLevel: this.desiredLogLevel, - showInConsole: this.showInConsole, - }); - return; - } - - this.logQueue.push(logMsg); - // don't store more than 100 msgs. keep most recent log msgs. - if (this.logQueue.length > MAX_LOG_QUEUE_LENGTH) { - this.logQueue.shift(); - } - } - - /** - * Cleans up logs older than a day - */ - private cleanupOldLogs(): void { - const files = fs.readdirSync(this.logPath); - const deleteTimeStamp = new Date().getTime() - 5 * 24 * 60 * 60 * 1000; - files.forEach((file) => { - // nosemgrep - const filePath = path.join(this.logPath, file); - if (fs.existsSync(filePath)) { - const stat = fs.statSync(filePath); - const fileTimestamp = new Date(util.inspect(stat.mtime)).getTime(); - if (fileTimestamp < deleteTimeStamp) { - fs.unlinkSync(filePath); - } - } - }); - } -} - -const logger = new Logger(); +const logger = new Logger('sda'); export { logger }; diff --git a/src/common/loggerBase.ts b/src/common/loggerBase.ts new file mode 100644 index 00000000..8e140593 --- /dev/null +++ b/src/common/loggerBase.ts @@ -0,0 +1,295 @@ +import { app, WebContents } from 'electron'; +import { create, ElectronLog, LogLevel } from 'electron-log'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as util from 'util'; + +import { isElectronQA, isWindowsOS } from './env'; +import { getCommandLineArgs } from './utils'; + +export interface ILogMsg { + level: LogLevel; + details: any; + showInConsole: boolean; + startTime: number; +} + +interface IClientLogMsg { + msgs?: ILogMsg[]; + logLevel?: LogLevel; + showInConsole?: boolean; +} + +const MAX_LOG_QUEUE_LENGTH = 100; + +// Force log path to local path in Windows rather than roaming +if (isWindowsOS && process.env.LOCALAPPDATA) { + app.setPath('appData', process.env.LOCALAPPDATA); + app.setPath('userData', path.join(app.getPath('appData'), app.getName())); +} + +// Electron wants this to be called initially before calling +// app.getPath('logs') +app.setAppLogsPath(); + +export class Logger { + private readonly showInConsole: boolean = false; + private readonly desiredLogLevel?: LogLevel; + private readonly logQueue: ILogMsg[]; + private readonly logPath: string; + private loggerWindow: WebContents | null; + private electronLog: ElectronLog; + private logName: string; + + constructor(logName: string) { + this.logName = logName; + this.electronLog = create(logName); + this.loggerWindow = null; + this.logQueue = []; + // If the user has specified a custom log path use it. + const customLogPathArg = getCommandLineArgs( + process.argv, + '--logPath=', + false, + ); + const customLogsFolder = + customLogPathArg && + customLogPathArg.substring(customLogPathArg.indexOf('=') + 1); + if (customLogsFolder) { + if (!fs.existsSync(customLogsFolder)) { + fs.mkdirSync(customLogsFolder, { recursive: true }); + } + app.setPath('logs', customLogsFolder); + } + + this.logPath = app.getPath('logs'); + + if (app.isPackaged) { + this.electronLog.transports.file.file = path.join( + this.logPath, + `${this.logName}_${Date.now()}.log`, + ); + this.electronLog.transports.file.level = 'debug'; + this.electronLog.transports.file.format = + '{y}-{m}-{d} {h}:{i}:{s}:{ms} {z} | {level} | {text}'; + this.electronLog.transports.file.appName = 'Symphony'; + } + + const logLevel = getCommandLineArgs(process.argv, '--logLevel=', false); + if (logLevel) { + const level = logLevel.split('=')[1]; + if (level) { + this.desiredLogLevel = level as LogLevel; + } + } + + if (getCommandLineArgs(process.argv, '--enableConsoleLogging', false)) { + this.showInConsole = true; + } + + // cleans up old logs if there are any + if (app.isPackaged) { + this.cleanupOldLogs(); + } + } + + /** + * get instance of logQueue + */ + public getLogQueue(): ILogMsg[] { + return this.logQueue; + } + + /** + * Log error + * + * @param message {string} - message to be logged + * @param data {any} - extra data that needs to be logged + */ + public error(message: string, ...data: any[]): void { + this.log('error', message, data); + } + + /** + * Log warn + * + * @param message {string} - message to be logged + * @param data {any} - extra data that needs to be logged + */ + public warn(message: string, ...data: any[]): void { + this.log('warn', message, data); + } + + /** + * Log info + * + * @param message {string} - message to be logged + * @param data {any} - extra data that needs to be logged + */ + public info(message: string, ...data: any[]): void { + this.log('info', message, data); + } + + /** + * Log verbose + * + * @param message {string} - message to be logged + * @param data {array} - extra data that needs to be logged + */ + public verbose(message: string, ...data: any[]): void { + this.log('verbose', message, data); + } + + /** + * Log debug + * + * @param message {string} - message to be logged + * @param data {any} - extra data that needs to be logged + */ + public debug(message: string, ...data: any[]): void { + this.log('debug', message, data); + } + + /** + * Log silly + * + * @param message {string} - message to be logged + * @param data {any} - extra data that needs to be logged + */ + public silly(message: string, ...data: any[]): void { + this.log('silly', message, data); + } + + /** + * Sets the renderer window for sending logs to the client + * + * @param window {WebContents} - renderer window + */ + public setLoggerWindow(window: WebContents): void { + this.loggerWindow = window; + + if (this.loggerWindow) { + const logMsgs: IClientLogMsg = {}; + if (this.logQueue.length) { + logMsgs.msgs = this.logQueue; + } + if (this.desiredLogLevel) { + logMsgs.logLevel = this.desiredLogLevel; + } + if (Object.keys(logMsgs).length) { + this.loggerWindow.send('log', logMsgs); + } + } + } + + /** + * Main instance of the logger method + * + * @param logLevel {LogLevel} - Different type of log levels + * @param message {string} - Log message + * @param data {array} - extra data to be logged + * @param sendToCloud {boolean} - wehether to send the logs on to cloud + */ + public log( + logLevel: LogLevel, + message: string, + data: any[] = [], + sendToCloud: boolean = true, + ): void { + if (data && data.length > 0) { + data.forEach((param) => { + message += `, '${param && typeof param}': ${JSON.stringify(param)}`; + }); + } + if (!isElectronQA) { + switch (logLevel) { + case 'error': + this.electronLog.error(message); + break; + case 'warn': + this.electronLog.warn(message); + break; + case 'info': + this.electronLog.info(message); + break; + case 'verbose': + this.electronLog.verbose(message); + break; + case 'debug': + this.electronLog.debug(message); + break; + case 'silly': + this.electronLog.silly(message); + break; + default: + this.electronLog.info(message); + } + } + if (sendToCloud) { + this.sendToCloud(this.formatLogMsg(logLevel, message)); + } + } + + /** + * Formats the logs in the format that required + * to send to the client + * + * @param level {LogLevel} - Different type of log levels + * @param details {any} - log format that required to send to client + */ + private formatLogMsg(level: LogLevel, details: any): ILogMsg { + return { + details, + level, + showInConsole: this.showInConsole, + startTime: Date.now(), + }; + } + + /** + * This will send the logs to the client if loggerWindow + * else adds the logs to a Queue + * + * @param logMsg {ILogMsg} + */ + private sendToCloud(logMsg: ILogMsg): void { + // don't send logs if it is not desired by the user + if (this.desiredLogLevel && this.desiredLogLevel !== logMsg.level) { + return; + } + + if (this.loggerWindow && !this.loggerWindow.isDestroyed()) { + this.loggerWindow.send('log', { + msgs: [logMsg], + logLevel: this.desiredLogLevel, + showInConsole: this.showInConsole, + }); + return; + } + + this.logQueue.push(logMsg); + // don't store more than 100 msgs. keep most recent log msgs. + if (this.logQueue.length > MAX_LOG_QUEUE_LENGTH) { + this.logQueue.shift(); + } + } + + /** + * Cleans up logs older than a day + */ + private cleanupOldLogs(): void { + const files = fs.readdirSync(this.logPath); + const deleteTimeStamp = new Date().getTime() - 5 * 24 * 60 * 60 * 1000; + files.forEach((file) => { + // nosemgrep + const filePath = path.join(this.logPath, file); + if (fs.existsSync(filePath)) { + const stat = fs.statSync(filePath); + const fileTimestamp = new Date(util.inspect(stat.mtime)).getTime(); + if (fileTimestamp < deleteTimeStamp) { + fs.unlinkSync(filePath); + } + } + }); + } +}