• Stars
    star
    151
  • Rank 246,057 (Top 5 %)
  • Language
    JavaScript
  • Created over 11 years ago
  • Updated 12 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

logfmt (key=value) logger and parser

node-logfmt

Build Status

"logfmt" is the name for a key value logging convention 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.

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.

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.

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)

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

process.stdin.pipe(logfmt.streamParser())

Or pipe from an HTTP request

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).

  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:

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:

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

  // 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:

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:

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.

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:

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

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

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)

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

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.

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.

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
var timer = logfmt.time();
timer.log();
//=> elapsed=1ms

String label changes the key to <string>=<milliseconds>ms

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.

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.

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

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 similar 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'

app.use(logfmt.requestLogger({immediate: true}, function(req, res){
  return {
    method: req.method
  }
}));
//=> method=POST
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

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

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

More Repositories

1

IMGKit

Uses wkhtmltoimage to create JPGs and PNGs from HTML
Ruby
726
star
2

arduino-restclient

Arduino RESTful HTTP Request Library
C++
205
star
3

rack-worker

Rack middleware that implements the Worker Pattern to process generic GET requests in the background and only serve them from a cache.
Ruby
103
star
4

fernet.js

Javascript implementation of Fernet symmetric encryption https://github.com/kr/fernet-spec
JavaScript
72
star
5

checkcheckit

command line checklist tool
Ruby
43
star
6

sinatra-google-auth

Drop-in google auth for Sinatra apps
Ruby
37
star
7

arduino-http-test

Test app for arduino-http library.
Ruby
9
star
8

resin-piminer

Bitcoin mining with Raspberry Pi and Resin.io
Shell
9
star
9

arduino-mqtt-example

sample node.js app that connects to Arduino to browser via CloudMQTT and Socket.io
JavaScript
8
star
10

postgres-o-matic

demo of postgres as a cloud service for Heroku
Ruby
8
star
11

node-proxy

Proxy Server(s) in Node.js and JS.Class
JavaScript
8
star
12

loopr

Loop Pedal with Arduino, Ruby, and SoX
Ruby
8
star
13

udacity-dlnd

Notes / scripts from my Udacity deep learning nanodegree
Jupyter Notebook
7
star
14

cablegator

Downloads all the Wikileaks CableGate files to a directory of your choosing
Ruby
6
star
15

oauth2-server

OAuth2 server implemented in Sinatra
Ruby
6
star
16

remote-control

node.js server that exposes a volume control for a mac as an HTTP app controlled via websockets
JavaScript
6
star
17

env.rb

Manage your ENV with ease
Ruby
5
star
18

draino

Heroku logplex drain framework for node.js
JavaScript
5
star
19

kensa-create-sinatra-production

Sinatra Add-on with all my idiosyncrasies- ready with a db, background processing, and testing
Ruby
4
star
20

ion-cannon

Multi-Protocol Load Tester
JavaScript
4
star
21

NQueens

BDD NQueens Solver for Austin on Rails presentiation
Ruby
3
star
22

cerberus-prox-frontend

A Fronted for the cerberus-proxy
JavaScript
3
star
23

neuron

UNIX process wrapper that uses etcd for configuration
Go
3
star
24

space_cowboy

Space Cowboy Rulez the Universe!
Java
3
star
25

Chaos-Composer

Ruby research program that uses event trees derived from chaotic equations to write melodies (with midi).
Ruby
3
star
26

deploying-rails-3.1-on-cedar

Talk for Austin on Rails about deploying Rails 3.1 on Cedar
Ruby
3
star
27

rack-cgi-proxy

an experiment in add-ons
Ruby
2
star
28

heartbeat-monitor

Sinatra app that functions as a simple heartbeat monitor.
Ruby
2
star
29

Bundler-Presentation

showoff slides from my rails user group presentation on the bundler
Ruby
2
star
30

simple-buffer-overflow

ruby program that brute force searches for the necessary offsets for an overflow
Ruby
2
star
31

cerberus_prox_sinatra_old

Old version fo the cerberus prox frontend for documentation
Ruby
1
star
32

test-devcenter-article

example of a complex devcenter article for use with devcenter gem
Ruby
1
star
33

resin-simple-demo

Simplest resin.io project that blinks Raspberry Pi ACT(ivity) LED
Shell
1
star
34

outage-lights-device

Heroku outage lights for Raspberry PI
JavaScript
1
star
35

arduino-EEE314-arduino

Instructions for Arduino-EEE314 class
1
star
36

jersey

Sinatra API Helpers
Ruby
1
star
37

callchain

Simple, composable pipelines for ruby
Ruby
1
star
38

anthropic-prompt-library-scores

Tests for the anthropic prompt library
Python
1
star
39

RGBreather

slowly pulsing RGB LED for desktop use as a reminder to breathe
Arduino
1
star
40

smart_env

Take those boring ENV vars and do something interesting with them.
Ruby
1
star
41

errorbucket

Python
1
star
42

env-conf

A better way to use the ENV for configuration
Ruby
1
star
43

devcenter

Gem for locally working with Heroku Dev Center
Ruby
1
star
44

.vim

My .vim directory
Vim Script
1
star
45

metrik

Metrics aggregator for logfmt-style metrics on STDOUT
JavaScript
1
star
46

heroku-buildpack-rack-nomagic

heroku buildpack for rack apps in shell
1
star
47

htmltocloud

Sinatra Service to generate PDFs and Images from html
Ruby
1
star
48

prompt-experiments

Jupyter notebooks for tracking experiments with prompting techniques
Jupyter Notebook
1
star
49

monotonic

Provides Monotonic.now to make syscalls to the monotonically increasing system clock
Ruby
1
star
50

game_of_life

Python CLI inmplemenation of game of life
Python
1
star
51

meta-prompt

ChatBot Prompt Improvement Tool
Python
1
star
52

arduino-http-switch

Arduino software that implements an HTTP server that toggles a 5V output and has a heartbeat for monitoring.
Arduino
1
star