Monitoring
Track rate limit performance and usage.
Available Metrics
When the MetricsPlugin is registered alongside RateLimitPlugin, the following Prometheus metrics are emitted automatically:
| Metric | Type | Labels | Description |
|---|---|---|---|
redisx_ratelimit_requests_total | Counter | status (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.1Custom 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
- Testing — Test rate limits
- Troubleshooting — Debug issues