Skip to main content

Alerting

Send entries to Slack, Discord or your own endpoint as they are collected. By default only exceptions trigger an alert.

Alerting is off unless you configure it.

NestLensModule.forRoot({
alerting: {
enabled: true,
webhooks: [
{
url: process.env.SLACK_WEBHOOK_URL,
type: 'slack',
},
],
},
});

That is enough to get a message in Slack whenever your application throws.

Options​

alerting​

OptionTypeDefaultDescription
enabledbooleanfalseTurns alerting on
webhooksAlertingWebhook[][]One or more destinations
timeoutMsnumber5000Per-delivery timeout

AlertingWebhook​

OptionTypeDefaultDescription
urlstring—Where to POST
type'slack' | 'discord' | 'generic''generic'Payload shape
eventsEntryType[]['exception']Entry types that trigger this webhook
throttleMsnumber60000Minimum gap between alerts sharing a dedup key

Payload shapes​

Slack — an incoming-webhook message:

{ "text": "🔭 *NestLens* — TypeError\nCannot read property 'id' of undefined — GET /orders" }

Discord — the same text under content:

{ "content": "🔭 *NestLens* — TypeError\nCannot read property 'id' of undefined — GET /orders" }

Generic — structured JSON for your own handler:

{
"event": "exception",
"entry": {
"id": 42,
"type": "exception",
"requestId": "b6f1…",
"title": "TypeError",
"description": "Cannot read property 'id' of undefined — GET /orders"
}
}

Throttling​

The same failure usually fires many times in a row. Each webhook keeps a dedup key per entry and refuses to send again until throttleMs has passed:

  • Exceptions are keyed by name and message, so a repeating TypeError with the same message is sent once a minute rather than once per request
  • Everything else is keyed by entry id, which means no deduplication in practice — use throttleMs: 0 to disable throttling explicitly

Multiple destinations​

Each webhook has its own event list and its own throttle:

NestLensModule.forRoot({
alerting: {
enabled: true,
webhooks: [
{
url: process.env.SLACK_WEBHOOK_URL,
type: 'slack',
events: ['exception'],
},
{
url: 'https://ops.internal/nestlens',
type: 'generic',
events: ['exception', 'job'],
throttleMs: 0,
},
],
},
});

Failure handling​

Alerting never interferes with your application:

  • Deliveries are fire-and-forget — request handling is not blocked
  • Each delivery has its own timeout (timeoutMs, default 5s)
  • A webhook that errors or times out is logged and ignored; the entry is still recorded normally

A dead webhook URL will not slow down or break your app.

Choosing what to alert on​

events accepts any entry type, or the word 'failures':

events: 'failures' // everything that went wrong, whatever its type
events: ['exception'] // default — exceptions only
events: ['exception', 'job'] // add background jobs, failed or not
events: ['exception', 'query'] // noisy; pair with a filter

'failures' is what a pager is usually for and takes a list plus an entry filter to say otherwise: exceptions, 5xx requests, failed operations, failed jobs, schedules, mail and notifications, and error logs. A 4xx is deliberately not among them — a malformed query is the caller's mistake, and a webhook anyone with curl can ring is a pager anyone with curl can ring.

For finer control — say, only exceptions from a specific route — use the entry filter to drop entries before they reach the collector. Anything filtered out never triggers an alert.

:::info On a GraphQL API ['exception'] covers resolvers too: the GraphQL watcher records what a resolver threw as an exception entry beside the operation, so this webhook fires for it. A malformed query is not among them — nobody threw, the caller made the mistake, and a webhook anyone with curl can trigger is a pager anyone with curl can ring.

Adding 'graphql' alerts on every operation rather than the failed ones; narrow it with a filter if that is what you want:

sampling: { rate: 0, always: ['exception', 'graphql'] },
filter: (entry) => entry.type !== 'graphql' || entry.payload.hasErrors === true,

sampling runs before filter, which is why the type has to be named in both. :::

Sending somewhere else​

type: 'slack' posts { "text": "..." }, which is also what Telegram's sendMessage accepts — pointing a Slack-shaped webhook at https://api.telegram.org/bot<token>/sendMessage?chat_id=<id> works with no adapter in between. Confirmed against a real bot. Anything that accepts a JSON body with a text field works the same way; type: 'generic' posts the whole entry when the receiver wants the detail.