Initial 1.0.0 commit (library extracted from existing project).

This commit is contained in:
2020-08-07 08:54:06 -05:00
commit 1a96000e81
25 changed files with 768 additions and 0 deletions

83
src/api-log-appender.ts Normal file
View File

@ -0,0 +1,83 @@
import Axios from 'axios';
import { LogMessage, LogLevel } from './log-message';
import LogAppender from './log-appender';
interface ApiMessage {
level: string;
message: string;
scope: string;
stacktrace: string;
timestamp: string;
}
export class ApiLogAppender implements LogAppender {
public batchSize = 10;
public minimumTimePassedInSec = 60;
public maximumTimePassedInSec = 120;
public threshold = LogLevel.ALL;
private http = Axios.create();
private msgBuffer: ApiMessage[] = [];
private lastSent = 0;
constructor(
public readonly apiEndpoint: string,
public authToken?: string,
threshold?: LogLevel
) {
setTimeout(this.checkPost, 1000);
if (threshold) {
this.threshold = threshold;
}
}
public appendMessage(msg: LogMessage): void {
if (this.threshold && msg.level < this.threshold) {
return;
}
this.msgBuffer.push({
level: LogLevel[msg.level],
message:
typeof msg.message === 'string'
? msg.message
: JSON.stringify(msg.message),
scope: msg.scope,
stacktrace: msg.stacktrace,
timestamp: msg.timestamp.toISOString()
});
}
private doPost() {
if (this.msgBuffer.length > 0 && this.authToken) {
this.http.post(this.apiEndpoint, this.msgBuffer, {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.authToken}`
}
});
this.lastSent = Date.now();
this.msgBuffer = [];
}
}
private checkPost = () => {
const now = Date.now();
const min = this.lastSent + this.minimumTimePassedInSec * 1000;
const max = this.lastSent + this.maximumTimePassedInSec * 1000;
if (
(this.msgBuffer.length >= this.batchSize && min < now) ||
(this.msgBuffer.length > 0 && max < now)
) {
this.doPost();
}
setTimeout(
this.checkPost,
Math.max(10000, this.minimumTimePassedInSec * 1000)
);
};
}
export default ApiLogAppender;

View File

@ -0,0 +1,55 @@
/*tslint:disable:no-console*/
import { LogMessage, LogLevel } from './log-message';
import LogAppender from './log-appender';
export class ConsoleLogAppender implements LogAppender {
public threshold = LogLevel.ALL;
constructor(threshold?: LogLevel) {
if (threshold) {
this.threshold = threshold;
}
}
public appendMessage(msg: LogMessage): void {
if (this.threshold && msg.level < this.threshold) {
return;
}
let logMethod = console.log;
switch (msg.level) {
case LogLevel.ALL:
logMethod = console.log;
break;
case LogLevel.TRACE:
logMethod = console.log;
break;
case LogLevel.DEBUG:
logMethod = console.debug;
break;
case LogLevel.LOG:
logMethod = console.log;
break;
case LogLevel.INFO:
logMethod = console.info;
break;
case LogLevel.WARN:
logMethod = console.warn;
break;
case LogLevel.ERROR:
case LogLevel.FATAL:
logMethod = console.trace;
break;
}
if (msg.error) {
logMethod(`[${msg.scope}]:`, msg.message, msg.error);
} else if (msg.stacktrace) {
logMethod(`[${msg.scope}]:`, msg.message, msg.stacktrace);
} else {
logMethod(`[${msg.scope}]:`, msg.message);
}
}
}
export default ConsoleLogAppender;

6
src/index.ts Normal file
View File

@ -0,0 +1,6 @@
export * from './log-message';
export * from './log-appender';
export * from './log-service';
export * from './console-log-appender';
export * from './api-log-appender';
export * from './logger';

5
src/log-appender.ts Normal file
View File

@ -0,0 +1,5 @@
import { LogLevel, LogMessage } from './log-message';
export default interface LogAppender {
threshold: LogLevel;
appendMessage(message: LogMessage): void;
}

21
src/log-message.ts Normal file
View File

@ -0,0 +1,21 @@
export enum LogLevel {
ALL = 0,
TRACE,
DEBUG,
LOG,
INFO,
WARN,
ERROR,
FATAL
}
export interface LogMessage {
scope: string;
level: LogLevel;
message: string | object;
stacktrace: string;
error?: Error;
timestamp: Date;
}
export default LogMessage;

48
src/log-service.ts Normal file
View File

@ -0,0 +1,48 @@
import { LogLevel } from './log-message';
import Logger from './logger';
const ROOT_LOGGER_NAME = 'ROOT';
export class LogService {
private loggers: { [key: string]: Logger };
public get ROOT_LOGGER() {
return this.loggers[ROOT_LOGGER_NAME];
}
public constructor() {
this.loggers = {};
this.loggers[ROOT_LOGGER_NAME] = new Logger(
ROOT_LOGGER_NAME,
undefined,
LogLevel.ALL
);
}
public getLogger(name: string, threshold?: LogLevel): Logger {
if (this.loggers[name]) {
return this.loggers[name];
}
let parentLogger: Logger;
const parentLoggerName = Object.keys(this.loggers)
.filter((n: string) => name.startsWith(n))
.reduce(
(acc: string, cur: string) => (acc.length > cur.length ? acc : cur),
''
);
if (parentLoggerName) {
parentLogger = this.loggers[parentLoggerName];
} else {
parentLogger = this.ROOT_LOGGER;
}
this.loggers[name] = parentLogger.createChildLogger(name, threshold);
return this.loggers[name];
}
}
export const logService = new LogService();
export default logService;

108
src/logger.ts Normal file
View File

@ -0,0 +1,108 @@
import { LogMessage, LogLevel } from './log-message';
import LogAppender from './log-appender';
export type DeferredMsg = () => string | object;
export type MessageType = string | DeferredMsg | object;
export class Logger {
public appenders: LogAppender[] = [];
public constructor(
public readonly name: string,
private parentLogger?: Logger,
public threshold?: LogLevel
) {}
public createChildLogger(name: string, threshold?: LogLevel): Logger {
return new Logger(name, this, threshold);
}
public doLog(
level: LogLevel,
message: Error | MessageType,
stacktrace?: string
): void {
if (level < this.getEffectiveThreshold()) {
return;
}
const logMsg: LogMessage = {
scope: this.name,
level,
message: '',
stacktrace: '',
timestamp: new Date()
};
if (message === undefined || message === null) {
logMsg.message = message;
logMsg.stacktrace = stacktrace == null ? '' : stacktrace;
} else if ((message as DeferredMsg).call !== undefined) {
logMsg.message = (message as DeferredMsg)();
logMsg.stacktrace = stacktrace == null ? '' : stacktrace;
} else if (message instanceof Error) {
const error = message as Error;
logMsg.error = error;
logMsg.message = `${error.name}: ${error.message}`;
logMsg.stacktrace = error.stack == null ? '' : error.stack;
} else {
// string | object
logMsg.message = message;
logMsg.stacktrace = stacktrace == null ? '' : stacktrace;
}
this.sendToAppenders(logMsg);
}
public trace(message: Error | MessageType, stacktrace?: string): void {
this.doLog(LogLevel.TRACE, message, stacktrace);
}
public debug(message: Error | MessageType, stacktrace?: string): void {
this.doLog(LogLevel.DEBUG, message, stacktrace);
}
public log(message: MessageType, stacktrace?: string): void {
this.doLog(LogLevel.LOG, message, stacktrace);
}
public info(message: MessageType, stacktrace?: string): void {
this.doLog(LogLevel.INFO, message, stacktrace);
}
public warn(message: MessageType, stacktrace?: string): void {
this.doLog(LogLevel.WARN, message, stacktrace);
}
public error(message: Error | MessageType, stacktrace?: string): void {
this.doLog(LogLevel.ERROR, message, stacktrace);
}
public fatal(message: Error | MessageType, stacktrace?: string): void {
this.doLog(LogLevel.FATAL, message, stacktrace);
}
protected sendToAppenders(logMsg: LogMessage) {
this.appenders.forEach(app => {
app.appendMessage(logMsg);
});
if (this.parentLogger) {
this.parentLogger.sendToAppenders(logMsg);
}
}
protected getEffectiveThreshold(): LogLevel {
if (this.threshold) {
return this.threshold;
}
if (this.parentLogger) {
return this.parentLogger.getEffectiveThreshold();
}
// should never happen (root logger should always have a threshold
return LogLevel.ALL;
}
}
export default Logger;