226 lines
6.0 KiB
JavaScript
226 lines
6.0 KiB
JavaScript
// Logger
|
|
//
|
|
// Wrapper around Winston logging library with custom query
|
|
//
|
|
// expected log entry to be in the shape of:
|
|
// {"level":"info","message":"Your Message","timestamp":"2016-02-04T05:59:27.412Z"}
|
|
//
|
|
import { LoggerAdapter } from './LoggerAdapter';
|
|
import winston from 'winston';
|
|
import fs from 'fs';
|
|
import { Parse } from 'parse/node';
|
|
|
|
const MILLISECONDS_IN_A_DAY = 24 * 60 * 60 * 1000;
|
|
const CACHE_TIME = 1000 * 60;
|
|
|
|
let LOGS_FOLDER = './logs/';
|
|
|
|
if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') {
|
|
LOGS_FOLDER = './test_logs/'
|
|
}
|
|
|
|
let currentDate = new Date();
|
|
|
|
let simpleCache = {
|
|
timestamp: null,
|
|
from: null,
|
|
until: null,
|
|
order: null,
|
|
data: [],
|
|
level: 'info',
|
|
};
|
|
|
|
// returns Date object rounded to nearest day
|
|
let _getNearestDay = (date) => {
|
|
return new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
|
}
|
|
|
|
// returns Date object of previous day
|
|
let _getPrevDay = (date) => {
|
|
return new Date(date - MILLISECONDS_IN_A_DAY);
|
|
}
|
|
|
|
// returns the iso formatted file name
|
|
let _getFileName = () => {
|
|
return _getNearestDay(currentDate).toISOString()
|
|
}
|
|
|
|
// check for valid cache when both from and util match.
|
|
// cache valid for up to 1 minute
|
|
let _hasValidCache = (from, until, level) => {
|
|
if (String(from) === String(simpleCache.from) &&
|
|
String(until) === String(simpleCache.until) &&
|
|
new Date() - simpleCache.timestamp < CACHE_TIME &&
|
|
level === simpleCache.level) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// renews transports to current date
|
|
let _renewTransports = ({infoLogger, errorLogger, logsFolder}) => {
|
|
if (infoLogger) {
|
|
infoLogger.add(winston.transports.File, {
|
|
filename: logsFolder + _getFileName() + '.info',
|
|
name: 'info-file',
|
|
level: 'info'
|
|
});
|
|
}
|
|
if (errorLogger) {
|
|
errorLogger.add(winston.transports.File, {
|
|
filename: logsFolder + _getFileName() + '.error',
|
|
name: 'error-file',
|
|
level: 'error'
|
|
});
|
|
}
|
|
};
|
|
|
|
// check that log entry has valid time stamp based on query
|
|
let _isValidLogEntry = (from, until, entry) => {
|
|
var _entry = JSON.parse(entry),
|
|
timestamp = new Date(_entry.timestamp);
|
|
return timestamp >= from && timestamp <= until
|
|
? true
|
|
: false
|
|
};
|
|
|
|
// ensure that file name is up to date
|
|
let _verifyTransports = ({infoLogger, errorLogger, logsFolder}) => {
|
|
if (_getNearestDay(currentDate) !== _getNearestDay(new Date())) {
|
|
currentDate = new Date();
|
|
if (infoLogger) {
|
|
infoLogger.remove('info-file');
|
|
}
|
|
if (errorLogger) {
|
|
errorLogger.remove('error-file');
|
|
}
|
|
_renewTransports({infoLogger, errorLogger, logsFolder});
|
|
}
|
|
}
|
|
|
|
export class FileLoggerAdapter extends LoggerAdapter {
|
|
constructor(options = {}) {
|
|
super();
|
|
|
|
this._logsFolder = options.logsFolder || LOGS_FOLDER;
|
|
|
|
// check logs folder exists
|
|
if (!fs.existsSync(this._logsFolder)) {
|
|
fs.mkdirSync(this._logsFolder);
|
|
}
|
|
|
|
this._errorLogger = new (winston.Logger)({
|
|
exitOnError: false,
|
|
transports: [
|
|
new (winston.transports.File)({
|
|
filename: this._logsFolder + _getFileName() + '.error',
|
|
name: 'error-file',
|
|
level: 'error'
|
|
})
|
|
]
|
|
});
|
|
|
|
this._infoLogger = new (winston.Logger)({
|
|
exitOnError: false,
|
|
transports: [
|
|
new (winston.transports.File)({
|
|
filename: this._logsFolder + _getFileName() + '.info',
|
|
name: 'info-file',
|
|
level: 'info'
|
|
})
|
|
]
|
|
});
|
|
}
|
|
|
|
info() {
|
|
_verifyTransports({infoLogger: this._infoLogger, logsFolder: this._logsFolder});
|
|
return this._infoLogger.info.apply(undefined, arguments);
|
|
}
|
|
|
|
error() {
|
|
_verifyTransports({errorLogger: this._errorLogger, logsFolder: this._logsFolder});
|
|
return this._errorLogger.error.apply(undefined, arguments);
|
|
}
|
|
|
|
// custom query as winston is currently limited
|
|
query(options, callback) {
|
|
if (!options) {
|
|
options = {};
|
|
}
|
|
// defaults to 7 days prior
|
|
let from = options.from || new Date(Date.now() - (7 * MILLISECONDS_IN_A_DAY));
|
|
let until = options.until || new Date();
|
|
let size = options.size || 10;
|
|
let order = options.order || 'desc';
|
|
let level = options.level || 'info';
|
|
let roundedUntil = _getNearestDay(until);
|
|
let roundedFrom = _getNearestDay(from);
|
|
|
|
if (_hasValidCache(roundedFrom, roundedUntil, level)) {
|
|
let logs = [];
|
|
if (order !== simpleCache.order) {
|
|
// reverse order of data
|
|
simpleCache.data.forEach((entry) => {
|
|
logs.unshift(entry);
|
|
});
|
|
} else {
|
|
logs = simpleCache.data;
|
|
}
|
|
callback(logs.slice(0, size));
|
|
return;
|
|
}
|
|
|
|
let curDate = roundedUntil;
|
|
let curSize = 0;
|
|
let method = order === 'desc' ? 'push' : 'unshift';
|
|
let files = [];
|
|
let promises = [];
|
|
|
|
// current a batch call, all files with valid dates are read
|
|
while (curDate >= from) {
|
|
files[method](this._logsFolder + curDate.toISOString() + '.' + level);
|
|
curDate = _getPrevDay(curDate);
|
|
}
|
|
|
|
// read each file and split based on newline char.
|
|
// limitation is message cannot contain newline
|
|
// TODO: strip out delimiter from logged message
|
|
files.forEach(function(file, i) {
|
|
let promise = new Parse.Promise();
|
|
fs.readFile(file, 'utf8', function(err, data) {
|
|
if (err) {
|
|
promise.resolve([]);
|
|
} else {
|
|
let results = data.split('\n').filter((value) => {
|
|
return value.trim() !== '';
|
|
});
|
|
promise.resolve(results);
|
|
}
|
|
});
|
|
promises[method](promise);
|
|
});
|
|
|
|
Parse.Promise.when(promises).then((results) => {
|
|
let logs = [];
|
|
results.forEach(function(logEntries, i) {
|
|
logEntries.forEach(function(entry) {
|
|
if (_isValidLogEntry(from, until, entry)) {
|
|
logs[method](JSON.parse(entry));
|
|
}
|
|
});
|
|
});
|
|
simpleCache = {
|
|
timestamp: new Date(),
|
|
from: roundedFrom,
|
|
until: roundedUntil,
|
|
data: logs,
|
|
order,
|
|
level,
|
|
};
|
|
callback(logs.slice(0, size));
|
|
});
|
|
}
|
|
}
|
|
|
|
export default FileLoggerAdapter;
|