NestJS Profiler
Tutorials

RabbitMQ message profiling

Profile RabbitMQ messages consumed via @RabbitSubscribe and inspect routing key, payload and timing in the web profiler.

This tutorial shows how to profile RabbitMQ messages consumed with @golevelup/nestjs-rabbitmq. Each consumed message produces a profile - with a Message panel plus any HTTP, cache, or database activity the handler triggered - that appears in a dedicated RabbitMQ view at /_profiler.

Prerequisites

  • @eleven-labs/nest-profiler installed and configured
  • @golevelup/nestjs-rabbitmq consuming messages via @RabbitSubscribe

Step 1 - Install the package

pnpm add @eleven-labs/nest-profiler-rabbitmq@alpha

@golevelup/nestjs-rabbitmq and amqplib are optional peer dependencies - the ones you already use to consume messages.

Step 2 - Register the module

Add RabbitMqCollectorModule to the application that consumes your messages (the same process that hosts the profiler):

app.module.ts
import { Module } from '@nestjs/common';
import { ProfilerModule } from '@eleven-labs/nest-profiler';
import { RabbitMqCollectorModule } from '@eleven-labs/nest-profiler-rabbitmq';
import { ConditionalModule } from '@nestjs/config';
import { ProfilerModule } from '@eleven-labs/nest-profiler';
import { RabbitMQModule } from './rabbitmq.module';

const isProfilerEnabled = (env: NodeJS.ProcessEnv) => env['PROFILER_ENABLED'] === 'true';

@Module({
  imports: [
    ConditionalModule.registerWhen(ProfilerModule.forRoot({ isGlobal: true }), isProfilerEnabled),
    ConditionalModule.registerWhen(RabbitMqCollectorModule.forRoot(), isProfilerEnabled),
    RabbitMQModule,
  ],
})
export class AppModule {}

Step 3 - Keep your consumer unchanged

You do not change anything for profiling - write an ordinary @RabbitSubscribe handler:

narration.service.ts
import { Injectable } from '@nestjs/common';
import { RabbitSubscribe } from '@golevelup/nestjs-rabbitmq';
import type { ConsumeMessage } from 'amqplib';

@Injectable()
export class NarrationService {
  @RabbitSubscribe({
    exchange: 'articles.events',
    routingKey: 'published.*',
    queue: 'tts.narration',
  })
  async createGeneration(message: ArticleEvent, raw: ConsumeMessage): Promise<void> {
    // … your business logic …
  }
}

Step 4 - Inspect the profile

Publish a message (or trigger your normal flow) and open /_profiler, then pick RabbitMQ in the sidebar. Messages get their own view, with a filter bar of their own. Open one to find:

  • Message - exchange, routing key, handler, redelivered flag, consumer/delivery tags, headers and payload (the HTTP request/response tabs are hidden)
  • HTTP Client, Database, … - whatever the handler triggered

A handler that throws is captured too: its profile is marked failed (status 500) and the thrown error appears in the Exceptions tab.

Try it in the example app

The example API wires this end to end behind feature flags (off by default): creating a review publishes a review.created event that a @RabbitSubscribe consumer reacts to. Start the infrastructure and run it with the flags on:

docker compose up -d mongodb rabbitmq
FEATURE_MONGOOSE=true FEATURE_RABBITMQ=true pnpm example:dev

Then create a review and open /_profiler to see the consumed message profiled:

curl -X POST http://localhost:3000/api/v1/reviews \
  -H 'content-type: application/json' \
  -d '{ "productId": "1", "rating": 5, "comment": "Great product!", "author": "Alice" }'

Configuration

RabbitMqCollectorModule.forRoot({
  captureHeaders: true, // AMQP headers (sensitive ones masked)
  captureBody: true, // deserialized payload - disable if payloads are large
  maskHeaders: ['x-tenant-secret'], // merged with the built-in mask list
});

Gate it per environment with ConditionalModule.registerWhen(RabbitMqCollectorModule.forRoot({ … }), isProfilerEnabled) — the same pattern used for every profiler module.

Driving options from ConfigService

Use forRootAsync() to resolve captureBody/maskHeaders from ConfigService (or any provider), gated per environment with ConditionalModule — the recommended way (for the enabled flag, see Enabling and disabling the profiler):

import { ConditionalModule, ConfigService } from '@nestjs/config';

ConditionalModule.registerWhen(
  RabbitMqCollectorModule.forRootAsync({
    inject: [ConfigService],
    useFactory: (config: ConfigService) => ({
      captureBody: config.get<boolean>('profiler.rabbitmqCaptureBody') ?? true,
      maskHeaders: config.get<string[]>('profiler.maskHeaders') ?? [],
    }),
  }),
  isProfilerEnabled,
);

How it works

A consumed message has no HTTP request/response, so the module registers an IContextAdapter for the rmq context that creates a fresh profile per message and registers the rabbitmq entrypoint type (the RabbitMQ list view and Message detail tab). See How it works on the package page.

Powered & maintained by

On this page