summaryrefslogtreecommitdiff
path: root/std/log
diff options
context:
space:
mode:
Diffstat (limited to 'std/log')
-rw-r--r--std/log/README.md316
-rw-r--r--std/log/handlers.ts236
-rw-r--r--std/log/handlers_test.ts541
-rw-r--r--std/log/levels.ts59
-rw-r--r--std/log/logger.ts189
-rw-r--r--std/log/logger_test.ts255
-rw-r--r--std/log/mod.ts209
-rw-r--r--std/log/mod_test.ts222
-rw-r--r--std/log/test.ts114
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));
-});