diff options
-rw-r--r-- | std/log/README.md | 18 | ||||
-rw-r--r-- | std/log/handlers_test.ts | 133 | ||||
-rw-r--r-- | std/log/logger.ts | 40 | ||||
-rw-r--r-- | std/log/logger_test.ts | 31 | ||||
-rw-r--r-- | std/log/mod.ts | 4 | ||||
-rw-r--r-- | std/log/mod_test.ts | 52 |
6 files changed, 243 insertions, 35 deletions
diff --git a/std/log/README.md b/std/log/README.md index 87055015c..b5e99525a 100644 --- a/std/log/README.md +++ b/std/log/README.md @@ -79,6 +79,7 @@ class LogRecord { readonly datetime: Date; readonly level: number; readonly levelName: string; + readonly loggerName: string; } ``` @@ -208,13 +209,21 @@ await log.setup({ return msg; } }), + + anotherFmt: new log.handlers.ConsoleHandler("DEBUG", { + formatter: "[{loggerName}] - {levelName} {msg}" + }), }, loggers: { default: { - level: "DEBUG", - handlers: ["stringFmt", "functionFmt"], + level: "DEBUG", + handlers: ["stringFmt", "functionFmt"], }, + dataLogger: { + level: "INFO", + handlers: ["anotherFmt"], + } } }) @@ -223,6 +232,11 @@ log.debug("Hello, world!", 1, "two", [3, 4, 5]); // results in: [DEBUG] Hello, world! // output from "stringFmt" handler 10 Hello, world!, arg0: 1, arg1: two, arg3: [3, 4, 5] // output from "functionFmt" formatter + +// calling +log.getLogger("dataLogger").error("oh no!"); +// results in: +[dataLogger] - ERROR oh no! // output from anotherFmt handler ``` #### Custom handlers diff --git a/std/log/handlers_test.ts b/std/log/handlers_test.ts index f7714dae3..7ff4c4c90 100644 --- a/std/log/handlers_test.ts +++ b/std/log/handlers_test.ts @@ -62,7 +62,12 @@ Deno.test("simpleHandler", function (): void { for (const levelName of LogLevelNames) { const level = getLevelByName(levelName as LevelName); handler.handle( - new LogRecord(`${levelName.toLowerCase()}-test`, [], level) + new LogRecord({ + msg: `${levelName.toLowerCase()}-test`, + args: [], + level: level, + loggerName: "default", + }) ); } @@ -77,7 +82,14 @@ Deno.test("testFormatterAsString", function (): void { formatter: "test {levelName} {msg}", }); - handler.handle(new LogRecord("Hello, world!", [], LogLevels.DEBUG)); + handler.handle( + new LogRecord({ + msg: "Hello, world!", + args: [], + level: LogLevels.DEBUG, + loggerName: "default", + }) + ); assertEquals(handler.messages, ["test DEBUG Hello, world!"]); }); @@ -88,7 +100,14 @@ Deno.test("testFormatterAsFunction", function (): void { `fn formatter ${logRecord.levelName} ${logRecord.msg}`, }); - handler.handle(new LogRecord("Hello, world!", [], LogLevels.ERROR)); + handler.handle( + new LogRecord({ + msg: "Hello, world!", + args: [], + level: LogLevels.ERROR, + loggerName: "default", + }) + ); assertEquals(handler.messages, ["fn formatter ERROR Hello, world!"]); }); @@ -102,12 +121,26 @@ Deno.test({ }); await fileHandler.setup(); - fileHandler.handle(new LogRecord("Hello World", [], LogLevels.WARNING)); + fileHandler.handle( + new LogRecord({ + msg: "Hello World", + args: [], + level: LogLevels.WARNING, + loggerName: "default", + }) + ); await fileHandler.destroy(); const firstFileSize = (await Deno.stat(LOG_FILE)).size; await fileHandler.setup(); - fileHandler.handle(new LogRecord("Hello World", [], LogLevels.WARNING)); + fileHandler.handle( + new LogRecord({ + msg: "Hello World", + args: [], + level: LogLevels.WARNING, + loggerName: "default", + }) + ); await fileHandler.destroy(); const secondFileSize = (await Deno.stat(LOG_FILE)).size; @@ -210,13 +243,34 @@ Deno.test({ }); await fileHandler.setup(); - fileHandler.handle(new LogRecord("AAA", [], LogLevels.ERROR)); // 'ERROR AAA\n' = 10 bytes + fileHandler.handle( + new LogRecord({ + msg: "AAA", + args: [], + level: LogLevels.ERROR, + loggerName: "default", + }) + ); // 'ERROR AAA\n' = 10 bytes fileHandler.flush(); assertEquals((await Deno.stat(LOG_FILE)).size, 10); - fileHandler.handle(new LogRecord("AAA", [], LogLevels.ERROR)); + fileHandler.handle( + new LogRecord({ + msg: "AAA", + args: [], + level: LogLevels.ERROR, + loggerName: "default", + }) + ); fileHandler.flush(); assertEquals((await Deno.stat(LOG_FILE)).size, 20); - fileHandler.handle(new LogRecord("AAA", [], LogLevels.ERROR)); + fileHandler.handle( + new LogRecord({ + msg: "AAA", + args: [], + level: LogLevels.ERROR, + loggerName: "default", + }) + ); fileHandler.flush(); // Rollover occurred. Log file now has 1 record, rollover file has the original 2 assertEquals((await Deno.stat(LOG_FILE)).size, 10); @@ -239,9 +293,30 @@ Deno.test({ }); await fileHandler.setup(); - fileHandler.handle(new LogRecord("AAA", [], LogLevels.ERROR)); // 'ERROR AAA\n' = 10 bytes - fileHandler.handle(new LogRecord("AAA", [], LogLevels.ERROR)); - fileHandler.handle(new LogRecord("AAA", [], LogLevels.ERROR)); + fileHandler.handle( + new LogRecord({ + msg: "AAA", + args: [], + level: LogLevels.ERROR, + loggerName: "default", + }) + ); // 'ERROR AAA\n' = 10 bytes + fileHandler.handle( + new LogRecord({ + msg: "AAA", + args: [], + level: LogLevels.ERROR, + loggerName: "default", + }) + ); + fileHandler.handle( + new LogRecord({ + msg: "AAA", + args: [], + level: LogLevels.ERROR, + loggerName: "default", + }) + ); await fileHandler.destroy(); @@ -277,7 +352,14 @@ Deno.test({ mode: "a", }); await fileHandler.setup(); - fileHandler.handle(new LogRecord("AAA", [], LogLevels.ERROR)); // 'ERROR AAA\n' = 10 bytes + fileHandler.handle( + new LogRecord({ + msg: "AAA", + args: [], + level: LogLevels.ERROR, + loggerName: "default", + }) + ); // 'ERROR AAA\n' = 10 bytes await fileHandler.destroy(); const decoder = new TextDecoder(); @@ -349,7 +431,14 @@ Deno.test({ mode: "w", }); await fileHandler.setup(); - fileHandler.handle(new LogRecord("AAA", [], LogLevels.ERROR)); // 'ERROR AAA\n' = 10 bytes + fileHandler.handle( + new LogRecord({ + msg: "AAA", + args: [], + level: LogLevels.ERROR, + loggerName: "default", + }) + ); // 'ERROR AAA\n' = 10 bytes assertEquals((await Deno.stat(LOG_FILE)).size, 0); dispatchEvent(new Event("unload")); @@ -402,13 +491,27 @@ Deno.test({ }); await fileHandler.setup(); - fileHandler.handle(new LogRecord("AAA", [], LogLevels.ERROR)); + fileHandler.handle( + new LogRecord({ + msg: "AAA", + args: [], + level: LogLevels.ERROR, + loggerName: "default", + }) + ); // ERROR won't trigger immediate flush const fileSize = (await Deno.stat(LOG_FILE)).size; assertEquals(fileSize, 0); - fileHandler.handle(new LogRecord("AAA", [], LogLevels.CRITICAL)); + fileHandler.handle( + new LogRecord({ + msg: "AAA", + args: [], + level: LogLevels.CRITICAL, + loggerName: "default", + }) + ); // CRITICAL will trigger immediate flush const fileSize2 = (await Deno.stat(LOG_FILE)).size; diff --git a/std/log/logger.ts b/std/log/logger.ts index 78599da91..004088d9a 100644 --- a/std/log/logger.ts +++ b/std/log/logger.ts @@ -7,19 +7,28 @@ import { } from "./levels.ts"; import { BaseHandler } from "./handlers.ts"; +export interface LogRecordOptions { + msg: string; + args: unknown[]; + level: number; + loggerName: string; +} + export class LogRecord { readonly msg: string; #args: unknown[]; #datetime: Date; readonly level: number; readonly levelName: string; + readonly loggerName: string; - constructor(msg: string, args: unknown[], level: number) { - this.msg = msg; - this.#args = [...args]; - this.level = level; + constructor(options: LogRecordOptions) { + this.msg = options.msg; + this.#args = [...options.args]; + this.level = options.level; + this.loggerName = options.loggerName; this.#datetime = new Date(); - this.levelName = getLevelName(level); + this.levelName = getLevelName(options.level); } get args(): unknown[] { return [...this.#args]; @@ -29,17 +38,27 @@ export class LogRecord { } } +export interface LoggerOptions { + handlers?: BaseHandler[]; +} + export class Logger { level: number; levelName: LevelName; // eslint-disable-next-line @typescript-eslint/no-explicit-any handlers: any[]; + loggerName: string; - constructor(levelName: LevelName, handlers?: BaseHandler[]) { + constructor( + loggerName: string, + levelName: LevelName, + options: LoggerOptions = {} + ) { + this.loggerName = loggerName; this.level = getLevelByName(levelName); this.levelName = levelName; - this.handlers = handlers || []; + this.handlers = options.handlers || []; } /** If the level of the logger is greater than the level to log, then nothing @@ -66,7 +85,12 @@ export class Logger { } else { logMessage = this.asString(msg); } - const record: LogRecord = new LogRecord(logMessage, args, level); + const record: LogRecord = new LogRecord({ + msg: logMessage, + args: args, + level: level, + loggerName: this.loggerName, + }); this.handlers.forEach((handler): void => { handler.handle(record); diff --git a/std/log/logger_test.ts b/std/log/logger_test.ts index 2425c15e6..f7b438cba 100644 --- a/std/log/logger_test.ts +++ b/std/log/logger_test.ts @@ -18,22 +18,39 @@ class TestHandler extends BaseHandler { } } +Deno.test({ + name: "Logger names can be output in logs", + fn() { + const handlerNoName = new TestHandler("DEBUG"); + const handlerWithLoggerName = new TestHandler("DEBUG", { + formatter: "[{loggerName}] {levelName} {msg}", + }); + + const logger = new Logger("config", "DEBUG", { + handlers: [handlerNoName, handlerWithLoggerName], + }); + logger.debug("hello"); + assertEquals(handlerNoName.messages[0], "DEBUG hello"); + assertEquals(handlerWithLoggerName.messages[0], "[config] DEBUG hello"); + }, +}); + Deno.test("simpleLogger", function (): void { const handler = new TestHandler("DEBUG"); - let logger = new Logger("DEBUG"); + let logger = new Logger("default", "DEBUG"); assertEquals(logger.level, LogLevels.DEBUG); assertEquals(logger.levelName, "DEBUG"); assertEquals(logger.handlers, []); - logger = new Logger("DEBUG", [handler]); + logger = new Logger("default", "DEBUG", { handlers: [handler] }); assertEquals(logger.handlers, [handler]); }); Deno.test("customHandler", function (): void { const handler = new TestHandler("DEBUG"); - const logger = new Logger("DEBUG", [handler]); + const logger = new Logger("default", "DEBUG", { handlers: [handler] }); const inlineData: string = logger.debug("foo", 1, 2); @@ -50,7 +67,7 @@ Deno.test("customHandler", function (): void { Deno.test("logFunctions", function (): void { const doLog = (level: LevelName): TestHandler => { const handler = new TestHandler(level); - const logger = new Logger(level, [handler]); + const logger = new Logger("default", level, { handlers: [handler] }); const debugData = logger.debug("foo"); const infoData = logger.info("bar"); const warningData = logger.warning("baz"); @@ -101,7 +118,7 @@ Deno.test( "String resolver fn will not execute if msg will not be logged", function (): void { const handler = new TestHandler("ERROR"); - const logger = new Logger("ERROR", [handler]); + const logger = new Logger("default", "ERROR", { handlers: [handler] }); let called = false; const expensiveFunction = (): string => { @@ -121,7 +138,7 @@ Deno.test( Deno.test("String resolver fn resolves as expected", function (): void { const handler = new TestHandler("ERROR"); - const logger = new Logger("ERROR", [handler]); + const logger = new Logger("default", "ERROR", { handlers: [handler] }); const expensiveFunction = (x: number): string => { return "expensive function result " + x; }; @@ -136,7 +153,7 @@ Deno.test( "All types map correctly to log strings and are returned as is", function (): void { const handler = new TestHandler("DEBUG"); - const logger = new Logger("DEBUG", [handler]); + const logger = new Logger("default", "DEBUG", { handlers: [handler] }); const sym = Symbol(); const syma = Symbol("a"); const fn = (): string => { diff --git a/std/log/mod.ts b/std/log/mod.ts index f59a84449..a1a863d4a 100644 --- a/std/log/mod.ts +++ b/std/log/mod.ts @@ -65,7 +65,7 @@ export function getLogger(name?: string): Logger { } const result = state.loggers.get(name); if (!result) { - const logger = new Logger("NOTSET", []); + const logger = new Logger(name, "NOTSET", { handlers: [] }); state.loggers.set(name, logger); return logger; } @@ -191,7 +191,7 @@ export async function setup(config: LogConfig): Promise<void> { }); const levelName = loggerConfig.level || DEFAULT_LEVEL; - const logger = new Logger(levelName, handlers); + const logger = new Logger(loggerName, levelName, { handlers: handlers }); state.loggers.set(loggerName, logger); } } diff --git a/std/log/mod_test.ts b/std/log/mod_test.ts index 98ac093c8..9de5d76af 100644 --- a/std/log/mod_test.ts +++ b/std/log/mod_test.ts @@ -1,7 +1,24 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. import { assert, assertEquals } from "../testing/asserts.ts"; -import { getLogger, debug, info, warning, error, critical } from "./mod.ts"; +import { + getLogger, + debug, + info, + warning, + error, + critical, + setup, +} from "./mod.ts"; import { Logger } from "./logger.ts"; +import { BaseHandler } from "./handlers.ts"; + +class TestHandler extends BaseHandler { + public messages: string[] = []; + + public log(str: string): void { + this.messages.push(str); + } +} let logger: Logger | null = null; try { @@ -39,3 +56,36 @@ Deno.test("default loggers work as expected", function (): void { assertEquals(criticalData, "foo"); assertEquals(criticalResolver, "bar"); }); + +Deno.test({ + name: "Logging config works as expected with logger names", + async fn() { + const consoleHandler = new TestHandler("DEBUG"); + const anotherConsoleHandler = new TestHandler("DEBUG", { + formatter: "[{loggerName}] {levelName} {msg}", + }); + await setup({ + handlers: { + console: consoleHandler, + anotherConsole: anotherConsoleHandler, + }, + + loggers: { + // configure default logger available via short-hand methods above + default: { + level: "DEBUG", + handlers: ["console"], + }, + + tasks: { + level: "ERROR", + handlers: ["anotherConsole"], + }, + }, + }); + getLogger().debug("hello"); + getLogger("tasks").error("world"); + assertEquals(consoleHandler.messages[0], "DEBUG hello"); + assertEquals(anotherConsoleHandler.messages[0], "[tasks] ERROR world"); + }, +}); |