diff options
Diffstat (limited to 'std/log')
-rw-r--r-- | std/log/README.md | 316 | ||||
-rw-r--r-- | std/log/handlers.ts | 236 | ||||
-rw-r--r-- | std/log/handlers_test.ts | 541 | ||||
-rw-r--r-- | std/log/levels.ts | 59 | ||||
-rw-r--r-- | std/log/logger.ts | 189 | ||||
-rw-r--r-- | std/log/logger_test.ts | 255 | ||||
-rw-r--r-- | std/log/mod.ts | 209 | ||||
-rw-r--r-- | std/log/mod_test.ts | 222 | ||||
-rw-r--r-- | std/log/test.ts | 114 |
9 files changed, 0 insertions, 2141 deletions
diff --git a/std/log/README.md b/std/log/README.md deleted file mode 100644 index 1208f5ba9..000000000 --- a/std/log/README.md +++ /dev/null @@ -1,316 +0,0 @@ -# Log - -## Usage - -```ts -import * as log from "https://deno.land/std@$STD_VERSION/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. You can log any data type. -log.debug("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). -await log.setup({ - handlers: { - console: new log.handlers.ConsoleHandler("DEBUG"), - - file: new log.handlers.FileHandler("WARNING", { - filename: "./log.txt", - // you can change format of output message using any keys in `LogRecord`. - formatter: "{levelName} {msg}", - }), - }, - - loggers: { - // configure default logger available via short-hand methods above. - default: { - level: "DEBUG", - handlers: ["console", "file"], - }, - - tasks: { - level: "ERROR", - handlers: ["console"], - }, - }, -}); - -let logger; - -// get default logger. -logger = log.getLogger(); -logger.debug("fizz"); // logs to `console`, because `file` handler requires "WARNING" level. -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({ 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 -// so no message is logged. -const unknownLogger = log.getLogger("mystery"); -unknownLogger.info("foobar"); // no-op -``` - -## Advanced usage - -### Loggers - -Loggers are objects that you interact with. When you use a logger method it -constructs a `LogRecord` and passes it down to its handlers for output. To -create custom loggers, specify them in `loggers` when calling `log.setup`. - -#### `LogRecord` - -`LogRecord` is an object that encapsulates provided message and arguments as -well some meta data that can be later used when formatting a message. - -```ts -class LogRecord { - readonly msg: string; - readonly args: any[]; - readonly datetime: Date; - readonly level: number; - readonly levelName: string; - readonly loggerName: string; -} -``` - -### Log Levels - -The different log levels are exported in the `LogLevels` enum type. It defaults -to INFO if none is specified. - -### Handlers - -Handlers are responsible for actual output of log messages. When a handler is -called by a logger, it firstly checks that `LogRecord`'s level is not lower than -level of the handler. If level check passes, handlers formats log record into -string and outputs it to target. - -`log` module comes with three built-in handlers: - -#### `ConsoleHandler` - -This is the default logger. It will output color coded log messages to the -console via `console.log()`. This logger takes `HandlerOptions`: - -```typescript -type FormatterFunction = (logRecord: LogRecord) => string; - -interface HandlerOptions { - formatter?: string | FormatterFunction; //see `Custom message format` below -} -``` - -#### `FileHandler` - -This handler will output to a file using an optional mode (default is `a`, e.g. -append). The file will grow indefinitely. It uses a buffer for writing to file. -Logs can be manually flushed with `fileHandler.flush()`. Log messages with a log -level greater than error are immediately flushed. Logs are also flushed on -process completion. This logger takes `FileOptions`: - -```typescript -interface FileHandlerOptions { - formatter?: string | FormatterFunction; //see `Custom message format` below - filename: string; - mode?: LogMode; // 'a', 'w', 'x' -} -``` - -Behavior of the log modes is as follows: - -- `'a'` - Default mode. Appends new log messages to the end of an existing log - file, or create a new log file if none exists. -- `'w'` - Upon creation of the handler, any existing log file will be removed - and a new one created. -- `'x'` - This will create a new log file and throw an error if one already - exists. - -This handler requires `--allow-write` permission on the log file. - -#### `RotatingFileHandler` - -This handler extends the functionality of the `FileHandler` by "rotating" the -log file when it reaches a certain size. `maxBytes` specifies the maximum size -in bytes that the log file can grow to before rolling over to a new one. If the -size of the new log message plus the current log file size exceeds `maxBytes` -then a roll over is triggered. When a roll over occurs, before the log message -is written, the log file is renamed and appended with `.1`. If a `.1` version -already existed, it would have been renamed `.2` first and so on. The maximum -number of log files to keep is specified by `maxBackupCount`. After the renames -are complete the log message is written to the original, now blank, file. - -Example: Given `log.txt`, `log.txt.1`, `log.txt.2` and `log.txt.3`, a -`maxBackupCount` of 3 and a new log message which would cause `log.txt` to -exceed `maxBytes`, then `log.txt.2` would be renamed to `log.txt.3` (thereby -discarding the original contents of `log.txt.3` since 3 is the maximum number of -backups to keep), `log.txt.1` would be renamed to `log.txt.2`, `log.txt` would -be renamed to `log.txt.1` and finally `log.txt` would be created from scratch -where the new log message would be written. - -This handler uses a buffer for writing log messages to file. Logs can be -manually flushed with `fileHandler.flush()`. Log messages with a log level -greater than ERROR are immediately flushed. Logs are also flushed on process -completion. - -Options for this handler are: - -```typescript -interface RotatingFileHandlerOptions { - maxBytes: number; - maxBackupCount: number; - formatter?: string | FormatterFunction; //see `Custom message format` below - filename: string; - mode?: LogMode; // 'a', 'w', 'x' -} -``` - -Additional notes on `mode` as described above: - -- `'a'` Default mode. As above, this will pick up where the logs left off in - rotation, or create a new log file if it doesn't exist. -- `'w'` in addition to starting with a clean `filename`, this mode will also - cause any existing backups (up to `maxBackupCount`) to be deleted on setup - giving a fully clean slate. -- `'x'` requires that neither `filename`, nor any backups (up to - `maxBackupCount`), exist before setup. - -This handler requires both `--allow-read` and `--allow-write` permissions on the -log files. - -### Custom message format - -If you want to override default format of message you can define `formatter` -option for handler. It can be either simple string-based format that uses -`LogRecord` fields or more complicated function-based one that takes `LogRecord` -as argument and outputs string. - -The default log format is `{levelName} {msg}`. - -Eg. - -```ts -await log.setup({ - handlers: { - stringFmt: new log.handlers.ConsoleHandler("DEBUG", { - formatter: "[{levelName}] {msg}" - }), - - functionFmt: new log.handlers.ConsoleHandler("DEBUG", { - formatter: logRecord => { - let msg = `${logRecord.level} ${logRecord.msg}`; - - logRecord.args.forEach((arg, index) => { - msg += `, arg${index}: ${arg}`; - }); - - return msg; - } - }), - - anotherFmt: new log.handlers.ConsoleHandler("DEBUG", { - formatter: "[{loggerName}] - {levelName} {msg}" - }), - }, - - loggers: { - default: { - level: "DEBUG", - handlers: ["stringFmt", "functionFmt"], - }, - dataLogger: { - level: "INFO", - handlers: ["anotherFmt"], - } - } -}) - -// calling: -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 - -Custom handlers can be implemented by subclassing `BaseHandler` or -`WriterHandler`. - -`BaseHandler` is bare-bones handler that has no output logic at all, - -`WriterHandler` is an abstract class that supports any target with `Writer` -interface. - -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. It is an -> antipattern use lazy evaluation with inline logging because the return value -> depends on the current log level. - -Example: - -```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/handlers.ts b/std/log/handlers.ts deleted file mode 100644 index acfb16641..000000000 --- a/std/log/handlers.ts +++ /dev/null @@ -1,236 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. -import { getLevelByName, LevelName, LogLevels } from "./levels.ts"; -import type { LogRecord } from "./logger.ts"; -import { blue, bold, red, yellow } from "../fmt/colors.ts"; -import { exists, existsSync } from "../fs/exists.ts"; -import { BufWriterSync } from "../io/bufio.ts"; - -const DEFAULT_FORMATTER = "{levelName} {msg}"; -type FormatterFunction = (logRecord: LogRecord) => string; -type LogMode = "a" | "w" | "x"; - -interface HandlerOptions { - formatter?: string | FormatterFunction; -} - -export class BaseHandler { - level: number; - levelName: LevelName; - formatter: string | FormatterFunction; - - constructor(levelName: LevelName, options: HandlerOptions = {}) { - this.level = getLevelByName(levelName); - this.levelName = levelName; - - this.formatter = options.formatter || DEFAULT_FORMATTER; - } - - handle(logRecord: LogRecord): void { - if (this.level > logRecord.level) return; - - const msg = this.format(logRecord); - return this.log(msg); - } - - format(logRecord: LogRecord): string { - if (this.formatter instanceof Function) { - return this.formatter(logRecord); - } - - return this.formatter.replace(/{(\S+)}/g, (match, p1): string => { - const value = logRecord[p1 as keyof LogRecord]; - - // do not interpolate missing values - if (value == null) { - return match; - } - - return String(value); - }); - } - - log(_msg: string): void {} - async setup(): Promise<void> {} - async destroy(): Promise<void> {} -} - -export class ConsoleHandler extends BaseHandler { - format(logRecord: LogRecord): string { - let msg = super.format(logRecord); - - switch (logRecord.level) { - case LogLevels.INFO: - msg = blue(msg); - break; - case LogLevels.WARNING: - msg = yellow(msg); - break; - case LogLevels.ERROR: - msg = red(msg); - break; - case LogLevels.CRITICAL: - msg = bold(red(msg)); - break; - default: - break; - } - - return msg; - } - - log(msg: string): void { - console.log(msg); - } -} - -export abstract class WriterHandler extends BaseHandler { - protected _writer!: Deno.Writer; - #encoder = new TextEncoder(); - - abstract log(msg: string): void; -} - -interface FileHandlerOptions extends HandlerOptions { - filename: string; - mode?: LogMode; -} - -export class FileHandler extends WriterHandler { - protected _file: Deno.File | undefined; - protected _buf!: BufWriterSync; - protected _filename: string; - protected _mode: LogMode; - protected _openOptions: Deno.OpenOptions; - protected _encoder = new TextEncoder(); - #unloadCallback = (): Promise<void> => this.destroy(); - - constructor(levelName: LevelName, options: FileHandlerOptions) { - super(levelName, options); - this._filename = options.filename; - // default to append mode, write only - this._mode = options.mode ? options.mode : "a"; - this._openOptions = { - createNew: this._mode === "x", - create: this._mode !== "x", - append: this._mode === "a", - truncate: this._mode !== "a", - write: true, - }; - } - - async setup(): Promise<void> { - this._file = await Deno.open(this._filename, this._openOptions); - this._writer = this._file; - this._buf = new BufWriterSync(this._file); - - addEventListener("unload", this.#unloadCallback); - } - - handle(logRecord: LogRecord): void { - super.handle(logRecord); - - // Immediately flush if log level is higher than ERROR - if (logRecord.level > LogLevels.ERROR) { - this.flush(); - } - } - - log(msg: string): void { - this._buf.writeSync(this._encoder.encode(msg + "\n")); - } - - flush(): void { - if (this._buf?.buffered() > 0) { - this._buf.flush(); - } - } - - destroy(): Promise<void> { - this.flush(); - this._file?.close(); - this._file = undefined; - removeEventListener("unload", this.#unloadCallback); - return Promise.resolve(); - } -} - -interface RotatingFileHandlerOptions extends FileHandlerOptions { - maxBytes: number; - maxBackupCount: number; -} - -export class RotatingFileHandler extends FileHandler { - #maxBytes: number; - #maxBackupCount: number; - #currentFileSize = 0; - - constructor(levelName: LevelName, options: RotatingFileHandlerOptions) { - super(levelName, options); - this.#maxBytes = options.maxBytes; - this.#maxBackupCount = options.maxBackupCount; - } - - async setup(): Promise<void> { - if (this.#maxBytes < 1) { - this.destroy(); - throw new Error("maxBytes cannot be less than 1"); - } - if (this.#maxBackupCount < 1) { - this.destroy(); - throw new Error("maxBackupCount cannot be less than 1"); - } - await super.setup(); - - if (this._mode === "w") { - // Remove old backups too as it doesn't make sense to start with a clean - // log file, but old backups - for (let i = 1; i <= this.#maxBackupCount; i++) { - if (await exists(this._filename + "." + i)) { - await Deno.remove(this._filename + "." + i); - } - } - } else if (this._mode === "x") { - // Throw if any backups also exist - for (let i = 1; i <= this.#maxBackupCount; i++) { - if (await exists(this._filename + "." + i)) { - this.destroy(); - throw new Deno.errors.AlreadyExists( - "Backup log file " + this._filename + "." + i + " already exists", - ); - } - } - } else { - this.#currentFileSize = (await Deno.stat(this._filename)).size; - } - } - - log(msg: string): void { - const msgByteLength = this._encoder.encode(msg).byteLength + 1; - - if (this.#currentFileSize + msgByteLength > this.#maxBytes) { - this.rotateLogFiles(); - this.#currentFileSize = 0; - } - - this._buf.writeSync(this._encoder.encode(msg + "\n")); - this.#currentFileSize += msgByteLength; - } - - rotateLogFiles(): void { - this._buf.flush(); - Deno.close(this._file!.rid); - - for (let i = this.#maxBackupCount - 1; i >= 0; i--) { - const source = this._filename + (i === 0 ? "" : "." + i); - const dest = this._filename + "." + (i + 1); - - if (existsSync(source)) { - Deno.renameSync(source, dest); - } - } - - this._file = Deno.openSync(this._filename, this._openOptions); - this._writer = this._file; - this._buf = new BufWriterSync(this._file); - } -} diff --git a/std/log/handlers_test.ts b/std/log/handlers_test.ts deleted file mode 100644 index a8e3a3df2..000000000 --- a/std/log/handlers_test.ts +++ /dev/null @@ -1,541 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. -import { - assert, - assertEquals, - assertNotEquals, - assertThrowsAsync, -} from "../testing/asserts.ts"; -import { - getLevelByName, - getLevelName, - LevelName, - LogLevelNames, - LogLevels, -} from "./levels.ts"; -import { BaseHandler, FileHandler, RotatingFileHandler } from "./handlers.ts"; -import { LogRecord } from "./logger.ts"; -import { existsSync } from "../fs/exists.ts"; - -const LOG_FILE = "./test_log.file"; - -class TestHandler extends BaseHandler { - public messages: string[] = []; - - public log(str: string): void { - this.messages.push(str); - } -} - -Deno.test("simpleHandler", function (): void { - const cases = new Map<number, string[]>([ - [ - LogLevels.DEBUG, - [ - "DEBUG debug-test", - "INFO info-test", - "WARNING warning-test", - "ERROR error-test", - "CRITICAL critical-test", - ], - ], - [ - LogLevels.INFO, - [ - "INFO info-test", - "WARNING warning-test", - "ERROR error-test", - "CRITICAL critical-test", - ], - ], - [ - LogLevels.WARNING, - ["WARNING warning-test", "ERROR error-test", "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 = getLevelName(testCase); - const handler = new TestHandler(testLevel); - - for (const levelName of LogLevelNames) { - const level = getLevelByName(levelName as LevelName); - handler.handle( - new LogRecord({ - msg: `${levelName.toLowerCase()}-test`, - args: [], - level: level, - loggerName: "default", - }), - ); - } - - assertEquals(handler.level, testCase); - assertEquals(handler.levelName, testLevel); - assertEquals(handler.messages, messages); - } -}); - -Deno.test("testFormatterAsString", function (): void { - const handler = new TestHandler("DEBUG", { - formatter: "test {levelName} {msg}", - }); - - handler.handle( - new LogRecord({ - msg: "Hello, world!", - args: [], - level: LogLevels.DEBUG, - loggerName: "default", - }), - ); - - assertEquals(handler.messages, ["test DEBUG Hello, world!"]); -}); - -Deno.test("testFormatterWithEmptyMsg", function () { - const handler = new TestHandler("DEBUG", { - formatter: "test {levelName} {msg}", - }); - - handler.handle( - new LogRecord({ - msg: "", - args: [], - level: LogLevels.DEBUG, - loggerName: "default", - }), - ); - - assertEquals(handler.messages, ["test DEBUG "]); -}); - -Deno.test("testFormatterAsFunction", function (): void { - const handler = new TestHandler("DEBUG", { - formatter: (logRecord): string => - `fn formatter ${logRecord.levelName} ${logRecord.msg}`, - }); - - handler.handle( - new LogRecord({ - msg: "Hello, world!", - args: [], - level: LogLevels.ERROR, - loggerName: "default", - }), - ); - - assertEquals(handler.messages, ["fn formatter ERROR Hello, world!"]); -}); - -Deno.test({ - name: "FileHandler with mode 'w' will wipe clean existing log file", - async fn() { - const fileHandler = new FileHandler("WARNING", { - filename: LOG_FILE, - mode: "w", - }); - - await fileHandler.setup(); - 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({ - msg: "Hello World", - args: [], - level: LogLevels.WARNING, - loggerName: "default", - }), - ); - await fileHandler.destroy(); - const secondFileSize = (await Deno.stat(LOG_FILE)).size; - - assertEquals(secondFileSize, firstFileSize); - Deno.removeSync(LOG_FILE); - }, -}); - -Deno.test({ - name: "FileHandler with mode 'x' will throw if log file already exists", - async fn() { - const fileHandler = new FileHandler("WARNING", { - filename: LOG_FILE, - mode: "x", - }); - Deno.writeFileSync(LOG_FILE, new TextEncoder().encode("hello world")); - - await assertThrowsAsync(async () => { - await fileHandler.setup(); - }, Deno.errors.AlreadyExists); - - await fileHandler.destroy(); - - Deno.removeSync(LOG_FILE); - }, -}); - -Deno.test({ - name: - "RotatingFileHandler with mode 'w' will wipe clean existing log file and remove others", - async fn() { - Deno.writeFileSync(LOG_FILE, new TextEncoder().encode("hello world")); - Deno.writeFileSync( - LOG_FILE + ".1", - new TextEncoder().encode("hello world"), - ); - Deno.writeFileSync( - LOG_FILE + ".2", - new TextEncoder().encode("hello world"), - ); - Deno.writeFileSync( - LOG_FILE + ".3", - new TextEncoder().encode("hello world"), - ); - - const fileHandler = new RotatingFileHandler("WARNING", { - filename: LOG_FILE, - maxBytes: 50, - maxBackupCount: 3, - mode: "w", - }); - await fileHandler.setup(); - await fileHandler.destroy(); - - assertEquals((await Deno.stat(LOG_FILE)).size, 0); - assert(!existsSync(LOG_FILE + ".1")); - assert(!existsSync(LOG_FILE + ".2")); - assert(!existsSync(LOG_FILE + ".3")); - - Deno.removeSync(LOG_FILE); - }, -}); - -Deno.test({ - name: - "RotatingFileHandler with mode 'x' will throw if any log file already exists", - async fn() { - Deno.writeFileSync( - LOG_FILE + ".3", - new TextEncoder().encode("hello world"), - ); - const fileHandler = new RotatingFileHandler("WARNING", { - filename: LOG_FILE, - maxBytes: 50, - maxBackupCount: 3, - mode: "x", - }); - await assertThrowsAsync( - async () => { - await fileHandler.setup(); - }, - Deno.errors.AlreadyExists, - "Backup log file " + LOG_FILE + ".3 already exists", - ); - - fileHandler.destroy(); - Deno.removeSync(LOG_FILE + ".3"); - Deno.removeSync(LOG_FILE); - }, -}); - -Deno.test({ - name: "RotatingFileHandler with first rollover, monitor step by step", - async fn() { - const fileHandler = new RotatingFileHandler("WARNING", { - filename: LOG_FILE, - maxBytes: 25, - maxBackupCount: 3, - mode: "w", - }); - await fileHandler.setup(); - - 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({ - msg: "AAA", - args: [], - level: LogLevels.ERROR, - loggerName: "default", - }), - ); - fileHandler.flush(); - assertEquals((await Deno.stat(LOG_FILE)).size, 20); - 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); - assertEquals((await Deno.stat(LOG_FILE + ".1")).size, 20); - await fileHandler.destroy(); - - Deno.removeSync(LOG_FILE); - Deno.removeSync(LOG_FILE + ".1"); - }, -}); - -Deno.test({ - name: "RotatingFileHandler with first rollover, check all at once", - async fn() { - const fileHandler = new RotatingFileHandler("WARNING", { - filename: LOG_FILE, - maxBytes: 25, - maxBackupCount: 3, - mode: "w", - }); - await fileHandler.setup(); - - 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(); - - assertEquals((await Deno.stat(LOG_FILE)).size, 10); - assertEquals((await Deno.stat(LOG_FILE + ".1")).size, 20); - - Deno.removeSync(LOG_FILE); - Deno.removeSync(LOG_FILE + ".1"); - }, -}); - -Deno.test({ - name: "RotatingFileHandler with all backups rollover", - async fn() { - Deno.writeFileSync(LOG_FILE, new TextEncoder().encode("original log file")); - Deno.writeFileSync( - LOG_FILE + ".1", - new TextEncoder().encode("original log.1 file"), - ); - Deno.writeFileSync( - LOG_FILE + ".2", - new TextEncoder().encode("original log.2 file"), - ); - Deno.writeFileSync( - LOG_FILE + ".3", - new TextEncoder().encode("original log.3 file"), - ); - - const fileHandler = new RotatingFileHandler("WARNING", { - filename: LOG_FILE, - maxBytes: 2, - maxBackupCount: 3, - mode: "a", - }); - await fileHandler.setup(); - fileHandler.handle( - new LogRecord({ - msg: "AAA", - args: [], - level: LogLevels.ERROR, - loggerName: "default", - }), - ); // 'ERROR AAA\n' = 10 bytes - await fileHandler.destroy(); - - const decoder = new TextDecoder(); - assertEquals(decoder.decode(Deno.readFileSync(LOG_FILE)), "ERROR AAA\n"); - assertEquals( - decoder.decode(Deno.readFileSync(LOG_FILE + ".1")), - "original log file", - ); - assertEquals( - decoder.decode(Deno.readFileSync(LOG_FILE + ".2")), - "original log.1 file", - ); - assertEquals( - decoder.decode(Deno.readFileSync(LOG_FILE + ".3")), - "original log.2 file", - ); - assert(!existsSync(LOG_FILE + ".4")); - - Deno.removeSync(LOG_FILE); - Deno.removeSync(LOG_FILE + ".1"); - Deno.removeSync(LOG_FILE + ".2"); - Deno.removeSync(LOG_FILE + ".3"); - }, -}); - -Deno.test({ - name: "RotatingFileHandler maxBytes cannot be less than 1", - async fn() { - await assertThrowsAsync( - async () => { - const fileHandler = new RotatingFileHandler("WARNING", { - filename: LOG_FILE, - maxBytes: 0, - maxBackupCount: 3, - mode: "w", - }); - await fileHandler.setup(); - }, - Error, - "maxBytes cannot be less than 1", - ); - }, -}); - -Deno.test({ - name: "RotatingFileHandler maxBackupCount cannot be less than 1", - async fn() { - await assertThrowsAsync( - async () => { - const fileHandler = new RotatingFileHandler("WARNING", { - filename: LOG_FILE, - maxBytes: 50, - maxBackupCount: 0, - mode: "w", - }); - await fileHandler.setup(); - }, - Error, - "maxBackupCount cannot be less than 1", - ); - }, -}); - -Deno.test({ - name: "Window unload flushes buffer", - async fn() { - const fileHandler = new FileHandler("WARNING", { - filename: LOG_FILE, - mode: "w", - }); - await fileHandler.setup(); - 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")); - assertEquals((await Deno.stat(LOG_FILE)).size, 10); - - Deno.removeSync(LOG_FILE); - }, -}); - -Deno.test({ - name: "RotatingFileHandler: rotate on byte length, not msg length", - async fn() { - const fileHandler = new RotatingFileHandler("WARNING", { - filename: LOG_FILE, - maxBytes: 7, - maxBackupCount: 1, - mode: "w", - }); - await fileHandler.setup(); - - const msg = "。"; - const msgLength = msg.length; - const msgByteLength = new TextEncoder().encode(msg).byteLength; - assertNotEquals(msgLength, msgByteLength); - assertEquals(msgLength, 1); - assertEquals(msgByteLength, 3); - - fileHandler.log(msg); // logs 4 bytes (including '\n') - fileHandler.log(msg); // max bytes is 7, but this would be 8. Rollover. - - await fileHandler.destroy(); - - const fileSize1 = (await Deno.stat(LOG_FILE)).size; - const fileSize2 = (await Deno.stat(LOG_FILE + ".1")).size; - - assertEquals(fileSize1, msgByteLength + 1); - assertEquals(fileSize2, msgByteLength + 1); - - Deno.removeSync(LOG_FILE); - Deno.removeSync(LOG_FILE + ".1"); - }, -}); - -Deno.test({ - name: "FileHandler: Critical logs trigger immediate flush", - async fn() { - const fileHandler = new FileHandler("WARNING", { - filename: LOG_FILE, - mode: "w", - }); - await fileHandler.setup(); - - 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({ - msg: "AAA", - args: [], - level: LogLevels.CRITICAL, - loggerName: "default", - }), - ); - - // CRITICAL will trigger immediate flush - const fileSize2 = (await Deno.stat(LOG_FILE)).size; - // ERROR record is 10 bytes, CRITICAL is 13 bytes - assertEquals(fileSize2, 23); - - await fileHandler.destroy(); - Deno.removeSync(LOG_FILE); - }, -}); diff --git a/std/log/levels.ts b/std/log/levels.ts deleted file mode 100644 index 6b748e992..000000000 --- a/std/log/levels.ts +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. -/** Get log level numeric values through enum constants - */ -export enum LogLevels { - NOTSET = 0, - DEBUG = 10, - INFO = 20, - WARNING = 30, - ERROR = 40, - CRITICAL = 50, -} - -/** Permitted log level names */ -export const LogLevelNames = Object.keys(LogLevels).filter((key) => - isNaN(Number(key)) -); - -/** Union of valid log level strings */ -export type LevelName = keyof typeof LogLevels; - -const byLevel: Record<string, LevelName> = { - [String(LogLevels.NOTSET)]: "NOTSET", - [String(LogLevels.DEBUG)]: "DEBUG", - [String(LogLevels.INFO)]: "INFO", - [String(LogLevels.WARNING)]: "WARNING", - [String(LogLevels.ERROR)]: "ERROR", - [String(LogLevels.CRITICAL)]: "CRITICAL", -}; - -/** Returns the numeric log level associated with the passed, - * stringy log level name. - */ -export function getLevelByName(name: LevelName): number { - switch (name) { - case "NOTSET": - return LogLevels.NOTSET; - case "DEBUG": - return LogLevels.DEBUG; - case "INFO": - return LogLevels.INFO; - case "WARNING": - return LogLevels.WARNING; - case "ERROR": - return LogLevels.ERROR; - case "CRITICAL": - return LogLevels.CRITICAL; - default: - throw new Error(`no log level found for "${name}"`); - } -} - -/** Returns the stringy log level name provided the numeric log level */ -export function getLevelName(level: number): LevelName { - const levelName = byLevel[level]; - if (levelName) { - return levelName; - } - throw new Error(`no level name found for level: ${level}`); -} diff --git a/std/log/logger.ts b/std/log/logger.ts deleted file mode 100644 index bd8d8cce5..000000000 --- a/std/log/logger.ts +++ /dev/null @@ -1,189 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. -import { getLevelByName, getLevelName, LogLevels } from "./levels.ts"; -import type { LevelName } from "./levels.ts"; -import type { BaseHandler } from "./handlers.ts"; - -// deno-lint-ignore no-explicit-any -export type GenericFunction = (...args: any[]) => any; - -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(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(options.level); - } - get args(): unknown[] { - return [...this.#args]; - } - get datetime(): Date { - return new Date(this.#datetime.getTime()); - } -} - -export interface LoggerOptions { - handlers?: BaseHandler[]; -} - -export class Logger { - #level: LogLevels; - #handlers: BaseHandler[]; - readonly #loggerName: string; - - constructor( - loggerName: string, - levelName: LevelName, - options: LoggerOptions = {}, - ) { - this.#loggerName = loggerName; - this.#level = getLevelByName(levelName); - this.#handlers = options.handlers || []; - } - - get level(): LogLevels { - return this.#level; - } - set level(level: LogLevels) { - this.#level = level; - } - - get levelName(): LevelName { - return getLevelName(this.#level); - } - set levelName(levelName: LevelName) { - this.#level = getLevelByName(levelName); - } - - get loggerName(): string { - return this.#loggerName; - } - - set handlers(hndls: BaseHandler[]) { - this.#handlers = hndls; - } - get handlers(): BaseHandler[] { - return this.#handlers; - } - - /** 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. - */ - private _log<T>( - level: number, - msg: (T extends GenericFunction ? never : T) | (() => T), - ...args: unknown[] - ): T | undefined { - if (this.level > level) { - return msg instanceof Function ? undefined : msg; - } - - 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({ - msg: logMessage, - args: args, - level: level, - loggerName: this.loggerName, - }); - - 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 (data instanceof Error) { - return data.stack!; - } else if (typeof data === "object") { - return JSON.stringify(data); - } - return "undefined"; - } - - debug<T>(msg: () => T, ...args: unknown[]): T | undefined; - debug<T>(msg: T extends GenericFunction ? never : T, ...args: unknown[]): T; - debug<T>( - msg: (T extends GenericFunction ? never : T) | (() => T), - ...args: unknown[] - ): T | undefined { - return this._log(LogLevels.DEBUG, msg, ...args); - } - - info<T>(msg: () => T, ...args: unknown[]): T | undefined; - info<T>(msg: T extends GenericFunction ? never : T, ...args: unknown[]): T; - info<T>( - msg: (T extends GenericFunction ? never : T) | (() => T), - ...args: unknown[] - ): T | undefined { - return this._log(LogLevels.INFO, msg, ...args); - } - - warning<T>(msg: () => T, ...args: unknown[]): T | undefined; - warning<T>(msg: T extends GenericFunction ? never : T, ...args: unknown[]): T; - warning<T>( - msg: (T extends GenericFunction ? never : T) | (() => T), - ...args: unknown[] - ): T | undefined { - return this._log(LogLevels.WARNING, msg, ...args); - } - - error<T>(msg: () => T, ...args: unknown[]): T | undefined; - error<T>(msg: T extends GenericFunction ? never : T, ...args: unknown[]): T; - error<T>( - msg: (T extends GenericFunction ? never : T) | (() => T), - ...args: unknown[] - ): T | undefined { - return this._log(LogLevels.ERROR, msg, ...args); - } - - critical<T>(msg: () => T, ...args: unknown[]): T | undefined; - critical<T>( - msg: T extends GenericFunction ? never : T, - ...args: unknown[] - ): T; - critical<T>( - msg: (T extends GenericFunction ? 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 deleted file mode 100644 index bfaa653c9..000000000 --- a/std/log/logger_test.ts +++ /dev/null @@ -1,255 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. -import { assert, assertEquals, assertMatch } from "../testing/asserts.ts"; -import { Logger, LogRecord } from "./logger.ts"; -import { LevelName, LogLevels } from "./levels.ts"; -import { BaseHandler } from "./handlers.ts"; - -class TestHandler extends BaseHandler { - public messages: string[] = []; - public records: LogRecord[] = []; - - handle(record: LogRecord): void { - this.records.push(record); - super.handle(record); - } - - public log(str: string): void { - this.messages.push(str); - } -} - -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("default", "DEBUG"); - - assertEquals(logger.level, LogLevels.DEBUG); - assertEquals(logger.levelName, "DEBUG"); - assertEquals(logger.handlers, []); - - 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("default", "DEBUG", { handlers: [handler] }); - - const inlineData: string = logger.debug("foo", 1, 2); - - const record = handler.records[0]; - assertEquals(record.msg, "foo"); - assertEquals(record.args, [1, 2]); - assertEquals(record.level, LogLevels.DEBUG); - assertEquals(record.levelName, "DEBUG"); - - assertEquals(handler.messages, ["DEBUG foo"]); - assertEquals(inlineData!, "foo"); -}); - -Deno.test("logFunctions", function (): void { - const doLog = (level: LevelName): TestHandler => { - const handler = new TestHandler(level); - const logger = new Logger("default", level, { handlers: [handler] }); - 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; - }; - - let handler: TestHandler; - handler = doLog("DEBUG"); - - assertEquals(handler.messages, [ - "DEBUG foo", - "INFO bar", - "WARNING baz", - "ERROR boo", - "CRITICAL doo", - ]); - - handler = doLog("INFO"); - - assertEquals(handler.messages, [ - "INFO bar", - "WARNING baz", - "ERROR boo", - "CRITICAL doo", - ]); - - handler = doLog("WARNING"); - - assertEquals(handler.messages, ["WARNING baz", "ERROR boo", "CRITICAL doo"]); - - handler = doLog("ERROR"); - - assertEquals(handler.messages, ["ERROR boo", "CRITICAL doo"]); - - handler = doLog("CRITICAL"); - - assertEquals(handler.messages, ["CRITICAL doo"]); -}); - -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("default", "ERROR", { handlers: [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); - }, -); - -Deno.test("String resolver fn resolves as expected", function (): void { - const handler = new TestHandler("ERROR"); - const logger = new Logger("default", "ERROR", { handlers: [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"); -}); - -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("default", "DEBUG", { handlers: [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}'); - - // error - const error = new RangeError("Uh-oh!"); - const data19: RangeError = logger.error(error); - assertEquals(data19, error); - const messages19 = handler.messages[18].split("\n"); - assertEquals(messages19[0], `ERROR ${error.name}: ${error.message}`); - assertMatch(messages19[1], /^\s+at file:.*\d+:\d+$/); - }, -); diff --git a/std/log/mod.ts b/std/log/mod.ts deleted file mode 100644 index 31fc9aaee..000000000 --- a/std/log/mod.ts +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. -import { Logger } from "./logger.ts"; -import type { GenericFunction } from "./logger.ts"; -import { - BaseHandler, - ConsoleHandler, - FileHandler, - RotatingFileHandler, - WriterHandler, -} from "./handlers.ts"; -import { assert } from "../_util/assert.ts"; -import type { LevelName } from "./levels.ts"; - -export { LogLevels } from "./levels.ts"; -export type { LevelName } from "./levels.ts"; -export { Logger } from "./logger.ts"; - -export class LoggerConfig { - level?: LevelName; - handlers?: string[]; -} - -export interface LogConfig { - handlers?: { - [name: string]: BaseHandler; - }; - loggers?: { - [name: string]: LoggerConfig; - }; -} - -const DEFAULT_LEVEL = "INFO"; -const DEFAULT_CONFIG: LogConfig = { - handlers: { - default: new ConsoleHandler(DEFAULT_LEVEL), - }, - - loggers: { - default: { - level: DEFAULT_LEVEL, - handlers: ["default"], - }, - }, -}; - -const state = { - handlers: new Map<string, BaseHandler>(), - loggers: new Map<string, Logger>(), - config: DEFAULT_CONFIG, -}; - -export const handlers = { - BaseHandler, - ConsoleHandler, - WriterHandler, - FileHandler, - RotatingFileHandler, -}; - -/** Get a logger instance. If not specified `name`, get the default logger. */ -export function getLogger(name?: string): Logger { - if (!name) { - const d = state.loggers.get("default"); - assert( - d != null, - `"default" logger must be set for getting logger without name`, - ); - return d; - } - const result = state.loggers.get(name); - if (!result) { - const logger = new Logger(name, "NOTSET", { handlers: [] }); - state.loggers.set(name, logger); - return logger; - } - return result; -} - -/** Log with debug level, using default logger. */ -export function debug<T>(msg: () => T, ...args: unknown[]): T | undefined; -export function debug<T>( - msg: T extends GenericFunction ? never : T, - ...args: unknown[] -): T; -export function debug<T>( - msg: (T extends GenericFunction ? 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); -} - -/** Log with info level, using default logger. */ -export function info<T>(msg: () => T, ...args: unknown[]): T | undefined; -export function info<T>( - msg: T extends GenericFunction ? never : T, - ...args: unknown[] -): T; -export function info<T>( - msg: (T extends GenericFunction ? 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); -} - -/** Log with warning level, using default logger. */ -export function warning<T>(msg: () => T, ...args: unknown[]): T | undefined; -export function warning<T>( - msg: T extends GenericFunction ? never : T, - ...args: unknown[] -): T; -export function warning<T>( - msg: (T extends GenericFunction ? 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); -} - -/** Log with error level, using default logger. */ -export function error<T>(msg: () => T, ...args: unknown[]): T | undefined; -export function error<T>( - msg: T extends GenericFunction ? never : T, - ...args: unknown[] -): T; -export function error<T>( - msg: (T extends GenericFunction ? 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); -} - -/** Log with critical level, using default logger. */ -export function critical<T>(msg: () => T, ...args: unknown[]): T | undefined; -export function critical<T>( - msg: T extends GenericFunction ? never : T, - ...args: unknown[] -): T; -export function critical<T>( - msg: (T extends GenericFunction ? 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); -} - -/** Setup logger config. */ -export async function setup(config: LogConfig): Promise<void> { - state.config = { - handlers: { ...DEFAULT_CONFIG.handlers, ...config.handlers }, - loggers: { ...DEFAULT_CONFIG.loggers, ...config.loggers }, - }; - - // tear down existing handlers - state.handlers.forEach((handler): void => { - handler.destroy(); - }); - state.handlers.clear(); - - // setup handlers - const handlers = state.config.handlers || {}; - - for (const handlerName in handlers) { - const handler = handlers[handlerName]; - await handler.setup(); - state.handlers.set(handlerName, handler); - } - - // remove existing loggers - state.loggers.clear(); - - // setup loggers - const loggers = state.config.loggers || {}; - for (const loggerName in loggers) { - const loggerConfig = loggers[loggerName]; - const handlerNames = loggerConfig.handlers || []; - const handlers: BaseHandler[] = []; - - handlerNames.forEach((handlerName): void => { - const handler = state.handlers.get(handlerName); - if (handler) { - handlers.push(handler); - } - }); - - const levelName = loggerConfig.level || DEFAULT_LEVEL; - const logger = new Logger(loggerName, levelName, { handlers: handlers }); - state.loggers.set(loggerName, logger); - } -} - -await setup(DEFAULT_CONFIG); diff --git a/std/log/mod_test.ts b/std/log/mod_test.ts deleted file mode 100644 index 7823042e7..000000000 --- a/std/log/mod_test.ts +++ /dev/null @@ -1,222 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. -import { assert, assertEquals } from "../testing/asserts.ts"; -import { - critical, - debug, - error, - getLogger, - info, - LevelName, - Logger, - LogLevels, - setup, - warning, -} from "./mod.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 { - // Need to initialize it here - // otherwise it will be already initialized on Deno.test - logger = getLogger(); -} catch { - // Pass -} - -Deno.test("logger is initialized", function (): void { - assert(logger instanceof Logger); -}); - -Deno.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"); -}); - -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"); - }, -}); - -Deno.test({ - name: "Loggers have level and levelName to get/set loglevels", - async fn() { - const testHandler = new TestHandler("DEBUG"); - await setup({ - handlers: { - test: testHandler, - }, - - loggers: { - // configure default logger available via short-hand methods above - default: { - level: "DEBUG", - handlers: ["test"], - }, - }, - }); - const logger: Logger = getLogger(); - assertEquals(logger.levelName, "DEBUG"); - assertEquals(logger.level, LogLevels.DEBUG); - - logger.debug("debug"); - logger.error("error"); - logger.critical("critical"); - assertEquals(testHandler.messages.length, 3); - assertEquals(testHandler.messages[0], "DEBUG debug"); - assertEquals(testHandler.messages[1], "ERROR error"); - assertEquals(testHandler.messages[2], "CRITICAL critical"); - - testHandler.messages = []; - logger.level = LogLevels.WARNING; - assertEquals(logger.levelName, "WARNING"); - assertEquals(logger.level, LogLevels.WARNING); - - logger.debug("debug2"); - logger.error("error2"); - logger.critical("critical2"); - assertEquals(testHandler.messages.length, 2); - assertEquals(testHandler.messages[0], "ERROR error2"); - assertEquals(testHandler.messages[1], "CRITICAL critical2"); - - testHandler.messages = []; - const setLevelName: LevelName = "CRITICAL"; - logger.levelName = setLevelName; - assertEquals(logger.levelName, "CRITICAL"); - assertEquals(logger.level, LogLevels.CRITICAL); - - logger.debug("debug3"); - logger.error("error3"); - logger.critical("critical3"); - assertEquals(testHandler.messages.length, 1); - assertEquals(testHandler.messages[0], "CRITICAL critical3"); - }, -}); - -Deno.test({ - name: "Loggers have loggerName to get logger name", - async fn() { - const testHandler = new TestHandler("DEBUG"); - await setup({ - handlers: { - test: testHandler, - }, - - loggers: { - namedA: { - level: "DEBUG", - handlers: ["test"], - }, - namedB: { - level: "DEBUG", - handlers: ["test"], - }, - }, - }); - - assertEquals(getLogger("namedA").loggerName, "namedA"); - assertEquals(getLogger("namedB").loggerName, "namedB"); - assertEquals(getLogger().loggerName, "default"); - assertEquals(getLogger("nonsetupname").loggerName, "nonsetupname"); - }, -}); - -Deno.test({ - name: "Logger has mutable handlers", - async fn() { - const testHandlerA = new TestHandler("DEBUG"); - const testHandlerB = new TestHandler("DEBUG"); - await setup({ - handlers: { - testA: testHandlerA, - testB: testHandlerB, - }, - - loggers: { - default: { - level: "DEBUG", - handlers: ["testA"], - }, - }, - }); - const logger: Logger = getLogger(); - logger.info("msg1"); - assertEquals(testHandlerA.messages.length, 1); - assertEquals(testHandlerA.messages[0], "INFO msg1"); - assertEquals(testHandlerB.messages.length, 0); - - logger.handlers = [testHandlerA, testHandlerB]; - - logger.info("msg2"); - assertEquals(testHandlerA.messages.length, 2); - assertEquals(testHandlerA.messages[1], "INFO msg2"); - assertEquals(testHandlerB.messages.length, 1); - assertEquals(testHandlerB.messages[0], "INFO msg2"); - - logger.handlers = [testHandlerB]; - - logger.info("msg3"); - assertEquals(testHandlerA.messages.length, 2); - assertEquals(testHandlerB.messages.length, 2); - assertEquals(testHandlerB.messages[1], "INFO msg3"); - - logger.handlers = []; - logger.info("msg4"); - assertEquals(testHandlerA.messages.length, 2); - assertEquals(testHandlerB.messages.length, 2); - }, -}); diff --git a/std/log/test.ts b/std/log/test.ts deleted file mode 100644 index 1966824e1..000000000 --- a/std/log/test.ts +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. -import { assertEquals, assertThrows } from "../testing/asserts.ts"; -import * as log from "./mod.ts"; -import { - getLevelByName, - getLevelName, - LevelName, - LogLevelNames, -} from "./levels.ts"; - -class TestHandler extends log.handlers.BaseHandler { - public messages: string[] = []; - - log(msg: string): void { - this.messages.push(msg); - } -} - -Deno.test("defaultHandlers", async function (): Promise<void> { - const loggers: { - [key: string]: (msg: string, ...args: unknown[]) => void; - } = { - DEBUG: log.debug, - INFO: log.info, - WARNING: log.warning, - ERROR: log.error, - CRITICAL: log.critical, - }; - - for (const levelName of LogLevelNames) { - if (levelName === "NOTSET") { - continue; - } - - const logger = loggers[levelName]; - const handler = new TestHandler(levelName as LevelName); - - await log.setup({ - handlers: { - default: handler, - }, - loggers: { - default: { - level: levelName as LevelName, - handlers: ["default"], - }, - }, - }); - - logger("foo"); - logger("bar", 1, 2); - - assertEquals(handler.messages, [`${levelName} foo`, `${levelName} bar`]); - } -}); - -Deno.test("getLogger", async function (): Promise<void> { - const handler = new TestHandler("DEBUG"); - - await log.setup({ - handlers: { - default: handler, - }, - loggers: { - default: { - level: "DEBUG", - handlers: ["default"], - }, - }, - }); - - const logger = log.getLogger(); - - assertEquals(logger.levelName, "DEBUG"); - assertEquals(logger.handlers, [handler]); -}); - -Deno.test("getLoggerWithName", async function (): Promise<void> { - const fooHandler = new TestHandler("DEBUG"); - - await log.setup({ - handlers: { - foo: fooHandler, - }, - loggers: { - bar: { - level: "INFO", - handlers: ["foo"], - }, - }, - }); - - const logger = log.getLogger("bar"); - - assertEquals(logger.levelName, "INFO"); - assertEquals(logger.handlers, [fooHandler]); -}); - -Deno.test("getLoggerUnknown", async function (): Promise<void> { - await log.setup({ - handlers: {}, - loggers: {}, - }); - - const logger = log.getLogger("nonexistent"); - - assertEquals(logger.levelName, "NOTSET"); - assertEquals(logger.handlers, []); -}); - -Deno.test("getInvalidLoggerLevels", function (): void { - assertThrows(() => getLevelByName("FAKE_LOG_LEVEL" as LevelName)); - assertThrows(() => getLevelName(5000)); -}); |