/* eslint-disable class-methods-use-this */
const { data, console } = require('@proceed/system');
const config = require('../configuration/configHandler');
const loggerLoader = require('./src/loggerHelpers/logger');
const rotationUtils = require('./src/utils/logRotationUtils');
const startRotation = require('./src/rotation/rotation');
const routes = require('./src/routes/logRoutes');
let singletonInstance;
/**
* @memberof module:@proceed/machine
* @class
*
* Class for initializing and getting a logger
*/
class Logging {
/**
* @hideconstructor
*/
constructor() {
this.doneInitializing = undefined;
// See @proceed/system.console
console.constructor._setLoggingModule(this);
}
/**
* Initializes all things necessary for the Logging
* - Provides the data module to the rotation utils
* - Creates the config data file if it doesn't exist
* - starts the log rotation
*/
init() {
// Don't initialize twice
if (this.doneInitializing) {
return this.doneInitializing;
}
const donePromise = new Promise((resolve) => {
config.readConfigData('processLogs').then((processLogs) => {
if (processLogs === null) {
// logging_meta_data doesn't exist. create one and fill it with default values
config
.createConfigData()
.then(() => {
this.rotationUtils = rotationUtils.init(data);
startRotation(this);
resolve();
})
.catch(() => {});
} else {
this.rotationUtils = rotationUtils.init(data);
startRotation(this);
resolve();
}
});
});
// storing the promise, so components further down the line can wait until
// the logging is initialized
this.doneInitializing = donePromise;
return donePromise;
}
start() {
routes(this);
}
/**
* Factory method creating a logger
* @param {object} confObject An object containing all configuration parameters for the logger
* @returns a logger
*/
getLogger(confObject) {
const logger = loggerLoader(confObject, this.init.bind(this));
return logger;
}
/**
* Returns the last [limit] standard logs
* @returns {object} standard logs
* @param {number} limit - how many logs we want to get
*/
async getStandardLogTables(limit = 100) {
const logData = await data.read('monitoring');
const oldLogData = await data.read('monitoring_old');
const res1 = Object.entries(logData || {}).map(([key, value]) => ({
[key]: JSON.parse(value),
}));
const res2 = Object.entries(oldLogData || {}).map(([key, value]) => ({
[key]: JSON.parse(value),
}));
return res2.concat(res1).slice(Math.max(res1.length + res2.length - limit, 0));
}
/**
* Returns all process logs across all tables
* @param {string} processID The ID of the process for which the logs are to be returned
* @param {number} limit - how many logs we want to get
*/
async getProcessLogTables(processID, limit) {
const configDataEntry = await config.readConfigData('processLogs');
// == to convert into strings if id is number
// eslint-disable-next-line eqeqeq
const processInfo = configDataEntry.find((c) => c.id == processID);
if (!processInfo) {
return [];
}
const numOfLogTables = processInfo.tables;
const { currentTableID } = processInfo;
const range = [];
for (let i = currentTableID; i > currentTableID - numOfLogTables; i -= 1) {
range.push(i);
}
let logTables = [];
let tableID;
for (tableID in range) {
const logTable = await data.read(`${tableID}_monitoring_${processID}`);
const arr = Object.entries(logTable || {}).map(([key, value]) => ({
[key]: JSON.parse(value),
}));
logTables = logTables.concat(arr);
}
if (limit) {
logTables = logTables.slice(Math.max(logTables.length - limit, 0));
}
return logTables;
}
/**
*
* @param {string} processId The ID of the process
* @param {string} instanceId the ID of the specific instance of the process for which the logs are to be returned
* @param {number} limit - how many logs we want to get
*/
async getInstanceLogs(processId, instanceId, limit) {
const processTables = await this.getProcessLogTables(processId);
let instanceLogs = processTables.filter((pt) => {
const [[key, info]] = Object.entries(pt);
return info.instanceId === instanceId;
});
if (limit) {
instanceLogs = instanceLogs.slice(Math.max(instanceLogs.length - limit, 0));
}
return instanceLogs;
}
async getAllLogTables(limit = 100) {
const configDataEntry = await config.readConfigData('processLogs');
const res = { standard: await this.getStandardLogTables(limit) };
const processesTables = await Promise.all(
configDataEntry.map(async (process) => [
process.definitionId,
await this.getProcessLogTables(process.id, limit),
])
);
processesTables.forEach((processTables) => {
[, res[processTables[0]]] = processTables;
});
return res;
}
/**
* Deletes a logging table with a specific name
* @param {string} name The name of the table that is to be deleted.
* @requires {module:@proceed/system.Data}
*/
deleteLoggingTable(name) {
data.delete(name);
}
/**
* Sets the standardLogs variable in the logging_meta_data to 0 and move monitoring
* to monitoring_old
*/
async clearStandardLogs() {
const old = await data.read('monitoring');
await data.delete('monitoring_old');
// TODO: do this with a bulk write instead of this once supported
Object.keys(old).forEach((key) => {
data.write(`monitoring_old/${key}`, old[key]);
});
await data.delete('monitoring');
this.rotationUtils.clearStandardLogs();
}
/**
* Sets the currentLogs variable in the logging_meta_data for a given process to 0
* @param {string} processID the process for which the operation is to be performed
*/
clearProcessLogs(processID) {
this.rotationUtils.clearProcessLogs(processID);
}
/**
* Sets the tables variable in the logging_meta_data for a given process to 0
* @param {string} processID the process for which the operation is to be performed
*/
clearProcessTables(processID) {
this.rotationUtils.clearProcessTables(processID);
}
/**
* Descreases the tables counter in the logging_meta_data by a given number
* @param {string} processID the process for which the operation is to be performed
* @param {number} number the number by which the tables counter is to be decreased
*/
decreaseProcessTables(processID, number) {
this.rotationUtils.decreaseProcessTables(processID, number);
}
async deleteStandardLogs() {
await data.delete('monitoring');
await data.delete('monitoring_old');
this.rotationUtils.clearStandardLogs();
}
async deleteProcessesLogs() {
const configDataEntry = await config.readConfigData('processLogs');
configDataEntry.forEach((processLogData) => {
const processID = processLogData.id;
const currentID = processLogData.currentTableID;
const numOfTables = processLogData.tables;
const removeStartID = Math.max(currentID + 1 - numOfTables, 0);
for (let i = removeStartID; i !== currentID + 1; i += 1) {
this.deleteLoggingTable(`${i}_monitoring_${processID}`);
}
});
const loggingData = JSON.parse(await data.read('logging_meta_data/config'));
loggingData.processLogs = [];
await data.write('logging_meta_data/config', JSON.stringify(loggingData));
}
async deleteProcessLogs(processID) {
const configDataEntry = await config.readConfigData('processLogs');
// == to convert into strings if id is number
// eslint-disable-next-line eqeqeq
const processInfo = configDataEntry.find((c) => c.id == processID);
if (!processInfo) {
return;
}
const currentID = processInfo.currentTableID;
const numOfTables = processInfo.tables;
const removeStartID = Math.max(currentID + 1 - numOfTables, 0);
for (let i = removeStartID; i !== currentID + 1; i += 1) {
this.deleteLoggingTable(`${i}_monitoring_${processID}`);
}
const loggingData = JSON.parse(await data.read('logging_meta_data/config'));
loggingData.processLogs.splice(loggingData.processLogs.indexOf(processInfo), 1);
await data.write('logging_meta_data/config', JSON.stringify(loggingData));
}
async deleteInstanceLogs(processId, instanceId) {
const configDataEntry = await config.readConfigData('processLogs');
const processInfo = configDataEntry.find((c) => c.id == processId);
if (!processInfo) {
return;
}
const currentID = processInfo.currentTableID;
const numOfTables = processInfo.tables;
const removeStartID = Math.max(currentID + 1 - numOfTables, 0);
for (let i = removeStartID; i !== currentID + 1; i += 1) {
const logTable = await data.read(`${i}_monitoring_${processId}`);
const instanceEntries = Object.entries(logTable || {})
.filter(([key, value]) => {
const entry = JSON.parse(value);
return entry.instanceId === instanceId;
})
.map(([key]) => key);
const deletePromises = instanceEntries.map(async (entry) => {
await data.delete(`${i}_monitoring_${processId}/${entry}`);
});
await Promise.all(deletePromises);
}
}
async deleteAllLogs() {
this.deleteStandardLogs();
this.deleteProcessesLogs();
}
}
/*
* Retruns the singleton instance and creates it if necessary
* returns the logging module's instance
*/
function getInstance() {
if (!singletonInstance) {
singletonInstance = new Logging();
}
return singletonInstance;
}
module.exports = getInstance();