DEV Community

Cover image for Improve: RxJS Debugging
Colum Ferry
Colum Ferry

Posted on

Improve: RxJS Debugging

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 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) 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:

obs$.pipe(debug());
Enter fullscreen mode Exit fullscreen mode

🤔 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.

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

// OUTPUT
// CONSOLE
// my test value
Enter fullscreen mode Exit fullscreen mode

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:

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
Enter fullscreen mode Exit fullscreen mode

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

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

// OUTPUT
// CONSOLE
// my test value
Enter fullscreen mode Exit fullscreen mode

With Label

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

// OUTPUT
// CONSOLE
// Label    my test value
Enter fullscreen mode Exit fullscreen mode

You can even change what happens on each notification:

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
Enter fullscreen mode Exit fullscreen mode

👩‍💻 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

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
Enter fullscreen mode Exit fullscreen mode

Locally

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
Enter fullscreen mode Exit fullscreen mode

⏱️ 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.

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
Enter fullscreen mode Exit fullscreen mode

🖥️ 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 as your logger!

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`
Enter fullscreen mode Exit fullscreen mode

🤩 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!


If you have any questions, please ask below or reach out to me on Twitter: @FerryColum.

Discussion (0)