domotz-remote-pawn
Version:
Domotz Agent
383 lines (347 loc) • 13.7 kB
JavaScript
/** This file is part of Domotz Agent.
* Copyright (C) 2016 Domotz Ltd
*
* Domotz Agent is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Domotz Agent is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Domotz Agent. If not, see <http://www.gnu.org/licenses/>.
*
* Created by Tommaso Latini <tommaso@domotz.com> on 06/05/15.
*
* LOG LEVEL DOCS:
*
* silent: Completely silent. Zero logging output.
* error: When something bad happens.
* An error should cause the SHUTDOWN of the application.
* Examples:
* --> Unable to get network information.
* --> Zabbix processes not running.
* alert: When something unexpected happens.
* Examples:
* --> AMQPS connection goes down.
* --> Unreachable Engine.
* --> Unable to communicate with proxy.
* warn: When something odd is happening.
* Examples:
* --> Locked Mutexes
* --> Cache Misses
* --> Timeouts
* info: Helpful information so you can track what’s happening.
* Examples:
* --> Event Handlers
* --> Device Discovery
* debug: Debug information.
* Examples:
* --> Body of HTTP request/response
* --> Body of AMQPS message
* verbose: Even more. Perhaps just a wee bit obnoxious, even.
* Examples:
* --> Unchanged status
* silly: Completely crazy, man. Dump everything.
* Whole objects, you name it, whatever.
* Examples:
* --> Log inside loop cycles.
*/
const DOMOTZ_COLORS = {
error: 'red',
alert: 'magenta',
warn: 'yellow',
info: 'green',
debug: 'white',
verbose: 'cyan',
silly: 'gray'
};
const DOMOTZ_LEVELS = {
silent: 7,
error: 6,
alert: 5,
warn: 4,
info: 3, // <== Actual Production Level!!!
debug: 2,
verbose: 1,
silly: 0
};
const STACK_POSITION = 11;
const MAX_LEN_FOR_FILE_NAME = 30;
const STACK_REGEXP = /at\s+(.*)\s+\((.*):(\d*):(\d*)\)/gi;
const STACK_REGEXP_2 = /at\s+()(.*):(\d*):(\d*)/gi;
const LOG_BASE_FILE = 'domotz_listener.log';
module.exports.factory = function (_, winston, util, path, file, proc, configuration) {
var env = process.env;
var logDir = env.DOMOTZ_LOG_DIR;
var logName = env.DNAME;
var logTransports = [];
var logOptions, logTransport;
function dummy() {
return null;
}
function defaultTimestamp() {
return (new Date()).toISOString() + ' ';
}
function defaultFormatter(options) {
return ((options.timestamp() || '') + '[' + options.level[0].toUpperCase() + '] ' + (options.message || 'No log message'));
}
/**
* Returns a logger object that prefixes all the messages with a given string
*/
function decorateLogs(prefix) {
var decoratedConsole = {};
function decorateLog(level, object) {
"use strict";
return function () {
var args = [];
args[0] = prefix + arguments[0];
for (var i = 1; i < arguments.length; i++) {
args.push(arguments[i]);
}
object[level].apply(null, args);
};
}
Object.keys(DOMOTZ_LEVELS).forEach(function (level) {
"use strict";
decoratedConsole[level] = decorateLog(level, console);
});
return decoratedConsole;
}
function cleanFolder() {
file.listDir(logDir).forEach(function (f) {
if (f.indexOf(LOG_BASE_FILE) === -1) {
var fileName = path.join(logDir, f);
proc.spawn('wc', ['-l', fileName])
.then(function (output) {
var rows = parseInt(output.split(/\s/)[0]);
console.info('LOG - Check file %s in log directory [%s]. ' +
'Length: %s rows', f, logDir, rows);
if (rows > 100) {
return proc.spawn('sed', ['-i', '-e',
'1,' + (rows - 100) + 'd', fileName]);
}
});
}
});
}
function getCurrentFile(index) {
return path.join(logDir, LOG_BASE_FILE + '.' + index);
}
function getNewestFileIndex() {
var fileRotation = 0, lastModified = new Date(0);
console.debug('LOG - Check LOG directory: %s', logDir);
file.listDir(logDir).forEach(function (f) {
if (f.indexOf(LOG_BASE_FILE) === 0) {
var index = f.slice(-1); // N.B. We assume no more than 10 files.
var fileName = path.join(logDir, f);
var stats = file.getFileStats(fileName);
var modAt = new Date(stats.mtime);
if (stats && modAt >= lastModified) {
fileRotation = parseInt(index);
lastModified = modAt;
}
console.debug('LOG - Analyze file: %s. Last modified: %s. Got index %s', f, modAt, index);
}
});
return fileRotation;
}
function addTransport(name, type, opts) {
console.log('LOG - Initialize log %s. Options: %s', name, JSON.stringify(opts));
logTransport = new (winston.transports[type])(opts);
logTransport.name = name;
logTransport.options = opts;
logTransports.push(logTransport);
}
function createDomotzTransport() {
var domotzLogger = winston.transports.Domotz = function (options) {
this.level = options.level || 'debug';
this.maxsize = options.maxsize || 1048576; // 1 MB
this.maxFiles = options.maxFiles || 3;// 3 files
this.timestamp = options.timestamp || null;
this.formatter = options.formatter || defaultFormatter;
this.writeOutput = [];
this.writeOutputSize = 0;
this.fileRotation = getNewestFileIndex();
this.lastFlush = null;
};
util.inherits(domotzLogger, winston.Transport);
domotzLogger.prototype.log = function (level, msg, meta, callback) {
var output = this.formatter({
level: level,
message: msg,
timestamp: this.timestamp
});
this.writeOutput.push(output);
this.writeOutputSize += output.length;
this.emit('logged');
callback(null, true);
};
domotzLogger.prototype.flush = function () {
var logFile = getCurrentFile(this.fileRotation);
var curSize = file.getSize(logFile);
var maxBytes = this.maxsize - curSize;
var stream = '', bytes = 0;
console.debug('LOG - Flush: File %s. Remaining Bytes %s', logFile, maxBytes);
for (var i = 0; i < this.writeOutput.length; i++) {
var str = this.writeOutput[i] + '\n';
var strlen = (Buffer.byteLength(str, 'utf8'));
stream += str;
bytes += strlen;
if (bytes > 16384) {
file.appendFile(logFile, stream);
stream = '';
bytes = 0;
}
maxBytes -= strlen;
if (maxBytes <= 0) {
this.fileRotation = (this.fileRotation + 1) % this.maxFiles;
logFile = getCurrentFile(this.fileRotation);
maxBytes = this.maxsize;
file.writeFile(logFile, '');
}
}
file.appendFile(logFile, stream);
this.writeOutput = [];
this.writeOutputSize = 0;
this.lastFlush = new Date();
};
domotzLogger.prototype.status = function () {
return {
buffer: {
length: this.writeOutput.length,
bytes: this.writeOutputSize
},
storage: {
directory: logDir,
curFile: LOG_BASE_FILE + '.' + this.fileRotation,
lastFlush: this.lastFlush
}
};
};
}
/**
* DEVELOPMENT LOGGER
* Transport --> Console with stderr for error level, Stdout for all the others
* Timestamp --> Locale Time String
* Formatter --> [file:row] timestamp [level] message
*/
if (logName !== 'domotz') {
logOptions = {
level: 'silly',
colorize: 'all',
debugStdout: true,
formatter: defaultFormatter,
timestamp: function () {
Error.stackTraceLimit = STACK_POSITION;
var stack = new Error().stack.split('\n');
var line = stack[STACK_POSITION];
var padding = Array(MAX_LEN_FOR_FILE_NAME).join(' ');
var sp = STACK_REGEXP.exec(line) || STACK_REGEXP_2.exec(line);
var file = sp ? '[' + sp[2].split('/').pop() + ':' + sp[3] + ']' : '';
var ts = (new Date()).toLocaleTimeString() + ' ';
return (ts + file + padding).substring(0, MAX_LEN_FOR_FILE_NAME);
}
};
addTransport('DEVELOPMENT', 'Console', logOptions);
}
/**
* PRODUCTION LOGGER:
*
* ==> OPENWRT
* Transport --> Console with stderr for error level, Stdout for all the others
* Timestamp --> Already present in system log
* Formatter --> [level] message
*
* N.B. OpenWRT procd provides to redirect stderr on syslog and stdout on /dev/null
* No need for timestamp because it's already in system log
*
* ==> STANDARD
* Transport --> Custom with a memory buffer flushed periodically
* Timestamp --> ISO String
* Formatter --> timestamp [level] message.
*
* N.B. The transport just offer te way to flush the log.
* The periodical trigger is external (see cron.js)
*/
if (env.DPLATFORM === 'openwrt') {
logOptions = configuration ? _.extend(configuration.log || {}) : {};
logOptions.debugStdout = true;
logOptions.timestamp = dummy;
logOptions.formatter = defaultFormatter;
addTransport('LUXUL', 'Console', logOptions);
} else {
logOptions = configuration ? _.extend(configuration.log || {}) : {};
logOptions.timestamp = defaultTimestamp;
logOptions.formatter = defaultFormatter;
createDomotzTransport();
addTransport('DOMOTZ', 'Domotz', logOptions);
}
var logger = new (winston.Logger)({
transports: logTransports,
colors: DOMOTZ_COLORS,
levels: DOMOTZ_LEVELS
});
console.silly = logger.silly;
console.verbose = logger.verbose;
console.debug = logger.debug;
console.info = logger.info;
console.log = logger.info;
console.warn = logger.warn;
console.alert = logger.alert;
console.error = logger.error;
console.info("+------------------------------------------------+");
console.info("| ___ _ |");
console.info("| ( _`\\ ( )_ |");
console.info("| | | ) | _ ___ ___ _ | ,_) ____ |");
console.info("| | | | ) /'_`\\ /' _ ` _ `\\ /'_`\\ | | (_ ,) |");
console.info("| | |_) |( (_) )| ( ) ( ) |( (_) )| |_ /'/_ |");
console.info("| (____/'`\\___/'(_) (_) (_)`\\___/'`\\__)(____) |");
console.info("| THE HOME NETWORK BUDDY |");
console.info("+------------------------------------------------+");
console.debug(" Domotz Environment: ");
Object.keys(env).forEach(function (key) {
console.debug(' --> ' + key + '=' + env[key]);
});
console.debug("+------------------------------------------------+");
function flush() {
logTransports.forEach(function (logTransport) {
console.log('LOG - Flush on transport %s. Method %s available.',
logTransport.name, logTransport.flush ? 'is' : 'isn\'t');
if (logTransport.flush) {
logTransport.flush();
}
});
}
function getInfo() {
var statuses = {};
for (var i = 0; i < logTransports.length; i++) {
var logTransport = logTransports[i];
var status = (logTransport.status ? logTransport.status() : 'Unavailable');
statuses[logTransport.name] = {
status: status,
options: logTransport.options
};
}
return {
transports: statuses
};
}
return {
flush: flush,
getInfo: getInfo,
decorateLogs: decorateLogs,
cleanFolder: cleanFolder,
// Testing
_dummy: dummy,
_createDomotzTransport: createDomotzTransport,
_addTransport: addTransport,
_getNewestFileIndex: getNewestFileIndex,
_getCurrentFile: getCurrentFile,
_defaultFormatter: defaultFormatter,
_defaultTimestamp: defaultTimestamp
};
};