From 63bbe7384b2968a5a30fd66bac9a99abe6769c9a Mon Sep 17 00:00:00 2001 From: Asher Gomez Date: Fri, 27 Sep 2024 10:33:50 +1000 Subject: [PATCH 1/3] docs(log): document `BaseHandler` --- _tools/check_docs.ts | 1 + log/base_handler.ts | 308 +++++++++++++++++++++++++++++++++++++++ log/base_handler_test.ts | 37 +++-- log/levels.ts | 2 + log/logger.ts | 163 +++++++++++++++++++++ 5 files changed, 496 insertions(+), 15 deletions(-) diff --git a/_tools/check_docs.ts b/_tools/check_docs.ts index c20e7ac5e592..783b54a81fe4 100644 --- a/_tools/check_docs.ts +++ b/_tools/check_docs.ts @@ -70,6 +70,7 @@ const ENTRY_POINTS = [ "../io/mod.ts", "../json/mod.ts", "../jsonc/mod.ts", + "../log/base_handler.ts", "../log/warn.ts", "../media_types/mod.ts", "../msgpack/mod.ts", diff --git a/log/base_handler.ts b/log/base_handler.ts index 8af7c0cd1507..e968a4fc1bd3 100644 --- a/log/base_handler.ts +++ b/log/base_handler.ts @@ -8,19 +8,83 @@ import { } from "./levels.ts"; import type { LogRecord } from "./logger.ts"; +export type { LevelName, LogLevel, LogRecord }; + +/** + * A function type that defines the structure of a formatter function. + * + * @param logRecord The log record that needs to be formatted. + * @returns A string representation of the log record. + */ export type FormatterFunction = (logRecord: LogRecord) => string; const DEFAULT_FORMATTER: FormatterFunction = ({ levelName, msg }) => `${levelName} ${msg}`; +/** Options for {@linkcode BaseHandler}. */ export interface BaseHandlerOptions { + /** A function that formats log records. */ formatter?: FormatterFunction; } +/** + * A base class for all log handlers. + * + * This class is abstract and should not be instantiated directly. Instead, it + * should be extended by other classes that implement the `log` method. + * + * @example Usage + * ```ts + * import { BaseHandler } from "@std/log/base-handler"; + * import { assertInstanceOf } from "@std/assert/instance-of"; + * + * class MyHandler extends BaseHandler { + * log(msg: string) { + * console.log(msg); + * } + * } + * + * const handler = new MyHandler("INFO"); + * assertInstanceOf(handler, BaseHandler); + * ``` + */ export abstract class BaseHandler { #levelName: LevelName; #level: LogLevel; + /** + * The function that formats log records. + * + * @example Usage + * ```ts + * import { BaseHandler } from "@std/log/base-handler"; + * import { LogRecord } from "@std/log/logger"; + * import { LogLevels } from "@std/log/levels"; + * import { assertEquals } from "@std/assert/equals"; + * + * class MyHandler extends BaseHandler { + * log(msg: string) { + * console.log(msg); + * } + * } + * + * const handler = new MyHandler("INFO"); + * const record = new LogRecord({ + * msg: "Hello, world!", + * args: ["foo", "bar"], + * level: LogLevels.INFO, + * loggerName: "example", + * }); + * const formatted = handler.formatter(record); + * assertEquals(formatted, "INFO Hello, world!"); + * ``` + */ formatter: FormatterFunction; + /** + * Constructs a new instance. + * + * @param levelName The name of the log level to handle. + * @param options Options for the handler. + */ constructor( levelName: LevelName, options?: BaseHandlerOptions, @@ -31,23 +95,138 @@ export abstract class BaseHandler { this.formatter = formatter; } + /** + * Getter for the log level that this handler will handle. + * + * @example Usage + * ```ts + * import { BaseHandler } from "@std/log/base-handler"; + * import { LogLevels } from "@std/log/levels"; + * import { assertEquals } from "@std/assert/equals"; + * + * class MyHandler extends BaseHandler { + * log(msg: string) { + * console.log(msg); + * } + * } + * + * const handler = new MyHandler("INFO"); + * assertEquals(handler.level, LogLevels.INFO); + * ``` + * + * @returns The log level to handle. + */ get level(): LogLevel { return this.#level; } + /** + * Setter for the log level that this handler will handle. + * + * @example Usage + * ```ts + * import { BaseHandler } from "@std/log/base-handler"; + * import { LogLevels } from "@std/log/levels"; + * import { assertEquals } from "@std/assert/equals"; + * + * class MyHandler extends BaseHandler { + * log(msg: string) { + * console.log(msg); + * } + * } + * + * const handler = new MyHandler("INFO"); + * handler.level = LogLevels.DEBUG; + * assertEquals(handler.level, LogLevels.DEBUG); + * ``` + * + * @param level The log level to handle. + */ set level(level: LogLevel) { this.#level = level; this.#levelName = getLevelName(level); } + /** + * Getter for the name of the log level that this handler will handle. + * + * @example Usage + * ```ts + * import { BaseHandler } from "@std/log/base-handler"; + * import { assertEquals } from "@std/assert/equals"; + * + * class MyHandler extends BaseHandler { + * log(msg: string) { + * console.log(msg); + * } + * } + * + * const handler = new MyHandler("INFO"); + * assertEquals(handler.levelName, "INFO"); + * ``` + * + * @returns The name of the log level to handle. + */ get levelName(): LevelName { return this.#levelName; } + + /** + * Setter for the name of the log level that this handler will handle. + * + * @param levelName The name of the log level to handle. + * + * @example Usage + * ```ts + * import { BaseHandler } from "@std/log/base-handler"; + * import { assertEquals } from "@std/assert/equals"; + * + * class MyHandler extends BaseHandler { + * log(msg: string) { + * console.log(msg); + * } + * } + * + * const handler = new MyHandler("INFO"); + * handler.levelName = "DEBUG"; + * assertEquals(handler.levelName, "DEBUG"); + * ``` + */ set levelName(levelName: LevelName) { this.#levelName = levelName; this.#level = getLevelByName(levelName); } + /** + * Handles a log record. + * + * @param logRecord The log record to handle. + * + * @example Usage + * ```ts + * import { BaseHandler } from "@std/log/base-handler"; + * import { LogRecord } from "@std/log/logger"; + * import { LogLevels } from "@std/log/levels"; + * import { assertInstanceOf } from "@std/assert/instance-of"; + * + * class MyHandler extends BaseHandler { + * log(msg: string) { + * console.log(msg); + * } + * } + * + * const handler = new MyHandler("INFO"); + * const record = new LogRecord({ + * msg: "Hello, world!", + * args: ["foo", "bar"], + * level: LogLevels.INFO, + * loggerName: "example", + * }); + * handler.handle(record); + * + * assertInstanceOf(handler, BaseHandler); + * ``` + */ handle(logRecord: LogRecord) { if (this.level > logRecord.level) return; @@ -55,14 +234,143 @@ export abstract class BaseHandler { this.log(msg); } + /** + * Formats a log record. + * + * @param logRecord The log record to format. + * @returns A string representation of the log record. + * + * @example Usage + * ```ts + * import { BaseHandler } from "@std/log/base-handler"; + * import { LogRecord } from "@std/log/logger"; + * import { LogLevels } from "@std/log/levels"; + * import { assertEquals } from "@std/assert/equals"; + * + * class MyHandler extends BaseHandler { + * log(msg: string) { + * console.log(msg); + * } + * } + * + * const handler = new MyHandler("INFO"); + * const record = new LogRecord({ + * msg: "Hello, world!", + * args: ["foo", "bar"], + * level: LogLevels.INFO, + * loggerName: "example", + * }); + * const formatted = handler.format(record); + * assertEquals(formatted, "INFO Hello, world!"); + * ``` + */ format(logRecord: LogRecord): string { return this.formatter(logRecord); } + /** + * Logs a message. + * + * This method should be implemented by subclasses to handle the log record. + * + * @param msg The message to log. + * + * @example Usage + * ```ts + * import { BaseHandler } from "@std/log/base-handler"; + * import { assertInstanceOf } from "@std/assert/instance-of"; + * + * class MyHandler extends BaseHandler { + * log(msg: string) { + * console.log(msg); + * } + * } + * + * const handler = new MyHandler("INFO"); + * handler.log("Hello, world!"); // Prints "Hello, world!" + * + * assertInstanceOf(handler, BaseHandler); + * ``` + */ abstract log(msg: string): void; + + /** + * Initializes the handler. + * + * This method is called when the handler is added to a logger. It can be + * used to perform any setup that is required by the handler. + * + * @example Usage + * ```ts + * import { BaseHandler } from "@std/log/base-handler"; + * import { assertInstanceOf } from "@std/assert/instance-of"; + * + * class MyHandler extends BaseHandler { + * log(msg: string) { + * console.log(msg); + * } + * + * override setup() { + * console.log("Handler setup!"); + * } + * } + * + * const handler = new MyHandler("INFO"); + * handler.setup(); // Prints "Handler setup!" + * + * assertInstanceOf(handler, BaseHandler); + * ``` + */ setup() {} + + /** + * Destroys the handler, performing any cleanup that is required. + * + * This method is called when the handler is removed from a logger. It can be + * used to perform any cleanup that is required by the handler. + * + * @example Usage + * ```ts + * import { BaseHandler } from "@std/log/base-handler"; + * import { assertInstanceOf } from "@std/assert/instance-of"; + * + * class MyHandler extends BaseHandler { + * log(msg: string) { + * console.log(msg); + * } + * + * override destroy() { + * console.log("Handler destroyed!"); + * } + * } + * + * const handler = new MyHandler("INFO"); + * handler.destroy(); // Prints "Handler destroyed!" + * assertInstanceOf(handler, BaseHandler); + * ``` + */ destroy() {} + /** + * Automatically disposes of the handler when instantiated with the `using` + * keyword by calling the {@linkcode BaseHandler.destroy} method. + * + * @example Usage + * ```ts + * import { BaseHandler } from "@std/log/base-handler"; + * import { LogRecord } from "@std/log/logger"; + * import { assertInstanceOf } from "@std/assert/instance-of"; + * + * class MyHandler extends BaseHandler { + * log(msg: string) { + * console.log(msg); + * } + * } + * + * using handler = new MyHandler("INFO"); + * assertInstanceOf(handler, BaseHandler); + * ``` + */ [Symbol.dispose]() { this.destroy(); } diff --git a/log/base_handler_test.ts b/log/base_handler_test.ts index 744474209ead..e9f939f3ca59 100644 --- a/log/base_handler_test.ts +++ b/log/base_handler_test.ts @@ -1,12 +1,19 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. import { assertEquals } from "@std/assert"; -import * as log from "./mod.ts"; +import { + getLevelByName, + getLevelName, + type LogLevel, + LogLevelNames, + LogLevels, +} from "./levels.ts"; +import { LogRecord } from "./logger.ts"; import { TestHandler } from "./_test_handler.ts"; Deno.test("BaseHandler handles default setup", function () { - const cases = new Map([ + const cases = new Map([ [ - log.LogLevels.DEBUG, + LogLevels.DEBUG, [ "DEBUG debug-test", "INFO info-test", @@ -16,7 +23,7 @@ Deno.test("BaseHandler handles default setup", function () { ], ], [ - log.LogLevels.INFO, + LogLevels.INFO, [ "INFO info-test", "WARN warn-test", @@ -25,21 +32,21 @@ Deno.test("BaseHandler handles default setup", function () { ], ], [ - log.LogLevels.WARN, + LogLevels.WARN, ["WARN warn-test", "ERROR error-test", "CRITICAL critical-test"], ], - [log.LogLevels.ERROR, ["ERROR error-test", "CRITICAL critical-test"]], - [log.LogLevels.CRITICAL, ["CRITICAL critical-test"]], + [LogLevels.ERROR, ["ERROR error-test", "CRITICAL critical-test"]], + [LogLevels.CRITICAL, ["CRITICAL critical-test"]], ]); for (const [testCase, messages] of cases.entries()) { - const testLevel = log.getLevelName(testCase); + const testLevel = getLevelName(testCase); const handler = new TestHandler(testLevel); - for (const levelName of log.LogLevelNames) { - const level = log.getLevelByName(levelName); + for (const levelName of LogLevelNames) { + const level = getLevelByName(levelName); handler.handle( - new log.LogRecord({ + new LogRecord({ msg: `${levelName.toLowerCase()}-test`, args: [], level: level, @@ -60,10 +67,10 @@ Deno.test("BaseHandler handles formatter with empty msg", function () { }); handler.handle( - new log.LogRecord({ + new LogRecord({ msg: "", args: [], - level: log.LogLevels.DEBUG, + level: LogLevels.DEBUG, loggerName: "default", }), ); @@ -78,10 +85,10 @@ Deno.test("BaseHandler handles formatter", function () { }); handler.handle( - new log.LogRecord({ + new LogRecord({ msg: "Hello, world!", args: [], - level: log.LogLevels.ERROR, + level: LogLevels.ERROR, loggerName: "default", }), ); diff --git a/log/levels.ts b/log/levels.ts index 6afa651318b4..1120df2b2615 100644 --- a/log/levels.ts +++ b/log/levels.ts @@ -4,6 +4,8 @@ /** * Use this to retrieve the numeric log level by it's associated name. * Defaults to INFO. + * + * @internal */ export const LogLevels = { NOTSET: 0, diff --git a/log/logger.ts b/log/logger.ts index 1ba00d8e18c4..a75a86e86135 100644 --- a/log/logger.ts +++ b/log/logger.ts @@ -8,10 +8,17 @@ import type { BaseHandler } from "./base_handler.ts"; // deno-lint-ignore no-explicit-any export type GenericFunction = (...args: any[]) => any; +/** + * Options for {@linkcode LogRecord}. + */ export interface LogRecordOptions { + /** The message to log. */ msg: string; + /** The arguments to log. */ args: unknown[]; + /** The log level of the message. */ level: LogLevel; + /** The name of the logger that created the log record. */ loggerName: string; } @@ -32,15 +39,127 @@ export interface LogConfig { /** * An object that encapsulates provided message and arguments as well some * metadata that can be later used when formatting a message. + * + * @example Usage + * ```ts + * import { LogRecord } from "@std/log/logger"; + * import { LogLevels } from "@std/log/levels"; + * import { assertEquals } from "@std/assert/equals"; + * + * const record = new LogRecord({ + * msg: "Hello, world!", + * args: ["foo", "bar"], + * level: LogLevels.INFO, + * loggerName: "example", + * }); + * + * assertEquals(record.msg, "Hello, world!"); + * assertEquals(record.args, ["foo", "bar"]); + * assertEquals(record.level, LogLevels.INFO); + * assertEquals(record.loggerName, "example"); + * ``` */ export class LogRecord { + /** The message to log. + * + * @example Usage + * ```ts + * import { LogRecord } from "@std/log/logger"; + * import { LogLevels } from "@std/log/levels"; + * import { assertEquals } from "@std/assert/equals"; + * + * const record = new LogRecord({ + * msg: "Hello, world!", + * args: ["foo", "bar"], + * level: LogLevels.INFO, + * loggerName: "example", + * }); + * + * assertEquals(record.msg, "Hello, world!"); + * ``` + */ readonly msg: string; #args: unknown[]; #datetime: Date; + /** + * The numeric log level of the log record. + * + * @example Usage + * ```ts + * import { LogRecord } from "@std/log/logger"; + * import { LogLevels } from "@std/log/levels"; + * import { assertEquals } from "@std/assert/equals"; + * + * const record = new LogRecord({ + * msg: "Hello, world!", + * args: ["foo", "bar"], + * level: LogLevels.INFO, + * loggerName: "example", + * }); + * + * assertEquals(record.level, LogLevels.INFO); + * ``` + */ readonly level: number; + /** + * The name of the log level of the log record. + * + * @example Usage + * ```ts + * import { LogRecord } from "@std/log/logger"; + * import { LogLevels } from "@std/log/levels"; + * import { assertEquals } from "@std/assert/equals"; + * + * const record = new LogRecord({ + * msg: "Hello, world!", + * args: ["foo", "bar"], + * level: LogLevels.INFO, + * loggerName: "example", + * }); + * + * assertEquals(record.loggerName, "example"); + * ``` + */ readonly levelName: string; + /** + * The name of the logger that created the log record. + * + * @example Usage + * ```ts + * import { LogRecord } from "@std/log/logger"; + * import { LogLevels } from "@std/log/levels"; + * import { assertEquals } from "@std/assert/equals"; + * + * const record = new LogRecord({ + * msg: "Hello, world!", + * args: ["foo", "bar"], + * level: LogLevels.INFO, + * loggerName: "example", + * }); + * + * assertEquals(record.loggerName, "example"); + * ``` + */ readonly loggerName: string; + /** + * Constructs a new instance. + * + * @param options The options to create a new log record. + * + * @example Usage + * ```ts + * import { LogRecord } from "@std/log/logger"; + * import { LogLevels } from "@std/log/levels"; + * + * const record = new LogRecord({ + * msg: "Hello, world!", + * args: ["foo", "bar"], + * level: LogLevels.INFO, + * loggerName: "example", + * }); + * ``` + */ constructor(options: LogRecordOptions) { this.msg = options.msg; this.#args = [...options.args]; @@ -49,9 +168,53 @@ export class LogRecord { this.#datetime = new Date(); this.levelName = getLevelName(options.level); } + + /** + * Getter for the arguments to log. + * + * @example Usage + * ```ts + * import { LogRecord } from "@std/log/logger"; + * import { LogLevels } from "@std/log/levels"; + * import { assertEquals } from "@std/assert/equals"; + * + * const record = new LogRecord({ + * msg: "Hello, world!", + * args: ["foo", "bar"], + * level: LogLevels.INFO, + * loggerName: "example", + * }); + * + * assertEquals(record.args, ["foo", "bar"]); + * ``` + * + * @returns A copy of the arguments to log. + */ get args(): unknown[] { return [...this.#args]; } + + /** + * Getter for the date and time the log record was created. + * + * @example Usage + * ```ts + * import { LogRecord } from "@std/log/logger"; + * import { LogLevels } from "@std/log/levels"; + * import { assertEquals } from "@std/assert/equals"; + * + * const record = new LogRecord({ + * msg: "Hello, world!", + * args: ["foo", "bar"], + * level: LogLevels.INFO, + * loggerName: "example", + * }); + * + * assertEquals(record.datetime, new Date()); + * ``` + * + * @returns The date and time the log record was created. + */ get datetime(): Date { return new Date(this.#datetime.getTime()); } From 6e0457bc026331d6abffdb137ecd33cd2c838ad2 Mon Sep 17 00:00:00 2001 From: Asher Gomez Date: Fri, 27 Sep 2024 10:40:16 +1000 Subject: [PATCH 2/3] fix --- log/logger.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/log/logger.ts b/log/logger.ts index a75a86e86135..b502be01186c 100644 --- a/log/logger.ts +++ b/log/logger.ts @@ -201,7 +201,7 @@ export class LogRecord { * ```ts * import { LogRecord } from "@std/log/logger"; * import { LogLevels } from "@std/log/levels"; - * import { assertEquals } from "@std/assert/equals"; + * import { assertAlmostEquals } from "@std/assert/almost-equals"; * * const record = new LogRecord({ * msg: "Hello, world!", @@ -210,7 +210,7 @@ export class LogRecord { * loggerName: "example", * }); * - * assertEquals(record.datetime, new Date()); + * assertAlmostEquals(record.datetime.getTime(), Date.now(), 1e5); * ``` * * @returns The date and time the log record was created. From 45b7e70950facc6216de15c9d3a7a3905deda815 Mon Sep 17 00:00:00 2001 From: Asher Gomez Date: Fri, 27 Sep 2024 10:41:01 +1000 Subject: [PATCH 3/3] fix --- log/logger.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/log/logger.ts b/log/logger.ts index b502be01186c..cc9fd65c4724 100644 --- a/log/logger.ts +++ b/log/logger.ts @@ -210,7 +210,7 @@ export class LogRecord { * loggerName: "example", * }); * - * assertAlmostEquals(record.datetime.getTime(), Date.now(), 1e5); + * assertAlmostEquals(record.datetime.getTime(), Date.now(), 1); * ``` * * @returns The date and time the log record was created.