Writing a wrapper for console.log for better control in JavaScript, Part I
Tags: javascript, programming, tutorial
Published: 2022-05-07 19:30:16

This is not about the different flavors of console logging, this is an attempt to create an organized way to deal with it, in multiple environments in a browser JavaScript app, be it in Angular, Vue, React, or BinCurse.

Why console?
------------

JavaScript is getting old, yet the best way to know what's going on, is a simple *console.log* in the right place. Read on Chrome Dev site [everything you need to know about console](https://developer.chrome.com/docs/devtools/console/api/). Disclaimer, I find no real value of most of them.

Working with complex frameworks that abstract vanilla JavaScript, we need to log everything in console during development, and that is the main "why".

What to console?
----------------

Here are some examples of things that need to be logged:

- Request and response of an HTTP call

- State update

- Initialization sequence

- Configuration ready

- Special third party logging events.

- Temporary attention messages

We also need a way to distinguish general logs, warnings and errors.

Where to console?
-----------------

We want to **disable logs upon environment change**, particularly production. Thus the `console.log` should not be called directly, rather wrapped in another function, that distinguishes environment.

The script
----------

Working backwards, we need a way to do this:

`ourFunction(object);`

We also want to call a message, but the message is optional:

`ourFunction(object, message);`

And we want to distinguish the different types of logging:

`ourFunction.log(object, message);`

`ourFunction.info(object, message);`

But we do not want to couple it too tightly, so we'll just pass the type as an optional parameter.

`ourFunction(object, message, type);`

So let's write ourFunction

```javascript
function ourFunction(o, message, type) {
  // switch type to have different flavors
  console.log(message, o);
}
```

The first enhancement is obviously the **function name**. I shall call it `_debug`. After years of using different names, and clashing with third party, and getting confused with the log, this name withstood the test of times.

Of course, you might argue, why not place it in its own module? You can. I refuse. I want something as dumb as `console.log`, even dumber.

The second enhancement is [adding format to the logs](https://developer.chrome.com/docs/devtools/console/format-style/), so they look like this:

![colorful log](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aco3j73pgslnpuj08o26.png)
 

`console.log("%c " + message, style);`

As for types, **the basic types** we want to log are general info (we can add new styles per request), errors and failures, traces, API call results, and warnings.

Third enhancement is to **detect a variable before logging,** a variable that is set to false in production. And no, this variable is not fed directly from environment, I'll tell you why later. This global constant could be:

`window._indebug`

Putting it together:

```javascript
function _debug(o, message, type) {
  if (window && window._indebug) {
    switch (type) {
      case 'e': // error
        console.log('%c ' + message, 'background: red; color: #fff', o);
        break;
      case 't': // trace error
        console.trace('%c ' + message, 'background: red; color: #fff', o);
        break;
      case 'p': // http response
        console.info('%c ' + message, 'background: #222; color: #bada55', o);
        break;
      case 'w': // warning
        console.warn('%c ' + message, 'background: #4f560f; color: #e6ff07', o);
        break;
      case 'gtm': // gtm events, special
        console.info('%cGTM: ' + message, 'background: #03dbfc; color: #000', o);
        break;
      default:
        console.info('%c ' + message, 'background: #d9d9d9; color: #a82868; font-weight: bold;', o);
    }
  }
}

// set _indebug to true in your environment file, could be anywhere
window._inebug = !process.env.production
```

Example of environment setting in Angular, is in `/environment.ts` file directly, and since it is in typescript and may run on SSR, check for window, and access it indirectly.

`window && (window['_indebug'] = true);`

The last enhancement is to add a seperate function, for "**attention**", that is needed during development. This looks different, and can easily be found and removed from code. Those are not supposed to exist, they are there to make us ditch the habit of ever "console.logging".

```javascript
function _attn(o, message) {
  if (window && window._indebug) {
    // use debug to filter in console window
    console.debug('%c ' + message, 'background: orange; font-weight: bold; color: black;', o);
  }
}
```

Typescript
----------

To make it available in typescript, we need to declare it in a **typing file**, could be `typings.d.ts`, that is included in the `tsconfig`. The script is still written in JavaScript, and added to files included in build, because, as you already know by know, it must stay dumb!

```typescript
// in typing.d.ts
declare function _debug(o: any, message?: string, type?: string): void;
declare function _attn(o: any, message?: string): void;
```

Using it
--------

The most popular use of custom console logging is intercepting fetch requests. One vanilla way to intercept all fetch calls, is to overwrite fetch. Let's do that:

```javascript
const oFetch = window.fetch;

window.fetch = async (...args) => {
  const response = await oFetch(...args);
  // let's console here, something like resource url and response
  _debug(response, 'GET ' + args[0], 'p');

  if (!response.ok) {
    // log errors
    _debug(response.statusText, 'Error', 'e');
    return Promise.reject(response);
  }
  return response;
};

// when used in client
fetch('https://saphire.sekrab.com/api/cats')
  .then((response) => response.json())
  .then((json) => {
    // use _attn for temporary logging
    _attn(json, 'after json call');
});
```

Running in [StackBlitz](https://js-console-wrapper.stackblitz.io/), it looks like this

![Console output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ucy7ekt96qi0foggb98l.png) 

Another example, is a call for GTM register event:

```javascript
function registerEvent(data){
  // ... call dataLayer push, then log
  _debug(data, 'register event', 'gtm');
}
```

A backdoor
----------

Why did we set a global variable? In an SPA, after first page load, we can set the **global variable to true in devTools console**, and gain access to these messages on production. It is safe, because it only logs already available information.

For other than SPA, or to make it set to true on load, we can gain access to the backdoor with a URL parameter, again, it's safe, and benign:

```javascript
const _insearch = window.location.search.indexOf('debug=true') > -1;
if (_insearch) {
    _indebug = true;
}
```

So now you can share a link with `?debug=true` in URL with your team, to gain access to the logged messages, for better insights.

Angular
-------

We can make great use of this function in Angular, logging state changes, HTTP interception, error handling, and more. Come back next week, or may be earlier. 😴

Did I make you look? There is no such thing as BinCurse.

### RESOURCES

-   [Format and style messages in the Console](https://developer.chrome.com/docs/devtools/console/format-style/)
-   [Console API reference](https://developer.chrome.com/docs/devtools/console/api/)
-   [Intercepting JavaScript Fetch API requests and responses](https://blog.logrocket.com/intercepting-javascript-fetch-api-requests-responses/)
-   [On StackBlitz](https://stackblitz.com/edit/js-console-wrapper?file=index.js)