├── .gitignore ├── .npmignore ├── LICENSE.md ├── README.md ├── bin └── cmd.js ├── index.js ├── lib ├── levels.js └── renderer.js ├── package.json └── test ├── app.js ├── baboon.png ├── demo.js ├── server.js └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | node_modules 3 | *.log 4 | .DS_Store 5 | bundle.js 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | node_modules 3 | *.log 4 | .DS_Store 5 | bundle.js 6 | test 7 | test.js 8 | demo/ 9 | .npmignore 10 | LICENSE.md -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2015 Matt DesLauriers 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 20 | OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # garnish 2 | 3 | [![stable](http://badges.github.io/stability-badges/dist/stable.svg)](http://github.com/badges/stability-badges) 4 | 5 | Prettifies [ndjson](http://ndjson.org/) or [bole](https://github.com/rvagg/bole) logs from [budo](https://github.com/mattdesl/budo), [wzrd](https://github.com/maxogden/wzrd/) and other tools. 6 | 7 | Example with [budo](https://github.com/mattdesl/budo), which uses this under the hood. 8 | 9 | 10 | 11 | ## Install 12 | 13 | ```sh 14 | npm install garnish [-g|--save-dev] 15 | ``` 16 | 17 | ## Usage 18 | 19 | ### CLI 20 | 21 | Pipe a ndjson emitter into `garnish` like so: 22 | 23 | ```sh 24 | node app.js | garnish [opts] 25 | 26 | Options: 27 | 28 | --level, -l the minimum debug level, default 'debug' 29 | --name, -n the default app name 30 | ``` 31 | 32 | Where `level` can be `debug`, `info`, `warn`, `error`. 33 | 34 | ### API 35 | 36 | #### `garnish([opt])` 37 | 38 | Returns a duplexer that parses input as ndjson, and writes a pretty-printed result. Options: 39 | 40 | - `level` (String) 41 | - the minimum log level to print (default `'debug'`) 42 | - the order is as follows: `debug`, `info`, `warn`, `error` 43 | - `name` (String) 44 | - the default name for your logger; a message's `name` field will not be printed when it matches this default name, to reduce redundant/obvious information in the logs. 45 | 46 | ## format 47 | 48 | Typically, you would use [bole](https://github.com/rvagg/bole) or [ndjson](https://www.npmjs.com/package/ndjson) to write the content to garnish. You can also write ndjson to `stdout` like so: 49 | 50 | ```js 51 | // a log message 52 | console.log({ 53 | name: 'myApp', 54 | level: 'warn', 55 | message: 'not found' 56 | }) 57 | 58 | // a typical server message 59 | console.log({ 60 | name: 'myApp', 61 | type: 'generated', 62 | level: 'info', 63 | url: '/foo.png', 64 | statusCode: 200, 65 | contentLength: 12800, // in bytes 66 | elapsed: 120 // in milliseconds 67 | }) 68 | ``` 69 | 70 | 71 | Currently garnish styles the following: 72 | 73 | - `level` 74 | - the log level e.g. `debug`, `info`, `warn`, `error` (default `debug`) - only shown if `message` is present 75 | - `name` 76 | - an optional event or application name. It's recommended to always have a name. 77 | - `message` 78 | - an event message. 79 | - `url` 80 | - a url (stripped to pathname), useful for router logging. 81 | - `statusCode` 82 | - an HTTP statusCode. Codes `>=400` are displayed in red. 83 | - `contentLength` 84 | - the response size; if a `number`, bytes are assumed 85 | - `elapsed` 86 | - time elapsed since the previous related event; if a `number`, milliseconds are assumed 87 | - `type` 88 | - the type of event logged 89 | - `colors` 90 | - an optional color mapping for custom styles 91 | 92 | You can use the `colors` field to override any of the default colors with a new [ANSI style](https://github.com/chalk/ansi-styles). 93 | 94 | For example, the following will print `elapsed` in yellow if it passes our threshold: 95 | 96 | ```js 97 | function logTime (msg) { 98 | var now = Date.now() 99 | var time = now - lastTime 100 | lastTime = now 101 | 102 | console.log({ 103 | name: 'app', 104 | message: msg, 105 | elapsed: time + ' ms', 106 | colors: { 107 | elapsed: time > 1000 ? 'yellow' : 'green' 108 | } 109 | }) 110 | } 111 | ``` 112 | 113 | ## See Also 114 | 115 | - [bistre](https://github.com/hughsk/bistre) 116 | 117 | ## License 118 | 119 | MIT, see [LICENSE.md](http://github.com/mattdesl/garnish/blob/master/LICENSE.md) for details. 120 | -------------------------------------------------------------------------------- /bin/cmd.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var stdout = require('stdout-stream') 3 | var garnish = require('../') 4 | var argv = require('minimist')(process.argv.slice(2), { 5 | alias: { 6 | level: 'l', 7 | name: 'n' 8 | } 9 | }) 10 | 11 | process.stdin.resume() 12 | process.stdin.setEncoding('utf8') 13 | process.stdin 14 | .pipe(garnish(argv)) 15 | .pipe(stdout) 16 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var split = require('split2') 2 | var eol = require('os').EOL 3 | 4 | var renderer = require('./lib/renderer') 5 | var levels = require('./lib/levels') 6 | 7 | module.exports = garnish 8 | 9 | function garnish (opt) { 10 | opt = opt || {} 11 | 12 | var loggerLevel = opt.level || 'debug' 13 | var render = renderer.create(opt.name) 14 | 15 | return split(parse) 16 | 17 | function parse (line) { 18 | try { 19 | var obj = JSON.parse(line) 20 | 21 | if (obj.name === 'http' && obj.message === 'request') return 22 | if (typeof obj.level === 'number') toBunyan(obj) 23 | 24 | // check if we need to style it 25 | if (!renderer.isStyleObject(obj)) return line + eol 26 | obj.level = obj.level || 'info' 27 | 28 | // allow user to filter to a specific level 29 | if (!levels.valid(loggerLevel, obj.level)) return 30 | 31 | // errors should be formatted differently 32 | if (typeof obj.err === 'object') return renderer.renderError(obj) + eol 33 | 34 | if (typeof obj.message === 'object') { 35 | return renderer.renderObject(obj) + eol 36 | } 37 | 38 | return render(obj) + eol 39 | } catch (e) { 40 | return line + eol 41 | } 42 | } 43 | } 44 | 45 | // mutate a bole log to bunyan log 46 | // obj -> null 47 | function toBunyan (obj) { 48 | if (obj.msg && !obj.message) { 49 | obj.message = obj.msg 50 | delete obj.msg 51 | } 52 | 53 | if (typeof obj.level === 'number') { 54 | if (obj.level === 20) obj.level = 'debug' 55 | if (obj.level === 30) obj.level = 'info' 56 | if (obj.level === 40) obj.level = 'warn' 57 | if (obj.level === 50) obj.level = 'error' 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lib/levels.js: -------------------------------------------------------------------------------- 1 | var padRight = require('pad-right') 2 | 3 | var colors = { 4 | debug: 'cyan', 5 | info: 'dim', 6 | warn: 'yellow', 7 | error: 'red' 8 | } 9 | 10 | var padLen = Object.keys(colors).reduce(function (prev, a) { 11 | return Math.max(prev, a.length) 12 | }, 0) 13 | 14 | var levels = Object.keys(colors) 15 | 16 | // whether the message level is valid for the given logger 17 | module.exports.valid = function (logLevel, msgLevel) { 18 | var levelIdx = levels.indexOf(logLevel) 19 | var msgIdx = levels.indexOf(msgLevel) 20 | if (msgIdx === -1 || levelIdx === -1) return true 21 | return msgIdx >= levelIdx 22 | } 23 | 24 | // stringify with padding 25 | module.exports.stringify = function (level) { 26 | return padRight(level, padLen, ' ') 27 | } 28 | 29 | // get a level's default color 30 | module.exports.color = function (level) { 31 | return colors[level] 32 | } 33 | 34 | module.exports.maxLength = padLen 35 | -------------------------------------------------------------------------------- /lib/renderer.js: -------------------------------------------------------------------------------- 1 | var chalk = require('chalk') 2 | var stripUrl = require('url-trim') 3 | var now = require('right-now') 4 | var levels = require('./levels') 5 | var padLeft = require('pad-left') 6 | var padRight = require('pad-right') 7 | var prettyBytes = require('prettier-bytes') 8 | var prettyMs = require('pretty-ms') 9 | 10 | var paddings = { 11 | method: 6, 12 | statusCode: 3, 13 | contentLength: 8, 14 | elapsed: 7 15 | } 16 | 17 | var leftAligns = [ 'method' ] 18 | 19 | var ansiStyles = Object.keys(chalk.styles) 20 | var keys = [ 21 | 'time', 22 | 'level', 23 | 'elapsed', 24 | 'contentLength', 25 | 'message', 26 | 'method', 27 | 'statusCode', 28 | 'url', 29 | 'type', 30 | 'name' 31 | ] 32 | 33 | var startTime = now() 34 | 35 | exports.isStyleObject = function (data) { 36 | // skip false/undefined/etc 37 | if (typeof data !== 'object' || !data) { 38 | return false 39 | } 40 | // ensure we have something worth styling 41 | return keys.some(function (key) { 42 | return data.hasOwnProperty(key) 43 | }) 44 | } 45 | 46 | exports.renderError = function (data) { 47 | var timeOff = String(Math.round((now() - startTime) / 1000) % 10000) 48 | var line = '[' + padLeft(timeOff, 4, '0') + '] ' 49 | line += chalk['magenta']('(' + data.name + ')') + '\n' 50 | line += chalk.red(data.err.stack) + '\n' 51 | return line 52 | } 53 | 54 | exports.renderObject = function (data) { 55 | var timeOff = String(Math.round((now() - startTime) / 1000) % 10000) 56 | var line = '' 57 | line += '[' + padLeft(timeOff, 4, '0') + '] ' 58 | line += levels.stringify(data.level) 59 | line += chalk['magenta']('(' + data.name + ')') + '\n' 60 | line += destructureMessage(data.message) 61 | return line 62 | } 63 | 64 | exports.create = function (defaultName) { 65 | return function render (data) { 66 | var level = data.level 67 | var name = data.name 68 | 69 | // some default colors 70 | var defaultColors = { 71 | level: levels.color(level) || 'yellow', 72 | name: 'magenta', 73 | time: 'dim', 74 | statusCode: data.statusCode >= 400 ? 'red' : 'green', 75 | contentLength: 'dim', 76 | elapsed: 'dim', 77 | url: 'bold', 78 | method: 'dim', 79 | type: 'dim' 80 | } 81 | 82 | // possible user overrides 83 | var colors = data.colors || {} 84 | 85 | if (typeof data.message === 'object') { 86 | data.message = destructureMessage(data.message) 87 | } 88 | 89 | // clean up the messages a little 90 | if (level) { 91 | data.level = levels.stringify(level) 92 | } 93 | 94 | if (name) { 95 | data.name = name === defaultName ? '' : ('(' + name + ')') 96 | } 97 | 98 | if (data.url) data.url = stripUrl(data.url) 99 | if (data.type) data.type = '(' + data.type + ')' 100 | 101 | var line = [] 102 | var timeOff = String(Math.round((now() - startTime) / 1000) % 10000) 103 | data.time = '[' + padLeft(timeOff, 4, '0') + ']' 104 | 105 | if (!data.message) { 106 | data.level = level = '' 107 | } 108 | 109 | var alignLeft = true 110 | 111 | // render each of our valid keys 112 | keys.forEach(function (key) { 113 | var value = data[key] 114 | 115 | // skip empty data 116 | if (!value && typeof value !== 'number') { 117 | return 118 | } 119 | 120 | // compact formatting 121 | if (key === 'elapsed') value = fixElapsed(value) 122 | if (key === 'contentLength') value = fixSize(value) 123 | 124 | // pad to length 125 | if (key in paddings) { 126 | var left = alignLeft || leftAligns.indexOf(key) >= 0 127 | var padFn = left ? padRight : padLeft 128 | value = padFn.call(padFn, value, paddings[key], ' ') 129 | alignLeft = false 130 | } 131 | 132 | // colorize chunk 133 | var newColor = getColor(key, colors, defaultColors) 134 | 135 | if (newColor) { 136 | value = chalk[newColor](value) 137 | } 138 | 139 | line.push(value) 140 | }) 141 | return line.join(' ') 142 | } 143 | } 144 | 145 | function fixElapsed (time) { 146 | if (typeof time === 'string' && /s$/i.test(time)) { 147 | return time 148 | } 149 | if (/infinity/i.test(time)) return time 150 | var ms = parseInt(time, 10) 151 | return ms > 9999 ? prettyMs(ms) : (ms + 'ms') 152 | } 153 | 154 | function fixSize (size) { 155 | if (typeof size === 'string' && /s$/i.test(size)) { 156 | return size 157 | } 158 | if (/infinity/i.test(size)) return size 159 | var bytes = parseInt(size, 10) 160 | return bytes > 9999 161 | ? prettyBytes(bytes) 162 | .replace(/ /, '') 163 | : (bytes + 'B') 164 | } 165 | 166 | function getColor (key, colors, defaultColors) { 167 | // try to apply user style 168 | var newColor = colors[key] 169 | 170 | // use default if style is invalid 171 | if (ansiStyles.indexOf(newColor) === -1) { 172 | newColor = null 173 | } 174 | return newColor || defaultColors[key] 175 | } 176 | 177 | // destructure a message onto an object if the message 178 | // is an object. 179 | // obj -> str 180 | function destructureMessage (msg) { 181 | const keys = Object.keys(msg) 182 | var res = '' 183 | for (var i = 0; i < keys.length; i++) { 184 | var key = keys[i] 185 | var val = msg[key] 186 | if (i !== 0) res += '\n' 187 | res += chalk.blue(' "' + key + '"') 188 | res += ': ' 189 | res += chalk.green('"' + val + '"') 190 | } 191 | return res 192 | } 193 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "garnish", 3 | "version": "5.2.0", 4 | "description": "prettifies ndjson from wzrd and similar tools", 5 | "main": "index.js", 6 | "license": "MIT", 7 | "author": { 8 | "name": "Matt DesLauriers", 9 | "email": "dave.des@gmail.com", 10 | "url": "https://github.com/mattdesl" 11 | }, 12 | "bin": { 13 | "garnish": "./bin/cmd.js" 14 | }, 15 | "dependencies": { 16 | "chalk": "^0.5.1", 17 | "minimist": "^1.1.0", 18 | "pad-left": "^2.0.0", 19 | "pad-right": "^0.2.2", 20 | "prettier-bytes": "^1.0.3", 21 | "pretty-ms": "^2.1.0", 22 | "right-now": "^1.0.0", 23 | "split2": "^0.2.1", 24 | "stdout-stream": "^1.4.0", 25 | "url-trim": "^1.0.0" 26 | }, 27 | "devDependencies": { 28 | "browserify": "^8.1.3", 29 | "standard": "^4.5.4", 30 | "strip-ansi": "^2.0.1", 31 | "tape": "^3.5.0", 32 | "wzrd": "^1.2.1" 33 | }, 34 | "scripts": { 35 | "test": "standard && node test/test.js", 36 | "wzrd": "wzrd test/demo.js | ./bin/cmd.js", 37 | "demo": "node test/app.js | ./bin/cmd.js", 38 | "start": "node test/server.js" 39 | }, 40 | "keywords": [ 41 | "prettify", 42 | "pretty", 43 | "print", 44 | "pretty-print", 45 | "ndjson", 46 | "bundle", 47 | "bundler", 48 | "browserify", 49 | "wzrd", 50 | "beefy", 51 | "wizz" 52 | ], 53 | "repository": { 54 | "type": "git", 55 | "url": "git://github.com/mattdesl/garnish.git" 56 | }, 57 | "homepage": "https://github.com/mattdesl/garnish", 58 | "bugs": { 59 | "url": "https://github.com/mattdesl/garnish/issues" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /test/app.js: -------------------------------------------------------------------------------- 1 | // to see how it looks, run one of: 2 | // npm run demo 3 | // npm run demo -- --verbose 4 | 5 | console.log('Start!') 6 | console.log(JSON.stringify({ name: 'app', url: '/foo/bar', elapsed: '32ms', type: 'bundle' })) 7 | console.log(JSON.stringify({ name: 'app', url: '/foo/bar', type: 'bundle' })) 8 | console.log(JSON.stringify({ 9 | name: 'app', url: '/foo/bar', type: 'static', 10 | statusCode: 404, contentLength: 1220 11 | })) 12 | console.log(JSON.stringify({ 13 | name: 'app:12345678', url: '/foo/bar', type: 'bundle', 14 | elapsed: '325ms', message: 'hello world', 15 | colors: { 16 | message: 'blue', 17 | elapsed: 'yellow', 18 | name: 'green', 19 | level: 'gray', 20 | type: 'blue' 21 | } 22 | })) 23 | 24 | console.log(JSON.stringify({ 25 | name: 'my-app', 26 | message: { pid: 12354, port: 1336, env: 'development' } 27 | })) 28 | 29 | console.log({}) 30 | var obj = new Buffer([]) 31 | console.log(obj) 32 | console.log({ foobar: 'blah' }) 33 | process.stdout.write(String({ foo: 'foo' }) + '\n') 34 | process.stdout.write(Buffer('somebuffer') + '\n') 35 | console.log(null) 36 | console.log(undefined) 37 | console.log(false) 38 | console.log(true) 39 | console.log('fancy %d format %s', 25, 'foo') 40 | console.log(function () {}) 41 | console.log(function Klass () {}) 42 | console.log() 43 | console.log(JSON.stringify({ name: 'foo', level: 'info' })) 44 | console.log('message') 45 | console.log(JSON.stringify({ level: 'warn', name: 'app', message: 'msg' })) 46 | console.log(JSON.stringify({ level: 'debug', name: 'app', message: 'msg' })) 47 | -------------------------------------------------------------------------------- /test/baboon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdesl/garnish/27e01cb2769cf5a7d3a1487478d8d9ae63879995/test/baboon.png -------------------------------------------------------------------------------- /test/demo.js: -------------------------------------------------------------------------------- 1 | /*globals Image*/ 2 | console.log('try reloading the page and seeing the new console output!') 3 | 4 | var img = new Image() 5 | img.src = 'test/baboon.png' 6 | -------------------------------------------------------------------------------- /test/server.js: -------------------------------------------------------------------------------- 1 | var stack = [] 2 | var garnish = require('../') 3 | 4 | log({ message: 'Server connected to', url: 'http://localhost:9966/' }) 5 | log({ message: 'LiveReload running on port 35729' }) 6 | log({ message: 'From another app', name: 'my-app' }) 7 | log({ message: 'No configuration found', level: 'warn' }) 8 | log({ message: 'Got some stats', level: 'debug' }) 9 | log({ message: 'Could not find foo.bar', level: 'error' }) 10 | log({ elapsed: 4000, contentLength: 20523, url: '/static/bundle.js', type: 'bundle' }) 11 | log({ method: 'GET', statusCode: 200, elapsed: 1299, url: '/', contentLength: 1500200, type: 'static' }) 12 | stack.push('some error') 13 | log({ method: 'GET', statusCode: 200, elapsed: 13298, url: '/static.js', contentLength: 1500200 }) 14 | log({ level: 'warn', method: 'DELETE', statusCode: 200, elapsed: 13298, url: '/bundle.js', contentLength: 1500200, type: 'generated' }) 15 | stack.push(JSON.stringify({ foo: 'bar' })) 16 | log({ method: 'POST', statusCode: 200, elapsed: 14, url: '/static.js', contentLength: 14020 }) 17 | log({ elapsed: 4000, contentLength: 20523, url: '/static/bundle.js', type: 'bundle' }) 18 | log({ elapsed: 4000, statusCode: 300, message: 'foo bar some message', level: 'warn', contentLength: 20523, url: '/static/bundle.js', type: 'bundle' }) 19 | 20 | print() 21 | 22 | function log (obj) { 23 | obj.time = new Date() 24 | if (!obj.name) obj.name = 'budo' 25 | if (!obj.level) obj.level = 'info' 26 | stack.push(JSON.stringify(obj)) 27 | } 28 | 29 | function print () { 30 | var pretty = garnish({ name: 'budo' }) 31 | pretty.pipe(process.stdout) 32 | 33 | var timer = setInterval(function () { 34 | if (stack.length > 0) { 35 | pretty.write(stack.shift() + '\n') 36 | } else { 37 | clearInterval(timer) 38 | } 39 | }, 50) 40 | } 41 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | 3 | var garnish = require('../') 4 | var through = require('through2') 5 | var strip = require('strip-ansi') 6 | 7 | // does not test colors, just ensures messages are not lost 8 | test('should handle streams', function (t) { 9 | run(null, 'null') 10 | run(0, '0') 11 | run(2, '2') 12 | run(false, 'false') 13 | 14 | // test non stylables 15 | run('some string', JSON.stringify('some string'), 'handles strings') 16 | run({ foo: 'bar' }, JSON.stringify({ foo: 'bar' }), 'handles strings') 17 | run('1', JSON.stringify('1')) 18 | 19 | ignored({ name: 'app', level: 'warn' }, 'should ignore level', { level: 'error' }) 20 | 21 | const expected = '[0000] debug foobar (app)' 22 | run({ name: 'app', message: 'foobar', level: 'debug' }, expected, 'should not ignore default debug') 23 | 24 | const expected0 = '[0000] (app)' 25 | run({ name: 'app', level: 'debug' }, expected0, 'prints with debug level') 26 | 27 | // test valid JSON 28 | // run({ name: 'foo', level: 'info' }, '[0000] ', 'shows app name and level') 29 | // run({ name: 'foo' }, 'info foo:', 'defaults to level info') 30 | // run({ message: 'bar', name: 'foo' }, 'info foo: bar', 'defaults to level info') 31 | 32 | // test url and type 33 | run({ url: '/home', type: 'static' }, '[0000] /home (static)') 34 | run({ url: '/home', type: 'static', name: 'app' }, '[0000] /home (static) (app)') 35 | 36 | // // test url and type + elapsed 37 | const expected1 = '[0000] infinity /home (static) (app)' 38 | run({ url: '/home', type: 'static', elapsed: 'infinity', name: 'app' }, expected1) 39 | 40 | const expected2 = '[0000] infinity /home (static) (app)' 41 | run({ url: '/home?blah=24#foo', type: 'static', elapsed: 'infinity', name: 'app' }, expected2, 'strips hash and query') 42 | 43 | const expected3 = '[0000] infinity http://localhost:9966/home (static) (app)' 44 | run({ url: 'http://localhost:9966/home?blah=24#foo', type: 'static', elapsed: 'infinity', name: 'app' }, expected3, 'does not strip host or port') 45 | 46 | const expected4 = '[0000] infinity http://localhost:9966/ (static) (app)' 47 | run({ url: 'http://localhost:9966/', type: 'static', elapsed: 'infinity', name: 'app' }, expected4, 'does not strip host or port') 48 | 49 | // level only appears on message 50 | // also, default name is hidden 51 | run({ 52 | name: 'myApp', 53 | message: 'hello world', 54 | level: 'info' 55 | }, '[0000] info hello world', 'test level + message', { name: 'myApp' }) 56 | 57 | // test everything 58 | run({ 59 | name: 'myApp', 60 | url: '/home', 61 | type: 'generated', 62 | statusCode: '200', 63 | contentLength: '12b', 64 | elapsed: '24ms' 65 | }, '[0000] 24ms 12B 200 /home (generated)', 'test all fields', { name: 'myApp' }) 66 | t.end() 67 | 68 | function ignored (input, msg, opt) { 69 | var val = true 70 | var stream = garnish(opt) 71 | var stdin = through() 72 | stdin 73 | .pipe(stream) 74 | .pipe(through(function (buf) { 75 | var len = strip(buf.toString()).trim().length 76 | if (len > 0) { 77 | val = false 78 | } 79 | })) 80 | stdin.write(JSON.stringify(input)) 81 | stdin.end() 82 | t.ok(val, msg || 'ignored') 83 | } 84 | 85 | function run (input, expected, msg, opt) { 86 | var stream = garnish(opt) 87 | var stdin = through() 88 | stdin 89 | .pipe(stream) 90 | .pipe(through(function (body) { 91 | t.deepEqual(strip(body.toString()).trim(), expected, msg) 92 | })) 93 | stdin.write(JSON.stringify(input)) 94 | stdin.end() 95 | } 96 | }) 97 | --------------------------------------------------------------------------------