# node-logfmt [![Build Status](https://travis-ci.org/csquared/node-logfmt.png)](https://travis-ci.org/csquared/node-logfmt) "logfmt" is the name for a [key value logging convention](https://github.com/kr/logfmt) we've adopted at Heroku. This library is for both converting lines in logfmt format to objects and for logging objects to a stream in logfmt format. It provides a logfmt parser, logfmt stringifier, a logging facility, and both streaming and non-streaming body parsers for express and restify. You should use this library if you're trying to write structured logs or if you're consuming them (especially if you're writing a logplex drain). ## install npm install logfmt # use ## node.js The `logfmt` module is a singleton that works directly from require. ```javascript var logfmt = require('logfmt'); logfmt.stringify({foo: 'bar'}); // 'foo=bar' logfmt.parse('foo=bar'); // {foo: 'bar'} ``` It is also a constructor function, so you can use `new logfmt` to create a new `logfmt` that you can configure differently. ```javascript var logfmt2 = new logfmt; // replace our stringify with JSON's logfmt2.stringify = JSON.stringify // now we log JSON! logfmt2.log({foo: 'bar'}) // {"foo":"bar"} // and the original logfmt is untouched logfmt.log({foo: 'bar'}) // foo=bar ``` ## command line ### logfmt accepts lines on STDIN and converts them to json > echo "foo=bar a=14 baz=\"hello kitty\" cool%story=bro f %^asdf" | logfmt { "foo": "bar", "a": 14, "baz": "hello kitty", "cool%story": "bro", "f": true, "%^asdf": true } ### logfmt -r (reverse) accepts JSON on STDIN and converts them to logfmt > echo '{ "foo": "bar", "a": 14, "baz": "hello kitty", \ "cool%story": "bro", "f": true, "%^asdf": true }' | logfmt -r foo=bar a=14 baz="hello kitty" cool%story=bro f=true %^asdf=true round trips for free! > echo "foo=bar a=14 baz=\"hello kitty\" cool%story=bro f %^asdf" | logfmt | logfmt -r | logfmt { "foo": "bar", "a": 14, "baz": "hello kitty", "cool%story": "bro", "f": true, "%^asdf": true } # API ## stringifying Serialize an object to logfmt format ### `logfmt.stringify(object)` Serializes a single object. ```javascript logfmt.stringify({foo: "bar", a: 14, baz: 'hello kitty'}) //> 'foo=bar a=14 baz="hello kitty"' ``` ## parsing Parse a line in logfmt format ### `logfmt.parse(string)` ```javascript logfmt.parse("foo=bar a=14 baz=\"hello kitty\" cool%story=bro f %^asdf code=H12") //> { "foo": "bar", "a": '14', "baz": "hello kitty", "cool%story": "bro", "f": true, "%^asdf": true, "code" : "H12" } ``` The only conversions are from the strings `true` and `false` to their proper boolean counterparts. We cannot arbitrarily convert numbers because that will drop precision for numbers that require more than 32 bits to represent them. ## Streaming Put this in your pipe and smoke it. ### `logfmt.streamParser()` Creates a streaming parser that will automatically split and parse incoming lines and emit javascript objects. Stream in from STDIN ```javascript process.stdin.pipe(logfmt.streamParser()) ``` Or pipe from an HTTP request ```javascript req.pipe(logfmt.streamParser()) ``` ### `logfmt.streamStringify([options])` Pipe objects into the stream and it will write logfmt. You can customize the delimiter via the `options` object, which defaults to `\n` (newlines). ```javascript var parseJSON = function(line) { if(!line) return; this.queue(JSON.parse(line.trim())) } process.stdin .pipe(split()) .pipe(through(parseJSON)) .pipe(logfmt.streamStringify()) .pipe(process.stdout) ``` #### Example Example command line of parsing logfmt and echoing objects to STDOUT: ```javascript var logfmt = require('logfmt'); var through = require('through'); process.stdin .pipe(logfmt.streamParser()) .pipe(through(function(object){ console.log(object); })) ``` Example HTTP request parsing logfmt and echoing objects to STDOUT: ```javascript var http = require('http'); var logfmt = require('logfmt'); var through = require('through'); http.createServer(function (req, res) { req.pipe(logfmt.streamParser()) .pipe(through(function(object){ console.log(object); })) res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('OK'); }).listen(3000); ``` ## express/restify parsing middleware ```javascript // streaming app.use(logfmt.bodyParserStream()); // buffering app.use(logfmt.bodyParser()); ``` #### `logfmt.bodyParserStream([opts])` Valid Options: - `contentType`: defaults to `application/logplex-1` If you use the `logfmt.bodyParserStream()` for a body parser, you will have a `req.body` that is a readable stream. Pipes FTW: ```javascript var app = require('express')(); var http = require('http'); var through = require('through'); var logfmt = require('logfmt'); app.use(logfmt.bodyParserStream()); app.post('/logs', function(req, res){ if(!req.body) return res.send('OK'); req.body.pipe(through(function(line){ console.dir(line); })) res.send('OK'); }) http.createServer(app).listen(3000); ``` Or you can just use the `readable` event: ```javascript var app = require('express')(); var http = require('http'); var logfmt = require('logfmt'); app.use(logfmt.bodyParserStream()); // req.body is now a Readable Stream app.post('/logs', function(req, res){ req.body.on('readable', function(){ var parsedLine = req.body.read(); if(parsedLine) console.log(parsedLine); else res.send('OK'); }) }) http.createServer(app).listen(3000); ``` ### Non-Streaming #### `logfmt.bodyParser([opts])` Valid Options: - `contentType`: defaults to `application/logplex-1` If you use the `logfmt.bodyParser()` for a body parser, you will have a `req.body` that is an array of objects. ```javascript var logfmt = require('logfmt'); app.use(logfmt.bodyParser()); // req.body is now an array of objects app.post('/logs', function(req, res){ console.log('BODY: ' + JSON.stringify(req.body)); req.body.forEach(function(data){ console.log(data); }); res.send('OK'); }) http.createServer(app).listen(3000); ``` test it: ```bash curl -X POST --header 'Content-Type: application/logplex-1' -d "foo=bar a=14 baz=\"hello kitty\" cool%story=bro f %^asdf" http://localhost:3000/logs ``` ## logging Log an object to `logfmt.stream` (defaults to STDOUT) Uses the `logfmt.stringify` function to write the result to `logfmt.stream` ```javascript logfmt.log({foo: "bar", a: 14, baz: 'hello kitty'}) //=> foo=bar a=14 baz="hello kitty" //> undefined ``` ### `logfmt.log(object, [stream])` Defaults to logging to `process.stdout` ```javascript logfmt.log({ "foo": "bar", "a": 14, baz: 'hello kitty'}) //=> foo=bar a=14 baz="hello kitty" ``` #### customizing logging location `logfmt.log()` Accepts as 2nd argument anything that responds to `write(string)` ```javascript var logfmt = require('logfmt'); logfmt.log({ "foo": "bar", "a": 14, baz: 'hello kitty'}, process.stderr) //=> foo=bar a=14 baz="hello kitty" ``` Overwrite the default global location by setting `logfmt.stream` ```javascript var logfmt = require('logfmt'); logfmt.stream = process.stderr logfmt.log({ "foo": "bar", "a": 14, baz: 'hello kitty'}) //=> foo=bar a=14 baz="hello kitty" ``` You can have multiple, isolated logfmts by using `new`. ```javascript var logfmt = require('logfmt'); var errorLogger = new logfmt; errorLogger.stream = process.stderr logfmt.log({hello: 'stdout'}); //=> hello=stdout errorLogger.log({hello: 'stderr'}); //=> hello=stderr ``` ### `logfmt.namespace(object)` Returns a new `logfmt` with object's data included in every `log` call. ```javascript var logfmt = require('logfmt').namespace({app: 'logfmt'}); logfmt.log({ "foo": "bar", "a": 14, baz: 'hello kitty'}) //=> app=logfmt foo=bar a=14 baz="hello kitty" logfmt.log({}) //=> app=logfmt logfmt.log({hello: 'world'}) //=> app=logfmt hello=world ``` ### `logfmt.time([label])` Log how long something takes. Returns a new `logfmt` with elapsed milliseconds included in every `log` call. - `label`: optional name for the milliseconds key. defaults to: `elapsed=ms` ```javascript var timer = logfmt.time(); timer.log(); //=> elapsed=1ms ``` String `label` changes the key to `=ms` ```javascript var timer = logfmt.time('time'); timer.log(); //=> time=1ms timer.log(); //=> time=2ms ``` If you'd like to include data, just chain a call to namespace. ```javascript var timer = logfmt.time('time').namespace({foo: 'bar'}); timer.log(); //=> time=1ms foo=bar timer.log(); //=> time=2ms foo=bar ``` ### `logfmt.error(error)` Accepts a Javascript `Error` object and converts it to logfmt format. It will print up to `logfmt.maxErrorLines` lines. ```javascript var logfmt = require('logfmt'); logfmt.error(new Error('test error')); //=> at=error id=12345 message="test error" //=> at=error id=12345 line=0 trace="Error: test error" //=> ... ``` ## express/restify logging middleware ```javascript app.use(logfmt.requestLogger()); //=> ip=127.0.0.1 time=2013-08-05T20:50:19.216Z method=POST path=/logs status=200 content_length=337 content_type=application/logplex-1 elapsed=4ms ``` #### `logfmt.requestLogger([options], [formatter(req, res)])` If no formatter is supplied it will default to `logfmt.requestLogger.commonFormatter` which is based on having similiar fields to the Apache Common Log format. Valid Options: - `immediate`: log before call to `next()` (ie: before the request finishes) - `elapsed`: renames the `elapsed` key to a key of your choice when in non-immediate mode Defaults to `immediate: true` and `elapsed: 'elapsed'` ```javascript app.use(logfmt.requestLogger({immediate: true}, function(req, res){ return { method: req.method } })); //=> method=POST ``` ```javascript app.use(logfmt.requestLogger({elapsed: 'request.time'}, function(req, res){ return { "request.method": req.method } })); //=> request.method=POST request.time=12ms ``` ##### `formatter(req, res)` A formatter takes the request and response and returns a JSON object for `logfmt.log` ```javascript app.use(logfmt.requestLogger(function(req, res){ return { method: req.method } })); //=> method=POST elapsed=4ms ``` It's always possible to piggyback on top of the `commonFormatter` ```javascript app.use(logfmt.requestLogger(function(req, res){ var data = logfmt.requestLogger.commonFormatter(req, res) return { ip: data.ip, time: data.time, foo: 'bar' }; })); //=> ip=127.0.0.1 time=2013-08-05T20:50:19.216Z foo=bar elapsed=4ms ``` # Development Pull Requests welcome. ## Tests > npm test # License MIT