From 3d72924c6850f1a5ab3ae1dcc5cb19672f35395a Mon Sep 17 00:00:00 2001 From: Space_Interprise <44732812+emanuelfranklyn@users.noreply.github.com> Date: Wed, 19 Jul 2023 21:55:17 -0300 Subject: [PATCH 1/4] (fix): duplicated logs and typescript types --- CHANGELOG.md | 5 +++++ package-lock.json | 4 ++-- package.json | 2 +- src/main/logger.ts | 16 ++++++++-------- src/tests/tester.js | 1 + 5 files changed, 17 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5adc5b..a119442 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# V1.1.1 + - [FIXED] Fixed a bug that caused logs that passed arguments to log the same thing twice + + - [FIXED] Fixed typescript types for the `Logger` class + # V1.1 - Added support for logging to a file diff --git a/package-lock.json b/package-lock.json index 6d5c7d3..78dda03 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@promisepending/logger.js", - "version": "1.1.0", + "version": "1.1.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@promisepending/logger.js", - "version": "1.1.0", + "version": "1.1.1", "license": "MIT", "dependencies": { "adm-zip": "^0.5.10", diff --git a/package.json b/package.json index 0cad410..228466b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@promisepending/logger.js", - "version": "1.1.0", + "version": "1.1.1", "description": "A better logger", "main": "build/index.js", "types": "src/main/index.ts", diff --git a/src/main/logger.ts b/src/main/logger.ts index 90a6afb..af9b294 100644 --- a/src/main/logger.ts +++ b/src/main/logger.ts @@ -177,7 +177,7 @@ export class Logger { }; } - log(text: string | number | Error, levelToLog?: ELoggerLevel, ...args: any): void { + log(text: any, levelToLog?: ELoggerLevel, ...args: any): void { const level = levelToLog ?? this.defaultLevel; var stackTrace = ''; if (text instanceof Error) { @@ -201,7 +201,7 @@ export class Logger { ; if ((this.debugActive && level === ELoggerLevel.DEBUG) || (level !== ELoggerLevel.DEBUG)) { - consoleLevels[level](coloredMessagePrefix + messageToConsole, ...args); + consoleLevels[level](coloredMessagePrefix + messageToConsole); } // escapes the text to a be secure to be used in html @@ -235,23 +235,23 @@ export class Logger { } } - info(text: string | number | Error, ...args: any): void { + info(text: any, ...args: any): void { this.log(text, ELoggerLevel.INFO, ...args); } - warn(text: string | number | Error, ...args: any): void { + warn(text: any, ...args: any): void { this.log(text, ELoggerLevel.WARN, ...args); } - error(text: string | number | Error, ...args: any): void { + error(text: any, ...args: any): void { this.log(text, ELoggerLevel.ERROR, ...args); } - debug(text: string | number | Error, ...args: any): void { + debug(text: any, ...args: any): void { this.log(text, ELoggerLevel.DEBUG, ...args); } - fatal(text: string | number | Error, ...args: any): void { + fatal(text: any, ...args: any): void { var message = text.toString(); var stack: string[] | undefined = []; var fullString = text.toString(); @@ -301,7 +301,7 @@ export class Logger { this.closeFileStreams(finalMessage, finalFatalMessage); } - console.error(msg, ...args); + console.error(msg); if (!this.disableFatalCrash) { process.exit(5); diff --git a/src/tests/tester.js b/src/tests/tester.js index 1dcf61d..0644ada 100644 --- a/src/tests/tester.js +++ b/src/tests/tester.js @@ -24,5 +24,6 @@ log.debug(ERROR); log.info(['Hello world!', 'This is a test!']); log.warn('

Hello world!

'); log.error({ message: 'Hello world!', code: 500 }); +log.error('This is a string', { message: 'Hello world!', code: 500 }); log.fatal(ERROR); log.debug('This happens after a fatal error!'); From 1fec8079ab34ccb265f89d998416022ae2d9426b Mon Sep 17 00:00:00 2001 From: Space_Interprise <44732812+emanuelfranklyn@users.noreply.github.com> Date: Thu, 20 Jul 2023 00:04:11 -0300 Subject: [PATCH 2/4] (fix): Fatal and zip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Lobo Metalúrgico <43734867+LoboMetalurgico@users.noreply.github.com> --- CHANGELOG.md | 30 +++++++++++++------ README.md | 3 ++ package.json | 3 +- src/main/logger.ts | 72 +++++++++++++++++++-------------------------- src/tests/tester.js | 46 +++++++++++++---------------- 5 files changed, 78 insertions(+), 76 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a119442..c3e6db0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,17 +1,27 @@ -# V1.1.1 +# v1.1.1: Saving Everything - [FIXED] Fixed a bug that caused logs that passed arguments to log the same thing twice - [FIXED] Fixed typescript types for the `Logger` class -# V1.1 - - Added support for logging to a file + - [FIXED] Fixed an issue that caused having two logger instances loggin to the same folder to cause a crash when the second instance tried to close the file stream + + - [FIXED] Zip files timestamp now reflects the last modified time of the latest log file instead of the time of the zip creation + + - [UPDATE] Fatal logs now are saved per fatal crash instead of all fatal crashes of the same execution being saved to the same file + + - [UPDATE] Fatal logs now have a 4 character random code at the start of the file name to prevent overwriting of files + + - [UPDATE] Zip files now have a 4 character random code at the start of the file name to prevent overwriting of files + +# v1.1.0: Saving Everything + - [NEW] Added support for logging to a file - [NEW] Added the `fileProperties` parameter to the `Logger` constructor which includes the following properties: - + - enable: enable logging to a file (defaults to `false`) [If `false` all the other properties will be ignored] - + - logFolderPath: path to the folder where the log files will be stored (will be created if it doesn't exist, defaults to `./logs`) - + - enableLatestLog?: if true, the latest log file will be stored in the `logFolderPath` with the name `latest.log` (defaults to `true`) - enableDebugLog?: if true, the debug log will be stored in the `logFolderPath` inside a folder named `latestLogs` with the name `debug.log` (defaults to `true`) @@ -23,15 +33,17 @@ - generateHTMLLog?: if true, the log files will be generated in HTML format (their extension will change from .log to .html) (defaults to `false`) - compressLogFilesAfterNewExecution?: if true, the log files will be compressed to a zip file after a new execution of the program (defaults to `true`) - + - [NEW] Added support to log objects, arrays and etc. Like the default `console.log` function. - [BREAKING] Changed the export of AutoLogEnd now importing the package will return the `Logger` class and a object named `AutoLogEnd` with the `activate` and `deactivate` functions. - [NEW] AutoLogEnd `activate` function now accepts a `Logger` instance as 2º parameter. If not passed it will create a new instance of `Logger` with the default parameters. otherwise it will use the passed instance. -# V1.0 +# v1.0.0: Logging Everything + +- [NEW] Initial release of logger -- Initial release of logger - Supports Warning, Error, Info, Debug, and Fatal logging levels + - Supports colored output if specified diff --git a/README.md b/README.md index 1bb7259..44f5572 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,9 @@ License + + +

# diff --git a/package.json b/package.json index 228466b..64a57d3 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "scripts": { "build": "npx tsc -p .", "pdeploy": "node scripts/prepareDeploy.js", - "test": "node src/tests/tester.js" + "test": "node src/tests/tester.js", + "pretest": "npm run build" }, "repository": { "type": "git", diff --git a/src/main/logger.ts b/src/main/logger.ts index af9b294..d3b6d37 100644 --- a/src/main/logger.ts +++ b/src/main/logger.ts @@ -17,9 +17,9 @@ export class Logger { private latestFileStream?: fs.WriteStream; private debugLogStream?: fs.WriteStream; private errorLogStream?: fs.WriteStream; - private fatalLogStream?: fs.WriteStream; private htmlBackgroundColor: string; private htmlTextColor: string; + private defaultHeader = ''; constructor({ prefix, debug, defaultLevel, coloredBackground, disableFatalCrash, allLineColored, fileProperties }: ILoggerOptions) { this.prefix = prefix ?? ''; @@ -55,35 +55,32 @@ export class Logger { if (!fs.existsSync(Path.join(this.fileProperties.logFolderPath, 'latestLogs'))) fs.mkdirSync(Path.join(this.fileProperties.logFolderPath, 'latestLogs')); // eslint-disable-next-line max-len - const defaultHeader = `\n`; + this.defaultHeader = `\n`; if (this.fileProperties.enableLatestLog) { this.latestFileStream = fs.createWriteStream( Path.join(this.fileProperties.logFolderPath, `latest.${this.fileProperties.generateHTMLLog ? 'html' : 'log'}`), { flags: 'a' }, ); - if (this.fileProperties.generateHTMLLog) this.latestFileStream.write(defaultHeader); + if (this.fileProperties.generateHTMLLog) this.latestFileStream.write(this.defaultHeader); } if (this.fileProperties.enableDebugLog) { this.debugLogStream = fs.createWriteStream( Path.join(this.fileProperties.logFolderPath, 'latestLogs', `debug.${this.fileProperties.generateHTMLLog ? 'html' : 'log'}`), { flags: 'a' }, ); - if (this.fileProperties.generateHTMLLog) this.debugLogStream.write(defaultHeader); + if (this.fileProperties.generateHTMLLog) this.debugLogStream.write(this.defaultHeader); } if (this.fileProperties.enableErrorLog) { this.errorLogStream = fs.createWriteStream( Path.join(this.fileProperties.logFolderPath, 'latestLogs', `error.${this.fileProperties.generateHTMLLog ? 'html' : 'log'}`), { flags: 'a' }, ); - if (this.fileProperties.generateHTMLLog) this.errorLogStream.write(defaultHeader); - } - if (this.fileProperties.enableFatalLog) { - this.fatalLogStream = fs.createWriteStream( - Path.join(this.fileProperties.logFolderPath, 'fatal-crash', `fatal-latest.${this.fileProperties.generateHTMLLog ? 'html' : 'log'}`), { flags: 'a' }, - ); - if (this.fileProperties.generateHTMLLog) this.fatalLogStream.write(defaultHeader); + if (this.fileProperties.generateHTMLLog) this.errorLogStream.write(this.defaultHeader); } // handles process exists to properly close the streams - process.on('exit', this.closeFileStreams.bind(this, 'Process exited', undefined)); + process.on('exit', (exitCode) => { + // eslint-disable-next-line max-len + this.closeFileStreams(`${this.fileProperties.generateHTMLLog ? '
\n' : '\n'}Process exited with code (${exitCode})${this.fileProperties.generateHTMLLog ? '\n
' : '\n'}`); + }); } else { this.fileProperties.enableLatestLog = false; this.fileProperties.enableDebugLog = false; @@ -95,24 +92,26 @@ export class Logger { } private closeFileStreams(closeStreamMessage?: string, customFatalMessage?: string): void { - if (this.latestFileStream) this.latestFileStream.end(closeStreamMessage?.toString()); - if (this.debugLogStream) this.debugLogStream.end(closeStreamMessage?.toString()); - if (this.errorLogStream) this.errorLogStream.end(closeStreamMessage?.toString()); - if (this.fatalLogStream) { - this.fatalLogStream?.end((customFatalMessage ?? closeStreamMessage)?.toString()); - // rename the file from fatal-latest.log to fatal-.log - fs.renameSync( - Path.resolve(this.fileProperties.logFolderPath, 'fatal-crash', `fatal-latest.${this.fileProperties.generateHTMLLog ? 'html' : 'log'}`), - Path.resolve(this.fileProperties.logFolderPath, 'fatal-crash', `fatal-${this.getTime(true, true)}.${this.fileProperties.generateHTMLLog ? 'html' : 'log'}`), - ); - } + this.writeToAllStreams(closeStreamMessage ?? '', customFatalMessage); + this.latestFileStream?.end(); + this.debugLogStream?.end(); + this.errorLogStream?.end(); } private writeToAllStreams(message: string, customFatalLog?: string): void { if (this.fileProperties.enableLatestLog) this.latestFileStream?.write(message); if (this.fileProperties.enableDebugLog) this.debugLogStream?.write(message); if (this.fileProperties.enableErrorLog) this.errorLogStream?.write(message); - if (this.fileProperties.enableFatalLog) this.fatalLogStream?.write(customFatalLog ?? message); + if (this.fileProperties.enableFatalLog && customFatalLog) { + // create a new stream for fatal log + // 4 random alphanumeric characters + const uniqueId = Math.random().toString(36).substring(2, 6); + const fatalLogStream = fs.createWriteStream( + Path.join(this.fileProperties.logFolderPath, 'fatal-crash', `fatal-${uniqueId}-${this.getTime(true, true)}.${this.fileProperties.generateHTMLLog ? 'html' : 'log'}`), + ); + fatalLogStream.write(this.defaultHeader); + fatalLogStream.end(customFatalLog); + } } private compressLastSessionLogs(): void { @@ -125,6 +124,11 @@ export class Logger { const latestLogsFiles = fs.readdirSync(Path.join(this.fileProperties.logFolderPath, 'latestLogs')); // files = files.concat(fatalCrashFiles.map((file) => Path.join('fatal-crash', file))); files = files.concat(latestLogsFiles.map((file) => Path.join('latestLogs', file))); + // use fs.stat on latest.log/html to get its last modified date + const latestLogPath = Path.join(this.fileProperties.logFolderPath, `latest.${this.fileProperties.generateHTMLLog ? 'html' : 'log'}`); + const latestLogStats = fs.statSync(latestLogPath); + // get mtime and replace : with - to avoid windows file system errors + const latestLogDate = latestLogStats.mtime.toISOString().replace(/:/g, '-').split('.')[0]; files.forEach((file) => { if (file.endsWith('.log') || file.endsWith('.html')) { zip.addLocalFile(Path.join(this.fileProperties.logFolderPath, file)); @@ -132,8 +136,8 @@ export class Logger { if (!file.startsWith('fatal')) fs.unlinkSync(Path.join(this.fileProperties.logFolderPath, file)); } }); - - fs.writeFileSync(Path.resolve(this.fileProperties.logFolderPath, `logs-${this.getTime(true, true)}.zip`), zip.toBuffer()); + const uniqueId = Math.random().toString(36).substring(2, 6); + fs.writeFileSync(Path.resolve(this.fileProperties.logFolderPath, `logs-${uniqueId}-${latestLogDate}.zip`), zip.toBuffer()); } private getFormattedPrefix(): string { @@ -179,9 +183,7 @@ export class Logger { log(text: any, levelToLog?: ELoggerLevel, ...args: any): void { const level = levelToLog ?? this.defaultLevel; - var stackTrace = ''; if (text instanceof Error) { - stackTrace = text.stack ?? ''; text = text.toString(); } text = utils.format(text, ...args); @@ -206,33 +208,22 @@ export class Logger { // escapes the text to a be secure to be used in html const escapedText = escape(text.toString()); - // escapes the stack trace and converts tabs to spaces and spaces to   to be used in html - const escapedStackTrace = '' + escape(stackTrace).replace(/\t/g, '    ').replace(/ /g, ' ').split('\n').join('') + ''; // eslint-disable-next-line max-len const textSpanElement = this.allLineColored ? `${escapedText}` : `${escapedText}`; // eslint-disable-next-line max-len - const stackTraceSpanElement = this.allLineColored ? `${escapedStackTrace}` : `${escapedStackTrace}`; - // eslint-disable-next-line max-len const parentSpanElement = `${rawMessagePrefix} ${textSpanElement}\n`; - // eslint-disable-next-line max-len - const parentStackTraceSpanElement = `${rawMessagePrefix} ${stackTraceSpanElement}\n`; if (this.fileProperties.enableDebugLog) { this.debugLogStream?.write(this.fileProperties.generateHTMLLog ? parentSpanElement : (rawMessagePrefix + ' ' + text + '\n')); } if (this.fileProperties.enableErrorLog && level === ELoggerLevel.ERROR) { // eslint-disable-next-line max-len - this.errorLogStream?.write(this.fileProperties.generateHTMLLog ? (stackTrace ? parentStackTraceSpanElement : parentSpanElement) : (rawMessagePrefix + ' ' + (stackTrace ?? text) + '\n')); + this.errorLogStream?.write(this.fileProperties.generateHTMLLog ? parentSpanElement : (rawMessagePrefix + ' ' + text + '\n')); } if (this.fileProperties.enableLatestLog && level !== ELoggerLevel.DEBUG) { this.latestFileStream?.write(this.fileProperties.generateHTMLLog ? parentSpanElement : (rawMessagePrefix + ' ' + text + '\n')); } - if (this.fileProperties.enableFatalLog && level !== ELoggerLevel.DEBUG) { - // write all logs to the fatal log file (including stack traces from non fatal logs) EXCEPT debug logs - // eslint-disable-next-line max-len - this.fatalLogStream?.write(this.fileProperties.generateHTMLLog ? (stackTrace ? parentStackTraceSpanElement : parentSpanElement) : (rawMessagePrefix + ' ' + (stackTrace ?? text) + '\n')); - } } info(text: any, ...args: any): void { @@ -256,7 +247,6 @@ export class Logger { var stack: string[] | undefined = []; var fullString = text.toString(); if (text instanceof Error) { - // create stacktrace stack = text.stack?.split('\n'); if (stack) { fullString = stack.join('\n'); diff --git a/src/tests/tester.js b/src/tests/tester.js index 0644ada..14da7f9 100644 --- a/src/tests/tester.js +++ b/src/tests/tester.js @@ -1,29 +1,25 @@ -const logger = require('../../build'); +const { Logger } = require('../../build'); -const log = new logger.Logger({ - prefix: 'TESTER', - debug: true, - coloredBackground: true, - allLineColored: true, - disableFatalCrash: true, - fileProperties: { - enable: true, - logFolderPath: './logs', - enableLatestLog: true, - enableDebugLog: true, - enableErrorLog: true, - enableFatalLog: true, - generateHTMLLog: true, - compressLogFilesAfterNewExecution: true, +const logger = new Logger({ + prefix: 'Logger.JS', // This will be the prefix of all logs (default: null) + disableFatalCrash: true, // If true, the logger will not crash the process when a fatal error occurs (default: false) + allLineColored: true, // If true, the whole line will be colored instead of only the prefix (default: false) + coloredBackground: true, // If true, the background of the lines will be colored instead of the text (default: false) + debug: false, // If true, the logger will log debug messages (default: false) + fileProperties: { // This is the configuration of the log files + enable: true, // If true, the logger will log to files (default: false) [NOTE: If false all below options will be ignored] + logFolderPath: './logs', // This is the path of the folder where the log files will be saved (default: './logs') + enableLatestLog: true, // If true, the logger will save the latest log in a file (default: true) + enableDebugLog: true, // If true, the logger will save the debug logs in a file (default: false) + enableErrorLog: true, // If true, the logger will save the error logs in a file (default: false) + enableFatalLog: true, // If true, the logger will save the fatal logs in a file (default: true) + generateHTMLLog: true, // If true, the logger will generate a HTML file with the logs otherwise a .log file (default: false) + compressLogFilesAfterNewExecution: true, // If true, the logger will compress the log files to zip after a new execution (default: true) }, }); -const ERROR = new Error('Example error'); - -log.debug(ERROR); -log.info(['Hello world!', 'This is a test!']); -log.warn('

Hello world!

'); -log.error({ message: 'Hello world!', code: 500 }); -log.error('This is a string', { message: 'Hello world!', code: 500 }); -log.fatal(ERROR); -log.debug('This happens after a fatal error!'); +logger.info('This is an info message'); +logger.warn('This is a warning message'); +logger.error('This is an error message'); +logger.debug('This is a debug message'); +logger.fatal('This is a fatal message'); From e6bcc2f32e29726767e37eda64f5643123ae61fa Mon Sep 17 00:00:00 2001 From: Space_Interprise <44732812+emanuelfranklyn@users.noreply.github.com> Date: Thu, 20 Jul 2023 00:11:22 -0300 Subject: [PATCH 3/4] (chore): Added readme example picture MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Lobo Metalúrgico <43734867+LoboMetalurgico@users.noreply.github.com> --- .github/assets/LoggerExample.png | Bin 0 -> 25970 bytes README.md | 8 ++++++-- src/tests/tester.js | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 .github/assets/LoggerExample.png diff --git a/.github/assets/LoggerExample.png b/.github/assets/LoggerExample.png new file mode 100644 index 0000000000000000000000000000000000000000..b4b65be9f6562974bdd4cfc9249f0cb75c41ea7c GIT binary patch literal 25970 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 +

📝 License

@@ -122,7 +126,7 @@ logger.fatal('This is a fatal message');
- + LoboMetlurgico's GitHub profile logo
LoboMetalurgico @@ -131,7 +135,7 @@ logger.fatal('This is a fatal message');
- + SpaceFox's GitHub profile logo
SpaceFox diff --git a/src/tests/tester.js b/src/tests/tester.js index 14da7f9..cf11316 100644 --- a/src/tests/tester.js +++ b/src/tests/tester.js @@ -4,7 +4,7 @@ const logger = new Logger({ prefix: 'Logger.JS', // This will be the prefix of all logs (default: null) disableFatalCrash: true, // If true, the logger will not crash the process when a fatal error occurs (default: false) allLineColored: true, // If true, the whole line will be colored instead of only the prefix (default: false) - coloredBackground: true, // If true, the background of the lines will be colored instead of the text (default: false) + coloredBackground: false, // If true, the background of the lines will be colored instead of the text (default: false) debug: false, // If true, the logger will log debug messages (default: false) fileProperties: { // This is the configuration of the log files enable: true, // If true, the logger will log to files (default: false) [NOTE: If false all below options will be ignored] From 898e4464f57ec150257fbec30c8c621f2ada0569 Mon Sep 17 00:00:00 2001 From: Space_Interprise <44732812+emanuelfranklyn@users.noreply.github.com> Date: Thu, 20 Jul 2023 00:13:20 -0300 Subject: [PATCH 4/4] (chore): Fixed changelog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Lobo Metalúrgico <43734867+LoboMetalurgico@users.noreply.github.com> --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3e6db0..5d54d45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,13 @@ -# v1.1.1: Saving Everything +# v1.1.1: Saving Everything (patch) - [FIXED] Fixed a bug that caused logs that passed arguments to log the same thing twice - [FIXED] Fixed typescript types for the `Logger` class - - [FIXED] Fixed an issue that caused having two logger instances loggin to the same folder to cause a crash when the second instance tried to close the file stream + - [FIXED] Fixed an issue that caused having two logger instances logging to the same folder to cause a crash when the second instance tried to close the file stream - [FIXED] Zip files timestamp now reflects the last modified time of the latest log file instead of the time of the zip creation - - [UPDATE] Fatal logs now are saved per fatal crash instead of all fatal crashes of the same execution being saved to the same file + - [UPDATE] Fatal logs now are saved per fatal crash, instead of all fatal crashes of the same execution being saved to the same file - [UPDATE] Fatal logs now have a 4 character random code at the start of the file name to prevent overwriting of files