diff --git a/demo/Demo.ts b/demo/Demo.ts index abdc009..71d4d70 100644 --- a/demo/Demo.ts +++ b/demo/Demo.ts @@ -14,23 +14,23 @@ class Demo { this.init(); //Messages - document.getElementById("trace").addEventListener("click", ()=>{ - this.logger.trace("Trace message"); + document.getElementById("trace").addEventListener("click", (e)=>{ + this.logger.trace("Trace message", e, 100); }); - document.getElementById("debug").addEventListener("click", ()=>{ - this.logger.debug("Debug message"); + document.getElementById("debug").addEventListener("click", (e)=>{ + this.logger.debug("Debug message", e, 100); }); - document.getElementById("info").addEventListener("click", ()=>{ - this.logger.info("Info message"); + document.getElementById("info").addEventListener("click", (e)=>{ + this.logger.info("Info message", e, 100); }); - document.getElementById("warn").addEventListener("click", ()=>{ - this.logger.warn("Warn message"); + document.getElementById("warn").addEventListener("click", (e)=>{ + this.logger.warn("Warn message", e, 100); }); - document.getElementById("error").addEventListener("click", ()=>{ - this.logger.error("Error message"); + document.getElementById("error").addEventListener("click", (e)=>{ + this.logger.error("Error message", e, 100); }); - document.getElementById("fatal").addEventListener("click", ()=>{ - this.logger.fatal("Fatal message"); + document.getElementById("fatal").addEventListener("click", (e)=>{ + this.logger.fatal("Fatal message", e, 100); }); //Level diff --git a/src/IAppender.ts b/src/IAppender.ts index e804d3b..0ef2998 100644 --- a/src/IAppender.ts +++ b/src/IAppender.ts @@ -1,9 +1,10 @@ -import {ILayout} from "./ILayout"; +import {ILayout, IDataFormatFunction} from "./ILayout"; import {LogEntry} from "./LogEntry"; import {ILayoutFunction} from "./ILayout"; + export interface IAppender { setLayout(layout: ILayout); - setLayoutFunction(layout: ILayoutFunction); + setLayoutFunction(layout: ILayoutFunction, format_data: IDataFormatFunction); append(entry: LogEntry); clear(); } diff --git a/src/ILayout.ts b/src/ILayout.ts index e31b39f..0c12a8d 100644 --- a/src/ILayout.ts +++ b/src/ILayout.ts @@ -1,9 +1,15 @@ import {LogEntry} from "./LogEntry"; export interface ILayout { - format(entry: LogEntry): string; + format(entry: LogEntry, include_data: boolean): string; + + formatData(entry: LogEntry): string; } export interface ILayoutFunction { + (entry: LogEntry, include_data: boolean): string; +} + +export interface IDataFormatFunction { (entry: LogEntry): string; } diff --git a/src/LogEntry.ts b/src/LogEntry.ts index eede5c1..be0caf7 100644 --- a/src/LogEntry.ts +++ b/src/LogEntry.ts @@ -2,7 +2,10 @@ import {LogLevel} from "./LogLevel"; export interface LogEntry { level: LogLevel; + object: any; + deep: number; time: Date; message: string; tag: string; + stack: any; } diff --git a/src/Logger.ts b/src/Logger.ts index 017baf8..2da6ab7 100644 --- a/src/Logger.ts +++ b/src/Logger.ts @@ -1,32 +1,30 @@ -import {IAppender} from "./IAppender"; import LoggerConfig from "./LoggerConfig"; import {LogLevel} from "./LogLevel"; -import {stringify} from "./Utils"; export default class Logger { constructor(private tag?: string) { } public log(message: string, object?: any, deep?: number) { - this.doLog(LogLevel.INFO, message, object, deep); + this.doLog(this.getStack(), LogLevel.INFO, message, object, deep); } public info(message: string, object?: any, deep?: number) { - this.doLog(LogLevel.INFO, message, object, deep); + this.doLog(this.getStack(), LogLevel.INFO, message, object, deep); } public fatal(message: string, object?: any, deep?: number) { - this.doLog(LogLevel.FATAL, message, object, deep); + this.doLog(this.getStack(), LogLevel.FATAL, message, object, deep); } public error(message: string, object?: any, deep?: number) { - this.doLog(LogLevel.ERROR, message, object, deep); + this.doLog(this.getStack(), LogLevel.ERROR, message, object, deep); } public debug(message: string, object?: any, deep?: number) { - this.doLog(LogLevel.DEBUG, message, object, deep); + this.doLog(this.getStack(), LogLevel.DEBUG, message, object, deep); } public warn(message: string, object?: any, deep?: number) { - this.doLog(LogLevel.WARN, message, object, deep); + this.doLog(this.getStack(), LogLevel.WARN, message, object, deep); } public trace(message: string, object?: any, deep?: number) { - this.doLog(LogLevel.TRACE, message, object, deep); + this.doLog(this.getStack(), LogLevel.TRACE, message, object, deep); } public static setConfig(config: LoggerConfig) { @@ -46,18 +44,28 @@ export default class Logger { private static loggers: {[tag: string]: Logger} = {}; - private doLog(level: LogLevel, message: string, object?: any, deep?: number) { - if (typeof object !== "undefined") { - message += ' ' + stringify(object, deep || 1); + private getStack() { + if(Logger.config.captureStack()) { + var err = new Error(); + return err["stack"] || err["stacktrace"]; } + + + return null; + } + + private doLog(stack: any, level: LogLevel, message: string, object?: any, deep?: number) { if (level >= Logger.config.getLevel() && Logger.config.hasTag(this.tag)) { for (var i in Logger.config.getAppenders()) { var appender = Logger.config.getAppenders()[i]; appender.append({ message: message, + object: object, + deep: deep || 1, time: new Date(), tag: this.tag, - level: level + level: level, + stack: stack }); } } diff --git a/src/LoggerConfig.ts b/src/LoggerConfig.ts index 8da6948..8deb153 100644 --- a/src/LoggerConfig.ts +++ b/src/LoggerConfig.ts @@ -9,7 +9,7 @@ import {HTMLLayoutColorTheme} from "./layouts/HTMLLayout"; import {HTMLLayoutColors} from "./layouts/HTMLLayout"; export default class LoggerConfig { - constructor(appender?: IAppender, private level: LogLevel = LogLevel.INFO, private tags?: string[]) { + constructor(appender?: IAppender, private level: LogLevel = LogLevel.INFO, private capture_stack: boolean = true, private tags?: string[]) { if (appender) { this.addAppender(appender); } @@ -30,6 +30,10 @@ export default class LoggerConfig { return this.level; } + public captureStack():boolean { + return this.capture_stack; + } + public hasTag(tag: string) { if (!this.tags || this.tags.length === 0) return true; @@ -46,7 +50,7 @@ export default class LoggerConfig { private appenders: IAppender[] = []; public static createFromJson(json: ConfigJson): LoggerConfig { - let config = new LoggerConfig(null, LogLevel[json.level], json.tags); + let config = new LoggerConfig(null, LogLevel[json.level], json.capture_stack, json.tags); for (let layout_json of json.layouts) { let layout: ILayout; @@ -92,6 +96,7 @@ export default class LoggerConfig { export interface ConfigJson { layouts: ConfigJsonLayout[]; + capture_stack:boolean; level: "ALL" | "TRACE" | "DEBUG" | "INFO" | "WARN" | "ERROR" | "FATAL" | "OFF"; tags: string[]; } diff --git a/src/appenders/BaseAppender.ts b/src/appenders/BaseAppender.ts index 1431395..4ba6da1 100644 --- a/src/appenders/BaseAppender.ts +++ b/src/appenders/BaseAppender.ts @@ -1,15 +1,16 @@ +import {ILayout, ILayoutFunction, IDataFormatFunction} from "../ILayout"; -import {ILayout} from "../ILayout"; -import {ILayoutFunction} from "../ILayout"; export default class BaseAppender { - setLayout(layout: ILayout) { + setLayout(layout:ILayout) { this.layout = layout; } - setLayoutFunction(layout: ILayoutFunction) { + + setLayoutFunction(layout:ILayoutFunction, format_data: IDataFormatFunction) { this.layout = { - format: layout + format: layout, + formatData: format_data } } - protected layout: ILayout; + protected layout:ILayout; } diff --git a/src/appenders/ConsoleAppender.ts b/src/appenders/ConsoleAppender.ts index e7a8b13..1871fcc 100644 --- a/src/appenders/ConsoleAppender.ts +++ b/src/appenders/ConsoleAppender.ts @@ -1,14 +1,53 @@ import {IAppender} from "../IAppender"; -import {ILayout} from "../ILayout"; import {LogEntry} from "../LogEntry"; import BaseAppender from "./BaseAppender"; export default class ConsoleAppender extends BaseAppender implements IAppender { append(entry:LogEntry) { - console.log(this.layout.format(entry)); + if (!console || !console.log) { + return; + } + + if (!this.isGroupingAvailable()) { + this.simpleAppend(entry); + } else { + this.complexAppend(entry); + } } clear() { console.clear(); } + + protected simpleAppend(entry:LogEntry) { + console.log(this.layout.format(entry, true)); + } + + protected complexAppend(entry:LogEntry) { + + this.startGroup(this.layout.format(entry, false)); + + if (entry.object) { + this.startGroup("Additional Data"); + + console.log(this.layout.formatData(entry)); + + console.groupEnd(); + } + + console.log(entry.stack); + console.groupEnd(); + } + + protected startGroup(msg: any) { + if(console.groupCollapsed) { + console.groupCollapsed(msg); + } else { + console.group(msg); + } + } + + protected isGroupingAvailable(): boolean { + return !!console.groupCollapsed || !!console.group + } } diff --git a/src/appenders/DOMAppender.ts b/src/appenders/DOMAppender.ts index 94ba370..0abf27b 100644 --- a/src/appenders/DOMAppender.ts +++ b/src/appenders/DOMAppender.ts @@ -12,7 +12,7 @@ export default class DOMAppender extends BaseAppender implements IAppender { append(entry:LogEntry) { if (!this.el) return; - var log = this.layout.format(entry); + var log = this.layout.format(entry, true); this.buffer.push((this.escape_html ? utils.escapeHtml(log) : log)); if (this.buffer_size && this.buffer.length > this.buffer_size) { this.buffer.shift(); diff --git a/src/layouts/BasicLayout.ts b/src/layouts/BasicLayout.ts index 0897872..be615f7 100644 --- a/src/layouts/BasicLayout.ts +++ b/src/layouts/BasicLayout.ts @@ -1,17 +1,26 @@ import {ILayout} from "../ILayout"; import {LogEntry} from "../LogEntry"; -import {logLevelToString, LogLevel} from "../LogLevel"; +import {logLevelToString} from "../LogLevel"; +import {stringify} from "../Utils"; /** * Simple layout, that formats logs as * "{time} {level} [{tag}] - {message}" */ export default class BasicLayout implements ILayout { - format(entry:LogEntry):string { - return this.formatDate(entry.time) + ' ' + logLevelToString(entry.level) + ' [' + entry.tag + '] - ' + entry.message; + format(entry:LogEntry, include_data: boolean):string { + return `${this.formatDate(entry.time)} ${logLevelToString(entry.level)} [${entry.tag}] - ${entry.message}${include_data? " " + this.formatData(entry): ''}`; } - private formatDate(date: Date): string { + formatData(entry:LogEntry):string { + if (typeof entry.object !== "undefined") { + return stringify(entry.object, entry.deep || 1); + } + + return ''; + } + + private formatDate(date:Date):string { function pad(number) { if (number < 10) { return '0' + number; diff --git a/src/layouts/HTMLLayout.ts b/src/layouts/HTMLLayout.ts index 036b916..e1666b9 100644 --- a/src/layouts/HTMLLayout.ts +++ b/src/layouts/HTMLLayout.ts @@ -1,6 +1,7 @@ import {ILayout} from "../ILayout"; import {LogEntry} from "../LogEntry"; import {LogLevel} from "../LogLevel"; +import {stringify} from "../Utils"; export interface HTMLLayoutColors { tag: string; @@ -16,6 +17,7 @@ export enum HTMLLayoutColorTheme { } export default class HTMLLayout implements ILayout { + constructor(colors_theme?: HTMLLayoutColorTheme | HTMLLayoutColors) { if (colors_theme === HTMLLayoutColorTheme.LIGHT) { this.colors = { @@ -42,11 +44,40 @@ export default class HTMLLayout implements ILayout { this.colors = colors_theme; } } - format(entry:LogEntry):string { - return '' + this.formatDate(entry.time) + ' ' + + format(entry:LogEntry, include_data: boolean):string { + let res = '' + this.formatDate(entry.time) + ' ' + '' + LogLevel[entry.level] + ' ' + '[' + entry.tag + '] ' + - '' + entry.message + ''; + '' + entry.message + ''; + + if(include_data) { + res += '
' + this.formatData(entry) + '
'; + } + + if(entry.stack) { + let formatted = this.formatStack(entry.stack); + + if(formatted) { + res += '
' + formatted + '
'; + } + } + + return res; + } + + formatData(entry:LogEntry):string { + if (typeof entry.object !== "undefined") { + return stringify(entry.object, entry.deep || 1); + } + } + + private formatStack(stack: any):string { + + if(stack && stack.split) { + return stack.split('\n').join('
'); + } + + return null; } private getTimeStyle() { diff --git a/test/appenders/ConsoleAppenderTest.ts b/test/appenders/ConsoleAppenderTest.ts index fb71b35..db17ec1 100644 --- a/test/appenders/ConsoleAppenderTest.ts +++ b/test/appenders/ConsoleAppenderTest.ts @@ -19,7 +19,8 @@ describe('ConsoleAppender', ()=>{ var log = helpers.createLogEntry('test'); appender.setLayout({ - format: (d)=>(data=d, '') + format: (d, i)=>(data=d, ''), + formatData: (d)=> "" }); appender.append(log); @@ -30,7 +31,7 @@ describe('ConsoleAppender', ()=>{ var data; var log = helpers.createLogEntry('test'); - appender.setLayoutFunction((d)=>(data=d, '')); + appender.setLayoutFunction((d, i)=>(data=d, ''), (d) => ""); appender.append(log); @@ -39,13 +40,13 @@ describe('ConsoleAppender', ()=>{ }); it('logs using window.console', ()=>{ - appender.setLayoutFunction((d)=>d.message); + appender.setLayoutFunction((d,i)=>d.message, (d)=> ""); appender.append(helpers.createLogEntry("1")); appender.append(helpers.createLogEntry("2")); appender.append(helpers.createLogEntry("3")); - expect(console.getLogs()).toEqual(["1", "2", "3"]); + expect(console.getTitles()).toEqual(["1", "2", "3"]); }); it('can clear console', ()=>{ @@ -60,6 +61,8 @@ describe('ConsoleAppender', ()=>{ class MockConsole implements Console { private logs: string[] = []; + private groups: string[] = []; + public log(message: string) { this.logs.push(message); } @@ -69,6 +72,11 @@ class MockConsole implements Console { getLogs() { return this.logs; } + + getTitles() { + return this.groups; + } + assert(test:boolean, message:string, optionalParams:any):void { } @@ -91,6 +99,7 @@ class MockConsole implements Console { } groupCollapsed(groupTitle:string):void { + this.groups.push(groupTitle); } groupEnd():void { diff --git a/test/appenders/DOMAppender.ts b/test/appenders/DOMAppender.ts index ae1d028..04f72b0 100644 --- a/test/appenders/DOMAppender.ts +++ b/test/appenders/DOMAppender.ts @@ -23,7 +23,8 @@ describe('DOMAppender', ()=>{ var log = helpers.createLogEntry('test'); appender.setLayout({ - format: (d)=>(data=d, '') + format: (d,i)=>(data=d, ''), + formatData: (d) => '' }); appender.append(log); @@ -34,7 +35,7 @@ describe('DOMAppender', ()=>{ var data; var log = helpers.createLogEntry('test'); - appender.setLayoutFunction((d)=>(data=d, '')); + appender.setLayoutFunction((d, i)=>(data=d, ''), (d) => ""); appender.append(log); @@ -43,7 +44,7 @@ describe('DOMAppender', ()=>{ }); it('appends to dom', ()=>{ - appender.setLayoutFunction(d=>d.message); + appender.setLayoutFunction((d, i)=>d.message, (d) => ""); appender.append(helpers.createLogEntry("test1")); appender.append(helpers.createLogEntry("test2")); appender.append(helpers.createLogEntry("test3")); @@ -59,7 +60,7 @@ describe('DOMAppender', ()=>{ }); it('can limit number of entries in DOM', ()=>{ appender = new DOMAppender('test', true, 3); - appender.setLayoutFunction(d=>d.message); + appender.setLayoutFunction((d, i)=>d.message, d => ""); appender.append(helpers.createLogEntry("test1")); appender.append(helpers.createLogEntry("test2")); appender.append(helpers.createLogEntry("test3")); @@ -69,7 +70,7 @@ describe('DOMAppender', ()=>{ it('works without container', ()=>{ expect(()=>{ appender = new DOMAppender('no_such_id'); - appender.setLayoutFunction(d=>d.message); + appender.setLayoutFunction((d, i)=>d.message, (d) => ""); appender.append(helpers.createLogEntry("test1")); appender.append(helpers.createLogEntry("test2")); appender.append(helpers.createLogEntry("test3")); diff --git a/test/helpers/TestHelpers.ts b/test/helpers/TestHelpers.ts index 1d7c89f..b44f819 100644 --- a/test/helpers/TestHelpers.ts +++ b/test/helpers/TestHelpers.ts @@ -5,6 +5,9 @@ export function createLogEntry(message: string): LogEntry { time: new Date(), message: message, level: LogLevel.INFO, - tag: 'tag' + tag: 'tag', + object: null, + deep: 1, + stack: {} } } diff --git a/test/layouts/BasicLayoutTest.ts b/test/layouts/BasicLayoutTest.ts index 1c4e4a1..4f4c538 100644 --- a/test/layouts/BasicLayoutTest.ts +++ b/test/layouts/BasicLayoutTest.ts @@ -11,6 +11,16 @@ describe('BasicLayout', ()=>{ level: LogLevel.ERROR, tag: 'tag', time: new Date(Date.parse("21 May 2015 10:12:42")) - })).toBe("2015-05-21 10:12:42 ERROR [tag] - test message"); + }, false)).toBe("2015-05-21 10:12:42 ERROR [tag] - test message"); + }); + + it('add data if required', ()=>{ + expect(layout.format({ + message: 'test message', + level: LogLevel.ERROR, + tag: 'tag', + time: new Date(Date.parse("21 May 2015 10:12:42")), + object: { foo: 1, bar: "asd" } + }, true)).toBe('2015-05-21 10:12:42 ERROR [tag] - test message ' + JSON.stringify({ foo: 1, bar: "asd" }, null, 2)); }); }); diff --git a/test/layouts/HTMLLayoutTest.ts b/test/layouts/HTMLLayoutTest.ts index 743dee6..f4feda5 100644 --- a/test/layouts/HTMLLayoutTest.ts +++ b/test/layouts/HTMLLayoutTest.ts @@ -9,18 +9,26 @@ describe('HTMLLayout', ()=>{ message: 'test message', level: LogLevel.ERROR, tag: 'tag', - time: new Date(Date.parse("21 May 2015 10:12:42")) + time: new Date(Date.parse("21 May 2015 10:12:42")), + object: null, + deep: 1, + stack: {} }; }); it('by default, formats without colors', ()=>{ var layout = new HTMLLayout(); - expect(layout.format(log)).toEqual('2015-05-21 10:12:42 ERROR [tag] test message') + expect(layout.format(log, false)).toEqual('2015-05-21 10:12:42 ERROR [tag] test message') + }); + + it('add additional data if required', ()=>{ + var layout = new HTMLLayout(); + expect(layout.format(log, true)).toEqual('2015-05-21 10:12:42 ERROR [tag] test message
{}
') }); describe('color themes', ()=>{ var layout: HTMLLayout; function testColors(colors: HTMLLayoutColors) { - expect(layout.format(log)).toBe('2015-05-21 10:12:42 ERROR [tag] test message');