From b2cd4093048b2cb563be33814b6ec8de5c4fda2e Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Wed, 3 Aug 2022 22:16:23 +0200 Subject: [PATCH] feat(logger): add MemoryLogger, ALogger - extract abstract ALogger - add MemoryLogger - refactor ConsoleLogger - add LogEntry tuple type (migrated from @thi.ng/rstream-log) - add tests --- packages/logger/package.json | 165 +++++++++++++++++---------------- packages/logger/src/alogger.ts | 36 +++++++ packages/logger/src/api.ts | 22 +++++ packages/logger/src/console.ts | 37 +------- packages/logger/src/index.ts | 1 + packages/logger/src/memory.ts | 19 ++++ packages/logger/test/index.ts | 35 ++++++- 7 files changed, 200 insertions(+), 115 deletions(-) create mode 100644 packages/logger/src/alogger.ts create mode 100644 packages/logger/src/memory.ts diff --git a/packages/logger/package.json b/packages/logger/package.json index 9f0dd5a09a..5dec55db3f 100644 --- a/packages/logger/package.json +++ b/packages/logger/package.json @@ -1,81 +1,88 @@ { - "name": "@thi.ng/logger", - "version": "1.1.9", - "description": "Types & basis infrastructure for arbitrary logging (w/ default impls)", - "type": "module", - "module": "./index.js", - "typings": "./index.d.ts", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/thi-ng/umbrella.git" - }, - "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/logger#readme", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - } - ], - "author": "Karsten Schmidt ", - "license": "Apache-2.0", - "scripts": { - "build": "yarn clean && tsc --declaration", - "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", - "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", - "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", - "doc:readme": "yarn doc:stats && tools:readme", - "doc:stats": "tools:module-stats", - "pub": "yarn npm publish --access public" - }, - "devDependencies": { - "@microsoft/api-extractor": "^7.25.0", - "rimraf": "^3.0.2", - "tools": "workspace:^", - "typedoc": "^0.22.17", - "typescript": "^4.7.4" - }, - "keywords": [ - "console", - "logger", - "filter", - "typescript" - ], - "publishConfig": { - "access": "public" - }, - "browser": { - "process": false, - "setTimeout": false - }, - "engines": { - "node": ">=12.7" - }, - "files": [ - "*.js", - "*.d.ts" - ], - "exports": { - ".": { - "default": "./index.js" - }, - "./api": { - "default": "./api.js" - }, - "./console": { - "default": "./console.js" - }, - "./null": { - "default": "./null.js" - } - }, - "thi.ng": { - "related": [ - "rstream-log" - ] - } + "name": "@thi.ng/logger", + "version": "1.1.9", + "description": "Types & basis infrastructure for arbitrary logging (w/ default impls)", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/logger#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "tools:node-esm test/index.ts" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.25.0", + "rimraf": "^3.0.2", + "tools": "workspace:^", + "typedoc": "^0.22.17", + "typescript": "^4.7.4" + }, + "keywords": [ + "console", + "logger", + "filter", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "browser": { + "process": false, + "setTimeout": false + }, + "engines": { + "node": ">=12.7" + }, + "files": [ + "*.js", + "*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./alogger": { + "default": "./alogger.js" + }, + "./api": { + "default": "./api.js" + }, + "./console": { + "default": "./console.js" + }, + "./memory": { + "default": "./memory.js" + }, + "./null": { + "default": "./null.js" + } + }, + "thi.ng": { + "related": [ + "rstream-log" + ] + } } diff --git a/packages/logger/src/alogger.ts b/packages/logger/src/alogger.ts new file mode 100644 index 0000000000..13040242dd --- /dev/null +++ b/packages/logger/src/alogger.ts @@ -0,0 +1,36 @@ +import { ILogger, LogLevel, LogLevelName } from "./api.js"; + +/** + * Abstract {@link ILogger} base implementation. + */ +export abstract class ALogger implements ILogger { + id: string; + level: LogLevel; + + constructor(id: string, level: LogLevel | LogLevelName = LogLevel.FINE) { + this.id = id; + this.level = typeof level === "string" ? LogLevel[level] : level; + } + + fine(...args: any[]): void { + this.level <= LogLevel.FINE && this.log(LogLevel.FINE, args); + } + + debug(...args: any[]): void { + this.level <= LogLevel.DEBUG && this.log(LogLevel.DEBUG, args); + } + + info(...args: any[]): void { + this.level <= LogLevel.INFO && this.log(LogLevel.INFO, args); + } + + warn(...args: any[]): void { + this.level <= LogLevel.WARN && this.log(LogLevel.WARN, args); + } + + severe(...args: any[]): void { + this.level <= LogLevel.SEVERE && this.log(LogLevel.SEVERE, args); + } + + protected abstract log(level: LogLevel, args: any[]): void; +} diff --git a/packages/logger/src/api.ts b/packages/logger/src/api.ts index 37480d2b56..dc206d7106 100644 --- a/packages/logger/src/api.ts +++ b/packages/logger/src/api.ts @@ -27,3 +27,25 @@ export interface ILogger { warn(...args: any[]): void; severe(...args: any[]): void; } + +/** + * Tuple type describing a single log entry. + */ +export interface LogEntry extends Array { + /** + * Entry's associated log level + */ + [0]: LogLevel; + /** + * Logger ID + */ + [1]: string; + /** + * Timestamp + */ + [2]: number; + /** + * Log entry body (any number/type of arguments given) + */ + [id: number]: any; +} diff --git a/packages/logger/src/console.ts b/packages/logger/src/console.ts index 0e499c334f..1f444506aa 100644 --- a/packages/logger/src/console.ts +++ b/packages/logger/src/console.ts @@ -1,38 +1,11 @@ -import { ILogger, LogLevel } from "./api.js"; +import { ALogger } from "./alogger.js"; +import { LogLevel } from "./api.js"; /** * {@link ILogger} implementation writing messages via `console.log`. */ -export class ConsoleLogger implements ILogger { - id: string; - level: LogLevel; - - constructor(id: string, level = LogLevel.FINE) { - this.id = id; - this.level = level; - } - - fine(...args: any[]): void { - this.level <= LogLevel.FINE && this.log("FINE", args); - } - - debug(...args: any[]): void { - this.level <= LogLevel.DEBUG && this.log("DEBUG", args); - } - - info(...args: any[]): void { - this.level <= LogLevel.INFO && this.log("INFO", args); - } - - warn(...args: any[]): void { - this.level <= LogLevel.WARN && this.log("WARN", args); - } - - severe(...args: any[]): void { - this.level <= LogLevel.SEVERE && this.log("SEVERE", args); - } - - protected log(level: string, args: any[]) { - console.log(`[${level}] ${this.id}:`, ...args); +export class ConsoleLogger extends ALogger { + protected log(level: LogLevel, args: any[]) { + console.log(`[${LogLevel[level]}] ${this.id}:`, ...args); } } diff --git a/packages/logger/src/index.ts b/packages/logger/src/index.ts index 1bea04a267..c6d629d8e7 100644 --- a/packages/logger/src/index.ts +++ b/packages/logger/src/index.ts @@ -1,3 +1,4 @@ export * from "./api.js"; export * from "./console.js"; +export * from "./memory.js"; export * from "./null.js"; diff --git a/packages/logger/src/memory.ts b/packages/logger/src/memory.ts new file mode 100644 index 0000000000..b668b19512 --- /dev/null +++ b/packages/logger/src/memory.ts @@ -0,0 +1,19 @@ +import { ALogger } from "./alogger.js"; +import type { LogEntry, LogLevel, LogLevelName } from "./api.js"; + +export class MemoryLogger extends ALogger { + journal: LogEntry[] = []; + + constructor( + id: string, + level?: LogLevel | LogLevelName, + public limit = 1e3 + ) { + super(id, level); + } + + protected log(level: LogLevel, args: any[]) { + if (this.journal.length >= this.limit) this.journal.shift(); + this.journal.push([level, this.id, Date.now(), ...args]); + } +} diff --git a/packages/logger/test/index.ts b/packages/logger/test/index.ts index ab32dffeb6..398934c62d 100644 --- a/packages/logger/test/index.ts +++ b/packages/logger/test/index.ts @@ -1,5 +1,32 @@ -import { group } from "@thi.ng/testament"; -// import * as assert from "assert"; -// import { } from "../src/index.js" +import * as assert from "assert"; +import { LogEntry, LogLevel, MemoryLogger } from "../src/index.js"; -group("logger", {}); +const journalWithoutTimestamp = (journal: LogEntry[]) => + journal.map((x) => { + const y = x.slice(); + y.splice(2, 1); // remove timestamp + return y; + }); + +const logger = new MemoryLogger("test", LogLevel.DEBUG, 3); +logger.fine(1, 2, 3); +logger.debug(1, 2, 3); +logger.info([1, 2, 3]); +assert.strictEqual(logger.journal.length, 2); +logger.warn("abc"); +assert.strictEqual(logger.journal.length, 3); + +assert.deepEqual(journalWithoutTimestamp(logger.journal), [ + [LogLevel.DEBUG, "test", 1, 2, 3], + [LogLevel.INFO, "test", [1, 2, 3]], + [LogLevel.WARN, "test", "abc"], +]); + +logger.warn("def"); +assert.strictEqual(logger.journal.length, 3); + +assert.deepEqual(journalWithoutTimestamp(logger.journal), [ + [LogLevel.INFO, "test", [1, 2, 3]], + [LogLevel.WARN, "test", "abc"], + [LogLevel.WARN, "test", "def"], +]);