473 lines
10 KiB
Markdown
473 lines
10 KiB
Markdown
|
# 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=<milliseconds>ms`
|
||
|
|
||
|
```javascript
|
||
|
var timer = logfmt.time();
|
||
|
timer.log();
|
||
|
//=> elapsed=1ms
|
||
|
```
|
||
|
|
||
|
String `label` changes the key to `<string>=<milliseconds>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
|