nest-profiler-rabbitmq
Types and API of the RabbitMQ message collectors.
The package ships two independent modules: RabbitMqCollectorModule profiles the messages the
application consumes (one profile per @RabbitSubscribe delivery), and
RabbitMqPublishCollectorModule lists the messages it publishes in a RabbitMQ panel.
RabbitMqInfo
Payload of a message profile's entrypoint.data (entrypoint type: 'rabbitmq'). The module
registers a rabbitmq entrypoint type, so the profiler renders a dedicated RabbitMQ list table
- with its own filters: Delivery (first delivery / redelivered), Exchange and Handler
(both populated from the values actually captured) and a free-text Routing key - and a
Message detail tab, including the captured RabbitMQ
headersandpayload. The HTTP-status filters do not appear here, since a consumed message has no HTTP response.
Prop
Type
RabbitMqPublishEntry
One row of the RabbitMQ panel — a message the profiled request published through
AmqpConnection.publish. Collected by RabbitMqPublishCollectorModule.
Prop
Type
Public exports
import { RabbitMqCollectorModule } from '@eleven-labs/nest-profiler-rabbitmq';
import { RabbitMqContextAdapter } from '@eleven-labs/nest-profiler-rabbitmq';
import {
RABBITMQ_ENTRYPOINT_TYPE,
RABBITMQ_ENTRYPOINT_TYPE_DEF,
buildRabbitMqEntrypointType,
} from '@eleven-labs/nest-profiler-rabbitmq';
import { RabbitMqPublishCollectorModule } from '@eleven-labs/nest-profiler-rabbitmq';
import { RabbitMqPublishCollector } from '@eleven-labs/nest-profiler-rabbitmq';
import { RabbitMqDiscoverSource } from '@eleven-labs/nest-profiler-rabbitmq';
import type { RabbitMqCollectorModuleOptions } from '@eleven-labs/nest-profiler-rabbitmq';
import type { RabbitMqCollectorModuleAsyncOptions } from '@eleven-labs/nest-profiler-rabbitmq';
import type { RabbitMqInfo } from '@eleven-labs/nest-profiler-rabbitmq';
import type { RabbitMqPublishEntry } from '@eleven-labs/nest-profiler-rabbitmq';
import type { RabbitMqPublishCollectorModuleOptions } from '@eleven-labs/nest-profiler-rabbitmq';
import type { RabbitMqPublishCollectorModuleAsyncOptions } from '@eleven-labs/nest-profiler-rabbitmq';RabbitMqCollectorModuleOptions
interface RabbitMqCollectorModuleOptions {
/** Capture incoming RabbitMQ message headers (masked). Default: `true`. */
captureHeaders?: boolean;
/** Capture the deserialized message payload. Default: `true`. */
captureBody?: boolean;
/** Extra header names (lowercase) to mask, merged with the built-in list. */
maskHeaders?: string[];
/** What counts as a failed message. Default: the handler threw. */
error?: ProfilerErrorOptions;
}A consumed message carries no status code, so error rests on whether the handler threw — narrow it to the exceptions that matter, or take over with classify. See What counts as an error.
Gate the module per environment with ConditionalModule.registerWhen(RabbitMqCollectorModule.forRoot({ … }), isProfilerEnabled) — the same pattern used for every profiler module.
RabbitMqPublishCollectorModuleOptions
interface RabbitMqPublishCollectorModuleOptions {
/** Capture the headers passed to `publish()` (masked). Default: `true`. */
captureHeaders?: boolean;
/** Capture the published message. Default: `true`. */
captureBody?: boolean;
/** Extra header names (lowercase) to mask, merged with the built-in list. */
maskHeaders?: string[];
/** Depth / size caps on the captured payload, forwarded to `toSafeData`. */
payloadLimits?: SafeDataOptions;
/** A publish at or above this duration (ms) is tagged `slow`. Default: `50`. */
slowThreshold?: number;
/** Identical publishes repeated this many times are tagged `n-plus-one`. Default: `2`. */
nPlusOneThreshold?: number;
/** A profile publishing this many messages is tagged `chatty`. Default: `10`. */
chattyThreshold?: number;
/** Severities of the `slow` / `n-plus-one` / `chatty` tags. */
slowSeverity?: TagSeverity;
nPlusOneSeverity?: TagSeverity;
chattySeverity?: TagSeverity;
/** What counts as a failed publish. Default: `publish()` rejected. */
error?: EntryErrorOptions;
}A publish carries no status code either, so error rests on publish() having rejected; a message the channel buffered (accepted: false) is a warning, not a failure.
Registering the module is enough — publishers keep injecting AmqpConnection unchanged, and AmqpConnection.request() (RPC) is captured too, since golevelup routes it through publish.
ConditionalModule.registerWhen(RabbitMqPublishCollectorModule.forRoot(), isProfilerEnabled);Setup — consuming
Register the module in the application that consumes your messages (the same process that hosts the profiler):
import { Module } from '@nestjs/common';
import { ConditionalModule } from '@nestjs/config';
import { RabbitMqCollectorModule } from '@eleven-labs/nest-profiler-rabbitmq';
const isProfilerEnabled = (env: NodeJS.ProcessEnv) => env['PROFILER_ENABLED'] === 'true';
@Module({
imports: [
ConditionalModule.registerWhen(RabbitMqCollectorModule.forRoot(), isProfilerEnabled),
// your RabbitMQModule with @RabbitSubscribe handlers
],
})
export class AppModule {}Drive the options from ConfigService (or any provider) with forRootAsync(), gated per environment with ConditionalModule (recommended) — it resolves option values only, not enabled (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,
);Wire the core ProfilerModule once at the root — see Enabling and disabling the profiler. If a service injects TracerService directly, also register its ProfilerNoopModule fallback.
@golevelup/nestjs-rabbitmq and amqplib are optional peer dependencies - the ones you already
install to talk to the broker. Neither module needs changes to your @RabbitSubscribe handlers or
your publishers.
Setup — publishing
RabbitMqPublishCollectorModule stands alone: a publish-only API registers it and nothing else
from this package. It patches AmqpConnection.prototype.publish and records the calls made while a
profile is active, so a publish outside a profiled request (at bootstrap, from a cron job) records
nothing.
import { Module } from '@nestjs/common';
import { ConditionalModule } from '@nestjs/config';
import { RabbitMqPublishCollectorModule } from '@eleven-labs/nest-profiler-rabbitmq';
@Module({
imports: [
ConditionalModule.registerWhen(RabbitMqPublishCollectorModule.forRoot(), isProfilerEnabled),
// your RabbitMQModule.forRoot(...)
],
})
export class AppModule {}The panel is profile-scoped, so it works under any entrypoint: an HTTP request, a CLI command, or a
consumed message when RabbitMqCollectorModule is registered too — which is how the messages a
consumer republishes show up.
How a message profile is shaped
A consumed message has no HTTP request/response. The profiler synthesises a profile whose
entrypoint is { type: 'rabbitmq', data: RabbitMqInfo }:
| Field | Value |
|---|---|
entrypoint.type | rabbitmq |
entrypoint.data.exchange | The exchange (empty string for the default exchange) |
entrypoint.data.routingKey | The routing key |
entrypoint.data.headers | RabbitMQ message headers, sensitive ones masked (when captureHeaders is enabled) |
entrypoint.data.payload | The deserialized payload (when captureBody is enabled) |
response.statusCode | 200 on success, 500 when the handler threw |
The exchange, routing key, handler, redelivered flag and RabbitMQ consumer/delivery tags are kept on
entrypoint.data. Because the handler runs inside the profiler's CLS context, profile-scoped
collectors (HTTP client, database, …) capture the work it performs and contribute their own panels.
The Discover / RabbitMQ view
RabbitMqCollectorModule also registers a ProfilerDiscoverSource (RabbitMqDiscoverSource), which
contributes the Discover / RabbitMQ view — visible when
@eleven-labs/nest-profiler-routes is installed. It reports
what the application declared at startup, read from the resolved RabbitMQModule configuration: no
management-API call, no extra credentials, and it stays accurate while the broker is unreachable.
The topology, as sections above the handler list:
| Section | What it lists |
|---|---|
| Connections | Every declared connection with its broker URI (credentials masked), prefetch, channels and handler configs |
| Exchanges | Each exchange with its type, durability flags and arguments |
| Queues | Each queue with the binding that feeds it (← exchange (routing keys)), its flags and its x-… arguments |
| Exchange bindings | Each exchangeBindings entry with its pattern |
Queues no handler consumes — dead-letter, retry and delay queues — are listed too: they are part of the flow even though nothing subscribes to them.
The handlers, one entry per registration golevelup performs, each expanding to the full
subscription rather than just its exchange → routingKey locator:
| Group | What it lists |
|---|---|
| Subscription | queue, exchange, routingKey, the connection it runs on, its module-level handler config and channel |
| Bindings | Each bindings: [{ exchange, routingKey }] pair, when the handler binds across exchanges |
| Queue options | The queueOptions it asserts, with arguments spread one x-… key per row |
| Behaviour | allowNonJsonMessages, errorBehavior, batchOptions, a custom deserializer, … |
Two golevelup behaviours the view makes visible: a handler with no connection is registered on
every declared connection (listed once per connection — the classic multi-vhost trap), and a
handler whose name matches no entry in that connection's handlers map is not registered at
all, which the entry states in place of its description.