typescript-logging
Version:
Library for logging, written in typescript, can be used by normal es5+ javascript as well.
335 lines (274 loc) • 8.66 kB
text/typescript
import {LogLevel} from "../LoggerOptions";
import {ErrorType, Logger, MessageType} from "./Logger";
import {LinkedList} from "../../utils/DataStructures";
import {MessageFormatUtils} from "../../utils/MessageUtils";
import {LogData} from "../LogData";
import {LogGroupRule} from "./LogGroupRule";
import {LogGroupRuntimeSettings} from "./LogGroupRuntimeSettings";
/**
* Log message, providing all data for a single message.
*/
export interface LogMessage {
/**
* Name of the logger.
*/
readonly loggerName: string;
/**
* Original, unformatted message or LogData.
*/
readonly message: string | LogData;
/**
* Returns the resolved stack (based on error).
* Available only when error is present, null otherwise.
*/
readonly errorAsStack: string | null;
/**
* Error when present, or null.
*/
readonly error: Error | null;
/**
* Which LogGroupRule matched for this message.
*/
readonly logGroupRule: LogGroupRule;
/**
* Time for message.
*/
readonly date: Date;
/**
* LogLevel used
*/
readonly level: LogLevel;
/**
* True if message represents LogData (false for a string message).
*/
readonly isMessageLogData: boolean;
/**
* Always retrieves the message, from either the string directly
* or in case of LogData from LogData itself.
*/
readonly messageAsString: string;
/**
* If present returns LogData, otherwise null.
*/
readonly logData: LogData | null;
}
interface LogMessageInternal extends LogMessage {
/**
* True if the message is done (ready), if false
* we wait for a promise.
*/
ready: boolean;
}
class LogMessageInternalImpl implements LogMessageInternal {
private _loggerName: string;
private _message: string | LogData;
private _errorAsStack: string | null = null;
private _error: Error | null = null;
private _logGroupRule: LogGroupRule;
private _date: Date;
private _level: LogLevel;
private _ready: boolean;
constructor(loggerName: string, message: string | LogData, errorAsStack: string | null, error: Error | null, logGroupRule: LogGroupRule, date: Date, level: LogLevel, ready: boolean) {
this._loggerName = loggerName;
this._message = message;
this._errorAsStack = errorAsStack;
this._error = error;
this._logGroupRule = logGroupRule;
this._date = date;
this._level = level;
this._ready = ready;
}
get loggerName(): string {
return this._loggerName;
}
get message(): string | LogData {
return this._message;
}
set message(value: string | LogData) {
this._message = value;
}
get errorAsStack(): string | any {
return this._errorAsStack;
}
set errorAsStack(value: string | any) {
this._errorAsStack = value;
}
get error(): Error | any {
return this._error;
}
set error(value: Error | any) {
this._error = value;
}
get logGroupRule(): LogGroupRule {
return this._logGroupRule;
}
set logGroupRule(value: LogGroupRule) {
this._logGroupRule = value;
}
get date(): Date {
return this._date;
}
set date(value: Date) {
this._date = value;
}
get level(): LogLevel {
return this._level;
}
set level(value: LogLevel) {
this._level = value;
}
get isMessageLogData(): boolean {
return typeof(this._message) !== "string";
}
get ready(): boolean {
return this._ready;
}
set ready(value: boolean) {
this._ready = value;
}
get messageAsString(): string {
if (typeof(this._message) === "string") {
return this._message;
}
return this._message.msg;
}
get logData(): LogData | null {
let result: LogData | null = null;
if (typeof(this._message) !== "string") {
result = this.message as LogData;
}
return result;
}
}
/**
* Abstract base logger, extend to easily implement a custom logger that
* logs wherever you want. You only need to implement doLog(msg: LogMessage) and
* log that somewhere (it will contain format and everything else).
*/
export abstract class AbstractLogger implements Logger {
private _logGroupRuntimeSettings: LogGroupRuntimeSettings;
private _allMessages: LinkedList<LogMessageInternal> = new LinkedList<LogMessageInternal>();
protected _name: string;
protected _open: boolean = true;
constructor(name: string, logGroupRuntimeSettings: LogGroupRuntimeSettings) {
this._name = name;
this._logGroupRuntimeSettings = logGroupRuntimeSettings;
}
get name(): string {
return this._name;
}
public trace(msg: MessageType, error: ErrorType = null): void {
this._log(LogLevel.Trace, msg, error);
}
public debug(msg: MessageType, error: ErrorType = null): void {
this._log(LogLevel.Debug, msg, error);
}
public info(msg: MessageType, error: ErrorType = null): void {
this._log(LogLevel.Info, msg, error);
}
public warn(msg: MessageType, error: ErrorType = null): void {
this._log(LogLevel.Warn, msg, error);
}
public error(msg: MessageType, error: ErrorType = null): void {
this._log(LogLevel.Error, msg, error);
}
public fatal(msg: MessageType, error: ErrorType = null): void {
this._log(LogLevel.Fatal, msg, error);
}
public isTraceEnabled(): boolean {
return this._logGroupRuntimeSettings.level === LogLevel.Trace;
}
public isDebugEnabled(): boolean {
return this._logGroupRuntimeSettings.level <= LogLevel.Debug;
}
public isInfoEnabled(): boolean {
return this._logGroupRuntimeSettings.level <= LogLevel.Info;
}
public isWarnEnabled(): boolean {
return this._logGroupRuntimeSettings.level <= LogLevel.Warn;
}
public isErrorEnabled(): boolean {
return this._logGroupRuntimeSettings.level <= LogLevel.Error;
}
public isFatalEnabled(): boolean {
return this._logGroupRuntimeSettings.level <= LogLevel.Fatal;
}
public getLogLevel(): LogLevel {
return this._logGroupRuntimeSettings.level;
}
public isOpen(): boolean {
return this._open;
}
public close(): void {
this._open = false;
this._allMessages.clear();
}
protected createDefaultLogMessage(msg: LogMessage): string {
return MessageFormatUtils.renderDefaultLog4jMessage(msg, true);
}
/**
* Return optional message formatter. All LoggerTypes (except custom) will see if
* they have this, and if so use it to log.
* @returns {((message:LogMessage)=>string)|null}
*/
protected _getMessageFormatter(): ((message: LogMessage) => string) | null {
return this._logGroupRuntimeSettings.formatterLogMessage;
}
protected abstract doLog(msg: LogMessage): void;
private _log(level: LogLevel, msg: MessageType, error: ErrorType = null): void {
if (this._open && this._logGroupRuntimeSettings.level <= level) {
const functionMessage = (): string | LogData => {
if (typeof msg === "function") {
return msg();
}
return msg;
};
const functionError = (): Error | null => {
if (typeof error === "function") {
return error();
}
return error;
};
this._allMessages.addTail(this.createMessage(level, functionMessage, functionError, new Date()));
this.processMessages();
}
}
private createMessage(level: LogLevel, msg: () => string | LogData, error: () => Error | null, date: Date): LogMessageInternal {
const errorResult = error();
if (errorResult !== null) {
const message = new LogMessageInternalImpl(this._name, msg(), null, errorResult, this._logGroupRuntimeSettings.logGroupRule, date, level, false);
MessageFormatUtils.renderError(errorResult).then((stack: string) => {
message.errorAsStack = stack;
message.ready = true;
this.processMessages();
}).catch(() => {
message.errorAsStack = "<UNKNOWN> unable to get stack.";
message.ready = true;
this.processMessages();
});
return message;
}
return new LogMessageInternalImpl(this._name, msg(), null, errorResult, this._logGroupRuntimeSettings.logGroupRule, date, level, true);
}
private processMessages(): void {
// Basically we wait until errors are resolved (those messages
// may not be ready).
const msgs = this._allMessages;
if (msgs.getSize() > 0) {
do {
const msg = msgs.getHead();
if (msg != null) {
if (!msg.ready) {
break;
}
msgs.removeHead();
// This can never be null normally, but strict null checking ...
if (msg.message !== null) {
this.doLog(msg);
}
}
}
while (msgs.getSize() > 0);
}
}
}