docs(log): document BaseHandler (#6067)

* docs(log): document `BaseHandler`

* fix

* fix
This commit is contained in:
Asher Gomez 2024-09-27 13:31:58 +10:00 committed by GitHub
parent fb2e30313a
commit e9716720b6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 496 additions and 15 deletions

View File

@ -70,6 +70,7 @@ const ENTRY_POINTS = [
"../io/mod.ts", "../io/mod.ts",
"../json/mod.ts", "../json/mod.ts",
"../jsonc/mod.ts", "../jsonc/mod.ts",
"../log/base_handler.ts",
"../log/warn.ts", "../log/warn.ts",
"../log/critical.ts", "../log/critical.ts",
"../log/debug.ts", "../log/debug.ts",

View File

@ -8,19 +8,83 @@ import {
} from "./levels.ts"; } from "./levels.ts";
import type { LogRecord } from "./logger.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; export type FormatterFunction = (logRecord: LogRecord) => string;
const DEFAULT_FORMATTER: FormatterFunction = ({ levelName, msg }) => const DEFAULT_FORMATTER: FormatterFunction = ({ levelName, msg }) =>
`${levelName} ${msg}`; `${levelName} ${msg}`;
/** Options for {@linkcode BaseHandler}. */
export interface BaseHandlerOptions { export interface BaseHandlerOptions {
/** A function that formats log records. */
formatter?: FormatterFunction; 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 { export abstract class BaseHandler {
#levelName: LevelName; #levelName: LevelName;
#level: LogLevel; #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; formatter: FormatterFunction;
/**
* Constructs a new instance.
*
* @param levelName The name of the log level to handle.
* @param options Options for the handler.
*/
constructor( constructor(
levelName: LevelName, levelName: LevelName,
options?: BaseHandlerOptions, options?: BaseHandlerOptions,
@ -31,23 +95,138 @@ export abstract class BaseHandler {
this.formatter = formatter; 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 { get level(): LogLevel {
return this.#level; 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) { set level(level: LogLevel) {
this.#level = level; this.#level = level;
this.#levelName = getLevelName(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 { get levelName(): LevelName {
return this.#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) { set levelName(levelName: LevelName) {
this.#levelName = levelName; this.#levelName = levelName;
this.#level = getLevelByName(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) { handle(logRecord: LogRecord) {
if (this.level > logRecord.level) return; if (this.level > logRecord.level) return;
@ -55,14 +234,143 @@ export abstract class BaseHandler {
this.log(msg); 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 { format(logRecord: LogRecord): string {
return this.formatter(logRecord); 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; 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() {} 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() {} 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]() { [Symbol.dispose]() {
this.destroy(); this.destroy();
} }

View File

@ -1,12 +1,19 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
import { assertEquals } from "@std/assert"; 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"; import { TestHandler } from "./_test_handler.ts";
Deno.test("BaseHandler handles default setup", function () { Deno.test("BaseHandler handles default setup", function () {
const cases = new Map<log.LogLevel, string[]>([ const cases = new Map<LogLevel, string[]>([
[ [
log.LogLevels.DEBUG, LogLevels.DEBUG,
[ [
"DEBUG debug-test", "DEBUG debug-test",
"INFO info-test", "INFO info-test",
@ -16,7 +23,7 @@ Deno.test("BaseHandler handles default setup", function () {
], ],
], ],
[ [
log.LogLevels.INFO, LogLevels.INFO,
[ [
"INFO info-test", "INFO info-test",
"WARN warn-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"], ["WARN warn-test", "ERROR error-test", "CRITICAL critical-test"],
], ],
[log.LogLevels.ERROR, ["ERROR error-test", "CRITICAL critical-test"]], [LogLevels.ERROR, ["ERROR error-test", "CRITICAL critical-test"]],
[log.LogLevels.CRITICAL, ["CRITICAL critical-test"]], [LogLevels.CRITICAL, ["CRITICAL critical-test"]],
]); ]);
for (const [testCase, messages] of cases.entries()) { for (const [testCase, messages] of cases.entries()) {
const testLevel = log.getLevelName(testCase); const testLevel = getLevelName(testCase);
const handler = new TestHandler(testLevel); const handler = new TestHandler(testLevel);
for (const levelName of log.LogLevelNames) { for (const levelName of LogLevelNames) {
const level = log.getLevelByName(levelName); const level = getLevelByName(levelName);
handler.handle( handler.handle(
new log.LogRecord({ new LogRecord({
msg: `${levelName.toLowerCase()}-test`, msg: `${levelName.toLowerCase()}-test`,
args: [], args: [],
level: level, level: level,
@ -60,10 +67,10 @@ Deno.test("BaseHandler handles formatter with empty msg", function () {
}); });
handler.handle( handler.handle(
new log.LogRecord({ new LogRecord({
msg: "", msg: "",
args: [], args: [],
level: log.LogLevels.DEBUG, level: LogLevels.DEBUG,
loggerName: "default", loggerName: "default",
}), }),
); );
@ -78,10 +85,10 @@ Deno.test("BaseHandler handles formatter", function () {
}); });
handler.handle( handler.handle(
new log.LogRecord({ new LogRecord({
msg: "Hello, world!", msg: "Hello, world!",
args: [], args: [],
level: log.LogLevels.ERROR, level: LogLevels.ERROR,
loggerName: "default", loggerName: "default",
}), }),
); );

View File

@ -4,6 +4,8 @@
/** /**
* Use this to retrieve the numeric log level by it's associated name. * Use this to retrieve the numeric log level by it's associated name.
* Defaults to INFO. * Defaults to INFO.
*
* @internal
*/ */
export const LogLevels = { export const LogLevels = {
NOTSET: 0, NOTSET: 0,

View File

@ -8,10 +8,17 @@ import type { BaseHandler } from "./base_handler.ts";
// deno-lint-ignore no-explicit-any // deno-lint-ignore no-explicit-any
export type GenericFunction = (...args: any[]) => any; export type GenericFunction = (...args: any[]) => any;
/**
* Options for {@linkcode LogRecord}.
*/
export interface LogRecordOptions { export interface LogRecordOptions {
/** The message to log. */
msg: string; msg: string;
/** The arguments to log. */
args: unknown[]; args: unknown[];
/** The log level of the message. */
level: LogLevel; level: LogLevel;
/** The name of the logger that created the log record. */
loggerName: string; loggerName: string;
} }
@ -32,15 +39,127 @@ export interface LogConfig {
/** /**
* An object that encapsulates provided message and arguments as well some * An object that encapsulates provided message and arguments as well some
* metadata that can be later used when formatting a message. * 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 { 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; readonly msg: string;
#args: unknown[]; #args: unknown[];
#datetime: Date; #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; 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; 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; 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) { constructor(options: LogRecordOptions) {
this.msg = options.msg; this.msg = options.msg;
this.#args = [...options.args]; this.#args = [...options.args];
@ -49,9 +168,53 @@ export class LogRecord {
this.#datetime = new Date(); this.#datetime = new Date();
this.levelName = getLevelName(options.level); 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[] { get args(): unknown[] {
return [...this.#args]; 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 { assertAlmostEquals } from "@std/assert/almost-equals";
*
* const record = new LogRecord({
* msg: "Hello, world!",
* args: ["foo", "bar"],
* level: LogLevels.INFO,
* loggerName: "example",
* });
*
* assertAlmostEquals(record.datetime.getTime(), Date.now(), 1);
* ```
*
* @returns The date and time the log record was created.
*/
get datetime(): Date { get datetime(): Date {
return new Date(this.#datetime.getTime()); return new Date(this.#datetime.getTime());
} }