From 70da3f2002bfd07835d1baf9a5f23acf1f3b092b Mon Sep 17 00:00:00 2001 From: Florian Bouillon Date: Tue, 7 Jul 2020 11:13:35 +0200 Subject: [PATCH] Updated Logger implement Console --- packages/logger/.npmignore | 8 +- packages/logger/README.md | 41 +++ packages/logger/package.json | 5 +- packages/logger/src/Logger.ts | 423 ++++++++++++++++++++++++++--- packages/logger/src/index.ts | 8 + packages/logger/src/types.ts | 17 ++ packages/logger/test/index.ts | 54 ++++ packages/logger/tsconfig.test.json | 6 + 8 files changed, 522 insertions(+), 40 deletions(-) create mode 100644 packages/logger/README.md create mode 100644 packages/logger/src/types.ts create mode 100644 packages/logger/test/index.ts create mode 100644 packages/logger/tsconfig.test.json diff --git a/packages/logger/.npmignore b/packages/logger/.npmignore index 02153da..42fe038 100644 --- a/packages/logger/.npmignore +++ b/packages/logger/.npmignore @@ -1,8 +1,8 @@ -dist/*.d.ts.map node_modules src +test +.babelrc +.eslintrc.js .gitignore webpack.config.js -tsconfig.json -.babelrc -.eslintrc.js \ No newline at end of file +tsconfig.* \ No newline at end of file diff --git a/packages/logger/README.md b/packages/logger/README.md new file mode 100644 index 0000000..22309a1 --- /dev/null +++ b/packages/logger/README.md @@ -0,0 +1,41 @@ +# @dzeio/logger + +A Better logger for your test + +## Install + +For the browser via `unpkg` use it from Unpkg or download the file locally + +```html + + +``` + +Via Yarn/NPM + +```bash +yarn add @dzeio/logger +# or +npm i @dzeio/logger +``` + +## Usage + +As the Logger Implements the `console` the usage don't vary on their function BUT you have more control + +ex: + +```js +import Logger from '@dzeio/logger' // Import the class +const logger = new Logger('prefix') // initialize it +// or +import { logger } from '@dzeio/logger' // Import already initialized one + +// You can block Logging for some time +Logger.isBlocked(true) // false to unblock +``` diff --git a/packages/logger/package.json b/packages/logger/package.json index 6fc842e..c1ac423 100644 --- a/packages/logger/package.json +++ b/packages/logger/package.json @@ -13,7 +13,7 @@ "browser": "./dist/browser.js", "types": "./dist/Logger.d.ts", "dependencies": { - "chalk": "^4.1.0" + "ansi-colors": "^4.1.1" }, "devDependencies": { "@babel/core": "^7.10.3", @@ -29,6 +29,7 @@ }, "scripts": { "prepublishOnly": "yarn build", - "build": "webpack --mode=\"production\" && tsc" + "build": "webpack --mode=\"production\" && tsc", + "test": "ts-node test" } } diff --git a/packages/logger/src/Logger.ts b/packages/logger/src/Logger.ts index 1b686c7..fa4d616 100644 --- a/packages/logger/src/Logger.ts +++ b/packages/logger/src/Logger.ts @@ -1,54 +1,372 @@ -import { white, blue, yellow, green } from 'chalk' +import colors, { black, white, blue, yellow, green } from 'ansi-colors' +import { theme, logType, ObjectArray } from './types' -/** - * Logger Class - */ -export default class Logger { +export default class Logger implements Console { /** - * If it is set to true all message won't be shown until it is set to false + * The maximun prefix length */ - public static isBlocked = false - private static queue: Array> = [] private static prefixLen = 0 /** - * Log a message into the console - * @param prefix the prefix used - * @param message the message to log + * Current theme in use + * + * - undefined = detect theme + * - light = theme for light background + * - dark = theme for dark background */ - public static log(prefix: string, ...message: Array) { - this.queue.push(this.formatMessage(prefix, ...message)) - while (this.queue.length > 0 && !this.isBlocked) { - const item = this.queue.shift() - if (!item) { - continue - } - console.log(...item) + private static theme: theme = undefined + + /** + * List of loggers currently in use + */ + private static loggers: Array = [] + + /** + * Console memory + * (IDK what it is) + */ + public memory = console.memory + + // NodeJS console (will be undefined on ) + /** + * NODEJS ONLY + * It will be undefined in Browser context + */ + // @ts-ignore + public Console = console.Console + /** + * The messages queue + */ + private queue: Array<{type: logType, msg: Array}> = [] + + /** + * If true message won't be shown to console + * (`urgent` message will still show) + */ + private blocked = false + + /** + * `count` function object value + */ + private countCount: ObjectArray = {} + + /** + * `time` function object value + */ + private timeCount: ObjectArray = {} + + /** + * Construct a new Logger + * @param prefix the prefix shown + */ + public constructor( + public prefix: string = 'Logger' + ) { + Logger.loggers.push(this) + + // If Firefox disable colors + // @ts-ignore + if (typeof InstallTrigger !== 'undefined') { + colors.enabled = false } } /** - * Log a message into the console (passthrough the `Logger.isBlocked` boolean) - * @param prefix The prefix used - * @param message the message to log + * STATIC FUNCTIONS */ - public static urgent(prefix: string, ...message: Array) { - console.log(...this.formatMessage(prefix, ...message)) + + /** + * Choose if every loggers should be blocked or not + * @param value + */ + public static isBlocked(value: boolean) { + for (const lgr of this.loggers) { + lgr.blocked = value + if (!value) { + lgr.processQueue() + } + } } - private static formatMessage(prefix: string, ...message: Array): Array { - if (this.prefixLen < prefix.length) { - this.prefixLen = prefix.length + /** + * Force a specific theme to be used + * + * (if undefined it will try to detect it) + * + * @param themeChosen The theme chosen to be overriden + */ + public static forceTheme(themeChosen: theme) { + this.theme = themeChosen + } + + /** + * INSTANCE FUNCTIONS + */ + + /** + * return nothing if `condition` is `true`, log `data` if it is not + * @param condition The value tested for being truthy + * @param data The message to pass if `condition` is false + */ + public assert(condition?: boolean | undefined, ...data: Array): void { + if (!condition) { + return this.error('Assertion failed:', ...data) } - const els: Array = ['', ''] - if (this.prefixLen > prefix.length) { - const diff = this.prefixLen - prefix.length - els[0] = this.buildSpace(diff / 2 - (diff % 2 !== 0 ? 1 : 0)) - els[1] = this.buildSpace(diff / 2) + } + + /** + * Clear the console/terminal + */ + public clear(): void { + console.clear() + } + + /** + * log the number of times `count` has been called + * @param label Display label, default to `default` + */ + public count(label: string = 'default'): void { + if (!Object.prototype.hasOwnProperty.call(this.countCount, label)) { + this.countCount[label] = 0 + } + this.log(`${label}:`, ++this.countCount[label]) + } + + /** + * Reset the count started with `count` function + * @param label Display label, default to `default` + */ + public countReset(label: string = 'default'): void { + delete this.countCount[label] + } + + /** + * log as debug the `data` + * (On browser like Chrome in the devtool you have to enable it beforehand) + * @param data the data to log + */ + public debug(...data: Array): void { + this.process('debug', data) + } + + /** + * NOTE: Method not fully implemented + * + * print a full object to the logger + * @param item Object to log + * @param options (NodeJS only) option for the display + */ + public dir(item?: any, options?: {showHidden?: boolean, depth?: number, colors?: boolean}): void { + if (typeof item !== 'object') { + return this.log(item) + } + this.log('Method not fully implemented') + if (typeof window !== 'undefined') { + console.dir(item) + return + } + console.dir(item, options) + } + + /** + * Alias for `log` function + * @param data data to log + */ + public dirxml(...data: Array): void { + this.process('dirxml', data) + } + + /** + * print to stderr on NodeJS, print with red coloring on browsers + * @param data data to log + */ + public error(...data: Array): void { + this.process('error', data) + } + + /** + * Alias to `error` function + * @param data data to log + */ + public exception(...data: Array): void { // NOT STANDARD / FIREFOX ONLY + this.error(...data) + } + + /** + * NodeJS -> Increase the indentation of future logs with `label` as header + * + * Browser -> Group futures logs + * @param label labels to use as header + */ + public group(...label: Array): void { + console.group(...label) + } + + /** + * Alias of `group` function + * @param label label to use as header + */ + public groupCollapsed(...label: Array): void { + this.group(...label) + } + + /** + * End the current group started with the `group` function + */ + public groupEnd(): void { + console.groupEnd() + } + + /** + * Log to console as Info + * (Only Chrome else it is an alias of `log`) + * @param data data to log + */ + public info(...data: Array): void { + this.process('info', data) + } + + /** + * Log to console + * @param data data to log + */ + public log(...data: Array) { + this.process('log', data) + } + + /** + * make a new profile in browsers or in NodeJS with `--inspect` flag + * @param name Profile name + */ + public profile(name?: string) { + // @ts-ignore + if (console.profile) { + // @ts-ignore + console.profile(name) + } else { + throw new Error('profile don\'t exist in the current contexte') + } + } + + /** + * end the current running profile + * @param name Profile name + */ + public profileEnd(name?: string) { + // @ts-ignore + console.profileEnd(name) + } + + /** + * Print a Table to the console + * @param tabularData Table data + * @param properties Table properties + */ + public table(tabularData?: any, properties?: Array | undefined): void { + console.table(tabularData, properties) + } + + /** + * Start a timer + * @param label Timer label + */ + public time(label: string = 'default'): void { + if (!Object.prototype.hasOwnProperty.call(this.timeCount, label)) { + this.timeCount[label] = new Date() + return + } + this.warn(`Timer '${label}' already exists.`) + } + + /** + * End a timer + * @param label Timer label + */ + public timeEnd(label: string = 'default'): void { + const diff = (new Date()).getTime() - this.timeCount[label].getTime() + this.log(`${label}: ${diff}ms - Timer ended`) + delete this.timeCount[label] + } + + /** + * Log to the console with timer before it + * @param label Timer label + * @param data data to log next to the timer + */ + public timeLog(label: string = 'default', ...data: Array): void { + if (!this.timeCount[label]) { + this.warn(`Timer '${label}' does not exist`) + return + } + const diff = (new Date()).getTime() - this.timeCount[label].getTime() + this.log(`${label}: ${diff}ms`, ...data) + } + + /** + * Browsers only, Add maker in the browser's Performance or Waterfall tool + * @param label Label to use + */ + public timeStamp(label?: string): void { + console.timeStamp(label) + } + + /** + * Print a stack trace to console + * @param data data to trace + */ + public trace(...data: Array): void { + this.process('trace', data) + } + + /** + * Warn in the console + * (in NodeJS context it is an alias to the `error` function) + * @param data data to log + */ + public warn(...data: Array): void { + this.process('warn', data) + } + + /** + * Log to the console but skiping `isBlocked` check + * @param data data to log + */ + public urgent(...data: Array) { + console.log(...this.formatMessage(data)) + } + + /** + * Precoess a message sent by one of the public functions + * @param type logType + * @param message message to log + */ + private process(type: logType, message: Array) { + this.queue.push({type, msg: this.formatMessage(message)}) + this.processQueue() + } + + /** + * Format a message for the console + * @param message message to format + */ + private formatMessage(message: Array): Array { + const prefix = this.prefix + const prefixLen = prefix.length + + if (Logger.prefixLen < prefixLen) { + Logger.prefixLen = prefixLen + } + + const spacers: Array = ['', ''] + if (Logger.prefixLen > prefixLen) { + const diff = Logger.prefixLen - prefixLen + const diff2 = diff /2 + spacers[0] = this.buildSpace(diff2 - (diff % 2 !== 0 ? 1 : 0)) + spacers[1] = this.buildSpace(diff2) } const res: Array = [ - `${white('[ ')}${els[0]}${blue(prefix)}${els[1]}${white(' ]')}:` // prefix + `${this.blackOrWhite('[ ')}${spacers[0]}${blue(prefix)}${spacers[1]}${this.blackOrWhite(' ]')}:` ].concat( message.map((el) => { if (typeof el === 'object') { @@ -60,11 +378,48 @@ export default class Logger { return res } - private static buildSpace(count: number): string { + /** + * Process Waiting queue + */ + private processQueue() { + while (this.queue.length > 0 && !this.blocked) { + const item = this.queue.shift() + if (!item) { + continue + } + if (item.type === 'trace') { + console.log(item.msg.shift()) + } + console[item.type](...item.msg) + } + } + + /** + * Build a new string of `count` spaces + * @param count number of spaces to add + */ + private buildSpace(count: number): string { let str = '' for(let i = 0; i < count; i++) { str += ' ' } return str } + + /** + * Color the text in black or white depending on `theme` variable or detection + * @param text the text to color + */ + private blackOrWhite(text: string): string { + if ((!Logger.theme && typeof window !== 'undefined') || Logger.theme === 'light') { + return black(text) + } + return white(text) + } + } + +/** + * Export a default Logger + */ +export const logger = new Logger() diff --git a/packages/logger/src/index.ts b/packages/logger/src/index.ts index 74b903b..0fab1a4 100644 --- a/packages/logger/src/index.ts +++ b/packages/logger/src/index.ts @@ -2,5 +2,13 @@ import Logger from './Logger' // Browser Import +/** + * Init Logger in global context and add function to replace default console + */ + // @ts-ignore window.Logger = Logger +// @ts-ignore +window.initConsole = function() { + window.console = new Logger('Console') +} diff --git a/packages/logger/src/types.ts b/packages/logger/src/types.ts new file mode 100644 index 0000000..c17879f --- /dev/null +++ b/packages/logger/src/types.ts @@ -0,0 +1,17 @@ +/** + * Object Array + */ +export interface ObjectArray { + [key: string]: T +} + +/** + * used Internally, + * the different types of logs + */ +export type logType = 'debug' | 'dir' | 'dirxml' | 'error' | 'info' | 'log' | 'trace' | 'warn' + +/** + * The possible themes + */ +export type theme = undefined | 'light' | 'dark' diff --git a/packages/logger/test/index.ts b/packages/logger/test/index.ts new file mode 100644 index 0000000..d8f6d65 --- /dev/null +++ b/packages/logger/test/index.ts @@ -0,0 +1,54 @@ +import { logger as console } from '../src/Logger' + +/** + * This test file is simple :D + */ + +// Should remove the precedent line +console.clear() + +// Start a timer +console.time() +console.timeLog(undefined, 'Started Timer') + +// Test assert +console.log('Assert') +console.assert(true, 'i am true') // should print nothing +console.assert(false, 'i am false') // Should print 'i am false' + +// all the different logs +console.debug('debug') +console.dir('dir') +console.dirxml('dirxml') +console.error('error') +console.info('info') +console.log('log') +console.trace('trace') +console.warn('warn') + +// Timelog +console.timeLog(undefined, 'timeLog') + +console.group('New group') +console.group('New group') +console.group('New group') +console.group('New group') +console.group('New group') +console.group('New group') +console.group('New group') +console.group('New group') +console.log('Log in a group') +console.groupEnd() +console.groupEnd() +console.groupEnd() +console.groupEnd() +console.groupEnd() +console.groupEnd() +console.groupEnd() +console.groupEnd() +console.groupEnd() + +console.table([{ a: 1, b: 'Y' }, { a: 'Z', b: 2 }], ['a']) + +// TimeEnd +console.timeEnd() diff --git a/packages/logger/tsconfig.test.json b/packages/logger/tsconfig.test.json new file mode 100644 index 0000000..43d7ac9 --- /dev/null +++ b/packages/logger/tsconfig.test.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig.json", + "files": [ + "test/index.ts" + ] +}