'log.child(..., true)' support for 10x faster with 'simple' field additions

This commit is contained in:
Trent Mick 2012-02-04 21:42:47 -08:00
parent 6806112d8a
commit 40777aaf35
6 changed files with 156 additions and 26 deletions

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
/tmp /tmp
/npm-debug.log /npm-debug.log
/node_modules /node_modules
*.log

View file

@ -2,6 +2,12 @@
## bunyan 0.3.0 (not yet released) ## bunyan 0.3.0 (not yet released)
- `log.child(options[, simple])` Added `simple` boolean arg. Set `true` to
assert that options only add fields (no config changes). Results in a 10x
speed increase in child creation. See "tools/timechild.js". On my Mac,
"fast child" creation takes about 0.001ms. IOW, if your app is dishing
10,000 req/s, then creating a log child for each request will take
about 1% of the request time.
- `log.clone` -> `log.child` to better reflect the relationship: streams and - `log.clone` -> `log.child` to better reflect the relationship: streams and
serializers are inherited. Streams can't be removed as part of the child serializers are inherited. Streams can't be removed as part of the child
creation. The child doesn't own the parent's streams (so can't close them). creation. The child doesn't own the parent's streams (so can't close them).

View file

@ -73,7 +73,7 @@ streams at different levels**.
] ]
}); });
A **`log.clone(...)`** is provided to specialize a logger for a sub-component. A **`log.child(...)`** is provided to specialize a logger for a sub-component.
The following will have log records from "Wuzzle" instances use exactly the The following will have log records from "Wuzzle" instances use exactly the
same config as its parent, plus include the "component" field. same config as its parent, plus include the "component" field.
@ -89,11 +89,25 @@ same config as its parent, plus include the "component" field.
this.log.warn("This wuzzle is woosey.") this.log.warn("This wuzzle is woosey.")
} }
var wuzzle = new Wuzzle({log: log.clone({component: "wuzzle"})}); var wuzzle = new Wuzzle({log: log.child({component: "wuzzle"})});
wuzzle.woos(); wuzzle.woos();
log.info("done with the wuzzle") log.info("done with the wuzzle")
An example and a hack: The [node-restify](https://github.com/mcavage/node-restify)
framework integrates bunyan. One feature is that each restify request handler
includes a `req.log` logger that is a:
log.child({req_id: <unique request id>}, true)
Apps using restify can then use `req.log` and have all such log records
include the unique request id (as "req_id"). Handy. *What is that `true`?* It
is a small bunyan hack by which you can assert that you're just adding
simple fields to the child logger. This makes `log.child` 10x faster and,
hence, never a worry for slowing down HTTP request handling. See the
changelog for node-bunyan 0.3.0 for details.
Back to the `log.{trace|debug|...|fatal}(...)` API: Back to the `log.{trace|debug|...|fatal}(...)` API:
log.info(); // returns a boolean: is the "info" level enabled? log.info(); // returns a boolean: is the "info" level enabled?

View file

@ -1,5 +1,3 @@
- fast clone: basically make it reasonable to clone per HTTP request.
Ditch mutability. Add another context (another entry in Log record tuple?)?
- `log.close` to close streams and shutdown and `this.closed` - `log.close` to close streams and shutdown and `this.closed`
- line/file: possible to get quickly with v8? Yunong asked. - line/file: possible to get quickly with v8? Yunong asked.
- what's the API for changing the logger/stream level(s)? - what's the API for changing the logger/stream level(s)?
@ -58,6 +56,8 @@
} }
} }
Want some way to have file/line only at certain levesl and lazily. Want some way to have file/line only at certain levesl and lazily.
- get Mark to show me dtrace provider stuff and consider adding for
logging, if helpful.
- add option to "streams" to take the raw object, not serialized. - add option to "streams" to take the raw object, not serialized.
It would be a good hook for people with custom needs that Bunyan doesn't It would be a good hook for people with custom needs that Bunyan doesn't
care about (e.g. log.ly or hook.io or whatever). care about (e.g. log.ly or hook.io or whatever).

View file

@ -138,9 +138,14 @@ function resolveLevel(nameOrNum) {
* All other keys are log record fields. * All other keys are log record fields.
* *
* An alternative *internal* call signature is used for creating a child: * An alternative *internal* call signature is used for creating a child:
* new Logger(<parent logger>, <child options>); * new Logger(<parent logger>, <child options>[, <child opts are simple>]);
*
* @param _childSimple (Boolean) An assertion that the given `_childOptions`
* (a) only add fields (no config) and (b) no serialization handling is
* required for them. IOW, this is a fast path for frequent child
* creation.
*/ */
function Logger(options, _childOptions) { function Logger(options, _childOptions, _childSimple) {
xxx('Logger start:', options) xxx('Logger start:', options)
if (! this instanceof Logger) { if (! this instanceof Logger) {
return new Logger(options, _childOptions); return new Logger(options, _childOptions);
@ -154,12 +159,6 @@ function Logger(options, _childOptions) {
if (! parent instanceof Logger) { if (! parent instanceof Logger) {
throw new TypeError('invalid Logger creation: do not pass a second arg'); throw new TypeError('invalid Logger creation: do not pass a second arg');
} }
if (options.level) {
throw new TypeError('cannot use "level" option when creating a child');
}
if (options.stream) {
throw new TypeError('cannot use "stream" option when creating a child');
}
} }
if (!options) { if (!options) {
throw new TypeError('options (object) is required'); throw new TypeError('options (object) is required');
@ -168,16 +167,34 @@ function Logger(options, _childOptions) {
throw new TypeError('cannot mix "streams" with "stream" or "level" options'); throw new TypeError('cannot mix "streams" with "stream" or "level" options');
} }
// Fast path for simple child creation.
if (parent && _childSimple) {
// Single to stream close handling that this child owns none of its
// streams.
this._isSimpleChild = true;
this.level = parent.level;
this.streams = parent.streams;
this.serializers = parent.serializers;
this.fields = parent.fields;
var names = Object.keys(options);
for (var i = 0; i < names.length; i++) {
var name = names[i];
this.fields[name] = options[name];
}
return;
}
// Null values. // Null values.
var self = this; var self = this;
if (parent) { if (parent) {
this.level = parent.level; this.level = parent.level;
this.streams = []; this.streams = [];
parent.streams.forEach(function (s) { for (var i = 0; i < parent.streams.length; i++) {
var s = objCopy(s); var s = objCopy(parent.streams[i]);
s.closeOnExit = false; // Don't own parent stream. s.closeOnExit = false; // Don't own parent stream.
self.streams.push(s); this.streams.push(s);
}); }
this.serializers = objCopy(parent.serializers); this.serializers = objCopy(parent.serializers);
this.fields = objCopy(parent.fields); this.fields = objCopy(parent.fields);
} else { } else {
@ -204,7 +221,7 @@ function Logger(options, _childOptions) {
if (s.level) { if (s.level) {
s.level = resolveLevel(s.level); s.level = resolveLevel(s.level);
} else { } else {
s.level = level; s.level = INFO;
} }
if (s.level < self.level) { if (s.level < self.level) {
self.level = s.level; self.level = s.level;
@ -314,9 +331,13 @@ function Logger(options, _childOptions) {
* call. * call.
* - The parent's serializers are inherited, though can effectively be * - The parent's serializers are inherited, though can effectively be
* overwritten by using duplicate keys. * overwritten by using duplicate keys.
* @param simple {Boolean} Optional. Set to true to assert that `options`
* (a) only add fields (no config) and (b) no serialization handling is
* required for them. IOW, this is a fast path for frequent child
* creation. See "tools/timechild.js" for numbers.
*/ */
Logger.prototype.child = function (options) { Logger.prototype.child = function (options, simple) {
return new Logger(this, options); return new Logger(this, options || {}, simple);
} }
@ -330,6 +351,7 @@ Logger.prototype.child = function (options) {
// if (this._closed) { // if (this._closed) {
// return; // return;
// } // }
// if (!this._isSimpleChild) {
// self.streams.forEach(function (s) { // self.streams.forEach(function (s) {
// if (s.endOnClose) { // if (s.endOnClose) {
// xxx("closing stream s:", s); // xxx("closing stream s:", s);
@ -337,6 +359,7 @@ Logger.prototype.child = function (options) {
// s.endOnClose = false; // s.endOnClose = false;
// } // }
// }); // });
// }
// this._closed = true; // this._closed = true;
//} //}

86
tools/timechild.js Executable file
View file

@ -0,0 +1,86 @@
#!/usr/bin/env node
/*
* Time `log.child(...)`.
*
* Getting 0.011ms on my Mac. For about 1000 req/s that means that the
* `log.child` would be about 1% of the time handling that request.
* Could do better. I.e. consider a hackish fast path.
*
* ...
*
* Added: `log.fastchild({...}, true)`. Use the `true` to assert that
* the given options are just new fields (and no serializers).
* Result: Another order of magnitude.
*/
var ben = require('ben'); // npm install ben
var Logger = require('../lib/bunyan');
var log = new Logger({
service: "svc",
streams: [
{
path: __dirname + "/timechild.log"
},
{
stream: process.stdout
}
],
serializers: {
err: Logger.stdSerializers.err
}
});
console.log("Time `log.child`:");
var ms = ben(1e5, function () {
var child = log.child();
});
console.log(' - adding no fields: %dms per iteration', ms);
var ms = ben(1e5, function () {
var child = log.child({a:1});
});
console.log(' - adding one field: %dms per iteration', ms);
var ms = ben(1e5, function () {
var child = log.child({a:1, b:2});
});
console.log(' - adding two fields: %dms per iteration', ms);
function fooSerializer(obj) {
return {bar: obj.bar};
}
var ms = ben(1e5, function () {
var child = log.child({
a: 1,
serializers: {foo: fooSerializer}
});
});
console.log(' - adding serializer and one field: %dms per iteration', ms);
var ms = ben(1e5, function () {
var child = log.child({
a: 1,
streams: [{
stream: process.stderr
}]
});
});
console.log(' - adding a (stderr) stream and one field: %dms per iteration', ms);
var ms = ben(1e6, function () {
var child = log.child({}, true);
});
console.log(' - [fast] adding no fields: %dms per iteration', ms);
var ms = ben(1e6, function () {
var child = log.child({a:1}, true);
});
console.log(' - [fast] adding one field: %dms per iteration', ms);
var ms = ben(1e6, function () {
var child = log.child({a:1, b:2}, true);
});
console.log(' - [fast] adding two fields: %dms per iteration', ms);