├── coverage.png ├── .gitignore ├── .travis.yml ├── test ├── util │ ├── console.js │ ├── lib │ │ ├── index.js │ │ └── deboog.js │ ├── debarg.js │ ├── spyhandler.js │ └── config.json ├── filter.js ├── hint.js ├── printf.js ├── handlers.js ├── formatter.js ├── config.js ├── console.js └── logger.js ├── lib ├── handlers │ ├── index.js │ ├── null.js │ ├── stream.js │ ├── file.js │ ├── console.js │ └── handler.js ├── utils │ ├── fn.js │ ├── klass.js │ ├── json.js │ └── printf.js ├── levels.js ├── filter.js ├── filterer.js ├── index.js ├── formatter.js ├── record.js ├── config.js ├── logger.js └── console.js ├── .jshintrc ├── package.json ├── benchmark └── logging.js ├── CHANGELOG.md ├── LICENSE └── README.md /coverage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanmonstar/intel/HEAD/coverage.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | .nyc_output 4 | .idea 5 | coverage.html 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4" 4 | - "6" 5 | script: npm test 6 | -------------------------------------------------------------------------------- /test/util/console.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | module.exports = function(x) { 6 | console.log(x); 7 | }; 8 | -------------------------------------------------------------------------------- /test/util/lib/index.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | const dbug = require('dbug')('test:util'); 5 | module.exports = function(x) { 6 | dbug(x); 7 | }; 8 | -------------------------------------------------------------------------------- /test/util/debarg.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | const dbug = require('dbug')('test:util:debarg'); 5 | module.exports = function(x) { 6 | dbug(x); 7 | }; 8 | -------------------------------------------------------------------------------- /test/util/lib/deboog.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | const dbug = require('dbug')('test:util:deboog'); 5 | module.exports = function(x) { 6 | dbug(x); 7 | }; 8 | -------------------------------------------------------------------------------- /lib/handlers/index.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | exports.Console = require('./console'); 6 | exports.File = require('./file'); 7 | exports.Null = require('./null'); 8 | exports.Stream = require('./stream'); 9 | -------------------------------------------------------------------------------- /lib/utils/fn.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | 6 | module.exports = function fn(args) { 7 | /*jshint evil: true*/ 8 | console.log(args, [].slice.call(arguments, 1).join('\n')); 9 | return new Function(args, [].slice.call(arguments, 1).join('\n')); 10 | }; 11 | -------------------------------------------------------------------------------- /test/util/spyhandler.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | const util = require('util'); 6 | 7 | const intel = require('../../'); 8 | 9 | function Spy() { 10 | intel.Handler.apply(this, arguments); 11 | } 12 | util.inherits(Spy, intel.Handler); 13 | 14 | Spy.prototype.emit = function() { 15 | }; 16 | 17 | module.exports = Spy; 18 | -------------------------------------------------------------------------------- /lib/handlers/null.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | const util = require('util'); 6 | 7 | const Handler = require('./handler'); 8 | 9 | function NullHandler() { 10 | Handler.apply(this, arguments); 11 | } 12 | util.inherits(NullHandler, Handler); 13 | 14 | NullHandler.prototype.emit = function nullEmit(){}; 15 | 16 | module.exports = NullHandler; 17 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": false, 3 | "boss": true, 4 | "browser": true, 5 | "camelcase": true, 6 | "curly": true, 7 | "esnext": true, 8 | "eqeqeq": true, 9 | "eqnull": true, 10 | "expr": true, 11 | "forin": false, 12 | "indent": 2, 13 | "latedef": true, 14 | "laxbreak": true, 15 | "laxcomma": true, 16 | "maxcomplexity": 10, 17 | "maxlen": 80, 18 | "maxerr": 100, 19 | "node": true, 20 | "noarg": true, 21 | "passfail": false, 22 | "shadow": true, 23 | "strict": false, 24 | "supernew": false, 25 | "trailing": true, 26 | "undef": true, 27 | "unused": true 28 | } 29 | -------------------------------------------------------------------------------- /lib/utils/klass.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | const util = require('util'); 6 | 7 | function klass(ctor) { 8 | if (!(this instanceof klass)) { 9 | return new klass(ctor); 10 | } 11 | this._ctor = ctor; 12 | } 13 | 14 | klass.prototype = { 15 | inherit: function inherit(par) { 16 | util.inherits(this._ctor, par); 17 | return this; 18 | }, 19 | mixin: function mixin(other) { 20 | for (var k in other) { 21 | this._ctor.prototype[k] = other[k]; 22 | } 23 | return this; 24 | } 25 | }; 26 | 27 | module.exports = klass; 28 | -------------------------------------------------------------------------------- /lib/handlers/stream.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | const util = require('util'); 6 | 7 | const Handler = require('./handler'); 8 | 9 | function StreamHandler(options) { 10 | options = options || {}; 11 | if (!options.stream) { 12 | options = { stream: options }; 13 | } 14 | Handler.call(this, options); 15 | this._stream = options.stream; 16 | } 17 | 18 | util.inherits(StreamHandler, Handler); 19 | 20 | StreamHandler.prototype.emit = function streamEmit(record) { 21 | this._stream.write(this.format(record) + '\n'); 22 | }; 23 | 24 | module.exports = StreamHandler; 25 | -------------------------------------------------------------------------------- /lib/handlers/file.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | const fs = require('fs'); 6 | const util = require('util'); 7 | 8 | const StreamHandler = require('./stream'); 9 | 10 | function FileHandler(options) { 11 | if (typeof options === 'string') { 12 | options = { file: options }; 13 | } 14 | this._file = options.file; 15 | 16 | options.stream = this._open(); 17 | StreamHandler.call(this, options); 18 | } 19 | util.inherits(FileHandler, StreamHandler); 20 | 21 | FileHandler.prototype._open = function open() { 22 | return fs.createWriteStream(this._file, { flags: 'a' }); 23 | }; 24 | 25 | module.exports = FileHandler; 26 | -------------------------------------------------------------------------------- /test/util/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatters": { 3 | "basic": "basic: %(message)s", 4 | "custom": { 5 | "formatFn": "function format(record) { return record.message.toUpperCase(); }" 6 | }, 7 | "klass": { 8 | "class": "intel/formatter" 9 | } 10 | }, 11 | "filters": { 12 | "s": "foo", 13 | "r": { 14 | "regex": "foo", 15 | "flags": "gi" 16 | }, 17 | "f": { 18 | "function": "function filter(record) { return record.args.length > 2; }" 19 | }, 20 | "c": { 21 | "class": "intel/filter" 22 | } 23 | }, 24 | "handlers": { 25 | "empty": { 26 | "class": "intel/handlers/null", 27 | "formatter": "custom", 28 | "filters": ["s", "r", "f"] 29 | }, 30 | "spy": { 31 | "class": "./test/util/spyhandler" 32 | } 33 | }, 34 | "loggers": { 35 | "test.config.json": { 36 | "level": "INFO", 37 | "propagate": false, 38 | "handlers": ["empty", "spy"] 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/utils/json.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | // util to protect us from circular references when stringifying 6 | 7 | 'use strict'; 8 | 9 | function stringify(obj, indent) { 10 | var seen = []; 11 | return JSON.stringify(obj, function filter(key, val) { 12 | if (!val || typeof val !== 'object') { 13 | return val; 14 | } else if (seen.indexOf(val) !== -1) { 15 | return '[Circular]'; 16 | } 17 | seen.push(val); 18 | return val; 19 | }, indent || 0); 20 | } 21 | 22 | function nativeJson(obj, indent) { 23 | return indent ? JSON.stringify(obj, null, indent) : JSON.stringify(obj); 24 | } 25 | 26 | module.exports = function json(obj, indent) { 27 | try { 28 | return nativeJson(obj, indent); 29 | } catch (e) { 30 | return stringify(obj, indent); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /lib/levels.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | const LEVELS = module.exports = {}; 6 | 7 | [ 8 | 'NOTSET', 9 | 'TRACE', 10 | 'VERBOSE', 11 | 'DEBUG', 12 | 'INFO', 13 | 'WARN', 14 | 'ERROR', 15 | 'CRITICAL' 16 | ].forEach(function(name, index) { 17 | LEVELS[name] = index * 10; 18 | }); 19 | 20 | 21 | 22 | const NUMBERS = {}; 23 | for (var levelname in LEVELS) { 24 | NUMBERS[LEVELS[levelname]] = levelname; 25 | } 26 | 27 | // additional levels, but not canonical names 28 | LEVELS.WARNING = LEVELS.WARN; 29 | LEVELS.ALL = LEVELS.NOTSET; 30 | LEVELS.NONE = Infinity; 31 | 32 | LEVELS.getLevelName = function getLevelName(number) { 33 | return NUMBERS[number]; 34 | }; 35 | 36 | LEVELS.getLevel = function getLevel(val) { 37 | // 5 => 5 38 | // '5' => 5 39 | // 'five' => undefined 40 | // 'debug' => 20 41 | // 'DEBUG' = > 20 42 | var level = Number(val); 43 | if (isNaN(level)) { 44 | level = LEVELS[String(val).toUpperCase()]; 45 | } 46 | return level; 47 | }; 48 | -------------------------------------------------------------------------------- /lib/handlers/console.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | const util = require('util'); 6 | 7 | const Handler = require('./handler'); 8 | const StreamHandler = require('./stream'); 9 | const LEVELS = require('../levels'); 10 | 11 | function ConsoleHandler(options) { 12 | options = options || {}; 13 | options.stream = process.stdout; 14 | this._out = new StreamHandler(options); 15 | options.stream = process.stderr; 16 | this._err = new StreamHandler(options); 17 | Handler.call(this, options); 18 | } 19 | 20 | util.inherits(ConsoleHandler, Handler); 21 | 22 | ConsoleHandler.prototype.emit = function consoleEmit(record) { 23 | var handler = (record.level >= LEVELS.WARN) ? this._err : this._out; 24 | handler.emit(record); 25 | }; 26 | 27 | ConsoleHandler.prototype.setFormatter = function setFormatter(formatter) { 28 | Handler.prototype.setFormatter.call(this, formatter); 29 | this._out.setFormatter(formatter); 30 | this._err.setFormatter(formatter); 31 | }; 32 | 33 | module.exports = ConsoleHandler; 34 | -------------------------------------------------------------------------------- /lib/filter.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | const util = require('util'); 6 | 7 | function nameFilter(record) { 8 | var filter = this._filter; 9 | if (filter === record.name) { 10 | return true; 11 | } else if (record.name.indexOf(filter) === -1) { 12 | return false; 13 | } else { 14 | return record.name[filter.length] === '.'; 15 | } 16 | } 17 | 18 | function regexpFilter(record) { 19 | return this._filter.test(record.message); 20 | } 21 | 22 | function Filter(filter) { 23 | // valid filters: regexp, string, function 24 | var type = typeof filter; 25 | if (type === 'function') { 26 | this.filter = filter; 27 | } else if (type === 'string') { 28 | this._filter = filter; 29 | this.filter = nameFilter; 30 | } else if (util.isRegExp(filter)) { 31 | this._filter = filter; 32 | this.filter = regexpFilter; 33 | } 34 | } 35 | 36 | Filter.prototype = { 37 | 38 | filter: function() { 39 | throw new Error('Filter type was not defined.'); 40 | } 41 | 42 | }; 43 | 44 | module.exports = Filter; 45 | -------------------------------------------------------------------------------- /lib/filterer.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | 6 | function filter(record) { 7 | var i = this._filters.length; 8 | while (i--) { 9 | if (!this._filters[i].filter(record)) { 10 | return false; 11 | } 12 | } 13 | return true; 14 | } 15 | 16 | function noop() { return true; } 17 | 18 | function Filterer() { 19 | this._filters = []; 20 | this.filter = noop; 21 | } 22 | 23 | Filterer.prototype = { 24 | 25 | __toggleFilter: function toggleFilter() { 26 | this.filter = this.filter === noop ? filter : noop; 27 | }, 28 | 29 | addFilter: function addFilter(_filter) { 30 | if (this._filters.length === 0) { 31 | this.__toggleFilter(); 32 | } 33 | this._filters.push(_filter); 34 | return this; 35 | }, 36 | 37 | removeFilter: function removeFilter(_filter) { 38 | var index = this._filters.indexOf(_filter); 39 | if (index !== -1) { 40 | this._filters.splice(index, 1); 41 | } 42 | if (this._filters.length === 0) { 43 | this.__toggleFilter(); 44 | } 45 | return this; 46 | } 47 | 48 | }; 49 | 50 | module.exports = Filterer; 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "intel", 3 | "version": "1.2.0", 4 | "license": "MPL-2.0", 5 | "dependencies": { 6 | "chalk": "^1.1.0", 7 | "dbug": "~0.4.2", 8 | "stack-trace": "~0.0.9", 9 | "strftime": "~0.10.0", 10 | "symbol": "~0.3.1", 11 | "utcstring": "~0.1.0" 12 | }, 13 | "devDependencies": { 14 | "bluebird": "^3.5", 15 | "debug": "~2.2.0", 16 | "insist": "^1.0", 17 | "jshint": "2.x", 18 | "matcha": "0.x", 19 | "mocha": "3.x", 20 | "nyc": "^11.0.1", 21 | "walk": "2.x", 22 | "winston": "^2.0" 23 | }, 24 | "readmeFilename": "README.md", 25 | "description": "I need more intel.", 26 | "main": "./lib/index.js", 27 | "scripts": { 28 | "test": "nyc mocha --ui exports -R dot", 29 | "bench": "matcha -I exports" 30 | }, 31 | "bugs": "https://github.com/seanmonstar/intel/issues", 32 | "homepage": "http://seanmonstar.github.io/intel", 33 | "repository": { 34 | "type": "git", 35 | "url": "https://github.com/seanmonstar/intel.git" 36 | }, 37 | "engines": { 38 | "node": ">=4.0.0" 39 | }, 40 | "keywords": [ 41 | "log", 42 | "debug", 43 | "dbug", 44 | "logging", 45 | "logger", 46 | "console" 47 | ], 48 | "author": "Sean McArthur (http://seanmonstar.com)", 49 | "licenses": [ 50 | { 51 | "type": "MPL 2.0", 52 | "url": "https://mozilla.org/MPL/2.0/" 53 | } 54 | ], 55 | "files": [ 56 | "LICENSE", 57 | "lib" 58 | ] 59 | } 60 | -------------------------------------------------------------------------------- /test/filter.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | const assert = require('insist'); 6 | 7 | const intel = require('../'); 8 | 9 | module.exports = { 10 | 'Filter': { 11 | 'with nothing': { 12 | 'should throw an error': function() { 13 | var f = new intel.Filter(); 14 | assert.throws(f.filter, /Filter type was not defined.$/); 15 | } 16 | }, 17 | 'with regexp': { 18 | 'should filter records based on message': function() { 19 | var f = new intel.Filter(/^foo/g); 20 | 21 | assert(!f.filter({ 22 | name: 'foo', 23 | message: 'bar baz boom' 24 | })); 25 | 26 | assert(!f.filter({ 27 | name: 'bar', 28 | message: 'not foobar' 29 | })); 30 | 31 | assert(f.filter({ 32 | name: 'bar', 33 | message: 'foobar' 34 | })); 35 | } 36 | }, 37 | 'with string': { 38 | 'should filter records based on logger name': function() { 39 | var f = new intel.Filter('foo.bar'); 40 | 41 | assert(f.filter({ 42 | name: 'foo.bar' 43 | })); 44 | 45 | assert(f.filter({ 46 | name: 'foo.bar.baz' 47 | })); 48 | 49 | assert(!f.filter({ 50 | name: 'food.bar', 51 | })); 52 | } 53 | }, 54 | 'with function': { 55 | 'should filter records': function() { 56 | var f = new intel.Filter(function(record) { 57 | return record.name === 'foo' && record.message.indexOf('bar') !== -1; 58 | }); 59 | 60 | assert(f.filter({ 61 | name: 'foo', 62 | message: 'bar baz boom' 63 | })); 64 | 65 | assert(!f.filter({ 66 | name: 'foo', 67 | message: 'boom' 68 | })); 69 | } 70 | } 71 | } 72 | }; 73 | -------------------------------------------------------------------------------- /test/hint.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | const fs = require('fs'); 6 | const path = require('path'); 7 | const util = require('util'); 8 | 9 | const jshint = require('jshint').JSHINT; 10 | const P = require('bluebird'); 11 | const walk = require('walk'); 12 | 13 | // read jshintrc 14 | var jshintrc = JSON.parse(fs.readFileSync(path.join(__dirname, '../.jshintrc')) 15 | .toString()); 16 | var filesToLint = []; 17 | 18 | var options = { 19 | listeners: { 20 | file: function(root, fStat, next) { 21 | var f = path.join(root, fStat.name); 22 | if (/\.js$/.test(f)) { 23 | filesToLint.push(f); 24 | } 25 | next(); 26 | } 27 | } 28 | }; 29 | 30 | module.exports = { 31 | 32 | 33 | 34 | 'jshint': { 35 | 36 | 'before': function() { 37 | walk.walkSync(path.join(__dirname, '../lib'), options); 38 | walk.walkSync(__dirname, options); 39 | }, 40 | 41 | 'should yield no errors': function(done) { 42 | this.slow(2000); // can be slow reading all the files :( 43 | var errors = []; 44 | 45 | var read = P.promisify(fs.readFile); 46 | P.all(filesToLint.map(function(fileName) { 47 | return read(String(fileName)).then(function(data) { 48 | var f = path.relative(process.cwd(), fileName); 49 | if (!jshint(String(data), jshintrc)) { 50 | jshint.errors.forEach(function(e) { 51 | errors.push( 52 | util.format("%s %s:%d - %s", e.id, f, e.line, e.reason)); 53 | }); 54 | } 55 | }); 56 | })).done(function() { 57 | if (errors.length) { 58 | var buf = util.format("\n %d errors:\n * ", 59 | errors.length); 60 | buf += errors.join("\n * "); 61 | done(buf); 62 | } else { 63 | done(null); 64 | } 65 | }); 66 | } 67 | 68 | } 69 | }; 70 | 71 | -------------------------------------------------------------------------------- /benchmark/logging.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | const Console = require('console').Console; 6 | const EE = require('events').EventEmitter; 7 | 8 | const winston = require('winston'); 9 | const intel = require('../'); 10 | 11 | var stdout = new EE(); 12 | stdout.write = function (out, encoding, cb) { 13 | if (typeof encoding === 'function') { 14 | cb = encoding; 15 | encoding = null; 16 | } 17 | cb && cb(); 18 | return true; 19 | }; 20 | 21 | var _console = new Console(stdout, stdout); 22 | intel.basicConfig({ 23 | stream: stdout, 24 | level: intel.INFO, 25 | format: '%O' 26 | }); 27 | 28 | winston.add(winston.transports.File, { stream: stdout }); 29 | winston.remove(winston.transports.Console); 30 | 31 | module.exports = { 32 | 'logging.info()': { 33 | 'bench': { 34 | 'console.info': function() { 35 | _console.info('asdf'); 36 | }, 37 | 'intel.info': function() { 38 | intel.info('asdf'); 39 | }, 40 | 'winston.info': function() { 41 | winston.info('asdf'); 42 | } 43 | } 44 | }, 45 | 'string interpolation': { 46 | 'bench': { 47 | 'console': function() { 48 | _console.info('foo', 'bar'); 49 | }, 50 | 'intel': function() { 51 | intel.info('foo', 'bar'); 52 | }, 53 | 'winston': function() { 54 | winston.info('foo', 'bar'); 55 | } 56 | } 57 | }, 58 | 'disabled': { 59 | 'before': function() { 60 | intel.setLevel(intel.INFO); 61 | intel.getLogger('foo'); 62 | intel.getLogger('foo.bar'); 63 | intel.getLogger('foo.bar.baz'); 64 | this.intel = intel.getLogger('foo.bar.baz.quux'); 65 | intel.setLevel(intel.WARN); 66 | winston.level = 'warn'; 67 | }, 68 | 'bench': { 69 | 'console': function() { 70 | _console.info('foo', 'bar'); 71 | }, 72 | 'intel': function() { 73 | this.intel.info('foo', 'bar'); 74 | }, 75 | 'winston': function() { 76 | winston.info('foo', 'bar'); 77 | } 78 | } 79 | } 80 | }; 81 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | const dbug = require('dbug')('intel'); 6 | 7 | const Logger = require('./logger'); 8 | const Handler = require('./handlers/handler'); 9 | const handlers = require('./handlers'); 10 | const Formatter = require('./formatter'); 11 | 12 | const root = new Logger(); 13 | 14 | root.propagate = false; 15 | root.setLevel(Logger.TRACE); 16 | var oldHandle = root.handle; 17 | root.handle = function handle() { 18 | if (this._handlers.length === 0) { 19 | root.basicConfig(); 20 | } 21 | return oldHandle.apply(this, arguments); 22 | }; 23 | 24 | const DEFAULT_FORMAT = '%(name)s.%(levelname)s: %(message)s'; 25 | root.basicConfig = function basicConfig(options) { 26 | if (root._handlers.length) { 27 | return; 28 | } 29 | dbug('basicConfig', options); 30 | 31 | // available options: 32 | // level, format, filename, stream 33 | options = options || {}; 34 | 35 | var hdlr; 36 | if (options.file) { 37 | hdlr = new handlers.File({ file: options.file }); 38 | } else if (options.stream) { 39 | hdlr = new handlers.Stream({ stream: options.stream }); 40 | } else if (options.null) { 41 | hdlr = new handlers.Null(); 42 | } else { 43 | hdlr = new handlers.Console(); 44 | } 45 | hdlr.setFormatter(new Formatter(options.format || DEFAULT_FORMAT)); 46 | root.addHandler(hdlr); 47 | 48 | if (options.level) { 49 | root.setLevel(options.level); 50 | } 51 | root.handle = oldHandle; 52 | }; 53 | 54 | root.Logger = Logger; 55 | root.Handler = Handler; 56 | root.handlers = handlers; 57 | root.Formatter = Formatter; 58 | root.Filter = require('./filter'); 59 | 60 | root.getLogger = function getLogger(name) { 61 | return new Logger(name); 62 | }; 63 | 64 | // lazy load it, since console depends on this module 65 | Object.defineProperty(root, 'config', { 66 | get: function() { 67 | return require('./config'); 68 | } 69 | }); 70 | Object.defineProperty(root, 'console', { 71 | get: function() { 72 | return require('./console'); 73 | } 74 | }); 75 | 76 | 77 | module.exports = root; 78 | -------------------------------------------------------------------------------- /lib/handlers/handler.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | const Formatter = require('../formatter'); 6 | const Filterer = require('../filterer'); 7 | const klass = require('../utils/klass'); 8 | const LEVELS = require('../levels'); 9 | 10 | const _defaultFormatter = new Formatter(); 11 | 12 | function emit(record) { 13 | return this._emit(record); 14 | } 15 | 16 | function handleFilter(record) { 17 | if (this.filter(record)) { 18 | this.__emit(record); 19 | } 20 | } 21 | 22 | function Handler(options) { 23 | if (typeof options !== 'object') { 24 | options = { level: options }; 25 | } 26 | var level = options.level; 27 | this.setLevel((level !== undefined) ? LEVELS.getLevel(level) : LEVELS.NOTSET); 28 | this.setFormatter(options.formatter || _defaultFormatter); 29 | this.handle = this.__emit; 30 | Filterer.call(this, options); 31 | } 32 | 33 | klass(Handler).inherit(Filterer).mixin({ 34 | 35 | level: null, 36 | 37 | _formatter: null, 38 | 39 | __toggleFilter: function handlerToggleFilter() { 40 | Filterer.prototype.__toggleFilter.call(this); 41 | this.handle = this.handle === this.__emit ? handleFilter : this.__emit; 42 | }, 43 | 44 | // sub-classes should override emit, not handle 45 | _emit: function emit(/*record*/) { 46 | throw new Error('Handler.emit must be implemented by sub-classes'); 47 | }, 48 | 49 | __emit: emit, 50 | 51 | format: function format(record) { 52 | return this._formatter.format(record); 53 | }, 54 | 55 | setFormatter: function setFormatter(formatter) { 56 | this._formatter = formatter; 57 | return this; 58 | }, 59 | 60 | setLevel: function setLevel(level) { 61 | this.level = LEVELS.getLevel(level); 62 | return this; 63 | } 64 | 65 | }); 66 | 67 | Object.defineProperty(Handler.prototype, 'emit', { 68 | get: function() { 69 | return this._emit; 70 | }, 71 | set: function(val) { 72 | if (typeof val !== 'function') { 73 | throw new TypeError('emit must be a function'); 74 | } 75 | this._emit = val; 76 | } 77 | }); 78 | 79 | module.exports = Handler; 80 | -------------------------------------------------------------------------------- /test/printf.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | const assert = require('assert'); 6 | 7 | const printf = require('../lib/utils/printf'); 8 | 9 | module.exports = { 10 | 'printf': { 11 | 'should format named args': function() { 12 | assert.equal(printf('%-3(foo)s:%3(bar)s', { foo: 'a', bar: 'b' }), 13 | 'a : b'); 14 | }, 15 | 'should print args': function() { 16 | assert.equal(printf('%s,%s,%d', 'a', 2, 3), 'a,2,3'); 17 | assert.equal(printf('%%s', 'a'), '%s a'); 18 | assert.equal(printf('%Z', 'a'), '%Z a'); 19 | }, 20 | 'should print %O': function() { 21 | assert.equal(printf('%O', { foo: 'bar' }), '{"foo":"bar"}'); 22 | }, 23 | 'should alias %j to %O': function() { 24 | assert.equal(printf('%j', { foo: 'bar' }), '{"foo":"bar"}'); 25 | }, 26 | 'should allow pretty printing JSON': function() { 27 | var obj = { foo: 'bar' }; 28 | assert.equal(printf('%:4j', obj), JSON.stringify(obj, null, 4)); 29 | }, 30 | 'should print default with %?': function() { 31 | assert.equal( 32 | printf('%?: %? %?', 'foo', new Error('bar'), { a: 'b'}), 33 | 'foo: Error: bar {"a":"b"}' 34 | ); 35 | }, 36 | 'should pad if value less then padding': function() { 37 | assert.equal(printf('%5s', 'abc'), ' abc'); 38 | assert.equal(printf('%-5s', 'abc'), 'abc '); 39 | assert.equal(printf('%5s', 123), ' 123'); 40 | assert.equal(printf('%-5s', 123), '123 '); 41 | }, 42 | 'should not pad if value more or equal padding': function() { 43 | assert.equal(printf('%3s', 'abc'), 'abc'); 44 | assert.equal(printf('%-3s', 'abc'), 'abc'); 45 | }, 46 | 'should not truncate if value more or equal trunc value': function() { 47 | assert.equal(printf('%.3s', 'abc'), 'abc'); 48 | assert.equal(printf('%.-3s', 'abc'), 'abc'); 49 | }, 50 | 'should truncate if value less then trunc value': function() { 51 | assert.equal(printf('%.2s', 'abc'), 'bc'); 52 | assert.equal(printf('%.-2s', 'abc'), 'ab'); 53 | assert.equal(printf('%.2s', 123), '23'); 54 | assert.equal(printf('%.-2s', 123), '12'); 55 | }, 56 | 'should first truncate and them pad': function() { 57 | assert.equal(printf('%5.2s', 'abc'), ' bc'); 58 | }, 59 | 'should default print extra args without placeholders': function() { 60 | assert.equal(printf('foo', 'bar'), 'foo bar'); 61 | assert.equal( 62 | printf('%s =', 'abc', 3, false, { foo: 'bar' }), 63 | 'abc = 3 false {"foo":"bar"}' 64 | ); 65 | }, 66 | 'should default regexps as strings': function() { 67 | assert.equal(printf('%?', /ba[rz]/g), '/ba[rz]/g'); 68 | assert.equal(printf('%?', new RegExp('ba[rz]', 'gi')), '/ba[rz]/gi'); 69 | } 70 | } 71 | }; 72 | -------------------------------------------------------------------------------- /lib/utils/printf.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | const json = require('./json'); 6 | 7 | const RE = /%(-?\d+)?(\.-?\d+)?(:\d+)?(%|(\([^\)]+\))?([sdOj\?]))/g; 8 | 9 | const toString = Object.prototype.toString; 10 | function type(x) { 11 | return toString.call(x).slice(8, -1).toLowerCase(); 12 | } 13 | 14 | function pad(str, value) { 15 | var isRight = false; 16 | 17 | if(value < 0) { 18 | isRight = true; 19 | value = -value; 20 | } 21 | 22 | if(str.length < value) { 23 | var padding = new Array(value - str.length + 1).join(' '); 24 | return isRight ? str + padding : padding + str; 25 | } else{ 26 | return str; 27 | } 28 | } 29 | 30 | function truncate(str, value) { 31 | if(value > 0) {// truncate from begining 32 | return str.slice(-value); 33 | } else {// truncate from end 34 | return str.slice(0, -value); 35 | } 36 | } 37 | 38 | function defaultFmt(x) { 39 | switch (type(x)) { 40 | case 'arguments': 41 | case 'object': 42 | return json(x); 43 | default: 44 | return String(x); 45 | } 46 | } 47 | 48 | function printfNextValue(format, nextValFn) { 49 | var str = String(format).replace(RE, 50 | function(match, padding, trunc, indent, kind, name, type) { 51 | 52 | if (kind === '%') { 53 | return '%'; 54 | } 55 | 56 | var val = nextValFn(name); 57 | 58 | 59 | var fmt = ''; 60 | 61 | switch (type) { 62 | case 's': 63 | fmt = String(val); 64 | break; 65 | case 'd': 66 | fmt = String(Number(val)); 67 | break; 68 | case 'O': 69 | case 'j': 70 | fmt = json(val, indent && parseInt(indent.slice(1), 10)); 71 | break; 72 | case '?': 73 | fmt = defaultFmt(val); 74 | break; 75 | } 76 | 77 | if (trunc !== undefined) { 78 | fmt = truncate(fmt, trunc.slice(1)); 79 | } 80 | 81 | if (padding !== undefined) { 82 | fmt = pad(fmt, padding); 83 | } 84 | 85 | return fmt; 86 | }); 87 | return str; 88 | } 89 | 90 | function printfObj(format, obj) { 91 | return printfNextValue(format, function(name) { 92 | name = name && name.slice(1, -1); 93 | return obj[name]; 94 | }); 95 | } 96 | 97 | function printfArgs(format, args) { 98 | var i = 0; 99 | var len = args.length; 100 | var str = printfNextValue(format, function() { 101 | return args[i++]; 102 | }); 103 | if (len > 0) { 104 | for (var x = args[i]; i < len; x = args[++i]) { 105 | str += ' ' + defaultFmt(x); 106 | } 107 | } 108 | 109 | return str; 110 | } 111 | 112 | // Usage: 113 | // printf('I am %d %s old', 4, 'years'); 114 | // printf('%(name)s: %:2(message)j', { name: 'foo', message: { foo: 'bar' }}); 115 | const OBJECT_FMT = /%([:\-]?\d+)?\([^\)]+\)[sdjO\?]/; 116 | module.exports = function printf(format, obj/*, ..args*/) { 117 | if (!OBJECT_FMT.test(format)) { 118 | var args = new Array(arguments.length - 1); 119 | for (var i = 0; i < args.length; i++) { 120 | args[i] = arguments[i + 1]; 121 | } 122 | return printfArgs(format, args); 123 | } else { 124 | return printfObj(format, obj); 125 | } 126 | }; 127 | -------------------------------------------------------------------------------- /lib/formatter.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | const chalk = require('chalk'); 6 | const strftime = require('strftime'); 7 | const printf = require('./utils/printf'); 8 | const json = require('./utils/json'); 9 | 10 | chalk.enabled = true; 11 | 12 | const COLORS = { 13 | 'TRACE': chalk.bold.bgBlue, 14 | 'VERBOSE': chalk.bold.magenta, 15 | 'DEBUG': chalk.bold.cyan, 16 | 'INFO': chalk.bold.green, 17 | 'WARN': chalk.bold.yellow, 18 | 'ERROR': chalk.bold.red, 19 | 'CRITICAL': chalk.bold.bgRed 20 | }; 21 | const BOLD = chalk.bold; 22 | 23 | const TO_JSON = '%O'; 24 | const MESSAGE_ONLY = '%(message)s'; 25 | const BASIC_FORMAT = '%(name)s.%(levelname)s: %(message)s'; 26 | 27 | function messageOnly(record) { 28 | return record.message; 29 | } 30 | function basicFormat(record) { 31 | return record.name + '.' + record.levelname + ': ' + record.message; 32 | } 33 | function sprintf(record) { 34 | return printf(this._format, record); 35 | } 36 | 37 | function formatDate(record) { 38 | record.date = strftime(this._datefmt, new Date(record.timestamp)); 39 | } 40 | 41 | function noop() { 42 | 43 | } 44 | 45 | function optimize() { 46 | switch (this._format) { 47 | // faster shortcuts 48 | case MESSAGE_ONLY: 49 | this.__format = messageOnly; 50 | break; 51 | case BASIC_FORMAT: 52 | this.__format = basicFormat; 53 | break; 54 | case TO_JSON: 55 | this.__format = json; 56 | break; 57 | // bring on the regexp 58 | default: 59 | this.__format = sprintf; 60 | } 61 | 62 | this.__time = this._usesTime ? formatDate : noop; 63 | } 64 | 65 | function Formatter(options) { 66 | options = options || {}; 67 | if (typeof options === 'string') { 68 | options = { format: options }; 69 | } 70 | this._format = options.format || this._format; 71 | this._datefmt = options.datefmt || this._datefmt; 72 | this._strip = options.strip || this._strip; 73 | this._colorize = this._strip ? false : options.colorize || this._colorize; 74 | this._usesTime = this._format.indexOf('%(date)s') !== -1; 75 | 76 | optimize.call(this); 77 | } 78 | 79 | Formatter.prototype = { 80 | 81 | _format: MESSAGE_ONLY, 82 | 83 | _datefmt: '%Y-%m-%d %H:%M:%S', 84 | 85 | _colorize: false, 86 | 87 | _strip: false, 88 | 89 | _usesTime: false, 90 | 91 | format: function format(record) { 92 | this.__time(record); 93 | 94 | var levelname = record.levelname; 95 | var name = record.name; 96 | 97 | if (this._colorize) { 98 | var colorize = COLORS[levelname]; 99 | if (colorize) { 100 | record.levelname = colorize(levelname); 101 | } 102 | // else levelname doesnt have a color 103 | record.name = BOLD(name); 104 | } 105 | 106 | var formatted = this.__format(record); 107 | 108 | if (record.stack && this._format !== TO_JSON) { 109 | formatted += record.stack; 110 | } 111 | 112 | record.levelname = levelname; 113 | record.name = name; 114 | record.date = undefined; 115 | 116 | if (this._strip) { 117 | formatted = chalk.stripColor(formatted); 118 | } 119 | 120 | return formatted; 121 | } 122 | 123 | }; 124 | 125 | module.exports = Formatter; 126 | -------------------------------------------------------------------------------- /lib/record.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | const util = require('util'); 6 | 7 | const stacktrace = require('stack-trace'); 8 | const _Symbol = require('symbol'); 9 | 10 | const LEVELS = require('./levels'); 11 | const printf = require('./utils/printf'); 12 | 13 | const UNCAUGHT_SYMBOL = _Symbol(); 14 | const LOG_VERSION = 1; // increment when log format changes 15 | const HOSTNAME = require('os').hostname(); 16 | const MESSAGE_CACHE = _Symbol(); 17 | const PID = process.pid; 18 | 19 | // stack formatter helper 20 | function stack(e) { 21 | return { 22 | toString: function() { 23 | // first line is err.message, which we already show, so start from 24 | // second line 25 | return e.stack.substr(e.stack.indexOf('\n')); 26 | }, 27 | toJSON: function() { 28 | return stacktrace.parse(e); 29 | } 30 | }; 31 | } 32 | 33 | function Trace(fn) { 34 | Error.captureStackTrace(this, fn); 35 | } 36 | 37 | Trace.prototype.toJSON = function() { 38 | return '[object Trace]'; 39 | }; 40 | 41 | function Record(name, level, args) { 42 | this.name = name; 43 | this.level = level; 44 | this.levelname = LEVELS.getLevelName(level); 45 | this.args = args; 46 | this.pid = PID; 47 | this.host = HOSTNAME; 48 | this.v = LOG_VERSION; 49 | this.timestamp = Date.now(); 50 | 51 | var trace; 52 | var isErr = false; 53 | var i = args.length; 54 | while (i--) { 55 | var a = args[i]; 56 | if (a && ((isErr = util.isError(a)) || a instanceof Trace)) { 57 | trace = a; 58 | break; 59 | } 60 | } 61 | this.exception = isErr || undefined; 62 | this.uncaughtException = isErr ? trace[UNCAUGHT_SYMBOL] : undefined; 63 | this.stack = trace && trace.stack ? stack(trace) : undefined; 64 | } 65 | 66 | Record.prototype.name = undefined; 67 | Record.prototype.level = undefined; 68 | Record.prototype.levelname = undefined; 69 | Record.prototype.args = undefined; 70 | Record.prototype.pid = undefined; 71 | Record.prototype.host = undefined; 72 | Record.prototype.v = undefined; 73 | Record.prototype.timestamp = undefined; 74 | Record.prototype.exception = undefined; 75 | Record.prototype.uncaughtException = undefined; 76 | Record.prototype.stack = undefined; 77 | Record.prototype[MESSAGE_CACHE] = undefined; 78 | 79 | Object.defineProperty(Record.prototype, 'message', { 80 | enumerable: true, 81 | get: function() { 82 | var message = this[MESSAGE_CACHE]; 83 | if (!message) { 84 | var args = this.args; 85 | message = args[0]; 86 | var isString = typeof message === 'string'; 87 | if (!isString || args.length > 1) { 88 | if (!isString) { 89 | args = new Array(this.args.length + 1); 90 | args[0] = '%?'; 91 | var i = args.length - 1; 92 | while (i--) { 93 | args[i + 1] = this.args[i]; 94 | } 95 | } 96 | message = printf.apply(null, args); 97 | } 98 | this[MESSAGE_CACHE] = message; 99 | } 100 | return message; 101 | } 102 | }); 103 | 104 | Record.prototype.toJSON = function toJSON() { 105 | var json = {}; 106 | var keys = Object.keys(this); 107 | var i = keys.length; 108 | while (i--) { 109 | var key = keys[i]; 110 | if (key === 'timestamp') { 111 | json.timestamp = new Date(this.timestamp); 112 | } else { 113 | json[key] = this[key]; 114 | } 115 | } 116 | return json; 117 | }; 118 | 119 | Record._Trace = Trace; 120 | Record._UNCAUGHT_SYMBOL = UNCAUGHT_SYMBOL; 121 | module.exports = Record; 122 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # intel ChangeLog 2 | 3 | ### v1.2.0 - 2017-06-01 4 | 5 | - changed handleExceptions exitOnError to exit immediately, instead of 6 | on a timout. 7 | 8 | #### v1.1.2 - 2016-12-02 9 | 10 | - fixed error when error does not have a stack property 11 | 12 | #### v1.1.1 - 2016-06-22 13 | 14 | - fixed error message when handler is undefined in `intel.config()` 15 | 16 | ### v1.1.0 - 2015-12-14 17 | 18 | - added `basename` option to `intel.console()` 19 | 20 | #### v1.0.2 - 2015-09-10 21 | 22 | - fixed formatting of RegExps by default 23 | 24 | #### v1.0.1 - 2015-08-19 25 | 26 | - fixed Filter bug when more than 1 filter was added 27 | - fixed debug() interception when using intel.console() 28 | 29 | ## v1.0.0 - 2014-12-17 30 | 31 | - added `intel.console({ logger: str })` to specify a parent logger 32 | - added `record.v` to indicate log record format 33 | - added `record.host` equal the `os.hostname()` 34 | - added `%j` alias to `%O` in printf 35 | - added `%?` default formatting for an argument 36 | - added `:4` flag for indenting JSON in printf. such as `%:2j`. 37 | - fixed `logger.trace()` to no longer set `record.exception=true` 38 | - fixed cirular references in log arguments 39 | - fixed `intel.console(debug)` with uncolored output 40 | - changed `log.info('%s', false)` string interpolation to use internal printf 41 | - changed JSON format of `Record` to not include interpolated `message`, since it already contains `args` 42 | - changed Record.timestamp to use Date.now() instead of new Date() 43 | - removed `Promise`s being returned from log methods. Not useful, slows it down. 44 | - removed `Rotating` handler from core. Use [logrotate-stream](https://npmjs.org/package/logrotate-stream) or similar. 45 | - performance **HUGE BOOST ACROSS THE BOARD** 46 | 47 | #### v0.5.2 - 2014-02-19 48 | 49 | - added `strip` option to Formatter, which will strip all ANSI code 50 | 51 | #### v0.5.1 - 2014-02-12 52 | 53 | - added bgBlue to TRACE 54 | - changed uncaught exceptions log level to CRITICAL 55 | - fixed error if null was passed an argument to Logger.log() 56 | 57 | ### v0.5.0 - 2014-02-10 58 | 59 | - added direct integration with `dbug` module 60 | - added `Logger.removeAllHandlers()` 61 | - added `formatFn` for Formatters and `regex`, `flags`, and `function` for Filters to ease `intel.config()` 62 | - added `Logger#trace` and `intel.TRACE` level 63 | - added `exception: boolean` and `uncaughtException: boolean` to LogRecord 64 | - added `Logger.NONE` and `Logger.ALL` levels 65 | - changed `intel.config` to remove default ROOT console handler 66 | - changed `intel.console` to remove "lib" from logger names like "connect.lib.session" to be "connect.session" 67 | - performance improved for `Logger#getEffectiveLevel()` 68 | 69 | ### v0.4.0 - 2013-12-04 70 | 71 | - added intel.console({ debug: 'foo' }) option 72 | - performance gains 73 | 74 | #### v0.3.1 - 2013-11-04 75 | 76 | - fixed Rotating handler writing lock (thanks @chopachom) 77 | 78 | ### v0.3.0 - 2013-10-22 79 | 80 | - added intel.handlers.Rotating 81 | - added ignore options to intel.console() 82 | - added chalk.enabled when colorize is used 83 | - added padding and truncation to printf 84 | - added Logger#handleExceptions to catch uncaught exceptions 85 | - added stack traces when an exception is logged 86 | - changed datetime to strftime, adds `%L` to output milliseconds 87 | - changed Promises from Q to bluebird, significantly faster 88 | - fixed Console handler from using accepting format options 89 | - optimizations for common cases, big boost 90 | 91 | ### v0.2.0 - 2013-10-04 92 | 93 | - added Filters for Handlers and Loggers 94 | - added Handler timeout option 95 | - added pid to LogRecord 96 | - added configuration using JSON 97 | - changed Promises to LazyPromises 98 | - changed printf to faster, smaller printf 99 | - changed internal forEach to faster while loops 100 | - removed node v0.6 support (it didn't work anyways) 101 | 102 | ### v0.1.0 - 2013-09-30 103 | 104 | - Initial release. 105 | -------------------------------------------------------------------------------- /lib/config.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | const path = require('path'); 5 | const util = require('util'); 6 | 7 | const intel = require('./'); 8 | const Formatter = require('./formatter'); 9 | const Filter = require('./filter'); 10 | 11 | const INTEL_PREFIX = 'intel/'; 12 | 13 | function req(str, root) { 14 | if (str.indexOf(INTEL_PREFIX) === 0) { 15 | str = str.replace(INTEL_PREFIX, './'); 16 | } else if (str.indexOf('./') === 0 || str.indexOf('../') === 0) { 17 | str = path.join(root || process.cwd(), str); 18 | } 19 | return require(str); 20 | } 21 | 22 | function isObject(value) { 23 | return Object.prototype.toString.call(value) === '[object Object]'; 24 | } 25 | 26 | 27 | function evalFunction(source) { 28 | /*jshint evil:true*/ 29 | var fn = Function('return (' + String(source).trim() + ');')(); 30 | if (!(fn instanceof Function)) { 31 | throw new Error('function parameter did not parse as a function'); 32 | } 33 | return fn; 34 | } 35 | 36 | function configureFormatter(formatter, options) { 37 | if (formatter.formatFn) { 38 | var customFormatter = new Formatter(); 39 | if (formatter.formatFn instanceof Function) { 40 | customFormatter.format = formatter.formatFn; 41 | } else { 42 | customFormatter.format = evalFunction(formatter.formatFn); 43 | } 44 | return customFormatter; 45 | } 46 | var FormatterClass = formatter['class'] || Formatter; 47 | if (typeof FormatterClass === 'string') { 48 | FormatterClass = req(FormatterClass, options.root); 49 | } 50 | return new FormatterClass(formatter); 51 | } 52 | 53 | function configureFilter(filterOptions, options) { 54 | 55 | var FilterClass = filterOptions['class'] || Filter; 56 | if (typeof FilterClass === 'string') { 57 | FilterClass = req(FilterClass, options.root); 58 | } 59 | if (isObject(filterOptions)) { 60 | var fOpts = filterOptions; 61 | var re = fOpts.regexp || fOpts.regex || fOpts.re; 62 | var fn = fOpts.function || fOpts.fn; 63 | if (re) { 64 | filterOptions = new RegExp(re, fOpts.flags); 65 | } else if (fn) { 66 | filterOptions = evalFunction(fn); 67 | } 68 | } 69 | return new FilterClass(filterOptions); 70 | } 71 | 72 | function configureHandler(handler, options) { 73 | var HandlerClass = handler['class']; 74 | if (typeof HandlerClass === 'string') { 75 | HandlerClass = req(HandlerClass, options.root); 76 | } 77 | delete handler['class']; 78 | if (handler.formatter) { 79 | handler.formatter = options.formatters[handler.formatter]; 80 | } 81 | var hndlr = new HandlerClass(handler); 82 | if (handler.filters) { 83 | handler.filters.forEach(function eachHandler(fname) { 84 | hndlr.addFilter(options.filters[fname]); 85 | }); 86 | } 87 | return hndlr; 88 | } 89 | 90 | function getHandler(name, options) { 91 | var handler = options.handlers && options.handlers[name]; 92 | if (!handler) { 93 | var errStr = util.format('Handler "%s" is not defined in config', name); 94 | throw new Error(errStr); 95 | } 96 | if (typeof handler.handle !== 'function') { 97 | handler = options.handlers[name] = configureHandler(handler, options); 98 | } 99 | return handler; 100 | } 101 | 102 | function configureLogger(name, loggerOptions, options) { 103 | var logger = intel.getLogger(name); 104 | if (loggerOptions.level != null) { 105 | logger.setLevel(loggerOptions.level); 106 | } 107 | 108 | if (loggerOptions.handlers) { 109 | loggerOptions.handlers.forEach(function eachHandler(hName) { 110 | logger.addHandler(getHandler(hName, options)); 111 | }); 112 | } 113 | if (loggerOptions.filters) { 114 | loggerOptions.filters.forEach(function eachHandler(fname) { 115 | logger.addFilter(options.filters[fname]); 116 | }); 117 | } 118 | 119 | if (loggerOptions.propagate != null) { 120 | logger.propagate = loggerOptions.propagate; 121 | } 122 | 123 | if (loggerOptions.handleExceptions) { 124 | logger.handleExceptions(loggerOptions.exitOnError); 125 | } 126 | } 127 | 128 | module.exports = function config(options) { 129 | // lets do formatters and filters first, since they dont depend on anything 130 | // then handlers, since they can depend on formatters 131 | // and then loggers, since they can depend on handlers 132 | 133 | var formatters = options.formatters || {}; 134 | for (var f in formatters) { 135 | formatters[f] = configureFormatter(formatters[f], options); 136 | } 137 | 138 | var filters = options.filters || {}; 139 | for (var fi in filters) { 140 | filters[fi] = configureFilter(filters[fi], options); 141 | } 142 | 143 | if (options.handlers) { 144 | intel.basicConfig({ null: true}); 145 | } 146 | 147 | var loggers = options.loggers || {}; 148 | for (var l in loggers) { 149 | configureLogger(l, loggers[l], options); 150 | } 151 | 152 | if (options.console) { 153 | var consoleOpts = isObject(options.console) ? options.console : {}; 154 | consoleOpts.__trace = config; 155 | intel.console(consoleOpts); 156 | } 157 | }; 158 | -------------------------------------------------------------------------------- /test/handlers.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | const assert = require('insist'); 6 | 7 | const intel = require('../'); 8 | 9 | module.exports = { 10 | 'Handler': { 11 | 'constructor': { 12 | 'should accept options': function() { 13 | var h = new intel.Handler({ level: intel.ERROR }); 14 | assert.equal(h.level, intel.ERROR); 15 | }, 16 | 'should accept a level': function() { 17 | var h = new intel.Handler(intel.WARN); 18 | assert.equal(h.level, intel.WARN); 19 | } 20 | }, 21 | 'handle': { 22 | 'requires emit to accept a callback argument': function() { 23 | var h = new intel.Handler(); 24 | 25 | assert.throws(function() { 26 | h.emit = 1; 27 | }, function(err) { 28 | return err.message === 'emit must be a function'; 29 | }); 30 | 31 | h = new intel.Handler(); 32 | assert.doesNotThrow(function() { 33 | h.emit = function() {}; 34 | }); 35 | }, 36 | 'should use filters on record': function() { 37 | var h = new intel.Handler(); 38 | var lastRecord; 39 | h.emit = function(record){ 40 | lastRecord = record; 41 | }; 42 | 43 | var filter = new intel.Filter('foo'); 44 | h.addFilter(filter); 45 | h.handle({ name: 'foo' }); 46 | assert.equal(lastRecord.name, 'foo'); 47 | h.handle({ name: 'foobar' }); 48 | assert.notEqual(lastRecord.name, 'foobar'); 49 | 50 | h.removeFilter(filter); 51 | h.handle({ name: 'foobar' }); 52 | assert.equal(lastRecord.name, 'foobar'); 53 | } 54 | }, 55 | 'emit': { 56 | 'must be overriden by subclasses': function() { 57 | var h = new intel.Handler(); 58 | assert.throws(h.emit); 59 | } 60 | } 61 | }, 62 | 'Stream': { 63 | 'constructor': { 64 | 'should accept options': function() { 65 | var stream = {}; 66 | var handler = new intel.handlers.Stream({ 67 | level: intel.INFO, 68 | stream: stream 69 | }); 70 | 71 | assert.equal(handler.level, intel.INFO); 72 | assert.equal(handler._stream, stream); 73 | }, 74 | 'should accept just a stream': function() { 75 | var stream = {}; 76 | var handler = new intel.handlers.Stream(stream); 77 | 78 | assert.equal(handler.level, intel.NOTSET); 79 | assert.equal(handler._stream, stream); 80 | } 81 | }, 82 | 'emit': { 83 | 'should write message to stream': function() { 84 | var out; 85 | var stream = { 86 | write: function(msg) { 87 | out = msg; 88 | } 89 | }; 90 | 91 | var handler = new intel.handlers.Stream(stream); 92 | handler.handle({ message: 'foo' }); 93 | assert.equal(out, 'foo\n'); 94 | } 95 | } 96 | }, 97 | 'File': { 98 | 'before': function() { 99 | intel.handlers.File.prototype._open = function() { 100 | return { 101 | write: function(val) { 102 | this._val = val; 103 | } 104 | }; 105 | }; 106 | }, 107 | 'constructor': { 108 | 'should accept options': function() { 109 | var filename = 'foo'; 110 | var handler = new intel.handlers.File({ 111 | level: intel.WARN, 112 | file: filename 113 | }); 114 | 115 | assert.equal(handler.level, intel.WARN); 116 | assert.equal(handler._file, filename); 117 | }, 118 | 'should accept a filename': function() { 119 | var filename = 'bar'; 120 | var handler = new intel.handlers.File(filename); 121 | 122 | assert.equal(handler._file, filename); 123 | } 124 | }, 125 | 'handle': { 126 | 'should write to the file': function() { 127 | var filename = 'baz'; 128 | var handler = new intel.handlers.File(filename); 129 | handler.handle({ message: 'recon' }); 130 | assert.equal(handler._stream._val, 'recon\n'); 131 | } 132 | } 133 | }, 134 | 'Console': { 135 | 'constructor': { 136 | 'should use stdout and stderr': function() { 137 | var h = new intel.handlers.Console(); 138 | assert.equal(h._out._stream, process.stdout); 139 | assert.equal(h._err._stream, process.stderr); 140 | }, 141 | 'should pass options to StreamHandlers': function() { 142 | var f = new intel.Formatter({ colorize: true }); 143 | var h = new intel.handlers.Console({ formatter: f }); 144 | assert(h._out._formatter._colorize); 145 | } 146 | }, 147 | 'handle': { 148 | 'should send low priority messages to stdout': function() { 149 | var h = new intel.handlers.Console(); 150 | var val; 151 | h._out._stream = { 152 | write: function(out) { 153 | val = out; 154 | return true; 155 | } 156 | }; 157 | 158 | h.handle({ level: intel.INFO, message: 'oscar mike' }); 159 | assert.equal(val, 'oscar mike\n'); 160 | }, 161 | 'should send warn and higher messages to stderr': function() { 162 | var h = new intel.handlers.Console(); 163 | var val; 164 | h._err._stream = { 165 | write: function(out) { 166 | val = out; 167 | return true; 168 | } 169 | }; 170 | 171 | h.handle({ level: intel.WARN, message: 'mayday' }); 172 | assert.equal(val, 'mayday\n'); 173 | } 174 | } 175 | } 176 | }; 177 | -------------------------------------------------------------------------------- /test/formatter.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | const assert = require('insist'); 6 | 7 | const intel = require('../'); 8 | 9 | 10 | module.exports = { 11 | 'Formatter': { 12 | 13 | 'constructor': { 14 | 'should accept a format string': function() { 15 | var formatter = new intel.Formatter('%(level)s'); 16 | assert.equal(formatter._format, '%(level)s'); 17 | assert.equal(formatter._datefmt, intel.Formatter.prototype._datefmt); 18 | assert(!formatter._colorize); 19 | assert.equal(formatter._strip, false); 20 | }, 21 | 'should accept options': function() { 22 | var formatter = new intel.Formatter({ 23 | format: '%(levelname)s', 24 | datefmt: '%Y', 25 | colorize: true, 26 | strip: false 27 | }); 28 | 29 | assert.equal(formatter._format, '%(levelname)s'); 30 | assert.equal(formatter._datefmt, '%Y'); 31 | assert.equal(formatter._colorize, true); 32 | assert.equal(formatter._strip, false); 33 | }, 34 | 'should disable colorize when strip is enabled': function() { 35 | var formatter = new intel.Formatter({ 36 | colorize: true, 37 | strip: true 38 | }); 39 | 40 | assert.equal(formatter._strip, true); 41 | assert.equal(formatter._colorize, false); 42 | } 43 | }, 44 | 45 | 46 | 'format': { 47 | 'should use printf': function() { 48 | var formatter = new intel.Formatter('%(name)s: %(message)s'); 49 | assert.equal(formatter.format({ name: 'foo', message: 'bar' }), 50 | 'foo: bar'); 51 | }, 52 | 'should be able to output record args': function() { 53 | var formatter = new intel.Formatter('%(name)s: %(message)s [%(args)s]'); 54 | var record = { 55 | name: 'foo', 56 | message: 'bar', 57 | args: ['baz', 3, new Error('quux')] 58 | }; 59 | assert.equal(formatter.format(record), 'foo: bar [baz,3,Error: quux]'); 60 | }, 61 | 'should output as JSON with %O': function() { 62 | var formatter = new intel.Formatter('%O'); 63 | var Record = require('../lib/record'); 64 | var record = new Record('foo', intel.INFO, ['bar', { a: 'b' }]); 65 | 66 | var out = formatter.format(record); 67 | assert.equal(out, JSON.stringify(record)); 68 | var obj = JSON.parse(out); 69 | assert.equal(obj.args[0], 'bar'); 70 | assert.equal(obj.args[1].a, 'b'); 71 | }, 72 | 'should handle circular references in JSON with %O': function() { 73 | var formatter = new intel.Formatter('%O'); 74 | var foo = { 75 | bar: 'bar' 76 | }; 77 | var baz = { 78 | quux: 'quux', 79 | foo: foo 80 | }; 81 | foo.baz = baz; 82 | var record = { 83 | name: 'foo', 84 | message: 'oh noes:', 85 | args: ['oh noes:', foo] 86 | }; 87 | 88 | assert.equal(JSON.parse(formatter.format(record)).args[1].baz.foo, 89 | '[Circular]'); 90 | }, 91 | 'should output an Error stack': function() { 92 | var formatter = new intel.Formatter('%(name)s: %(message)s'); 93 | var e = new Error('boom'); 94 | var trace = e.stack.substr(e.stack.indexOf('\n')); 95 | var record = { 96 | name: 'foo', 97 | message: 'oh noes: ', 98 | args: ['oh noes:', e], 99 | stack: trace 100 | }; 101 | 102 | assert.equal(formatter.format(record), 'foo: oh noes: ' + trace); 103 | }, 104 | 'datefmt': { 105 | 'should format the date': function() { 106 | var formatter = new intel.Formatter({ 107 | format: '%(date)s', 108 | datefmt: '%Y-%m' 109 | }); 110 | 111 | var d = new Date(); 112 | var record = { 113 | timestamp: d.getTime() 114 | }; 115 | 116 | function pad(val) { 117 | if (val > 9) { 118 | return val; 119 | } 120 | return '0' + val; 121 | } 122 | var expected = d.getFullYear() + '-' + pad(d.getMonth() + 1); 123 | assert.equal(formatter.format(record), expected); 124 | } 125 | }, 126 | 'colorize': { 127 | 'should colorize the output': function() { 128 | var formatter = new intel.Formatter({ 129 | format: '%(levelname)s: %(message)s', 130 | colorize: true 131 | }); 132 | 133 | 134 | var record = { 135 | levelname: 'ERROR', 136 | message: 'foo' 137 | }; 138 | 139 | assert.equal(formatter.format(record), 140 | '\u001b[1m\u001b[31mERROR\u001b[39m\u001b[22m: foo'); 141 | } 142 | }, 143 | 'strip': { 144 | 'should strip ANSI escape codes from the output': function() { 145 | var formatter = new intel.Formatter({ 146 | format: '%(levelname)s: %(message)s [%(args)s]', 147 | strip: true 148 | }); 149 | 150 | var record = { 151 | levelname: 'INFO', 152 | message: '\u001b[36mHello World\u001b[39m', 153 | args: [ 154 | '\u001b[31mFoo\u001b[39m', 155 | '\u001b[35mBar\u001b[39m', 156 | '\u001b[33mBaz\u001b[39m' 157 | ] 158 | }; 159 | 160 | assert.equal(formatter.format(record), 161 | 'INFO: Hello World [Foo,Bar,Baz]'); 162 | } 163 | } 164 | } 165 | } 166 | }; 167 | -------------------------------------------------------------------------------- /test/config.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | const os = require('os'); 6 | const path = require('path'); 7 | const util = require('util'); 8 | 9 | const assert = require('insist'); 10 | 11 | const intel = require('../'); 12 | 13 | const NOW = Date.now(); 14 | var counter = 1; 15 | function tmp() { 16 | return path.join(os.tmpDir(), 17 | 'intel-' + NOW + '-' + process.pid + '-' + (counter++)); 18 | } 19 | 20 | function spy() { 21 | var args = []; 22 | var fn = function() { 23 | args.push(arguments); 24 | }; 25 | fn.getCallCount = function() { return args.length; }; 26 | fn.getLastArgs = function() { return args[args.length - 1]; }; 27 | return fn; 28 | } 29 | 30 | function SpyHandler() { 31 | intel.Handler.apply(this, arguments); 32 | this.spy = spy(); 33 | } 34 | util.inherits(SpyHandler, intel.Handler); 35 | SpyHandler.prototype.emit = function spyEmit(record) { 36 | this.spy(record); 37 | }; 38 | 39 | var oldBasic; 40 | var oldLevel; 41 | module.exports = { 42 | 'basicConfig': { 43 | 'before': function() { 44 | oldBasic = intel.basicConfig; 45 | oldLevel = intel._level; 46 | }, 47 | 'root logger calls basicConfig': function() { 48 | var val; 49 | var stream = { 50 | write: function(out) { 51 | val = out; 52 | } 53 | }; 54 | 55 | intel.basicConfig = function() { 56 | oldBasic({ stream: stream }); 57 | }; 58 | 59 | intel.info('danger'); 60 | assert.equal(val, 'root.INFO: danger\n'); 61 | assert.equal(intel._level, oldLevel); 62 | }, 63 | 'only works once': function() { 64 | intel.basicConfig(); 65 | assert.equal(intel._level, oldLevel); 66 | 67 | intel.basicConfig({ level: 'critical' }); 68 | assert.equal(intel._handlers.length, 1); 69 | assert.equal(intel._level, oldLevel); 70 | }, 71 | 'works with file option': function() { 72 | var name = tmp(); 73 | intel.basicConfig({ file: name }); 74 | assert.equal(intel._handlers.length, 1); 75 | assert.equal(intel._handlers[0]._file, name); 76 | }, 77 | 'works with level': function() { 78 | intel.basicConfig({ level: 'error' }); 79 | assert.equal(intel._level, intel.ERROR); 80 | }, 81 | 'works with format': function() { 82 | intel.basicConfig({ format: '%(foo)s'}); 83 | assert.equal(intel._handlers[0]._formatter._format, '%(foo)s'); 84 | }, 85 | 'afterEach': function() { 86 | intel.basicConfig = oldBasic; 87 | intel.setLevel(oldLevel); 88 | intel._handlers = []; 89 | } 90 | }, 91 | 'config': { 92 | 'should be able to configure logging': function() { 93 | intel.config({ 94 | formatters: { 95 | 'basic': { 96 | 'format': '%(message)s' 97 | }, 98 | 'foo': { 99 | 'format': 'foo! %(levelname)s: %(message)s' 100 | }, 101 | 'fn': { 102 | 'formatFn': function() {} 103 | } 104 | }, 105 | filters: { 106 | 'user': /\buser\b/g 107 | }, 108 | handlers: { 109 | 'null': { 110 | 'class': SpyHandler, 111 | 'formatter': 'foo', 112 | 'filters': ['user'] 113 | } 114 | }, 115 | loggers: { 116 | 'qqq.zzz': { 117 | 'level': 'INFO', 118 | 'propagate': false, 119 | 'handlers': ['null'] 120 | }, 121 | 'qqq.ww.zzz': { 122 | 'level': 'INFO', 123 | 'propagate': false, 124 | 'handleExceptions': true, 125 | 'exitOnError': false, 126 | 'filters': ['user'], 127 | 'handlers': ['null'] 128 | } 129 | }, 130 | console: true 131 | }); 132 | intel.console.restore(); 133 | 134 | var excepts = intel.getLogger('qqq.ww.zzz'); 135 | assert(excepts._uncaughtException); 136 | assert(!excepts._exitOnError); 137 | assert.equal(excepts._filters.length, 1); 138 | excepts.unhandleExceptions(); 139 | 140 | var log = intel.getLogger('qqq.zzz'); 141 | var handler = log._handlers[0]; 142 | assert.equal(log._handlers.length, 1); 143 | assert(!log.propagate); 144 | 145 | var msg = handler.format({ message: 'hi', levelname: 'BAR'}); 146 | assert.equal(msg, 'foo! BAR: hi'); 147 | 148 | log.debug('user'); 149 | assert.equal(handler.spy.getCallCount(), 0); 150 | 151 | log.info('user foo'); 152 | assert.equal(handler.spy.getCallCount(), 1); 153 | assert.equal(handler.spy.getLastArgs()[0].message, 'user foo'); 154 | 155 | log.info('ignore me'); 156 | assert.equal(handler.spy.getCallCount(), 1); 157 | }, 158 | 'should be able to config with just JSON': function() { 159 | intel.config(require('./util/config.json')); 160 | 161 | var log = intel.getLogger('test.config.json'); 162 | assert.equal(log._handlers.length, 2); 163 | 164 | var s = log._handlers[0]._filters[0]; 165 | var r = log._handlers[0]._filters[1]; 166 | var f = log._handlers[0]._filters[2]; 167 | 168 | assert(s.filter({ name: 'foo.bar' })); 169 | assert(!s.filter({ name: 'food' })); 170 | 171 | assert(r.filter({ message: 'FOO' })); 172 | assert(!r.filter({ message: '' })); 173 | 174 | assert(f.filter({ args: [1, 2, 3] })); 175 | assert(!f.filter({ args: [1] })); 176 | 177 | var custom = log._handlers[0]._formatter; 178 | assert.equal(custom.format({ message: 'foo' }), 'FOO'); 179 | }, 180 | 'should error if formatFn is not a function': function() { 181 | assert.throws(function() { 182 | intel.config({ 183 | formatters: { 184 | 'foo': { 185 | formatFn: 'this' 186 | } 187 | } 188 | }); 189 | }, /function parameter did not parse as a function$/); 190 | }, 191 | 'should error if cannot find handler': function() { 192 | assert.throws(function() { 193 | intel.config({ 194 | loggers: { 195 | 'missingHandler': { 196 | handlers: ['nope'] 197 | } 198 | } 199 | }); 200 | }, /Handler "nope" is not defined in config$/); 201 | }, 202 | 'should assign a NullHandler to ROOT if handlers object': function() { 203 | intel._handlers = []; 204 | intel.config({ 205 | handlers: {}, 206 | loggers: { 207 | 'port': {}, 208 | } 209 | }); 210 | assert(intel._handlers[0] instanceof intel.handlers.Null); 211 | } 212 | } 213 | }; 214 | -------------------------------------------------------------------------------- /test/console.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | const assert = require('insist'); 6 | 7 | const intel = require('../'); 8 | const consoleUtil = require('./util/console'); 9 | const dbugUtil = require('./util/debarg'); 10 | 11 | var spy = new intel.handlers.Null(); 12 | spy.emit = function(record) { 13 | this._lastRecord = record; 14 | }; 15 | 16 | var __log = require('dbug').__log; 17 | var prevLog = console.log; 18 | var lastMock; 19 | function mockLog() { 20 | lastMock = arguments; 21 | } 22 | 23 | var prevStdErr = process.stderr.write; 24 | var lastStdErr; 25 | function mockStdErr() { 26 | lastStdErr = arguments; 27 | return true; 28 | } 29 | 30 | function clearDebug() { 31 | Object.keys(require.cache).filter(function(key) { 32 | return key.indexOf('debug') !== -1; 33 | }).forEach(function(key) { 34 | delete require.cache[key]; 35 | }); 36 | } 37 | 38 | module.exports = { 39 | 40 | 'console': { 41 | 'before': function() { 42 | intel.addHandler(spy); 43 | intel.setLevel(intel.TRACE); 44 | console.log = mockLog; 45 | }, 46 | 'beforeEach': function() { 47 | delete spy._lastRecord; 48 | // not passing root means this file becomes root. 49 | // which means dirname.basename, or test.console 50 | intel.console(); 51 | }, 52 | 'can inject into global scope': function() { 53 | console.warn('test'); 54 | assert(spy._lastRecord); 55 | assert.equal(spy._lastRecord.message, 'test'); 56 | }, 57 | 'can generate a name': function() { 58 | console.log('foo'); 59 | assert.equal(spy._lastRecord.name, 'test.console'); 60 | 61 | consoleUtil('bar'); 62 | assert.equal(spy._lastRecord.name, 'test.util.console'); 63 | }, 64 | 'can ignore paths': function() { 65 | intel.console({ ignore: 'test.util' }); 66 | 67 | console.log('quux'); 68 | assert.equal(spy._lastRecord.message, 'quux'); 69 | 70 | consoleUtil('baz'); 71 | assert.notEqual(spy._lastRecord.message, 'baz'); 72 | assert.equal(lastMock[0], 'baz'); 73 | 74 | intel.console(); 75 | }, 76 | 'can specify a parent logger': function() { 77 | intel.console({ logger: 'foo.bar' }); 78 | 79 | console.log('baz'); 80 | assert.equal(spy._lastRecord.name, 'foo.bar.test.console'); 81 | }, 82 | 'overrides console.dir()': function() { 83 | var obj = { foo: 'bar' }; 84 | console.dir(obj); 85 | assert.equal(spy._lastRecord.message, "{ foo: 'bar' }"); 86 | }, 87 | 'overrides console.trace()': function() { 88 | console.trace("foo"); 89 | assert.equal(spy._lastRecord.message, "foo"); 90 | assert(spy._lastRecord.stack); 91 | }, 92 | 'afterEach': function() { 93 | intel.console.restore(); 94 | }, 95 | 'after': function() { 96 | intel.console.restore(); 97 | assert.equal(console.log, mockLog); 98 | console.log = prevLog; 99 | intel._handlers = []; 100 | } 101 | }, 102 | 103 | 'console.debug': { 104 | 'before': function() { 105 | intel.addHandler(spy); 106 | }, 107 | 'beforeEach': function() { 108 | delete spy._lastRecord; 109 | process.stderr.write = mockStdErr; 110 | }, 111 | 'with true sets debug to star': function() { 112 | intel.console({ debug: true }); 113 | assert.equal(process.env.DEBUG, '*'); 114 | }, 115 | 'with string adds to DEBUG': function() { 116 | process.env.DEBUG = 'quux'; 117 | intel.console({ debug: 'foo:bar,baz'}); 118 | assert.equal(process.env.DEBUG, 'quux,foo:bar,baz'); 119 | }, 120 | 'with string sets DEBUG if empty': function() { 121 | delete process.env.DEBUG; 122 | intel.console({ debug: 'foo:bar,baz'}); 123 | assert.equal(process.env.DEBUG, 'foo:bar,baz'); 124 | }, 125 | 'with false sets DEBUG to empty': function() { 126 | process.env.DEBUG = 'fooz'; 127 | intel.console({ debug: false }); 128 | assert(!process.env.DEBUG); 129 | }, 130 | 'intercepts dbug() messages': function() { 131 | intel.console({ debug: 'platoon' }); 132 | 133 | var dbug = require('dbug')('platoon:sarge:squad'); 134 | dbug('oscar mike'); 135 | 136 | assert(spy._lastRecord); 137 | assert.equal(spy._lastRecord.message, 'oscar mike'); 138 | assert.equal(spy._lastRecord.name, 'test.console.platoon.sarge.squad'); 139 | assert.equal(spy._lastRecord.level, intel.DEBUG); 140 | 141 | dbug.warn('boom'); 142 | assert.equal(spy._lastRecord.message, 'boom'); 143 | assert.equal(spy._lastRecord.level, intel.WARN); 144 | 145 | }, 146 | 'intercepts dbug() colorless messages': function() { 147 | intel.console({ debug: 'company' }); 148 | 149 | var _dbug = require('dbug'); 150 | _dbug.__log = __log; 151 | var dbug = _dbug('company:bravo'); 152 | dbug.colored = false; 153 | dbug('oscar mike'); 154 | 155 | assert(spy._lastRecord); 156 | assert.equal(spy._lastRecord.message, 'oscar mike'); 157 | assert.equal(spy._lastRecord.name, 'test.console.company.bravo'); 158 | assert.equal(spy._lastRecord.level, intel.DEBUG); 159 | }, 160 | 'removes duplicate parts of dbug names': function() { 161 | intel.console({ debug: 'test' }); 162 | 163 | dbugUtil('hut'); 164 | assert(spy._lastRecord); 165 | assert.equal(spy._lastRecord.name, 'test.util.debarg'); 166 | }, 167 | 'removes lib from name if matches': function() { 168 | intel.console({ debug: 'test' }); 169 | 170 | var deboog = require('./util/lib/deboog'); 171 | deboog('hat'); 172 | assert(spy._lastRecord); 173 | assert.equal(spy._lastRecord.name, 'test.util.deboog'); 174 | }, 175 | 'removes index from names': function() { 176 | intel.console({ debug: 'test' }); 177 | 178 | var deboog = require('./util/lib/index.js'); 179 | deboog('hat'); 180 | assert(spy._lastRecord); 181 | assert.equal(spy._lastRecord.name, 'test.util'); 182 | }, 183 | 'intercepts debug() messages': function() { 184 | intel.console({ debug: 'recon,escapes', ignore: 'test.console.escapes' }); 185 | clearDebug(); 186 | 187 | var debug = require('debug')('recon'); 188 | assert(debug.enabled); 189 | debug('report'); 190 | 191 | assert(spy._lastRecord); 192 | assert.equal(spy._lastRecord.message, 'report +0ms'); 193 | assert.equal(spy._lastRecord.name, 'test.console.recon'); 194 | assert.equal(spy._lastRecord.level, intel.DEBUG); 195 | 196 | var escapes = require('debug')('escapes'); 197 | assert(escapes.enabled); 198 | escapes('fugitive'); 199 | assert(lastStdErr[0].indexOf('fugitive') !== -1); 200 | 201 | 202 | }, 203 | 'afterEach': function() { 204 | process.env.DEBUG_COLORS = ""; 205 | intel.console.restore(); 206 | process.stderr.write = prevStdErr; 207 | }, 208 | 'after': function() { 209 | intel.setLevel(intel.DEBUG); 210 | console.log = prevLog; 211 | intel._handlers = []; 212 | } 213 | } 214 | 215 | }; 216 | -------------------------------------------------------------------------------- /lib/logger.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | const assert = require('assert'); 6 | 7 | const dbug = require('dbug')('intel:logger'); 8 | 9 | const klass = require('./utils/klass'); 10 | const LEVELS = require('./levels'); 11 | const Filterer = require('./filterer'); 12 | const Record = require('./record'); 13 | const Trace = Record._Trace; 14 | 15 | var __loggers = {}; 16 | var __levelCache = {}; 17 | const ROOT = 'root'; 18 | const DIVIDER = '.'; 19 | const OTHER_DIVIDERS = /[\/\\]/g; 20 | 21 | function emptyStr() { return ''; } 22 | function traceStr() { return 'Trace'; } 23 | 24 | function getEffectiveParent(name) { 25 | if (name === ROOT) { 26 | return; 27 | } 28 | 29 | var parent; 30 | while (name.indexOf(DIVIDER) !== -1 && !parent) { 31 | name = name.substring(0, name.lastIndexOf(DIVIDER)); 32 | parent = __loggers[name]; 33 | } 34 | return parent || __loggers[ROOT]; 35 | } 36 | 37 | function getChildren(name) { 38 | var children = []; 39 | for (var k in __loggers) { 40 | if ((name === ROOT || k.indexOf(name) === 0) && 41 | k !== name && 42 | getEffectiveParent(k) === __loggers[name]) { 43 | children.push(__loggers[k]); 44 | } 45 | } 46 | return children; 47 | } 48 | 49 | function logNoop() {} 50 | 51 | function disableLevels(logger) { 52 | for (var name in LEVELS) { 53 | if (typeof LEVELS[name] === 'number') { 54 | var level = LEVELS[name]; 55 | name = name.toLowerCase(); 56 | if (logger[name]) { 57 | if (logger.isEnabledFor(level)) { 58 | // delete any noops 59 | delete logger[name]; 60 | } else { 61 | Object.defineProperty(logger, name, { 62 | configurable: true, 63 | value: logNoop 64 | }); 65 | } 66 | } 67 | } 68 | } 69 | var children = getChildren(logger._name); 70 | children.forEach(function(child) { 71 | disableLevels(child); 72 | }); 73 | } 74 | 75 | function logAtLevel(level) { 76 | return function(msg /*, args...*/) { 77 | switch (arguments.length) { 78 | //faster cases 79 | case 0: 80 | case 1: 81 | return this._log(level, msg); 82 | case 2: 83 | return this._log(level, msg, arguments[1]); 84 | default: 85 | //turtles 86 | var args = new Array(arguments.length + 1); 87 | args[0] = level; 88 | for (var i = 1; i < args.length; i++) { 89 | args[i] = arguments[i - 1]; 90 | } 91 | return this._log.apply(this, args); 92 | } 93 | }; 94 | } 95 | 96 | 97 | function Logger(name) { 98 | if (!name) { 99 | name = ROOT; 100 | } 101 | name = name.replace(OTHER_DIVIDERS, DIVIDER); 102 | if (name in __loggers) { 103 | return __loggers[name]; 104 | } 105 | __loggers[name] = this; 106 | this._name = name; 107 | disableLevels(this); 108 | 109 | Filterer.call(this); 110 | 111 | this._handlers = []; 112 | 113 | dbug('Logger (%s) created', name); 114 | } 115 | 116 | klass(Logger).inherit(Filterer).mixin({ 117 | 118 | //_handlers: [], 119 | 120 | _name: null, 121 | 122 | _level: null, 123 | 124 | _handlesExceptions: false, 125 | 126 | _exitOnError: true, 127 | 128 | propagate: true, 129 | 130 | setLevel: function setLevel(val) { 131 | var level = LEVELS.getLevel(val); 132 | assert(level != null, 'Cannot set level with provided value: ' + val); 133 | this._level = level; 134 | // clean __levelsCache 135 | __levelCache = {}; 136 | disableLevels(this); 137 | return this; 138 | }, 139 | 140 | 141 | getEffectiveLevel: function getEffectiveLevel() { 142 | if (this._level != null) { 143 | return this._level; 144 | } else if (this._name in __levelCache) { 145 | return __levelCache[this._name]; 146 | } else { 147 | var parent = getEffectiveParent(this._name); 148 | return __levelCache[this._name] = parent ? 149 | parent.getEffectiveLevel() : 150 | LEVELS.NOTSET; 151 | } 152 | }, 153 | 154 | isEnabledFor: function isEnabledFor(level) { 155 | return level >= this.getEffectiveLevel(); 156 | }, 157 | 158 | addHandler: function addHandler(handler) { 159 | this._handlers.push(handler); 160 | return this; 161 | }, 162 | 163 | removeHandler: function removeHandler(handler) { 164 | var index = this._handlers.indexOf(handler); 165 | if (index !== -1) { 166 | this._handlers.splice(index, 1); 167 | } 168 | return this; 169 | }, 170 | 171 | removeAllHandlers: function removeAllHandlers() { 172 | this._handlers = []; 173 | }, 174 | 175 | handleExceptions: function handleExceptions(exitOnError/* = true */) { 176 | this._exitOnError = exitOnError === false ? false : true; 177 | if (!this._uncaughtException) { 178 | this._uncaughtException = this.catchException.bind(this); 179 | this._process.on('uncaughtException', this._uncaughtException); 180 | } 181 | }, 182 | 183 | unhandleExceptions: function unhandleExceptions() { 184 | this._process.removeListener('uncaughtException', this._uncaughtException); 185 | delete this._uncaughtException; 186 | }, 187 | 188 | catchException: function catchException(err) { 189 | var exits = this._exitOnError; 190 | 191 | err[Record._UNCAUGHT_SYMBOL] = true; 192 | this.critical(err); 193 | if (exits) { 194 | this._exit(); 195 | } 196 | }, 197 | 198 | _exit: function _exit() { 199 | this._process.exit(1); 200 | }, 201 | 202 | makeRecord: function makeRecord(name, level, msg) { 203 | return new Record(name, level, msg); 204 | }, 205 | 206 | handle: function handle(record) { 207 | if (this.filter(record)) { 208 | 209 | var i = this._handlers.length; 210 | while (i--) { 211 | if (record.level >= this._handlers[i].level) { 212 | this._handlers[i].handle(record); 213 | } 214 | } 215 | 216 | // if this.propagate, tell our parent 217 | if (this.propagate) { 218 | var par = getEffectiveParent(this._name); 219 | if (par) { 220 | par.handle(record); 221 | } 222 | } 223 | 224 | } else { 225 | dbug('%s filtered message [%s]', this._name, record.message); 226 | } 227 | }, 228 | 229 | _log: function _log(_level, msg /*, args...*/) { 230 | var level = LEVELS.getLevel(_level); 231 | var args; 232 | if (arguments.length < 3) { 233 | args = [msg]; 234 | } else { 235 | args = new Array(arguments.length - 1); 236 | for (var i = 0; i < args.length; i++) { 237 | args[i] = arguments[i + 1]; 238 | } 239 | } 240 | var record = this.makeRecord(this._name, level, args); 241 | this.handle(record); 242 | }, 243 | 244 | log: function log(_level /* msg, messageArs... */) { 245 | var level = LEVELS.getLevel(_level); 246 | // if level >= this.getEffectiveLevel(), tell our handlers 247 | if (this.isEnabledFor(level)) { 248 | this._log.apply(this, arguments); 249 | } 250 | }, 251 | 252 | trace: function trace(_msg) { 253 | var obj = new Trace(trace); 254 | 255 | var slice = 0; 256 | var message = _msg; 257 | if (typeof message === 'string') { 258 | // [TRACE, msg, obj, ...arguments] 259 | slice = 1; 260 | message = '%s' + message; 261 | obj.toString = emptyStr; 262 | } else { 263 | message = '%s'; 264 | obj.toString = traceStr; 265 | } 266 | 267 | var args = new Array(3 + arguments.length - slice); 268 | args[0] = LEVELS.TRACE; 269 | args[1] = message; 270 | args[2] = obj; 271 | for (var i = 3; i < args.length; i++) { 272 | args[i] = arguments[i - 3 + slice]; 273 | } 274 | return this._log.apply(this, args); 275 | }, 276 | 277 | verbose: logAtLevel(LEVELS.VERBOSE), 278 | debug: logAtLevel(LEVELS.DEBUG), 279 | info: logAtLevel(LEVELS.INFO), 280 | warn: logAtLevel(LEVELS.WARNING), 281 | error: logAtLevel(LEVELS.ERROR), 282 | critical: logAtLevel(LEVELS.CRITICAL), 283 | 284 | // aliases 285 | warning: function warning() { 286 | return this.warn.apply(this, arguments); 287 | }, 288 | 289 | /*jshint -W106*/ // ignore camelCase warning for this fun functions 290 | o_O: function o_O() { 291 | return this.warn.apply(this, arguments); 292 | }, 293 | 294 | O_O: function O_O() { 295 | return this.error.apply(this, arguments); 296 | }, 297 | 298 | _process: process 299 | 300 | }); 301 | 302 | for (var k in LEVELS) { 303 | if (typeof LEVELS[k] === 'number') { 304 | Logger[k] = Logger.prototype[k] = LEVELS[k]; 305 | } 306 | } 307 | 308 | module.exports = Logger; 309 | -------------------------------------------------------------------------------- /lib/console.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | const path = require('path'); 6 | const util = require('util'); 7 | 8 | const chalk = require('chalk'); 9 | const dbug = require('dbug'); 10 | const stack = require('stack-trace'); 11 | const utc = require('utcstring'); 12 | 13 | const intel = require('./'); 14 | 15 | const ALIASES = [ 16 | 'trace', 17 | 'debug', 18 | 'info', 19 | 'warn', 20 | 'error' 21 | ]; 22 | 23 | function copyProperties(source, target, props) { 24 | props.forEach(function(prop) { 25 | target[prop] = source[prop]; 26 | }); 27 | } 28 | 29 | var ORIGINAL_METHODS = {}; 30 | const METHOD_NAMES = [ 31 | 'trace', 32 | 'debug', 33 | 'dir', 34 | 'error', 35 | 'info', 36 | 'log', 37 | 'warn' 38 | ]; 39 | 40 | var ORIGINAL_STDERR; 41 | 42 | function endsWith(str, suffix) { 43 | return str.indexOf(suffix, str.length - suffix.length) !== -1; 44 | } 45 | 46 | var root; 47 | var ignore; 48 | var parentLogger; 49 | var basename; 50 | 51 | function getLoggerName(debugName) { 52 | var trace = stack.get(); 53 | // walk up the stack until we find a function that isn't from this 54 | // module. that's the calling module. 55 | // ALSO: 'console.js' could be on the stack if console.trace() or 56 | // something similar was called, cause we don't modify those, since 57 | // they internally call console.log(), which is us! 58 | var filename; 59 | var debug = []; 60 | if (debugName) { 61 | debug = [ 62 | // debug@0.x 63 | path.join('node_modules', 'debug', 'lib', 'debug.js'), 64 | // debug@1.x 65 | path.join('node_modules', 'debug', 'node.js'), 66 | path.join('node_modules', 'debug', 'debug.js'), 67 | // dbug@0.x 68 | path.join('node_modules', 'dbug', 'lib', 'dbug.js'), 69 | path.join('node_modules', 'dbug', 'index.js') 70 | ]; 71 | } 72 | 73 | function skip(name) { 74 | if (name === __filename || name === 'console.js') { 75 | return true; 76 | } 77 | return debug.some(function(d) { 78 | return endsWith(name, d); 79 | }); 80 | } 81 | 82 | for (var i = 0, len = trace.length; i < len; i++) { 83 | filename = path.normalize(trace[i].getFileName()); 84 | if (!skip(filename)) { 85 | break; 86 | } 87 | } 88 | var topName = basename || path.basename(root); 89 | topName = topName.replace(path.extname(topName), ''); 90 | 91 | var moduleName = path.join(topName, path.relative(root, filename)); 92 | moduleName = moduleName.replace(path.extname(moduleName), ''); 93 | moduleName = moduleName.replace(/[\\\/]/g, '.'); 94 | 95 | // lib is the defacto place to store js files, but removing lib looks 96 | // better: connect.lib.session -> connect.session 97 | moduleName = moduleName.replace(/\.lib\./g, '.'); 98 | 99 | // index.js filename shouldn't be used, since it's really the folder 100 | // up. better: gryphon.index -> gryphon 101 | moduleName = moduleName.replace(/\.index/g, ''); 102 | 103 | 104 | if (debugName) { 105 | // clean up duplicated parts of the name 106 | // ex: node_modules.intel.logger.intel.logger => 107 | // node_modules.intel.logger 108 | if (!endsWith(moduleName, debugName)) { 109 | moduleName += '.' + debugName; 110 | } 111 | } 112 | 113 | if (parentLogger) { 114 | moduleName = parentLogger + '.' + moduleName; 115 | } 116 | 117 | return moduleName; 118 | } 119 | 120 | function setRoot(r) { 121 | root = r; 122 | } 123 | 124 | function setIgnore(i) { 125 | if (typeof i === 'string') { 126 | i = [i]; 127 | } 128 | ignore = i; 129 | } 130 | 131 | const DEBUG_COLORED_RE = new RegExp([ 132 | '^', 133 | ' ', // starts with 2 spaces. yea really 134 | '\\u001b\\[\\d{1,2};1m', // colored debug has colors 135 | '(.+)', // logger name 136 | '\\u001b\\[\\d?0m', // color end 137 | '(.+)', // message 138 | '\n?', // debug 2.0 addes a newline 139 | '$' 140 | ].join('')); 141 | 142 | const DBUG_LEVELS = ['debug', 'info', 'warn', 'error']; 143 | 144 | 145 | function getLoggerLevel(name) { 146 | var i = DBUG_LEVELS.length; 147 | while (i--) { 148 | var level = DBUG_LEVELS[i]; 149 | if (endsWith(name, '.' + level)) { 150 | return level; 151 | } 152 | } 153 | return 'debug'; 154 | } 155 | 156 | function dbugName(name) { 157 | if (!name) { 158 | return; 159 | } 160 | if (name.indexOf('.') === -1) { 161 | return name; 162 | } 163 | var level = getLoggerLevel(name.toLowerCase()); 164 | level = new RegExp('\\.' + level + '$', 'i'); 165 | return name.replace(level, ''); 166 | } 167 | 168 | 169 | function parseDebug(args) { 170 | // O_O 171 | // Dear reader: I'm so sorry. 172 | var str = String(args[0]); 173 | 174 | // is it colored debug() ? 175 | var match = str.match(DEBUG_COLORED_RE); 176 | if (match) { 177 | var logger = chalk.stripColor(match[1]).trim(); 178 | var msg = chalk.stripColor(match[2]).trim(); 179 | args[0] = msg; // overwrite the message portion 180 | return logger.replace(/:/g, '.'); 181 | } else if (utc.has(str)) { 182 | str = str.replace(utc.get(str), '').trim(); 183 | var logger = str.split(' ').shift(); 184 | var msg = str.replace(logger, '').trim(); 185 | args[0] = msg; 186 | return logger.replace(/:/g, '.'); 187 | } 188 | } 189 | 190 | // cached loggers that hook into dbug.__log 191 | // this way, we don't format a level message, and then parse it with 192 | // regex, just to format a new message in intel 193 | // ALSO: we only get a stacktrace once, and then save the name, so 194 | // PERFORMANCE WIN! 195 | var dbugLoggers = {}; 196 | function dbugHook(name, level, args) { 197 | var logger = dbugLoggers[name]; 198 | if (!logger) { 199 | logger = dbugLoggers[name] = 200 | intel.getLogger(getLoggerName(name.replace(/:/g, '.'))); 201 | } 202 | logger[level].apply(logger, args); 203 | } 204 | 205 | var isDebugging = false; 206 | var __log = dbug.__log; 207 | function setDebug(debug) { 208 | if (debug === true) { 209 | process.env.DEBUG = dbug.env = '*'; 210 | } else if (debug === false) { 211 | process.env.DEBUG = dbug.env = ''; 212 | } else if (debug) { 213 | if (process.env.DEBUG) { 214 | process.env.DEBUG += ',' + debug; 215 | dbug.env = process.env.DEBUG; 216 | } else { 217 | process.env.DEBUG = dbug.env = '' + debug; 218 | } 219 | } 220 | isDebugging = !!debug; 221 | dbug.__log = dbugHook; 222 | } 223 | 224 | function setLoggerBaseName(bn){ 225 | basename = bn; 226 | } 227 | 228 | function deliver(method, args) { 229 | var debugged = isDebugging && parseDebug(args); 230 | var name = getLoggerName(dbugName(debugged)); 231 | var i = ignore.length; 232 | var logger = intel.getLogger(name); 233 | name = logger._name; 234 | while (i--) { 235 | if (name.indexOf(ignore[i]) === 0) { 236 | if (method === 'stderr') { 237 | ORIGINAL_STDERR.call(process.stderr, args[0]); 238 | } else { 239 | ORIGINAL_METHODS[method].apply(console, args); 240 | } 241 | return; 242 | } 243 | } 244 | if (debugged) { 245 | method = getLoggerLevel(debugged); 246 | } 247 | var level = ALIASES.indexOf(method) !== -1 ? method : 'debug'; 248 | logger[level].apply(logger, args); 249 | } 250 | 251 | 252 | function overrideConsole(options) { 253 | options = options || {}; 254 | setRoot(options.root || path.join( 255 | stack.get(options.__trace || overrideConsole)[0].getFileName(), 256 | '..' 257 | )); 258 | setIgnore(options.ignore || []); 259 | parentLogger = options.logger; 260 | 261 | setDebug(options.debug); 262 | 263 | setLoggerBaseName(options.basename); 264 | if (!ORIGINAL_METHODS.log) { 265 | copyProperties(console, ORIGINAL_METHODS, METHOD_NAMES); 266 | } 267 | 268 | if (!ORIGINAL_STDERR) { 269 | ORIGINAL_STDERR = process.stderr.write; 270 | } 271 | 272 | ALIASES.forEach(function(method) { 273 | console[method] = function alias(){ 274 | deliver(method, arguments); 275 | }; 276 | }); 277 | 278 | console.log = function log() { 279 | deliver('log', arguments); 280 | }; 281 | 282 | console.dir = function dir(obj) { 283 | deliver('dir', [util.inspect(obj)]); 284 | }; 285 | 286 | process.stderr.write = function write(str) { 287 | deliver('stderr', [str]); 288 | return true; 289 | }; 290 | } 291 | 292 | function restoreConsole() { 293 | for (var name in ORIGINAL_METHODS) { 294 | if (ORIGINAL_METHODS.hasOwnProperty(name) && ORIGINAL_METHODS[name]) { 295 | console[name] = ORIGINAL_METHODS[name]; 296 | } 297 | } 298 | copyProperties({}, ORIGINAL_METHODS, METHOD_NAMES); 299 | dbug.__log = __log; 300 | if (ORIGINAL_STDERR) { 301 | process.stderr.write = ORIGINAL_STDERR; 302 | ORIGINAL_STDERR = undefined; 303 | } 304 | } 305 | 306 | module.exports = exports = overrideConsole; 307 | exports.restore = restoreConsole; 308 | -------------------------------------------------------------------------------- /test/logger.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | const EventEmitter = require('events').EventEmitter; 6 | 7 | const assert = require('insist'); 8 | 9 | const intel = require('../'); 10 | const Logger = intel.Logger; 11 | 12 | var __counter = 1; 13 | function unique() { 14 | return "u-" + __counter++; 15 | } 16 | 17 | function spy(cb) { 18 | var args = []; 19 | var fn = function() { 20 | args.push(arguments); 21 | if (cb) { 22 | return cb.apply(this, arguments); 23 | } 24 | }; 25 | fn.getCallCount = function() { return args.length; }; 26 | fn.getLastArgs = function() { return args[args.length - 1]; }; 27 | return fn; 28 | } 29 | 30 | function aliasLog(alias, shouldCall) { 31 | return function() { 32 | var n = unique(); 33 | var a = new Logger(n); 34 | a.propagte = false; 35 | var args; 36 | a[shouldCall] = function() { 37 | args = [].slice.call(arguments); 38 | }; 39 | 40 | a[alias]('foo', 'bar'); 41 | 42 | assert.equal(args[0], 'foo'); 43 | assert.equal(args[1], 'bar'); 44 | }; 45 | } 46 | 47 | module.exports = { 48 | 'Logger': { 49 | 'constructor': { 50 | 'should return the same instance for the same name': function() { 51 | var n = unique(); 52 | var a = new Logger(n); 53 | var a2 = new Logger(n); 54 | 55 | assert.equal(a, a2); 56 | } 57 | }, 58 | 'removeHandler': function() { 59 | var n = unique(); 60 | var a = new Logger(n); 61 | a.addHandler(new intel.handlers.Null()); 62 | assert.equal(a._handlers.length, 1); 63 | a.removeHandler(a._handlers[0]); 64 | assert.equal(a._handlers.length, 0); 65 | }, 66 | 'removeAllHandlers': function() { 67 | var n = unique(); 68 | var a = new Logger(n); 69 | a.addHandler(new intel.handlers.Null()); 70 | a.addHandler(new intel.handlers.Null()); 71 | assert.equal(a._handlers.length, 2); 72 | a.removeAllHandlers(); 73 | assert.equal(a._handlers.length, 0); 74 | }, 75 | 'getEffectiveLevel': { 76 | 'should have an effective level': function() { 77 | var n = unique(); 78 | var a = new Logger(n); 79 | a.setLevel('error'); 80 | 81 | var n2 = n + '.' + unique(); 82 | var b = new Logger(n2); 83 | 84 | assert.equal(b.getEffectiveLevel(), Logger.ERROR); 85 | }, 86 | 'should change if parent changes': function() { 87 | var n = unique(); 88 | var a = new Logger(n); 89 | a.setLevel('error'); 90 | 91 | var n2 = n + '.' + unique(); 92 | var b = new Logger(n2); 93 | assert.equal(b.getEffectiveLevel(), Logger.ERROR); 94 | 95 | a.setLevel('info'); 96 | assert.equal(b.getEffectiveLevel(), Logger.INFO); 97 | } 98 | }, 99 | 'makeRecord': { 100 | 'should make a record with a simple message': function() { 101 | var n = unique(); 102 | var a = new Logger(n); 103 | var record = a.makeRecord(n, intel.DEBUG, ["foo"]); 104 | assert.equal(record.name, n); 105 | assert.equal(record.level, intel.DEBUG); 106 | assert.equal(record.levelname, 'DEBUG'); 107 | assert.equal(record.message, 'foo'); 108 | assert.equal(record.pid, process.pid); 109 | assert.equal(record.args.length, 1); 110 | }, 111 | 'should make a record without a string message': function() { 112 | var n = unique(); 113 | var a = new Logger(n); 114 | var foo = { bar: 'baz' }; 115 | var record = a.makeRecord(n, intel.DEBUG, [foo, 'quux', true]); 116 | record.timestamp; 117 | assert.equal(record.message, '{"bar":"baz"} quux true'); 118 | } 119 | }, 120 | 'log': { 121 | 'should output arguments': function() { 122 | var n = unique(); 123 | var a = new Logger(n); 124 | a.propagate = false; 125 | 126 | var spyA = spy(); 127 | a.addHandler({ handle: spyA, level: 0 }); 128 | 129 | a.info('foo', { bar: 'baz' }); 130 | 131 | assert.equal(spyA.getCallCount(), 1); 132 | assert.equal(spyA.getLastArgs()[0].message, 'foo {"bar":"baz"}'); 133 | }, 134 | 'should be usable without alias': function() { 135 | var n = unique(); 136 | var a = new Logger(n); 137 | a.propagate = false; 138 | 139 | var spyA = spy(); 140 | a.addHandler({ handle: spyA, level: 0 }); 141 | a.addHandler({ handle: spy(), level: 0 }); 142 | 143 | a.log('info', 'foo'); 144 | 145 | assert.equal(spyA.getCallCount(), 1); 146 | }, 147 | 'should filter messages': function() { 148 | var n = unique(); 149 | var a = new Logger(n); 150 | a.propagate = false; 151 | 152 | var spyA = spy(); 153 | a.addHandler({ handle: spyA, level: 0 }); 154 | 155 | a.addFilter(new intel.Filter(/^foo/g)); 156 | a.addFilter(new intel.Filter(/^foo/g)); 157 | 158 | a.debug('bar'); 159 | assert.equal(spyA.getCallCount(), 0); 160 | 161 | a.info('foobar', 'baz', null); 162 | assert.equal(spyA.getCallCount(), 1); 163 | }, 164 | 'should propagate': function() { 165 | // C should propagate to B, but B should not propagate to A, 166 | // since we set propagate to false. 167 | var n = unique(); 168 | var a = new Logger(n); 169 | var spyA = spy(); 170 | a.addHandler({ handle: spyA, level: 0 }); 171 | 172 | var n2 = n + '.' + unique(); 173 | var b = new Logger(n2); 174 | b.propagate = false; 175 | var spyB = spy(); 176 | b.addHandler({ handle: spyB, level: 0 }); 177 | 178 | var n3 = n2 + '.' + unique(); 179 | var c = new Logger(n3); 180 | var spyC = spy(); 181 | c.addHandler({ handle: spyC, level: 0 }); 182 | 183 | 184 | c.debug('one'); 185 | 186 | assert.equal(spyA.getCallCount(), 0); 187 | assert.equal(spyB.getCallCount(), 1); 188 | assert.equal(spyC.getCallCount(), 1); 189 | }, 190 | 'should format stacktraces': function() { 191 | var a = new Logger(unique()); 192 | a.propagate = false; 193 | var spyA = spy(); 194 | a.addHandler({ handle: spyA, level: 0 }); 195 | 196 | var error = new Error('foo'); 197 | a.error(error); 198 | var record = spyA.getLastArgs()[0]; 199 | assert.equal(String(record.stack), 200 | error.stack.substr(error.stack.indexOf('\n'))); 201 | var obj = JSON.parse(JSON.stringify(record.stack)); 202 | assert(obj[0].fileName); 203 | assert.equal(record.exception, true); 204 | }, 205 | 'should be with empty stack if NONE err.stack': function(){ 206 | var a = new Logger(unique()); 207 | a.propagate = false; 208 | var spyA = spy(); 209 | a.addHandler({ handle: spyA, level: 0 }); 210 | 211 | var error = new Error('foo'); 212 | delete error.stack; 213 | a.error(error); 214 | 215 | var record = spyA.getLastArgs()[0]; 216 | assert.equal(record.stack, undefined); 217 | }, 218 | 'ALL should receive all levels': function() { 219 | var a = new Logger(unique()); 220 | a.propagate = false; 221 | var spyA = spy(); 222 | a.addHandler({ handle: spyA, level: 0 }); 223 | a.setLevel(Logger.ALL); 224 | 225 | a.log(0, 'hallo'); 226 | var record = spyA.getLastArgs()[0]; 227 | assert.equal(record.message, 'hallo'); 228 | }, 229 | 'NONE should receive no levels': function() { 230 | var a = new Logger(unique()); 231 | a.propagate = false; 232 | var spyA = spy(); 233 | a.addHandler({ handle: spyA, level: 0 }); 234 | a.setLevel(Logger.NONE); 235 | 236 | a.error('poof'); 237 | a.trace('nope'); 238 | assert.equal(spyA.getCallCount(), 0); 239 | a.log(1000, 'hallo'); 240 | assert.equal(spyA.getCallCount(), 0); 241 | }, 242 | 'warning should alias warn': aliasLog('warning', 'warn'), 243 | 'o_O should alias warn': aliasLog('o_O', 'warn'), 244 | 'O_O should alias error': aliasLog('O_O', 'error') 245 | }, 246 | 247 | 'trace': { 248 | 'should include a stacktrace in message': function() { 249 | var a = new Logger(unique()); 250 | a.propagate = false; 251 | a.setLevel(Logger.TRACE); 252 | var spyA = spy(); 253 | a.addHandler({ handle: spyA, level: 0 }); 254 | 255 | a.trace('intrusion'); 256 | var record = spyA.getLastArgs()[0]; 257 | assert.equal(record.level, Logger.TRACE); 258 | assert.equal(record.message, "intrusion"); 259 | assert(record.stack); 260 | assert(!record.exception); 261 | 262 | a.trace(); 263 | var record = spyA.getLastArgs()[0]; 264 | assert.equal(record.message, "Trace"); 265 | assert(record.stack); 266 | 267 | a.trace('red %s', 'alert'); 268 | var record = spyA.getLastArgs()[0]; 269 | assert.equal(record.message, "red alert"); 270 | assert(record.stack); 271 | 272 | a.trace({ a: 'b' }); 273 | var record = spyA.getLastArgs()[0]; 274 | assert.equal(record.message, 'Trace {"a":"b"}'); 275 | assert(record.stack); 276 | }, 277 | 'should overwrite Trace.toJSON': function() { 278 | var a = new Logger(unique()); 279 | a.propagate = false; 280 | a.setLevel(Logger.TRACE); 281 | var spyA = spy(); 282 | a.addHandler({ handle: spyA, level: 0 }); 283 | 284 | a.trace('intrusion'); 285 | var record = spyA.getLastArgs()[0]; 286 | assert.equal(JSON.stringify(record.args[1]), '"[object Trace]"'); 287 | } 288 | }, 289 | 290 | 'handleExceptions': { 291 | 'should catch uncaughtErrors': function() { 292 | var logger = new Logger(unique()); 293 | var p = logger._process = new EventEmitter(); 294 | p.exit = spy(); 295 | 296 | var handlerSpy = spy(); 297 | logger.addHandler({ handle: handlerSpy, level: 0 }); 298 | logger.propagate = false; 299 | 300 | logger.handleExceptions(); 301 | logger._process = p; 302 | p.emit('uncaughtException', new Error("catch me if you can")); 303 | 304 | assert.equal(handlerSpy.getCallCount(), 1); 305 | var record = handlerSpy.getLastArgs()[0]; 306 | assert.equal(record.level, Logger.CRITICAL); 307 | assert.equal(record.message, 'Error: catch me if you can'); 308 | assert.equal(record.uncaughtException, true); 309 | 310 | assert.equal(p.exit.getCallCount(), 1); 311 | }, 312 | 'should not exit if exitOnError is false': function() { 313 | var logger = new Logger(unique()); 314 | var p = logger._process = new EventEmitter(); 315 | logger._exit = spy(); 316 | 317 | var handlerSpy = spy(); 318 | logger.addHandler({ handle: handlerSpy, level: 0 }); 319 | logger.propagate = false; 320 | 321 | logger.handleExceptions(false); 322 | p.emit('uncaughtException', new Error("catch me if you can")); 323 | 324 | assert.equal(logger._exit.getCallCount(), 0); 325 | } 326 | }, 327 | 'unhandleExceptions': { 328 | 'should remove exception listener': function() { 329 | var logger = new Logger(unique()); 330 | var p = logger._process = new EventEmitter(); 331 | 332 | var handlerSpy = spy(); 333 | logger.addHandler({ handle: handlerSpy, level: 0 }); 334 | logger.propagate = false; 335 | 336 | logger.handleExceptions(); 337 | logger.unhandleExceptions(); 338 | p.emit('uncaughtException', new Error("catch me if you can")); 339 | 340 | assert.equal(handlerSpy.getCallCount(), 0); 341 | } 342 | } 343 | } 344 | }; 345 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # intel 2 | 3 | [![Build Status](https://travis-ci.org/seanmonstar/intel.svg?branch=master)](https://travis-ci.org/seanmonstar/intel) 4 | ![Coverage Status](./coverage.png) 5 | [![NPM version](https://badge.fury.io/js/intel.svg)](http://badge.fury.io/js/intel) 6 | 7 | An abbreviation of intelligence. In this case, the acquirement of information. 8 | 9 | > I'm ganna need more intel. 10 | 11 | ## Motivation 12 | 13 | - hierarchial named loggers 14 | - powerful config 15 | - console injection works with all libraries 16 | - fast where possible 17 | 18 | ## Table of Contents 19 | 20 | - [Logging](#logging) 21 | - [Using Default Logger](#using-default-logger) 22 | - [String Interpolation](#string-interpolation) 23 | - [Setting the Log Level](#setting-the-log-level) 24 | - [Adding a Handler](#adding-a-handler) 25 | - [Getting Named Loggers](#getting-a-named-logger) 26 | - [Logging Exceptions](#logging-exceptions) 27 | - [Handlers](#handlers) 28 | - [ConsoleHandler](#consolehandler) 29 | - [StreamHandler](#streamhandler) 30 | - [FileHandler](#filehandler) 31 | - [NullHandler](#nullhandler) 32 | - [Creating a Custom Handler](#creating-a-custom-handler) 33 | - [Filters](#filters) 34 | - [Formatters](#formatters) 35 | - [LogRecord Formatting](#logrecord) 36 | - [config](#config) 37 | - [basicConfig](#basicconfig) 38 | - [Full Configuration](#full-configuration) 39 | - [console](#console) 40 | - [debug()](#console.debug) 41 | 42 | ## Logging 43 | 44 | ### Using Default Logger 45 | 46 | To get started right away, intel provides a default logger. The module itself is an instance of a `Logger`. 47 | 48 | ```js 49 | require('intel').info('Hello intel'); 50 | ``` 51 | 52 | ### String interpolation 53 | 54 | You can log messages using interpolation just as you can when using the `console.log` API: 55 | 56 | ```js 57 | require('intel').info('Situation %s!', 'NORMAL'); 58 | ``` 59 | 60 | ### Setting the Log Level 61 | 62 | Loggers have a log level that is compared against log messages. All messages that are of a lower level than the Logger are ignored. This is useful to reduce less important messages in production deployments. 63 | 64 | ```js 65 | var intel = require('intel'); 66 | intel.setLevel(intel.WARN); 67 | intel.warn('i made it!'); 68 | intel.debug('nobody loves me'); 69 | ``` 70 | 71 | All levels by order: (each level has a corresponding method on a Logger) 72 | 73 | ```js 74 | intel.TRACE // intel.trace() 75 | intel.VERBOSE // intel.verbose() 76 | intel.DEBUG // intel.debug() 77 | intel.INFO // intel.info() 78 | intel.WARN // intel.warn() 79 | intel.ERROR // intel.error() 80 | intel.CRITICAL // intel.critical() 81 | ``` 82 | 83 | Useful levels that don't have accompanying logger methods are `intel.NONE` and `intel.ALL`. 84 | 85 | ### Adding a Handler 86 | 87 | The default logger will use a [ConsoleHandler](#consolehandler) if you don't specify anything else. You can add handlers to any logger: 88 | 89 | ```js 90 | var intel = require('intel'); 91 | intel.addHandler(new intel.handlers.File('/path/to/file.log')); 92 | 93 | intel.info('going to a file!'); 94 | ``` 95 | 96 | You can remove all handlers from a particular logger with `logger.removeAllHandlers()`. 97 | 98 | ### Getting a Named Logger 99 | 100 | Using named loggers gives you a lot of power in `intel`. First, the logger name can be included in the log message, so you can more easily understand where log messages are happening inside your application. 101 | 102 | ```js 103 | var log = require('intel').getLogger('foo.bar.baz'); 104 | log.setLevel(log.INFO).warn('baz reporting in'); 105 | ``` 106 | 107 | The names are used to build an hierarchy of loggers. Child loggers can inherit their parents' handlers and log level. 108 | 109 | ```js 110 | var intel = require('intel'); 111 | var alpha = intel.getLogger('alpha'); 112 | alpha.setLevel(intel.WARN).addHandler(new intel.handlers.File('alpha.log')); 113 | 114 | var bravo = intel.getLogger('alpha.bravo'); 115 | bravo.verbose('hungry') // ignored, since alpha has level of WARN 116 | bravo.warn('enemy spotted'); // logged to alpha.log 117 | ``` 118 | 119 | The power of logger hierarchies can seen more when using [intel.config](#config). 120 | 121 | ### Logging Exceptions 122 | 123 | Any time you pass an exception (an `Error`!) to a log method, the stack 124 | will be included in the output. In most cases, it will be appended at 125 | the end of the message. If the format is `%O`, meaning JSON output, a 126 | stack property will be included. 127 | 128 | ```js 129 | intel.error('ermahgawd', new Error('boom')); 130 | ``` 131 | 132 | Loggers can also handle `uncaughtException`, passing it to its handlers, 133 | and optionally exiting afterwards. 134 | 135 | ```js 136 | var logger = intel.getLogger('medbay'); 137 | logger.handleExceptions(exitOnError); 138 | ``` 139 | 140 | Pass a boolean for `exitOnError`. Default is `true` if no value is passed. 141 | 142 | ## Handlers 143 | 144 | Loggers build a message and try to pass the message to all of it's handlers and to it's parent. Handlers determine exactly what to do with that message, whether it's sending it to console, to a file, over a socket, or nothing at all. 145 | 146 | All Handlers have a `level` and a [`Formatter`](#formatters). 147 | 148 | ```js 149 | new intel.Handler({ 150 | level: intel.WARN, // default is NOTSET 151 | formatter: new intel.Formatter(), // default formatter 152 | timeout: 5000 // default is 5seconds 153 | }); 154 | ``` 155 | 156 | Just like Loggers, if a message's level is not equal to or greater than the Handler's level, the Handler won't even be given the message. 157 | 158 | ### ConsoleHandler 159 | 160 | ```js 161 | new intel.handlers.Console(options); 162 | ``` 163 | 164 | The Console handler outputs messages to the `stdio`, just like `console.log()` would. 165 | 166 | ### StreamHandler 167 | 168 | ```js 169 | new intel.handlers.Stream(streamOrOptions); 170 | ``` 171 | 172 | The Stream handler can take any writable stream, and will write messages to the stream. The [Console](#consolehandler) handler essentially uses 2 Stream handlers internally pointed at `process.stdout` and `process.stderr`. 173 | 174 | - **stream**: Any [WritableStream](http://nodejs.org/api/stream.html#stream_class_stream_writable) 175 | - Plus options from [Handler](#handlers) 176 | 177 | As a shortcut, you can pass the `stream` directly to the constructor, and all other options will just use default values. 178 | 179 | ### FileHandler 180 | 181 | ```js 182 | new intel.handlers.File(filenameOrOptions); 183 | ``` 184 | 185 | The File handler will write messages to a file on disk. It extends the [Stream](#streamhandler) handler, by using the `WritableStream` created from the filename. 186 | 187 | - **file**: A string of a filename to write messages to. 188 | - Plus options from [Handler](#handlers) 189 | 190 | As a shortcut, you can pass the `file` String directly to the constructor, and all other options will just use default values. 191 | 192 | ### NullHandler 193 | 194 | ```js 195 | new intel.handlers.Null(); 196 | ``` 197 | 198 | The Null handler will do nothing with received messages. This can useful if there's instances where you wish to quiet certain loggers when in production (or enemy territory). 199 | 200 | ### Creating Custom Handlers 201 | 202 | Adding a new custom handler that isn't included in intel is a snap. Just make a subclass of [Handler](#handlers), and implement the `emit` method. 203 | 204 | ```js 205 | const util = require('util'); 206 | const intel = require('intel'); 207 | 208 | function CustomHandler(options) { 209 | intel.Handler.call(this, options); 210 | // whatever setup you need 211 | } 212 | // don't forget to inhert from Handler (or a subclass, like Stream) 213 | util.inherits(CustomHandler, intel.Handler); 214 | 215 | CustomHandler.prototype.emit = function customEmit(record) { 216 | // do whatever you need to with the log record 217 | // this could be storing it in a db, or sending an email, or sending an HTTP request... 218 | // if you want the message formatted: 219 | // str = this.format(record); 220 | } 221 | ``` 222 | 223 | A list of known Handlers is kept on the [wiki](https://github.com/seanmonstar/intel/wiki/Custom-Handlers). 224 | 225 | ## Filters 226 | 227 | You can already plug together handlers and loggers, with varying levels, to get powerful filtering of messages. However, sometimes you really need to filter on a specific detail on a message. You can add these filters to a [Handler](#handlers) or [Logger](#logging). 228 | 229 | ```js 230 | intel.addFilter(new intel.Filter(/^foo/g)); 231 | intel.addFilter(new intel.Filter('patrol.db')); 232 | intel.addFilter(new intel.Filter(filterFunction)); 233 | ``` 234 | 235 | Filters come in 3 forms: 236 | 237 | - **string** - pass a string to filter based on Logger name. So, `Filter('foo.bar')` will allow messages from `foo.bar`, `foo.bar.baz`, but not `foo.barstool`. 238 | - **regexp** - pass a RegExp to filter based on the text content of the log message. So, `Filter(/^foo/g)` will allow messages like `log.info('foo bar')` but not `log.info('bar baz foo')`; 239 | - **function** - pass a function that receives a [LogRecord](#logrecord) object, and returns true if the record meets the filter. 240 | 241 | ## Formatters 242 | 243 | ```js 244 | new intel.Formatter(formatOrOptions); 245 | ``` 246 | 247 | A `Formatter` is used by a [`Handler`](#handlers) to format the message before being sent out. An useful example is wanting logs that go to the [Console](#consolehandler) to be terse and easy to read, but messages sent to a [File](#filehandler) to include a lot more detail. 248 | 249 | - **format**: A format string that will be used with `printf`. Default: `%(message)s` 250 | - **datefmt**: A string to be used to format the date. Will replace instances of `%(date)s` in the `format` string. Default: `%Y-%m-%d %H:%M-%S`. See [samsonjs/strftime](https://github.com/samsonjs/strftime#supported-specifiers) for supported specifiers. 251 | - **strip**: A boolean for whether to strip [ANSI escape codes](http://en.wikipedia.org/wiki/ANSI_escape_code#Colors_and_Styles) from the `message` and `args`. Default: `false` 252 | - **colorize**: A boolean for whether to colorize the `levelname`. Disabled when `strip` is used. Default: `false` 253 | 254 | ### LogRecord 255 | 256 | The record that is created by loggers is passed to each handler, and handlers pass it to formatters to do their formatting. 257 | 258 | ```js 259 | { 260 | name: "foo.bar", 261 | level: 30, 262 | levelname: "DEBUG", 263 | timestamp: new Date(), 264 | message: "all clear", 265 | args: [], 266 | stack: undefined, // if an Error was passed, or trace() 267 | exception: false, // if an Error was passed 268 | uncaughtException: false // if passed Error was from process.on('uncaughtException') 269 | } 270 | ``` 271 | 272 | You can output the values from these properties using the [Formatter](#formatters) and a string with `%(property)s`. Some example format strings: 273 | 274 | - `%(name)s.%(levelname)s: %(message)s`: foo.bar.DEBUG: all clear 275 | - `[%(date)s] %(name)s:: %(message)s`: \[2013-09-18 11:29:32\] foo.bar:: all clear 276 | 277 | #### printf 278 | 279 | The `printf` bundled in intel does basic string interpolation. It can 280 | get named properties from an argument, or output several arguments. It 281 | can truncate, pad, or indent, and convert values. 282 | 283 | Conversion types: 284 | 285 | - `s` - String. Example: `printf('%s', new Error('foo'))` creates `Error: foo` 286 | - `d` - Number. 287 | - `j` and `O` - JSON. `JSON.stringify` the value. 288 | - `?` - Default. Will output a sane default conversion based on argument type. 289 | 290 | Conversion flags: 291 | 292 | - `:1` - Indent a JSON format with spaces based on number after colon. 293 | Example: `printf('%:2j', { a: 'b' })` would indent the `"a": "b"` by 2 294 | spaces. Ignored on other conversion types. 295 | - `3` - A number will pad the output. Example: `printf('%5s', 'abc')` 296 | returns `' abc'`. 297 | - `-3` - Pads on the right side. Example: `printf('%-5s', 'abc')` 298 | returns `'abc '`. 299 | - `.2` - Truncates to specified length. Example: `printf('%.3s', 12345)` 300 | returns `'345'`. 301 | - `.-2` - Truncates on the right side. Example: `printf('%.-3s', 12345)` 302 | returns `'123'`. 303 | 304 | ## config 305 | 306 | Once you understand the power of intel's [named loggers](#getting-a-named-logger), you'll appreciate being able to quickly configure logging in your application. 307 | 308 | ### basicConfig 309 | 310 | The basicConfig is useful if you don't wish to do any complicated configuration (no way, really?). It's a quick way to setup the root default logger in one function call. Note that if you don't setup any handlers before logging, `basicConfig` will be called to setup the default logger. 311 | 312 | ```js 313 | intel.basicConfig({ 314 | 'file': '/path/to/file.log', // file and stream are exclusive. only pass 1 315 | 'stream': stream, 316 | 'format': '%(message)s', 317 | 'level': intel.INFO 318 | }); 319 | ``` 320 | 321 | The options passed to basicConfig can include: 322 | - **file** - filename to log 323 | - **stream** - any WritableStream 324 | - **format** - a format string 325 | - **level** - the log level 326 | 327 | You cannot pass a `file` and `stream` to basicConfig. If you don't provide either, a [Console](#consolehandler) handler will be used. If you wish to specify multiple or different handlers, take a look at the more comprehensive [config](#full-configuration). 328 | 329 | ### Full Configuration 330 | 331 | ```js 332 | intel.config({ 333 | formatters: { 334 | 'simple': { 335 | 'format': '[%(levelname)s] %(message)s', 336 | 'colorize': true 337 | }, 338 | 'details': { 339 | 'format': '[%(date)s] %(name)s.%(levelname)s: %(message)s', 340 | 'strip': true 341 | } 342 | }, 343 | filters: { 344 | 'db': 'patrol.db' 345 | }, 346 | handlers: { 347 | 'terminal': { 348 | 'class': intel.handlers.Console, 349 | 'formatter': 'simple', 350 | 'level': intel.VERBOSE 351 | }, 352 | 'logfile': { 353 | 'class': intel.handlers.File, 354 | 'level': intel.WARN, 355 | 'file': '/var/log/report.log', 356 | 'formatter': 'details', 357 | 'filters': ['db'] 358 | } 359 | }, 360 | loggers: { 361 | 'patrol': { 362 | 'handlers': ['terminal'], 363 | 'level': 'INFO', 364 | 'handleExceptions': true, 365 | 'exitOnError': false, 366 | 'propagate': false 367 | }, 368 | 'patrol.db': { 369 | 'handlers': ['logfile'], 370 | 'level': intel.ERROR 371 | }, 372 | 'patrol.node_modules.express': { // huh what? see below :) 373 | 'handlers': ['logfile'], 374 | 'level': 'WARN' 375 | } 376 | } 377 | }); 378 | ``` 379 | 380 | We set up 2 handlers, one [Console](#consolehandler) with a level of `VERBOSE` and a simple format, and one [File](#filehandler) with a level of `WARN` and a detailed format. We then set up a few options on loggers. Not all loggers need to be defined here, as child loggers will inherit from their parents. So, the root logger that we'll use in this application is `patrol`. It will send all messages that are `INFO` and greater to the the terminal. We also specifically want database errors to be logged to the our log file. And, there's a logger for express? What's that all about? See the [intel.console](#console) section. 381 | 382 | Config also accepts JSON, simply put a require path in any `class` properties. 383 | 384 | ```js 385 | // logging.json 386 | { 387 | "handlers": { 388 | "foo": { 389 | "class": "intel/handlers/console" 390 | } 391 | } 392 | // ... 393 | } 394 | ``` 395 | 396 | ```js 397 | intel.config(require('./logging.json')); 398 | ``` 399 | 400 | Passing a `handlers` option to `intel.config` will remove the default ROOT console handler, unless you've previously manually assigned handlers to `intel`. 401 | 402 | ## console 403 | 404 | ```js 405 | require('intel').console(); 406 | ``` 407 | 408 | So, a problem with logging libraries is trying to get them to work with 3rd party modules. Many libraries may benefit from logging when certain things occur, but can't really pick a logging library, since that sort of choice should be up to the app developer. The only real options they have are to not log anything, or to use `console.log`. So really, they should [console.log() all the the things](http://seanmonstar.com/post/56448644049/console-log-all-the-things), and `intel` can work just fine with that. 409 | 410 | Intel has the ability to override the global `console`, such that calling any of it's methods will send it through a [Logger](#logging). This means that messages from other libraries can be sent to your log files, or through an email, or whatever. Even better, `intel` will automatically derive a name for the each module that access `console.log` (or `info`, `warn`, `dir`, `trace`, etc). In the [config](#full-configuration) example, we set up rules for `patrol.node_modules.express`. If `express` were to log things as it handled requests, they would all derive a name that was a child of our logger. So, in case it's chatty, we're only interesting in `WARN` or greater messages, and send those to a log file. 411 | 412 | It tries its darndest to best guess a name, by comparing the relative paths from the `root` and the module accessing `console`. By default, the `root` is equal to the `dirname` of the module where you call `intel.console()`. 413 | 414 | Options: 415 | 416 | - **root** - String to define root logger, defaults to calling module's filename 417 | - **ignore** - Array of strings of log names that should be ignored and use standard `console` methods. Ex: `['intel.node_modules.mocha']` 418 | - **debug** - boolean or String. `true` will set `process.env.DEBUG='*'`. Otherwise, String is used, ex: `'request,express'` 419 | 420 | ```js 421 | // file: patrol/index.js 422 | require('intel').console(); // root is '/path/to/patrol' 423 | ``` 424 | 425 | If you override the console in a file deep inside some directories, you can manually set the root as an option: 426 | 427 | ```js 428 | // file: patrol/lib/utils/log.js 429 | require('intel').console({ root: '/path/to/patrol' }); 430 | ``` 431 | ### console.debug 432 | 433 | The `debug` option for `intel.console()`, huh? Yea, so many libraries use the `debug()` library to handle their internal logging needs. It works by not outputing any logs unless you opt-in with an environment variable. In many case, it would make sense to just leave this off, to keep the noise down. However, you can use this option to turn on a libraries logging, and route it into properly named loggers. Since the `debug` module checks `process.env` at require time, you will need to use this option firstmost, before requiring anything else that may require `debug`. 434 | 435 | Example 436 | 437 | ```js 438 | // file: patrol/index.js 439 | require('intel').console({ debug: 'request,express' }); 440 | var request = require('request'); 441 | 442 | request.get('http://example.domain'); 443 | // log messages will appear from "patrol.node_modules.request" 444 | ``` 445 | --------------------------------------------------------------------------------