Skip to content

Commit

Permalink
Improve logging module (denoland#51)
Browse files Browse the repository at this point in the history
  • Loading branch information
bartlomieju authored and ry committed Jan 2, 2019
1 parent 77831c3 commit 439885c
Show file tree
Hide file tree
Showing 9 changed files with 276 additions and 170 deletions.
2 changes: 1 addition & 1 deletion azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@ jobs:
# steps:
# - powershell: iex (iwr https://deno.land/x/install/install.ps1)
# - script: echo '##vso[task.prependpath]C:\Users\VssAdministrator\.deno\bin\'
# - script: 'C:\Users\VssAdministrator\.deno\bin\deno.exe test.ts --allow-run --allow-net'
# - script: 'C:\Users\VssAdministrator\.deno\bin\deno.exe test.ts --allow-run --allow-net --allow-write'
45 changes: 34 additions & 11 deletions logging/README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,38 @@
# Logging module for Deno
# Basic usage

Very much work in progress. Contributions welcome.
```ts
import * as log from "https://deno.land/x/std/logging/index.ts";

This library is heavily inspired by Python's
[logging](https://docs.python.org/3/library/logging.html#logging.Logger.log)
module, altough it's not planned to be a direct port. Having separate loggers,
handlers, formatters and filters gives developer very granular control over
logging which is most desirable for server side software.
// 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");

Todo:
// configure as needed
await log.setup({
handlers: {
console: new log.handlers.ConsoleHandler("DEBUG"),
file: new log.handlers.FileHandler("WARNING", "./log.txt"),
},

- [ ] implement formatters
- [ ] implement `FileHandler`
- [ ] tests
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
```
18 changes: 0 additions & 18 deletions logging/handler.ts

This file was deleted.

65 changes: 65 additions & 0 deletions logging/handlers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
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();
}
}
26 changes: 0 additions & 26 deletions logging/handlers/console.ts

This file was deleted.

128 changes: 69 additions & 59 deletions logging/index.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
import { Logger } from "./logger.ts";
import { BaseHandler } from "./handler.ts";
import { ConsoleHandler } from "./handlers/console.ts";

export interface HandlerConfig {
// TODO: replace with type describing class derived from BaseHandler
class: typeof BaseHandler;
level?: string;
}
import { BaseHandler, ConsoleHandler, WriterHandler, FileHandler } from "./handlers.ts";

export class LoggerConfig {
level?: string;
handlers?: string[];
}

export interface LoggingConfig {
export interface LogConfig {
handlers?: {
[name: string]: HandlerConfig;
[name: string]: BaseHandler;
};
loggers?: {
[name: string]: LoggerConfig;
Expand All @@ -24,78 +17,95 @@ export interface LoggingConfig {

const DEFAULT_LEVEL = "INFO";
const DEFAULT_NAME = "";
const DEFAULT_CONFIG: LoggingConfig = {
const DEFAULT_CONFIG: LogConfig = {
handlers: {
[DEFAULT_NAME]: {
level: DEFAULT_LEVEL,
class: ConsoleHandler
}

},

loggers: {
[DEFAULT_NAME]: {
level: DEFAULT_LEVEL,
handlers: [DEFAULT_NAME]
"": {
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
config: DEFAULT_CONFIG,
};

function createNewHandler(name: string) {
let handlerConfig = state.config.handlers[name];

if (!handlerConfig) {
handlerConfig = state.config.handlers[DEFAULT_NAME];
}

const constructor = handlerConfig.class;
console.log(constructor);
const handler = new constructor(handlerConfig.level);
return handler;
}

function createNewLogger(name: string) {
let loggerConfig = state.config.loggers[name];

if (!loggerConfig) {
loggerConfig = state.config.loggers[DEFAULT_NAME];
}

const handlers = (loggerConfig.handlers || []).map(createNewHandler);
const levelName = loggerConfig.level || DEFAULT_LEVEL;
return new Logger(levelName, handlers);
}

export const handlers = {
BaseHandler: BaseHandler,
ConsoleHandler: ConsoleHandler
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) {
name = DEFAULT_NAME;
return defaultLogger;
}

if (!state.loggers.has(name)) {
return createNewLogger(name);
const logger = new Logger("NOTSET", []);
state.loggers.set(name, logger);
return logger;
}

return state.loggers.get(name);
}

export function setup(config: LoggingConfig) {
state.config = {
handlers: {
...DEFAULT_CONFIG.handlers,
...config.handlers!
},
loggers: {
...DEFAULT_CONFIG.loggers,
...config.loggers!
}
};
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);
9 changes: 6 additions & 3 deletions logging/levels.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export const LogLevel = {
NOTSET: 0,
DEBUG: 10,
INFO: 20,
WARNING: 30,
Expand All @@ -7,25 +8,27 @@ export const LogLevel = {
};

const byName = {
NOTSET: LogLevel.NOTSET,
DEBUG: LogLevel.DEBUG,
INFO: LogLevel.INFO,
WARNING: LogLevel.WARNING,
ERROR: LogLevel.ERROR,
CRITICAL: LogLevel.DEBUG
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) {
export function getLevelByName(name: string): number {
return byName[name];
}

export function getLevelName(level) {
export function getLevelName(level: number): string {
return byLevel[level];
}
Loading

0 comments on commit 439885c

Please sign in to comment.