diff --git a/README.md b/README.md index 0af19308b..5bc258c08 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ It also includes a shell utility to pretty-print its log files. * [API](#api) * [Benchmarks](#benchmarks) * [How do I rotate log files?](#rotate) +* [Acknowledgements](#acknowledgements) * [License](#license) ## Install @@ -62,6 +63,8 @@ This produces: * logger.info() * logger.debug() * logger.trace() + * pino.stdSerializers.req + * pino.stdSerializers.res ### pino([opts], [stream]) @@ -71,9 +74,28 @@ Returns a new logger. Allowed options are: * `safe`: avoid error causes by circular references in the object tree, default `true` * `name`: the name of the logger, default `undefined` +* `serializers`: an object containing functions for custom serialization + of objects. These functions should return an jsonificable object and + they should never throw. `stream` is a Writable stream, defaults to `process.stdout`. +Example: + +```js +'use strict' + +var pino = require('pino') +var instance = pino({ + name: 'myapp', + safe: true, + serializers: { + req: pino.stdSerializers.req + res: pino.stdSerializers.res + } +} +``` + ### logger.level @@ -139,6 +161,59 @@ object, all its properties will be included in the JSON line. If more args follows `msg`, these will be used to format `msg` using [`util.format`](https://nodejs.org/api/util.html#util_util_format_format) + +### pino.stdSerializers.req + +Function to generate a jsonificable object out of an HTTP request from +node HTTP server. + +It returns an object in the form: + +```js +{ + pid: 93535, + hostname: 'your host', + level: 30, + msg: 'my request', + time: '2016-03-07T12:21:48.766Z', + v: 0, + req: { + method: 'GET', + url: '/', + headers: { + host: 'localhost:50201', + connection: 'close' + }, + remoteAddress: '::ffff:127.0.0.1', + remotePort: 50202 + } +} +``` + + +### pino.stdSerializers.res + +Function to generate a jsonificable object out of an HTTP +response from +node HTTP server. + +It returns an object in the form: + +```js +{ + pid: 93581, + hostname: 'myhost', + level: 30, + msg: 'my response', + time: '2016-03-07T12:23:18.041Z', + v: 0, + res: { + statusCode: 200, + header: 'HTTP/1.1 200 OK\r\nDate: Mon, 07 Mar 2016 12:23:18 GMT\r\nConnection: close\r\nContent-Length: 5\r\n\r\n' + } +} +``` + ## Benchmarks @@ -190,6 +265,11 @@ In order to rotate your log files, add in `/etc/logrotate.d/myapp`: } ``` + +## Acknowledgements + +This project was kindly sponsored by [nearForm](http://nearform.com). + ## License MIT diff --git a/pino.js b/pino.js index 3aec8cd08..e7a083355 100644 --- a/pino.js +++ b/pino.js @@ -35,6 +35,7 @@ function pino (opts, stream) { debug: null, trace: null } + var serializers = opts.serializers || {} for (var key in levels) { funcs[key] = genLogFunction(key) @@ -76,6 +77,12 @@ function pino (opts, stream) { obj = a params = [b, c, d, e, f, g, h, i, j, k] base = 1 + + if (obj.method && obj.headers && obj.socket) { + obj = mapHttpRequest(obj) + } else if (obj.statusCode) { + obj = mapHttpResponse(obj) + } } else { params = [a, b, c, d, e, f, g, h, i, j, k] } @@ -92,6 +99,7 @@ function pino (opts, stream) { msg = obj.message } var data = message(num, msg) + var value if (obj) { data = data.slice(0, data.length - 1) @@ -99,8 +107,10 @@ function pino (opts, stream) { data += ',"type":"Error","stack":' + stringify(obj.stack) + '}' } else { for (var key in obj) { - if (obj.hasOwnProperty(key) && obj[key] !== undefined) { - data += ',"' + key + '":' + stringify(obj[key]) + value = obj[key] + if (obj.hasOwnProperty(key) && value !== undefined) { + value = serializers[key] ? serializers[key](value) : value + data += ',"' + key + '":' + stringify(value) } } data += '}' @@ -122,4 +132,38 @@ function pino (opts, stream) { function noop () {} +function mapHttpRequest (req) { + return { + req: asReqValue(req) + } +} + +function mapHttpResponse (res) { + return { + res: asResValue(res) + } +} + +function asReqValue (req) { + return { + method: req.method, + url: req.url, + headers: req.headers, + remoteAddress: req.connection.remoteAddress, + remotePort: req.connection.remotePort + } +} + +function asResValue (res) { + return { + statusCode: res.statusCode, + header: res._header + } +} + module.exports = pino + +module.exports.stdSerializers = { + req: asReqValue, + res: asResValue +} diff --git a/test.js b/test.js index 7f8dc2b88..bcfa15f24 100644 --- a/test.js +++ b/test.js @@ -5,6 +5,7 @@ var pino = require('./') var writeStream = require('flush-write-stream') var os = require('os') var split = require('split2') +var http = require('http') var pid = process.pid var hostname = os.hostname() @@ -250,3 +251,157 @@ test('set properties defined in the prototype chain', function (t) { instance.info(new MyObject()) }) + +test('http request support', function (t) { + t.plan(3) + + var originalReq + var instance = pino(sink(function (chunk, enc, cb) { + t.ok(Date.parse(chunk.time) <= new Date(), 'time is greater than Date.now()') + delete chunk.time + t.deepEqual(chunk, { + pid: pid, + hostname: hostname, + level: 30, + msg: 'my request', + v: 0, + req: { + method: originalReq.method, + url: originalReq.url, + headers: originalReq.headers, + remoteAddress: originalReq.connection.remoteAddress, + remotePort: originalReq.connection.remotePort + } + }) + cb() + })) + + var server = http.createServer(function (req, res) { + originalReq = req + instance.info(req, 'my request') + res.end('hello') + }).listen(function (err) { + t.error(err) + t.teardown(server.close.bind(server)) + + http.get('http://localhost:' + server.address().port, function (res) { + res.resume() + }) + }) +}) + +test('http request support via serializer', function (t) { + t.plan(3) + + var originalReq + var instance = pino({ + serializers: { + req: pino.stdSerializers.req + } + }, sink(function (chunk, enc, cb) { + t.ok(Date.parse(chunk.time) <= new Date(), 'time is greater than Date.now()') + delete chunk.time + t.deepEqual(chunk, { + pid: pid, + hostname: hostname, + level: 30, + msg: 'my request', + v: 0, + req: { + method: originalReq.method, + url: originalReq.url, + headers: originalReq.headers, + remoteAddress: originalReq.connection.remoteAddress, + remotePort: originalReq.connection.remotePort + } + }) + cb() + })) + + var server = http.createServer(function (req, res) { + originalReq = req + instance.info({ req: req }, 'my request') + res.end('hello') + }).listen(function (err) { + t.error(err) + t.teardown(server.close.bind(server)) + + http.get('http://localhost:' + server.address().port, function (res) { + res.resume() + }) + }) +}) + +test('http response support', function (t) { + t.plan(3) + + var originalRes + var instance = pino(sink(function (chunk, enc, cb) { + t.ok(Date.parse(chunk.time) <= new Date(), 'time is greater than Date.now()') + delete chunk.time + t.deepEqual(chunk, { + pid: pid, + hostname: hostname, + level: 30, + msg: 'my response', + v: 0, + res: { + statusCode: originalRes.statusCode, + header: originalRes._header + } + }) + cb() + })) + + var server = http.createServer(function (req, res) { + originalRes = res + res.end('hello') + instance.info(res, 'my response') + }).listen(function (err) { + t.error(err) + t.teardown(server.close.bind(server)) + + http.get('http://localhost:' + server.address().port, function (res) { + res.resume() + }) + }) +}) + +test('http response support via a serializer', function (t) { + t.plan(3) + + var originalRes + var instance = pino({ + serializers: { + res: pino.stdSerializers.res + } + }, sink(function (chunk, enc, cb) { + t.ok(Date.parse(chunk.time) <= new Date(), 'time is greater than Date.now()') + delete chunk.time + t.deepEqual(chunk, { + pid: pid, + hostname: hostname, + level: 30, + msg: 'my response', + v: 0, + res: { + statusCode: originalRes.statusCode, + header: originalRes._header + } + }) + cb() + })) + + var server = http.createServer(function (req, res) { + originalRes = res + res.end('hello') + instance.info({ res: res }, 'my response') + }).listen(function (err) { + t.error(err) + t.teardown(server.close.bind(server)) + + http.get('http://localhost:' + server.address().port, function (res) { + res.resume() + }) + }) +})