/
logger.ts
266 lines (235 loc) · 8.13 KB
/
logger.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
import { app, BrowserWindow } from 'electron';
import electronLog, { ILogLevel as LogLevel, transports } from 'electron-log';
import * as fs from 'fs';
import * as path from 'path';
import * as util from 'util';
import { isElectronQA, isLinux, 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: Electron.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);
}
if (isLinux) {
this.logPath = app.getPath('appData');
} else {
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: Electron.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
*/
private log(logLevel: LogLevel, message: string, data: any[] = []): 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);
}
}
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) {
const browserWindow = BrowserWindow.fromWebContents(this.loggerWindow);
if (!(!!browserWindow && typeof browserWindow.isDestroyed === 'function' && !browserWindow.isDestroyed())) {
return;
}
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) => {
const filePath = path.join(this.logPath, file);
const stat = fs.statSync(filePath);
const fileTimestamp = new Date(util.inspect(stat.mtime)).getTime();
if (fileTimestamp < deleteTimeStamp) {
fs.unlinkSync(filePath);
}
});
}
}
const logger = new Logger();
export { logger };