Skip to content

Support background transactions and non-http contexts #2

@JesKingDev

Description

@JesKingDev

Overview

When using an asynchronous handler in my app, a new transaction is not registered.
This transaction should be visible in New Relic as a "background" transaction, like a cron or other non-http transaction would.

In my case, I'm using the @golevelup/nestjs-rabbitmq library to register a subscriber on a RabbitMQ topic.

import { RabbitSubscribe } from '@golevelup/nestjs-rabbitmq';
import { Injectable } from '@nestjs/common';

import type { ConsumeMessage } from 'amqplib';

@Injectable()
export class SubscriberService {
  @RabbitSubscribe({
    exchange: 'test',
    routingKey: 'test.topic',
    queue: 'test-queue',
  })
  async subscribeHandler(msg: unknown, amqpMsg: ConsumeMessage) {
    console.log(`Received message: ${JSON.stringify(msg)}`);
  }
}

This library is using amqp-connection-manager behind the scenes to connect a RabbitMQ instance and establishes a listener. The listener waits until a message arrives on the queue and then executes. When I use this library, I can see my web traffic registered, but the asynchronous service is not instrumented.

Suggested Fix

My current solution (before finding this library) is

//newrelic.interceptor.ts
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

const newrelic = require('newrelic');

@Injectable()
export class NewrelicInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {

    const contextType = context.getType();

    /* start a new relic background transaction for non-http requests such as async listeners */

    if (contextType !== 'http') {
      return newrelic.startBackgroundTransaction(context.getHandler().name, function () {
        const transaction = newrelic.getTransaction();

        return next.handle().pipe(
          tap(() => {
            return transaction.end();
          }),
        );
      });
    }

    return newrelic.startWebTransaction(context.getHandler().name, function () {
      const transaction = newrelic.getTransaction();

      return next.handle().pipe(
        tap(() => {
          return transaction.end();
        }),
      );
    });
  }
}
// main.ts
import { NewrelicInterceptor } from './newrelic.interceptor';

app.useGlobalInterceptors(new NewrelicInterceptor());

When a message is published to the configured topic, the handler executes. The execution is shown in New Relic as a "non-web" transaction.


PS. Awesome work on this library! Thank you!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions