You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+76-8Lines changed: 76 additions & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -71,6 +71,7 @@ On top of that:
71
71
-**Memory**: heap and RSS deltas per request, event-loop delay
72
72
-**A timeline** that lays all of the above in order, plus your own custom marks
73
73
-**Socket.io events** — inbound `@SubscribeMessage` handlers captured like a request, with all the SQL/Redis/HTTP they run (see below)
74
+
-**Background jobs** — BullMQ processors, microservice consumers and scheduled tasks captured like a request, with everything they run (see below)
74
75
75
76
## Socket.io events
76
77
@@ -82,6 +83,75 @@ Socket events appear in the **same list** as HTTP requests, tagged with a `WS` b
82
83
83
84
> Capture is on by default. For rare cases the automatic attachment can't reach (e.g. a single handler, or a gateway created outside the module scan), the `@TrackSocketEvents()` decorator is exported as an explicit opt-in — normally you won't need it.
84
85
86
+
## Background jobs
87
+
88
+
Background work is captured the same way as requests — no annotations, no per-queue wiring. Each run becomes its own profile with everything it did (SQL/Redis/HTTP, N+1, timeline, exceptions), plus the queue, job name, id, attempt, payload (redacted) and return value. Runs appear in the **Jobs** monitor.
89
+
90
+
**Setup depends on your process layout:**
91
+
92
+
-**Jobs run in the same process as the panel** (e.g. everything under one `npm start`): nothing to do — `DebugModule.forRoot()` is all you need, and jobs show up right next to requests and sockets.
93
+
-**Jobs run in a separate worker process** (the common production layout — an API process and a dedicated worker, e.g. a `worker.module.ts` bootstrapped by its own `main.ts`/`worker.ts`): a NestJS module tree is only instrumented in the process that actually loads it, so **`DebugModule.forRoot()` must be imported in the worker's own root module too** — not just the API's. Adding it there does not require an HTTP server; a standalone `NestFactory.createApplicationContext(WorkerModule)` bootstrap works fine. Once both processes load it, set `NEST_DEBUG_PANEL_STORAGE=redis` in **both** and one panel shows requests, sockets and jobs together — one env var on top of the import, using the Redis you already run for BullMQ (full setup just below in [Multi-process apps](#multi-process-apps-api--worker)).
94
+
95
+
What's captured automatically, with **zero extra code**:
96
+
97
+
| Library / pattern | Auto | How |
98
+
| --- | --- | --- |
99
+
|**`@nestjs/bullmq`** (`@Processor` + `WorkerHost`) | ✅ always | the processor class is wrapped at startup |
100
+
|**`@nestjs/microservices`** (`@MessagePattern` / `@EventPattern`) | ✅ always | consumers already flow through the interceptor |
101
+
|**`@nestjs/schedule`** (`@Cron` / `@Interval` / `@Timeout`) | ✅ always | decorated methods are wrapped at startup |
102
+
|**bee-queue / Agenda** (registered as a DI provider) | ✅ best-effort | the queue's registrar is wrapped so your handler is traced |
103
+
104
+
Turn it off with `jobs: false`, skip one processor with `@DebugIgnore()`, or drop payloads with `captureJobData: false`.
105
+
106
+
**The only case needing a line of code** is a worker created entirely outside Nest's DI (e.g. a bare `new Worker(...)`), or legacy `@nestjs/bull`. Annotate the handler with `@TrackJob()`, or wrap it with `trackJob()`:
Most production NestJS apps run the HTTP API and the BullMQ worker as **two separate processes** (e.g. `src/main.ts` bootstrapping `AppModule`, and a separate `src/worker.ts` bootstrapping `WorkerModule`). Getting this right needs **two things**, and missing either one is the most common reason the Jobs list looks empty:
124
+
125
+
**1. Import `DebugModule.forRoot()` in *every* process's own root module — including the worker's.** The panel only instruments the module tree it's actually loaded into. If your worker's root module (e.g. `WorkerModule`) never imports it, nothing in that process is ever wrapped, no matter what else you configure. This is true even when the worker has no HTTP server — `DebugModule.forRoot()` works the same inside a standalone `NestFactory.createApplicationContext(...)` bootstrap:
126
+
127
+
```ts
128
+
// worker.module.ts — the worker's OWN root module, separate from AppModule
129
+
import { Module } from'@nestjs/common';
130
+
import { DebugModule } from'nest-debug-panel';
131
+
132
+
@Module({
133
+
imports: [
134
+
DebugModule.forRoot(),
135
+
// ...your BullMQ / processor modules
136
+
],
137
+
})
138
+
exportclassWorkerModule {}
139
+
```
140
+
141
+
**2. Point both processes at the same Redis**, so the API's panel can see what the worker captured — each process still has its own in-memory store by default, and in-memory is never shared across processes. Set the same environment variable in **both**:
142
+
143
+
```bash
144
+
NEST_DEBUG_PANEL_STORAGE=redis # shared Redis store (reads NEST_DEBUG_PANEL_REDIS_URL, or REDIS_URL)
145
+
# (unset, or =memory) # default: in-memory, per-process
146
+
```
147
+
148
+
You already run Redis for BullMQ, so there's nothing new to install. With both pieces in place — the import in the worker's module, and the env var in both processes — one panel shows requests, sockets **and** the worker's jobs together.
149
+
150
+
- The connection comes from `NEST_DEBUG_PANEL_REDIS_URL` or `REDIS_URL`; if you configure Redis in parts instead, it's built from `REDIS_HOST` / `REDIS_PORT` / `REDIS_PASSWORD` / `REDIS_DB` — so most apps need no new variable at all.
151
+
-**Fail-safe:** if Redis or the URL is missing, it falls back to in-memory with a warning — it never breaks boot.
152
+
- The **worker doesn't need to serve HTTP** — it just writes captures to Redis; the API process serves the panel and reads them back.
153
+
-**Single-process app** (everything under one `npm start`)? You don't need any of this — the default in-memory store already shows jobs alongside requests.
154
+
85
155
## Database, Redis and HTTP capture is automatic
86
156
87
157
At startup the panel scans your app's providers and instruments what it recognizes. In most projects you install the package and queries just show up:
@@ -116,6 +186,8 @@ DebugModule.forRoot({
116
186
captureHttp: true,
117
187
captureLogs: true, // capture console.* emitted during a request (Logs monitor)
captureJobData: true, // capture the job payload (job.data), redacted
119
191
autoInstrument: true, // scan providers and hook them automatically
120
192
slowQueryThreshold: 100, // ms; queries at or above get flagged
121
193
slowRequestThreshold: 500, // ms; requests at or above get flagged
@@ -127,7 +199,7 @@ DebugModule.forRoot({
127
199
maxBodyLength: 65536, // bytes kept per captured body
128
200
getUser: (req) => (reqasany).user,
129
201
authorize: (req) =>true, // gate the dashboard, e.g. admins only
130
-
storage: undefined, // custom storage driver; default is in-memory
202
+
storage: undefined, // custom DebugStorage instance; mode is set via NEST_DEBUG_PANEL_STORAGE
131
203
plugins: [],
132
204
});
133
205
```
@@ -294,14 +366,10 @@ Build your own frontend or tooling on top of it if you like.
294
366
295
367
## Storage
296
368
297
-
The default store is an in-memory ring buffer that keeps the latest `maxRequests` profiles. Need persistence or sharing across instances? Implement the five-method `DebugStorage` interface and pass it in:
369
+
The default store is an in-memory ring buffer that keeps the latest `maxRequests` profiles — perfect for a single-process app.
-**Running an API + worker as separate processes?** Set `NEST_DEBUG_PANEL_STORAGE=redis` so one panel shows everything — see [Multi-process apps (API + worker)](#multi-process-apps-api--worker).
372
+
-**Bringing your own driver?** Implement the five-method `DebugStorage` interface (`save`/`find`/`list`/`clear`/`count`) and pass it as `storage` — a database or file store works the same way.
0 commit comments