WIP Logging service and API.
This commit is contained in:
@ -3,11 +3,16 @@ import App from './App.vue';
|
||||
import router from './router';
|
||||
import store from './store';
|
||||
import './registerServiceWorker';
|
||||
import { LogService, LogLevel, ApiAppender, ConsoleAppender } from './services/logging';
|
||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
||||
|
||||
Vue.component('fa-icon', FontAwesomeIcon);
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
LogService.getRootLogger().appenders.push(
|
||||
// TODO: prod/dev config settings for logging?
|
||||
new ConsoleAppender(LogLevel.DEBUG),
|
||||
new ApiAppender(process.env.VUE_APP_PM_API_BASE + '/log/batch', '', LogLevel.WARN)
|
||||
);
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
|
65
web/src/services/logging/api-appender.ts
Normal file
65
web/src/services/logging/api-appender.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import Axios from 'axios';
|
||||
|
||||
import { LogMessage, LogLevel } from './log-message';
|
||||
import Logger from './logger';
|
||||
import LogAppender from './log-appender';
|
||||
|
||||
interface ApiMessage {
|
||||
level: string;
|
||||
message: string;
|
||||
scope: string;
|
||||
stacktrace: string;
|
||||
timestamp: string;
|
||||
}
|
||||
export class ApiAppender implements LogAppender {
|
||||
public batchSize = 10;
|
||||
public minimumTimePassedInSec = 60;
|
||||
public maximumTimePassedInSec = 120;
|
||||
|
||||
private http = Axios.create();
|
||||
private msgBuffer: ApiMessage[] = [];
|
||||
private lastSent = 0;
|
||||
|
||||
constructor(public readonly apiEndpoint: string, public authToken: string, public threshold?: LogLevel) {
|
||||
setInterval(this.checkPost, 1000);
|
||||
}
|
||||
|
||||
public appendMessage(logger: Logger, msg: LogMessage): void {
|
||||
if (this.threshold && msg.level < this.threshold) { return; }
|
||||
|
||||
this.msgBuffer.push({
|
||||
level: LogLevel[msg.level],
|
||||
message: msg.message,
|
||||
scope: logger.name,
|
||||
stacktrace: msg.stacktrace,
|
||||
timestamp: msg.timestamp.toISOString()
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
setInterval(this.checkPost, Math.max(10000, this.minimumTimePassedInSec * 1000));
|
||||
}
|
||||
|
||||
private doPost() {
|
||||
if (this.msgBuffer.length === 0) { return; }
|
||||
|
||||
this.http.post(this.apiEndpoint, this.msgBuffer,
|
||||
{ headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${this.authToken}`
|
||||
}});
|
||||
|
||||
this.lastSent = Date.now();
|
||||
this.msgBuffer = [];
|
||||
}
|
||||
}
|
||||
|
||||
export default ApiAppender;
|
30
web/src/services/logging/console-appender.ts
Normal file
30
web/src/services/logging/console-appender.ts
Normal file
@ -0,0 +1,30 @@
|
||||
/*tslint:disable:no-console*/
|
||||
import { LogMessage, LogLevel} from './log-message';
|
||||
import Logger from './logger';
|
||||
import LogAppender from './log-appender';
|
||||
|
||||
export class ConsoleAppender implements LogAppender {
|
||||
constructor(public threshold?: LogLevel) {}
|
||||
|
||||
public appendMessage(logger: Logger, msg: LogMessage): void {
|
||||
if (this.threshold && msg.level < this.threshold) { return; }
|
||||
|
||||
let logMethod = console.log;
|
||||
switch (msg.level) {
|
||||
case LogLevel.ALL: logMethod = console.trace; break;
|
||||
case LogLevel.DEBUG: logMethod = console.debug; 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(logger.name, msg.message, msg.error);
|
||||
} else {
|
||||
logMethod(logger.name, msg.message, msg.stacktrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ConsoleAppender;
|
5
web/src/services/logging/index.ts
Normal file
5
web/src/services/logging/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export * from './log-message';
|
||||
export * from './log-appender';
|
||||
export * from './log-service';
|
||||
export * from './console-appender';
|
||||
export * from './api-appender';
|
5
web/src/services/logging/log-appender.ts
Normal file
5
web/src/services/logging/log-appender.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { LogLevel, LogMessage } from './log-message';
|
||||
import Logger from './logger';
|
||||
export default interface LogAppender {
|
||||
appendMessage(logger: Logger, message: LogMessage): void;
|
||||
}
|
11
web/src/services/logging/log-message.ts
Normal file
11
web/src/services/logging/log-message.ts
Normal file
@ -0,0 +1,11 @@
|
||||
export enum LogLevel { ALL = 0, DEBUG, INFO, WARN, ERROR, FATAL }
|
||||
|
||||
export interface LogMessage {
|
||||
level: LogLevel;
|
||||
message: string;
|
||||
stacktrace: string;
|
||||
error?: Error;
|
||||
timestamp: Date;
|
||||
}
|
||||
|
||||
export default LogMessage;
|
33
web/src/services/logging/log-service.ts
Normal file
33
web/src/services/logging/log-service.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { LogLevel } from './log-message';
|
||||
import Logger from './logger';
|
||||
import { default as Axios, AxiosInstance } from 'axios';
|
||||
|
||||
/* tslint:disable:max-classes-per-file*/
|
||||
export class LogService {
|
||||
|
||||
public static getRootLogger(): Logger { return Logger.ROOT_LOGGER; }
|
||||
|
||||
private loggers: { [key: string]: Logger } = { };
|
||||
private http: AxiosInstance = Axios.create();
|
||||
|
||||
public getLogger(name: string, threshold?: LogLevel): Logger {
|
||||
if (this.loggers.hasOwnProperty(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 = Logger.ROOT_LOGGER;
|
||||
}
|
||||
|
||||
this.loggers[name] = parentLogger.createChildLogger(name, threshold);
|
||||
return this.loggers[name];
|
||||
}
|
||||
}
|
||||
|
||||
export default new LogService();
|
66
web/src/services/logging/logger.ts
Normal file
66
web/src/services/logging/logger.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import { LogMessage, LogLevel } from './log-message';
|
||||
import LogAppender from './log-appender';
|
||||
|
||||
export default class Logger {
|
||||
|
||||
public static readonly ROOT_LOGGER = new Logger('ROOT', undefined, LogLevel.ALL);
|
||||
|
||||
public appenders: LogAppender[] = [];
|
||||
|
||||
protected 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 log(level: LogLevel, message: (Error | string), stacktrace?: string): void {
|
||||
|
||||
if (level < this.getEffectiveThreshold()) { return; }
|
||||
|
||||
const logMsg: LogMessage = { level, message: '', stacktrace: '', timestamp: new Date() };
|
||||
|
||||
if (typeof message === 'string') {
|
||||
logMsg.message = message;
|
||||
logMsg.stacktrace = stacktrace == null ? '' : stacktrace;
|
||||
} else {
|
||||
logMsg.error = message;
|
||||
logMsg.message = `${message.name}: ${message.message}`;
|
||||
logMsg.stacktrace = message.stack == null ? '' : message.stack;
|
||||
}
|
||||
|
||||
this.appenders.forEach((app) => {
|
||||
app.appendMessage(this, logMsg);
|
||||
});
|
||||
}
|
||||
|
||||
public debug(message: (Error | string), stacktrace?: string): void {
|
||||
this.log(LogLevel.DEBUG, message, stacktrace);
|
||||
}
|
||||
|
||||
public info(message: string, stacktrace?: string): void {
|
||||
this.log(LogLevel.INFO, message, stacktrace);
|
||||
}
|
||||
|
||||
public warn(message: string, stacktrace?: string): void {
|
||||
this.log(LogLevel.WARN, message, stacktrace);
|
||||
}
|
||||
|
||||
public error(message: string, stacktrace?: string): void {
|
||||
this.log(LogLevel.ERROR, message, stacktrace);
|
||||
}
|
||||
|
||||
public fatal(message: string, stacktrace?: string): void {
|
||||
this.log(LogLevel.FATAL, message, stacktrace);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user