JAVASCRIPT / ERRORS, STRICT MODE, AND DEBUGGING
Logging with console levels beyond log
Use console.debug, info, warn, error, assert, and group so tools can filter and route your messages by severity instead of one flat log stream.
What you will learn
- Choose debug, info, warn, or error by who needs the message, not by habit
- Filter DevTools by level to reveal or mute console.debug without editing code
- Predict which console calls hit stderr when you redirect a Node process's stdout
- Replace ad hoc counters and indentation with console.count and console.group
Understanding Logging with console levels beyond log
console.log is a message with no claim attached to it: the console prints the text and nothing else happens. console.debug, console.info, console.warn and console.error print the same text but tag the entry with a severity, and that tag is what tooling acts on. In Chrome DevTools the level selector filters by that tag, and console.debug lands in the Verbose bucket that is hidden at the default setting, so debug calls can stay in the source and be switched on when you need them. warn and error also get a colour, an icon, and an expandable stack trace, which is why a failure logged at the wrong level is easy to walk straight past.
Node keeps the same names but maps them onto two streams: log, info, debug, dir and table write to stdout, while warn, error, trace and assert write to stderr. Inside Node they are literally aliases — console.debug and console.info are console.log, console.warn is console.error — so expecting different colours or prefixes there will disappoint you; the difference is the file descriptor. That matters as soon as output is redirected: `node build.js > result.txt` sends only stdout to the file and leaves the warnings on your terminal, and CI runners, Docker and systemd all record the two streams separately. Reporting a failure with console.log buries it inside whatever consumes the program's real output.
The rest of the console API encodes structure rather than severity. console.group and console.groupEnd indent (and in a browser, collapse) everything logged between them, console.count keeps a per-label tally so you never add a counter variable, console.table lays an array of records out as columns, console.dir controls how deep an object is expanded, and console.trace prints the call path that reached the line. console.assert is the odd one out: it logs only when its first argument is falsy, which makes it a cheap way to write down an invariant. None of these change control flow — a red console.error is still just a printed line, and the function containing it carries on and returns whatever it was going to return.
// Run with: node config.js -- log/info/debug go to stdout, warn/error to stderr
function loadConfig(raw) {
console.debug('loadConfig called with', raw);
if (raw === undefined) {
console.error('loadConfig: nothing passed, falling back to defaults');
return { retries: 3 };
}
if (typeof raw.retries !== 'number') {
console.warn('loadConfig: retries missing, using 3');
raw = { ...raw, retries: 3 };
}
console.info('config ready:', raw.retries, 'retries');
return raw;
}
loadConfig({ retries: 5 });
loadConfig({ host: 'db.local' });
loadConfig();Every console method prints text, but the method you pick declares a severity that decides which stream, which filter, and which stack trace your message gets.
Worked examples
console.assert for invariants
Shows that a failed assertion prints one line and then lets the bad value through.
function withdraw(balance, amount) {
console.assert(amount > 0, 'amount must be positive, got %d', amount);
console.assert(amount <= balance, 'overdraw: %d > %d', amount, balance);
return balance - amount;
}
console.log(withdraw(100, 30));
console.log(withdraw(100, 250));
console.log(withdraw(100, -5));Example explained
Line 1Both assertions hold for (100, 30), so console.assert prints nothing and only the returned 70 appears.
Line 2console.assert prepends 'Assertion failed: ' to your message and fills in %d from the extra arguments.
Line 3The overdraw assertion fails, yet -150 is still printed: assert logs, it does not throw or return early.
Line 4For (100, -5) only the first assertion fails, because -5 <= 100 is still true, so one line is logged and 105 comes back.
Grouping and counting instead of hand-rolled bookkeeping
Uses console.group to indent a loop's output and console.count to tally iterations without a variable.
const items = [{ id: 'a', qty: 2 }, { id: 'b', qty: 0 }];
console.group('checkout');
for (const item of items) {
console.count('item seen');
if (item.qty === 0) {
console.warn(`skipping ${item.id}: qty is 0`);
continue;
}
console.info(`${item.id} x${item.qty}`);
}
console.groupEnd();
console.log('done');Example explained
Line 1console.group('checkout') prints its label at the current indent level, then indents everything logged afterwards.
Line 2console.count('item seen') keeps its own counter per label, so the numbers 1 and 2 come from the console, not from your code.
Line 3The indent applies to console.warn as well even though that line goes to stderr, because the indent lives on the console object.
Line 4console.groupEnd() removes one indent level, which is why 'done' is flush against the left margin again.
A threshold logger that keeps the real call site
Maps a minimum level onto the matching console methods and drops anything below it.
const LEVELS = ['debug', 'info', 'warn', 'error'];
function makeLogger(minLevel) {
const min = LEVELS.indexOf(minLevel);
const log = {};
LEVELS.forEach((level, i) => {
log[level] = i < min ? () => {} : console[level].bind(console, `[${level}]`);
});
return log;
}
const log = makeLogger('warn');
log.debug('cache key computed');
log.info('request received');
log.warn('retrying in 500ms');
log.error('gave up after 3 tries');Example explained
Line 1LEVELS is ordered by severity, so comparing indexes is enough to decide what to keep.
Line 2console[level] works for all four names because debug, info, warn and error exist in both browsers and Node.
Line 3bind returns the real console method with the prefix pre-applied, so DevTools still blames the caller's line rather than this file.
Line 4Levels below the threshold become an empty function, so nothing is formatted and nothing reaches either stream.
Important notes
Level filters hide messages at display time, not at call time: console.debug(buildSnapshot()) still calls buildSnapshot() while Verbose is off, so keep filtered logging cheap.
In a browser, logging an object stores a live reference, so expanding the entry later shows the object's current state; log a copy or the specific fields when you need the value as it was at that moment.
Common mistakes
Calling console.error inside a catch block and then returning normally: the red line looks like handling, but the caller receives undefined and fails later, far from the real cause.
Expecting console.assert to abort like an assert() from a test library: a false condition prints one line and the function keeps running with the invalid value.
Wrapping console.warn in a helper function without .bind, so DevTools reports your logger file as the source of every message and the source link no longer points at the real call site.
Try it yourself
Change, predict, then run
In a browser DevTools console, loop over five orders where two have qty 0, logging every order with console.debug and the empty ones with console.warn, all wrapped in console.group('orders') and console.groupEnd(). Then switch the level filter from Default to Verbose and back, and note exactly which lines the filter removes.
Open the JavaScript workspaceCheck your understanding
A Node script prints its result with console.log and its warnings with console.warn. You run it as `node build.js > result.txt`. What happens?
- Both kinds of line land in result.txt, because redirecting stdout captures everything the console prints
- The warnings stay on your terminal and result.txt contains only the console.log output
- The warnings stay on your terminal only if you also add 2>&1 to the command
- The warnings are dropped entirely, because Node hides console.warn unless the level is set to verbose
Show answer
`>` redirects file descriptor 1 only, and console.warn is an alias of console.error, which writes to file descriptor 2 — still attached to the terminal. The first option is tempting because console.debug and console.info really are aliases of console.log, but warn aliases error, not log; and 2>&1 would do the opposite of keeping warnings visible, since it merges stderr into the redirected file. The verbose answer confuses DevTools' display filter with Node's stream routing: Node has no level filter.