UNPKG

domotz-remote-pawn

Version:

Domotz Agent

383 lines (347 loc) 13.7 kB
/** 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 }; };