DEV Community

Cover image for Colorizing console logs with JS Proxy. Part 1
Glib Zaycev
Glib Zaycev

Posted on • Updated on

Colorizing console logs with JS Proxy. Part 1

Hey there!

Today we're going to talk about two things modern society cannot imagine its life without:

  • console logs
  • Proxy objects

Well, maybe not that dramatic, but this will be a short introduction to the subject with examples of the code I used in my work for real clients. This whole thing is a QoL feature; it's not about performance or optimal code, but it for sure made my life better during my work.

What you'll learn:

  • How to style console logs
  • What the JS Proxy class is
  • How to apply it to logging to create better outputs

Let's go!


Imagine you have a lot of logs. Like a lot. And you hate your life for the mess they create at your favorite browser console.

You want to distinguish them. To highlight test messages with green and system messages with blue, maybe you have state machines that log out their state (and you want them to be different from the other logs).

Well, the first thing you think of is styling the console logs.

Basic console log styling

Now it's better. We can go further and incapsulate it in the method to hide the wires.

export const systemLog = (msg) =>
  console.log(
    `%с ${msg}`, 
    `background: blue; color: white;`
  );
Enter fullscreen mode Exit fullscreen mode

But what if we need different styles for different messages? Well, we can create a generic method and a number of useful methods:

const log = 
  (msg, styles = ``) => console.log(`%c ${msg}`, styles);

const logSystem = (...args) =>
  log(args, `background: blue; color: white;`);
const logWarning = (...args) =>
  log(args, `background: orange; color: black;`);
Enter fullscreen mode Exit fullscreen mode

Now we can use it like that:

System and warning logs

It's already pretty useful as it is; we can specify 10–20 of those methods and use them around the app. It's not too elegant, but it's working and pretty clear.

However, we're going to do something much cooler and more flexible. Something to use any color and not care about background or text color combinations. For that, we need a proxy object.

Proxy

Before I explain the use case of proxies for our case, here's a brief explanation of what a proxy is in JS:

Proxy is a class that wraps around any object in JS and lets us intercept any calls made to that object.

It is used just as a usual object, but the difference is that it allows us to handle calls to any property of that object, modify it, or add any side effects.

Here's a quick example: let's create a simple object that will be the base for our proxy.

const everySringfieldCitizen = {
  callsHim: "Flanders",
};
Enter fullscreen mode Exit fullscreen mode

And after that, immediately wrap it with a fresh Proxy:

const homer = new Proxy (averageSringfieldCitizen, {
  get: (target, prop, receiver) => {
    return `Stupid sexy ${target[prop]}`;
  }
});
Enter fullscreen mode Exit fullscreen mode

Now that's what we have when we call the original object and the proxy made using it:
Sprinfield

What is going on here?

Proxy is a class that receives two objects in its constructor:

  • The base object, which basically can be whatever object, even just
{}
Enter fullscreen mode Exit fullscreen mode

fits

  • handler that works as a configuration for our proxy. It has a set of methods predefined by JS called "traps". 

We are not going to dive deep into traps and focus only on get one that will serve all our needs.

Now, the get trap is being called whenever you call any property, even an existing one, on the proxy object. It works as a callback that receives three arguments:

  • target: the object we used as the first argument in Proxy, the base object
  • property: the name of the called property is whatever is typed after the period (in my example, it is calls him").
  • receiver: the object of the proxy itself

Let's have another example with more generic data:

const quiz = {
  question: "How much is the fish?",
  answer: "Very much",
};

const handler = {
  get: (target, prop, receiver) => {
    return `42`;
  },
};

const proxiedQuiz = new Proxy(quiz, handler);
Enter fullscreen mode Exit fullscreen mode

Now let's call the properties:

We can intercept property calls and return whatever we need.

As you see, now any call of the proxy object always returns the same data.

We can clearly do whatever we want with object calls on proxies. Even assign or reassign keys and values to the object. We will use this knowledge to create dynamically set, colorful logging.

Using a proxy in the real world

We've learned a lot about proxies and are ready to apply this knowledge to create a useful, minimalistic tool for better logging.

For now, we're going to focus on the background color for our logs. We'll leave text as white by default, but in the next article, I'm going to show how you can automate picking up a contrast color for any background.

The core idea of dynamic color logging is to set the background with a property name. So this is the goal:

Each background is picked up dynamically.

We'll define base object as an empty object for now:

const baseObject = {};
Enter fullscreen mode Exit fullscreen mode

And this will be our handler:

const handler = {
  get: (target, prop, receiver) => {
    if (!(prop in target)) {
      target[prop] = (...args) =>
        console.log(
          `%c${args}`,
          `background: ${prop}; color: white;`
        );
    }

    return target[prop];
  },
};
Enter fullscreen mode Exit fullscreen mode

Finally, we're going to create a proxy just like we did before:

const log = new Proxy(baseObject, handler);
Enter fullscreen mode Exit fullscreen mode

It's ready now, but let's take a closer look:

  • When any property is called on the proxy object, we check if it exists on that object.
  • If it is, we just return whatever is stored in there (it is implied to be another instance of our logging callback with a different background color).
  • If it doesn't exist in the target object, we're going to create a new key-vvalue pair, where the key is the requested property name and the value is the callback with the console log.
  • After that, we just return that callback with target[prop].

And immediately call this callback with any arguments like that:

log.red("Apple");
Enter fullscreen mode Exit fullscreen mode

Now we're going to see:
If it's an apple, it's obviously red.

Let's take a closer look at our proxy:

Internals of proxy object

Before any interactions with the proxy, we had zero properties in it, and that was just a simple empty object. Now we've added a "red" property that has a callback as a value.

We are also using property name as the name of the CSS color for the console log background. So technically, this will work:

Any color definition will work.

You can see any called property will be stored as a new key.

Be careful with this; it can be dangerously beautiful.

Now you can see how it works. We start with an empty object, but we keep accumulating callbacks.

There are several questions that you could raise after all this:

  1. Why add a property at all?
  2. Why can't we just use color from properties to use it in the console log?
  3. How does it work with non-primitives like arrays and objects? (Hint: it does not work well, but that's a case to handle.)
  4. What's up with picking the right text color to not have a white-on-white situation?

All these are pretty valid points, and just to not make the article too long and boring, I am leaving them for the next parts.

In the second part, we'll work on points 1–3 from the list above and refactor what we already have (because there is always room for improvement, right?)

In the final, third part, we'll implement the functionality for smartly picking colors for the background and mess with CSS a bit.

And now lets recap what we've learnt today:

  1. Console logs could be styled with simple rule (%c in the start of the log text)
  2. What is JS Proxy
  3. How Proxy can be used in practive for everyday needs

Thanks for reading; hopefully that was helpful and see you soon in part 2!

Top comments (0)