Improve: RxJS Debugging
Tags: rxjs, typescript, javascript, webdev
Published: 2021-03-23 10:36:07

Reactive programming in Frontend Web Development makes sense as it's a paradigm focused on reacting to events. The user interacts with the DOM, which broadcasts events.

[RxJS](https://rxjs.dev/) is the Swiss Army Knife of Reactive Programming. It gives us a push-based pattern that allows us to respond to events created by the user.

😱 But there's one major problem. _Debugging RxJS Streams can be a nightmare!_

This article will introduce you to a new community-owned RxJS Operator aiming to help streamline debugging and provide the ability to do some additional robust debugging techniques.

## 🔥 The `debug()` Operator

What is this magical operator? It's the `debug()` operator _(you can find out more [here](https://github.com/Coly010/rxjs-debug-operator))_ and can be installed pretty easily:

NPM:  
`npm install rxjs-debug-operator`

Yarn:  
`yarn add rxjs-debug-operator`

It's also pretty easy to use! Just pipe it into any stream you already have set up:

```js
obs$.pipe(debug());
```

### 🤔 But what does it do?

In short, it helps you figure out what is going wrong, or right, with your RxJS Streams.  
By default, it'll simply log values to the console.

```js
const obs$ = of('my test value');
obs$.pipe(debug()).subscribe();

// OUTPUT
// CONSOLE
// my test value
```

However, it is so much more flexible than that and can be a powerful tool for diagnosing problems or even reporting critical errors in user pathways that could negatively impact the business!

### 💡 How can I take full advantage of it?

There are endless use-cases that you could employ to take advantage of the operator.

A common one is to see how the value changes throughout a series of operations in the stream.  
This can be made even easier to work with thanks to the handy `label` option:

```js
const obs$ = of('my test value');
obs$
  .pipe(
    debug('Starting Value'),
    map((v) => `Hello this is ${v}`),
    debug('Finishing Value')
  )
  .subscribe();

// OUTPUT
// CONSOLE
// Starting Value   my test value
// Finishing Value  Hello this is my test value
```

We'll look at some more specific use cases below!

## 🎸 Examples

Hopefully, these examples will come in useful and show the power of the operator!

### 📝 Simple logging

**Without Label**

```ts
const obs$ = of('my test value');
obs$.pipe(debug()).subscribe();

// OUTPUT
// CONSOLE
// my test value
```

**With Label**

```ts
const obs$ = of('my test value');
obs$.pipe(debug('Label')).subscribe();

// OUTPUT
// CONSOLE
// Label    my test value
```

You can even change what happens on each notification:

```ts
const obs$ = of('my test value');
obs$
  .pipe(
    debug({
      next: (value) => console.warn(value),
    })
  )
  .subscribe();

// OUTPUT
// WARNING
// my test value

const obs$ = throwError('my error');
obs$
  .pipe(
    debug({
      error: (value) => console.warn(value),
    })
  )
  .subscribe();

// OUTPUT
// WARNING
// my error
```

### 👩‍💻 Only logging in dev mode

You can also only log when in dev mode.

There are two ways you can do this, globally for all instances of `debug()` or locally, on a case-by-case basis.

**Globally**

```ts
import { environment } from '@app/env';

setGlobalDebugConfig({ shouldIgnore: !environment.isDev });

const obs$ = of('my test value');
obs$.pipe(debug()).subscribe();

// When environment.isDev === false
// Nothing is output
```

**Locally**

```ts
import { environment } from '@app/env';

const obs$ = of('my test value');
obs$.pipe(debug({ shouldIgnore: !environment.isDev })).subscribe();

// When environment.isDev === false
// Nothing is output
```

### ⏱️ Measuring the performance of a set of operators

Now for something potentially very cool. We can use the debug operator with custom `next` notification handlers to measure the performance/time of a set of piped operations to a Stream.

The example below shows it being used to measure the time it takes for a `switchMap` to a network request, however, the approach could be taken with any set of operators.

```ts
import { of } from 'rxjs';
import { ajax } from 'rxjs/ajax';
import { switchMap } from 'rxjs/operators';
import { debug } from 'rxjs-debug-operator';

const obs$ = of('my test value');

obs$
  .pipe(
    debug({
      next: () => console.time('Measure Perf'),
    }),
    switchMap((ar) =>
      ajax.getJSON('https://elephant-api.herokuapp.com/elephants/random')
    ),
    debug({
      next: () => console.timeEnd('Measure Perf'),
    })
  )
  .subscribe();

// OUTPUT
// Measure Perf     14.07653492ms
```

### 🖥️ Remote logging for increased observability

`rxjs-debug-operator` has a Global Config that also allows you to change the logger that is used.

As such, you could potentially use something like [Winston](https://github.com/winstonjs/winston) as your logger!

```ts
import { DebugLogger, setGlobalDebugConfig } from 'rxjs-debug-operator';
const winston = require('winston');

const sysLogger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports: [
    //
    // - Write all logs with level `error` and below to `error.log`
    // - Write all logs with level `info` and below to `combined.log`
    //
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' }),
  ],
});

const debugLogger: DebugLogger = {
  log: (v) => sysLogger.info(v),
  error: (e) => sysLogger.error(e),
};

setGlobalDebugConfig({ logger: debugLogger });

const obs$ = of('my test value');
obs$.pipe(debug()).subscribe();

// OUTPUT
// 'my test value' written to `combined.log`
```

## 🤩 Conclusion

This all started as a joke.

> How many times do developers `tap(console.log)` with RxJS?

But it has flourished into so much more! Hopefully, this article shows what is possible with such a simple operator, and I look forward to seeing what weird and wonderful things developers start to do with it!

If you notice any problems or have a feature request, open an issue over on the Repo [https://github.com/Coly010/rxjs-debug-operator](https://github.com/Coly010/rxjs-debug-operator)!

---

If you have any questions, please ask below or reach out to me on Twitter: [@FerryColum](https://twitter.com/FerryColum).