multiple streams support at different levels; add 'file' stream type
parent
6d94985f2d
commit
fdb9114218
|
@ -0,0 +1 @@
|
||||||
|
/tmp
|
26
TODO.md
26
TODO.md
|
@ -1,17 +1,25 @@
|
||||||
- mark wants pretty output for debug output
|
|
||||||
- not sure if 'bunyan --pretty' or whatever would suffice
|
|
||||||
- mark suggested a list of streams. This is what ring could be.
|
|
||||||
- `bunyan` cli
|
- `bunyan` cli
|
||||||
- expand set of fields
|
- renderer support (i.e. repr of a restify request obj)
|
||||||
|
- expand set of fields: from dap
|
||||||
|
time, hostname
|
||||||
<https://github.com/Graylog2/graylog2-docs/wiki/GELF>
|
<https://github.com/Graylog2/graylog2-docs/wiki/GELF>
|
||||||
<http://journal.paul.querna.org/articles/2011/12/26/log-for-machines-in-json/>
|
<http://journal.paul.querna.org/articles/2011/12/26/log-for-machines-in-json/>
|
||||||
require: facility and hostname
|
require: facility and hostname
|
||||||
- renderer support (i.e. repr of a restify request obj)
|
|
||||||
- docs
|
- docs
|
||||||
- feel out usage
|
- mark wants pretty output for debug output
|
||||||
- not sure about `log.info()` for is-enabled. Perhaps `log.isInfo()` because
|
- not sure if 'bunyan --pretty' or whatever would suffice
|
||||||
can then use that for `log.isInfo(true)` for 'ring' argument. Separate level
|
- ringBuffer stream
|
||||||
and ringLevel.
|
- syslog: Josh uses https://github.com/chrisdew/node-syslog
|
||||||
|
streams: [
|
||||||
|
...
|
||||||
|
{
|
||||||
|
level: "warn",
|
||||||
|
type: "syslog",
|
||||||
|
syslog_facility: "LOG_LOCAL1", // one of the syslog facility defines
|
||||||
|
syslog_pid: true, // syslog logopt "LOG_PID"
|
||||||
|
syslog_cons: false // syslog logopt "LOG_CONS"
|
||||||
|
}
|
||||||
- Logger.set to mutate config or `this.fields`
|
- Logger.set to mutate config or `this.fields`
|
||||||
- Logger.del to remove a field
|
- Logger.del to remove a field
|
||||||
|
- "canWrite" handling for full streams. Need to buffer a la log4js
|
||||||
- test suite
|
- test suite
|
||||||
|
|
|
@ -1,14 +1,22 @@
|
||||||
var Logger = require('./lib/bunyan');
|
var Logger = require('../lib/bunyan');
|
||||||
|
|
||||||
|
// Basic usage.
|
||||||
var log = new Logger({facility: "myapp", level: "info"});
|
var log = new Logger({facility: "myapp", level: "info"});
|
||||||
|
|
||||||
|
// isInfoEnabled replacement
|
||||||
console.log("log.info() is:", log.info())
|
console.log("log.info() is:", log.info())
|
||||||
|
|
||||||
|
// `util.format`-based printf handling
|
||||||
log.info("hi");
|
log.info("hi");
|
||||||
log.info("hi", "trent");
|
log.info("hi", "trent");
|
||||||
log.info("hi %s there", true);
|
log.info("hi %s there", true);
|
||||||
|
|
||||||
|
// First arg as an object adds fields to the log record.
|
||||||
log.info({foo:"bar"}, "hi %d", 1, "two", 3);
|
log.info({foo:"bar"}, "hi %d", 1, "two", 3);
|
||||||
|
|
||||||
|
|
||||||
console.log("\n--\n")
|
// Shows `log.clone(...)` to specialize a logger for a sub-component.
|
||||||
|
console.log("\n\n")
|
||||||
|
|
||||||
function Wuzzle(options) {
|
function Wuzzle(options) {
|
||||||
this.log = options.log;
|
this.log = options.log;
|
|
@ -0,0 +1,21 @@
|
||||||
|
var Logger = require('../lib/bunyan');
|
||||||
|
log = new Logger({
|
||||||
|
service: "amon",
|
||||||
|
streams: [
|
||||||
|
{
|
||||||
|
level: "info",
|
||||||
|
stream: process.stdout,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
level: "error",
|
||||||
|
path: "tmp/error.log"
|
||||||
|
// when to close file stream?
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
log.debug("hi nobody on debug");
|
||||||
|
log.info("hi stdout on info");
|
||||||
|
log.error("hi both on error");
|
||||||
|
log.fatal("hi both on fatal");
|
130
lib/bunyan.js
130
lib/bunyan.js
|
@ -97,6 +97,12 @@ var nameFromLevel = {
|
||||||
FATAL: 'fatal'
|
FATAL: 'fatal'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function getLevel(nameOrNum) {
|
||||||
|
return (typeof(nameOrNum) === 'string'
|
||||||
|
? levelFromName[nameOrNum]
|
||||||
|
: nameOrNum);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//---- Logger class
|
//---- Logger class
|
||||||
|
|
||||||
|
@ -105,8 +111,13 @@ function Logger(options) {
|
||||||
if (! this instanceof Logger) {
|
if (! this instanceof Logger) {
|
||||||
return new Logger(options);
|
return new Logger(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var self = this;
|
||||||
if (!options) {
|
if (!options) {
|
||||||
throw new TypeError("options (object) is required");
|
throw new TypeError('options (object) is required');
|
||||||
|
}
|
||||||
|
if (options.stream && options.streams) {
|
||||||
|
throw new TypeError('can only have one of "stream" or "streams"');
|
||||||
}
|
}
|
||||||
|
|
||||||
// These are the default fields for log records (minus the attributes
|
// These are the default fields for log records (minus the attributes
|
||||||
|
@ -115,25 +126,105 @@ function Logger(options) {
|
||||||
// any changes.
|
// any changes.
|
||||||
this.fields = objCopy(options);
|
this.fields = objCopy(options);
|
||||||
|
|
||||||
if (options.stream) {
|
// Extract and setup the configuration options (the remaining ones are
|
||||||
this.stream = options.stream;
|
// log record fields).
|
||||||
delete this.fields.stream;
|
var lowestLevel = Number.POSITIVE_INFINITY;
|
||||||
} else {
|
var level;
|
||||||
this.stream = process.stdout;
|
|
||||||
}
|
|
||||||
if (options.level) {
|
if (options.level) {
|
||||||
this.level = (typeof(options.level) === 'string'
|
level = getLevel(options.level);
|
||||||
? levelFromName[options.level]
|
if (! (DEBUG <= level && level <= FATAL)) {
|
||||||
: options.level);
|
|
||||||
if (! (DEBUG <= this.level && this.level <= FATAL)) {
|
|
||||||
throw new Error('invalid level: ' + options.level);
|
throw new Error('invalid level: ' + options.level);
|
||||||
}
|
}
|
||||||
delete this.fields.level;
|
delete this.fields.level;
|
||||||
} else {
|
} else {
|
||||||
this.level = 2; // INFO is default level.
|
level = INFO;
|
||||||
}
|
}
|
||||||
|
this.streams = [];
|
||||||
|
if (options.stream) {
|
||||||
|
this.streams.push({
|
||||||
|
type: "stream",
|
||||||
|
stream: options.stream,
|
||||||
|
closeOnExit: false,
|
||||||
|
level: level
|
||||||
|
});
|
||||||
|
if (level < lowestLevel) {
|
||||||
|
lowestLevel = level;
|
||||||
|
}
|
||||||
|
delete this.fields.stream;
|
||||||
|
} else if (options.streams) {
|
||||||
|
options.streams.forEach(function (s) {
|
||||||
|
s = objCopy(s);
|
||||||
|
|
||||||
|
// Implicit 'type' from other args.
|
||||||
|
type = s.type;
|
||||||
|
if (!s.type) {
|
||||||
|
if (s.stream) {
|
||||||
|
s.type = "stream";
|
||||||
|
} else if (s.path) {
|
||||||
|
s.type = "file"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s.level) {
|
||||||
|
s.level = getLevel(s.level);
|
||||||
|
} else {
|
||||||
|
s.level = level;
|
||||||
|
}
|
||||||
|
if (s.level < lowestLevel) {
|
||||||
|
lowestLevel = s.level;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (s.type) {
|
||||||
|
case "stream":
|
||||||
|
if (!s.closeOnExit) {
|
||||||
|
s.closeOnExit = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "file":
|
||||||
|
if (!s.stream) {
|
||||||
|
s.stream = fs.createWriteStream(s.path,
|
||||||
|
{flags: 'a', encoding: 'utf8'});
|
||||||
|
if (!s.closeOnExit) {
|
||||||
|
s.closeOnExit = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!s.closeOnExit) {
|
||||||
|
s.closeOnExit = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new TypeError('unknown stream type "' + s.type + '"');
|
||||||
|
}
|
||||||
|
|
||||||
|
self.streams.push(s);
|
||||||
|
});
|
||||||
|
delete this.fields.streams;
|
||||||
|
} else {
|
||||||
|
this.streams.push({
|
||||||
|
type: "stream",
|
||||||
|
stream: process.stdout,
|
||||||
|
closeOnExit: false,
|
||||||
|
level: level
|
||||||
|
});
|
||||||
|
if (level < lowestLevel) {
|
||||||
|
lowestLevel = level;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.level = lowestLevel;
|
||||||
|
|
||||||
|
paul("Logger: ", self)
|
||||||
|
|
||||||
//XXX Non-core fields should go in 'x' sub-object.
|
//XXX Non-core fields should go in 'x' sub-object.
|
||||||
|
|
||||||
|
//process.on('exit', function () {
|
||||||
|
// self.streams.forEach(function (s) {
|
||||||
|
// if (s.closeOnExit) {
|
||||||
|
// paul("closing stream s:", s);
|
||||||
|
// s.stream.end();
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
//});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -154,10 +245,8 @@ function Logger(options) {
|
||||||
* Supports the same set of options as the constructor.
|
* Supports the same set of options as the constructor.
|
||||||
*/
|
*/
|
||||||
Logger.prototype.clone = function (options) {
|
Logger.prototype.clone = function (options) {
|
||||||
paul("keys", Object.keys(this))
|
|
||||||
var cloneOptions = objCopy(this.fields);
|
var cloneOptions = objCopy(this.fields);
|
||||||
cloneOptions.level = this.level;
|
cloneOptions.streams = this.streams;
|
||||||
cloneOptions.stream = this.stream;
|
|
||||||
if (options) {
|
if (options) {
|
||||||
Object.keys(options).forEach(function(k) {
|
Object.keys(options).forEach(function(k) {
|
||||||
cloneOptions[k] = options[k];
|
cloneOptions[k] = options[k];
|
||||||
|
@ -195,11 +284,18 @@ Logger.prototype._emit = function (rec) {
|
||||||
obj[k] = recFields[k];
|
obj[k] = recFields[k];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
obj.level = rec[2];
|
var level = obj.level = rec[2];
|
||||||
paul("Record:", rec)
|
paul("Record:", rec)
|
||||||
obj.msg = format.apply(this, rec[3]);
|
obj.msg = format.apply(this, rec[3]);
|
||||||
obj.v = VERSION;
|
obj.v = VERSION;
|
||||||
this.stream.write(JSON.stringify(obj) + '\n');
|
var str = JSON.stringify(obj) + '\n';
|
||||||
|
this.streams.forEach(function(s) {
|
||||||
|
if (s.level <= level) {
|
||||||
|
paul('writing log rec "%s" to "%s" stream (%d <= %d)', obj.msg, s.type,
|
||||||
|
s.level, level);
|
||||||
|
s.stream.write(str);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue