Using console.group for hierarchical logging
Recursive functions are usually very hard to debug, especially with logging. They tend to produce a plethora of entries.
Consider a simple recursive function, for example, a merge sort:
function mergeSort(arr) {
if (arr.length < 2) {
return arr;
}
const middle = Math.floor(arr.length / 2);
const left = mergeSort(arr.slice(0, middle));
const right = mergeSort(arr.slice(middle));
const result = merge(left, right);
return result;
}
function merge(a, b) {
let i = 0, j = 0, result = [];
while (i < a.length || j < b.length) {
if (j >= b.length || a[i] < b[j]) {
result.push(a[i++]);
}else {
result.push(b[j++]);
}
}
return result;
}
console.log(mergeSort([2, 1, 3])); // [1, 2, 3]
Add some logging
Then add some logging to see what is going on inside the functions:
function mergeSort(arr) {
console.log(`Sorting ${arr}`);
if (arr.length < 2) {
console.log("Already sorted");
return arr;
}
const middle = Math.floor(arr.length / 2);
console.log(`Middle: ${middle}`);
const left = mergeSort(arr.slice(0, middle));
const right = mergeSort(arr.slice(middle));
const result = merge(left, right);
console.log(`Result: ${result}`);
return result;
}
function merge(a, b) {
console.log(`Merging ${a} with ${b}`);
let i = 0, j = 0, result = [];
while (i < a.length || j < b.length) {
if (j >= b.length || a[i] < b[j]) {
console.log(`Pushing ${a[i]}`);
result.push(a[i++]);
}else {
console.log(`Pushing ${b[j]}`);
result.push(b[j++]);
}
}
return result;
}
console.log(mergeSort([2, 1, 3])); // [1, 2, 3]
This logging generates the rather useless output of intertwined log entries:
Sorting 2,1,3
Middle: 1
Sorting 2
Already sorted
Sorting 1,3
Middle: 1
Sorting 1
Already sorted
Sorting 3
Already sorted
Merging 1 with 3
Pushing 1
Pushing 3
Result: 1,3
Merging 2 with 1,3
Pushing 1
Pushing 2
Pushing 3
Result: 1,2,3
The above output is hardly helpful in any real-world situation.
Meet console.group
For a better solution, use console.group(name)
and console.groupEnd()
! They add hierarchy to the log messages, so you instantly see which level emitted the message.
console.group(name)
works like console.log(message)
, but it opens a new group as well as outputs the message.
console.groupEnd()
closes the deepest group. group
and groupEnd
should always go in pairs.
If you see too many console messages, use console.groupCollapsed(name)
instead, which shows the group collapsed by default.
With just a few lines of code, you can add grouping.
function mergeSort(arr) {
console.group(`Sorting ${arr}`);
if (arr.length < 2) {
console.log("Already sorted");
console.groupEnd();
return arr;
}
const middle = Math.floor(arr.length / 2);
console.log(`Middle: ${middle}`);
const left = mergeSort(arr.slice(0, middle));
const right = mergeSort(arr.slice(middle));
const result = merge(left, right);
console.log(`Result: ${result}`);
console.groupEnd();
return result;
}
function merge(a, b) {
console.group(`Merging ${a} with ${b}`);
let i = 0, j = 0, result = [];
while (i < a.length || j < b.length) {
if (j >= b.length || a[i] < b[j]) {
console.log(`Pushing ${a[i]}`);
result.push(a[i++]);
}else {
console.log(`Pushing ${b[j]}`);
result.push(b[j++]);
}
}
console.groupEnd();
return result;
}
console.log(mergeSort([2, 1, 3])); // [1, 2, 3]
Running this code gives a useful log:
Sorting 2,1,3
Middle: 1
Sorting 2
Already sorted
Sorting 1,3
Middle: 1
Sorting 1
Already sorted
Sorting 3
Already sorted
Merging 1 with 3
Pushing 1
Pushing 3
Result: 1,3
Merging 2 with 1,3
Pushing 1
Pushing 2
Pushing 3
Result: 1,2,3
Browser support
Although not being a web standard, all the major browsers support grouping (IE >= 11).