A multi-transport, peer-dependent logging library
Logule is a pretty, but heavily configurable logging utility for nodejs. It allows multiple transports (stdout + JSON filestream + emitted logs) as well as being configurable per user, per app and per module via localized config files.
Logging is a simple yet deceptively hairy problem. You don't want foreign modules to spam your app with needless messages, but you also want them to say something if you are passing down invalid arguments. You want logs to look pretty, but you also want a consistent style.
What you really want, is not simply configurability, but a hierarchy of configurability and suppression. You want to be able to:
as well as being able to configure how your:
Manipulating these settings should be super easy, as it's most useful during development and debug sessions where time is of the essence.
Finally, you should be able to get trace/debug messages from a module that's not behaving correctly, without spamming the shit out of people not using logule!
Logule strives to adhere these goals and beyond that has since 1.0 maintained a stable API. Features so far has been greatly improved via issues/pull requests contributions, so please follow this path if there is anything you feel deserves attention.
File scope usage; pass down the
module object at the start.
var log = require'logule'initmodule;log.error"this is an error message"log.warn"<- heed this"log.info"info %s %d %j" "message" 1 a: 2logdebug"this message has to be turned on";
To add a namespace to this module, add a second parameter to
log = require'logule'initmodule 'BUILD';logtrace"Trying to compile client.js";log.error"Failed";
Namespaces inherit from the call tree in order of registration. If your entry point required the 'BUILD' module, and this has a namespace, then 'BUILD' becomes the next namespace in the chain. When printing to
stdout there's a default limit of nesting printed out of 3.
In this case, the entry point that required 'BUILD' had no namespace. See Filtering Branches for a more detailed example of how namespaces nest.
Subs are copies of logger instances that do not save settings to the call tree. This allows you to have muted/unmuted logger instances inside a particular file without inadvertently muting or unmuting levels (resp.) from dependent modules.
var log = require'logule'initmodule 'myFile';var spammy = logsub'spammy'unmute'trace';// pass spammy logger to external/internal code
log.sub() will maintain the the original namespaces and mute settings of
log as well as config suppressions (so unmute does not override config
suppress). It can also optionally append one extra namespace to the ones existing, in this case, 'external' will be appended.
See Filtering Branches for a more detailed example of how subs are used.
There are 7 log levels (and one method getter) available available on
For debug info to help out during bad times. Suppressed by default, safe to leave in.
For temporary debug messages that you sprinkle liberally throughout your nasty functions. Prepends filename.js:lineNum to the message. Note these fetch new stacks at each call and are not meant to be used in production. They are always shown in the default config, so don't leave these in code.
var log = require'logule'initmodule 'broken';logdebug'dumping lines to console';logline;logline;
For info messages that are too common to see all the time. Suppressed by default, safe to leave in.
For messages that show events/standard progressions. These will be shown by default in any module you embed logule in when installed on a new box (i.e. without a custom config). Ensure you're not talking when you should not need to - consider `trace.
For warnings. Shown by default.
For errors. Shown by default in bold. Not meant to handle Error objects, just the logging please.
Disclaimer; this is mostly for fun. Find your own use case or suppress it. Note that zalgolization is only applied to stdout, not to the
Levels can be retrieved as single functions that do not chain by using this method.
Useful if you have a debug module that only needs
log.debug, so you can pass down a perhaps namespaced function to it.
var dbg = logget'debug';dbg"works like log.debug - but this function does not chain and expose other methods";
get() result obeys the mute API and config suppression entirely like the individual level methods.
Warning: while you simulate this with
log.debug.bind(log), this would chain and allow modules to break out of the constricted environment.
These methods are shortcuts for modifying the private list of muted levels.
Muting affects by default
filestream, but the config allows changing this to any combination of transports being affected.
Suppress logs for passed in methods.
Unmutes logs for passed in methods.
logmute'warn';var l2 = logsub'forModuleX'unmute'warn' 'info';log.warn'muted';l2.warn'works!';
NB: unmute does not override config suppression. It has lower precedence.
A convenience for muting all levels passed in, and unmuting all others.
logmuteOnly'debug' 'trace'; // unmutes everything except trace & debuglogmuteOnly; // muteOnly nothing === unmute everything
A convenience for unmuting all levels passed in, and muting the others.
logunmuteOnly'error'; // mutes everything except errorlogunmuteOnly; // unmuteOnly nothing === mute everything
Configuration of colors, style, date formatting, transports, setting mutability and global suppression levels are done via via config files. The default configuration file results in output looking like the images herein.
Configs are located via the confortable module. This module performs priority based config searches. In particular, it is used here with the following path priorities:
$HOME) Up to and including
Step 3 enables modules to bundle their own default config which can be overriden by apps by utilizing step 2.
The found config file is merged one level deep with the default config, so you don't have to include more in your config than what you disagree with.
Logule supports 3 transports:
These all have similar options in the config, but by default only
stdout is enabled.
NB: Logule does not strip colors. If you log pre-colored strings, those colors will show up in the other transports as well!
All transports have the following options:
suppress- list of levels to globally suppress from the transport
mutable- a boolean to indicate whether the mute API affects the transport
timestamp- an object/string to indicate timesta
All logs are by default written directly to
emitter has an
enabled attribute set to
true, logule will expose an
EventEmitter instance on
Then you can listen to
var logule = require'logule';var e = loguleemitter;var log = loguleinitmodule;eon'log'// plain replacement loggingconsole.logobj.timevalueOf + ' - ' + objlevel + ' - ' + objmessage;;
The types of the keys in
obj are as follows:
time : Datelevel: Stringnamespaces : Stringmessage : String
In the case of level being
line key is also available with the short location identifier string
line() typically outputs.
filestream has a
file attribute filled in (with a cwd relative path), this file will be appended to with JSON log messages (one message per line - so JSON.parse forEach line in file.split('\n') will work).
By default the individual JSON messages use the current format:
"time": "2012-11-08T11:08:25.092Z""level": "error""namespaces": "build""message": "message part, without stdout formatting"
time value may differ depending on the
timestamp config option.
"line" key is present if level is
"line" like in the EventEmitter.
The first four blocks in the default config describes the default style set used by the
stdout transport and are all of the form
levelName : fn where
fn is any function in the module dye. Functions can even be composed by delimiting them with a dot; e.g.
delimiters object contains the subtle default styling of the delimiters joining the optional timestamp, the log level, the optional namespaces and the message.
levels object contain the styling for the log levels. By default we only apply
bold to the critical messages.
messages object contain styling fot the messages (i.e. the result of
util.format(args..)). By default only zalgo gets message level formatting.
colors object contain the misc. styling used:
namespace- the default blue namespaces
timestamp- the default grey optional timestamp
callsite- the default green file.js:line prefix added by .line()
NB: Levels already listed in the
messages objects, can be disabled by overriding them in your personal/module's config with the value of
Timestamps can be configured via the "timestamp" key in the config which is overloaded as follows:
"none"- nothing prepended; log output starts at type, e.g. the
precision- prepends HH:MM:SS:MSS + delimiter via above + padded
dateMethod- prepends the result of any custom method on
object- full customization mode
Example of using the custom mode:
"timestamp":"date" : true"reverse" : false"delimiter" : "-""precise" : false
This prepends the date, in the form YYYY-MM-DD (i.e. normal non-reversed european style), and adds the timestamp after the date without precision (i.e. just
Most sensible methods on
toJSON- default for file
toLocaleTimeString- default for stdout
Note that the first two can be perfectly serialized/deserialized with
Date.parse and are thusly a good format for the filestream JSON transport.
suppress flag to globally turn all listed log methods into chaining no-ops.
Alternatively list the exceptions under
allow instead if you like to suppress most levels.
See the Branch based filtration section for more granular control.
debug messages are suppressed.
Controlling global levels is done via config files, but the levels not globally suppressed therein can temporarily muted/unmuted at any branch point and these settings will propagate down the call tree.
NB: The following techniques require your disired transport(s) to be
mutable in the config.
To get the most out of call tree filtration consider the following example of an application structure:
When just using mute/unmute on an instance returned directly by
init() logule will remember the call tree and apply the same rules to the ones further down the tree by default:
// a.jsvar l = require'logule'initmodule 'app'mute'info';var b = require'./b';// b.jsvar l = require'logule'initmodule;var c = require'./c';l.info'muted';lunmute'info';l.info'works';// c.jsvar l = require'logule'initmodule 'leaf';l.info'works';
With the following code,
a.js sets the an app default of no info messages, which is overridden by
b.js, so the unmute propagates to
c.js. Note that the
app namespace set in
a.js propagates down to both
c.js will show two namespaces:
leaf provided the config setting
nesting >= 2.
Note that any
unmute calls to a
sub() does not propagate to other files:
// a.js as above// b.jsvar l = require'logule'initmodulesubunmute'info';l.info'works';// c.jsvar l = require'logule'initmodule;l.info'still muted';
In short tree based log levels is the safe, overridable version of log levels. To enforce strict suppression of certain levels, the config file is the way to go.
Say you want to mute warnings in the file
c.js above. If you own the file, you easily just edit the first line to be:
// c.jsvar l = require'logule'initmodulemute'warn' 'info';
However, if you don't own the file, perhaps it's deep down in the npm hierarchy for instance, you can propagate more simply from
// b.jsvar l = require'logule'initmodulemute'warn' 'info'subunmute'warn' 'info';var c = require'./c';l.warn'unmuted, but down the call tree it is muted';
Here we mute main logger from
b.js (the one from
init), but unmute a
sub that will be used inside this file to preserve the same behaviour inside
Essentially the reverse process of Muting chatty modules, there are two cases, you own the file c.js (modify imports line to mute it):
// c.jsvar l = require'logule'initmodule 'leaf'unmute'info';l.info'works';
Or if you don't own the file (so unmute above in the hierarchy):
// b.jsvar l = require'logule'initmoduleunmute'info'submute'info';l.info'works';
This preserves muting of
b.js, but opens up for its descendants.
The ASNI color code wrapping and zalgolizer is provided by dye, wheras it used to rely on
colors. Dye does not introduce implicit global dependencies on
String.prototype, and provides more sensible zalgolizations.
When logging with
logule >=2 inside an npm published library/executable, the practice is to put
peerDependencies and NOT the normal
dependencies. This ensures all modules use the same code and thus logule can encapsulate everything needed to process ALL the logs an application uses. Logule's API is stable, so simply restricting to
"logule": "~2" will suffice.
"logule": "~1", bundling of separate copies per npm module was the standard and so logule then adopted the method of communicating with other copies via
process.logule to compensate for not having any one central piece of code where all logs went through. Ultimately, decisions were being made on behalf of the config so this worked well.
$ npm install logule
Install development dependencies
$ npm install
Run the tests
$ npm test
MIT-Licensed. See LICENSE file for details.