diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/LICENSE b/LICENSE old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 index 41ba6e2..09c8781 --- a/README.md +++ b/README.md @@ -1,5 +1,13 @@ # log4bro (nodejs logger) -- the (only) one usable logger + +- log4bro makes it hassle free to have compliant microservice log behaviour +- just require & init and log via global variable in a few seconds +- you can run in production mode to automatically switch log-levels +- you can run in dockerMode to stop logfile writing and change output to json fields +- you can attach some extra fields to json logs so that they are also loved by your ELK stack and your sysops +- node + docker + log4bro = happy you, happy ELK stack and happy sysops + +# simple example ```javascript @@ -31,4 +39,58 @@ LOG.fatal("bla"); //thats all there is to it.. ``` +# different example show text & json output + - (just run npm start to run example locally) + +``` +var log4bro = require("log4bro"); + +var options = { + "productionMode": false, + "logDir": "logs", + "skipEnhance": true, + "namespace": "", + "silence": false, + "loggerName": "dev", + "dockerMode": false, + "varKey": "MLOG" +}; + +var logger = new log4bro(options); + +var msg = "ich mache mir sorgen, ob der logger denn noch funktioniert."; + +MLOG.trace(msg); +MLOG.debug(msg); +MLOG.info(msg); +MLOG.warn(msg); +MLOG.error(msg); +MLOG.fatal(msg); + +console.log(""); +/* json style */ + +var options2 = { + "productionMode": true, + "logDir": "logs", + "skipEnhance": true, + "namespace": "", + "silence": false, + "loggerName": "dev", + "dockerMode": true, + "varKey": "JLOG" +}; + +var logger2 = new log4bro(options2); + +JLOG.trace(msg); +JLOG.debug(msg); +JLOG.info(msg); +JLOG.warn(msg); +JLOG.error(msg); +JLOG.fatal(msg); + +``` + +- enjoy.. need help? contact me.. @krystianity or on twitter: @silentleave - author: Christian Fröhlingsdorf, \ No newline at end of file diff --git a/index.js b/index.js old mode 100644 new mode 100755 diff --git a/package.json b/package.json old mode 100644 new mode 100755 index 69bddc0..c95afab --- a/package.json +++ b/package.json @@ -1,9 +1,11 @@ { "name": "log4bro", - "version": "1.0.5", - "description": "no-brain logger with perfect output format, based on bunyan.", + "version": "1.0.6", + "description": "no-brain logger with compliant output format, based on bunyan.", "main": "index.js", - "scripts": {}, + "scripts": { + "start": "node ./src/example/index.js" + }, "repository": { "type": "git", "url": "https://github.com/krystianity/log4bro.git" diff --git a/src/LoggerRawStream.js b/src/LoggerRawStream.js old mode 100644 new mode 100755 index d8bfc91..09426c6 --- a/src/LoggerRawStream.js +++ b/src/LoggerRawStream.js @@ -7,15 +7,22 @@ var chalk = require("chalk"); * will either write to process.stdout or logfile (depending on a given logFile) * @constructor */ -function LoggerRawStream(logFile) { +function LoggerRawStream(logFile, logFieldOptions, dockerMode) { this.buffer = []; this.bufferFlushSize = 10; this.bufferTimeout = 5000; + this.logFieldOptions = logFieldOptions || null; + this.logFieldKeys = this.logFieldOptions ? Object.keys(logFieldOptions) : null; //do once only + this.dockerMode = dockerMode || false; + this.logStream = null; + this.outStream = null; - if (logFile) { + if (logFile && !dockerMode) { this.logStream = fs.createWriteStream(logFile, { "flags": "a" }); + } else { + this.outStream = process.stdout; //get ref and stop using the getter } } @@ -26,16 +33,19 @@ function LoggerRawStream(logFile) { LoggerRawStream.prototype.write = function(rec) { if (typeof rec !== "object") { - console.error("error: raw stream got a non-object record: %j", rec); + console.log("[log4bro] error: raw stream got a non-object record: %j", rec); return; } rec = this.alterLogFields(rec); - this.consoleOutput(rec); - this.buffer.push(JSON.stringify(rec)); - rec = null; - this.checkAndFlushBuffer(); + this.dockerMode ? this.jsonConsoleOutput(rec) : this.consoleOutput(rec); + + if(this.logStream){ + this.buffer.push(JSON.stringify(rec)); + rec = null; + this.checkAndFlushBuffer(); + } }; /** @@ -43,7 +53,10 @@ LoggerRawStream.prototype.write = function(rec) { * @param log * @returns {*} */ -LoggerRawStream.prototype.alterLogFields = function(log) { +LoggerRawStream.prototype.alterLogFields = function(_log) { + + //TODO hmm.. + var log = JSON.parse(JSON.stringify(_log)); //work on copy //time -> @timestamp if (log.time) { @@ -51,16 +64,73 @@ LoggerRawStream.prototype.alterLogFields = function(log) { delete log.time; } + //remove v:0 field + if(log.v !== null){ + delete log.v; + } + + //remove logger name field + if(log.name !== null){ + delete log.name; + } + + //level -> loglevel_value(int) + loglevel(string) + if(log.level !== null){ + log["loglevel"] = this.levelToName(log.level); + log["loglevel_value"] = JSON.parse(JSON.stringify(log.level)); + delete log.level; + } + + var jmsg = this._isJsonString(log.msg); + if(jmsg != null){ + log["msg_json"] = jmsg; + delete log.msg; + } + + if(this.logFieldKeys){ + for(var i = 0; i < this.logFieldKeys.length; i++){ + log[this.logFieldKeys[i]] = this.logFieldOptions[this.logFieldKeys[i]]; + } + } + return log; }; /** - * writes a console output if the logstream is not set + * parses json if possible */ -LoggerRawStream.prototype.consoleOutput = function(str) { +LoggerRawStream.prototype._isJsonString = function(str) { + var obj = null; + try { + obj = JSON.parse(str); + } catch (e) { + //empty + } + return obj; +}; + +/** + * writes a text console output if the logstream is not set + */ +LoggerRawStream.prototype.consoleOutput = function(obj) { + + if (!this.logStream) { + var msg = this.levelToName(obj.level ? obj.level : obj.loglevel_value) + + " @ " + obj["@timestamp"] + " : " + (obj.msg ? obj.msg : JSON.stringify(obj.msg_json)) + "\n"; + msg = this.levelToColorWrap(msg, obj.level ? obj.level : obj.loglevel_value); + this.outStream.write(msg); + msg = null; + } +}; + +/** + * writes a json console output if the logstream is not set + */ +LoggerRawStream.prototype.jsonConsoleOutput = function(obj) { + if (!this.logStream) { - var msg = this.levelToName(str.level) + " @ " + str["@timestamp"] + " : " + str.msg + "\n"; - process.stdout.write(this.levelToColorWrap(msg, str.level)); + var msg = this.levelToColorWrap(JSON.stringify(obj), obj.level ? obj.level : obj.loglevel_value) + "\n"; + this.outStream.write(msg); msg = null; } }; @@ -92,7 +162,7 @@ LoggerRawStream.prototype.levelToColorWrap = function(str, level) { case 30: return chalk.green(str); case 40: return chalk.yellow(str); case 50: return chalk.red(str); - case 60: return chalk.purple(str); + case 60: return chalk.red(str); default: return chalk.blue(str); } }; diff --git a/src/ServiceLogger.js b/src/ServiceLogger.js old mode 100644 new mode 100755 index 50f40ff..9637c31 --- a/src/ServiceLogger.js +++ b/src/ServiceLogger.js @@ -2,9 +2,7 @@ var fs = require("fs"); var bunyan = require("bunyan"); var RawStream = require("./LoggerRawStream.js"); -function ServiceLogger(loggerName, silence, logDir, productionMode, dockerMode, varKey) { - - if (ServiceLogger.instance) return ServiceLogger.instance; +function ServiceLogger(loggerName, silence, logDir, productionMode, dockerMode, varKey, logFieldOptions) { if(typeof loggerName === "object" && arguments.length === 1){ productionMode = loggerName.productionMode; @@ -12,75 +10,69 @@ function ServiceLogger(loggerName, silence, logDir, productionMode, dockerMode, silence = loggerName.silence; dockerMode = loggerName.dockerMode; varKey = loggerName.varKey; + logFieldOptions = loggerName.logFieldOptions; loggerName = loggerName.name; //last } this.varKey = varKey || "LOG"; - this.dockerMode = dockerMode || false; + this.logFieldOptions = logFieldOptions || null; + this.silence = silence || false; + this.logDir = logDir || "logs"; if (!loggerName && !productionMode) { - loggerName = loggerName || "dev"; + this.loggerName = loggerName || "dev"; } else { - loggerName = loggerName || "prod"; + this.loggerName = loggerName || "prod"; } if(productionMode){ - console.log("[ServiceLogger] Logger is in production mode."); + console.log("[log4bro] Logger is in production mode."); } else { - console.log("[ServiceLogger] Logger is in development mode."); - } - - this.silence = silence || false; - if (this.silence) { - //console.log("Any kind of logger was silenced!"); - //console.log = function() {}; - //console.dir = function() {}; + console.log("[log4bro] Logger is in development mode."); } - this.logDir = logDir || "logs"; - var streams = [ { "type": "raw", "level": productionMode ? "WARN" : "TRACE", - "stream": new RawStream(null) //will only write to console/stdout + "stream": new RawStream(null, this.logFieldOptions, this.dockerMode) //will only write to console/stdout } ]; - if(!dockerMode){ + if(!this.dockerMode){ + + console.log("[log4bro] Logger is not in docker mode."); + this.createLoggingDir(); + streams.push({ "type": "raw", "level": productionMode ? "WARN" : "INFO", - "stream": new RawStream(this.logDir + "/service-log.json") //will only write to logfile + "stream": new RawStream(this.logDir + "/service-log.json", this.logFieldOptions) //will only write to logfile }); } else { - console.log("[ServiceLogger] Logger is in docker mode."); + console.log("[log4bro] Logger is in docker mode."); } - this.createLoggingDir(); this.LOG = bunyan.createLogger({ - "name": loggerName, + "name": this.loggerName, "streams": streams, "src": false }); this.setGlobal(); - - ServiceLogger.instance = this; - return this; } ServiceLogger.prototype.createLoggingDir = function() { if (!fs.existsSync(this.logDir)) { - console.log("[ServiceLogger] Logs folder does not exists creating " + this.logDir + " make sure to set path in blammo.xml."); + console.log("[log4bro] Logs folder does not exists creating " + this.logDir + " make sure to set path in blammo.xml."); fs.mkdirSync(this.logDir); return; } - console.log("[ServiceLogger] Logs folder exists, clearing " + this.logDir); + console.log("[log4bro] Logs folder exists, clearing " + this.logDir); var files = null; try { files = fs.readdirSync(this.logDir); } @@ -96,10 +88,6 @@ ServiceLogger.prototype.createLoggingDir = function() { } }; -ServiceLogger.prototype.getLogger = function() { - return this; -}; - ServiceLogger.prototype.setGlobal = function() { global[this.varKey] = this; }; @@ -136,6 +124,17 @@ ServiceLogger.prototype.fatal = function(message) { ServiceLogger.prototype.enhance = function(message) { /* enhance */ + + //TODO hmm.. + if(typeof message === "object"){ + + if(Object.keys(message).length <= 15){ + return JSON.stringify(message); + } + + return "[Object object, with more than 15 keys.]"; + } + return message; }; diff --git a/src/example/index.js b/src/example/index.js new file mode 100755 index 0000000..9a3be09 --- /dev/null +++ b/src/example/index.js @@ -0,0 +1,55 @@ +var log4bro = require("./../../index.js"); + +var options = { + "productionMode": false, + "logDir": "logs", + "skipEnhance": true, + "namespace": "", + "silence": false, + "loggerName": "dev", + "dockerMode": false, + "varKey": "MLOG" +}; + +var logger = new log4bro(options); + +var msg = "ich mache mir sorgen, ob der logger denn noch funktioniert."; + +MLOG.trace(msg); +MLOG.debug(msg); +MLOG.info(msg); +MLOG.warn(msg); +MLOG.error(msg); +MLOG.fatal(msg); + +MLOG.info(options); + +console.log(""); +/* json style */ + +var options2 = { + "productionMode": true, + "logDir": "logs", + "skipEnhance": true, + "namespace": "", + "silence": false, + "loggerName": "dev", + "dockerMode": true, + "varKey": "JLOG", + "logFieldOptions": { + "log_type": "application", + "application_type": "service", + "service": "bro-service" + } +}; + +var logger2 = new log4bro(options2); + +JLOG.trace(msg); +JLOG.debug(msg); +JLOG.info(msg); +JLOG.warn(msg); +JLOG.error(msg); +JLOG.fatal(msg); + +JLOG.error(options2);