diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..b7bf294 --- /dev/null +++ b/index.ts @@ -0,0 +1,7 @@ +export { Level, type Options } from 'types' +export * as Writer from 'writer' +export {set_options, get_options, set_writer, get_writer, remove_writer, Logger} from 'index' + +import {Logger} from 'index' + +export default (namespace: string) => new Logger(namespace) diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..139d109 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,75 @@ +import {Level, level_to_string, type Options} from 'types' +import {Writer} from 'writer' + +export const writers = new Map +export let options: Options = { + format: "[$time] $level $namespace :", +} + +export function set_options(opt: Partial) { + options = {...options, ...opt} +} +export function get_options() : Readonly { + return options +} + +export function set_writer(name: string, writer: Writer) { + writers.set(name, writer) +} +export function get_writer(name: string) : Readonly | undefined { + return writers.get(name) +} +export function remove_writer(name: string) : boolean { + return writers.delete(name) +} + +export class Logger { + private readonly _namespace: string + + constructor(namespace: string) { + this._namespace = namespace + } + + public get namespace() : string { + return this._namespace + } + + public extend(sub_namespace: string) : Logger { + return new Logger(`${this.namespace}:${sub_namespace}`) + } + + public debug(...data: any[]) : void { + log(data, Level.DEBUG, this._namespace) + } + + public trace(...data: any[]) : void { + log(data, Level.TRACE, this._namespace) + } + + public info(...data: any[]) : void { + log(data, Level.INFO, this._namespace) + } + + public warn(...data: any[]) : void { + log(data, Level.WARNING, this._namespace) + } + + public error(...data: any[]) : void { + log(data, Level.ERROR, this._namespace) + } +} + +function log(message: any[], level: Level, namespace: string) : void { + if (writers.size === 0) return + + // Format header of log + const head = options.format + .replace("$time", new Date().toISOString()) + .replace("$namespace", namespace) + const head_bw = head.replace('$level', level_to_string(level, false)) + const head_color = head.replace("$level", level_to_string(level, true)) + + for (const writer of writers.values()) { + writer.log(level, writer.options.with_color ? head_color : head_bw, ...message) + } +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..fdaf5f9 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,59 @@ +import { Chalk } from 'chalk' + +export type Options = { + format: string, +} + +export enum Level { + DEBUG = 0, + TRACE = 1, + INFO = 2, + WARNING = 3, + ERROR = 4, +} + +export enum LevelFilter { + DEBUG = 0, + TRACE = 1, + INFO = 2, + WARNING = 3, + ERROR = 4, + OFF = 6, +} + +export type WriterOptions = { + minLevel: Level, + with_color: boolean, + [key: string | number | symbol]: any, +} + +export function level_to_string(level: Level, with_color: boolean) : string { + const str = get_string(level) + + if (!with_color) return str + + const color = get_color(level) + return color(str) +} + +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 " + } +} + +const chalk = new Chalk({ level: 2 }) // 256 colors + +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/writer/console.ts b/src/writer/console.ts new file mode 100644 index 0000000..309a6c6 --- /dev/null +++ b/src/writer/console.ts @@ -0,0 +1,18 @@ +import {Writer} from 'writer' +import {Level} from 'types' + +export class Console extends Writer { + protected write(level: Level, ...data: any[]) : void { + Console.get_logger(level)(...data) + } + + private static get_logger(level: Level) { + switch (level) { + case Level.DEBUG: return console.debug + case Level.TRACE: return console.debug + case Level.INFO: return console.log + case Level.WARNING: return console.warn + case Level.ERROR: return console.error + } + } +} diff --git a/src/writer/counter.ts b/src/writer/counter.ts new file mode 100644 index 0000000..11ff0b4 --- /dev/null +++ b/src/writer/counter.ts @@ -0,0 +1,30 @@ +import {Writer} from 'writer' +import {Level} from 'types' + +export class Counter extends Writer { + private readonly calls = { + debug: 0, + trace: 0, + info: 0, + warn: 0, + error: 0, + } + + public constructor() { + super({minLevel: Level.DEBUG, with_color: false}); + } + + protected write(level: Level, ...data: any[]): void { + switch (level) { + case Level.DEBUG: this.calls.debug++; break + case Level.TRACE: this.calls.trace++; break + case Level.INFO: this.calls.info++; break + case Level.WARNING: this.calls.warn++; break + case Level.ERROR: this.calls.error++; break + } + } + + public get count() { + return this.calls + } +} diff --git a/src/writer/file.ts b/src/writer/file.ts new file mode 100644 index 0000000..aa154f8 --- /dev/null +++ b/src/writer/file.ts @@ -0,0 +1,43 @@ +import {Writer} from 'writer' +import {type WriterOptions, Level} from 'types' + +export class File extends Writer { + public constructor(options: WriterOptions & { path: string }) { + const {path, ...rest} = options + super(rest) + } + + protected write(level: Level, ...data: any[]) : void { + // TODO + } +} + +export class DailyFile extends Writer { + private _writer: File + private readonly _folder: string + private now: Date = new Date() + + public constructor(options: WriterOptions & {folder: string}) { + const {folder, ...rest} = options + + super(rest) + this._folder = folder + + this._writer = DailyFile.start(this.options, this._folder, this.now) + } + + private static start(options: WriterOptions, folder: string, now: Date) : File { + const date = now.toISOString().split('T')[0] + const path = `${folder}/${date}.log` + return new File({...options, path}) + } + + protected write(level: Level, ...data: any[]): void { + if (new Date().getDay() !== this.now.getDay()) { + this.now = new Date() + this._writer = DailyFile.start(this.options, this._folder, this.now) + } + + return this._writer.log(level, data) + } +} diff --git a/src/writer/index.ts b/src/writer/index.ts new file mode 100644 index 0000000..ff32f5a --- /dev/null +++ b/src/writer/index.ts @@ -0,0 +1,22 @@ +export {Console} from 'writer/console' +export {File} from 'writer/file' +export {Counter} from 'writer/counter' + +import {Level, type WriterOptions} from 'types' + +export abstract class Writer { + protected readonly _options: WriterOptions; + protected constructor(options: WriterOptions) { + this._options = options + } + + public log(level: Level, ...data: any[]) : void { + if (level < this.options.minLevel) return + this.write(level, ...data) + } + protected abstract write(level: Level, ...data: any[]) : void; + + public get options() : WriterOptions { + return this._options + } +} \ No newline at end of file