diff --git a/README.md b/README.md index 59f5cfaac..2ff4fce8a 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ There are two different ways to use winston: directly via the default logger, or * [Using winston in a CLI tool](#using-winston-in-a-cli-tool) * [Extending another object with Logging](#extending-another-object-with-logging) * [Working with transports](#working-with-transports) - * [Adding Custom Transports](#adding-custom-transports) + * [Adding Custom Transports](#adding-custom-transports) * [Installation](#installation) * [Run Tests](#run-tests) @@ -645,6 +645,7 @@ The Console transport takes a few simple options: * __prettyPrint:__ Boolean flag indicating if we should `util.inspect` the meta (default false). If function is specified, its return value will be the string representing the meta. * __depth__ Numeric indicating how many times to recurse while formatting the object with `util.inspect` (only used with `prettyPrint: true`) (default null, unlimited) * __showLevel:__ Boolean flag indicating if we should prepend output with level (default true). +* __formatter:__ If function is specified, its return value will be used instead of default output. (default undefined) *Metadata:* Logged via util.inspect(meta); @@ -668,6 +669,7 @@ The File transport should really be the 'Stream' transport since it will accept * __depth__ Numeric indicating how many times to recurse while formatting the object with `util.inspect` (only used with `prettyPrint: true`) (default null, unlimited) * __logstash:__ If true, messages will be logged as JSON and formatted for logstash (default false). * __showLevel:__ Boolean flag indicating if we should prepend output with level (default true). +* __formatter:__ If function is specified, its return value will be used instead of default output. (default undefined) *Metadata:* Logged via util.inspect(meta); @@ -737,6 +739,17 @@ As of `0.3.0` the MongoDB transport has been broken out into a new module: [wins For more information about its arguments, check [winston-mongodb's README][16]. +* __level:__ Level of messages that this transport should log. +* __silent:__ Boolean flag indicating whether to suppress output. +* __db:__ The name of the database you want to log to. *[required]* +* __collection__: The name of the collection you want to store log messages in, defaults to 'log'. +* __safe:__ Boolean indicating if you want eventual consistency on your log messages, if set to true it requires an extra round trip to the server to ensure the write was committed, defaults to true. +* __host:__ The host running MongoDB, defaults to localhost. +* __port:__ The port on the host that MongoDB is running on, defaults to MongoDB's default port. + +*Metadata:* Logged as a native JSON object. +>>>>>>> 3b57730... Custom log formatter functionality were added. + ### SimpleDB Transport The [winston-simpledb][18] transport is just as easy: @@ -902,6 +915,28 @@ Adding a custom transport (say for one of the datastore on the Roadmap) is actua }; ``` +### Custom Log Format +To specify custom log format you should set formatter function for transport. Currently supported transports are: Console, File, Memory. +Options object will be passed to the format function. It's general properties are: timestamp, level, message, meta. Depending on the transport type may be additional properties. + +``` js +var logger = new (winston.Logger)({ + transports: [ + new (winston.transports.Console)({ + timestamp: function() { + return Date.now(); + }, + formatter: function(options) { + // Return string will be passed to logger. + return options.timestamp() +' '+ options.level.toUpperCase() +' '+ (undefined !== options.message ? options.message : '') + + (options.meta && Object.keys(options.meta).length ? '\n\t'+ JSON.stringify(options.meta) : '' ); + } + }) + ] +}); +logger.info('Data to log.'); +``` + ### Inspirations 1. [npm][0] 2. [log.js][4] diff --git a/lib/winston/common.js b/lib/winston/common.js index 5ce29376b..d78aa07c4 100644 --- a/lib/winston/common.js +++ b/lib/winston/common.js @@ -193,6 +193,10 @@ exports.log = function (options) { // // Remark: this should really be a call to `util.format`. // + if (typeof options.formatter == 'function') { + return String(options.formatter(exports.clone(options))); + } + output = timestamp ? timestamp + ' - ' : ''; if (showLevel) { output += options.colorize === 'all' || options.colorize === 'level' || options.colorize === true diff --git a/lib/winston/transports/console.js b/lib/winston/transports/console.js index ae662d8fe..4ed7e530b 100644 --- a/lib/winston/transports/console.js +++ b/lib/winston/transports/console.js @@ -77,7 +77,8 @@ Console.prototype.log = function (level, msg, meta, callback) { raw: this.raw, label: this.label, logstash: this.logstash, - depth: this.depth + depth: this.depth, + formatter: this.formatter }); if (level === 'error' || (level === 'debug' && !this.debugStdout) ) { diff --git a/lib/winston/transports/daily-rotate-file.js b/lib/winston/transports/daily-rotate-file.js index 59d1ce058..5c2250657 100644 --- a/lib/winston/transports/daily-rotate-file.js +++ b/lib/winston/transports/daily-rotate-file.js @@ -160,7 +160,8 @@ DailyRotateFile.prototype.log = function (level, msg, meta, callback) { label: this.label, stringify: this.stringify, showLevel: this.showLevel, - depth: this.depth + depth: this.depth, + formatter: this.formatter }) + '\n'; this._size += output.length; diff --git a/lib/winston/transports/file.js b/lib/winston/transports/file.js index ead359eca..2a8749b39 100644 --- a/lib/winston/transports/file.js +++ b/lib/winston/transports/file.js @@ -134,7 +134,8 @@ File.prototype.log = function (level, msg, meta, callback) { timestamp: this.timestamp, stringify: this.stringify, label: this.label, - depth: this.depth + depth: this.depth, + formatter: this.formatter }) + this.eol; this._size += output.length; diff --git a/lib/winston/transports/memory.js b/lib/winston/transports/memory.js index 48e267444..e387c5f37 100644 --- a/lib/winston/transports/memory.js +++ b/lib/winston/transports/memory.js @@ -69,7 +69,8 @@ Memory.prototype.log = function (level, msg, meta, callback) { prettyPrint: this.prettyPrint, raw: this.raw, label: this.label, - depth: this.depth + depth: this.depth, + formatter: this.formatter }); if (level === 'error' || level === 'debug') { @@ -85,4 +86,4 @@ Memory.prototype.log = function (level, msg, meta, callback) { Memory.prototype.clearLogs = function () { this.errorOutput = []; this.writeOutput = []; -}; \ No newline at end of file +}; diff --git a/lib/winston/transports/transport.js b/lib/winston/transports/transport.js index 36bba2e02..75bb4c63e 100644 --- a/lib/winston/transports/transport.js +++ b/lib/winston/transports/transport.js @@ -18,11 +18,12 @@ var events = require('events'), var Transport = exports.Transport = function (options) { events.EventEmitter.call(this); - options = options || {}; - this.level = options.level === undefined ? 'info' : options.level; - this.silent = options.silent || false; - this.raw = options.raw || false; - this.name = options.name || this.name; + options = options || {}; + this.level = options.level === undefined ? 'info' : options.level; + this.silent = options.silent || false; + this.raw = options.raw || false; + this.name = options.name || this.name; + this.formatter = options.formatter; this.handleExceptions = options.handleExceptions || false; this.exceptionsLevel = options.exceptionsLevel || 'error'; diff --git a/test/custom-formatter-test.js b/test/custom-formatter-test.js new file mode 100644 index 000000000..6740bf4c4 --- /dev/null +++ b/test/custom-formatter-test.js @@ -0,0 +1,73 @@ +/* + * custom-formatter-test.js: Test function as formatter option for transport `{ formatter: function () {} }` + * + * (C) 2011 Charlie Robbins, Tom Shinnick, Andrii Melnyk + * MIT LICENSE + * + */ + +var assert = require('assert'), + events = require('events'), + fs = require('fs'), + path = require('path'), + vows = require('vows'), + winston = require('../lib/winston'), + helpers = require('./helpers'); + +function assertFileFormatter (basename, options) { + var filename = path.join(__dirname, 'fixtures', 'logs', basename + '.log'); + + try { fs.unlinkSync(filename) } + catch (ex) { } + + return { + topic: function () { + options.filename = filename; + var transport = new (winston.transports.File)(options); + + // We must wait until transport file has emitted the 'flush' + // event to be sure the file has been created and written + transport.once('flush', this.callback.bind(this, null, filename)); + transport.log('info', 'What does the fox say?', null, function () {}); + }, + "should log with the appropriate format": function (_, filename) { + var data = fs.readFileSync(filename, 'utf8'); + assert.isNotNull(data.match(options.pattern)); + } + } +} + +vows.describe('winston/transport/formatter').addBatch({ + "Without formatter": { + "with file transport": assertFileFormatter('customFormatterNotSetForFile', { + pattern: /info\:/, + json: false, + formatter: false + }) + }, + "When formatter option is used": { + "with file transport": { + "with value set to false": assertFileFormatter('customFormatterFalseValue', { + pattern: /info\:/, + json: false, + formatter: false + }), + "with value set to object ": assertFileFormatter('customFormatterObjectValue', { + pattern: /info\:/, + json: false, + formatter: {} + }), + "and function value with custom format": assertFileFormatter('customFormatter', { + pattern: /^\d{13,} INFO What does the fox say\?/, + json: false, + timestamp: function() { + return Date.now(); + }, + formatter: function(params) { + return params.timestamp() +' '+ params.level.toUpperCase() +' '+ (undefined !== params.message ? params.message : '') + + ( params.meta && Object.keys(params.meta).length ? '\n'+ JSON.stringify(params.meta) : '' ); + } + }) + } + } +}).export(module);