Trent Mick (@trentmick)
Joyent
Vancouver Node.js Meetup, 7 Aug 2014
① A node.js lib for structured service (and CLI) logging
$ npm install bunyan
$ cat foo.js
var bunyan = require('bunyan');
var log = bunyan.createLogger({
name: 'foo',
// ...
});
log.info('hi');
var err = new Error('ack');
log.error(err, 'boom!');
② A (lightweight) JSON log format
{"name":"foo","hostname":"grape.local","pid":68313,"level":30,
"msg":"hi","time":"2014-08-07T06:18:53.057Z","v":0}
③ A bunyan
CLI for viewing and filtering logs
Reading JSON sucks, but ANSI codes in files + custom parsing code suck more:
bunyan foo.log [bar.log ...]
bunyan -l warn foo.log
bunyan -c 'this.user=="bob"' foo.log
log.child
See README for more.
log.info('%s %s %s %s', req.method, req.url,
res.statusCode, res.latency)
log.info({req: req}, 'received request');
log.debug({res: res}, 'responded');
log.error({err: err, path: path}, 'save failed');
Make having the extra details not a burden when browsing logs by rendering them nicely.
Maintain error cause chains with node-verror. Bunyan will render these.
Motivate logging of more structured details by making filtering convenient and compelling.
# ERROR level and above
$ bunyan foo.log -l error
# Watch incoming requests.
$ tail -f foo.log | bunyan -c 'this.req'
# Server errors in HTTP responses.
$ tail -f foo.log | bunyan -c 'this.res && this.res.statusCode >= 500'
err
, req
, and res
. Use is explicit, e.g.:
var log = bunyan.createLogger({
//...
serializers: {
err: bunyan.stdSerializers.err,
// custom
user: function (u) { if (u) return {login: u.login, name: u.name}; }
}
});
log.info({user: theUser}, 'logged in');
log.error({err: e, user: theUser}, 'logged failed');
[2014-08-07T17:25:55.126Z] INFO: imgapi/74427 on e540e25b-6afc-41ee-a940-a1da39628676: handled: 200 (req_id=dd8d4ad0-1e57-11e4-8499-df7725ba1cdc, route=createimage, action=create-from-vm, audit=true, remoteAddress=172.25.1.39, remotePort=38440, err=false, latency=6354, _audit=true, req.version=~1)
POST /images?action=create-from-vm&vm_uuid=c691c60d-8f65-6eb2-81d6-d163d65ea421&incremental=true&account=551a64ca-3e08-c0de-cfd2-da81641890ec HTTP/1.1
x-request-id: dd8d4ad0-1e57-11e4-8499-df7725ba1cdc
accept: application/json
content-type: application/json
user-agent: restify/2.6.1 (ia32-sunos; v8/3.14.5.9; OpenSSL/1.0.1h) node/0.10.30
date: Thu, 07 Aug 2014 17:25:48 GMT
content-length: 65
content-md5: d02bUF89wkeSJT+jGTEENg==
host: imgapi.nightly-1.joyent.us
connection: keep-alive
{
"name": "1cfeb76f-d951-c1e3-b9e7-97825c7c4efc",
"version": "1.0.0",
"owner": "551a64ca-3e08-c0de-cfd2-da81641890ec",
"uuid": "1c7a2b2d-f5da-eed2-d37c-e3a01de9a8ee",
"requirements": {
"min_platform": {
"7.0": "20140807T022845Z"
}
}
}
--
HTTP/1.1 200 OK
workflow-api: http://workflow.nightly-1.joyent.us
content-type: application/json
content-length: 112
date: Thu, 07 Aug 2014 17:25:55 GMT
server: imgapi/2.0.0
x-request-id: dd8d4ad0-1e57-11e4-8499-df7725ba1cdc
x-response-time: 6353
x-server-name: e540e25b-6afc-41ee-a940-a1da39628676
{
"image_uuid": "1c7a2b2d-f5da-eed2-d37c-e3a01de9a8ee",
"job_uuid": "071c5d60-1e28-488a-bdaa-0810c902a3da"
}
--
req.timers: {
"handler-0": 9,
"bunyan": 47,
"parseQueryString": 79,
"reqNoAuth": 4,
"reqChannel": 14,
"resume": 90,
"readBody": 840,
"parseBody": 260,
"apiCreateImage": 250,
"apiCreateImageFromVm": 6350993
}
log.warn
or above, then all low level
records for that request id are logged.var log = bunyan.createLogger({
name: 'cloudapi',
serializers: restify.bunyan.serializers,
streams: [
{
level: 'info',
stream: process.stderr
},
{
type: 'raw',
stream: new RequestCaptureStream({
level: bunyan.WARN,
maxRecords: 100,
maxRequestIds: 100,
streams: [ {
stream: process.stderr
} ]
})
}
]
});
INFO
, or DEBUG
at best.TRACE
logging details?bunyan
provider. No restart.
$ bunyan -p $PID
$ bunyan -p $NAME # search processes by name via `pgrep` or `ps`
$ bunyan -p '*' # instrument *all* processes!
$ bunyan -p '*' -l DEBUG
$ bunyan -p '*' -c 'this.name === "cloudapi"'
bunyan -p
` demolib/main.js: var log = log4j.getLogger('foo')
lib/db.js: var log = log4j.getLogger('foo.db')
lib/model.js: var log = log4j.getLogger('foo.model')
Common with log4j-inspired libraries.
Groups on module, not on related operations.
log.info('user=%s start blerging', user)
log.info('user=%s frob the frib', user);
log.info('bliging bloop'); // oops, forgot to log `user`
log.child
req.log = app.log.child({req_id: req.getId()});
// ...
req.log.info({route: route}, 'routed request');
// ...
req.log.debug(err, 'validation failed');
log.child
.<request>.log
for
each HTTP request.req_id
$ bunyan foo.log \
-c "this.req_id === 'd6740300-1e58-11e4-8f85-2fef7c7c36dd'"
Now we can see all logging for a particular request.
req_id++
How about tracking a request through multiple services?
Example: provision a VM in SmartDataCenter
req_id++
Demo tracking VM creation in SmartDataCenter via
req_id
.
Trent Mick (@trentmick)
github.com/trentm/node-bunyan
bunyan-logging@googlegroups.com
Slides: trentm.com/talk-bunyan-in-prod/
and on GitHub: github.com/trentm/talk-bunyan-in-prod