Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add stack trace to log #5

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions demo/Demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions src/IAppender.ts
Original file line number Diff line number Diff line change
@@ -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();
}
8 changes: 7 additions & 1 deletion src/ILayout.ts
Original file line number Diff line number Diff line change
@@ -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;
}
3 changes: 3 additions & 0 deletions src/LogEntry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import {LogLevel} from "./LogLevel";

export interface LogEntry {
level: LogLevel;
object: any;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Separating text and object is a good idea, but I insist on passing object data as string, not as object itself. It would give some overhead in JSON serializing/unserializing, but only in console appender

deep: number;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant field in context of other comments

time: Date;
message: string;
tag: string;
stack: any;
}
34 changes: 21 additions & 13 deletions src/Logger.ts
Original file line number Diff line number Diff line change
@@ -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) {
Expand All @@ -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
});
}
}
Expand Down
9 changes: 7 additions & 2 deletions src/LoggerConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[]) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should add new optional parameters to the end of constructor to prevent backward compatibility problems

if (appender) {
this.addAppender(appender);
}
Expand All @@ -30,6 +30,10 @@ export default class LoggerConfig {
return this.level;
}

public captureStack():boolean {
return this.capture_stack;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The good idea to pass not only flag, but minimal level for displaying stacktraces. For example, you could print stacktraces for WARNING level and higher.

}

public hasTag(tag: string) {
if (!this.tags || this.tags.length === 0) return true;

Expand All @@ -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;

Expand Down Expand Up @@ -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[];
}
Expand Down
13 changes: 7 additions & 6 deletions src/appenders/BaseAppender.ts
Original file line number Diff line number Diff line change
@@ -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;
}
43 changes: 41 additions & 2 deletions src/appenders/ConsoleAppender.ts
Original file line number Diff line number Diff line change
@@ -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
}
}
2 changes: 1 addition & 1 deletion src/appenders/DOMAppender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
17 changes: 13 additions & 4 deletions src/layouts/BasicLayout.ts
Original file line number Diff line number Diff line change
@@ -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 {
Copy link
Owner

@koroandr koroandr Jul 21, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea was to keep API simple and mimic the log4j and others. I think, passing some flags (especially when they lead us to repeating code in formatData) is not a good idea.

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") {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code would be copy&pasted in every appender created

return stringify(entry.object, entry.deep || 1);
}

return '';
}

private formatDate(date:Date):string {
function pad(number) {
if (number < 10) {
return '0' + number;
Expand Down
37 changes: 34 additions & 3 deletions src/layouts/HTMLLayout.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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 = {
Expand All @@ -42,11 +44,40 @@ export default class HTMLLayout implements ILayout {
this.colors = <HTMLLayoutColors>colors_theme;
}
}
format(entry:LogEntry):string {
return '<span' + this.getTimeStyle() + '>' + this.formatDate(entry.time) + '</span> ' +
format(entry:LogEntry, include_data: boolean):string {
let res = '<span' + this.getTimeStyle() + '>' + this.formatDate(entry.time) + '</span> ' +
'<span' + this.getLevelStyle() + '>' + LogLevel[entry.level] + '</span> ' +
'<span' + this.getTagStyle() + '>[' + entry.tag + ']</span> ' +
'<span' + this.getMessageStyle() + '>' + entry.message + '</span>';
'<span' + this.getMessageStyle() + '>' + entry.message + '</span>';

if(include_data) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of passing the flag, why not just check for entry.object?

res += '<pre>' + this.formatData(entry) + '</pre>';
}

if(entry.stack) {
let formatted = this.formatStack(entry.stack);

if(formatted) {
res += '<pre>' + formatted + '</pre>';
}
}

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('<br />');
}

return null;
}

private getTimeStyle() {
Expand Down
Loading