node-red-contrib-msg-ozlogger
Version:
Basic implementations for a log helper on node-red
194 lines (177 loc) • 6.81 kB
JavaScript
const path = require('path');
const bodyParser = require('body-parser');
const fs = require('fs');
const fse = require('fs-extra');
const decache = require('decache');
const stringify = require("json-stringify-safe")
const nodeName = path.basename(__filename).split('.')[0];
let loggedNodes = {};
let flows = {};
let subflows = {};
let nodesInFlow = {};
module.exports = function (RED) {
const msgTracerConfigFolderPath = path.resolve(RED.settings.userDir, 'msg-ozlogger-config');
const msgTracerConfigPath = path.join(msgTracerConfigFolderPath, 'local.json');
let flowManagerConfig;
const persistFlowManagerConfigFile = async function () {
await fse.ensureDir(msgTracerConfigFolderPath);
fs.writeFile(msgTracerConfigPath, JSON.stringify(flowManagerConfig), 'utf8', function () {
});
};
try {
const buEnv = process.env["NODE_CONFIG_DIR"];
process.env["NODE_CONFIG_DIR"] = msgTracerConfigFolderPath;
decache('config');
flowManagerConfig = JSON.parse(JSON.stringify(require('config')));
decache('config');
if (buEnv === undefined) {
delete process.env["NODE_CONFIG_DIR"];
} else {
process.env["NODE_CONFIG_DIR"] = buEnv;
}
if (Object.keys(flowManagerConfig).length === 0) throw null;
loggedNodes = flowManagerConfig.nodes;
} catch (e) {
flowManagerConfig = {};
persistFlowManagerConfigFile();
}
function publishloggedNodes() {
RED.comms.publish(nodeName + "/lognode", loggedNodes, true);
}
RED.httpAdmin.post('/' + nodeName + '/removelog', bodyParser.json(), function (req, res) {
if(req.body && req.body.nodeId){
delete flowManagerConfig.nodes[req.body.nodeId];
persistFlowManagerConfigFile();
publishloggedNodes();
}
res.send({ok: true});
})
RED.httpAdmin.get('/' + nodeName + '/config', function (req, res) {
publishloggedNodes();
res.send();
})
//Recebe o evento do botão pra habilitar logger neste nó
RED.httpAdmin.put('/' + nodeName + '/lognode', bodyParser.json(), function (req, res) {
if (req.body && req.body.constructor === {}.constructor) {
loggedNodes = req.body;
flowManagerConfig.nodes = loggedNodes;
persistFlowManagerConfigFile();
res.send({ok: true});
publishloggedNodes();
} else {
res.status(202).send({error: 'Must send json object {...}'});
}
});
function prepareNodesAndFlows(configs) {
flows = {};
subflows = {};
nodesInFlow = {}
for (let node of configs.config.flows) {
if (node.type === 'tab') {
flows[node.id] = node.label;
}
if (node.type === 'subflow') {
subflows[node.id] = node.label;
}
if (node.z && flows[node.z]) {
nodesInFlow[node.id] = flows[node.z];
}
if (node.z && subflows[node.z]) {
nodesInFlow[node.id] = subflows[node.z];
}
}
publishloggedNodes();
}
function sendToLog(source, msg) {
let nodeId = source.node.id.split('-');
nodeId = nodeId[nodeId.length - 1];
let flowOrSubflow = nodesInFlow[nodeId];
let level = loggedNodes[nodeId];
if (level && level.whatLog) {
loggedNodes[nodeId].name = source.node.name;
let toLog = {
flow: flowOrSubflow,
nodeId: nodeId,
msgId: msg._msgid,
...level
};
switch (level.whatLog) {
case'msg':
toLog.msg = msg;
break;
case'msg-reqres':
toLog.msg = deepClone(msg, ['req', 'res']);
break;
case 'payload':
toLog.msg = msg.payload;
break;
case 'payload+ozextralogs':
toLog.msg = {
payload: msg.payload,
ozextralogs: msg.ozextralogs
}
break;
case 'event-only':
break
}
console.log(stringify(toLog));
}
}
RED.events.on('nodes:change', prepareNodesAndFlows);
RED.events.on('flows:started', prepareNodesAndFlows);
RED.hooks.add("onReceive", (receiveEvent) => {
sendToLog(receiveEvent.destination, receiveEvent.msg);
})
//vamos usar apenas para nós que criam mensagem de eventos externos
RED.hooks.add("preRoute", (sendEvent) => {
if (sendEvent.source && sendEvent.source.type === "http in") {
sendToLog(sendEvent.source, sendEvent.msg);
}
});
//vamos logar caso exista algum erro não tratado na mensagem, vamos logar sempre
RED.hooks.add("onComplete", (completeEvent) => {
if (completeEvent.error) {
let nodeId = completeEvent.node.node.id;
let flowOrSubflow = nodesInFlow[nodeId];
console.log(stringify(
{
level: 'error',
flow: flowOrSubflow,
name: completeEvent.node.node.name,
nodeId: nodeId,
msgId: completeEvent.msg._msgid,
msg: {
payload: stringify(completeEvent.msg.payload),
error: stringify(completeEvent.error)
}
}));
}
});
//----------------- helper functions -----------------
function deepClone(obj, keysToRemove) {
// Mapa de referências para lidar com referências circulares
const references = new WeakMap();
function _clone(value) {
if (typeof value !== 'object' || value === null) return value;
if (references.has(value)) return references.get(value);
let clonedObj;
if (Array.isArray(value)) {
clonedObj = [];
references.set(value, clonedObj);
for (let i = 0; i < value.length; i++) {
clonedObj[i] = _clone(value[i]);
}
} else {
clonedObj = {};
references.set(value, clonedObj);
for (let key in value) {
if (Object.prototype.hasOwnProperty.call(value, key) && !keysToRemove.includes(key)) {
clonedObj[key] = _clone(value[key]);
}
}
}
return clonedObj;
}
return _clone(obj);
}
};