Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the ability to create levels (was: Introduce a createLevel call, document, add tests) #465

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,6 @@ log.levels(0, "info") // can use "info" et al aliases
log.levels("foo", WARN) // set stream named "foo" to WARN
```


## Level suggestions

Trent's biased suggestions for server apps: Use "debug" sparingly. Information
Expand All @@ -508,6 +507,34 @@ ever log at `trace`-level. Fine control over log output should be up to the
app using a library. Having a library that spews log output at higher levels
gets in the way of a clear story in the *app* logs.

## Creating new Levels

You can register a new level:

```js
var bunyan = require('bunyan');

bunyan.createLevel('notice', 35);
```

.. at which point that level is usable in (most of) the normal ways:


```js
log.level("notice") // set all streams to level NOTICE
log.notice("this will be logged");

log.level("warn") // set all streams to level WARN
log.notice("this will not be logged");
```

There are some caveats:

1. If you require DTrace support (see below), you need to create
new levels prior to instantiating Loggers; levels will work if
you do it the other way around, but DTrace won't be fired.
2. No constants are defined for dynamically created levels; you'll
need to track your level values yourself.

# Log Record Fields

Expand Down
104 changes: 89 additions & 15 deletions bin/bunyan
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ var OM_FROM_NAME = {
};


// Levels
// Default Levels
var TRACE = 10;
var DEBUG = 20;
var INFO = 30;
Expand All @@ -75,15 +75,71 @@ var levelFromName = {
'error': ERROR,
'fatal': FATAL
};

function colorFromLevel(level) {
if (level <= 10) {
return 'white';
}

if (level <= 20) {
return 'yellow';
}

if (level <= 30) {
return 'cyan';
}

if (level <= 40) {
return 'magenta';
}

if (level <= 50) {
return 'red';
}

return 'inverse';
}

var maxNameLength = 0;
var nameFromLevel = {};
var upperNameFromLevel = {};
var upperPaddedNameFromLevel = {};
Object.keys(levelFromName).forEach(function (name) {
var lvl = levelFromName[name];
var LEVELS = {};

function mkLevel(name, lvl) {
nameFromLevel[lvl] = name;
upperNameFromLevel[lvl] = name.toUpperCase();
upperPaddedNameFromLevel[lvl] = (
name.length === 4 ? ' ' : '') + name.toUpperCase();

if (name.length > maxNameLength) {
// pad, pad.
Object.keys(upperPaddedNameFromLevel).forEach(function(l) {
var paddedName = upperPaddedNameFromLevel[l];
for (var delta = name.length - maxNameLength; delta > 0; delta--) {
paddedName = ' ' + paddedName;
}
upperPaddedNameFromLevel[l] = paddedName;
});
maxNameLength = name.length;
}

if (name.length < maxNameLength) {
for (var delta = maxNameLength - name.length; delta > 0; delta--) {
name = ' ' + name;
}
}
upperPaddedNameFromLevel[lvl] = name.toUpperCase();

LEVELS[name.toUpperCase()] = lvl;

// if adding mappings..
if (!levelFromName[name]) {
levelFromName[name] = lvl;
}
}

Object.keys(levelFromName).forEach(function (name) {
var lvl = levelFromName[name];
mkLevel(name, lvl);
});


Expand Down Expand Up @@ -249,6 +305,13 @@ function printHelp() {
p(' -0 shortcut for `-o bunyan`');
p(' -L, --time local');
p(' Display time field in local time, rather than UTC.');
p(' --map "NUM=NAME"');
p(' Map specific error levels to names in the output. You');
p(' can pass this option multiple times, first use wins, e.g.');
p(' --map "35=notice" --map "35=security"');
p(' will map level 35 to the string \'NOTICE\'. You cannot');
p(' override the default mappings (i.e., 30 will always mean');
p(' INFO).');
p('');
p('Environment Variables:');
p(' BUNYAN_NO_COLOR Set to a non-empty value to force no output ');
Expand Down Expand Up @@ -505,6 +568,25 @@ function parseArgv(argv) {
if (parsed.outputMode === undefined) {
throw new Error('unknown output mode: "'+name+'"');
}
break;
case '--map':
var lvlArg = args.shift().trim();
var lvlIdx = lvlArg.indexOf('=');
var lvl = null;
var mapsTo = null;
if (lvlIdx !== -1) {
lvl = lvlArg.substring(0, lvlIdx).trim();
mapsTo = lvlArg.substring(lvlIdx+1).trim();
}

if (parseInt(lvl, 10).toString() !== lvl || !mapsTo) {
throw new Error('level argument must be NUMBER=NAME, e.g. "35=notice"');
}

if (!nameFromLevel[lvl]) {
mkLevel(mapsTo, lvl);
}

break;
case '-j': // output with JSON.stringify
parsed.outputMode = OM_JSON;
Expand Down Expand Up @@ -699,7 +781,7 @@ function isValidRecord(rec) {
}
var minValidRecord = {
v: 0, //TODO: get this from bunyan.LOG_VERSION
level: INFO,
level: LEVELS.INFO,
name: 'name',
hostname: 'hostname',
pid: 123,
Expand Down Expand Up @@ -815,15 +897,7 @@ function emitRecord(rec, line, opts, stylize) {

var level = (upperPaddedNameFromLevel[rec.level] || 'LVL' + rec.level);
if (opts.color) {
var colorFromLevel = {
10: 'white', // TRACE
20: 'yellow', // DEBUG
30: 'cyan', // INFO
40: 'magenta', // WARN
50: 'red', // ERROR
60: 'inverse', // FATAL
};
level = stylize(level, colorFromLevel[rec.level]);
level = stylize(level, colorFromLevel(rec.level));
}
delete rec.level;

Expand Down
112 changes: 83 additions & 29 deletions lib/bunyan.js
Original file line number Diff line number Diff line change
Expand Up @@ -239,11 +239,11 @@ var _warned = {};

function ConsoleRawStream() {}
ConsoleRawStream.prototype.write = function (rec) {
if (rec.level < INFO) {
if (rec.level < defaultLevels.INFO) {
console.log(rec);
} else if (rec.level < WARN) {
} else if (rec.level < defaultLevels.WARN) {
console.info(rec);
} else if (rec.level < ERROR) {
} else if (rec.level < defaultLevels.ERROR) {
console.warn(rec);
} else {
console.error(rec);
Expand All @@ -253,20 +253,22 @@ ConsoleRawStream.prototype.write = function (rec) {

//---- Levels

var TRACE = 10;
var DEBUG = 20;
var INFO = 30;
var WARN = 40;
var ERROR = 50;
var FATAL = 60;
var defaultLevels = {
TRACE: 10,
DEBUG: 20,
INFO: 30,
WARN: 40,
ERROR: 50,
FATAL: 60
};

var levelFromName = {
'trace': TRACE,
'debug': DEBUG,
'info': INFO,
'warn': WARN,
'error': ERROR,
'fatal': FATAL
'trace': defaultLevels.TRACE,
'debug': defaultLevels.DEBUG,
'info': defaultLevels.INFO,
'warn': defaultLevels.WARN,
'error': defaultLevels.ERROR,
'fatal': defaultLevels.FATAL
};
var nameFromLevel = {};
Object.keys(levelFromName).forEach(function (name) {
Expand Down Expand Up @@ -313,6 +315,57 @@ function isWritable(obj) {
}


/**
* Create a new logging level, and expose it. Name must be case-insensitive
* unique, and is normalized to lowercase. Level must not exist, and is
* normalized to the floor of its absolute value (e.g., -2.3 becomes 2, -32
* becomes 32).
*
* If you need DTrace support, create new levels *prior* to creating any
* loggers (levels created after Loggers will work, they just won't trigger
* DTrace).
*
* @api public
*/

function createLevel(name, level) {
if (typeof name !== 'string' || !name.trim()) {
throw new TypeError('name (string) is required');
}

name = name.toLowerCase();

if (!name.match(/^[a-z_]+$/)) {
throw new Error('level \'' + name + '\' is invalid');
}

if (levelFromName[name]) {
throw new Error('level \'' + name + '\' already exists');
}

if (Logger.prototype[name]) {
throw new Error('level \'' + name + '\' is invalid');
}

if (typeof level !== 'number') {
throw new TypeError('level (number) is required')
}

level = Math.floor(Math.abs(level));

if (nameFromLevel[level]) {
throw new Error('level \'' + name + '\' would duplicate \'' + nameFromLevel[level] + '\'');
}

levelFromName[name] = level;
nameFromLevel[level] = name;

Logger.prototype[name] = mkLogEmitter(level);

return Logger.prototype[name];
}


//---- Logger class

/**
Expand Down Expand Up @@ -551,7 +604,7 @@ util.inherits(Logger, EventEmitter);
Logger.prototype.addStream = function addStream(s, defaultLevel) {
var self = this;
if (defaultLevel === null || defaultLevel === undefined) {
defaultLevel = INFO;
defaultLevel = defaultLevels.INFO;
}

s = objCopy(s);
Expand Down Expand Up @@ -1061,7 +1114,7 @@ function mkLogEmitter(minLevel) {
str = this._emit(rec);
}

if (probes) {
if (probes && probes[minLevel]) {
probes[minLevel].fire(mkProbeArgs, str, log, minLevel, msgArgs);
}
}
Expand All @@ -1086,12 +1139,12 @@ function mkLogEmitter(minLevel) {
* arguments that are handled like
* [util.format](http://nodejs.org/docs/latest/api/all.html#util.format).
*/
Logger.prototype.trace = mkLogEmitter(TRACE);
Logger.prototype.debug = mkLogEmitter(DEBUG);
Logger.prototype.info = mkLogEmitter(INFO);
Logger.prototype.warn = mkLogEmitter(WARN);
Logger.prototype.error = mkLogEmitter(ERROR);
Logger.prototype.fatal = mkLogEmitter(FATAL);
Logger.prototype.trace = mkLogEmitter(defaultLevels.TRACE);
Logger.prototype.debug = mkLogEmitter(defaultLevels.DEBUG);
Logger.prototype.info = mkLogEmitter(defaultLevels.INFO);
Logger.prototype.warn = mkLogEmitter(defaultLevels.WARN);
Logger.prototype.error = mkLogEmitter(defaultLevels.ERROR);
Logger.prototype.fatal = mkLogEmitter(defaultLevels.FATAL);



Expand Down Expand Up @@ -1555,15 +1608,16 @@ RingBuffer.prototype.destroySoon = function () {

module.exports = Logger;

module.exports.TRACE = TRACE;
module.exports.DEBUG = DEBUG;
module.exports.INFO = INFO;
module.exports.WARN = WARN;
module.exports.ERROR = ERROR;
module.exports.FATAL = FATAL;
module.exports.TRACE = defaultLevels.TRACE;
module.exports.DEBUG = defaultLevels.DEBUG;
module.exports.INFO = defaultLevels.INFO;
module.exports.WARN = defaultLevels.WARN;
module.exports.ERROR = defaultLevels.ERROR;
module.exports.FATAL = defaultLevels.FATAL;
module.exports.resolveLevel = resolveLevel;
module.exports.levelFromName = levelFromName;
module.exports.nameFromLevel = nameFromLevel;
module.exports.createLevel = createLevel;

module.exports.VERSION = VERSION;
module.exports.LOG_VERSION = LOG_VERSION;
Expand Down
Loading