npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@iqai/alert-logger

v1.1.0

Published

Smart alert aggregation for any destination — Discord, Sentry, Slack, console, or your own adapter

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 APIlogger.error('msg', error, { fields }) routes to every configured adapter
  • Rate-aware suppression — alerts ramp quickly, then switch to quieter periodic updates when an incident is clearly ongoing
  • 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 integrationinstrumentation.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

npm install @iqai/alert-logger
# or
pnpm add @iqai/alert-logger
# or
yarn add @iqai/alert-logger

🚀 Quick Start

Standalone (any Node.js project)

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

npm install @iqai/alert-logger @nestjs/common @nestjs/config
// 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 {}
// 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

npm install @iqai/alert-logger next
// 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 until rate/count handoff | Compact: "Payment failed (x8 — 4 suppressed)" | | Sustained | >64 total, or current rate crosses threshold after at least one ramp alert | Digest every 15min: "x37 since last update · x412 total" | | 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.

By default, the rate check uses a 1-minute sliding window and exits ramp early at 0.5 events/sec after the first ramp checkpoint has been sent.

🌍 Per-Environment Config

Same codebase, different behavior per environment. Dev won't bug you as much as prod:

AlertLogger.init({
  adapters: [new DiscordAdapter({ webhookUrl: '...' })],
  environment: process.env.NODE_ENV,
  environments: {
    production: {
      levels: ['warning', 'critical'],
      aggregation: { digestIntervalMs: 15 * 60_000 },
    },
    staging: {
      levels: ['critical'],           // only errors, no warnings
      aggregation: { digestIntervalMs: 15 * 60_000 },
    },
    development: {
      levels: ['critical'],
      aggregation: {
        rampThreshold: 8,
        rampExitRatePerSecond: 0.25,
        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:

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:

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<void> {
    // POST to PagerDuty Events API
  }
}

⚙️ Full Configuration

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,             // count-based handoff into sustained mode
    rampExitRatePerSecond: 0.5,    // early sustained handoff after a ramp alert
    rampExitRateWindowMs: 60_000,  // sliding window used for current-rate calculation
    digestIntervalMs: 15 * 60_000, // how often to send sustained updates
    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 for guidelines.

📄 License

MIT