import {Console} from "console"; // eslint-disable-line node/prefer-global/console import electron from "electron"; import fs from "fs"; import os from "os"; import {initSetUp} from "./default-util"; import {sentryInit, captureException} from "./sentry-util"; const {app} = process.type === "renderer" ? electron.remote : electron; interface LoggerOptions { timestamp?: true | (() => string); file?: string; level?: boolean; logInDevMode?: boolean; } initSetUp(); let reportErrors = true; if (process.type === "renderer") { // Report Errors to Sentry only if it is enabled in settings // Gets the value of reportErrors from config-util for renderer process // For main process, sentryInit() is handled in index.js const {ipcRenderer} = electron; ipcRenderer.send("error-reporting"); ipcRenderer.on( "error-reporting-val", (_event: Event, errorReporting: boolean) => { reportErrors = errorReporting; if (reportErrors) { sentryInit(); } }, ); } const logDir = `${app.getPath("userData")}/Logs`; type Level = "log" | "debug" | "info" | "warn" | "error"; export default class Logger { nodeConsole: Console; timestamp?: () => string; level: boolean; logInDevMode: boolean; constructor(options: LoggerOptions = {}) { let { timestamp = true, file = "console.log", level = true, logInDevMode = false, } = options; file = `${logDir}/${file}`; if (timestamp === true) { timestamp = this.getTimestamp; } // Trim log according to type of process if (process.type === "renderer") { requestIdleCallback(async () => this.trimLog(file)); } else { process.nextTick(async () => this.trimLog(file)); } const fileStream = fs.createWriteStream(file, {flags: "a"}); const nodeConsole = new Console(fileStream); this.nodeConsole = nodeConsole; this.timestamp = timestamp; this.level = level; this.logInDevMode = logInDevMode; } _log(type: Level, ...args: unknown[]): void { const {nodeConsole, timestamp, level, logInDevMode} = this; switch (true) { case typeof timestamp === "function": args.unshift(timestamp() + " |\t"); // Fall through case level: args.unshift(type.toUpperCase() + " |"); // Fall through case !app.isPackaged || logInDevMode: nodeConsole[type](...args); break; default: } console[type](...args); } log(...args: unknown[]): void { this._log("log", ...args); } debug(...args: unknown[]): void { this._log("debug", ...args); } info(...args: unknown[]): void { this._log("info", ...args); } warn(...args: unknown[]): void { this._log("warn", ...args); } error(...args: unknown[]): void { this._log("error", ...args); } getTimestamp(): string { const date = new Date(); const timestamp = `${date.getMonth()}/${date.getDate()} ` + `${date.getMinutes()}:${date.getSeconds()}`; return timestamp; } reportSentry(error: unknown): void { if (reportErrors) { captureException(error); } } async trimLog(file: string): Promise { const data = await fs.promises.readFile(file, "utf8"); const MAX_LOG_FILE_LINES = 500; const logs = data.split(os.EOL); const logLength = logs.length - 1; // Keep bottom MAX_LOG_FILE_LINES of each log instance if (logLength > MAX_LOG_FILE_LINES) { const trimmedLogs = logs.slice(logLength - MAX_LOG_FILE_LINES); const toWrite = trimmedLogs.join(os.EOL); await fs.promises.writeFile(file, toWrite); } } }