diff options
Diffstat (limited to 'std/log')
-rw-r--r-- | std/log/README.md | 62 | ||||
-rw-r--r-- | std/log/logger.ts | 92 | ||||
-rw-r--r-- | std/log/logger_test.ts | 142 | ||||
-rw-r--r-- | std/log/mod.ts | 89 | ||||
-rw-r--r-- | std/log/mod_test.ts | 28 |
5 files changed, 375 insertions, 38 deletions
diff --git a/std/log/README.md b/std/log/README.md index 3989d1fb7..87055015c 100644 --- a/std/log/README.md +++ b/std/log/README.md @@ -7,11 +7,11 @@ import * as log from "https://deno.land/std/log/mod.ts"; // Simple default logger out of the box. You can customize it // by overriding logger and handler named "default", or providing -// additional logger configurations +// additional logger configurations. You can log any data type. log.debug("Hello world"); -log.info("Hello world"); -log.warning("Hello world"); -log.error("Hello world"); +log.info(123456); +log.warning(true); +log.error({ foo: "bar", fizz: "bazz" }); log.critical("500 Internal server error"); // custom configuration with 2 loggers (the default and `tasks` loggers) @@ -45,12 +45,12 @@ let logger; // get default logger logger = log.getLogger(); logger.debug("fizz"); // logs to `console`, because `file` handler requires "WARNING" level -logger.warning("buzz"); // logs to both `console` and `file` handlers +logger.warning(41256); // logs to both `console` and `file` handlers // get custom logger logger = log.getLogger("tasks"); logger.debug("fizz"); // won't get output because this logger has "ERROR" level -logger.error("buzz"); // log to `console` +logger.error({ productType: "book", value: "126.11" }); // log to `console` // if you try to use a logger that hasn't been configured // you're good to go, it gets created automatically with level set to 0 @@ -239,3 +239,53 @@ During setup async hooks `setup` and `destroy` are called, you can use them to open and close file/HTTP connection or any other action you might need. For examples check source code of `FileHandler` and `TestHandler`. + +### Inline Logging + +Log functions return the data passed in the `msg` parameter. Data is returned +regardless if the logger actually logs it. + +```ts +const stringData: string = logger.debug("hello world"); +const booleanData: boolean = logger.debug(true, 1, "abc"); +const fn = (): number => { + return 123; +}; +const resolvedFunctionData: number = logger.debug(fn()); +console.log(stringData); // 'hello world' +console.log(booleanData); // true +console.log(resolvedFunctionData); // 123 +``` + +### Lazy Log Evaluation + +Some log statements are expensive to compute. In these cases, you can use lazy +log evaluation to prevent the computation taking place if the logger won't log +the message. + +```ts +// `expensiveFn(5)` is only evaluated if this logger is configured for debug logging +logger.debug(() => `this is expensive: ${expensiveFn(5)}`); +``` + +NOTE: When using lazy log evaluation, `undefined` will be returned if the +resolver function is not called because the logger won't log it. E.g. + +```ts +await log.setup({ + handlers: { + console: new log.handlers.ConsoleHandler("DEBUG"), + }, + + loggers: { + tasks: { + level: "ERROR", + handlers: ["console"], + }, + }, +}); + +// not logged, as debug < error +const data: string | undefined = logger.debug(() => someExpenseFn(5, true)); +console.log(data); // undefined +``` diff --git a/std/log/logger.ts b/std/log/logger.ts index 6a12325e8..78599da91 100644 --- a/std/log/logger.ts +++ b/std/log/logger.ts @@ -42,33 +42,99 @@ export class Logger { this.handlers = handlers || []; } - _log(level: number, msg: string, ...args: unknown[]): void { - if (this.level > level) return; + /** If the level of the logger is greater than the level to log, then nothing + * is logged, otherwise a log record is passed to each log handler. `msg` data + * passed in is returned. If a function is passed in, it is only evaluated + * if the msg will be logged and the return value will be the result of the + * function, not the function itself, unless the function isn't called, in which + * case undefined is returned. All types are coerced to strings for logging. + */ + _log<T>( + level: number, + msg: (T extends Function ? never : T) | (() => T), + ...args: unknown[] + ): T | undefined { + if (this.level > level) { + return msg instanceof Function ? undefined : msg; + } - const record: LogRecord = new LogRecord(msg, args, level); + let fnResult: T | undefined; + let logMessage: string; + if (msg instanceof Function) { + fnResult = msg(); + logMessage = this.asString(fnResult); + } else { + logMessage = this.asString(msg); + } + const record: LogRecord = new LogRecord(logMessage, args, level); this.handlers.forEach((handler): void => { handler.handle(record); }); + + return msg instanceof Function ? fnResult : msg; + } + + asString(data: unknown): string { + if (typeof data === "string") { + return data; + } else if ( + data === null || + typeof data === "number" || + typeof data === "bigint" || + typeof data === "boolean" || + typeof data === "undefined" || + typeof data === "symbol" + ) { + return String(data); + } else if (typeof data === "object") { + return JSON.stringify(data); + } + return "undefined"; } - debug(msg: string, ...args: unknown[]): void { - this._log(LogLevels.DEBUG, msg, ...args); + debug<T>(msg: () => T, ...args: unknown[]): T | undefined; + debug<T>(msg: T extends Function ? never : T, ...args: unknown[]): T; + debug<T>( + msg: (T extends Function ? never : T) | (() => T), + ...args: unknown[] + ): T | undefined { + return this._log(LogLevels.DEBUG, msg, ...args); } - info(msg: string, ...args: unknown[]): void { - this._log(LogLevels.INFO, msg, ...args); + info<T>(msg: () => T, ...args: unknown[]): T | undefined; + info<T>(msg: T extends Function ? never : T, ...args: unknown[]): T; + info<T>( + msg: (T extends Function ? never : T) | (() => T), + ...args: unknown[] + ): T | undefined { + return this._log(LogLevels.INFO, msg, ...args); } - warning(msg: string, ...args: unknown[]): void { - this._log(LogLevels.WARNING, msg, ...args); + warning<T>(msg: () => T, ...args: unknown[]): T | undefined; + warning<T>(msg: T extends Function ? never : T, ...args: unknown[]): T; + warning<T>( + msg: (T extends Function ? never : T) | (() => T), + ...args: unknown[] + ): T | undefined { + return this._log(LogLevels.WARNING, msg, ...args); } - error(msg: string, ...args: unknown[]): void { - this._log(LogLevels.ERROR, msg, ...args); + error<T>(msg: () => T, ...args: unknown[]): T | undefined; + error<T>(msg: T extends Function ? never : T, ...args: unknown[]): T; + error<T>( + msg: (T extends Function ? never : T) | (() => T), + ...args: unknown[] + ): T | undefined { + return this._log(LogLevels.ERROR, msg, ...args); } - critical(msg: string, ...args: unknown[]): void { - this._log(LogLevels.CRITICAL, msg, ...args); + critical<T>(msg: () => T, ...args: unknown[]): T | undefined; + critical<T>(msg: T extends Function ? never : T, ...args: unknown[]): T; + critical<T>( + msg: (T extends Function ? never : T) | (() => T), + ...args: unknown[] + ): T | undefined { + return this._log(LogLevels.CRITICAL, msg, ...args); } } diff --git a/std/log/logger_test.ts b/std/log/logger_test.ts index 44b0edd06..b2e3cdab1 100644 --- a/std/log/logger_test.ts +++ b/std/log/logger_test.ts @@ -1,6 +1,6 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. const { test } = Deno; -import { assertEquals } from "../testing/asserts.ts"; +import { assertEquals, assert } from "../testing/asserts.ts"; import { LogRecord, Logger } from "./logger.ts"; import { LogLevels, LevelName } from "./levels.ts"; import { BaseHandler } from "./handlers.ts"; @@ -36,7 +36,7 @@ test("customHandler", function (): void { const handler = new TestHandler("DEBUG"); const logger = new Logger("DEBUG", [handler]); - logger.debug("foo", 1, 2); + const inlineData: string = logger.debug("foo", 1, 2); const record = handler.records[0]; assertEquals(record.msg, "foo"); @@ -45,17 +45,23 @@ test("customHandler", function (): void { assertEquals(record.levelName, "DEBUG"); assertEquals(handler.messages, ["DEBUG foo"]); + assertEquals(inlineData!, "foo"); }); test("logFunctions", function (): void { const doLog = (level: LevelName): TestHandler => { const handler = new TestHandler(level); const logger = new Logger(level, [handler]); - logger.debug("foo"); - logger.info("bar"); - logger.warning("baz"); - logger.error("boo"); - logger.critical("doo"); + const debugData = logger.debug("foo"); + const infoData = logger.info("bar"); + const warningData = logger.warning("baz"); + const errorData = logger.error("boo"); + const criticalData = logger.critical("doo"); + assertEquals(debugData, "foo"); + assertEquals(infoData, "bar"); + assertEquals(warningData, "baz"); + assertEquals(errorData, "boo"); + assertEquals(criticalData, "doo"); return handler; }; @@ -91,3 +97,125 @@ test("logFunctions", function (): void { assertEquals(handler.messages, ["CRITICAL doo"]); }); + +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]); + let called = false; + + const expensiveFunction = (): string => { + called = true; + return "expensive function result"; + }; + + const inlineData: string | undefined = logger.debug(expensiveFunction, 1, 2); + assert(!called); + assertEquals(inlineData, undefined); +}); + +test("String resolver fn resolves as expected", function (): void { + const handler = new TestHandler("ERROR"); + const logger = new Logger("ERROR", [handler]); + const expensiveFunction = (x: number): string => { + return "expensive function result " + x; + }; + + const firstInlineData = logger.error(() => expensiveFunction(5)); + const secondInlineData = logger.error(() => expensiveFunction(12), 1, "abc"); + assertEquals(firstInlineData, "expensive function result 5"); + assertEquals(secondInlineData, "expensive function result 12"); +}); + +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 sym = Symbol(); + const syma = Symbol("a"); + const fn = (): string => { + return "abc"; + }; + + // string + const data1: string = logger.debug("abc"); + assertEquals(data1, "abc"); + const data2: string = logger.debug("def", 1); + assertEquals(data2, "def"); + assertEquals(handler.messages[0], "DEBUG abc"); + assertEquals(handler.messages[1], "DEBUG def"); + + // null + const data3: null = logger.info(null); + assertEquals(data3, null); + const data4: null = logger.info(null, 1); + assertEquals(data4, null); + assertEquals(handler.messages[2], "INFO null"); + assertEquals(handler.messages[3], "INFO null"); + + // number + const data5: number = logger.warning(3); + assertEquals(data5, 3); + const data6: number = logger.warning(3, 1); + assertEquals(data6, 3); + assertEquals(handler.messages[4], "WARNING 3"); + assertEquals(handler.messages[5], "WARNING 3"); + + // bigint + const data7: bigint = logger.error(5n); + assertEquals(data7, 5n); + const data8: bigint = logger.error(5n, 1); + assertEquals(data8, 5n); + assertEquals(handler.messages[6], "ERROR 5"); + assertEquals(handler.messages[7], "ERROR 5"); + + // boolean + const data9: boolean = logger.critical(true); + assertEquals(data9, true); + const data10: boolean = logger.critical(false, 1); + assertEquals(data10, false); + assertEquals(handler.messages[8], "CRITICAL true"); + assertEquals(handler.messages[9], "CRITICAL false"); + + // undefined + const data11: undefined = logger.debug(undefined); + assertEquals(data11, undefined); + const data12: undefined = logger.debug(undefined, 1); + assertEquals(data12, undefined); + assertEquals(handler.messages[10], "DEBUG undefined"); + assertEquals(handler.messages[11], "DEBUG undefined"); + + // symbol + const data13: symbol = logger.info(sym); + assertEquals(data13, sym); + const data14: symbol = logger.info(syma, 1); + assertEquals(data14, syma); + assertEquals(handler.messages[12], "INFO Symbol()"); + assertEquals(handler.messages[13], "INFO Symbol(a)"); + + // function + const data15: string | undefined = logger.warning(fn); + assertEquals(data15, "abc"); + const data16: string | undefined = logger.warning(fn, 1); + assertEquals(data16, "abc"); + assertEquals(handler.messages[14], "WARNING abc"); + assertEquals(handler.messages[15], "WARNING abc"); + + // object + const data17: { payload: string; other: number } = logger.error({ + payload: "data", + other: 123, + }); + assertEquals(data17, { + payload: "data", + other: 123, + }); + const data18: { payload: string; other: number } = logger.error( + { payload: "data", other: 123 }, + 1 + ); + assertEquals(data18, { + payload: "data", + other: 123, + }); + assertEquals(handler.messages[16], 'ERROR {"payload":"data","other":123}'); + assertEquals(handler.messages[17], 'ERROR {"payload":"data","other":123}'); +}); diff --git a/std/log/mod.ts b/std/log/mod.ts index e596c81ba..f59a84449 100644 --- a/std/log/mod.ts +++ b/std/log/mod.ts @@ -72,16 +72,85 @@ export function getLogger(name?: string): Logger { return result; } -export const debug = (msg: string, ...args: unknown[]): void => - getLogger("default").debug(msg, ...args); -export const info = (msg: string, ...args: unknown[]): void => - getLogger("default").info(msg, ...args); -export const warning = (msg: string, ...args: unknown[]): void => - getLogger("default").warning(msg, ...args); -export const error = (msg: string, ...args: unknown[]): void => - getLogger("default").error(msg, ...args); -export const critical = (msg: string, ...args: unknown[]): void => - getLogger("default").critical(msg, ...args); +export function debug<T>(msg: () => T, ...args: unknown[]): T | undefined; +export function debug<T>( + msg: T extends Function ? never : T, + ...args: unknown[] +): T; +export function debug<T>( + msg: (T extends Function ? never : T) | (() => T), + ...args: unknown[] +): T | undefined { + // Assist TS compiler with pass-through generic type + if (msg instanceof Function) { + return getLogger("default").debug(msg, ...args); + } + return getLogger("default").debug(msg, ...args); +} + +export function info<T>(msg: () => T, ...args: unknown[]): T | undefined; +export function info<T>( + msg: T extends Function ? never : T, + ...args: unknown[] +): T; +export function info<T>( + msg: (T extends Function ? never : T) | (() => T), + ...args: unknown[] +): T | undefined { + // Assist TS compiler with pass-through generic type + if (msg instanceof Function) { + return getLogger("default").info(msg, ...args); + } + return getLogger("default").info(msg, ...args); +} + +export function warning<T>(msg: () => T, ...args: unknown[]): T | undefined; +export function warning<T>( + msg: T extends Function ? never : T, + ...args: unknown[] +): T; +export function warning<T>( + msg: (T extends Function ? never : T) | (() => T), + ...args: unknown[] +): T | undefined { + // Assist TS compiler with pass-through generic type + if (msg instanceof Function) { + return getLogger("default").warning(msg, ...args); + } + return getLogger("default").warning(msg, ...args); +} + +export function error<T>(msg: () => T, ...args: unknown[]): T | undefined; +export function error<T>( + msg: T extends Function ? never : T, + ...args: unknown[] +): T; +export function error<T>( + msg: (T extends Function ? never : T) | (() => T), + ...args: unknown[] +): T | undefined { + // Assist TS compiler with pass-through generic type + if (msg instanceof Function) { + return getLogger("default").error(msg, ...args); + } + return getLogger("default").error(msg, ...args); +} + +export function critical<T>(msg: () => T, ...args: unknown[]): T | undefined; +export function critical<T>( + msg: T extends Function ? never : T, + ...args: unknown[] +): T; +export function critical<T>( + msg: (T extends Function ? never : T) | (() => T), + ...args: unknown[] +): T | undefined { + // Assist TS compiler with pass-through generic type + if (msg instanceof Function) { + return getLogger("default").critical(msg, ...args); + } + return getLogger("default").critical(msg, ...args); +} export async function setup(config: LogConfig): Promise<void> { state.config = { diff --git a/std/log/mod_test.ts b/std/log/mod_test.ts index 30576011b..21b944fb5 100644 --- a/std/log/mod_test.ts +++ b/std/log/mod_test.ts @@ -1,8 +1,8 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. const { test } = Deno; +import { assert, assertEquals } from "../testing/asserts.ts"; +import { getLogger, debug, info, warning, error, critical } from "./mod.ts"; import { Logger } from "./logger.ts"; -import { assert } from "../testing/asserts.ts"; -import { getLogger } from "./mod.ts"; let logger: Logger | null = null; try { @@ -16,3 +16,27 @@ try { test("logger is initialized", function (): void { assert(logger instanceof Logger); }); + +test("default loggers work as expected", function (): void { + const sym = Symbol("a"); + const debugData: string = debug("foo"); + const debugResolver: string | undefined = debug(() => "foo"); + const infoData: number = info(456, 1, 2, 3); + const infoResolver: boolean | undefined = info(() => true); + const warningData: symbol = warning(sym); + const warningResolver: null | undefined = warning(() => null); + const errorData: undefined = error(undefined, 1, 2, 3); + const errorResolver: bigint | undefined = error(() => 5n); + const criticalData: string = critical("foo"); + const criticalResolver: string | undefined = critical(() => "bar"); + assertEquals(debugData, "foo"); + assertEquals(debugResolver, undefined); + assertEquals(infoData, 456); + assertEquals(infoResolver, true); + assertEquals(warningData, sym); + assertEquals(warningResolver, null); + assertEquals(errorData, undefined); + assertEquals(errorResolver, 5n); + assertEquals(criticalData, "foo"); + assertEquals(criticalResolver, "bar"); +}); |