From 04010de36f5e144d10d3f08b7c43a385c283087d Mon Sep 17 00:00:00 2001 From: "S. MohammadMahdi Zamanian" Date: Mon, 4 Mar 2024 09:50:44 +0330 Subject: [PATCH] feat(logger): new package --- packages/logger/README.md | 108 +++++++++++++++++++++ packages/logger/package.json | 43 +++++++++ packages/logger/src/color-manager.ts | 37 ++++++++ packages/logger/src/core.ts | 7 ++ packages/logger/src/logger.ts | 135 +++++++++++++++++++++++++++ packages/logger/tsconfig.json | 13 +++ tsconfig.json | 1 + yarn.lock | 10 +- 8 files changed, 353 insertions(+), 1 deletion(-) create mode 100644 packages/logger/README.md create mode 100644 packages/logger/package.json create mode 100644 packages/logger/src/color-manager.ts create mode 100644 packages/logger/src/core.ts create mode 100644 packages/logger/src/logger.ts create mode 100644 packages/logger/tsconfig.json diff --git a/packages/logger/README.md b/packages/logger/README.md new file mode 100644 index 0000000..54cc235 --- /dev/null +++ b/packages/logger/README.md @@ -0,0 +1,108 @@ +# Gecut Logger + +Gecut Logger is a flexible and colorful logging tool built to enhance your debugging experience for both Node.js and browser-based JavaScript applications. With environment-aware functionalities and stylish output, Gecut Logger helps you to keep track of your application's behavior in an organized and visually appealing way. + +## Features 🚀 + +- **Environment Compatibility**: Works seamlessly in both Node.js and browser environments. +- **Debug Modes**: Easily toggle debugging on and off with DEV_MODE, taking advantage of the localStorage in browsers and environment variables in Node.js. +- **Color Coding**: A colorful logging experience with auto-rotating colors to distinguish logs in both the browser console and terminal. +- **Structured Logging**: Logs are presented with indices, domains, and scope stylings to make tracing easier. +- **Development Friendly**: Rich debug functions available during development for an in-depth examination of your code. +- **Sub-Loggers**: Create sub-loggers with inherited features from a parent logger to maintain the context during complex application workflows. + +## Installation 📦 + +Install the package using npm: + +```bash +npm install @gecut/logger +``` + +## Usage 🛠️ + +Getting started with Gecut Logger is straightforward: + +```ts +import { GecutLogger } from '@gecut/logger'; + +const logger = new GecutLogger('MyApp'); + +// Log an error with a method and description +logger.error('init', 'INIT_FAIL', 'Initialization failed due to an unknown error'); + +// Warn with additional arguments +logger.warning('loadData', 'INVALID_RESPONSE', 'Data response is invalid', { userId: 1 }); + +// Begin a timed operation (development mode only) +logger.time?.('fetchData'); +// code to fetch data here... +logger.timeEnd?.('fetchData'); +``` + +### Example in Browser 🌐 + +```ts +// Assuming DEV_MODE is enabled in localStorage +const logger = new GecutLogger('UIComponent'); + +// Trace a method call and its arguments +logger.methodArgs?.('render', { items: 5 }); + +// Log a property change +logger.property?.('isVisible', true); + +// Other custom logs +logger.other?.('The component has been successfully rendered'); +``` + +### Example in Node.js 🖥️ + +```ts +const logger = new GecutLogger('Server'); + +// Log an error with detailed information +logger.error('start', 'STARTUP_ERROR', 'Server failed to start on port 3000'); + +// Log a method entry +logger.method?.('handleRequest'); +``` + +### Sub-Logger Creation 🧱 + +```ts +const mainLogger = new GecutLogger('App'); +const dbLogger = mainLogger.sub('Database'); + +dbLogger.error('query', 'QUERY_FAIL', 'Query to the products table failed'); +``` + +## API Documentation 📖 + +### GecutLogger + +- **constructor(domain: string, devMode?: boolean)**: Initializes a new logger instance with a domain namespace and an optional development mode flag. + +### Methods + +- **property(property: string, value: unknown)**: Logs a property change (development mode only). +- **method(method: string)**: Logs a method invocation (development mode only). +- **methodArgs(method: string, args: unknown)**: Logs a method call with its arguments (development mode only). +- **methodFull(method: string, args: unknown, result: unknown)**: Logs a method with arguments and the result (development mode only). +- **other(...args: unknown[])**: Logs any custom information (development mode only). +- **warning(method: string, code: string, desc: string, ...args: unknown[])**: Logs a warning message, with a method reference, warning code, description, and additional arguments. +- **error(method: string, code: string, ...args: unknown[])**: Logs an error message, with a method reference, error code, and additional arguments. + +### Static Methods + +- **sub(domain: string, devMode?: boolean)**: Creates a sub-logger with a specified domain scope and optional development mode flag. + +## Contributing 🤝 + +Your contributions are always welcome! If you have suggestions or find bugs, feel free to submit an issue or pull request on our [GitHub repository](https://github.com/gecut/gecut). + +## License 📄 + +This project is licensed under the MIT License - see the LICENSE file for details. + +Bring some color and structure to your debugging efforts with Gecut Logger! 🎨🔍 diff --git a/packages/logger/package.json b/packages/logger/package.json new file mode 100644 index 0000000..1a7c5b5 --- /dev/null +++ b/packages/logger/package.json @@ -0,0 +1,43 @@ +{ + "name": "@gecut/logger", + "version": "1.2.4", + "type": "module", + "author": { + "name": "S. MohammadMahdi Zamanian", + "email": "mm25zamanian@gmail.com", + "url": "https://mm25zamanain.ir" + }, + "description": "Fancy colorful console debugger with custom scope written in tiny TypeScript, ES module.", + "keywords": [ + "log", + "logger", + "console", + "debug", + "typescript", + "esm", + "gecut" + ], + "license": "MIT", + "publishConfig": { + "access": "public" + }, + "main": "logger.js", + "types": "logger.d.ts", + "exports": { + ".": { + "default": "./logger.js", + "types": "./logger.d.ts" + } + }, + "repository": { + "type": "git", + "url": "https://github.com/gecut/hybrid-core", + "directory": "packages/logger" + }, + "bugs": { + "url": "https://github.com/gecut/hybrid-core/issues" + }, + "dependencies": { + "@gecut/utilities": "workspace:^" + } +} diff --git a/packages/logger/src/color-manager.ts b/packages/logger/src/color-manager.ts new file mode 100644 index 0000000..143448d --- /dev/null +++ b/packages/logger/src/color-manager.ts @@ -0,0 +1,37 @@ +import {NODE_MODE} from './core'; + +export const colors = { + browsers: { + RED: '#EF5350', + PINK: '#F06292', + PURPLE: '#AB47BC', + DEEP_PURPLE: '#7E57C2', + INDIGO: '#5C6BC0', + BLUE: '#42A5F5', + LIGHT_BLUE: '#03A9F4', + CYAN: '#26C6DA', + TEAL: '#009688', + GREEN: '#4CAF50', + LIGHT_GREEN: '#8BC34A', + LIME: '#CDDC39', + YELLOW: '#FDD835', + AMBER: '#FFC107', + ORANGE: '#FF9800', + }, + node: ['0;36', '0;35', '0;34', '0;33', '0;32'], +}; + +const colorsList = NODE_MODE ? colors.node : Object.values(colors.browsers); +let colorIndex = 0; + +export function getColor(): string { + const color = colorsList[colorIndex]; + + colorIndex++; + + if (colorIndex >= colorsList.length) { + colorIndex = 0; + } + + return color; +} diff --git a/packages/logger/src/core.ts b/packages/logger/src/core.ts new file mode 100644 index 0000000..e37891e --- /dev/null +++ b/packages/logger/src/core.ts @@ -0,0 +1,7 @@ +import {isNode} from '@gecut/utilities/browser-or-node.js'; +import {env} from '@gecut/utilities/env.js'; + +export const NODE_MODE = isNode(); + +export const DEV_MODE = + (NODE_MODE === false ? localStorage?.getItem('DEBUG') ?? '0' : env('DEBUG', '0', 'string')) === '1'; diff --git a/packages/logger/src/logger.ts b/packages/logger/src/logger.ts new file mode 100644 index 0000000..66e5a35 --- /dev/null +++ b/packages/logger/src/logger.ts @@ -0,0 +1,135 @@ +/* eslint-disable max-len */ +import {getColor} from './color-manager'; +import {DEV_MODE, NODE_MODE} from './core'; + +let globalIndex = 0; + +export class GecutLogger { + constructor(domain: string, devMode = DEV_MODE) { + this.domain = GecutLogger.stabilizeDomain(domain); + this.devMode = devMode; + this.style = GecutLogger.style.scope.replaceAll('{{color}}', getColor()); + + this.index = ++globalIndex; + + this.initial(); + + if (DEV_MODE) { + this.initialDevelopments(); + } + } + + private static keySection = NODE_MODE ? '%s%s%s%s%s' : '%c%s%c%s%c'; + private static style = { + scope: NODE_MODE ? '\x1b[{{color}}m' : 'color: {{color}};', + reset: NODE_MODE ? '\x1b[0m' : 'color: inherit;', + dim: NODE_MODE ? '\x1b[2m' : 'color:#888;', + }; + + index: number; + domain: string; + devMode: boolean; + style: string; + + property?: (property: string, value: unknown) => void; + method?: (method: string) => void; + methodArgs?: (method: string, args: unknown) => void; + methodFull?: (method: string, args: unknown, result: unknown) => void; + other?: (...args: unknown[]) => void; + + warning!: (method: string, code: string, desc: string, ...args: unknown[]) => void; + error!: (method: string, code: string, ...args: unknown[]) => void; + + time?: (label: string) => void; + timeEnd?: (label: string) => void; + + sub(domain: string, _devMode = this.devMode) { + return new GecutLogger(`${this.domain} ⬅️ ${domain}`, _devMode); + } + + private static stabilizeDomain(domain: string): string { + domain = domain.trim(); + + const first = domain.charAt(0); + + if (first !== '[' && first !== '{' && first !== '<') { + domain = `[${domain}]`; + } + + return domain; + } + + private initial() { + this.error = NODE_MODE + ? console.error.bind( + console, + `${GecutLogger.style.dim}[${this.index}] ${this.style}❌ \n%s\x1b[31m.%s() Error \`%s\`${GecutLogger.style.reset}\n`, + this.domain, + ) + : console.error.bind(console, '%c%s%c.%s() Error `%s`\n', this.style, this.domain, GecutLogger.style.reset); + + this.warning = NODE_MODE + ? console.warn.bind( + console, + `${GecutLogger.style.dim}[${this.index}] ${this.style}⚠️ \n%s\x1b[33m.%s() Accident \`%s\` %s!${GecutLogger.style.reset}`, + this.domain, + ) + : console.warn.bind(console, '%c%s%c.%s() Warn `%s` %s!', this.style, this.domain, GecutLogger.style.reset); + } + + private initialDevelopments() { + this.time = (label: string) => console.time(`[${this.index}] ${this.domain} ${label} duration`); + + this.timeEnd = (label: string) => console.timeEnd(`[${this.index}] ${this.domain} ${label} duration`); + + this.property = console.debug.bind( + console, + `${GecutLogger.keySection}.%s = %o;`, + GecutLogger.style.dim, + `[${this.index}] `, + this.style, + this.domain, + GecutLogger.style.reset, + ); + + this.method = console.debug.bind( + console, + `${GecutLogger.keySection}.%s();`, + GecutLogger.style.dim, + `[${this.index}] `, + this.style, + this.domain, + GecutLogger.style.reset, + ); + + this.methodArgs = console.debug.bind( + console, + `${GecutLogger.keySection}.%s(%o);`, + GecutLogger.style.dim, + `[${this.index}] `, + this.style, + this.domain, + GecutLogger.style.reset, + ); + + this.methodFull = console.debug.bind( + console, + `${GecutLogger.keySection}.%s(%o) => %o`, + GecutLogger.style.dim, + `[${this.index}] `, + this.style, + this.domain, + GecutLogger.style.reset, + ); + + this.other = console.debug.bind( + console, + GecutLogger.keySection, + GecutLogger.style.dim, + `[${this.index}] `, + this.style, + this.domain, + GecutLogger.style.reset, + ); + } +} diff --git a/packages/logger/tsconfig.json b/packages/logger/tsconfig.json new file mode 100644 index 0000000..db03d4c --- /dev/null +++ b/packages/logger/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.base", + "compilerOptions": { + "composite": true, + "tsBuildInfoFile": ".tsbuildinfo", + "rootDir": "src", + "outDir": "." + }, + + "include": ["src/**/*.ts"], + "exclude": [], + "references": [{"path": "../utilities"}] +} diff --git a/tsconfig.json b/tsconfig.json index fd8f6a0..13a5ba8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,6 +11,7 @@ "references": [ // packages {"path": "./packages/types"}, + {"path": "./packages/logger"}, {"path": "./packages/utilities"}, // demo diff --git a/yarn.lock b/yarn.lock index 75c8b39..3269eda 100644 --- a/yarn.lock +++ b/yarn.lock @@ -92,6 +92,14 @@ __metadata: languageName: node linkType: hard +"@gecut/logger@workspace:packages/logger": + version: 0.0.0-use.local + resolution: "@gecut/logger@workspace:packages/logger" + dependencies: + "@gecut/utilities": "workspace:^" + languageName: unknown + linkType: soft + "@gecut/types@workspace:^, @gecut/types@workspace:packages/types": version: 0.0.0-use.local resolution: "@gecut/types@workspace:packages/types" @@ -102,7 +110,7 @@ __metadata: languageName: unknown linkType: soft -"@gecut/utilities@workspace:packages/utilities": +"@gecut/utilities@workspace:^, @gecut/utilities@workspace:packages/utilities": version: 0.0.0-use.local resolution: "@gecut/utilities@workspace:packages/utilities" dependencies: