https://github.com/iqaicom/alert-logger
Smart alert aggregation for any destination. Exponential suppression, auto-resolution detection, and adapters for Discord, Slack, Sentry, and more.
https://github.com/iqaicom/alert-logger
Last synced: 2 months ago
JSON representation
Smart alert aggregation for any destination. Exponential suppression, auto-resolution detection, and adapters for Discord, Slack, Sentry, and more.
- Host: GitHub
- URL: https://github.com/iqaicom/alert-logger
- Owner: IQAIcom
- Created: 2026-04-05T16:06:03.000Z (2 months ago)
- Default Branch: main
- Last Pushed: 2026-04-06T11:29:46.000Z (2 months ago)
- Last Synced: 2026-04-07T11:02:27.878Z (2 months ago)
- Language: TypeScript
- Size: 159 KB
- Stars: 2
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
Awesome Lists containing this project
README
# @iqai/alert-logger
Smart alert aggregation for any destination. One call to log everywhere — Discord, Sentry, Slack, console, or your own adapter.
Stop drowning in alert storms. `@iqai/alert-logger` groups repeated errors using exponential suppression, sends periodic digests during sustained incidents, and notifies you when issues resolve — automatically.
## ✨ Features
- **Unified API** — `logger.error('msg', error, { fields })` routes to every configured adapter
- **Exponential suppression** — alerts fire at 1, 2, 4, 8, 16, 32, 64... then switch to periodic digests
- **Resolution detection** — get a "resolved" message when an error stops occurring
- **Error fingerprinting** — same bug from different requests groups automatically (strips IDs, timestamps, UUIDs)
- **Multi-channel routing** — route by severity level or custom tags to different channels
- **Adapter architecture** — Discord, Slack, Telegram, and Console built-in; or build your own
- **NestJS integration** — drop-in `@Global()` module with automatic exception filter
- **NextJS integration** — `instrumentation.ts` hook with automatic `onRequestError` handler
- **Per-environment config** — different suppression thresholds, levels, and ping rules for prod/staging/dev
- **Environment badges** — `[PROD]`, `[STG]`, `[DEV]` prefix on every alert so you never confuse environments
- **Request context (NestJS)** — auto-attaches request ID, method, path via `AsyncLocalStorage`
- **Rate-limit aware** — respects per-adapter limits, queues on failure, drains on recovery
- **Zero framework deps in core** — just `node:crypto` and `fetch`
## 📦 Install
```bash
npm install @iqai/alert-logger
# or
pnpm add @iqai/alert-logger
# or
yarn add @iqai/alert-logger
```
## 🚀 Quick Start
### Standalone (any Node.js project)
```ts
import { AlertLogger, DiscordAdapter, SlackAdapter, TelegramAdapter } from '@iqai/alert-logger'
const logger = AlertLogger.init({
adapters: [
new DiscordAdapter({
webhookUrl: process.env.DISCORD_WEBHOOK_URL,
channels: { critical: process.env.DISCORD_ONCALL_WEBHOOK },
mentions: { critical: ['<@&oncall-role>'] },
}),
new SlackAdapter({
webhookUrl: process.env.SLACK_WEBHOOK_URL,
mentions: { critical: ['<@U0123ONCALL>'] },
}),
new TelegramAdapter({
botToken: process.env.TELEGRAM_BOT_TOKEN,
chatId: process.env.TELEGRAM_CHAT_ID,
topics: { critical: 42, warning: 43, info: 44 },
}),
],
serviceName: 'my-service',
})
// Simple error — goes to all adapters with full context
logger.error('Payment failed', error)
// With metadata
logger.error('Payment failed', error, {
fields: { orderId: 'abc', amount: '$50' },
tags: ['billing'],
})
// Warning
logger.warn('Queue depth high', { fields: { depth: 150 } })
// Info
logger.info('Deployment complete', { fields: { version: '1.2.3' } })
```
### NestJS
```bash
npm install @iqai/alert-logger @nestjs/common @nestjs/config
```
```ts
// app.module.ts
import { AlertLoggerModule } from '@iqai/alert-logger/nestjs'
import { DiscordAdapter } from '@iqai/alert-logger'
@Module({
imports: [
AlertLoggerModule.forRoot({
adapters: [
new DiscordAdapter({ webhookUrl: process.env.DISCORD_WEBHOOK_URL }),
],
serviceName: 'backend',
}),
],
})
export class AppModule {}
```
```ts
// any.service.ts — AlertLoggerService is globally available
import { AlertLoggerService } from '@iqai/alert-logger/nestjs'
@Injectable()
export class PaymentService {
constructor(private readonly alert: AlertLoggerService) {}
async charge(order: Order) {
try {
await this.process(order)
} catch (error) {
this.alert.error('Payment failed', error, {
fields: { orderId: order.id, amount: order.total },
})
throw error
}
}
}
```
Unhandled 5xx errors are caught automatically by the built-in global exception filter — no extra code needed.
### NextJS
```bash
npm install @iqai/alert-logger next
```
```ts
// instrumentation.ts
import { createAlertLoggerHandler, captureRequestError } from '@iqai/alert-logger/nextjs'
import { DiscordAdapter } from '@iqai/alert-logger'
export function register() {
createAlertLoggerHandler({
adapters: [
new DiscordAdapter({ webhookUrl: process.env.DISCORD_WEBHOOK_URL }),
],
serviceName: 'frontend',
})
}
export { captureRequestError as onRequestError }
```
That's it. All server-side errors (API routes, server components, server actions) are captured automatically.
## 🧠 How Aggregation Works
When the same error fires repeatedly, the library doesn't spam your channel:
| Phase | Trigger | What gets sent |
|-------|---------|----------------|
| **Onset** | 1st occurrence | Full alert with stack trace, fields, tags |
| **Ramp** | 2nd, 4th, 8th, 16th, 32nd, 64th | Compact: `"Payment failed (x8 — 4 suppressed)"` |
| **Sustained** | >64 in window | Digest every 5min: `"x4,812 in last 5m"` |
| **Resolution** | 0 hits for 2min | `"Resolved: Payment failed — 12,847 total over 23m"` |
Errors are grouped by **fingerprint** — the library strips variable parts (IDs, timestamps, UUIDs, hex addresses) from the error message and hashes it with the top stack frames. Same bug, different request = same group.
## 🌍 Per-Environment Config
Same codebase, different behavior per environment. Dev won't bug you as much as prod:
```ts
AlertLogger.init({
adapters: [new DiscordAdapter({ webhookUrl: '...' })],
environment: process.env.NODE_ENV,
environments: {
production: {
levels: ['warning', 'critical'],
aggregation: { digestIntervalMs: 5 * 60_000 },
},
staging: {
levels: ['critical'], // only errors, no warnings
aggregation: { digestIntervalMs: 15 * 60_000 },
},
development: {
levels: ['critical'],
aggregation: { rampThreshold: 8, digestIntervalMs: 30 * 60_000 },
},
},
})
```
Every alert is prefixed with an environment badge (`[PROD]`, `[STG]`, `[DEV]`) so you never mistake staging for production.
## 📡 Multi-Channel Routing
Each adapter owns its routing. Route alerts to different channels/topics by severity or tags:
```ts
AlertLogger.init({
adapters: [
// Discord: route by level to different webhook URLs
new DiscordAdapter({
webhookUrl: process.env.DISCORD_DEFAULT_WEBHOOK,
channels: {
critical: process.env.DISCORD_ONCALL_WEBHOOK,
warning: process.env.DISCORD_WARNINGS_WEBHOOK,
},
tags: {
indexer: process.env.DISCORD_INDEXER_WEBHOOK,
},
mentions: {
critical: ['<@&oncall-role>'],
},
}),
// Slack: same pattern with Incoming Webhook URLs
new SlackAdapter({
webhookUrl: process.env.SLACK_DEFAULT_WEBHOOK,
channels: {
critical: process.env.SLACK_ONCALL_WEBHOOK,
},
mentions: {
critical: ['<@U0123ONCALL>'],
},
}),
// Telegram: route by level to forum topics
new TelegramAdapter({
botToken: process.env.TELEGRAM_BOT_TOKEN,
chatId: process.env.TELEGRAM_CHAT_ID,
topics: {
critical: 42,
warning: 43,
info: 44,
},
tags: {
indexer: 99,
},
mentions: {
critical: ['@oncall_dev'],
},
}),
],
})
```
## 🔌 Custom Adapters
Implement the `AlertAdapter` interface to send alerts anywhere:
```ts
import { AlertAdapter, FormattedAlert, AlertLevel } from '@iqai/alert-logger'
class PagerDutyAdapter implements AlertAdapter {
readonly name = 'pagerduty'
levels: AlertLevel[] = ['critical']
rateLimits() {
return { maxPerWindow: 60, windowMs: 60_000 }
}
async send(alert: FormattedAlert): Promise {
// POST to PagerDuty Events API
}
}
```
## ⚙️ Full Configuration
```ts
AlertLogger.init({
// Required — each adapter configures its own routing
adapters: [
new DiscordAdapter({
webhookUrl: '...',
channels: {}, // level → webhook URL
tags: {}, // tag → webhook URL
mentions: {}, // level → mention strings
}),
],
// Identity
serviceName: 'backend', // defaults to hostname
environment: 'production', // attached to every alert
// Aggregation tuning
aggregation: {
rampThreshold: 64, // switch from ramp to digest phase
digestIntervalMs: 5 * 60_000, // how often to send digests
resolutionCooldownMs: 2 * 60_000, // silence before "resolved"
},
// Per-environment overrides
environments: {
production: { levels: ['warning', 'critical'] },
staging: { levels: ['critical'] },
development: { levels: ['critical'], aggregation: { rampThreshold: 8 } },
},
// Reliability
queue: {
maxSize: 500, // retry buffer size
persistPath: null, // optional disk persistence
},
// Fingerprinting
fingerprint: {
stackDepth: 3, // stack frames to hash
normalizers: [], // custom regex replacements
},
})
```
## 🧩 Adapters Ecosystem
| Adapter | Package | Status |
|---------|---------|--------|
| Discord | `@iqai/alert-logger` (built-in) | Available |
| Slack | `@iqai/alert-logger` (built-in) | Available |
| Telegram | `@iqai/alert-logger` (built-in) | Available |
| Console | `@iqai/alert-logger` (built-in) | Available |
| Sentry | `@iqai/alert-logger-sentry` | Planned |
## 🤝 Contributing
Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
## 📄 License
MIT