Observability Features
Use this skill when working with the observability features: rate limiting, telemetry visualization, alerting, and request tracing.
Feature Documentation
Comprehensive documentation is available in architecture/features/observability/:
| Document | Purpose |
|---|---|
README.md | User guide with visualization interpretation |
rate-limiting.md | Rate limiting architecture and extension |
visualization.md | Recharts components and testing patterns |
alerting.md | Alert channel architecture and extension |
configuration.md | Environment variables reference |
troubleshooting.md | Common issues and solutions |
1. Rate Limiting
Overview
Rate limiting uses @nestjs/throttler with a custom RateLimitGuard for per-endpoint configuration.
Key Files
src/server/modules/rate-limit/rate-limiter.guard.ts- Custom guardsrc/server/modules/rate-limit/rate-limit.config.ts- Per-endpoint rulessrc/server/modules/rate-limit/rate-limit.service.ts- Metrics tracking
Adding New Rate Limit Rules
// rate-limit.config.ts
export const rateLimitRules: RateLimitRule[] = [
{
pattern: '/api/your-endpoint',
limit: 30,
ttlSeconds: 60,
},
];
Testing Rate Limits
// Integration test
it('should rate limit after threshold', async () => {
for (let i = 0; i < limit; i++) {
await request(app.getHttpServer()).get('/api/endpoint').expect(200);
}
await request(app.getHttpServer()).get('/api/endpoint').expect(429);
});
2. Visualization Components
Overview
Telemetry visualization uses Recharts for charts and SSE for real-time updates.
Key Components
| Component | Purpose | Location |
|---|---|---|
TraceTrends | Line chart for trends | src/ui/containers/status/traces/components/TraceTrends.tsx |
EndpointBreakdown | Table of top endpoints | src/ui/containers/status/traces/components/EndpointBreakdown.tsx |
AlertsPanel | Active alerts display | src/ui/containers/status/traces/components/AlertsPanel.tsx |
TraceFilters | Filter controls | src/ui/containers/status/traces/components/TraceFilters.tsx |
Testing Recharts
Use the mockRecharts helper to mock Recharts components:
import { mockRecharts } from 'ui/test-utils/mockRecharts';
jest.mock('recharts', () => mockRecharts);
it('should render chart', async () => {
render(<TracesContainer />);
await waitFor(() => {
// ResponsiveContainer renders as a div with specific class
expect(document.querySelector('.recharts-responsive-container')).toBeInTheDocument();
});
});
Testing SSE Streams
Use MockEventSource for SSE testing:
import { MockEventSource } from 'ui/test-utils/mockEventSource';
beforeEach(() => {
global.EventSource = MockEventSource as unknown as typeof EventSource;
});
it('should handle SSE messages', async () => {
render(<TracesContainer />);
// Simulate SSE message
MockEventSource.simulateMessage('traces', {
type: 'trace.created',
data: { id: 1, path: '/api/test' },
});
await waitFor(() => {
expect(screen.getByText('/api/test')).toBeInTheDocument();
});
});
3. Alerting System
Overview
Alerts are triggered when metrics exceed thresholds. Alert channels implement the IAlertChannel interface.
Key Files
src/server/modules/traces/trace-alert.service.ts- Core alerting logicsrc/server/modules/traces/channels/*.channel.ts- Alert channelssrc/shared/types/trace.types.ts- Alert type definitions
Adding New Alert Channels
- Create channel class implementing
IAlertChannel:
// src/server/modules/traces/channels/slack.channel.ts
import { Injectable } from '@nestjs/common';
import { IAlertChannel, AlertPayload } from '../alert.types';
@Injectable()
export class SlackChannel implements IAlertChannel {
readonly name = 'slack';
async send(payload: AlertPayload): Promise<void> {
// Implement Slack webhook call
}
isEnabled(): boolean {
return !!process.env.SLACK_WEBHOOK_URL;
}
}
- Register in
TracesModule:
providers: [
AlertService,
LogChannel,
SlackChannel, // Add new channel
],
- Inject in
AlertService:
constructor(
private readonly logChannel: LogChannel,
private readonly slackChannel: SlackChannel,
) {
this.channels = [logChannel, slackChannel];
}
4. Request Tracing
Overview
Request tracing captures timing, status, and path information for all API requests.
Key Files
src/server/shared/interceptors/tracing.interceptor.ts- Captures request tracessrc/server/modules/traces/trace.service.ts- Trace storage and queriessrc/server/modules/traces/events.ts- Event name constantssrc/shared/types/trace.types.ts- Trace type definitions
Trace Data Flow
Request → TracingInterceptor → TraceService.recordTrace()
→ EventEmitter.emit(TRACE_EVENTS.TRACE_CREATED) → SSE to UI
5. Testing Patterns
Cron Job Testing
Use cronTestUtils for testing scheduled tasks:
import {
simulateCronExecution,
parseCronExpression,
} from 'server/test-utils/cronTestUtils';
it('should validate cron expression', () => {
const expression = '0 0 * * *'; // Daily at midnight
const result = parseCronExpression(expression);
expect(result.hour).toBe(0);
expect(result.minute).toBe(0);
});
it('should run cleanup on schedule', async () => {
await simulateCronExecution(service, 'runCleanup');
expect(mockRepo.delete).toHaveBeenCalled();
});
Integration Test Patterns
describe('Traces Integration', () => {
let app: INestApplication;
let dataSource: DataSource;
beforeAll(async () => {
const module = await Test.createTestingModule({
imports: [
TracesModule,
TypeOrmModule.forRoot({
type: 'better-sqlite3',
database: ':memory:',
entities: [TraceEntity, AlertEntity],
synchronize: true,
}),
],
}).compile();
app = module.createNestApplication();
await app.init();
});
it('should record traces', async () => {
// Make request
await request(app.getHttpServer()).get('/api/traces');
// Verify trace recorded
const traces = await request(app.getHttpServer())
.get('/api/traces')
.expect(200);
expect(traces.body.length).toBeGreaterThan(0);
});
});
6. Environment Variables
| Variable | Default | Description |
|---|---|---|
TRACE_RETENTION_DAYS | 7 | Days to keep trace data |
ALERT_COOLDOWN_MINUTES | 5 | Minutes between same alerts |
RATE_LIMIT_GLOBAL_LIMIT | 100 | Default requests per window |
RATE_LIMIT_GLOBAL_TTL | 60 | Window size in seconds |
See architecture/features/observability/configuration.md for complete reference.
7. Troubleshooting
Charts Not Rendering in Tests
Mock Recharts with the mockRecharts helper:
jest.mock('recharts', () => mockRecharts);
SSE Connection Issues in Tests
Use MockEventSource instead of real EventSource:
global.EventSource = MockEventSource as unknown as typeof EventSource;
Rate Limit Not Applying
Check that:
- The pattern matches your endpoint path
- The guard is applied to the controller/route
- The ThrottlerModule is imported in your module
Alerts Not Firing
Check that:
- Thresholds are configured correctly
- Cooldown period hasn't blocked the alert
- At least one channel is enabled
