Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release: v1.1.1: Saving Everything (patch) #2

Merged
merged 4 commits into from
Jul 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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