summaryrefslogtreecommitdiff
path: root/std/log
diff options
context:
space:
mode:
Diffstat (limited to 'std/log')
-rw-r--r--std/log/README.md62
-rw-r--r--std/log/logger.ts92
-rw-r--r--std/log/logger_test.ts142
-rw-r--r--std/log/mod.ts89
-rw-r--r--std/log/mod_test.ts28
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");
+});