mirror of
https://github.com/finos/SymphonyElectron.git
synced 2024-11-21 08:34:40 -06:00
SDA-4408: Experimental Build for logger
This commit is contained in:
parent
f3380b06aa
commit
d1171056db
@ -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 {};
|
||||
});
|
||||
|
@ -12,8 +12,6 @@ jest.mock('../src/common/env', () => {
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('electron-log');
|
||||
|
||||
describe('logger', () => {
|
||||
let instance;
|
||||
beforeEach(() => {
|
||||
|
@ -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;
|
||||
|
@ -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';
|
||||
|
5
src/common/c9-logger.ts
Normal file
5
src/common/c9-logger.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { Logger } from './loggerBase';
|
||||
|
||||
const logger = new Logger('iv');
|
||||
|
||||
export { logger };
|
@ -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 };
|
||||
|
295
src/common/loggerBase.ts
Normal file
295
src/common/loggerBase.ts
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user