Extending console.log() to show trend reversal in an array of numbers

In the previous post I used a JavaScript proxy object to extend console.log() to show a small coloured flag next to the output, when called as console.log.red(), or console.log.orchid() – or any other CSS colour name. The code was a simple almost-one-liner:

console.log = new Proxy(console.log, {
  get(obj, prop) {
    return (...params) => console.log('%c ', `border-left: 5px solid ${prop}`, ...params);
  }
});

It’s unlikely that this extra level of names will ever clash with anything the browsers add by default. I can’t really see them adding extra properties to the console.log object rather than adding them directly to the parent console object itself. But just in case console.log.tomato() ever becomes a standard part of the developer tools, it might be better to extend the code a little to return any default property that is found, before falling back to our custom code if there’s no existing property with the specified name:

console.log = new Proxy(console.log, {
  get(obj, prop) {
    if (prop in obj) {
      return obj[prop];
    } else {
      return (...params) => console.log('%c ', `border-left: 5px solid ${prop}`, ...params);
    }
  }
});

Now that we’ve got a full if…else structure, however, we can begin to extend it further with an “else if” in the middle. That will allow us to create our own custom functions in the console.log namespace, while still falling back to the CSS colour version for all other function names. As the first example of this, I’m going to add a short function that takes an array of numbers and outputs up and down arrows to indicate whether the value goes up or down between each pair (it will output an “=” if they’re the same). This may seem a slightly pointless ability, but I often have to deal with large arrays of timestamps which should all be in ascending order (they’re the x-axis values for a graph). This new logging function, which I’ve implemented as console.log.direction(), makes it very easy to spot if there’s ever a problem with the timestamps not being sorted into the right order.

console.log = new Proxy(console.log, {
  get(obj, prop) {
    if (prop in obj) {
      return obj[prop];
    } else if (prop === 'direction') {
      return (arr) => {
        if (!Array.isArray(arr) || !arr.every((v) => typeof v === 'number')) {
          return console.error('You must pass an array of nunbers to console.log.direction');
        }

        const out = arr.reduce((acc, val, idx, arr) => {
          if (idx === 0) return acc;

          if (val > arr[idx-1]) {
            acc += '⬆';
          } else if (val < arr[idx-1]) {
            acc += '⬇';
          } else {
            acc += '=';
          }
          return acc;
        }, '%c');

        console.log(out, 'font-size: xx-large;', '\n', arr);
      };
    } else {
      return (...params) => console.log('%c ', `border-left: 5px solid ${prop}`, ...params);
    }
  }
});

The output is a series of up and down arrows (and equals signs) indicating the direction of change between values in the supplied array, followed by a copy of the array itself for further manual inspection, if needed. Here it is in Firefox on Linux:

Examples of output from my console.log.direction() function

I’ll grant that this specific function and use case is pretty niche. You’ll probably never need to spot a trend reversal in a long array of numbers, but that’s not really the point. This is just one example of how you can use the JavaScript proxy technique I described in the previous post to further extend the capabilities of the console log with custom functions that may help you to visualize tricky data sets in ways that a plain old textual log just doesn’t manage. If there’s a way to map your data into something that is still classed as “text” (after all, the arrows I used are just Unicode characters), but which helps to make it more comprehensible, perhaps you should think about adding your own custom console.log extensions to your codebase.

Leave a Reply

Your email address will not be published. Required fields are marked *