.
ulog
started as Picolog, the smallest
possible logging library I could build that could act as a drop-in replacement
for the native console (which varied wildly across environments back then).
Compared to picolog
, ulog
adds some features from
debug that I missed. Even with these extra
features, ulog
is still very small, weighing in just over 1 kB minified
and gzipped.
In version 2, ulog
is more compatible with debug
than ever before.
The biggest change is that the logger objects returned from ulog()
are no
longer objects. They are functions, just like in debug
. But unlike the
functions returned by debug
, those from ulog
are also fully functional
logger objects will all methods we have on the native console and more.
For most code using ulog
v1 or debug
, upgrading to ulog
v2 should be
relatively painless. The only backward-incompatibility this version introduces
compared to ulog
v1 is the fact that the statement typeof log == 'object'
is no longer true for ulog
v2 loggers. For code
using ulog
as a replacement for debug
,
the upgrade path should be easier than for ulog
v1, because of the improved
compatibility in v2.
Please try out the latest v2 beta. To do so, you need to add a beta flag to the npm install command:
npm install --save ulog@beta
I do recommend trying out the beta, but if you just need the tried and tested v1, switch branch to the latest 1.x tag for the docs for that release and just don't add the beta flag to your npm install command.
I recommend installing ulog
with NPM:
npm install --save ulog
You should also be able to use e.g. Yarn etc.
If you want the file for the browser to include in your project yourself, you can download it from here.
- ulog.umd.js (~3kB, source)
- ulog.min.js (~2kB, minified)
I recommend using require
or import
in your projects and bundling a browser
version with Webpack.
my-module.js
var ulog = require('ulog')
var log = ulog('my-module')
// or, shorthand
var log = require('ulog')('my-module')
my-module.js
import ulog from 'ulog'
const log = ulog('my-module')
If you want, you can import ulog
with a script tag:
<script src="https://cdn.rawgit.com/download/ulog/2.0.0-beta.7/ulog.min.js"></script>
<script src="myscript.js"></script>
myscript.js
var log = ulog('my-module')
ulog
defines 6 logging methods, which correspond with available log levels:
log.error('This logs an ERROR message')
log.warn('This logs a WARN message')
log.info('This logs an INFO message')
log.log('This logs a LOG message')
log.debug('This logs a DEBUG message')
log.trace('This logs a TRACE message')
Note how these methods are all from the standard console API. Whenever possible, calls to these methods go directly to the corresponding native method.
On some systems,
debug()
is not natively available.
In addition, ulog v2 defines some additional methods that map to native functions and are there to increase compatibility with other popular logging libraries:
log.verbose('This logs a message at level LOG using method log()')
log.silly('This logs a message at level TRACE using method log()')
Calls to all these methods will be direct calls to the native logging functions if available and enabled. This means that there will be no code in the callstack that is not yours. This helps with debugging because browsers tend to show the file name and line number of the point the log call was made from. When logging using these methods, line numbers shown in the console will be from your code, not from some wrapper function.
In recent years, browsers have been adding features to ignore some parts of the call stack
In v2, the logger objects returned by ulog
are functions. We can call them to
log messages, like so:
log('This logs a DEBUG message')
By default, this will log a message at level DEBUG
. But we can change that
by specifying the desired log level as the first argument:
log('info', 'This logs an INFO message')
In the section about logging methods we discussed how calls to
these methods will be direct native calls whenever possible. This is great for our
line numbers. But this also means ulog
can't do any formatting on these log calls.
And formatting log messages can be a great feature.
So for v2, calls to the new logger function will be intercepted, so we can do some formatting if we want. No formatting is applied by default, but you can easily add your own formatter like this:
import ulog from 'ulog'
function myFormat(logger, method, args){
// add the logger name to the call
args.unshift(logger.name + ': ')
}
ulog.formats.push(myFormat) // from here on, our format is used
Any code calling the logger function will have the formatting applied:
import ulog from 'ulog'
const log = ulog('my-module')
log.level = log.DEBUG
log('Hello, World') // > 'my-module: Hello World'
Calls to native methods are not formatted:
log.info('Hello, World') // > 'Hello World'
Adding the option to add formatters came at a cost of ~50 bytes on the total file but I feel it is worth it.
ulog defines 6 logging levels, which correspond with the natively available logging functions:
log.ERROR // 1
log.WARN // 2
log.INFO // 3
log.LOG // 4
log.DEBUG // 5
log.TRACE // 6
In addition, there is a 7th level that completely disables all logging:
log.NONE // 0
To get or set the log level, we use the log.level
property:
if (log.level >= log.INFO) {
log.info('This message will be logged')
}
log.level = log.WARN
log.info('This info message will NOT be logged.')
log.warn('This warning message WILL be logged.')
log.level = log.NONE
log.error('Logging is completely disabled.')
In general, code should not set the log level directly, but instead rely on the host environment for the log level. See the sections below about the default log level, the related startup parameter and debug mode.
I've found that it makes sense to have different default log levels in the browser and in Node. In Node, logging is often the only UI we have available and we (the devs) are the only ones that will see that logging. In the browser, we have an alternative UI (the webpage itself), so logging will be less useful for normal users.
In Node, the log level defaults to log.INFO
. This allows you to use
INFO, WARN and ERROR when informing the user of stuff that happened.
With Picolog I found I had to resort to logging informational messages
at WARN because I wanted them to be visible with the default settings
and this did not feel right.
In the browser the log level defaults to log.WARN
. This means INFO
messages will be excluded, but for most users these messages won't be
relevant anyway and we can easily change the log level in the browser
using a query parameter in the URL or localStorage (see next section).
Changing the log level can be done in two ways:
- Programmatically, through the API
- Via a startup parameter
We can set the global log level directly on the ulog
function:
import ulog from 'ulog'
ulog.level = ulog.DEBUG
But this is not recommended for most code! It is much better to set the log level directly on a single module and leave the global settings alone.
We can set the level of a specific module in much the same way:
import ulog from 'ulog'
const log = ulog('my-module')
log.level = log.DEBUG
For most code, it is best to treat log.level
as if it was read-only,
and rely upon the environment to set the log level, using the startup
parameters described below.
We can set the initial global log level with a startup parameter. In Node we use an environment variable, whereas in the browser we use a querystring parameter in the url or a key in localStorage.
Set the environment variable LOG
to the desired log level.
$ LOG=info && node ./myapp.js
or, in Windows:
$ set LOG=INFO && node ./myapp.js
Add the parameter log
to the querystring of the page:
http://www.example.com/?log=debug
Both the uppercase and lowercase names of the log levels work, as well as their numerical values.
Add the key log
to the localStorage of the page:
localStorage.setItem('log', 'info')
then refresh the page.
Both the uppercase and lowercase names of the log levels work, as well as their numerical values.
In addition to setting the global log level and setting the log levels of individual loggers, you can also enable debug mode for a group of loggers. When in debug mode, the logger's individual log level will only be used if it is set to TRACE. Otherwise it will be ignored and the module will behave as if its level was set to DEBUG.
The ulog
function has 3 methods that allow us to control debug mode:
ulog.enable(str)
- Enables debug mode for the loggers listed instr
ulog.enabled(name)
- Tests whether the logger is currently in debug modeulog.disable()
- Disables debug mode for all loggers
The *
character may be used as a wildcard. Suppose for example your module has
loggers named "connect:bodyParser", "connect:compress" and "connect:session".
Instead of listing all three with connect:bodyParser,connect:compress,connect:session
,
you may simply use connect:*
.
You can also exclude specific loggers by prefixing them with a "-" character.
For example, *,-connect:*
would include all debuggers except those
starting with "connect:".
// given modules app, lib, connect:bodyParser, connect:compress and connect:session
ulog.enable('app,connect:*')
ulog.enabled('app') // true
ulog.enabled('lib') // false
ulog.enabled('connect:compress') // true
ulog.enable('app,connect:*,-connect:compress') // negation symbol means 'except'
ulog.enabled('app') // true
ulog.enabled('lib') // false
ulog.enabled('connect:compress') // false
ulog.disable()
ulog.enabled('app') // false
We can enable debug mode for some loggers using a startup parameter. On Node we use environment variables and on the browser we use querystring parameters or localStorage keys.
Set the environment variable DEBUG
to the string with logger names:
$ DEBUG=my-module && node ./myapp.js
or, in Windows:
$ set DEBUG=my-module && node ./myapp.js
Add the parameter debug
to the querystring of the page:
http://www.example.com/?
debug=my-module
Add the key debug
to the localStorage of the page:
localStorage.setItem('debug', 'my-module')
then refresh the page.
If you are using ulog
as a replacement for debug
, you may want to try the
new endpoint ulog/debug
introduced in v2:
import ulog from 'ulog/debug'
const log = ulog('my-module')
const other = ulog('other-module')
log('Message') // > '20:15 my-module Message'
other('Another message') // > '20:15 other-module Another message'
It is not doing that much at the moment but I plan to make this a compatibility endpoint for debug.
You can also use this as a Webpack alias, to make all modules that use debug
switch to ulog/debug
instead. Compatibility is not guaranteed though so your
mileage may vary:
{
resolve: {
alias: {
'debug': 'ulog/debug'
}
}
}
ulog supports all functions in the
NodeJS Console API, so you should be
able to use it as a polyfill in environments where there is no console
available (e.g. Nashorn):
// assuming you already made sure there is a `global` object
global.console = log;
console.info('Nashorn can do logging to!');
Since v2, the logger objects are functions and not objects, which may affect code testing for the existence of the console like:
if (typeof console == 'object') {
// use the console
}
Such tests will fail as of v2. I am hoping to solve this with a dedicated
ulog/polyfill
endpoint, but have not yet got around to implementing it.
In the mean time you can use some extend
function to create an object
from a logger function:
// assuming you have some `extend` function:
global.console = extend({}, log)
console.info('Nashorn can do logging to!');
ulog patches the different behavior
of console.assert
in Node compared to browsers. In ulog, assert
behaves
just like in the browsers and never throws.
The logging methods on the log
object that correspond to a log level which
is higher than the currently set level, are replaced by no-op methods. As such,
you generally don't have to worry about the performance overhead of leaving
the log statements in the production code. There is one exception to this rule
though. If preparing the message itself is a costly operation, you may want to
surround the log code with an if (log.level >= myLevel)
statement:
if (log.level >= log.INFO) {
var message = doLotsOfWorkToGenerateLogMessage();
log.info(message);
}
Add an issue in the issue tracker to let me know of any problems you find, or questions you may have.
Credits go to:
- Felix Geisendörfer from debuggable.com for kindly
giving up the
ulog
namespace on NPM. Thanks Felix! - TJ Holowaychuk for creating debug, which was a great inspiration for ulog.
Copyright 2018 by Stijn de Witt. Some rights reserved.
Licensed under the Creative Commons Attribution 4.0 International (CC-BY-4.0) Open Source license.