1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-01-03 04:48:52 -05:00

refactor(std/log): support enum log level (#4859)

This commit is contained in:
Christopher Dieringer 2020-04-25 02:13:26 -07:00 committed by GitHub
parent f8d83361cd
commit b28e60ecaf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 124 additions and 67 deletions

View file

@ -19,7 +19,7 @@ await log.setup({
handlers: { handlers: {
console: new log.handlers.ConsoleHandler("DEBUG"), console: new log.handlers.ConsoleHandler("DEBUG"),
file: new log.handlers.FileHandler("WARNING", { file: new log.handlers.FileHandler(log.LogLevels.DEBUG, {
filename: "./log.txt", filename: "./log.txt",
// you can change format of output message using any keys in `LogRecord` // you can change format of output message using any keys in `LogRecord`
formatter: "{levelName} {msg}", formatter: "{levelName} {msg}",
@ -34,7 +34,7 @@ await log.setup({
}, },
tasks: { tasks: {
level: "ERROR", level: log.LogLevels.ERROR,
handlers: ["console"], handlers: ["console"],
}, },
}, },

View file

@ -3,7 +3,7 @@ const { open, openSync, close, renameSync, statSync } = Deno;
type File = Deno.File; type File = Deno.File;
type Writer = Deno.Writer; type Writer = Deno.Writer;
type OpenOptions = Deno.OpenOptions; type OpenOptions = Deno.OpenOptions;
import { getLevelByName, LogLevel } from "./levels.ts"; import { getLevelByName, LevelName, LogLevels } from "./levels.ts";
import { LogRecord } from "./logger.ts"; import { LogRecord } from "./logger.ts";
import { red, yellow, blue, bold } from "../fmt/colors.ts"; import { red, yellow, blue, bold } from "../fmt/colors.ts";
import { existsSync, exists } from "../fs/exists.ts"; import { existsSync, exists } from "../fs/exists.ts";
@ -18,10 +18,10 @@ interface HandlerOptions {
export class BaseHandler { export class BaseHandler {
level: number; level: number;
levelName: string; levelName: LevelName;
formatter: string | FormatterFunction; formatter: string | FormatterFunction;
constructor(levelName: string, options: HandlerOptions = {}) { constructor(levelName: LevelName, options: HandlerOptions = {}) {
this.level = getLevelByName(levelName); this.level = getLevelByName(levelName);
this.levelName = levelName; this.levelName = levelName;
@ -62,16 +62,16 @@ export class ConsoleHandler extends BaseHandler {
let msg = super.format(logRecord); let msg = super.format(logRecord);
switch (logRecord.level) { switch (logRecord.level) {
case LogLevel.INFO: case LogLevels.INFO:
msg = blue(msg); msg = blue(msg);
break; break;
case LogLevel.WARNING: case LogLevels.WARNING:
msg = yellow(msg); msg = yellow(msg);
break; break;
case LogLevel.ERROR: case LogLevels.ERROR:
msg = red(msg); msg = red(msg);
break; break;
case LogLevel.CRITICAL: case LogLevels.CRITICAL:
msg = bold(red(msg)); msg = bold(red(msg));
break; break;
default: default:
@ -105,7 +105,7 @@ export class FileHandler extends WriterHandler {
protected _openOptions: OpenOptions; protected _openOptions: OpenOptions;
#encoder = new TextEncoder(); #encoder = new TextEncoder();
constructor(levelName: string, options: FileHandlerOptions) { constructor(levelName: LevelName, options: FileHandlerOptions) {
super(levelName, options); super(levelName, options);
this._filename = options.filename; this._filename = options.filename;
// default to append mode, write only // default to append mode, write only
@ -143,7 +143,7 @@ export class RotatingFileHandler extends FileHandler {
#maxBytes: number; #maxBytes: number;
#maxBackupCount: number; #maxBackupCount: number;
constructor(levelName: string, options: RotatingFileHandlerOptions) { constructor(levelName: LevelName, options: RotatingFileHandlerOptions) {
super(levelName, options); super(levelName, options);
this.#maxBytes = options.maxBytes; this.#maxBytes = options.maxBytes;
this.#maxBackupCount = options.maxBackupCount; this.#maxBackupCount = options.maxBackupCount;

View file

@ -1,7 +1,13 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
const { test } = Deno; const { test } = Deno;
import { assert, assertEquals, assertThrowsAsync } from "../testing/asserts.ts"; import { assert, assertEquals, assertThrowsAsync } from "../testing/asserts.ts";
import { LogLevel, getLevelName, getLevelByName } from "./levels.ts"; import {
LogLevels,
LogLevelNames,
getLevelName,
getLevelByName,
LevelName,
} from "./levels.ts";
import { BaseHandler, FileHandler, RotatingFileHandler } from "./handlers.ts"; import { BaseHandler, FileHandler, RotatingFileHandler } from "./handlers.ts";
import { LogRecord } from "./logger.ts"; import { LogRecord } from "./logger.ts";
import { existsSync } from "../fs/exists.ts"; import { existsSync } from "../fs/exists.ts";
@ -19,7 +25,7 @@ class TestHandler extends BaseHandler {
test(function simpleHandler(): void { test(function simpleHandler(): void {
const cases = new Map<number, string[]>([ const cases = new Map<number, string[]>([
[ [
LogLevel.DEBUG, LogLevels.DEBUG,
[ [
"DEBUG debug-test", "DEBUG debug-test",
"INFO info-test", "INFO info-test",
@ -29,7 +35,7 @@ test(function simpleHandler(): void {
], ],
], ],
[ [
LogLevel.INFO, LogLevels.INFO,
[ [
"INFO info-test", "INFO info-test",
"WARNING warning-test", "WARNING warning-test",
@ -38,19 +44,19 @@ test(function simpleHandler(): void {
], ],
], ],
[ [
LogLevel.WARNING, LogLevels.WARNING,
["WARNING warning-test", "ERROR error-test", "CRITICAL critical-test"], ["WARNING warning-test", "ERROR error-test", "CRITICAL critical-test"],
], ],
[LogLevel.ERROR, ["ERROR error-test", "CRITICAL critical-test"]], [LogLevels.ERROR, ["ERROR error-test", "CRITICAL critical-test"]],
[LogLevel.CRITICAL, ["CRITICAL critical-test"]], [LogLevels.CRITICAL, ["CRITICAL critical-test"]],
]); ]);
for (const [testCase, messages] of cases.entries()) { for (const [testCase, messages] of cases.entries()) {
const testLevel = getLevelName(testCase); const testLevel = getLevelName(testCase);
const handler = new TestHandler(testLevel); const handler = new TestHandler(testLevel);
for (const levelName in LogLevel) { for (const levelName of LogLevelNames) {
const level = getLevelByName(levelName); const level = getLevelByName(levelName as LevelName);
handler.handle( handler.handle(
new LogRecord(`${levelName.toLowerCase()}-test`, [], level) new LogRecord(`${levelName.toLowerCase()}-test`, [], level)
); );
@ -67,7 +73,7 @@ test(function testFormatterAsString(): void {
formatter: "test {levelName} {msg}", formatter: "test {levelName} {msg}",
}); });
handler.handle(new LogRecord("Hello, world!", [], LogLevel.DEBUG)); handler.handle(new LogRecord("Hello, world!", [], LogLevels.DEBUG));
assertEquals(handler.messages, ["test DEBUG Hello, world!"]); assertEquals(handler.messages, ["test DEBUG Hello, world!"]);
}); });
@ -78,7 +84,7 @@ test(function testFormatterAsFunction(): void {
`fn formatter ${logRecord.levelName} ${logRecord.msg}`, `fn formatter ${logRecord.levelName} ${logRecord.msg}`,
}); });
handler.handle(new LogRecord("Hello, world!", [], LogLevel.ERROR)); handler.handle(new LogRecord("Hello, world!", [], LogLevels.ERROR));
assertEquals(handler.messages, ["fn formatter ERROR Hello, world!"]); assertEquals(handler.messages, ["fn formatter ERROR Hello, world!"]);
}); });
@ -92,12 +98,12 @@ test({
}); });
await fileHandler.setup(); await fileHandler.setup();
fileHandler.handle(new LogRecord("Hello World", [], LogLevel.WARNING)); fileHandler.handle(new LogRecord("Hello World", [], LogLevels.WARNING));
await fileHandler.destroy(); await fileHandler.destroy();
const firstFileSize = (await Deno.stat(LOG_FILE)).size; const firstFileSize = (await Deno.stat(LOG_FILE)).size;
await fileHandler.setup(); await fileHandler.setup();
fileHandler.handle(new LogRecord("Hello World", [], LogLevel.WARNING)); fileHandler.handle(new LogRecord("Hello World", [], LogLevels.WARNING));
await fileHandler.destroy(); await fileHandler.destroy();
const secondFileSize = (await Deno.stat(LOG_FILE)).size; const secondFileSize = (await Deno.stat(LOG_FILE)).size;
@ -198,11 +204,11 @@ test({
}); });
await fileHandler.setup(); await fileHandler.setup();
fileHandler.handle(new LogRecord("AAA", [], LogLevel.ERROR)); // 'ERROR AAA\n' = 10 bytes fileHandler.handle(new LogRecord("AAA", [], LogLevels.ERROR)); // 'ERROR AAA\n' = 10 bytes
assertEquals((await Deno.stat(LOG_FILE)).size, 10); assertEquals((await Deno.stat(LOG_FILE)).size, 10);
fileHandler.handle(new LogRecord("AAA", [], LogLevel.ERROR)); fileHandler.handle(new LogRecord("AAA", [], LogLevels.ERROR));
assertEquals((await Deno.stat(LOG_FILE)).size, 20); assertEquals((await Deno.stat(LOG_FILE)).size, 20);
fileHandler.handle(new LogRecord("AAA", [], LogLevel.ERROR)); fileHandler.handle(new LogRecord("AAA", [], LogLevels.ERROR));
// Rollover occurred. Log file now has 1 record, rollover file has the original 2 // 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)).size, 10);
assertEquals((await Deno.stat(LOG_FILE + ".1")).size, 20); assertEquals((await Deno.stat(LOG_FILE + ".1")).size, 20);
@ -238,7 +244,7 @@ test({
mode: "a", mode: "a",
}); });
await fileHandler.setup(); await fileHandler.setup();
fileHandler.handle(new LogRecord("AAA", [], LogLevel.ERROR)); // 'ERROR AAA\n' = 10 bytes fileHandler.handle(new LogRecord("AAA", [], LogLevels.ERROR)); // 'ERROR AAA\n' = 10 bytes
await fileHandler.destroy(); await fileHandler.destroy();
const decoder = new TextDecoder(); const decoder = new TextDecoder();

View file

@ -1,26 +1,59 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
export const LogLevel: Record<string, number> = { /** Get log level numeric values through enum constants
NOTSET: 0, */
DEBUG: 10, export enum LogLevels {
INFO: 20, NOTSET = 0,
WARNING: 30, DEBUG = 10,
ERROR: 40, INFO = 20,
CRITICAL: 50, WARNING = 30,
}; ERROR = 40,
CRITICAL = 50,
const byLevel = {
[LogLevel.NOTSET]: "NOTSET",
[LogLevel.DEBUG]: "DEBUG",
[LogLevel.INFO]: "INFO",
[LogLevel.WARNING]: "WARNING",
[LogLevel.ERROR]: "ERROR",
[LogLevel.CRITICAL]: "CRITICAL",
};
export function getLevelByName(name: string): number {
return LogLevel[name];
} }
export function getLevelName(level: number): string { /** Permitted log level names */
return byLevel[level]; 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}`);
} }

View file

@ -1,5 +1,10 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import { LogLevel, getLevelByName, getLevelName } from "./levels.ts"; import {
LogLevels,
getLevelByName,
getLevelName,
LevelName,
} from "./levels.ts";
import { BaseHandler } from "./handlers.ts"; import { BaseHandler } from "./handlers.ts";
export class LogRecord { export class LogRecord {
@ -26,11 +31,11 @@ export class LogRecord {
export class Logger { export class Logger {
level: number; level: number;
levelName: string; levelName: LevelName;
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
handlers: any[]; handlers: any[];
constructor(levelName: string, handlers?: BaseHandler[]) { constructor(levelName: LevelName, handlers?: BaseHandler[]) {
this.level = getLevelByName(levelName); this.level = getLevelByName(levelName);
this.levelName = levelName; this.levelName = levelName;
@ -48,22 +53,22 @@ export class Logger {
} }
debug(msg: string, ...args: unknown[]): void { debug(msg: string, ...args: unknown[]): void {
this._log(LogLevel.DEBUG, msg, ...args); this._log(LogLevels.DEBUG, msg, ...args);
} }
info(msg: string, ...args: unknown[]): void { info(msg: string, ...args: unknown[]): void {
this._log(LogLevel.INFO, msg, ...args); this._log(LogLevels.INFO, msg, ...args);
} }
warning(msg: string, ...args: unknown[]): void { warning(msg: string, ...args: unknown[]): void {
this._log(LogLevel.WARNING, msg, ...args); this._log(LogLevels.WARNING, msg, ...args);
} }
error(msg: string, ...args: unknown[]): void { error(msg: string, ...args: unknown[]): void {
this._log(LogLevel.ERROR, msg, ...args); this._log(LogLevels.ERROR, msg, ...args);
} }
critical(msg: string, ...args: unknown[]): void { critical(msg: string, ...args: unknown[]): void {
this._log(LogLevel.CRITICAL, msg, ...args); this._log(LogLevels.CRITICAL, msg, ...args);
} }
} }

View file

@ -2,7 +2,7 @@
const { test } = Deno; const { test } = Deno;
import { assertEquals } from "../testing/asserts.ts"; import { assertEquals } from "../testing/asserts.ts";
import { LogRecord, Logger } from "./logger.ts"; import { LogRecord, Logger } from "./logger.ts";
import { LogLevel } from "./levels.ts"; import { LogLevels, LevelName } from "./levels.ts";
import { BaseHandler } from "./handlers.ts"; import { BaseHandler } from "./handlers.ts";
class TestHandler extends BaseHandler { class TestHandler extends BaseHandler {
@ -23,7 +23,7 @@ test(function simpleLogger(): void {
const handler = new TestHandler("DEBUG"); const handler = new TestHandler("DEBUG");
let logger = new Logger("DEBUG"); let logger = new Logger("DEBUG");
assertEquals(logger.level, LogLevel.DEBUG); assertEquals(logger.level, LogLevels.DEBUG);
assertEquals(logger.levelName, "DEBUG"); assertEquals(logger.levelName, "DEBUG");
assertEquals(logger.handlers, []); assertEquals(logger.handlers, []);
@ -41,14 +41,14 @@ test(function customHandler(): void {
const record = handler.records[0]; const record = handler.records[0];
assertEquals(record.msg, "foo"); assertEquals(record.msg, "foo");
assertEquals(record.args, [1, 2]); assertEquals(record.args, [1, 2]);
assertEquals(record.level, LogLevel.DEBUG); assertEquals(record.level, LogLevels.DEBUG);
assertEquals(record.levelName, "DEBUG"); assertEquals(record.levelName, "DEBUG");
assertEquals(handler.messages, ["DEBUG foo"]); assertEquals(handler.messages, ["DEBUG foo"]);
}); });
test(function logFunctions(): void { test(function logFunctions(): void {
const doLog = (level: string): TestHandler => { const doLog = (level: LevelName): TestHandler => {
const handler = new TestHandler(level); const handler = new TestHandler(level);
const logger = new Logger(level, [handler]); const logger = new Logger(level, [handler]);
logger.debug("foo"); logger.debug("foo");

View file

@ -8,9 +8,12 @@ import {
RotatingFileHandler, RotatingFileHandler,
} from "./handlers.ts"; } from "./handlers.ts";
import { assert } from "../testing/asserts.ts"; import { assert } from "../testing/asserts.ts";
import { LevelName } from "./levels.ts";
export { LogLevels } from "./levels.ts";
export class LoggerConfig { export class LoggerConfig {
level?: string; level?: LevelName;
handlers?: string[]; handlers?: string[];
} }

View file

@ -1,8 +1,13 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
const { test } = Deno; const { test } = Deno;
import { assertEquals } from "../testing/asserts.ts"; import { assertEquals, assertThrows } from "../testing/asserts.ts";
import * as log from "./mod.ts"; import * as log from "./mod.ts";
import { LogLevel } from "./levels.ts"; import {
LogLevelNames,
LevelName,
getLevelByName,
getLevelName,
} from "./levels.ts";
class TestHandler extends log.handlers.BaseHandler { class TestHandler extends log.handlers.BaseHandler {
public messages: string[] = []; public messages: string[] = [];
@ -23,13 +28,13 @@ test(async function defaultHandlers(): Promise<void> {
CRITICAL: log.critical, CRITICAL: log.critical,
}; };
for (const levelName in LogLevel) { for (const levelName of LogLevelNames) {
if (levelName === "NOTSET") { if (levelName === "NOTSET") {
continue; continue;
} }
const logger = loggers[levelName]; const logger = loggers[levelName];
const handler = new TestHandler(levelName); const handler = new TestHandler(levelName as LevelName);
await log.setup({ await log.setup({
handlers: { handlers: {
@ -37,7 +42,7 @@ test(async function defaultHandlers(): Promise<void> {
}, },
loggers: { loggers: {
default: { default: {
level: levelName, level: levelName as LevelName,
handlers: ["default"], handlers: ["default"],
}, },
}, },
@ -103,3 +108,8 @@ test(async function getLoggerUnknown(): Promise<void> {
assertEquals(logger.levelName, "NOTSET"); assertEquals(logger.levelName, "NOTSET");
assertEquals(logger.handlers, []); assertEquals(logger.handlers, []);
}); });
test(function getInvalidLoggerLevels(): void {
assertThrows(() => getLevelByName("FAKE_LOG_LEVEL" as LevelName));
assertThrows(() => getLevelName(5000));
});