1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-22 23:34:47 -05:00

feat(std/log): expose logger name to LogRecord (#6316)

This commit is contained in:
Chris Knight 2020-06-18 11:50:18 +01:00 committed by GitHub
parent 78a311aa5f
commit 940f8e8433
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 243 additions and 35 deletions

View file

@ -79,6 +79,7 @@ class LogRecord {
readonly datetime: Date;
readonly level: number;
readonly levelName: string;
readonly loggerName: string;
}
```
@ -208,13 +209,21 @@ await log.setup({
return msg;
}
}),
anotherFmt: new log.handlers.ConsoleHandler("DEBUG", {
formatter: "[{loggerName}] - {levelName} {msg}"
}),
},
loggers: {
default: {
level: "DEBUG",
handlers: ["stringFmt", "functionFmt"],
level: "DEBUG",
handlers: ["stringFmt", "functionFmt"],
},
dataLogger: {
level: "INFO",
handlers: ["anotherFmt"],
}
}
})
@ -223,6 +232,11 @@ 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

View file

@ -62,7 +62,12 @@ Deno.test("simpleHandler", function (): void {
for (const levelName of LogLevelNames) {
const level = getLevelByName(levelName as LevelName);
handler.handle(
new LogRecord(`${levelName.toLowerCase()}-test`, [], level)
new LogRecord({
msg: `${levelName.toLowerCase()}-test`,
args: [],
level: level,
loggerName: "default",
})
);
}
@ -77,7 +82,14 @@ Deno.test("testFormatterAsString", function (): void {
formatter: "test {levelName} {msg}",
});
handler.handle(new LogRecord("Hello, world!", [], LogLevels.DEBUG));
handler.handle(
new LogRecord({
msg: "Hello, world!",
args: [],
level: LogLevels.DEBUG,
loggerName: "default",
})
);
assertEquals(handler.messages, ["test DEBUG Hello, world!"]);
});
@ -88,7 +100,14 @@ Deno.test("testFormatterAsFunction", function (): void {
`fn formatter ${logRecord.levelName} ${logRecord.msg}`,
});
handler.handle(new LogRecord("Hello, world!", [], LogLevels.ERROR));
handler.handle(
new LogRecord({
msg: "Hello, world!",
args: [],
level: LogLevels.ERROR,
loggerName: "default",
})
);
assertEquals(handler.messages, ["fn formatter ERROR Hello, world!"]);
});
@ -102,12 +121,26 @@ Deno.test({
});
await fileHandler.setup();
fileHandler.handle(new LogRecord("Hello World", [], LogLevels.WARNING));
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("Hello World", [], LogLevels.WARNING));
fileHandler.handle(
new LogRecord({
msg: "Hello World",
args: [],
level: LogLevels.WARNING,
loggerName: "default",
})
);
await fileHandler.destroy();
const secondFileSize = (await Deno.stat(LOG_FILE)).size;
@ -210,13 +243,34 @@ Deno.test({
});
await fileHandler.setup();
fileHandler.handle(new LogRecord("AAA", [], LogLevels.ERROR)); // 'ERROR AAA\n' = 10 bytes
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("AAA", [], LogLevels.ERROR));
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("AAA", [], LogLevels.ERROR));
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);
@ -239,9 +293,30 @@ Deno.test({
});
await fileHandler.setup();
fileHandler.handle(new LogRecord("AAA", [], LogLevels.ERROR)); // 'ERROR AAA\n' = 10 bytes
fileHandler.handle(new LogRecord("AAA", [], LogLevels.ERROR));
fileHandler.handle(new LogRecord("AAA", [], LogLevels.ERROR));
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();
@ -277,7 +352,14 @@ Deno.test({
mode: "a",
});
await fileHandler.setup();
fileHandler.handle(new LogRecord("AAA", [], LogLevels.ERROR)); // 'ERROR AAA\n' = 10 bytes
fileHandler.handle(
new LogRecord({
msg: "AAA",
args: [],
level: LogLevels.ERROR,
loggerName: "default",
})
); // 'ERROR AAA\n' = 10 bytes
await fileHandler.destroy();
const decoder = new TextDecoder();
@ -349,7 +431,14 @@ Deno.test({
mode: "w",
});
await fileHandler.setup();
fileHandler.handle(new LogRecord("AAA", [], LogLevels.ERROR)); // 'ERROR AAA\n' = 10 bytes
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"));
@ -402,13 +491,27 @@ Deno.test({
});
await fileHandler.setup();
fileHandler.handle(new LogRecord("AAA", [], LogLevels.ERROR));
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("AAA", [], LogLevels.CRITICAL));
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;

View file

@ -7,19 +7,28 @@ import {
} from "./levels.ts";
import { BaseHandler } from "./handlers.ts";
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(msg: string, args: unknown[], level: number) {
this.msg = msg;
this.#args = [...args];
this.level = level;
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(level);
this.levelName = getLevelName(options.level);
}
get args(): unknown[] {
return [...this.#args];
@ -29,17 +38,27 @@ export class LogRecord {
}
}
export interface LoggerOptions {
handlers?: BaseHandler[];
}
export class Logger {
level: number;
levelName: LevelName;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
handlers: any[];
loggerName: string;
constructor(levelName: LevelName, handlers?: BaseHandler[]) {
constructor(
loggerName: string,
levelName: LevelName,
options: LoggerOptions = {}
) {
this.loggerName = loggerName;
this.level = getLevelByName(levelName);
this.levelName = levelName;
this.handlers = handlers || [];
this.handlers = options.handlers || [];
}
/** If the level of the logger is greater than the level to log, then nothing
@ -66,7 +85,12 @@ export class Logger {
} else {
logMessage = this.asString(msg);
}
const record: LogRecord = new LogRecord(logMessage, args, level);
const record: LogRecord = new LogRecord({
msg: logMessage,
args: args,
level: level,
loggerName: this.loggerName,
});
this.handlers.forEach((handler): void => {
handler.handle(record);

View file

@ -18,22 +18,39 @@ class TestHandler extends BaseHandler {
}
}
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("DEBUG");
let logger = new Logger("default", "DEBUG");
assertEquals(logger.level, LogLevels.DEBUG);
assertEquals(logger.levelName, "DEBUG");
assertEquals(logger.handlers, []);
logger = new Logger("DEBUG", [handler]);
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("DEBUG", [handler]);
const logger = new Logger("default", "DEBUG", { handlers: [handler] });
const inlineData: string = logger.debug("foo", 1, 2);
@ -50,7 +67,7 @@ Deno.test("customHandler", function (): void {
Deno.test("logFunctions", function (): void {
const doLog = (level: LevelName): TestHandler => {
const handler = new TestHandler(level);
const logger = new Logger(level, [handler]);
const logger = new Logger("default", level, { handlers: [handler] });
const debugData = logger.debug("foo");
const infoData = logger.info("bar");
const warningData = logger.warning("baz");
@ -101,7 +118,7 @@ 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("ERROR", [handler]);
const logger = new Logger("default", "ERROR", { handlers: [handler] });
let called = false;
const expensiveFunction = (): string => {
@ -121,7 +138,7 @@ Deno.test(
Deno.test("String resolver fn resolves as expected", function (): void {
const handler = new TestHandler("ERROR");
const logger = new Logger("ERROR", [handler]);
const logger = new Logger("default", "ERROR", { handlers: [handler] });
const expensiveFunction = (x: number): string => {
return "expensive function result " + x;
};
@ -136,7 +153,7 @@ 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("DEBUG", [handler]);
const logger = new Logger("default", "DEBUG", { handlers: [handler] });
const sym = Symbol();
const syma = Symbol("a");
const fn = (): string => {

View file

@ -65,7 +65,7 @@ export function getLogger(name?: string): Logger {
}
const result = state.loggers.get(name);
if (!result) {
const logger = new Logger("NOTSET", []);
const logger = new Logger(name, "NOTSET", { handlers: [] });
state.loggers.set(name, logger);
return logger;
}
@ -191,7 +191,7 @@ export async function setup(config: LogConfig): Promise<void> {
});
const levelName = loggerConfig.level || DEFAULT_LEVEL;
const logger = new Logger(levelName, handlers);
const logger = new Logger(loggerName, levelName, { handlers: handlers });
state.loggers.set(loggerName, logger);
}
}

View file

@ -1,7 +1,24 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import { assert, assertEquals } from "../testing/asserts.ts";
import { getLogger, debug, info, warning, error, critical } from "./mod.ts";
import {
getLogger,
debug,
info,
warning,
error,
critical,
setup,
} from "./mod.ts";
import { Logger } from "./logger.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 {
@ -39,3 +56,36 @@ Deno.test("default loggers work as expected", function (): void {
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");
},
});