Skip to content

Instantly share code, notes, and snippets.

@farhad-taran
Last active September 13, 2021 16:37
Show Gist options
  • Save farhad-taran/f9b8f2552902a2e86fb03fc40e9c3a6d to your computer and use it in GitHub Desktop.
Save farhad-taran/f9b8f2552902a2e86fb03fc40e9c3a6d to your computer and use it in GitHub Desktop.
RXJS, newrelic and recording failed or succeeded observables

recording events related to observables can be tricky and might cause a lot of duplication in code, the following snippet allows for handling such scenarios in a cleaner way:

import * as newrelic from 'newrelic';
import { Observable } from 'rxjs';
import { catchError, map } from 'rxjs/operators';

export function recordCustomEvent(
  isSuccess: boolean,
  eventName: string,
  body: { [keys: string]: boolean | number | string },
) {
  newrelic.recordCustomEvent(
    eventName,
      {
          success : isSuccess,
          ...body
      }
  );
}

export function recordNewrelicEvent(
  eventName,
  body: { [keys: string]: boolean | number | string },
) {
  return (source: Observable<any>) =>
    source.pipe(
      map((x) => {
        recordCustomEvent(true, eventName, body);
        return x;
      }),
      catchError((err) => {
        recordCustomEvent(false, eventName, body);
        throw err;
      }),
    );
}

you can use recordCustomEvent in a normal js function, and recordNewrelicEvent is when you are using rxJs, simply pass in the function in this way:

.pipe(
        map((response) => response.data),
        tap((res) => {
          //log
        }),
        catchError((err: Error | BaseError) => {
          //do some custom error handling
        }),
        recordNewrelicEvent('importantEvent', metaData),
      );

below is a es6 class implementation in nest which also allows you to select more attributes from the success or error responses:

import * as newrelic from 'newrelic';
import { Observable } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { PinoLogger } from 'nestjs-pino';
import { Inject } from '@nestjs/common';

export class Diagnostics {
  constructor(@Inject(PinoLogger) private readonly logger: PinoLogger) {}

  recordCustomEvent(
    isSuccess: boolean,
    eventName: string,
    meta: { [keys: string]: boolean | number | string },
  ) {
    newrelic.recordCustomEvent('giftCards', {
      eventName,
      success: isSuccess,
      ...meta,
    });
  }

  public record<T, E>(
    eventName: string,
    meta: { [keys: string]: boolean | number | string },
    errorPropsSelector: (
      err: E,
    ) => { [keys: string]: boolean | number | string } | undefined = () => ({}),
    successPropsSelector: (
      res: T,
    ) => { [keys: string]: boolean | number | string } | undefined = () => ({}),
  ) {
    return (source: Observable<T>) =>
      source.pipe(
        map((success) => {
          const data = {
            ...meta,
            ...successPropsSelector(success),
          };
          this.recordCustomEvent(true, eventName, data);
          this.logger.info(data, `${eventName} succeeded`);
          return success;
        }),
        catchError((err: E) => {
          const data = {
            ...meta,
            ...errorPropsSelector(err),
          };
          this.recordCustomEvent(false, eventName, data);
          this.logger.error(data, `${eventName} failed`);
          throw err;
        }),
      );
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment