├── .gitignore ├── .travis.yml ├── .jshintrc ├── lib ├── wip │ ├── through-persist.js │ ├── level.js │ ├── muxer.js │ └── grunt-log.js ├── progress │ ├── pct.js │ ├── spinner.js │ └── bar.js ├── progress.js └── prolog.js ├── wip ├── problem.js ├── event_test.js ├── stream_wtf.js ├── muxer_example.js ├── log_example.js ├── mux_log_example.js ├── log3_example.js └── log2_example.js ├── test └── prolog_test.js ├── LICENSE-MIT ├── Gruntfile.js ├── package.json ├── examples ├── filter.js ├── output.js ├── levels-padding.js ├── formatting.js ├── stream.js ├── basic.js └── parent-child.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /tmp/ 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.10 4 | before_script: 5 | - npm install -g grunt-cli 6 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "latedef": true, 6 | "newcap": true, 7 | "noarg": true, 8 | "sub": true, 9 | "undef": true, 10 | "unused": true, 11 | "boss": true, 12 | "eqnull": true, 13 | "node": true, 14 | "es5": true, 15 | "proto": true 16 | } 17 | -------------------------------------------------------------------------------- /lib/wip/through-persist.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var through = require('through'); 4 | 5 | // https://github.com/dominictarr/through/issues/18 6 | module.exports = function throughPersist(write, end) { 7 | var stream = through(write); 8 | stream.end = function(data) { 9 | if (arguments.length) { write.call(this, data); } 10 | if (end) { end.call(stream); } 11 | stream.writable = stream.readable = true; // Unsure if this is necessary. 12 | return stream; 13 | }; 14 | return stream; 15 | }; 16 | -------------------------------------------------------------------------------- /wip/problem.js: -------------------------------------------------------------------------------- 1 | var es = require('event-stream'); 2 | var throughp = require('./lib/through-persist'); 3 | 4 | var myThrough = throughp(null, function() { 5 | this.queue('(DONE)\n'); 6 | }); 7 | 8 | myThrough.pipe(process.stdout); 9 | 10 | var noise1 = es.through(); 11 | noise1.pipe(myThrough); 12 | noise1.write('This is test #1.\n'); 13 | noise1.end('Done #1!\n'); 14 | 15 | var noise2 = es.through(); 16 | noise2.pipe(myThrough); 17 | noise2.write('This is test #2.\n'); 18 | noise2.end('Done #2!\n'); 19 | 20 | console.log('All done.'); -------------------------------------------------------------------------------- /lib/progress/pct.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-log 3 | * https://github.com/gruntjs/grunt-log 4 | * 5 | * Copyright (c) 2013 "Cowboy" Ben Alman 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | 'use strict'; 10 | 11 | var filter = module.exports = {}; 12 | 13 | filter.defaults = { 14 | padding: false, 15 | }; 16 | 17 | filter.update = function(cur, max, message) { 18 | var p = this.options.padding ? ' ' : ''; 19 | return parseInt(100 * cur / max, 10) + p + '%' + (message ? p + message : ''); 20 | }; 21 | 22 | filter.done = function(message) { 23 | return message; 24 | }; 25 | -------------------------------------------------------------------------------- /lib/progress/spinner.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-log 3 | * https://github.com/gruntjs/grunt-log 4 | * 5 | * Copyright (c) 2013 "Cowboy" Ben Alman 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | 'use strict'; 10 | 11 | var filter = module.exports = {}; 12 | 13 | filter.defaults = { 14 | chars: ['|', '/', '-', '\\'], 15 | }; 16 | 17 | filter.update = function(message) { 18 | if (!('counter' in this)) { this.counter = 0; } 19 | else if (++this.counter >= this.options.chars.length) { this.counter = 0; } 20 | return this.options.chars[this.counter] + (message || ''); 21 | }; 22 | 23 | filter.done = function(message) { 24 | return message; 25 | }; 26 | -------------------------------------------------------------------------------- /wip/event_test.js: -------------------------------------------------------------------------------- 1 | var EventEmitter2 = require('eventemitter2').EventEmitter2; 2 | 3 | var a = new EventEmitter2({wildcard: true}); 4 | a.onAny(function(foo, bar) { 5 | console.log(this.event, foo, bar); 6 | }); 7 | a.emit('a', 1, 2); 8 | // logs a 1 2 9 | 10 | // Is there a more elegant way of forwarding all events from one 11 | // EventEmitter2 to another than using .apply and arguments like this? 12 | 13 | var b = new EventEmitter2({wildcard: true}); 14 | // b -> a 15 | b.onAny(function() { 16 | a.emit.apply(a, [this.event].concat([].slice.call(arguments))); 17 | }); 18 | b.emit('b', 3, 4); 19 | // logs b 3 4 20 | 21 | var c = new EventEmitter2({wildcard: true}); 22 | // c -> b -> a 23 | c.onAny(function() { 24 | b.emit.apply(b, [this.event].concat([].slice.call(arguments))); 25 | }); 26 | c.emit('c', 5, 6); 27 | // logs c 5 6 28 | -------------------------------------------------------------------------------- /lib/progress/bar.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-log 3 | * https://github.com/gruntjs/grunt-log 4 | * 5 | * Copyright (c) 2013 "Cowboy" Ben Alman 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | 'use strict'; 10 | 11 | var filter = module.exports = {}; 12 | 13 | filter.defaults = { 14 | barsize: 10, 15 | barfill: '#', 16 | barempty: ' ', 17 | barends: ['[', ']'], 18 | padding: false, 19 | }; 20 | 21 | filter.update = function(cur, max, message) { 22 | var a = parseInt(this.options.barsize * cur / max, 10); 23 | var b = this.options.barsize - a; 24 | var p = this.options.padding ? ' ' : ''; 25 | return this.options.barends[0] + p + 26 | new Array(a + 1).join(this.options.barfill) + 27 | new Array(b + 1).join(this.options.barempty) + 28 | p + this.options.barends[1] + (message ? p + message : ''); 29 | }; 30 | 31 | filter.done = function(message) { 32 | return message; 33 | }; 34 | -------------------------------------------------------------------------------- /test/prolog_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var ProLog = require('../lib/prolog.js').ProLog; 4 | 5 | /* 6 | ======== A Handy Little Nodeunit Reference ======== 7 | https://github.com/caolan/nodeunit 8 | 9 | Test methods: 10 | test.expect(numAssertions) 11 | test.done() 12 | Test assertions: 13 | test.ok(value, [message]) 14 | test.equal(actual, expected, [message]) 15 | test.notEqual(actual, expected, [message]) 16 | test.deepEqual(actual, expected, [message]) 17 | test.notDeepEqual(actual, expected, [message]) 18 | test.strictEqual(actual, expected, [message]) 19 | test.notStrictEqual(actual, expected, [message]) 20 | test.throws(block, [error], [message]) 21 | test.doesNotThrow(block, [error], [message]) 22 | test.ifError(value) 23 | */ 24 | 25 | exports['awesome'] = { 26 | setUp: function(done) { 27 | // setup here 28 | done(); 29 | }, 30 | 'instance': function(test) { 31 | test.expect(1); 32 | // tests here 33 | test.ok(new ProLog() instanceof ProLog, 'Totally lazy.'); 34 | test.done(); 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 "Cowboy" Ben Alman 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(grunt) { 4 | 5 | // Project configuration. 6 | grunt.initConfig({ 7 | nodeunit: { 8 | files: ['test/**/*_test.js'], 9 | }, 10 | jshint: { 11 | options: { 12 | jshintrc: '.jshintrc' 13 | }, 14 | gruntfile: { 15 | src: 'Gruntfile.js' 16 | }, 17 | lib: { 18 | src: ['lib/**/*.js'] 19 | }, 20 | test: { 21 | src: ['*.js', '!Gruntfile.js', 'test/**/*.js'] 22 | }, 23 | }, 24 | watch: { 25 | gruntfile: { 26 | files: '<%= jshint.gruntfile.src %>', 27 | tasks: ['jshint:gruntfile'] 28 | }, 29 | lib: { 30 | files: '<%= jshint.lib.src %>', 31 | tasks: ['jshint:lib', 'nodeunit'] 32 | }, 33 | test: { 34 | files: '<%= jshint.test.src %>', 35 | tasks: ['jshint:test', 'nodeunit'] 36 | }, 37 | }, 38 | }); 39 | 40 | // These plugins provide necessary tasks. 41 | grunt.loadNpmTasks('grunt-contrib-nodeunit'); 42 | grunt.loadNpmTasks('grunt-contrib-jshint'); 43 | grunt.loadNpmTasks('grunt-contrib-watch'); 44 | 45 | // Default task. 46 | grunt.registerTask('default', ['jshint', 'nodeunit']); 47 | 48 | }; 49 | -------------------------------------------------------------------------------- /lib/wip/level.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-log 3 | * https://github.com/gruntjs/grunt-log 4 | * 5 | * Copyright (c) 2013 "Cowboy" Ben Alman 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | 'use strict'; 10 | 11 | var Progress = require('./progress').Progress; 12 | 13 | var _ = require('lodash'); 14 | 15 | function Level(level, log) { 16 | var instance = function() { 17 | return instance.__default__.apply(instance, arguments); 18 | }; 19 | instance.__proto__ = Level.prototype; 20 | instance.level = level; 21 | instance.log = log; 22 | return instance; 23 | } 24 | 25 | exports.Level = Level; 26 | 27 | Level.prototype.__proto__ = Function.prototype; 28 | 29 | // Write a full line. 30 | Level.prototype.writeln = function(message) { 31 | this.log.write(this.log.formatMessage(this.level, message) + '\n'); 32 | }; 33 | 34 | // Write a partial line. Used to log progresses. 35 | Level.prototype.write = function(message) { 36 | this.log.write(this.log.formatMessage(this.level, message)); 37 | }; 38 | 39 | // Create a new progress instance at this level. 40 | Level.prototype.progress = function(prefix, options) { 41 | options = _.extend({logger: this.write.bind(this)}, options); 42 | return new Progress(prefix, options); 43 | }; 44 | 45 | Level.prototype.__default__ = Level.prototype.writeln; 46 | -------------------------------------------------------------------------------- /wip/stream_wtf.js: -------------------------------------------------------------------------------- 1 | var es = require('event-stream'); 2 | var throughPersist = require('./lib/through-persist'); 3 | 4 | function makeThing() { 5 | var out = es.through(); 6 | 7 | var substream = es.through(); 8 | substream.pipe(out); 9 | setInterval(function() { 10 | substream.write('internal data\n'); 11 | }, 200); 12 | 13 | var buffered = ''; 14 | var lineBuffer = throughPersist( 15 | function(data) { 16 | var parts = (buffered + data).split(/\r?\n/); 17 | buffered = parts.pop(); 18 | for (var i = 0; i < parts.length; i++) { 19 | this.queue(parts[i] + '\n'); 20 | } 21 | }, 22 | function() { 23 | if (buffered) { 24 | this.queue(buffered + '\n'); 25 | buffered = ''; 26 | } 27 | } 28 | ); 29 | return es.pipeline(lineBuffer, out); 30 | } 31 | 32 | 33 | var myThing = makeThing(); 34 | myThing.pipe(process.stdout); 35 | 36 | // noise goes into thing 37 | function makeNoise(str) { 38 | myThing.write('makeNoise (' + str + ')\n'); 39 | var noise = es.through(); 40 | noise.pipe(myThing); 41 | var counter = 0; 42 | var id = setInterval(function() { 43 | counter++; 44 | noise.write(str + '[' + counter + ']' + (counter % 5 === 0 ? '\n' : ' ')); 45 | if (counter === 7) { 46 | clearInterval(id); 47 | noise.end('done'); 48 | setTimeout(makeNoise.bind(null, str), 1000); 49 | } 50 | }, 150); 51 | } 52 | makeNoise('noise'); 53 | // makeNoise('NOISE'); 54 | -------------------------------------------------------------------------------- /lib/progress.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-log 3 | * https://github.com/gruntjs/grunt-log 4 | * 5 | * Copyright (c) 2013 "Cowboy" Ben Alman 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | 'use strict'; 10 | 11 | var _ = require('lodash'); 12 | 13 | function Progress(prefix, options) { 14 | this.prefix = prefix; 15 | this.message = ''; 16 | this.lastMessage = null; 17 | this.completed = false; 18 | this.options = options = _.extend({}, this.defaults, options); 19 | if (typeof options.filter === 'string') { 20 | options.filter = require('./progress/' + options.filter); 21 | } 22 | _.defaults(this.options, options.filter.defaults); 23 | } 24 | 25 | exports.Progress = Progress; 26 | 27 | Progress.prototype.defaults = { 28 | filter: 'pct', 29 | logger: function(p) { 30 | process.stdout.write('\r' + String(p) + (p.completed ? '\n' : '')); 31 | }, 32 | }; 33 | 34 | Progress.prototype.update = function() { 35 | var message = this.options.filter.update.apply(this, arguments); 36 | if (message !== this.message) { 37 | this.message = message; 38 | this.options.logger(this); 39 | } 40 | }; 41 | 42 | Progress.prototype.done = function() { 43 | this.message = this.options.filter.done.apply(this, arguments); 44 | this.completed = true; 45 | this.options.logger(this); 46 | }; 47 | 48 | Progress.prototype.getProgress = function() { 49 | return this.prefix + (this.message || ''); 50 | }; 51 | 52 | Progress.prototype.toString = Progress.prototype.getProgress; 53 | 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "prolog", 3 | "description": "An event- and stream-aware logger for pros. Meaning, professionals.", 4 | "version": "0.1.1-alpha", 5 | "homepage": "https://github.com/cowboy/node-prolog", 6 | "author": { 7 | "name": "\"Cowboy\" Ben Alman", 8 | "url": "http://benalman.com/" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git://github.com/cowboy/node-prolog.git" 13 | }, 14 | "bugs": { 15 | "url": "https://github.com/cowboy/node-prolog/issues" 16 | }, 17 | "licenses": [ 18 | { 19 | "type": "MIT", 20 | "url": "https://github.com/cowboy/node-prolog/blob/master/LICENSE-MIT" 21 | } 22 | ], 23 | "main": "lib/prolog", 24 | "engines": { 25 | "node": ">= 0.8.0" 26 | }, 27 | "scripts": { 28 | "test": "grunt nodeunit" 29 | }, 30 | "dependencies": { 31 | "lodash": "~1.3.1", 32 | "readable-stream": "~1.0.15" 33 | }, 34 | "devDependencies": { 35 | "grunt-contrib-jshint": "~0.1.1", 36 | "grunt-contrib-nodeunit": "~0.1.2", 37 | "grunt-contrib-watch": "~0.2.0", 38 | "grunt": "~0.4.0", 39 | "split": "~0.2.6", 40 | "through": "~2.3.4", 41 | "event-stream": "~3.0.16", 42 | "async": "~0.2.9", 43 | "chalk": "~0.2.0", 44 | "moment": "~2.1.0" 45 | }, 46 | "keywords": [ 47 | "log", 48 | "logging", 49 | "pro", 50 | "professional", 51 | "event", 52 | "stream", 53 | "stdout", 54 | "stderr", 55 | "stdio", 56 | "awesome" 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /wip/muxer_example.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Muxer = require('./lib/muxer').Muxer; 4 | var Progress = require('./lib/progress').Progress; 5 | 6 | var muxer = new Muxer({formatString: '[muxer] %s'}); 7 | muxer.stream.pipe(process.stdout); 8 | 9 | muxer.write('1 This is a test.'); 10 | muxer.write('2 Testing %s: %d, %j.', 'A', 123, {a: 1}); 11 | muxer.writef('[muxer/foo] %s', 'Testing %s: %d, %j.', 'A', 123, {a: 1}); 12 | 13 | var progress = new Progress('Progress... ', { 14 | logger: muxer.write.bind(muxer, '%s') 15 | }); 16 | 17 | // var through = require('through'); 18 | // var split = require('split'); 19 | // var noise = through(); 20 | // noise.pipe(muxer.stream); 21 | // var noiseCounter = 0; 22 | // var id = setInterval(function() { 23 | // if (done) { 24 | // clearInterval(id); 25 | // noise.end(); 26 | // console.log('noise total =', noiseCounter); 27 | // } else { 28 | // noiseCounter++; 29 | // noise.write('noise[' + noiseCounter + '] ' + (noiseCounter % 5 === 0 ? '
\n' : '')); 30 | // } 31 | // }, 35); 32 | 33 | var done; 34 | var counter = 0; 35 | var max = 100; 36 | (function loopy() { 37 | counter++; 38 | progress.update(max - counter + 1, max); 39 | if (counter < max) { 40 | setTimeout(loopy, 20); 41 | } else { 42 | // console.log('[con] Testing %s: %d, %j.', 'B', 456, {a: 2}); 43 | muxer.write('Testing %s: %d, %j.', 'B', 456, {a: 2}); 44 | progress.done('OK'); 45 | muxer.write('Testing %s: %d, %j.', 'C', 789, {a: 3}); 46 | done = true; 47 | } 48 | }()); 49 | -------------------------------------------------------------------------------- /examples/filter.js: -------------------------------------------------------------------------------- 1 | var ProLog = require('../lib/prolog').ProLog; 2 | 3 | // Instantiate logger with a custom filter method. 4 | var log = new ProLog({ 5 | filter: function(data) { 6 | // Prevent logging of "info" levels. 7 | if (data.level === 'info') { 8 | return false; 9 | } 10 | // Modify the message for "error" levels. 11 | else if (data.level === 'error') { 12 | data.message = data.message.split('\n').map(function(line) { 13 | return '>>>> ' + line; 14 | }).join('\n'); 15 | } 16 | }, 17 | }); 18 | 19 | // You can log a single string. 20 | log.log('This is a test log message.'); 21 | log.info('This is a test info message.'); 22 | log.debug('This is a test debug message.'); 23 | log.warn('This is a test warn message.'); 24 | log.error(new Error('This is a test error message.').stack); 25 | 26 | // Or anything you'd send to console.log, really. 27 | log.log('Testing log %s: %d, %j.', 'A', 1, {a: 1}); 28 | log.log([['This', 'array', 'will', 'be'], ['logged', 'over'], ['multiple', 'lines.']]); 29 | log.info('Testing info %s: %d, %j.', 'A', 2, {b: 2}); 30 | log.debug('Testing debug %s: %d, %j.', 'A', 3, {c: 3}); 31 | log.warn('Testing warn %s: %d, %j.', 'A', 4, {d: 4}); 32 | log.error('Testing error %s: %d, %j.', 'A', 5, {e: 5}); 33 | 34 | // You can group messages as well. 35 | log.group(); 36 | log.log('This log message should be indented once.'); 37 | log.group(); 38 | log.info('This info message should be indented twice.'); 39 | log.debug('This debug message should be indented twice.'); 40 | log.groupEnd(); 41 | log.warn('This warn message should be indented once.'); 42 | log.groupEnd(); 43 | log.error('This error message should not be indented.'); 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # prolog [![Build Status](https://secure.travis-ci.org/cowboy/node-prolog.png?branch=master)](http://travis-ci.org/cowboy/node-prolog) 2 | 3 | An event- and stream-aware logger for pros. Meaning, professionals. 4 | 5 | ## Getting Started 6 | Install the module with: `npm install prolog` 7 | 8 | ```javascript 9 | // Create a logger. Simple. 10 | var ProLog = require('prolog').ProLog; 11 | var log = new ProLog(); 12 | log.log('This goes to stdout.'); 13 | log.error('This goes to stderr.'); 14 | log.group(); 15 | log.info('This is indented...'); 16 | log.groupEnd(); 17 | log.warn('But this is not!'); 18 | 19 | // This logger forwards all its messages to the parent "log" logger, but 20 | // adds an additional child-only "childonly" level and *removes* the "error" 21 | // level. Also, group indentation is cumulative. 22 | var childlog = new ProLog(log, { 23 | levels: { 24 | error: null, 25 | childonly: '[child] %s%s', 26 | }, 27 | }); 28 | childlog.log('This goes to the parent, then to stdout.'); 29 | childlog.childonly('This goes to the parent, then to stdout.'); 30 | childlog.error('This throws an exception, whoops!'); 31 | ``` 32 | 33 | ## Documentation 34 | 35 | _Total work-in-progress. Haven't added the stream or progress stuff yet._ 36 | 37 | See the [examples](examples) directory for code-as-documentation. 38 | 39 | ## Examples 40 | _(Coming soon)_ 41 | 42 | ## Contributing 43 | In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [Grunt](http://gruntjs.com/). 44 | 45 | ## Release History 46 | _(Nothing yet)_ 47 | 48 | ## License 49 | Copyright (c) 2013 "Cowboy" Ben Alman 50 | Licensed under the MIT license. 51 | -------------------------------------------------------------------------------- /examples/output.js: -------------------------------------------------------------------------------- 1 | var ProLog = require('../lib/prolog').ProLog; 2 | 3 | // Instantiate logger. Setting output to a function overrides the default 4 | // "output" method. In this example, all logging messages are sent to 5 | // stdout via console.log. 6 | var log = new ProLog({ 7 | output: function(data) { 8 | console.log(log.format(data)); 9 | }, 10 | }); 11 | 12 | // If the "output" option is set to false, the logger will not output. In 13 | // that case, an event handler can be manually bound using the 14 | // "log.event.onAny" method, like so: 15 | 16 | // var log = new ProLog({output: false}); 17 | // log.event.onAny(function(data) { 18 | // console.log(log.format(data)); 19 | // }); 20 | 21 | 22 | // You can log a single string. 23 | log.log('This is a test log message.'); 24 | log.info('This is a test info message.'); 25 | log.debug('This is a test debug message.'); 26 | log.warn('This is a test warn message.'); 27 | log.error(new Error('This is a test error message.').stack); 28 | 29 | // Or anything you'd send to console.log, really. 30 | log.log('Testing log %s: %d, %j.', 'A', 1, {a: 1}); 31 | log.log([['This', 'array', 'will', 'be'], ['logged', 'over'], ['multiple', 'lines.']]); 32 | log.info('Testing info %s: %d, %j.', 'A', 2, {b: 2}); 33 | log.debug('Testing debug %s: %d, %j.', 'A', 3, {c: 3}); 34 | log.warn('Testing warn %s: %d, %j.', 'A', 4, {d: 4}); 35 | log.error('Testing error %s: %d, %j.', 'A', 5, {e: 5}); 36 | 37 | // You can group messages as well. 38 | log.group(); 39 | log.log('This log message should be indented once.'); 40 | log.group(); 41 | log.info('This info message should be indented twice.'); 42 | log.debug('This debug message should be indented twice.'); 43 | log.groupEnd(); 44 | log.warn('This warn message should be indented once.'); 45 | log.groupEnd(); 46 | log.error('This error message should not be indented.'); 47 | -------------------------------------------------------------------------------- /wip/log_example.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Log = require('./lib/grunt-log').Log; 4 | var log = new Log({ 5 | // levels: ['error', 'warn', 'info'], 6 | }); 7 | 8 | // For testing, etc. 9 | log.stream.pipe(process.stdout); 10 | 11 | function start(id, delay, level, message, max, interrupt) { 12 | log[level]('Before' + id); 13 | // var chars = '•oO'; 14 | // var chars = ['[ ]','[ # ]','[ ### ]','[#####]','[ ### ]','[ # ]']; 15 | var chars = ['[ ]','[# ]','[## ]','[### ]','[ ### ]','[ ###]','[ ##]','[ #]']; 16 | // var chars = ['( )','( O )','( OOO )','(OOOOO)','( OOO )','( O )']; 17 | // var chars = [' ', ' <> ', ' <<>> ', '<<<>>>', ' <<>> ', ' <> ']; 18 | // var chars = ['[WORKING]', '[-ORKING]', '[W-RKING]', '[WO-KING]', '[WOR-ING]', '[WORK-NG]', '[WORKI-G]', '[WORKIN-]']; 19 | var progress = log[level].progress(message, {filter: 'spinner', chars: chars}); 20 | var i = 0; 21 | (function loopy() { 22 | if (i && i % interrupt === 0) { log[level]('Random interruption 1'); } 23 | i++; 24 | // var message = 'abcdefghijk'.slice(0, Math.random() * 10 + 1); 25 | // progress.update(parseInt(100 * (max - i + 1) / max) + '%'); 26 | // progress.update({value: max - i + 1, max: max, pct: true}); 27 | // progress.update(i /*max - i + 1*/, max); 28 | // progress.update({value: max - i + 1, max: max}); 29 | // progress.update({add: '.'}); 30 | progress.update(); 31 | if (i === max) { 32 | progress.done('OK' + id); 33 | // progress.done({add: 'OK' + id}); 34 | log.info('After' + id); 35 | } else { 36 | setTimeout(loopy, delay); 37 | } 38 | }()); 39 | 40 | } 41 | 42 | // start(1, 20, 'info', 'Doing something...', 333, 50); 43 | start(1, 50, 'info', 'Doing something ', 50); 44 | // start(1, 150, 'info', 'Doing something... ', 11); 45 | // start(2, 50, 'warn', 'Doing something else...', 60, 30); 46 | -------------------------------------------------------------------------------- /examples/levels-padding.js: -------------------------------------------------------------------------------- 1 | var ProLog = require('../lib/prolog').ProLog; 2 | var $ = require('chalk'); 3 | var util = require('util'); 4 | 5 | // Instantiate logger with custom options. 6 | var log = new ProLog({ 7 | // Custom formatting function (fancy padding). 8 | format: function(data) { 9 | var pad = data.indent === 0 ? '' : 10 | $.gray('├' + Array(data.indent * 2).join('─') + ' '); 11 | return data.message.split('\n').map(function(line) { 12 | return util.format(data.format, pad, line); 13 | }).join('\n'); 14 | }, 15 | // Custom logging levels and format strings. 16 | levels: { 17 | log: {priority: 1, format: 'log' + ' %s' + '%s'}, 18 | info: {priority: 2, format: $.cyan('inf') + ' %s' + $.cyan('%s')}, 19 | success: {priority: 3, format: $.green('yay') + ' %s' + $.green('%s')}, 20 | error: {priority: 4, format: $.bgRed.white('wtf') + ' %s' + $.red('%s')}, 21 | }, 22 | }); 23 | 24 | // You can log a single string. 25 | log.log('This is a test log message.'); 26 | log.info('This is a test info message.'); 27 | log.error(new Error('This is a test error message.').stack); 28 | log.success('This is a test success message.'); 29 | 30 | // Or anything you'd send to console.log, really. 31 | log.log('Testing log %s: %d, %j.', 'A', 1, {a: 1}); 32 | log.log([['This', 'array', 'will', 'be'], ['logged', 'over'], ['multiple', 'lines.']]); 33 | log.info('Testing info %s: %d, %j.', 'A', 2, {b: 2}); 34 | log.error('Testing error %s: %d, %j.', 'A', 5, {e: 5}); 35 | log.success('Testing success %s: %d, %j.', 'A', 6, {e: 6}); 36 | 37 | // You can group messages as well. 38 | log.group(); 39 | log.log('This log message should be indented once.'); 40 | log.group(); 41 | log.info('This info message should be indented twice.'); 42 | log.groupEnd(); 43 | log.error('This error message should be indented once.'); 44 | log.groupEnd(); 45 | log.success('This success message should not be indented.'); 46 | -------------------------------------------------------------------------------- /examples/formatting.js: -------------------------------------------------------------------------------- 1 | var ProLog = require('../lib/prolog').ProLog; 2 | var $ = require('chalk'); 3 | 4 | // Logging level formatting string helper function. 5 | function getFormat(options) { 6 | return options.name + ' ' + $.gray('${date}${debug}${padding}') + 7 | (options.message || $.green('${message}')); 8 | } 9 | 10 | // Formatting helper function. 11 | function stringOrFiller(str, index) { 12 | return index === 0 ? str : new Array(str.length + 1).join('='); 13 | } 14 | 15 | // Instantiate logger with custom formatters and level formatting. 16 | var log = new ProLog({ 17 | // Ripped from the source. 18 | levels: { 19 | info: {priority: 2, format: getFormat({name: $.cyan('info')})}, 20 | data: {priority: 3, format: getFormat({name: $.green('data')})}, 21 | warn: {priority: 4, format: getFormat({name: $.yellow('warn')})}, 22 | debug: {priority: 5, format: getFormat({name: $.magenta('dbug') })}, 23 | error: {priority: 6, format: getFormat({name: $.white.bgRed('ERR!'), message: $.red('${message}')})}, 24 | header: {priority: 2, format: getFormat({name: '>>>>', message: $.underline('${message}')})}, 25 | }, 26 | // Show some of the date... 27 | formatDate: function(data, index) { 28 | var s = '(' + String(new Date(data.timeStamp)).split(' ').slice(0, 5).join(' '); 29 | // ..but only if it's the first line. 30 | return stringOrFiller(s, index); 31 | }, 32 | // Show the function name, file name, and line number (which is a silly thing 33 | // to show BEFORE the message if padding is being displayed, but whatever)... 34 | formatDebug: function(data, index) { 35 | var s = data.stack[0]; 36 | var filename = require('path').basename(s.getFileName()); 37 | var s = ' ' + s.getFunctionName() + ' ' + filename + ' ' + s.getLineNumber() + ') '; 38 | // ..but only if it's the first line. 39 | return stringOrFiller(s, index); 40 | }, 41 | // Make padding look like an arrow! 42 | formatPadding: function(data, index) { 43 | return '=' + new Array(data.indent + 1).join('==') + '> '; 44 | }, 45 | }); 46 | 47 | function foo() { 48 | log.info('This info message is not indented.'); 49 | log.timeGroup('Grouped messages'); 50 | log.info('This info message should be indented once\nand split over two lines.'); 51 | log.group('A second level of grouping:'); 52 | log.data('This data message should be indented twice.'); 53 | log.group('A third level of grouping:'); 54 | } 55 | 56 | function bar() { 57 | log.error('This error message should be indented three times\nand split over two lines.'); 58 | log.warn('This warn message should be indented three times.'); 59 | log.groupEnd(); 60 | log.debug('This debug message should be indented twice\nand split over two lines.'); 61 | log.info('This info message should be indented twice.'); 62 | log.groupEnd(); 63 | log.warn('This warn message should be indented once.'); 64 | log.timeGroupEnd('Grouped messages'); 65 | log.error('This error message should not be indented.'); 66 | } 67 | 68 | foo(); 69 | bar(); 70 | -------------------------------------------------------------------------------- /lib/wip/muxer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-log 3 | * https://github.com/gruntjs/grunt-log 4 | * 5 | * Copyright (c) 2013 "Cowboy" Ben Alman 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | 'use strict'; 10 | 11 | var util = require('util'); 12 | var through = require('through'); 13 | var _ = require('lodash'); 14 | 15 | function Muxer(options) { 16 | this.options = _.defaults({}, options, { 17 | progressState: {}, 18 | }); 19 | this.progressState = this.options.progressState; 20 | 21 | this.stream = through(); 22 | 23 | // Only piped input should be split on newlines. 24 | this.buffer = ''; 25 | this.stream._write = this.stream.write; 26 | this.stream.write = function(data) { 27 | var parts = (this.buffer + data).split(/\r?\n/); 28 | this.buffer = parts.pop(); 29 | for (var i = 0; i < parts.length; i++) { 30 | this.stream._write(this.format(parts[i])); 31 | } 32 | }.bind(this); 33 | 34 | this.stream._end = this.stream.end; 35 | this.stream.end = function(data) { 36 | this.stream._write(this.format(this.buffer)); 37 | this.buffer = ''; 38 | this.stream._end(data); // does this actually do anything? 39 | }.bind(this); 40 | } 41 | 42 | exports.Muxer = Muxer; 43 | 44 | Muxer.prototype.format = function() { 45 | var parts = this.formatParts(arguments); 46 | return parts.leading + parts.message + parts.trailing; 47 | }; 48 | 49 | Muxer.prototype.formatParts = function(args) { 50 | args = _.toArray(args); 51 | var progress = args[1]; 52 | var isProgress = progress && typeof progress.getProgress === 'function'; 53 | var interrupt = this.progressState.instance && progress !== this.progressState.instance; 54 | if (interrupt && this.progressState.muxer) { 55 | this.progressState.muxer.stream._write('\n'); 56 | } 57 | var leading = ''; 58 | var trailing = ''; 59 | if (isProgress) { 60 | if (interrupt) { this.progressState.length = 0; } 61 | if (progress === this.progressState.instance) { leading += '\r'; } 62 | this.progressState.instance = progress.completed ? null : progress; 63 | this.progressState.muxer = progress.completed ? null : this; 64 | args[1] = progress.getProgress(); 65 | trailing = this.progressPadding(progress); 66 | if (progress.completed) { 67 | trailing += '\n'; 68 | } 69 | } else { 70 | this.progressState.length = 0; 71 | this.progressState.instance = null; 72 | this.progressState.muxer = null; 73 | trailing = '\n'; 74 | } 75 | return { 76 | leading: leading, 77 | message: util.format.apply(null, args), 78 | trailing: trailing, 79 | }; 80 | }; 81 | 82 | Muxer.prototype.progressPadding = function(progress) { 83 | var length = progress.getProgress().length; 84 | var delta = (this.progressState.length || 0) - length; 85 | this.progressState.length = length; 86 | return delta > 0 ? new Array(delta + 1).join(' ') : ''; 87 | }; 88 | 89 | Muxer.prototype.write = function(formatString, args) { 90 | var parts = this.formatParts(args); 91 | var message = util.format(formatString, parts.message); 92 | this.stream._write(parts.leading + message + parts.trailing); 93 | }; 94 | -------------------------------------------------------------------------------- /lib/wip/grunt-log.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-log 3 | * https://github.com/gruntjs/grunt-log 4 | * 5 | * Copyright (c) 2013 "Cowboy" Ben Alman 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | 'use strict'; 10 | 11 | var Stream = require('stream'); 12 | var Progress = require('./progress').Progress; 13 | var Level = require('./level').Level; 14 | 15 | var _ = require('lodash'); 16 | 17 | function Log(options) { 18 | this.options = options = _.extend({ 19 | levels: ['error', 'warn', 'info'], 20 | }, options); 21 | 22 | this.stream = new Stream(); 23 | // Can this go away in Node.js 0.10.0? 24 | this.stream.write = this.stream.emit.bind(this.stream, 'data'); 25 | 26 | this._levels = []; 27 | this.setLevels(this.options.levels); 28 | 29 | this.lastProgress = {maxlen: 0, instance: null}; 30 | } 31 | 32 | exports.Log = Log; 33 | exports.Progress = Progress; 34 | 35 | Log.prototype.setLevels = function(levels) { 36 | var self = this; 37 | // Remove any levels that no longer exist. 38 | self._levels.filter(function(level) { 39 | return levels.indexOf(level) === -1; 40 | }).forEach(function(level) { 41 | delete self[level]; 42 | }); 43 | // Add only new levels that don't already exist. 44 | levels.filter(function(level) { 45 | return self._levels.indexOf(level) === -1; 46 | }).forEach(function(level) { 47 | self[level] = new Level(level, self); 48 | // // Shortcut to write a full line. 49 | // self[level] = function(message) { 50 | // self[level].writeln(message); 51 | // }; 52 | // // Write a full line. 53 | // self[level].writeln = function(message) { 54 | // self.write(self.formatMessage(level, message) + '\n'); 55 | // }; 56 | // // Write a partial line. Used to log progresses. 57 | // self[level].write = function(message) { 58 | // self.write(self.formatMessage(level, message)); 59 | // }; 60 | // // Create a new progress instance at this level. 61 | // self[level].progress = function(prefix, options) { 62 | // options = _.extend({logger: self[level].write}, options); 63 | // return new Progress(prefix, options); 64 | // }; 65 | }); 66 | self._levels = levels; 67 | }; 68 | 69 | 70 | Log.prototype.formatProgressMessage = function(progress) { 71 | var str = progress.toString(); 72 | var len = str.length; 73 | if (len < this.lastProgress.maxlen) { 74 | str += new Array(this.lastProgress.maxlen - len + 1).join(' '); 75 | } 76 | this.lastProgress.maxlen = len; 77 | if (progress.completed) { 78 | str += '\n'; 79 | } 80 | return str; 81 | }; 82 | 83 | Log.prototype.formatMessage = function(level, value) { 84 | var prefix = '[' + level.toUpperCase() + '] '; 85 | var str; 86 | var interrupt = this.lastProgress.instance && value !== this.lastProgress.instance; 87 | if (value instanceof Progress) { 88 | if (interrupt) { 89 | this.lastProgress.maxlen = 0; 90 | } 91 | str = '\r' + prefix + this.formatProgressMessage(value); 92 | this.lastProgress.instance = value.completed ? null : value; 93 | } else { 94 | this.lastProgress = {maxlen: 0, instance: null}; 95 | str = prefix + value; 96 | } 97 | return (interrupt ? '\n' : '') + str; 98 | }; 99 | 100 | Log.prototype.write = function(message) { 101 | this.stream.write(message); 102 | }; 103 | -------------------------------------------------------------------------------- /examples/stream.js: -------------------------------------------------------------------------------- 1 | var stream = require('readable-stream'); 2 | var Transform = stream.Transform; 3 | 4 | function Logger() { 5 | Transform.call(this); 6 | } 7 | Logger.prototype = Object.create(Transform.prototype); 8 | Logger.prototype._transform = function(chunk, encoding, done) { 9 | done(null, chunk); 10 | }; 11 | Logger.prototype.log = function(message) { 12 | this.push(message + '\n'); 13 | }; 14 | 15 | var a = new Logger(); 16 | var b = new Logger(); 17 | b.pipe(a).pipe(process.stdout); 18 | 19 | a.log('order: 1, stream: a'); 20 | b.log('order: 2, stream: b'); 21 | a.log('order: 3, stream: a'); 22 | b.log('order: 4, stream: b'); 23 | 24 | // order: 1, stream: a 25 | // order: 3, stream: a 26 | // order: 2, stream: b 27 | // order: 4, stream: b 28 | 29 | 30 | // var util = require('util'); 31 | // var _ = require('lodash'); 32 | // var $ = require('chalk') 33 | 34 | // // Just serialize a object stream into lines of JSON 35 | // function Serializer() { 36 | // Transform.call(this, {objectMode: true}); 37 | // } 38 | // Serializer.prototype = Object.create(Transform.prototype, {constructor: {value: Serializer}}); 39 | // Serializer.prototype._transform = function(chunk, encoding, done) { 40 | // done(null, $.green(JSON.stringify(chunk)) + '\n'); 41 | // }; 42 | 43 | // // For each "log" method call, output an object. Allow indentation to be 44 | // // increased and decreased. For any logger piped into this logger, increment 45 | // // indentation cumulatively. 46 | // var counter = 0; 47 | // function Log(name, options) { 48 | // Transform.call(this, {objectMode: true}); 49 | // this.name = name; 50 | // this.indent = 0; 51 | // } 52 | // Log.prototype = Object.create(Transform.prototype, {constructor: {value: Log}}); 53 | // Log.prototype._transform = function(chunk, encoding, done) { 54 | // console.log('[%s] %s', this.name, '_transform'); 55 | // var obj = _.extend({}, chunk); 56 | // obj.indent += this.indent; 57 | // done(null, obj); 58 | // }; 59 | // Log.prototype.group = function() { 60 | // console.log('[%s] %s', this.name, 'group'); 61 | // this.indent++; 62 | // }; 63 | // Log.prototype.groupEnd = function() { 64 | // console.log('[%s] %s', this.name, 'groupEnd'); 65 | // this.indent--; 66 | // }; 67 | // Log.prototype.log = function(message) { 68 | // console.log('[%s] %s "%s"', this.name, 'log', message); 69 | // this.push({ 70 | // counter: ++counter, 71 | // logger: this.name, 72 | // message: message, 73 | // indent: this.indent, 74 | // }); 75 | // }; 76 | 77 | // // Create parent logger that will be serialized and output to stdout. 78 | // var parent = new Log('parent'); 79 | // parent.pipe(new Serializer).pipe(process.stdout); 80 | 81 | // // Create child logger that will be passed through the parent logger, 82 | // // so that indentation can (theoretically) be accumulated, then serialized 83 | // // and output to stdout. 84 | // var child = new Log('child'); 85 | // child.pipe(parent); 86 | 87 | // parent.log('indent should be 0'); 88 | // child.log('indent should stay at 0'); 89 | // parent.group(); 90 | // parent.log('indent should be 1'); 91 | // child.log('indent should go from 0→1'); 92 | // child.group(); 93 | // parent.log('indent should be 1'); 94 | // child.log('indent should go from 1→2'); 95 | // child.groupEnd(); 96 | // parent.log('indent should be 1'); 97 | // child.log('indent should go from 0→1'); 98 | // parent.groupEnd(); 99 | // child.group(); 100 | // parent.log('indent should be 0'); 101 | // child.log('indent should stay at 1'); 102 | // child.groupEnd(); 103 | -------------------------------------------------------------------------------- /wip/mux_log_example.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var colors = require('colors'); 4 | 5 | var _ = require('lodash'); 6 | 7 | var log = {}; 8 | 9 | log.Muxer = require('./lib/muxer').Muxer; 10 | log.Progress = require('./lib/progress').Progress; 11 | 12 | log.levels = {}; 13 | log.progressState = {}; 14 | 15 | log.addLevel = function(name, formatString) { 16 | var level = log[name] = function() { 17 | var args = [log.levels[name].formatString].concat([].slice.call(arguments)); 18 | level.muxer.writef.apply(level.muxer, args); 19 | }; 20 | level.muxer = new log.Muxer({progressState: log.progressState}); 21 | level.stream = level.muxer.stream; 22 | level.progress = function(prefix) { 23 | return new log.Progress(prefix, { 24 | logger: level.bind(null, '%s') 25 | }); 26 | }; 27 | log.levels[name] = { 28 | formatString: formatString, 29 | }; 30 | }; 31 | 32 | var through = require('through'); 33 | log.combine = function() { 34 | var levels = _(arguments).toArray().groupBy(function(s) { 35 | return /^!/.test(s) ? 'exclude' : 'include'; 36 | }).value(); 37 | if (!levels.include) { 38 | levels.include = Object.keys(log.levels); 39 | } 40 | if (levels.exclude) { 41 | levels.exclude = levels.exclude.map(function(s) { return s.slice(1); }); 42 | levels.include = _.difference(levels.include, levels.exclude); 43 | } 44 | var stream = through(); 45 | levels.include.forEach(function(level) { 46 | log[level].stream.pipe(stream); 47 | }); 48 | return stream; 49 | }; 50 | 51 | // add logging level methods / streams 52 | 53 | log.addLevel('log', '[LOG] %s'); 54 | log.addLevel('info', '[INFO] %s'.cyan); 55 | log.addLevel('debug', '[DEBUG] %s'.magenta); 56 | log.addLevel('warn', '[WARN] %s'.yellow); 57 | log.addLevel('error', '[ERROR] %s'.red); 58 | 59 | // set up streams 60 | 61 | log.combine('log', 'info').pipe(process.stdout); 62 | log.combine('warn', 'error').pipe(process.stderr); 63 | // log.debug.stream.pipe(process.stdout); 64 | 65 | log.combine()//'!debug') 66 | .pipe(through(function(data) { 67 | data = colors.stripColors(data); 68 | data = data.replace(/\r/g, '\n•'); 69 | this.queue(data); 70 | })) 71 | .pipe(require('fs').createWriteStream('tmp/out2.txt')); 72 | 73 | // simulation 74 | 75 | var cmds = [ 76 | log.log.bind(log, 'This is a test log message.'), 77 | log.info.bind(log, 'This is a test info message.'), 78 | log.debug.bind(log, 'This is a test debug message.'), 79 | log.warn.bind(log, 'This is a test warning message.'), 80 | log.error.bind(log, 'This is a test error message.'), 81 | log.log.bind(log, 'Testing log %s: %d, %j.', 'A', 123, {a: 1}), 82 | log.info.bind(log, 'Testing info %s: %d, %j.', 'A', 123, {a: 1}), 83 | log.debug.bind(log, 'Testing debug %s: %d, %j.', 'A', 123, {a: 1}), 84 | log.warn.bind(log, 'Testing warning %s: %d, %j.', 'A', 123, {a: 1}), 85 | log.error.bind(log, 'Testing error %s: %d, %j.', 'A', 123, {a: 1}), 86 | ]; 87 | 88 | var id1 = setInterval(function() { 89 | var cmd = cmds.shift(); 90 | if (cmd) { 91 | cmd(); 92 | } else { 93 | clearInterval(id1); 94 | } 95 | }, 200); 96 | 97 | var p = log.log.progress('Progress...'); 98 | var max = 11; 99 | var counter = 0; 100 | var id2 = setInterval(function() { 101 | p.update(++counter, max); 102 | if (counter === max) { 103 | p.done('OK'); 104 | clearInterval(id2); 105 | } 106 | }, 150); 107 | 108 | // node mux_log_example.js; echo ---; cat tmp/out2.txt 109 | // node mux_log_example.js >/dev/null; echo ---; cat tmp/out2.txt 110 | // node mux_log_example.js 2>/dev/null; echo ---; cat tmp/out2.txt 111 | -------------------------------------------------------------------------------- /wip/log3_example.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var colors = require('colors'); 4 | var _ = require('lodash'); 5 | var util = require('util'); 6 | 7 | var Log = require('./lib/log').Log; 8 | var log = new Log({ 9 | filter: function(data) { 10 | // if (data.level === 'debug') { return false; } 11 | // if (data.logger !== log) { return false; } 12 | }, 13 | format: function(data) { 14 | var pad = data.indent === 0 ? '' : 15 | ('├' + Array(data.indent * 2).join('─') + ' ').grey; 16 | var suffix = data.taskName ? ' (' + data.taskName + ')' : ''; 17 | return util.format(data.format, pad, data.message, suffix); 18 | }, 19 | levels: { 20 | task: '[tsk] %s'.green + '%s%s'.green, 21 | log: '[log] %s' + '%s%s', 22 | info: '[inf] %s'.cyan + '%s%s'.cyan, 23 | debug: '[dbg] %s'.magenta + '%s%s'.magenta, 24 | warn: '[wrn] %s'.yellow + '%s%s'.yellow, 25 | error: '[err] %s'.red + '%s%s'.red, 26 | }, 27 | }); 28 | 29 | log.event.onAny(function(data) { 30 | console.log(log.format(data)); 31 | }); 32 | 33 | var cmds = [ 34 | ];void [ 35 | // log.log.bind(log, 'This is a test log message.'), 36 | // log.info.bind(log, 'This is a test info message.'), 37 | // // log.debug.bind(log, 'This is a test debug message.'), 38 | // log.warn.bind(log, 'This is a test warning message.'), 39 | // log.error.bind(log, 'This is a test error message.'), 40 | // log.log.bind(log, 'Testing log %s: %d, %j.', 'A', 123, {a: 1}), 41 | // log.info.bind(log, 'Testing info %s: %d, %j.', 'A', 123, {a: 1}), 42 | // // log.debug.bind(log, 'Testing debug %s: %d, %j.', 'A', 123, {a: 1}), 43 | // log.warn.bind(log, 'Testing warning %s: %d, %j.', 'A', 123, {a: 1}), 44 | // log.error.bind(log, 'Testing error %s: %d, %j.', 'A', 123, {a: 1}), 45 | ]; 46 | 47 | var done; 48 | var id1 = setInterval(function() { 49 | var cmd = cmds.shift(); 50 | if (cmd) { 51 | cmd(); 52 | } else { 53 | clearInterval(id1); 54 | done = true; 55 | } 56 | }, 200); 57 | 58 | function t(name) { 59 | return { 60 | name: name, 61 | fn: function(log, done) { 62 | var s = 'Task "' + name + '" says: '; 63 | var cmds = [ 64 | log.log.bind(log, s + 'This is a test log message.'), 65 | log.group.bind(log), 66 | log.info.bind(log, s + 'This is a test info message.'), 67 | log.warn.bind(log, s + 'Testing info %s: %d, %j.', 'A', 123, {a: 1}), 68 | log.groupEnd.bind(log), 69 | log.error.bind(log, s + 'Testing error %s: %d, %j.', 'A', 123, {a: 1}), 70 | ]; 71 | log.group(); 72 | var id = setInterval(function() { 73 | var cmd = cmds.shift(); 74 | if (cmd) { 75 | cmd(); 76 | } else { 77 | clearInterval(id); 78 | log.groupEnd(); 79 | done(); 80 | } 81 | }, 100); 82 | } 83 | }; 84 | } 85 | 86 | var async = require('async'); 87 | async.eachLimit([t('aa'), t('bbbb'), t('cccccc')], 1, function(o, next) { 88 | // var s = ' (' + o.name + ')'; 89 | var taskLogger = new Log(log, { 90 | filter: function(data) { 91 | data.taskName = o.name; 92 | }, 93 | levels: { 94 | task: null, 95 | } 96 | // levels: { 97 | // log: ('[LOG] %s' + s), 98 | // info: ('[INF] %s' + s).cyan, 99 | // debug: ('[DBG] %s' + s).magenta, 100 | // warn: ('[WRN] %s' + s).yellow, 101 | // error: ('[ERR] %s' + s).red, 102 | // }, 103 | }); 104 | log.task('Task "' + o.name + '" starting.'), 105 | // log.group(); 106 | o.fn(taskLogger, function() { 107 | // log.groupEnd(); 108 | log.task('Task "' + o.name + '" done.'), 109 | next(); 110 | }); 111 | }); 112 | -------------------------------------------------------------------------------- /examples/basic.js: -------------------------------------------------------------------------------- 1 | var ProLog = require('../lib/prolog').ProLog; 2 | 3 | // Instantiate logger. The default "output" method will log "warn" and 4 | // "error" to stderr, and everything else to stdout. 5 | var log = new ProLog(); 6 | 7 | // // Plain JSON output: 8 | // var log = new ProLog({format: false}); 9 | 10 | // // Add timestamp and debugging info using default formatting: 11 | // var log = new ProLog({formatDate: true, formatDebug: true}); 12 | 13 | // // Add custom date and padding formatters, using default formatting: 14 | // var log = new ProLog({ 15 | // formatDate: function(data, index) { 16 | // return '[' + require('moment')(data.timeStamp).format('HH:mm:ss') + '] '; 17 | // }, 18 | // formatPadding: function(data, index) { 19 | // return new Array(data.indent + 1).join('> '); 20 | // }, 21 | // }); 22 | 23 | // // Completely customize output: 24 | // var log = new ProLog({output: false}); 25 | // log.on('log', function(data) { console.log('[%s] %s', data.level, data.message); }); 26 | 27 | // There's a generic log method. 28 | log.time('Generic log method'); 29 | log.log('header', 'Generic log method'); 30 | log.log('silly', 'This is a test "silly" message logged generically.'); 31 | log.log('verbose', 'This is a test "verbose" message logged generically.'); 32 | log.log('info', 'This is a test "info" message logged generically.'); 33 | log.log('data', 'This is a test "data" message logged generically.'); 34 | log.log('warn', 'This is a test "warn" message logged generically.'); 35 | log.log('debug', 'This is a test "debug" message logged generically.'); 36 | log.log('error', 'This is a test "error" message logged generically.'); 37 | log.timeEnd('Generic log method'); 38 | log.spacer(); 39 | 40 | // And per-level helper methods. 41 | log.time('Per-level helper methods'); 42 | log.group('Per-level helper methods'); 43 | log.silly('This is a test silly message.'); 44 | log.verbose('This is a test verbose message.'); 45 | log.info('This is a test info message.'); 46 | log.data('This is a test data message.'); 47 | log.error(new Error('This is a test error message (with stack trace).').stack); 48 | log.warn('This is a test warn message.'); 49 | log.debug('This is a test debug message.'); 50 | log.groupEnd(); 51 | log.timeEnd('Per-level helper methods'); 52 | log.spacer(); 53 | 54 | // You can log more complex things than just strings. 55 | log.timeGroup('More visually complex messages'); 56 | log.silly('Testing silly %s: %d, %j.', 'A', 1, {a: 1}); 57 | log.verbose('Testing verbose %s: %d, %j.', 'A', 1, {a: 1}); 58 | log.info('Testing info %s: %d, %j.', 'A', 1, {a: 1}); 59 | log.data('Testing data %s: %d, %j.', 'A', 2, {b: 2}); 60 | log.debug('Testing debug %s: %d, %j.', 'A', 3, {c: 3}); 61 | log.warn('Testing warn %s: %d, %j.', 'A', 4, {d: 4}); 62 | log.error('Testing error %s: %d, %j.', 'A', 5, {e: 5}); 63 | log.timeGroupEnd('More visually complex messages'); 64 | log.spacer(); 65 | 66 | // Objects can be logged different ways. 67 | var obj = { 68 | a: 1, 69 | b: ['x', 'y', 'z'], 70 | toString: function() { 71 | return '[object Fancypants]'; 72 | }, 73 | }; 74 | log.timeGroup('Logging objects a few ways'); 75 | log.data('Objects can be inspected:', obj); 76 | log.info('Objects can be logged as strings: %s', obj); 77 | log.verbose('Objects can be logged as JSON: %j', obj); 78 | log.silly([['Things might be'], ['logged over'], ['multiple lines.'], obj]); 79 | log.timeGroupEnd('Logging objects a few ways'); 80 | log.spacer(); 81 | 82 | // Messages can be counted. 83 | log.timeGroup('Counted messages'); 84 | log.debug.count('This is a counted message'); 85 | log.debug.count('This is another counted message'); 86 | log.info.count('This is a counted message'); 87 | log.debug.count('This is a counted message'); 88 | log.timeGroupEnd('Counted messages'); 89 | log.spacer(); 90 | 91 | // You can group messages as well. 92 | function foo() { 93 | log.timeGroup('Grouped messages'); 94 | log.info('This info message should be indented once\nand split over two lines.'); 95 | log.group('A second level of grouping:'); 96 | log.data('This data message should be indented twice.'); 97 | log.group('A third level of grouping:'); 98 | log.error('This error message should be indented three times\nand split over two lines.'); 99 | log.warn('This warn message should be indented three times.'); 100 | log.groupEnd(); 101 | log.debug('This debug message should be indented twice\nand split over two lines.'); 102 | log.info('This info message should be indented twice.'); 103 | log.groupEnd(); 104 | log.warn('This warn message should be indented once.'); 105 | log.timeGroupEnd('Grouped messages'); 106 | log.error('This error message should not be indented.'); 107 | } 108 | 109 | foo(); -------------------------------------------------------------------------------- /wip/log2_example.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var colors = require('colors'); 4 | var _ = require('lodash'); 5 | 6 | var Log = require('./lib/log').Log; 7 | var log = new Log({ 8 | log: '[LOG] %s', 9 | info: '[INFO] %s'.cyan, 10 | debug: '[DEBUG] %s'.magenta, 11 | warn: '[WARN] %s'.yellow, 12 | error: '[ERROR] %s'.red, 13 | }); 14 | 15 | log.event.onAny(console.log); 16 | 17 | var through = require('through'); 18 | 19 | log.muxer = function() { 20 | var muxers = {}; 21 | var progressState = {}; 22 | var logLevels = log.getLevels(Object.keys(log.levels), arguments); 23 | logLevels.forEach(function(name) { 24 | var muxer = muxers[name] = new log.Muxer({progressState: progressState}); 25 | log.event.on(name, function(data) { 26 | muxer.write(log.levels[data.level].formatString, data.args); // allow per-muxer formatString override 27 | }); 28 | }); 29 | 30 | var inMuxer = new log.Muxer({progressState: progressState}); 31 | function filter() { 32 | var filtered = through(); 33 | filtered.logLevels = log.getLevels(logLevels, arguments); 34 | filtered.logLevels.forEach(function(name) { 35 | muxers[name].stream.pipe(filtered); 36 | }); 37 | if (_.toArray(arguments).indexOf('_input') !== -1) { 38 | inMuxer.stream.pipe(filtered); 39 | } 40 | filtered.filter = filter; 41 | return filtered; 42 | } 43 | var muxed = filter(); 44 | // muxed.pipe(inMuxer.stream); 45 | muxed.filter = filter; 46 | // // filtered._write = filtered.write; 47 | // muxed.write = function() { 48 | // // inMuxer.stream.write.apply(inMuxer, arguments); 49 | // }; 50 | return muxed; 51 | }; 52 | 53 | log.getLevels = function(superset, subset) { 54 | subset = _.isArray(subset[0]) ? subset[0] : _.toArray(subset); 55 | var map = _.groupBy(subset, function(s) { 56 | return /^!/.test(s) ? 'exclude' : 'include'; 57 | }); 58 | var result = map.include ? _.intersection(map.include, superset) : superset; 59 | if (map.exclude) { 60 | result = _.difference(result, _.invoke(map.exclude, 'slice', 1)); 61 | } 62 | return result; 63 | }; 64 | 65 | // set up streams 66 | 67 | /* 68 | var logStdio; 69 | var debugMode = 0; 70 | if (debugMode) { 71 | logStdio = log.muxer(); 72 | logStdio.filter('debug').pipe(process.stdout); 73 | } else { 74 | logStdio = log.muxer('!debug'); 75 | } 76 | logStdio.filter('_input', 'log', 'info').pipe(process.stdout); 77 | logStdio.filter('warn', 'error').pipe(process.stderr); 78 | console.log('log.muxer levels:', logStdio.logLevels); 79 | // logStdio.pipe(process.stdout); 80 | 81 | log.muxer() 82 | .pipe(through(function(data) { 83 | data = colors.stripColors(data); 84 | data = data.replace(/\r/g, '\n'); // only do this if you want to cat the output file 85 | this.queue(data); 86 | })) 87 | .pipe(require('fs').createWriteStream('tmp/out3.txt')); 88 | */ 89 | 90 | // simulation 91 | 92 | var cmds = [ 93 | log.log.bind(log, 'This is a test log message.'), 94 | log.info.bind(log, 'This is a test info message.'), 95 | log.debug.bind(log, 'This is a test debug message.'), 96 | log.warn.bind(log, 'This is a test warning message.'), 97 | log.error.bind(log, 'This is a test error message.'), 98 | log.log.bind(log, 'Testing log %s: %d, %j.', 'A', 123, {a: 1}), 99 | log.info.bind(log, 'Testing info %s: %d, %j.', 'A', 123, {a: 1}), 100 | log.debug.bind(log, 'Testing debug %s: %d, %j.', 'A', 123, {a: 1}), 101 | log.warn.bind(log, 'Testing warning %s: %d, %j.', 'A', 123, {a: 1}), 102 | log.error.bind(log, 'Testing error %s: %d, %j.', 'A', 123, {a: 1}), 103 | ]; 104 | 105 | var done; 106 | var id1 = setInterval(function() { 107 | var cmd = cmds.shift(); 108 | if (cmd) { 109 | cmd(); 110 | } else { 111 | clearInterval(id1); 112 | done = true; 113 | } 114 | }, 200); 115 | 116 | // var p = log.log.progress('Progress...'); 117 | // var max = 33; 118 | // var counter = 0; 119 | // var id2 = setInterval(function() { 120 | // p.update(++counter, max); 121 | // if (counter === max) { 122 | // p.done('OK'); 123 | // clearInterval(id2); 124 | // } 125 | // }, 50); 126 | 127 | // var split = require('split'); 128 | // var noise = through(); 129 | // noise.pipe(logStdio); 130 | // // noise.pipe(process.stdout); 131 | // var noiseCounter = 0; 132 | // var id = setInterval(function() { 133 | // if (done) { 134 | // clearInterval(id); 135 | // noise.end(); 136 | // console.log('noise total =', noiseCounter); 137 | // } else { 138 | // noiseCounter++; 139 | // noise.write('noise[' + noiseCounter + '] ' + (noiseCounter % 5 === 0 ? '
\n' : '')); 140 | // } 141 | // }, 35); 142 | 143 | // node log2_example.js; echo ---; cat tmp/out3.txt 144 | // node log2_example.js >/dev/null; echo ---; cat tmp/out3.txt 145 | // node log2_example.js 2>/dev/null; echo ---; cat tmp/out3.txt 146 | -------------------------------------------------------------------------------- /examples/parent-child.js: -------------------------------------------------------------------------------- 1 | var ProLog = require('../lib/prolog').ProLog; 2 | var $ = require('chalk'); 3 | 4 | function makeFormat(level, messageColor) { 5 | return '<% if (date) { %>' + $.gray('${date}') + '<% } %>' + 6 | level + ' ' + 7 | '<% if (padding) { %>' + $.gray('${padding}') + '<% } %>' + 8 | $[messageColor]('${message}') + 9 | '<% if (debug) { %>' + $.gray('${debug}') + '<% } %>'; 10 | } 11 | 12 | // Instantiate logger with custom levels. 13 | var parentLog = new ProLog({ 14 | levels: { 15 | header: {priority: 0, format: makeFormat('>>>', 'underline')}, 16 | log: {priority: 1, format: makeFormat('log', 'reset')}, 17 | parentonly: {priority: 2, format: makeFormat($.green('par'), 'green')}, 18 | error: {priority: 3, format: makeFormat($.red('err'), 'red')}, 19 | }, 20 | }); 21 | 22 | // This child logger will send all logging messages to its parent. Note that 23 | // the "output" option is set to false by default for child loggers. 24 | var childLogs = []; 25 | var childLog = new ProLog(parentLog, { 26 | levels: { 27 | // Don't inherit the parent-only level. 28 | parentonly: null, 29 | // Create a child-only level. 30 | childonly: {priority: 2, format: makeFormat($.cyan('chi'), 'cyan')}, 31 | }, 32 | // Totally custom output just for the child logger. 33 | // Push un-colored formatted message onto an array. 34 | output: function(data) { 35 | childLogs.push($.stripColor(this.format(data))); 36 | }, 37 | // Don't split output across multiple lines. 38 | format: function(data) { 39 | return data.format(this.dataPlus(data)); 40 | }, 41 | // Show date and debugging info. 42 | formatDate: true, 43 | formatDebug: true, 44 | // Simplify padding. 45 | formatPadding: function(data) { 46 | return new Array(data.indent + 1).join(' '); 47 | }, 48 | }); 49 | 50 | console.log('===== Parent and Child Logs =====\n'); 51 | 52 | var examples = {}; 53 | examples.parent = function() { 54 | parentLog.group('Logging levels that don\'t exist on a logger can\'t be called.'); 55 | parentLog.log('This log message comes from the parent.'); 56 | parentLog.parentonly('This "parentonly" message comes from the parent.'); 57 | try { 58 | parentLog.childonly('This will throw an exception.'); 59 | } catch (err) { 60 | parentLog.error('Exception: %s', err.message); 61 | } 62 | parentLog.groupEnd(); 63 | } 64 | 65 | examples.child = function() { 66 | childLog.group('But will be passed-through.'); 67 | childLog.log('This log message comes from the child.'); 68 | childLog.childonly('This "childonly" message comes from the child.'); 69 | try { 70 | childLog.parentonly('This will throw an exception.'); 71 | } catch (err) { 72 | childLog.error('Exception: %s', err.message); 73 | } 74 | childLog.groupEnd(); 75 | } 76 | 77 | examples.grouping = function() { 78 | parentLog.header('Note that indentation is cumulative.'); 79 | parentLog.log('This parent log message should not be indented.'); 80 | childLog.log('This child log message should not be indented.'); 81 | parentLog.group('[1] Increase parentLog indent'); 82 | parentLog.log('This parent log message should be indented once.'); 83 | childLog.log('This child log message should be indented once.'); 84 | childLog.group('[2] Increase childLog indent'); 85 | parentLog.log('This parent log message should still be indented once.'); 86 | childLog.log('This child log message should be indented twice.'); 87 | childLog.log([['This array will be indented twice'], ['and logged over'], ['multiple lines.']]); 88 | childLog.log('Testing twice-indented child log message %s: %d, %j.', 'A', 1, {a: 1}); 89 | childLog.groupEnd(); 90 | childLog.header('[2] Decrease childLog indent'); 91 | parentLog.log('This parent log message should still be indented once.'); 92 | childLog.log('This child log message should be indented once.'); 93 | parentLog.groupEnd(); 94 | parentLog.header('[1] Decrease parentLog indent'); 95 | parentLog.log('This parent log message should not be indented.'); 96 | childLog.log('This child log message should not be indented.'); 97 | childLog.group('[3] Increase childLog indent'); 98 | parentLog.log('This parent log message should not be indented.'); 99 | childLog.log('This child log message should be indented once.'); 100 | childLog.groupEnd(); 101 | childLog.header('[3] Decrease childLog indent'); 102 | } 103 | 104 | examples.parent(); 105 | examples.child(); 106 | 107 | // Differentiate childLog messages visually when logged through parentLog. 108 | parentLog.filter = function(data) { 109 | if (data.logger !== this) { 110 | data.message = this.eachLine(data.message, $.yellow); 111 | } 112 | }; 113 | childLog.log('All childlog messages logged via parentLog should now be yellow.'); 114 | 115 | examples.grouping(); 116 | 117 | console.log('\n===== Just Child Logs =====\n'); 118 | console.log(childLogs.join('\n')); 119 | -------------------------------------------------------------------------------- /lib/prolog.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-log 3 | * https://github.com/gruntjs/grunt-log 4 | * 5 | * Copyright (c) 2013 "Cowboy" Ben Alman 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | 'use strict'; 10 | 11 | var util = require('util'); 12 | var EventEmitter = require('events').EventEmitter; 13 | 14 | var _ = require('lodash'); 15 | var $ = require('chalk'); 16 | 17 | // var Muxer = require('./muxer').Muxer; 18 | var Progress = require('./progress').Progress; 19 | 20 | function ProLog(log, options) { 21 | if (!(this instanceof ProLog)) { return new ProLog(log, options); } 22 | ProLog.super_.call(this); 23 | 24 | // this.Muxer = Muxer; 25 | this.Progress = Progress; 26 | this._indent = 0; 27 | this._timers = {}; 28 | 29 | // If an output function if specified, call it for every 'log' event. 30 | this.on('log', function(data) { // TODO: remove? 31 | if (this.output) { this.output(data); } 32 | }); 33 | 34 | var levels; 35 | if (log instanceof ProLog) { 36 | // Forward all log events to the specified logger. 37 | this.on('log', log.emit.bind(log, 'log')); 38 | // Override log levels with explicitly-specified levels. 39 | levels = _.defaults({}, options && options.levels, log._levels); 40 | // Override defaults, defaulting "output" to false. 41 | options = _.extend({}, this.defaults, {output: false}, options); 42 | } else { 43 | // Shuffle arguments. 44 | options = log; 45 | log = null; 46 | // Either use explicitly-specified levels or use defaults. 47 | levels = options && options.levels || this.defaults.levels; 48 | // Override defaults. 49 | options = _.extend({}, this.defaults, options); 50 | } 51 | // Init levels. 52 | this._levels = {}; 53 | this.initLevels(levels); 54 | 55 | // Override default methods? 56 | if (options.filter) { this.filter = options.filter; } 57 | 58 | // If a function, override the default method with it. If true, just use 59 | // the default method. If falsy, don't use it (disabling that feature). 60 | [ 61 | 'output', 62 | 'format', 63 | 'formatDate', 64 | 'formatDebug', 65 | 'formatPadding', 66 | ].forEach(function(method) { 67 | if (_.isFunction(options[method])) { this[method] = options[method]; } 68 | else if (!options[method]) { this[method] = null; } 69 | }, this); 70 | 71 | this.timeLevel = options.timeLevel; 72 | this.groupLevel = options.groupLevel; 73 | } 74 | 75 | util.inherits(ProLog, EventEmitter); 76 | exports.ProLog = ProLog; 77 | 78 | // Internal helper function for creating default logging levels. 79 | function getFormat(options) { 80 | return '<% if (date) { %>' + $.gray('${date}') + '<% } %>' + 81 | options.name + ' ' + 82 | '<% if (padding) { %>' + $.gray('${padding}') + '<% } %>' + 83 | (options.message || '${message}') + 84 | '<% if (debug) { %>' + $.gray('${debug}') + '<% } %>'; 85 | } 86 | 87 | ProLog.prototype.defaults = { 88 | levels: { 89 | silly: {priority: 0, format: getFormat({name: $.black.bgWhite('sill')})}, 90 | verbose: {priority: 1, format: getFormat({name: $.blue('verb')})}, 91 | info: {priority: 2, format: getFormat({name: $.cyan('info')})}, 92 | data: {priority: 3, format: getFormat({name: $.green('data')})}, 93 | warn: {priority: 4, format: getFormat({name: $.yellow('warn')})}, 94 | debug: {priority: 5, format: getFormat({name: $.magenta('dbug') })}, 95 | error: {priority: 6, format: getFormat({name: $.white.bgRed('ERR!'), message: $.red('${message}')})}, 96 | header: {priority: 2, format: getFormat({name: '>>>>', message: $.underline('${message}')})}, 97 | spacer: {priority: 2, format: ''}, 98 | }, 99 | timeLevel: 'header', 100 | groupLevel: 'header', 101 | output: true, 102 | format: true, 103 | formatDate: false, 104 | formatDebug: false, 105 | formatPadding: true, 106 | }; 107 | 108 | // Overriding the .emit method allows the filter method to modify the data 109 | // object or prevent the event from being emitted. Also, it allows the 110 | // data.indent property to be modified when a logging event is emitted, 111 | // which allows chained loggers to accumulate indentation. 112 | ProLog.prototype.emit = function(event, data) { 113 | var isLog = event === 'log' && data && data.level; 114 | // Don't emit if the "filter" method returns false. 115 | if (isLog && this.filter && this.filter(data) === false) { return; } 116 | // Modify the data object, adding the correct indentation. 117 | var _indent = this._indent; 118 | var indent = isLog && typeof data.indent === 'number' && _indent !== 0; 119 | if (indent) { data.indent += _indent; } 120 | var result = ProLog.super_.prototype.emit.apply(this, arguments); 121 | if (indent) { data.indent -= _indent; } 122 | return result; 123 | }; 124 | 125 | // Log each message to the console using the specified formatter, sending 126 | // "warn" and "error" to stderr, and everything else to stdout. If format 127 | // is false, output JSON. 128 | ProLog.prototype.output = function(data) { 129 | var logger = /error|warn/.test(data.level) ? console.error : console.log; 130 | logger(this.format ? this.format(data) : JSON.stringify(data)); 131 | }; 132 | 133 | // Default message formatter. 134 | ProLog.prototype.format = function(data) { 135 | // Iterate over each line in the message. 136 | return this.eachLine(data.message, function(line, index) { 137 | // Format message based on an augmented data object. 138 | var dataPlus = this.dataPlus(data, {message: line, index: index}); 139 | return data.format(dataPlus); 140 | }); 141 | }; 142 | 143 | // Convenience method for simplifying multi-line formatting. 144 | ProLog.prototype.eachLine = function(lines, formatter) { 145 | return lines.split('\n').map(formatter, this).join('\n'); 146 | }; 147 | 148 | // Augment a standard log data object with additional properties to make 149 | // logging output much more EXCITING 150 | ProLog.prototype.dataPlus = function(data, options) { 151 | options = _.defaults({}, options, {index: -1}); 152 | var dataPlus = Object.create(data); 153 | // Add a few method-based properties. 154 | var self = this; 155 | [ 156 | 'Date', 157 | 'Debug', 158 | 'Padding', 159 | ].forEach(function(name) { 160 | var method = 'format' + name; 161 | Object.defineProperty(dataPlus, name.toLowerCase(), { 162 | configurable: true, 163 | get: function() { 164 | return self[method] && self[method](dataPlus, options.index); 165 | }, 166 | }); 167 | }); 168 | // Add EVEN MORE properties. 169 | Object.keys(options).forEach(function(prop) { 170 | Object.defineProperty(dataPlus, prop, { 171 | writable: true, 172 | configurable: true, 173 | value: options[prop], 174 | }); 175 | }); 176 | return dataPlus; 177 | }; 178 | 179 | // Make the date look pretty. Return null to omit this. 180 | ProLog.prototype.formatDate = function(data) { 181 | return '[' + new Date(data.timeStamp).toISOString() + '] '; 182 | }; 183 | 184 | // A little stack info to help with debugging. Return null to omit this. 185 | // Note that data.stack is a structured stack trace (Array of CallSite 186 | // objects) per https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi 187 | ProLog.prototype.formatDebug = function(data, index) { 188 | if (index > 0) { return null; } 189 | // stack[0] should be the first non-ProLog CallSite. 190 | var s = data.stack[0]; 191 | return ' (' + s.getFunctionName() + ' ' + s.getFileName() + ':' + s.getLineNumber() + ')'; 192 | }; 193 | 194 | // The indentation is a little fancy by default. Return null to omit this. 195 | ProLog.prototype.formatPadding = function(data, index) { 196 | if (data.indent === 0) { return ''; } 197 | return new Array(data.indent).join('│ ') + (index === 0 ? '├─ ' : '│ '); 198 | }; 199 | 200 | // Optionally modify logging event "data" objects or prevent logging events 201 | // from being emitted. 202 | ProLog.prototype.filter = null; 203 | 204 | // Increase indentation. 205 | ProLog.prototype.group = function() { 206 | if (arguments.length > 0) { 207 | this.logArgs(this.groupLevel, _.toArray(arguments)); 208 | } 209 | this._indent++; 210 | return this; 211 | }; 212 | 213 | // Decrease indentation. 214 | ProLog.prototype.groupEnd = function() { 215 | if (--this._indent < 0) { this._indent = 0; } 216 | return this; 217 | }; 218 | 219 | // Store a time. 220 | ProLog.prototype.time = function(label) { 221 | this._timers[label] = process.hrtime(); 222 | return this; 223 | }; 224 | 225 | // Return a time. 226 | ProLog.prototype.timeEnd = function(label) { 227 | if (!this._timers[label]) { return null; } 228 | var diff = process.hrtime(this._timers[label]); 229 | delete this._timers[label]; 230 | var nanoseconds = diff[0] * 1e9 + diff[1]; 231 | var milliseconds = Math.floor(nanoseconds / 1e3) / 1e3; 232 | return this.log(this.timeLevel, label + ': ' + milliseconds + 'ms'); 233 | }; 234 | 235 | // I seemed to be doing this a lot. 236 | ProLog.prototype.timeGroup = function(label) { 237 | return this.group(label).time(label); 238 | }; 239 | 240 | ProLog.prototype.timeGroupEnd = function(label) { 241 | return this.groupEnd().timeEnd(label); 242 | }; 243 | 244 | // Create level-named method(s) for each specified logging level, 245 | // removing any falsy (null) levels. 246 | ProLog.prototype.initLevels = function(levels) { 247 | _.each(levels, function(options, name) { 248 | if (options) { 249 | this.addLevel(name, options); 250 | } else if (name in this._levels) { 251 | this.removeLevel(name); 252 | } 253 | }, this); 254 | }; 255 | 256 | // A generic log method. 257 | ProLog.prototype.log = function(level) { 258 | return this.logArgs(level, _.toArray(arguments).slice(1)); 259 | }; 260 | 261 | // The most generic logging method. 262 | ProLog.prototype.logArgs = function(level, args) { 263 | var data = { 264 | level: level, 265 | timeStamp: +new Date(), 266 | indent: 0, // This will get incremented as necessry when event is emitted. 267 | args: args, 268 | message: util.format.apply(util, args), 269 | priority: this._levels[level].priority, 270 | format: this._levels[level].format, 271 | }; 272 | Object.defineProperties(data, { 273 | // Create logger property as non-enumerable so that it's omitted when 274 | // outputting JSON.stringify(data). 275 | logger: { 276 | value: this, 277 | writable: true, 278 | configurable: true, 279 | }, 280 | // Create stack property as non-enumerable AND as a getter to avoid a 281 | // perf hit if not being accessed. 282 | stack: { 283 | get: function() { 284 | // Inspired by this seriously awesome little bit of code: 285 | // https://github.com/substack/node-resolve/blob/281b336/lib/caller.js 286 | // https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi 287 | var origPrepareStackTrace = Error.prepareStackTrace; 288 | Error.prepareStackTrace = function (_, stack) { return stack; }; 289 | var err = new Error(); 290 | // Filter out as many ProLog-internal function calls as possible. 291 | Error.captureStackTrace(err, ProLog.prototype.logArgs); 292 | // Filter out any remaining ProLog-internal function calls. 293 | var sliceIndex = 0; 294 | err.stack.some(function(s, i) { 295 | var filename = s.getFileName(); 296 | if (!s.isNative() && filename && filename.indexOf(__dirname) !== 0) { 297 | sliceIndex = i; 298 | return true; 299 | } 300 | }); 301 | Error.prepareStackTrace = origPrepareStackTrace; 302 | return err.stack.slice(sliceIndex); 303 | }, 304 | }, 305 | }); 306 | this.emit('log', data); 307 | return this; 308 | }; 309 | 310 | // Create level-named method(s) for the specified logging level. 311 | ProLog.prototype.addLevel = function(level, options) { 312 | var self = this; 313 | this._levels[level] = _.extend({}, options); 314 | if (_.isString(this._levels[level].format)) { 315 | this._levels[level].format = _.template(this._levels[level].format); 316 | } 317 | // Primary logging method for this level. 318 | this[level] = function() { 319 | return self.logArgs(level, _.toArray(arguments)); 320 | }; 321 | // Progress sub-method. 322 | this[level].progress = function(label) { 323 | return new this.Progress(label, { 324 | logger: self[level].bind(null, '%s') 325 | }); 326 | }; 327 | // Count sub-method. TODO: keep? make cross-level? 328 | var counts = {}; 329 | this[level].count = function(label) { 330 | counts[label] = (counts[label] || 0) + 1; 331 | self[level](label + ': %d', counts[label]); 332 | }; 333 | }; 334 | 335 | // Remove level-named method(s) for the specified logging level. 336 | ProLog.prototype.removeLevel = function(name) { 337 | delete this._levels[name]; 338 | delete this[name]; 339 | }; 340 | --------------------------------------------------------------------------------