summaryrefslogtreecommitdiff
path: root/log
diff options
context:
space:
mode:
authorAndy Hayden <andyhayden1@gmail.com>2019-01-12 13:50:04 -0800
committerRyan Dahl <ry@tinyclouds.org>2019-01-12 16:50:04 -0500
commitf626b04ebe320a96a220af29595c6ca84cf9a10a (patch)
treeea059a796fed0650060398a8d347ae001d6f621c /log
parent7d6a0f64f20004f89f5e2cbfcf7941f0a8dafd21 (diff)
Reorgnanize repos, examples and tests (denoland/deno_std#105)
Original: https://github.com/denoland/deno_std/commit/c5e6e015b5be19027f60c19ca86283d12f9258f3
Diffstat (limited to 'log')
-rw-r--r--log/README.md38
-rw-r--r--log/handlers.ts62
-rw-r--r--log/levels.ts34
-rw-r--r--log/logger.ts62
-rw-r--r--log/mod.ts119
-rw-r--r--log/test.ts28
6 files changed, 343 insertions, 0 deletions
diff --git a/log/README.md b/log/README.md
new file mode 100644
index 000000000..1d88cb070
--- /dev/null
+++ b/log/README.md
@@ -0,0 +1,38 @@
+# Basic usage
+
+```ts
+import * as log from "https://deno.land/x/std/logging/index.ts";
+
+// simple console logger
+log.debug("Hello world");
+log.info("Hello world");
+log.warning("Hello world");
+log.error("Hello world");
+log.critical("500 Internal server error");
+
+// configure as needed
+await log.setup({
+ handlers: {
+ console: new log.handlers.ConsoleHandler("DEBUG"),
+ file: new log.handlers.FileHandler("WARNING", "./log.txt")
+ },
+
+ loggers: {
+ default: {
+ level: "DEBUG",
+ handlers: ["console", "file"]
+ }
+ }
+});
+
+// get configured logger
+const logger = log.getLogger("default");
+logger.debug("fizz"); // <- logs to `console`, because `file` handler requires 'WARNING' level
+logger.warning("buzz"); // <- logs to both `console` and `file` handlers
+
+// 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
+```
diff --git a/log/handlers.ts b/log/handlers.ts
new file mode 100644
index 000000000..a0163e6cd
--- /dev/null
+++ b/log/handlers.ts
@@ -0,0 +1,62 @@
+import { open, File, Writer } from "deno";
+import { getLevelByName } from "./levels.ts";
+import { LogRecord } from "./logger.ts";
+
+export class BaseHandler {
+ level: number;
+ levelName: string;
+
+ constructor(levelName: string) {
+ this.level = getLevelByName(levelName);
+ this.levelName = levelName;
+ }
+
+ handle(logRecord: LogRecord) {
+ if (this.level > logRecord.level) return;
+
+ // TODO: implement formatter
+ const msg = `${logRecord.levelName} ${logRecord.msg}`;
+
+ return this.log(msg);
+ }
+
+ log(msg: string) {}
+ async setup() {}
+ async destroy() {}
+}
+
+export class ConsoleHandler extends BaseHandler {
+ log(msg: string) {
+ console.log(msg);
+ }
+}
+
+export abstract class WriterHandler extends BaseHandler {
+ protected _writer: Writer;
+
+ log(msg: string) {
+ const encoder = new TextEncoder();
+ // promise is intentionally not awaited
+ this._writer.write(encoder.encode(msg + "\n"));
+ }
+}
+
+export class FileHandler extends WriterHandler {
+ private _file: File;
+ private _filename: string;
+
+ constructor(levelName: string, filename: string) {
+ super(levelName);
+ this._filename = filename;
+ }
+
+ async setup() {
+ // open file in append mode - write only
+ this._file = await open(this._filename, "a");
+ this._writer = this._file;
+ }
+
+ async destroy() {
+ await this._file.close();
+ }
+}
diff --git a/log/levels.ts b/log/levels.ts
new file mode 100644
index 000000000..52d28aea5
--- /dev/null
+++ b/log/levels.ts
@@ -0,0 +1,34 @@
+export const LogLevel = {
+ NOTSET: 0,
+ DEBUG: 10,
+ INFO: 20,
+ WARNING: 30,
+ ERROR: 40,
+ CRITICAL: 50
+};
+
+const byName = {
+ NOTSET: LogLevel.NOTSET,
+ DEBUG: LogLevel.DEBUG,
+ INFO: LogLevel.INFO,
+ WARNING: LogLevel.WARNING,
+ ERROR: LogLevel.ERROR,
+ CRITICAL: LogLevel.CRITICAL
+};
+
+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 byName[name];
+}
+
+export function getLevelName(level: number): string {
+ return byLevel[level];
+}
diff --git a/log/logger.ts b/log/logger.ts
new file mode 100644
index 000000000..9f34f9c32
--- /dev/null
+++ b/log/logger.ts
@@ -0,0 +1,62 @@
+import { LogLevel, getLevelByName, getLevelName } from "./levels.ts";
+import { BaseHandler } from "./handlers.ts";
+
+export interface LogRecord {
+ msg: string;
+ args: any[];
+ datetime: Date;
+ level: number;
+ levelName: string;
+}
+
+export class Logger {
+ level: number;
+ levelName: string;
+ handlers: any[];
+
+ constructor(levelName: string, handlers?: BaseHandler[]) {
+ this.level = getLevelByName(levelName);
+ this.levelName = levelName;
+
+ this.handlers = handlers || [];
+ }
+
+ _log(level: number, msg: string, ...args: any[]) {
+ if (this.level > level) return;
+
+ // TODO: it'd be a good idea to make it immutable, so
+ // no handler mangles it by mistake
+ // TODO: iterpolate msg with values
+ const record: LogRecord = {
+ msg: msg,
+ args: args,
+ datetime: new Date(),
+ level: level,
+ levelName: getLevelName(level)
+ };
+
+ this.handlers.forEach(handler => {
+ handler.handle(record);
+ });
+ }
+
+ debug(msg: string, ...args: any[]) {
+ return this._log(LogLevel.DEBUG, msg, ...args);
+ }
+
+ info(msg: string, ...args: any[]) {
+ return this._log(LogLevel.INFO, msg, ...args);
+ }
+
+ warning(msg: string, ...args: any[]) {
+ return this._log(LogLevel.WARNING, msg, ...args);
+ }
+
+ error(msg: string, ...args: any[]) {
+ return this._log(LogLevel.ERROR, msg, ...args);
+ }
+
+ critical(msg: string, ...args: any[]) {
+ return this._log(LogLevel.CRITICAL, msg, ...args);
+ }
+}
diff --git a/log/mod.ts b/log/mod.ts
new file mode 100644
index 000000000..e8c762ac6
--- /dev/null
+++ b/log/mod.ts
@@ -0,0 +1,119 @@
+import { Logger } from "./logger.ts";
+import {
+ BaseHandler,
+ ConsoleHandler,
+ WriterHandler,
+ FileHandler
+} from "./handlers.ts";
+
+export class LoggerConfig {
+ level?: string;
+ handlers?: string[];
+}
+
+export interface LogConfig {
+ handlers?: {
+ [name: string]: BaseHandler;
+ };
+ loggers?: {
+ [name: string]: LoggerConfig;
+ };
+}
+
+const DEFAULT_LEVEL = "INFO";
+const DEFAULT_NAME = "";
+const DEFAULT_CONFIG: LogConfig = {
+ handlers: {},
+
+ loggers: {
+ "": {
+ level: "INFO",
+ handlers: [""]
+ }
+ }
+};
+
+const defaultHandler = new ConsoleHandler("INFO");
+const defaultLogger = new Logger("INFO", [defaultHandler]);
+
+const state = {
+ defaultHandler,
+ defaultLogger,
+ handlers: new Map(),
+ loggers: new Map(),
+ config: DEFAULT_CONFIG
+};
+
+export const handlers = {
+ BaseHandler,
+ ConsoleHandler,
+ WriterHandler,
+ FileHandler
+};
+
+export const debug = (msg: string, ...args: any[]) =>
+ defaultLogger.debug(msg, ...args);
+export const info = (msg: string, ...args: any[]) =>
+ defaultLogger.info(msg, ...args);
+export const warning = (msg: string, ...args: any[]) =>
+ defaultLogger.warning(msg, ...args);
+export const error = (msg: string, ...args: any[]) =>
+ defaultLogger.error(msg, ...args);
+export const critical = (msg: string, ...args: any[]) =>
+ defaultLogger.critical(msg, ...args);
+
+export function getLogger(name?: string) {
+ if (!name) {
+ return defaultLogger;
+ }
+
+ if (!state.loggers.has(name)) {
+ const logger = new Logger("NOTSET", []);
+ state.loggers.set(name, logger);
+ return logger;
+ }
+
+ return state.loggers.get(name);
+}
+
+export async function setup(config: LogConfig) {
+ state.config = config;
+
+ // tear down existing handlers
+ state.handlers.forEach(handler => {
+ 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 = [];
+
+ handlerNames.forEach(handlerName => {
+ if (state.handlers.has(handlerName)) {
+ handlers.push(state.handlers.get(handlerName));
+ }
+ });
+
+ const levelName = loggerConfig.level || DEFAULT_LEVEL;
+ const logger = new Logger(levelName, handlers);
+ state.loggers.set(loggerName, logger);
+ }
+}
+
+setup(DEFAULT_CONFIG);
diff --git a/log/test.ts b/log/test.ts
new file mode 100644
index 000000000..fdc994eb7
--- /dev/null
+++ b/log/test.ts
@@ -0,0 +1,28 @@
+import { remove, open, readAll } from "deno";
+import { assertEqual, test } from "../testing/mod.ts";
+import * as log from "./mod.ts";
+import { FileHandler } from "./handlers.ts";
+
+// TODO: establish something more sophisticated
+let testOutput = "";
+
+class TestHandler extends log.handlers.BaseHandler {
+ constructor(levelName: string) {
+ super(levelName);
+ }
+
+ log(msg: string) {
+ testOutput += `${msg}\n`;
+ }
+}
+
+test(function testDefaultlogMethods() {
+ log.debug("Foobar");
+ log.info("Foobar");
+ log.warning("Foobar");
+ log.error("Foobar");
+ log.critical("Foobar");
+
+ const logger = log.getLogger("");
+ console.log(logger);
+});