Skip to content

Commit

Permalink
Release: v1.1.1: Saving Everything (patch) (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
LoboMetalurgico authored Jul 20, 2023
2 parents 3e58a7c + 898e446 commit 07889b7
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 87 deletions.
Binary file added .github/assets/LoggerExample.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 25 additions & 8 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
# V1.1
- Added support for logging to a file
# 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 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 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`)
Expand All @@ -18,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
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
<a href="">
<img src="https://img.shields.io/github/license/PromisePending/logger.js?style=flat-square&color=0394fc&label=Licen%C3%A7a" alt="License" />
</a>
<a href="https://discord.gg/qUMUJW2XgF">
<img src="https://img.shields.io/discord/866707606433562634?style=flat-square&color=7289da&logo=discord&logoColor=FFFFFF"/>
</a>
</p>

#
Expand Down Expand Up @@ -85,6 +88,10 @@ logger.debug('This is a debug message');
logger.fatal('This is a fatal message');
```

### Results:

<img alt="The result of the above logs in a windows command prompt" src="https://raw.githubusercontent.com/PromisePending/logger.js/release/.github/assets/LoggerExample.png">

<br>

<h2 align="center">📝 License</h2>
Expand Down Expand Up @@ -119,7 +126,7 @@ logger.fatal('This is a fatal message');
<tr>
<td align="center">
<a href="https://github.com/LoboMetalurgico">
<img src="https://avatars.githubusercontent.com/u/43734867?v=4" width="100px;" alt=""/>
<img src="https://avatars.githubusercontent.com/u/43734867?v=4" width="100px;" alt="LoboMetlurgico's GitHub profile logo"/>
<br />
<sub>
<b>LoboMetalurgico</b>
Expand All @@ -128,7 +135,7 @@ logger.fatal('This is a fatal message');
</td>
<td align="center">
<a href="https://github.com/emanuelfranklyn">
<img src="https://avatars.githubusercontent.com/u/44732812?v=4" width="100px;" alt=""/>
<img src="https://avatars.githubusercontent.com/u/44732812?v=4" width="100px;" alt="SpaceFox's GitHub profile logo"/>
<br />
<sub>
<b>SpaceFox</b>
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
{
"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",
"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",
Expand Down
88 changes: 39 additions & 49 deletions src/main/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 ?? '';
Expand Down Expand Up @@ -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 = `<body style="--txtBackground: ${this.htmlBackgroundColor}; color: ${this.htmlTextColor}; background: ${this.htmlBackgroundColor}; margin: 0;padding: 0.25rem;display:flex;flex-direction:column;"><style>* {padding: 0.15rem 0;} body > span {position: relative;display: flex;flex-direction: row;} span > span {height: 100%;display: block;padding: 0;width: 100%;box-shadow: 0 0 0 0.16rem var(--txtBackground)} .pre {width: fit-content;white-space: nowrap;box-shadow: none;}</style>\n`;
this.defaultHeader = `<body style="--txtBackground: ${this.htmlBackgroundColor}; color: ${this.htmlTextColor}; background: ${this.htmlBackgroundColor}; margin: 0;padding: 0.25rem;display:flex;flex-direction:column;"><style>* {padding: 0.15rem 0;} body > span {position: relative;display: flex;flex-direction: row;} span > span {height: 100%;display: block;padding: 0;width: 100%;box-shadow: 0 0 0 0.16rem var(--txtBackground)} .pre {width: fit-content;white-space: nowrap;box-shadow: none;}</style>\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 ? '<br>\n<span>' : '\n'}Process exited with code (${exitCode})${this.fileProperties.generateHTMLLog ? '</span>\n<br>' : '\n'}`);
});
} else {
this.fileProperties.enableLatestLog = false;
this.fileProperties.enableDebugLog = false;
Expand All @@ -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-<timestamp>.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 {
Expand All @@ -125,15 +124,20 @@ 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));
// don't delete fatal-crash logs
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 {
Expand Down Expand Up @@ -177,11 +181,9 @@ 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) {
stackTrace = text.stack ?? '';
text = text.toString();
}
text = utils.format(text, ...args);
Expand All @@ -201,62 +203,50 @@ 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
const escapedText = escape(text.toString());
// escapes the stack trace and converts tabs to spaces and spaces to &nbsp; to be used in html
const escapedStackTrace = '<span>' + escape(stackTrace).replace(/\t/g, '&nbsp;&nbsp;&nbsp;&nbsp;').replace(/ /g, '&nbsp;').split('\n').join('</span><span>') + '</span>';

// eslint-disable-next-line max-len
const textSpanElement = this.allLineColored ? `<span style="color: ${textColor}; ${this.coloredBackground ? 'background: ' + ELoggerLevelBaseColors[level] : ''}">${escapedText}</span>` : `<span style="color: ${this.htmlTextColor}; background: ${this.htmlBackgroundColor};">${escapedText}</span>`;
// eslint-disable-next-line max-len
const stackTraceSpanElement = this.allLineColored ? `<span style="color: ${textColor}; ${this.coloredBackground ? 'background: ' + ELoggerLevelBaseColors[level] : ''}">${escapedStackTrace}</span>` : `<span style="color: ${this.htmlTextColor}; background: ${this.htmlBackgroundColor};">${escapedStackTrace}</span>`;
// eslint-disable-next-line max-len
const parentSpanElement = `<span style="color: ${textColor}; ${this.coloredBackground ? 'background: ' + ELoggerLevelBaseColors[level] + ';' : ''}${(this.allLineColored && this.coloredBackground) ? '--txtBackground: ' + ELoggerLevelBaseColors[level] + ';' : ''}"><span class='pre'>${rawMessagePrefix}&nbsp;</span>${textSpanElement}</span>\n`;
// eslint-disable-next-line max-len
const parentStackTraceSpanElement = `<span style="color: ${textColor}; ${this.coloredBackground ? 'background: ' + ELoggerLevelBaseColors[level] + ';' : ''}${(this.allLineColored && this.coloredBackground) ? '--txtBackground: ' + ELoggerLevelBaseColors[level] + ';' : ''}"><span class='pre'>${rawMessagePrefix}&nbsp;</span>${stackTraceSpanElement}</span>\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: 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();
if (text instanceof Error) {
// create stacktrace
stack = text.stack?.split('\n');
if (stack) {
fullString = stack.join('\n');
Expand Down Expand Up @@ -301,7 +291,7 @@ export class Logger {
this.closeFileStreams(finalMessage, finalFatalMessage);
}

console.error(msg, ...args);
console.error(msg);

if (!this.disableFatalCrash) {
process.exit(5);
Expand Down
Loading

0 comments on commit 07889b7

Please sign in to comment.