Writing a wrapper for console.log for better control in Angular, Part II
Tags: javascript, angular, tutorial, productivity
Published: 2022-05-12 08:51:22

Following up with our [console wrapper](https://garage.sekrab.com/posts/writing-a-wrapper-for-console-log-for-better-control-in-javascript-part-i), in this article I will use that specifically in an Angular app. The logging can be useful in `RxJS` piping, and error logging.

First, let's add the script to the **assets**, and declare it in `typings.d.ts` (as mentioned in the previous post).

```json
{
 "projects": {
   "main": {
     "architect": {
       "build": {
         "options": {
           "scripts": [
             // add the script however you wish
              {
                "input": "src/assets/js/console.js",
                "bundleName": "script"
              }
            ]
         }
       }
     }
   }
 }
}
```

This, on SSR run, will error on server side, we will fix it later.

> The final project is on [StackBlitz](https://stackblitz.com/edit/angular-console-wrapper?file=src/app/core/rxjsops.ts)

Angular error handling
----------------------

The default error handler in Angular logs out the unhandled error. We can override it with our own [ErrorHandler](https://angular.io/api/core/ErrorHandler) (provided in root app module):

```typescript
@Injectable()
export class OurErrorHandler implements ErrorHandler {
    handleError(error: any) {
      _debug(error, 'Unhandled Error', 'e');
    }
}
```

Use it with RxJS
----------------

We can create a [custom operator](https://medium.com/javascript-everyday/rxjs-custom-operators-f8b9aeab9631) that outputs the message in a pipe:

```typescript
export const debug = (message: string, type?: string): MonoTypeOperatorFunction<any>  => {
  return pipe(
    tap(nextValue => {
       _debug(nextValue, message, type);
    })
  );
};

// this is used with observables like this
obs$.pipe(
  debug('obs value')
);
```

In a previous article about [RxJS based state management in Angular](https://garage.sekrab.com/posts/rxjs-based-angular-state-management), we had a base class for state. We can update it with a debug statement to log all state progress. In `StateService`:

```typescript
export class StateService<T>  {
 protected stateList: BehaviorSubject<T[]> = new BehaviorSubject([]);
 stateList$: Observable<T[]> = this.stateList
    .asObservable()
    // pipe to debug the constructor used, like ParamState, or TransactionState
    .pipe(debug(this.constructor.name));
}
```

Now any updates to state will be logged in console.

Http interceptor
----------------

In devTools debug, network failed attempts are logged showing the full URL, but not successful attempts. Let's add the debug operator to the `HttpInterceptor` to log all data the way we like it, *that's the way it has to be* 😉

> **[method] [URL] [response body]**

```typescript
@Injectable()
export class OurInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    // adjust req if needed
    return next
      .handle(req)
      .pipe(
        // pipe with req information, method, and url with params
        debug(`${req.method} ${req.urlWithParams}`, 'p')
      )
    }
}
```

Let's rewrite the debug operator to handle the following:

-   if `nextValue` is of type `HttpResponse`, we need to log the **body**
-   we also need to filter out the useless value of type `Sent`. That's a an event set before every http response of little value, and it is `undefined` in other observables.

```typescript
export const debug = (message: string, type?: string): MonoTypeOperatorFunction<any>  => {
     return pipe(
        tap(nextValue => {
            let value = nextValue;

            if (nextValue instanceof HttpResponse) {
                // value is the body
                value = nextValue.body;
            }
            // just filter out the sent event
            if (nextValue && <any>nextValue.type !== HttpEventType.Sent){
                _debug(value, message, type);
            }

        })
    );
};
```

Now my log looks like this

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

We can also log the request `body` in case of `PUT` or `POST` events, in the Http interceptor:

```typescript
@Injectable()
export class OurInterceptor implements HttpInterceptor {
  intercept(
    req: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {

    // log request body, prefix to distinguish or create a new console type
    if (req.body) {
      _debug(req.body, `Request: ${req.method} ${req.urlWithParams}`, 'p');
    }

    return next.handle(adjustedReq).pipe(
     //...
    );
  }
}
```

The log looks like this

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

Let's push it up a notch. Let me log Http errors as well, using the `tap` extended call back, like this:

```typescript
export const debug = (message: string, type?: string): MonoTypeOperatorFunction<any> => {
  return pipe(
    tap({
      next: (nextValue) => {
        let value = nextValue;

        if (nextValue instanceof HttpResponse) {
          // value is the body
          value = nextValue.body;
        }
        // just filter out the sent event
        if (nextValue && <any>nextValue.type !== HttpEventType.Sent) {
          _debug(value, message, type);
        }
      },
      error: (error) => {
        // in error, log erros, check for pecific type of http response errors
        let value = error;
        if (error instanceof HttpErrorResponse) {
          value = `${error.status} ${error.message}`;
        }
        _debug(value, message, 'e');
      },
    })
  );
};
```

Before I show you the log, I went back to the [GTM Angular service](https://garage.sekrab.com/posts/gtm-tracking-service-in-angular) we built last week, and added a `_debug` statement as well:

```typescript
// GTM service updated from a previous post
export class GtmTracking {
  // ...
  public static RegisterEvent(track: IGtmTrack, extra?: any): void {
    let data = {
      event: track.event, gr_track: {
        source: track.source,
        ...extra
      }
    };
    // add a special type of log here
    _debug(data, 'register event', 'gtm');
    this.Push(data)
  }

  public static SetValues(values: any): void {
    let data = {
      gr_values: { ...values }
    };
    // and here:
    _debug(data, 'Set GA value', 'gtm');
    this.Push(data);
  }
}
```

In the `ErrorService`, we called the **GTM register event**, so now we end up with a console log looking good:

![console error log](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b9vturtjjrryotkvk12g.png)
 
The error object can be enhanced further, but that is a different subject, and a different post.

### Console filtered view

There is a reason why I `console.log` errors instead of `console.error`, and that is; the bad habit of filtering console into **info** messages, and **forget to switch back to all messages.** Attention span ever shrinking. This way we never miss out on errors, and we get to enjoy some sanity on retirement.

`_attn` was created on purpose to **replace the usual console.log**, the output of `_attn` calls is shown under console **verbose logs**, and they look bright, bright enough to remember to remove them before we build. If however we fail to remove them, that's okay, they won't be displayed in production anyway.

Does that affect performance? Not in 99% percent of apps (99% is like saying half of the world, not a real statistic).

![Filtered log](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xwvvmty0cyj51ornntq7.png)
 
Server platform logs
--------------------

The script used, is a JavaScript added to build, and injected in `index.html`. This is done on purpose because **I do not wish to `import` every time I use**. There is another perk out of this approach. First, let's fix server platform not running because those methods do not exist in **NodeJS**. This is done by defining those functions in NodeJS `global` object. The NodeJS **express server** should contain the following

```typescript
// fix NodeJs server running SSR
global._debug = function (o, message, type) {
  if(process.env.NODE_ENV !== 'production') {
    console.log(message, o);
  }
};
global._attn = function (o, message) {
 if(process.env.NODE_ENV !== 'production') {
    console.log(message, o);
  }
}
```

Now, we can display messages on server, after build, but before deployment. **Turn it on or off without rebuilding**. Context is king, separation is the new queen.

Thank you for reading this far. Let me know if I played any wrong hands.

### RESOURCES

-   [Angular Http Guide](https://angular.io/guide/http#intercepting-requests-and-responses)
-   [StackBlitz App (view devTools console)](https://angular-console-wrapper.stackblitz.io/)