Skip to content

Commit

Permalink
feat(logger): new package
Browse files Browse the repository at this point in the history
  • Loading branch information
MM25Zamanian committed Mar 4, 2024
1 parent aa96ef4 commit 04010de
Show file tree
Hide file tree
Showing 8 changed files with 353 additions and 1 deletion.
108 changes: 108 additions & 0 deletions packages/logger/README.md
Original file line number Diff line number Diff line change
@@ -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! 🎨🔍
43 changes: 43 additions & 0 deletions packages/logger/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"name": "@gecut/logger",
"version": "1.2.4",
"type": "module",
"author": {
"name": "S. MohammadMahdi Zamanian",
"email": "[email protected]",
"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:^"
}
}
37 changes: 37 additions & 0 deletions packages/logger/src/color-manager.ts
Original file line number Diff line number Diff line change
@@ -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;
}
7 changes: 7 additions & 0 deletions packages/logger/src/core.ts
Original file line number Diff line number Diff line change
@@ -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';
135 changes: 135 additions & 0 deletions packages/logger/src/logger.ts
Original file line number Diff line number Diff line change
@@ -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,
);
}
}
13 changes: 13 additions & 0 deletions packages/logger/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.base",
"compilerOptions": {
"composite": true,
"tsBuildInfoFile": ".tsbuildinfo",
"rootDir": "src",
"outDir": "."
},

"include": ["src/**/*.ts"],
"exclude": [],
"references": [{"path": "../utilities"}]
}
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"references": [
// packages
{"path": "./packages/types"},
{"path": "./packages/logger"},
{"path": "./packages/utilities"},

// demo
Expand Down
10 changes: 9 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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:
Expand Down

0 comments on commit 04010de

Please sign in to comment.