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;
}),
);
}
}