Skip to content

Monitoring

Track rate limit performance and usage.

Available Metrics

When the MetricsPlugin is registered alongside RateLimitPlugin, the following Prometheus metrics are emitted automatically:

MetricTypeLabelsDescription
redisx_ratelimit_requests_totalCounterstatus (allowed | rejected)Rate limit check results

Prometheus Integration

Metrics are emitted automatically when MetricsPlugin is registered alongside RateLimitPlugin:

typescript
import { Module } from '@nestjs/common';
import { RedisModule } from '@nestjs-redisx/core';
import { RateLimitPlugin } from '@nestjs-redisx/rate-limit';
import { MetricsPlugin } from '@nestjs-redisx/metrics';

@Module({
  imports: [
    RedisModule.forRoot({
      clients: { host: 'localhost', port: 6379 },
      plugins: [
        new RateLimitPlugin({
          defaultAlgorithm: 'sliding-window',
          defaultPoints: 100,
          defaultDuration: 60,
        }),
        new MetricsPlugin(),
      ],
    }),
  ],
})
export class AppModule {}

Prometheus Queries

yaml
# Rejection rate
rate(redisx_ratelimit_requests_total{status="rejected"}[5m])

# Success rate
rate(redisx_ratelimit_requests_total{status="allowed"}[5m])
/ rate(redisx_ratelimit_requests_total[5m])

Grafana Dashboard

Query Examples

Rejection Rate:

yaml
rate(redisx_ratelimit_requests_total{status="rejected"}[5m])

Success Rate:

yaml
rate(redisx_ratelimit_requests_total{status="allowed"}[5m])
/ rate(redisx_ratelimit_requests_total[5m])

Alert Condition — High Rejection Rate (>10%):

yaml
(
  sum(rate(redisx_ratelimit_requests_total{status="rejected"}[5m]))
  /
  sum(rate(redisx_ratelimit_requests_total[5m]))
) > 0.1

Custom Logging

Rate limit lifecycle events are tracked automatically via MetricsPlugin and TracingPlugin. For custom logging, wrap the RateLimitService:

typescript
import { Injectable, Inject, Logger } from '@nestjs/common';
import { RATE_LIMIT_SERVICE, IRateLimitService, RateLimitResult } from '@nestjs-redisx/rate-limit';

@Injectable()
export class LoggingRateLimitService {
  private readonly logger = new Logger('RateLimit');

  constructor(
    @Inject(RATE_LIMIT_SERVICE) private readonly rateLimitService: IRateLimitService,
  ) {}

  async check(key: string, config?: any): Promise<RateLimitResult> {
    const result = await this.rateLimitService.check(key, config);
    this.logger.debug(
      `Check: ${key} - ${result.allowed ? 'ALLOWED' : 'REJECTED'} (${result.remaining} left)`,
    );
    return result;
  }
}

Log Aggregation

Structured Logging:

typescript
import { WinstonModule } from 'nest-winston';
import * as winston from 'winston';

@Module({
  imports: [
    WinstonModule.forRoot({
      transports: [
        new winston.transports.Console({
          format: winston.format.combine(
            winston.format.timestamp(),
            winston.format.json(),
          ),
        }),
      ],
    }),
  ],
})
export class AppModule {}

Log Example:

json
{
  "timestamp": "2025-01-28T12:00:00.000Z",
  "level": "warn",
  "message": "Rate limit exceeded",
  "context": "RateLimit",
  "key": "user:123",
  "endpoint": "/api/data",
  "retryAfter": 45
}

Alerting

Alert Rules

High Rejection Rate:

yaml
- alert: HighRateLimitRejections
  expr: |
    (
      sum(rate(redisx_ratelimit_requests_total{status="rejected"}[5m]))
      /
      sum(rate(redisx_ratelimit_requests_total[5m]))
    ) > 0.1
  for: 5m
  labels:
    severity: warning
  annotations:
    summary: "High rate limit rejection rate"
    description: "{{ $value | humanizePercentage }} of requests are being rate limited"

Custom Monitoring

Event Emitter

typescript
import { Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';

@Injectable()
export class RateLimitMonitor {
  constructor(private eventEmitter: EventEmitter2) {}

  onRateLimitCheck(key: string, result: RateLimitResult): void {
    this.eventEmitter.emit('rate-limit.check', { key, result });
  }

  onRateLimitExceeded(key: string, result: RateLimitResult): void {
    this.eventEmitter.emit('rate-limit.exceeded', { key, result });
  }
}

// Listen to events
@Injectable()
export class RateLimitListener {
  @OnEvent('rate-limit.exceeded')
  handleExceeded(payload: { key: string; result: RateLimitResult }): void {
    console.log(`Rate limit exceeded for ${payload.key}`);
    // Send to monitoring service, Slack, etc.
  }
}

Health Checks

typescript
import { Injectable, Inject } from '@nestjs/common';
import { HealthIndicator, HealthIndicatorResult } from '@nestjs/terminus';
import { RATE_LIMIT_SERVICE, IRateLimitService } from '@nestjs-redisx/rate-limit';

@Injectable()
export class RateLimitHealthIndicator extends HealthIndicator {
  constructor(
    @Inject(RATE_LIMIT_SERVICE)
    private rateLimitService: IRateLimitService,
  ) {
    super();
  }

  async isHealthy(): Promise<HealthIndicatorResult> {
    try {
      await this.rateLimitService.check('health-check', { points: 1, duration: 60 });
      return this.getStatus('rate-limit', true);
    } catch (error) {
      return this.getStatus('rate-limit', false, { message: (error as Error).message });
    }
  }
}

Dashboard Example

Create a monitoring endpoint:

typescript
@Controller('admin')
export class AdminController {
  constructor(
    @Inject(RATE_LIMIT_SERVICE)
    private rateLimitService: IRateLimitService,
  ) {}

  @Get('rate-limits')
  async getRateLimitStats() {
    return {
      topUsers: await this.getTopRateLimitedUsers(),
      rejectionRate: await this.getRejectionRate(),
      activeKeys: await this.getActiveKeys(),
    };
  }

  private async getTopRateLimitedUsers() {
    // Query metrics or Redis for top rate-limited keys
    // Return list of users hitting limits most often
  }
}

Real-Time Monitoring

WebSocket Updates

typescript
@WebSocketGateway()
export class RateLimitGateway {
  @WebSocketServer()
  server: Server;

  onRateLimitExceeded(key: string, result: RateLimitResult): void {
    this.server.emit('rate-limit-exceeded', {
      key,
      retryAfter: result.retryAfter,
      timestamp: new Date(),
    });
  }
}

Next Steps

Released under the MIT License.