diff --git a/eslint.config.ts b/eslint.config.ts index 754d790..7da81ef 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -9,7 +9,12 @@ export default defineConfig([ files: ['**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], plugins: {js}, extends: ['js/recommended'], - languageOptions: {globals: {...globals.browser, ...globals.node}} + languageOptions: {globals: {...globals.browser, ...globals.node}}, + rules: { + 'no-unused-vars': 'warn', + '@typescript-eslint/no-unused-vars': ['warn'] + } }, + globalIgnores(['dist']) ]) diff --git a/package.json b/package.json index 41832e1..2c9336d 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,9 @@ "typecheck": "tsc --noEmit" }, - "dependencies": {}, + "dependencies": { + "chalk": "^5.6.2" + }, "devDependencies": { "@eslint/js": "^9.35.0", "@types/bun": "^1.2.21", diff --git a/src/color.ts b/src/color.ts new file mode 100644 index 0000000..52ca221 --- /dev/null +++ b/src/color.ts @@ -0,0 +1,19 @@ +import {Chalk} from 'chalk' +import {Level} from './types' + +const chalk = new Chalk({level: 2}) // 256 colors + +export function get_color(level: Level) { + switch (level) { + case Level.DEBUG: + return chalk.blueBright + case Level.TRACE: + return chalk.green + case Level.INFO: + return (str: string) => str + case Level.WARNING: + return chalk.hex('#FFA500') + case Level.ERROR: + return chalk.red + } +} diff --git a/src/index.ts b/src/index.ts index dbe58b4..1eef0f4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,5 @@ -export function greet() { - console.log('Hello, world!') -} +import {Level, type WriterOptions, type Writer} from './types' +import {Logger, writers, options} from './logger' + +export default (namespace: string): Logger => new Logger(namespace) +export {Logger, Level, type WriterOptions, type Writer, writers, options} diff --git a/src/logger.ts b/src/logger.ts new file mode 100644 index 0000000..981f981 --- /dev/null +++ b/src/logger.ts @@ -0,0 +1,79 @@ +import {Level, type Options, type Writer} from './types' + +export const writers = new Map() +export const options: Options = { + format: '[$time] $level $namespace :', + pad_level: true, + verbose: true +} + +export class Logger { + constructor(private readonly _namespace: string) {} + + public get namespace(): string { + return this._namespace + } + + public extend(sub_namespace: string): Logger { + return new Logger(`${this.namespace}:${sub_namespace}`) + } + + public debug(...data: unknown[]): void { + log(data, Level.DEBUG, this._namespace) + } + + public trace(...data: unknown[]): void { + log(data, Level.TRACE, this._namespace) + } + + public info(...data: unknown[]): void { + log(data, Level.INFO, this._namespace) + } + + public warn(...data: unknown[]): void { + log(data, Level.WARNING, this._namespace) + } + + public error(...data: unknown[]): void { + log(data, Level.ERROR, this._namespace) + } +} + +function log(message: unknown[], level: Level, namespace: string): void { + if (writers.size === 0) { + if (options.verbose) console.log('No writer subscribed, discard message') + return + } + + // Format header of log + const head = options.format.replace('$time', new Date().toISOString()).replace('$namespace', namespace) + + let lvl = get_string(level) + if (!options.pad_level) lvl = lvl.trimEnd() + + const head_bw = head.replace('$level', lvl) + + for (const [name, writer] of writers.entries()) { + const options = writer.options + if (options?.minLevel > level) { + if (options.verbose) console.log(`Writer's level is lower, discard message for ${name}`) + continue + } + writer.log(level, head_bw, ...message) + } +} + +function get_string(level: Level): string { + switch (level) { + case Level.DEBUG: + return 'DEBUG ' + case Level.TRACE: + return 'TRACE ' + case Level.INFO: + return 'INFO ' + case Level.WARNING: + return 'WARNING' + case Level.ERROR: + return 'ERROR ' + } +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..2e1fa6d --- /dev/null +++ b/src/types.ts @@ -0,0 +1,29 @@ +/** + * Global options for loggers + * + * Format : can contain $time, $level and $namespace + */ +export type Options = { + format: string + pad_level: boolean + verbose: boolean +} + +export enum Level { + TRACE = 0, + DEBUG = 1, + INFO = 2, + WARNING = 3, + ERROR = 4 +} + +export type WriterOptions = { + minLevel: Level + [key: string | number | symbol]: unknown +} + +export interface Writer { + log(level: Level, ...data: unknown[]): void + get options(): WriterOptions + readonly _options: WriterOptions +}