├── .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 [](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 |
--------------------------------------------------------------------------------