Update logging/metric system

This commit is contained in:
Nick O'Leary 2015-02-03 22:02:26 +00:00
parent 7d6ce1ec12
commit 0aaea1ec40
24 changed files with 245 additions and 209 deletions

20
red.js
View File

@ -170,34 +170,34 @@ RED.start().then(function() {
if (settings.httpAdminRoot !== false || settings.httpNodeRoot !== false || settings.httpStatic) { if (settings.httpAdminRoot !== false || settings.httpNodeRoot !== false || settings.httpStatic) {
server.on('error', function(err) { server.on('error', function(err) {
if (err.errno === "EADDRINUSE") { if (err.errno === "EADDRINUSE") {
util.log('[red] Unable to listen on '+getListenPath()); RED.log.error('Unable to listen on '+getListenPath());
util.log('[red] Error: port in use'); RED.log.error('Error: port in use');
} else { } else {
util.log('[red] Uncaught Exception:'); RED.log.error('Uncaught Exception:');
if (err.stack) { if (err.stack) {
util.log(err.stack); RED.log.error(err.stack);
} else { } else {
util.log(err); RED.log.error(err);
} }
} }
process.exit(1); process.exit(1);
}); });
server.listen(settings.uiPort,settings.uiHost,function() { server.listen(settings.uiPort,settings.uiHost,function() {
if (settings.httpAdminRoot === false) { if (settings.httpAdminRoot === false) {
util.log('[red] Admin UI disabled'); RED.log.info('Admin UI disabled');
} }
process.title = 'node-red'; process.title = 'node-red';
util.log('[red] Server now running at '+getListenPath()); RED.log.info('Server now running at '+getListenPath());
}); });
} else { } else {
util.log('[red] Running in headless mode'); util.log('[red] Running in headless mode');
} }
}).otherwise(function(err) { }).otherwise(function(err) {
util.log("[red] Failed to start server:"); RED.log.error("Failed to start server:");
if (err.stack) { if (err.stack) {
util.log(err.stack); RED.log.error(err.stack);
} else { } else {
util.log(err); RED.log.error(err);
} }
}); });

View File

@ -1,5 +1,5 @@
/** /**
* Copyright 2014 IBM Corp. * Copyright 2014, 2015 IBM Corp.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,8 +17,8 @@ var express = require('express');
var fs = require("fs"); var fs = require("fs");
var events = require("../events"); var events = require("../events");
var path = require("path"); var path = require("path");
var util = require("util");
var log = require("../log");
var redNodes = require("../nodes"); var redNodes = require("../nodes");
var settings = require("../settings"); var settings = require("../settings");
@ -32,9 +32,9 @@ module.exports = {
redNodes.setFlows(flows,deploymentType).then(function() { redNodes.setFlows(flows,deploymentType).then(function() {
res.send(204); res.send(204);
}).otherwise(function(err) { }).otherwise(function(err) {
util.log("[red] Error saving flows : "+err); log.warn("Error saving flows : "+err.message);
log.warn(err.stack);
res.send(500,err.message); res.send(500,err.message);
console.log(err.stack);
}); });
} }
} }

View File

@ -1,5 +1,5 @@
/** /**
* Copyright 2013, 2014 IBM Corp. * Copyright 2013, 2015 IBM Corp.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,10 +14,9 @@
* limitations under the License. * limitations under the License.
**/ **/
var util = require("util");
var redApp = null; var redApp = null;
var storage = require("../storage"); var storage = require("../storage");
var log = require("../log");
function createLibrary(type) { function createLibrary(type) {
if (redApp) { if (redApp) {
@ -33,7 +32,7 @@ function createLibrary(type) {
} }
}).otherwise(function(err) { }).otherwise(function(err) {
if (err) { if (err) {
util.log("[red] Error loading library entry '"+path+"' : "+err); log.warn("Error loading library entry '"+path+"' : "+err);
if (err.message.indexOf('forbidden') === 0) { if (err.message.indexOf('forbidden') === 0) {
res.send(403); res.send(403);
return; return;
@ -53,7 +52,7 @@ function createLibrary(type) {
storage.saveLibraryEntry(type,path,req.query,fullBody).then(function() { storage.saveLibraryEntry(type,path,req.query,fullBody).then(function() {
res.send(204); res.send(204);
}).otherwise(function(err) { }).otherwise(function(err) {
util.log("[red] Error saving library entry '"+path+"' : "+err); log.warn("Error saving library entry '"+path+"' : "+err);
if (err.message.indexOf('forbidden') === 0) { if (err.message.indexOf('forbidden') === 0) {
res.send(403); res.send(403);
return; return;
@ -81,7 +80,7 @@ module.exports = {
res.send(data); res.send(data);
}).otherwise(function(err) { }).otherwise(function(err) {
if (err) { if (err) {
util.log("[red] Error loading flow '"+req.params[0]+"' : "+err); log.warn("Error loading flow '"+req.params[0]+"' : "+err);
if (err.message.indexOf('forbidden') === 0) { if (err.message.indexOf('forbidden') === 0) {
res.send(403); res.send(403);
return; return;
@ -95,7 +94,7 @@ module.exports = {
storage.saveFlow(req.params[0],flow).then(function() { storage.saveFlow(req.params[0],flow).then(function() {
res.send(204); res.send(204);
}).otherwise(function(err) { }).otherwise(function(err) {
util.log("[red] Error loading flow '"+req.params[0]+"' : "+err); log.warn("Error loading flow '"+req.params[0]+"' : "+err);
if (err.message.indexOf('forbidden') === 0) { if (err.message.indexOf('forbidden') === 0) {
res.send(403); res.send(403);
return; return;

View File

@ -1,5 +1,5 @@
/** /**
* Copyright 2014 IBM Corp. * Copyright 2014, 2015 IBM Corp.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,12 +17,12 @@ var express = require('express');
var fs = require("fs"); var fs = require("fs");
var path = require("path"); var path = require("path");
var when = require('when'); var when = require('when');
var util = require('util');
var events = require("../events"); var events = require("../events");
var redNodes = require("../nodes"); var redNodes = require("../nodes");
var comms = require("../comms"); var comms = require("../comms");
var server = require("../server"); var server = require("../server");
var log = require("../log");
var settings = require("../settings"); var settings = require("../settings");
@ -196,13 +196,13 @@ module.exports = {
} }
if (info.enabled === body.enabled && !info.err) { if (info.enabled === body.enabled && !info.err) {
comms.publish("node/"+(body.enabled?"enabled":"disabled"),info,false); comms.publish("node/"+(body.enabled?"enabled":"disabled"),info,false);
util.log("[red] "+(body.enabled?"Enabled":"Disabled")+" node types:"); log.info(" "+(body.enabled?"Enabled":"Disabled")+" node types:");
for (var j = 0; j < info.types.length; j++) { for (var j = 0; j < info.types.length; j++) {
util.log("[red] - " + info.types[j]); log.info(" - " + info.types[j]);
} }
} else if (body.enabled && info.err) { } else if (body.enabled && info.err) {
util.log("[red] Failed to enable node:"); log.warn("Failed to enable node:");
util.log("[red] - "+info.name+" : "+info.err); log.warn(" - "+info.name+" : "+info.err);
} }
} }
} }
@ -241,13 +241,13 @@ function putNode(node, enabled) {
if (info.enabled === enabled && !info.err) { if (info.enabled === enabled && !info.err) {
comms.publish("node/"+(enabled?"enabled":"disabled"),info,false); comms.publish("node/"+(enabled?"enabled":"disabled"),info,false);
util.log("[red] "+(enabled?"Enabled":"Disabled")+" node types:"); log.info(" "+(enabled?"Enabled":"Disabled")+" node types:");
for (var i=0;i<info.types.length;i++) { for (var i=0;i<info.types.length;i++) {
util.log("[red] - "+info.types[i]); log.info(" - "+info.types[i]);
} }
} else if (enabled && info.err) { } else if (enabled && info.err) {
util.log("[red] Failed to enable node:"); log.warn("Failed to enable node:");
util.log("[red] - "+info.name+" : "+info.err); log.warn(" - "+info.name+" : "+info.err);
} }
} }

View File

@ -1,5 +1,5 @@
/** /**
* Copyright 2014 IBM Corp. * Copyright 2014, 2015 IBM Corp.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -15,7 +15,7 @@
**/ **/
var ws = require("ws"); var ws = require("ws");
var util = require("util"); var log = require("./log");
var server; var server;
var settings; var settings;
@ -57,7 +57,7 @@ function start() {
try { try {
msg = JSON.parse(data); msg = JSON.parse(data);
} catch(err) { } catch(err) {
util.log("[red:comms] received malformed message : "+err.toString()); log.warn("comms received malformed message : "+err.toString());
return; return;
} }
if (msg.subscribe) { if (msg.subscribe) {
@ -65,12 +65,12 @@ function start() {
} }
}); });
ws.on('error', function(err) { ws.on('error', function(err) {
util.log("[red:comms] error : "+err.toString()); log.warn("comms error : "+err.toString());
}); });
}); });
wsServer.on('error', function(err) { wsServer.on('error', function(err) {
util.log("[red:comms] server error : "+err.toString()); log.warn("comms server error : "+err.toString());
}); });
lastSentTime = Date.now(); lastSentTime = Date.now();
@ -110,7 +110,7 @@ function publishTo(ws,topic,data) {
try { try {
ws.send(msg); ws.send(msg);
} catch(err) { } catch(err) {
util.log("[red:comms] send error : "+err.toString()); log.warn("comms send error : "+err.toString());
} }
} }

View File

@ -1,5 +1,5 @@
/** /**
* Copyright 2014 IBM Corp. * Copyright 2014, 2015 IBM Corp.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,64 +17,63 @@
var util = require("util"); var util = require("util");
var EventEmitter = require("events").EventEmitter; var EventEmitter = require("events").EventEmitter;
var logLevel;
var metricsOn;
var levels = { var levels = {
"fatal" : 10, fatal: 10,
"error" : 20, error: 20,
"warn" : 30, warn: 30,
"info" : 40, info: 40,
"debug" : 50, debug: 50,
"trace" : 60 trace: 60,
metric: 99
}
var levelNames = {
10: "fatal",
20: "error",
30: "warn",
40: "info",
50: "debug",
60: "trace",
99: "metric",
} }
var logHandlers = []; var logHandlers = [];
var ConsoleLogHandler = new EventEmitter(); var ConsoleLogHandler = function(settings) {
ConsoleLogHandler.on("log",function(msg) { this.logLevel = levels[settings.level]||levels.info;
if ((msg.level === 'metric')) { this.metricsOn = settings.metrics||false;
if (metricsOn) {
util.log("["+msg.level+"] ["+msg.event+":"+ msg.nodeid+":"+ msg.msguuid+"]"+metrics); this.on("log",function(msg) {
if (this.shouldReportMessage(msg.level)) {
if (msg.level == log.METRIC) {
util.log("[metric] "+JSON.stringify(msg));
} else {
util.log("["+levelNames[msg.level]+"] "+(msg.type?"["+msg.type+":"+(msg.name||msg.id)+"] ":"")+msg.msg);
}
} }
} else if (shouldReportMessage(msg.level)) { });
util.log("["+msg.level+"] ["+msg.type+":"+(msg.name||msg.id)+"] "+msg.msg); }
} util.inherits(ConsoleLogHandler, EventEmitter);
});
function shouldReportMessage(msglevel) { ConsoleLogHandler.prototype.shouldReportMessage = function(msglevel) {
if (((msglevel === 'fatal') && (logLevel === 10)) || return msglevel <= this.logLevel || (msglevel == log.METRIC && this.metricsOn);
((msglevel === 'error') && (logLevel >= 20)) ||
((msglevel === 'warn') && (logLevel >= 30)) ||
((msglevel === 'info') && (logLevel >= 40)) ||
((msglevel === 'debug') && (logLevel >= 50)) ||
((msglevel === 'trace') && (logLevel === 60))) {
return true;
}
return false;
} }
var log = module.exports = { var log = module.exports = {
FATAL: 10,
ERROR: 20,
WARN: 30,
INFO: 40,
DEBUG: 50,
TRACE: 60,
METRIC: 99,
init: function(settings) { init: function(settings) {
if (settings.logLevel) { var consoleSettings = {};
var levelNames = Object.keys(levels); if (settings.logging) {
levelNames.forEach(function(levelName) { consoleSettings = settings.logging.console || {};
if (levelName === settings.logLevel) {
logLevel =levels[levelName];
}
});
}
if (!logLevel) {
// handles case if someone has put in garbage for the log level
logLevel = 40;
}
if (settings.metricsOn) {
metricsOn = settings.metricsOn
} else {
metricsOn = false;
} }
log.addHandler(new ConsoleLogHandler(consoleSettings));
}, },
addHandler: function(func) { addHandler: function(func) {
@ -86,7 +85,12 @@ var log = module.exports = {
logHandlers.forEach(function(handler) { logHandlers.forEach(function(handler) {
handler.emit("log",msg); handler.emit("log",msg);
}); });
},
info: function(msg) {
log.log({level:log.INFO,msg:msg});
},
warn: function(msg) {
log.log({level:log.WARN,msg:msg});
} }
} }
log.addHandler(ConsoleLogHandler);

View File

@ -14,7 +14,6 @@
* limitations under the License. * limitations under the License.
**/ **/
var util = require("util");
var when = require("when"); var when = require("when");
var clone = require("clone"); var clone = require("clone");
@ -22,6 +21,7 @@ var typeRegistry = require("./registry");
var credentials = require("./credentials"); var credentials = require("./credentials");
var redUtil = require("../util"); var redUtil = require("../util");
var events = require("../events"); var events = require("../events");
var log = require("../log");
function getID() { function getID() {
return (1+Math.random()*4294967295).toString(16); return (1+Math.random()*4294967295).toString(16);
@ -35,10 +35,10 @@ function createNode(type,config) {
nn = new nt(clone(config)); nn = new nt(clone(config));
} }
catch (err) { catch (err) {
util.log("[red] "+type+" : "+err); log.warn(type+" : "+err);
} }
} else { } else {
util.log("[red] unknown type: "+type); log.warn("unknown type: "+type);
} }
return nn; return nn;
} }

View File

@ -1,5 +1,5 @@
/** /**
* Copyright 2014 IBM Corp. * Copyright 2014, 2015 IBM Corp.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -96,10 +96,10 @@ Node.prototype.send = function(msg) {
// A single message and a single wire on output 0 // A single message and a single wire on output 0
// TODO: pre-load flows.get calls - cannot do in constructor // TODO: pre-load flows.get calls - cannot do in constructor
// as not all nodes are defined at that point // as not all nodes are defined at that point
if (!msg._messageUuid) { if (!msg._id) {
msg._messageUuid = constructUniqueIdentifier(); msg._id = constructUniqueIdentifier();
} }
this.metric("Node.prototype.send", msg); this.metric("send",msg);
node = flows.get(this._wire); node = flows.get(this._wire);
if (node) { if (node) {
node.receive(msg); node.receive(msg);
@ -116,6 +116,8 @@ Node.prototype.send = function(msg) {
// any calls to node.receive // any calls to node.receive
var sendEvents = []; var sendEvents = [];
var sentMessageId = null;
// for each output of node eg. [msgs to output 0, msgs to output 1, ...] // for each output of node eg. [msgs to output 0, msgs to output 1, ...]
for (var i = 0; i < numOutputs; i++) { for (var i = 0; i < numOutputs; i++) {
var wires = this.wires[i]; // wires leaving output i var wires = this.wires[i]; // wires leaving output i
@ -132,20 +134,15 @@ Node.prototype.send = function(msg) {
if (node) { if (node) {
// for each msg to send eg. [[m1, m2, ...], ...] // for each msg to send eg. [[m1, m2, ...], ...]
for (k = 0; k < msgs.length; k++) { for (k = 0; k < msgs.length; k++) {
var m = msgs[k];
if (!sentMessageId) {
sentMessageId = m._id;
}
if (msgSent) { if (msgSent) {
var clonedmsg = redUtil.cloneMessage(msgs[k]); var clonedmsg = redUtil.cloneMessage(m);
// overwriting any previously written uuid because a cloned
// message is a different one
clonedmsg._messageUuid = constructUniqueIdentifier();
this.metric("Node.prototype.send",clonedmsg,msgs[k]._messageUuid);
sendEvents.push({n:node,m:clonedmsg}); sendEvents.push({n:node,m:clonedmsg});
} else { } else {
// first msg sent so don't clone sendEvents.push({n:node,m:m});
if (!msgs[k]._messageUuid) {
msgs[k]._messageUuid = constructUniqueIdentifier();
}
this.metric("Node.prototype.send", msgs[k]);
sendEvents.push({n:node,m:msgs[k]});
msgSent = true; msgSent = true;
} }
} }
@ -154,9 +151,16 @@ Node.prototype.send = function(msg) {
} }
} }
} }
if (!sentMessageId) {
sentMessageId = constructUniqueIdentifier();
}
this.metric("send",{_id:sentMessageId});
for (i=0;i<sendEvents.length;i++) { for (i=0;i<sendEvents.length;i++) {
var ev = sendEvents[i]; var ev = sendEvents[i];
if (!ev.m._id) {
ev.m._id = sentMessageId;
}
ev.n.receive(ev.m); ev.n.receive(ev.m);
} }
}; };
@ -165,10 +169,10 @@ Node.prototype.receive = function(msg) {
if (!msg) { if (!msg) {
msg = {}; msg = {};
} }
if (!msg._messageUuid) { if (!msg._id) {
msg._messageUuid = constructUniqueIdentifier(); msg._id = constructUniqueIdentifier();
} }
this.metric("Node.prototype.receive",msg); this.metric("receive",msg);
this.emit("input", msg); this.emit("input", msg);
}; };
@ -186,25 +190,24 @@ function log_helper(self, level, msg) {
} }
Node.prototype.log = function(msg) { Node.prototype.log = function(msg) {
log_helper(this, 'info', msg); log_helper(this, Log.INFO, msg);
}; };
Node.prototype.warn = function(msg) { Node.prototype.warn = function(msg) {
log_helper(this, 'warn', msg); log_helper(this, Log.WARN, msg);
}; };
Node.prototype.error = function(msg) { Node.prototype.error = function(msg) {
log_helper(this, 'error', msg); log_helper(this, Log.ERROR, msg);
}; };
Node.prototype.metric = function(eventname, msg, metricValue) { Node.prototype.metric = function(eventname, msg, metricValue) {
var metrics = {}; var metrics = {};
metrics.level = "metric"; metrics.level = Log.METRIC;
metrics.nodeid = this.id; metrics.nodeid = this.id;
metrics.event = eventname; metrics.event = "node."+this.type+"."+eventname;
metrics.msguuid = msg._messageUuid; metrics.msgid = msg._id;
metrics.metric = metricValue; metrics.metric = metricValue;
Log.log(metrics); Log.log(metrics);
} }

View File

@ -1,5 +1,5 @@
/** /**
* Copyright 2014 IBM Corp. * Copyright 2014, 2015 IBM Corp.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,9 +14,10 @@
* limitations under the License. * limitations under the License.
**/ **/
var util = require("util");
var when = require("when"); var when = require("when");
var log = require("../log");
var credentialCache = {}; var credentialCache = {};
var storage = null; var storage = null;
var credentialsDef = {}; var credentialsDef = {};
@ -75,7 +76,7 @@ module.exports = {
return storage.getCredentials().then(function (creds) { return storage.getCredentials().then(function (creds) {
credentialCache = creds; credentialCache = creds;
}).otherwise(function (err) { }).otherwise(function (err) {
util.log("[red] Error loading credentials : " + err); log.warn("Error loading credentials : " + err);
}); });
}, },
@ -168,7 +169,7 @@ module.exports = {
var dashedType = nodeType.replace(/\s+/g, '-'); var dashedType = nodeType.replace(/\s+/g, '-');
var definition = credentialsDef[dashedType]; var definition = credentialsDef[dashedType];
if (!definition) { if (!definition) {
util.log('Credential Type ' + nodeType + ' is not registered.'); log.warn('Credential Type ' + nodeType + ' is not registered.');
return; return;
} }

View File

@ -14,7 +14,6 @@
* limitations under the License. * limitations under the License.
**/ **/
var util = require("util");
var clone = require("clone"); var clone = require("clone");
var when = require("when"); var when = require("when");
@ -37,7 +36,7 @@ var activeConfigNodes = {};
events.on('type-registered',function(type) { events.on('type-registered',function(type) {
if (activeFlow) { if (activeFlow) {
if (activeFlow.typeRegistered(type)) { if (activeFlow.typeRegistered(type)) {
util.log("[red] Missing type registered: "+type); log.info("Missing type registered: "+type);
} }
} }
}); });
@ -58,7 +57,7 @@ var flowNodes = module.exports = {
flowNodes.startFlows(); flowNodes.startFlows();
}); });
}).otherwise(function(err) { }).otherwise(function(err) {
util.log("[red] Error loading flows : "+err); log.warn("Error loading flows : "+err);
console.log(err.stack); console.log(err.stack);
}); });
}, },
@ -162,22 +161,22 @@ var flowNodes = module.exports = {
} }
}, },
startFlows: function() { startFlows: function() {
util.log("[red] Starting flows"); log.info("Starting flows");
try { try {
activeFlow.start(); activeFlow.start();
} catch(err) { } catch(err) {
var missingTypes = activeFlow.getMissingTypes(); var missingTypes = activeFlow.getMissingTypes();
if (missingTypes.length > 0) { if (missingTypes.length > 0) {
util.log("[red] Waiting for missing types to be registered:"); log.info("Waiting for missing types to be registered:");
for (var i=0;i<missingTypes.length;i++) { for (var i=0;i<missingTypes.length;i++) {
util.log("[red] - "+missingTypes[i]); log.info(" - "+missingTypes[i]);
} }
} }
} }
}, },
stopFlows: function() { stopFlows: function() {
util.log("[red] Stopping flows"); log.info("Stopping flows");
return activeFlow.stop(); return activeFlow.stop();
} }
}; };

View File

@ -1,5 +1,5 @@
/** /**
* Copyright 2013, 2014 IBM Corp. * Copyright 2013, 2015 IBM Corp.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -109,6 +109,7 @@ module.exports = {
// Node registry // Node registry
createNode: createNode, createNode: createNode,
getNode: flows.get, getNode: flows.get,
eachNode: flows.eachNode,
addNode: registry.addNode, addNode: registry.addNode,
removeNode: removeNode, removeNode: removeNode,

View File

@ -1,5 +1,5 @@
/** /**
* Copyright 2014 IBM Corp. * Copyright 2014, 2015 IBM Corp.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -1,5 +1,5 @@
/** /**
* Copyright 2013 IBM Corp. * Copyright 2013, 2015 IBM Corp.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -1,5 +1,5 @@
/** /**
* Copyright 2013, 2014 IBM Corp. * Copyright 2013, 2015 IBM Corp.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -15,13 +15,13 @@
**/ **/
var express = require('express'); var express = require('express');
var util = require('util');
var when = require('when'); var when = require('when');
var child_process = require('child_process'); var child_process = require('child_process');
var redNodes = require("./nodes"); var redNodes = require("./nodes");
var comms = require("./comms"); var comms = require("./comms");
var storage = require("./storage"); var storage = require("./storage");
var log = require("./log");
var app = null; var app = null;
var nodeApp = null; var nodeApp = null;
@ -49,9 +49,9 @@ function start() {
settings.load(storage).then(function() { settings.load(storage).then(function() {
console.log("\nWelcome to Node-RED\n===================\n"); console.log("\nWelcome to Node-RED\n===================\n");
if (settings.version) { if (settings.version) {
util.log("[red] Version: "+settings.version); log.info("Version: "+settings.version);
} }
util.log("[red] Loading palette nodes"); log.info("Loading palette nodes");
redNodes.init(settings,storage); redNodes.init(settings,storage);
redNodes.load().then(function() { redNodes.load().then(function() {
var i; var i;
@ -59,19 +59,19 @@ function start() {
var nodeErrors = nodes.filter(function(n) { return n.err!=null;}); var nodeErrors = nodes.filter(function(n) { return n.err!=null;});
var nodeMissing = nodes.filter(function(n) { return n.module && n.enabled && !n.loaded && !n.err;}); var nodeMissing = nodes.filter(function(n) { return n.module && n.enabled && !n.loaded && !n.err;});
if (nodeErrors.length > 0) { if (nodeErrors.length > 0) {
util.log("------------------------------------------"); log.warn("------------------------------------------");
if (settings.verbose) { if (settings.verbose) {
for (i=0;i<nodeErrors.length;i+=1) { for (i=0;i<nodeErrors.length;i+=1) {
util.log("["+nodeErrors[i].name+"] "+nodeErrors[i].err); log.warn("["+nodeErrors[i].name+"] "+nodeErrors[i].err);
} }
} else { } else {
util.log("[red] Failed to register "+nodeErrors.length+" node type"+(nodeErrors.length==1?"":"s")); log.warn("Failed to register "+nodeErrors.length+" node type"+(nodeErrors.length==1?"":"s"));
util.log("[red] Run with -v for details"); log.warn("Run with -v for details");
} }
util.log("------------------------------------------"); log.warn("------------------------------------------");
} }
if (nodeMissing.length > 0) { if (nodeMissing.length > 0) {
util.log("[red] Missing node modules:"); log.warn("Missing node modules:");
var missingModules = {}; var missingModules = {};
for (i=0;i<nodeMissing.length;i++) { for (i=0;i<nodeMissing.length;i++) {
var missing = nodeMissing[i]; var missing = nodeMissing[i];
@ -80,7 +80,7 @@ function start() {
var promises = []; var promises = [];
for (i in missingModules) { for (i in missingModules) {
if (missingModules.hasOwnProperty(i)) { if (missingModules.hasOwnProperty(i)) {
util.log("[red] - "+i+": "+missingModules[i].join(", ")); log.warn(" - "+i+": "+missingModules[i].join(", "));
if (settings.autoInstallModules) { if (settings.autoInstallModules) {
installModule(i).otherwise(function(err) { installModule(i).otherwise(function(err) {
// Error already reported. Need the otherwise handler // Error already reported. Need the otherwise handler
@ -90,7 +90,7 @@ function start() {
} }
} }
if (!settings.autoInstallModules) { if (!settings.autoInstallModules) {
util.log("[red] Removing modules from config"); log.info("Removing modules from config");
redNodes.cleanModuleList(); redNodes.cleanModuleList();
} }
} }
@ -113,10 +113,10 @@ function start() {
function reportAddedModules(info) { function reportAddedModules(info) {
comms.publish("node/added",info,false); comms.publish("node/added",info,false);
if (info.length > 0) { if (info.length > 0) {
util.log("[red] Added node types:"); log.info("Added node types:");
for (var i=0;i<info.length;i++) { for (var i=0;i<info.length;i++) {
for (var j=0;j<info[i].types.length;j++) { for (var j=0;j<info[i].types.length;j++) {
util.log("[red] - "+ log.info(" - "+
(info[i].module?info[i].module+":":"")+ (info[i].module?info[i].module+":":"")+
info[i].types[j]+ info[i].types[j]+
(info[i].err?" : "+info[i].err:"") (info[i].err?" : "+info[i].err:"")
@ -129,10 +129,10 @@ function reportAddedModules(info) {
function reportRemovedModules(removedNodes) { function reportRemovedModules(removedNodes) {
comms.publish("node/removed",removedNodes,false); comms.publish("node/removed",removedNodes,false);
util.log("[red] Removed node types:"); log.info("Removed node types:");
for (var j=0;j<removedNodes.length;j++) { for (var j=0;j<removedNodes.length;j++) {
for (var i=0;i<removedNodes[j].types.length;i++) { for (var i=0;i<removedNodes[j].types.length;i++) {
util.log("[red] - "+(removedNodes[i].module?removedNodes[i].module+":":"")+removedNodes[j].types[i]); log.info(" - "+(removedNodes[i].module?removedNodes[i].module+":":"")+removedNodes[j].types[i]);
} }
} }
return removedNodes; return removedNodes;
@ -145,24 +145,24 @@ function installModule(module) {
reject(new Error("Invalid module name")); reject(new Error("Invalid module name"));
return; return;
} }
util.log("[red] Installing module: "+module); log.info("Installing module: "+module);
var child = child_process.exec('npm install --production '+module, function(err, stdin, stdout) { var child = child_process.exec('npm install --production '+module, function(err, stdin, stdout) {
if (err) { if (err) {
var lookFor404 = new RegExp(" 404 .*"+module+"$","m"); var lookFor404 = new RegExp(" 404 .*"+module+"$","m");
if (lookFor404.test(stdout)) { if (lookFor404.test(stdout)) {
util.log("[red] Installation of module "+module+" failed: module not found"); log.warn("Installation of module "+module+" failed: module not found");
var e = new Error(); var e = new Error();
e.code = 404; e.code = 404;
reject(e); reject(e);
} else { } else {
util.log("[red] Installation of module "+module+" failed:"); log.warn("Installation of module "+module+" failed:");
util.log("------------------------------------------"); log.warn("------------------------------------------");
console.log(err.toString()); log.warn(err.toString());
util.log("------------------------------------------"); log.warn("------------------------------------------");
reject(new Error("Install failed")); reject(new Error("Install failed"));
} }
} else { } else {
util.log("[red] Installed module: "+module); log.info("Installed module: "+module);
resolve(redNodes.addModule(module).then(reportAddedModules)); resolve(redNodes.addModule(module).then(reportAddedModules));
} }
}); });
@ -176,16 +176,16 @@ function uninstallModule(module) {
return; return;
} }
var list = redNodes.removeModule(module); var list = redNodes.removeModule(module);
util.log("[red] Removing module: "+module); log.info("Removing module: "+module);
var child = child_process.exec('npm remove '+module, function(err, stdin, stdout) { var child = child_process.exec('npm remove '+module, function(err, stdin, stdout) {
if (err) { if (err) {
util.log("[red] Removal of module "+module+" failed:"); log.warn("Removal of module "+module+" failed:");
util.log("------------------------------------------"); log.warn("------------------------------------------");
console.log(err.toString()); log.warn(err.toString());
util.log("------------------------------------------"); log.warn("------------------------------------------");
reject(new Error("Removal failed")); reject(new Error("Removal failed"));
} else { } else {
util.log("[red] Removed module: "+module); log.info("Removed module: "+module);
reportRemovedModules(list); reportRemovedModules(list);
resolve(list); resolve(list);
} }

View File

@ -18,10 +18,11 @@ var fs = require('fs');
var when = require('when'); var when = require('when');
var nodeFn = require('when/node/function'); var nodeFn = require('when/node/function');
var keys = require('when/keys'); var keys = require('when/keys');
var util = require('util');
var fspath = require("path"); var fspath = require("path");
var mkdirp = require("mkdirp"); var mkdirp = require("mkdirp");
var log = require("../log");
var promiseDir = nodeFn.lift(mkdirp); var promiseDir = nodeFn.lift(mkdirp);
var settings; var settings;
@ -170,12 +171,12 @@ var localfilesystem = {
var defer = when.defer(); var defer = when.defer();
fs.exists(flowsFullPath, function(exists) { fs.exists(flowsFullPath, function(exists) {
if (exists) { if (exists) {
util.log("[red] Loading flows : "+flowsFile); log.info("Loading flows : "+flowsFile);
defer.resolve(nodeFn.call(fs.readFile,flowsFullPath,'utf8').then(function(data) { defer.resolve(nodeFn.call(fs.readFile,flowsFullPath,'utf8').then(function(data) {
return JSON.parse(data); return JSON.parse(data);
})); }));
} else { } else {
util.log("[red] Flows file not found : "+flowsFile ); log.info("Flows file not found : "+flowsFile );
defer.resolve([]); defer.resolve([]);
} }
}); });
@ -236,7 +237,7 @@ var localfilesystem = {
try { try {
return JSON.parse(data); return JSON.parse(data);
} catch(err) { } catch(err) {
util.log("[red] Corrupted config detected - resetting"); log.info("Corrupted config detected - resetting");
return {}; return {};
} }
} else { } else {

View File

@ -134,16 +134,21 @@ module.exports = {
// arduino:require('duino') // arduino:require('duino')
}, },
// Level of logging to be recorded. Options are: // Configure the logging output
// fatal - only those errors which make the application unusable should be recorded logging: {
// error - record errors which are deemed fatal for a particular request + fatal errors // Only console logging is currently supported
// warn - record problems which are non fatal + errors + fatal errors console: {
// info - record information about the general running of the application + warn + error + fatal errors // Level of logging to be recorded. Options are:
// debug - record information which is more verbose than info + info + warn + error + fatal errors // fatal - only those errors which make the application unusable should be recorded
// trace - record very detailed logging + debug + info + warn + error + fatal errors // error - record errors which are deemed fatal for a particular request + fatal errors
logLevel: "info", // warn - record problems which are non fatal + errors + fatal errors
// info - record information about the general running of the application + warn + error + fatal errors
// Whether metrics are reported // debug - record information which is more verbose than info + info + warn + error + fatal errors
metricsOn: false // trace - record very detailed logging + debug + info + warn + error + fatal errors
level: "info",
// Whether or not to include metric events in the log output
metrics: false
}
}
} }

View File

@ -70,11 +70,11 @@ describe('debug node', function() {
try { try {
helper.log().called.should.be.true; helper.log().called.should.be.true;
var logEvents = helper.log().args.filter(function(evt) { var logEvents = helper.log().args.filter(function(evt) {
return evt[0].level == "info"; return evt[0].type == "debug";
}); });
logEvents.should.have.length(1); logEvents.should.have.length(1);
var tstmp = logEvents[0][0].timestamp; var tstmp = logEvents[0][0].timestamp;
logEvents[0][0].should.eql({level:'info',id:'n1',type:'debug',msg:'test', timestamp:tstmp}); logEvents[0][0].should.eql({level:helper.log().INFO, id:'n1',type:'debug',msg:'test', timestamp:tstmp});
done(); done();
} catch(err) { } catch(err) {

View File

@ -139,11 +139,11 @@ describe('function node', function() {
try { try {
helper.log().called.should.be.true; helper.log().called.should.be.true;
var logEvents = helper.log().args.filter(function(evt) { var logEvents = helper.log().args.filter(function(evt) {
return evt[0].level == "error"; return evt[0].type == "function";
}); });
logEvents.should.have.length(1); logEvents.should.have.length(1);
var msg = logEvents[0][0]; var msg = logEvents[0][0];
msg.should.have.property('level', 'error'); msg.should.have.property('level', helper.log().ERROR);
msg.should.have.property('id', 'n1'); msg.should.have.property('id', 'n1');
msg.should.have.property('type', 'function'); msg.should.have.property('type', 'function');
msg.should.have.property('msg', 'ReferenceError: retunr is not defined'); msg.should.have.property('msg', 'ReferenceError: retunr is not defined');

View File

@ -129,14 +129,13 @@ describe('html node', function() {
{id:"n2", type:"helper"}]; {id:"n2", type:"helper"}];
helper.load(htmlNode, flow, function() { helper.load(htmlNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
try { try {
helper.log().called.should.be.false; var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n1.receive({payload:null,topic: "bar"}); n1.receive({payload:null,topic: "bar"});
helper.log().called.should.be.true; helper.log().called.should.be.true;
var logEvents = helper.log().args.filter(function(evt) { var logEvents = helper.log().args.filter(function(evt) {
return evt[0].level == "info"; return evt[0].type == "html";
}); });
logEvents.should.have.length(1); logEvents.should.have.length(1);
// Each logEvent is the array of args passed to the function. // Each logEvent is the array of args passed to the function.

View File

@ -76,13 +76,11 @@ describe('JSON node', function() {
{id:"jn2", type:"helper"}]; {id:"jn2", type:"helper"}];
helper.load(jsonNode, flow, function() { helper.load(jsonNode, flow, function() {
try { try {
helper.log().called.should.be.false;
var jn1 = helper.getNode("jn1"); var jn1 = helper.getNode("jn1");
var jn2 = helper.getNode("jn2"); var jn2 = helper.getNode("jn2");
jn1.receive({payload:'foo',topic: "bar"}); jn1.receive({payload:'foo',topic: "bar"});
helper.log().called.should.be.true;
var logEvents = helper.log().args.filter(function(evt) { var logEvents = helper.log().args.filter(function(evt) {
return evt[0].level == "info"; return evt[0].type == "json";
}); });
logEvents.should.have.length(1); logEvents.should.have.length(1);
logEvents[0][0].should.have.a.property('msg',"SyntaxError: Unexpected token o"+ "\nfoo"); logEvents[0][0].should.have.a.property('msg',"SyntaxError: Unexpected token o"+ "\nfoo");
@ -98,13 +96,11 @@ describe('JSON node', function() {
{id:"jn2", type:"helper"}]; {id:"jn2", type:"helper"}];
helper.load(jsonNode, flow, function() { helper.load(jsonNode, flow, function() {
try { try {
helper.log().called.should.be.false;
var jn1 = helper.getNode("jn1"); var jn1 = helper.getNode("jn1");
var jn2 = helper.getNode("jn2"); var jn2 = helper.getNode("jn2");
jn1.receive({payload:1,topic: "bar"}); jn1.receive({payload:1,topic: "bar"});
helper.log().called.should.be.true;
var logEvents = helper.log().args.filter(function(evt) { var logEvents = helper.log().args.filter(function(evt) {
return evt[0].level == "info"; return evt[0].type == "json";
}); });
logEvents.should.have.length(1); logEvents.should.have.length(1);
logEvents[0][0].should.have.a.property('msg',"dropped: 1"); logEvents[0][0].should.have.a.property('msg',"dropped: 1");

View File

@ -84,9 +84,8 @@ describe('XML node', function() {
n1.receive({payload:'<not valid xml>',topic: "bar"}); n1.receive({payload:'<not valid xml>',topic: "bar"});
setTimeout(function() { setTimeout(function() {
try { try {
helper.log().called.should.be.true;
var logEvents = helper.log().args.filter(function(evt) { var logEvents = helper.log().args.filter(function(evt) {
return evt[0].level == "error"; return evt[0].type == "xml";
}); });
logEvents.should.have.length(1); logEvents.should.have.length(1);
logEvents[0][0].should.have.a.property('msg'); logEvents[0][0].should.have.a.property('msg');
@ -109,9 +108,8 @@ describe('XML node', function() {
n1.receive({payload:1,topic: "bar"}); n1.receive({payload:1,topic: "bar"});
setTimeout(function() { setTimeout(function() {
try { try {
helper.log().called.should.be.true;
var logEvents = helper.log().args.filter(function(evt) { var logEvents = helper.log().args.filter(function(evt) {
return evt[0].level == "info"; return evt[0].type == "xml";
}); });
logEvents.should.have.length(1); logEvents.should.have.length(1);
logEvents[0][0].should.have.a.property('msg',"This node only handles xml strings or js objects."); logEvents[0][0].should.have.a.property('msg',"This node only handles xml strings or js objects.");

View File

@ -53,6 +53,13 @@ function helperNode(n) {
module.exports = { module.exports = {
load: function(testNode, testFlows, testCredentials, cb) { load: function(testNode, testFlows, testCredentials, cb) {
logSpy = sinon.spy(log,"log"); logSpy = sinon.spy(log,"log");
logSpy.FATAL = log.FATAL;
logSpy.ERROR = log.ERROR;
logSpy.WARN = log.WARN;
logSpy.INFO = log.INFO;
logSpy.DEBUG = log.DEBUG;
logSpy.TRACE = log.TRACE;
logSpy.METRIC = log.METRIC;
if (typeof testCredentials === 'function') { if (typeof testCredentials === 'function') {
cb = testCredentials; cb = testCredentials;

View File

@ -91,7 +91,7 @@ describe('Node', function() {
var n = new RedNode({id:'123',type:'abc'}); var n = new RedNode({id:'123',type:'abc'});
n.on('input',function(msg) { n.on('input',function(msg) {
(typeof msg).should.not.be.equal("undefined"); (typeof msg).should.not.be.equal("undefined");
(typeof msg._messageUuid).should.not.be.equal("undefined"); (typeof msg._id).should.not.be.equal("undefined");
done(); done();
}); });
n.receive(); n.receive();
@ -101,7 +101,7 @@ describe('Node', function() {
var n = new RedNode({id:'123',type:'abc'}); var n = new RedNode({id:'123',type:'abc'});
n.on('input',function(msg) { n.on('input',function(msg) {
(typeof msg).should.not.be.equal("undefined"); (typeof msg).should.not.be.equal("undefined");
(typeof msg._messageUuid).should.not.be.equal("undefined"); (typeof msg._id).should.not.be.equal("undefined");
done(); done();
}); });
n.receive(null); n.receive(null);
@ -308,6 +308,31 @@ describe('Node', function() {
n1.send(message); n1.send(message);
}); });
it("logs the uuid for all messages sent", function(done) {
var flowGet = sinon.stub(flows,"get",function(id) {
return {'n1':sender,'n2':receiver1,'n3':receiver2}[id];
});
var logHandler = {
messagesSent: 0,
emit: function(event, msg) {
if (msg.event == "node.abc.send" && msg.level == Log.METRIC) {
this.messagesSent++;
(typeof msg.msgid).should.not.be.equal("undefined");
flowGet.restore();
done();
}
}
};
Log.addHandler(logHandler);
var sender = new RedNode({id:'n1',type:'abc', wires:[['n2', 'n3']]});
var receiver1 = new RedNode({id:'n2',type:'abc'});
var receiver2 = new RedNode({id:'n3',type:'abc'});
sender.send({"some": "message"});
})
}); });
@ -319,14 +344,11 @@ describe('Node', function() {
loginfo = msg; loginfo = msg;
}); });
n.log("a log message"); n.log("a log message");
should.deepEqual({level:"info", id:n.id, should.deepEqual({level:Log.INFO, id:n.id,
type:n.type, msg:"a log message", }, loginfo); type:n.type, msg:"a log message", }, loginfo);
Log.log.restore(); Log.log.restore();
done(); done();
}); });
});
describe('#log', function() {
it('produces a log message with a name', function(done) { it('produces a log message with a name', function(done) {
var n = new RedNode({id:'123', type:'abc', name:"barney"}); var n = new RedNode({id:'123', type:'abc', name:"barney"});
var loginfo = {}; var loginfo = {};
@ -334,7 +356,7 @@ describe('Node', function() {
loginfo = msg; loginfo = msg;
}); });
n.log("a log message"); n.log("a log message");
should.deepEqual({level:"info", id:n.id, name: "barney", should.deepEqual({level:Log.INFO, id:n.id, name: "barney",
type:n.type, msg:"a log message"}, loginfo); type:n.type, msg:"a log message"}, loginfo);
Log.log.restore(); Log.log.restore();
done(); done();
@ -349,7 +371,7 @@ describe('Node', function() {
loginfo = msg; loginfo = msg;
}); });
n.warn("a warning"); n.warn("a warning");
should.deepEqual({level:"warn", id:n.id, should.deepEqual({level:Log.WARN, id:n.id,
type:n.type, msg:"a warning"}, loginfo); type:n.type, msg:"a warning"}, loginfo);
Log.log.restore(); Log.log.restore();
done(); done();
@ -364,7 +386,7 @@ describe('Node', function() {
loginfo = msg; loginfo = msg;
}); });
n.error("an error message"); n.error("an error message");
should.deepEqual({level:"error", id:n.id, should.deepEqual({level:Log.ERROR, id:n.id,
type:n.type, msg:"an error message"}, loginfo); type:n.type, msg:"an error message"}, loginfo);
Log.log.restore(); Log.log.restore();
done(); done();
@ -378,10 +400,10 @@ describe('Node', function() {
sinon.stub(Log, 'log', function(msg) { sinon.stub(Log, 'log', function(msg) {
loginfo = msg; loginfo = msg;
}); });
var msg = {payload:"foo", _messageUuid:"987654321"}; var msg = {payload:"foo", _id:"987654321"};
n.metric("test.metric",msg,"15mb"); n.metric("test.metric",msg,"15mb");
should.deepEqual({level:"metric", nodeid:n.id, should.deepEqual({metric:"15mb", level:Log.METRIC, nodeid:n.id,
event:"test.metric",msguuid:"987654321", metric:"15mb"}, loginfo); event:"node.abc.test.metric",msgid:"987654321"}, loginfo);
Log.log.restore(); Log.log.restore();
done(); done();
}); });

View File

@ -21,6 +21,7 @@ var util = require("util");
var index = require("../../../red/nodes/index"); var index = require("../../../red/nodes/index");
var credentials = require("../../../red/nodes/credentials"); var credentials = require("../../../red/nodes/credentials");
var log = require("../../../red/log");
describe('Credentials', function() { describe('Credentials', function() {
@ -131,18 +132,18 @@ describe('Credentials', function() {
return when(true); return when(true);
} }
}; };
var logmsg = 'no errors yet'; var logmsg = 'nothing logged yet';
sinon.stub(util, 'log', function(msg) { sinon.stub(log, 'warn', function(msg) {
logmsg = msg; logmsg = msg;
}); });
credentials.init(storage); credentials.init(storage);
credentials.load().then(function() { credentials.load().then(function() {
should.equal('[red] Error loading credentials : test forcing failure', logmsg); logmsg.should.equal("Error loading credentials : test forcing failure");
util.log.restore(); log.warn.restore();
done(); done();
}).otherwise(function(err){ }).otherwise(function(err){
util.log.restore(); log.warn.restore();
done(err); done(err);
}); });
}); });
@ -189,7 +190,7 @@ describe('Credentials', function() {
}); });
} }
var logmsg = 'nothing logged yet'; var logmsg = 'nothing logged yet';
sinon.stub(util, 'log', function(msg) { sinon.stub(log, 'warn', function(msg) {
logmsg = msg; logmsg = msg;
}); });
var settings = { var settings = {
@ -201,10 +202,10 @@ describe('Credentials', function() {
var testnode = new TestNode({id:'tab1',type:'test',name:'barney'}); var testnode = new TestNode({id:'tab1',type:'test',name:'barney'});
credentials.extract(testnode); credentials.extract(testnode);
should.equal(logmsg, 'Credential Type test is not registered.'); should.equal(logmsg, 'Credential Type test is not registered.');
util.log.restore(); log.warn.restore();
done(); done();
}).otherwise(function(err){ }).otherwise(function(err){
util.log.restore(); log.warn.restore();
done(err); done(err);
}); });
}); });