We use cookies to enhance your experience on the site
CodeWorlds

Monitoring and Alerting - Early Warning Systems

Observation guard! Consul Caesar.js has entrusted you with the most important task in the application - being the eyes and ears of the entire legion. Monitoring and Alerting are our early warning systems that allow us to detect problems before they become a catastrophe!

In a cohort, a good lookout on the watchtower could spot approaching threats early - an enemy unit on the horizon, an incoming storm, or a hidden trap. Monitoring in NestJS works similarly - it continuously observes the application state and warns about problems.

Basic Monitoring System

Prometheus Metrics Integration

1// monitoring/prometheus.service.ts
2import { Injectable } from '@nestjs/common';
3import { register, Counter, Histogram, Gauge, collectDefaultMetrics } from 'prom-client';
4
5@Injectable()
6export class PrometheusService {
7 private readonly httpRequestCounter: Counter<string>;
8 private readonly httpRequestDuration: Histogram<string>;
9 private readonly activeConnections: Gauge<string>;
10 private readonly tributeOperations: Counter<string>;
11 private readonly cohortHealth: Gauge<string>;
12
13 constructor() {
14 // Enable default metrics (CPU, memory, etc.)
15 collectDefaultMetrics({ register });
16
17 // HTTP Request Counter
18 this.httpRequestCounter = new Counter({
19 name: 'legionaries_http_requests_total',
20 help: 'Total number of HTTP requests',
21 labelNames: ['method', 'route', 'status_code', 'cohort_section'],
22 registers: [register],
23 });
24
25 // HTTP Request Duration
26 this.httpRequestDuration = new Histogram({
27 name: 'legionaries_http_request_duration_seconds',
28 help: 'Duration of HTTP requests in seconds',
29 labelNames: ['method', 'route', 'cohort_section'],
30 buckets: [0.1, 0.5, 1, 2, 5, 10],
31 registers: [register],
32 });
33
34 // Active WebSocket Connections
35 this.activeConnections = new Gauge({
36 name: 'legionaries_active_websocket_connections',
37 help: 'Number of active WebSocket connections',
38 labelNames: ['cohort_name'],
39 registers: [register],
40 });
41
42 // Tribute Operations Counter
43 this.tributeOperations = new Counter({
44 name: 'legionaries_tribute_operations_total',
45 help: 'Total number of tribute operations',
46 labelNames: ['operation_type', 'tribute_type', 'legionaries_rank'],
47 registers: [register],
48 });
49
50 // Legion Health Status
51 this.cohortHealth = new Gauge({
52 name: 'legionaries_cohort_health_status',
53 help: 'Health status of cohort systems (0-100)',
54 labelNames: ['cohort_name', 'system_name'],
55 registers: [register],
56 });
57 }
58
59 // Increment HTTP request counter
60 incrementHttpRequests(method: string, route: string, statusCode: number, section: string) {
61 this.httpRequestCounter.inc({
62 method,
63 route,
64 status_code: statusCode.toString(),
65 cohort_section: section,
66 });
67 }
68
69 // Record HTTP request duration
70 recordHttpDuration(method: string, route: string, section: string, duration: number) {
71 this.httpRequestDuration.observe(
72 { method, route, cohort_section: section },
73 duration / 1000 // Convert to seconds
74 );
75 }
76
77 // Set active connections
78 setActiveConnections(cohortName: string, count: number) {
79 this.activeConnections.set({ cohort_name: cohortName }, count);
80 }
81
82 // Increment tribute operations
83 incrementTributeOperation(operationType: string, tributeType: string, legionariesRank: string) {
84 this.tributeOperations.inc({
85 operation_type: operationType,
86 tribute_type: tributeType,
87 legionaries_rank: legionariesRank,
88 });
89 }
90
91 // Update cohort health
92 updateLegionHealth(cohortName: string, systemName: string, healthScore: number) {
93 this.cohortHealth.set(
94 { cohort_name: cohortName, system_name: systemName },
95 Math.max(0, Math.min(100, healthScore))
96 );
97 }
98
99 // Get metrics for scraping
100 async getMetrics(): Promise<string> {
101 return register.metrics();
102 }
103
104 // Reset all metrics (useful for testing)
105 resetMetrics() {
106 register.resetMetrics();
107 }
108}

Monitoring Middleware

1// middleware/monitoring.middleware.ts
2import { Injectable, NestMiddleware } from '@nestjs/common';
3import { Request, Response, NextFunction } from 'express';
4import { PrometheusService } from '../monitoring/prometheus.service';
5
6@Injectable()
7export class MonitoringMiddleware implements NestMiddleware {
8 constructor(private readonly prometheusService: PrometheusService) {}
9
10 use(req: Request, res: Response, next: NextFunction) {
11 const startTime = Date.now();
12 const { method, originalUrl } = req;
13 
14 // Determine cohort section based on URL
15 const cohortSection = this.getLegionSection(originalUrl);
16 
17 // Override res.end to capture metrics when response finishes
18 const originalEnd = res.end;
19 res.end = function(...args: any[]) {
20 const duration = Date.now() - startTime;
21 const statusCode = res.statusCode;
22 
23 // Record metrics
24 this.prometheusService.incrementHttpRequests(method, originalUrl, statusCode, cohortSection);
25 this.prometheusService.recordHttpDuration(method, originalUrl, cohortSection, duration);
26 
27 // Log slow requests
28 if (duration > 5000) {
29 console.warn(`Slow request detected: ${method} ${originalUrl} took ${duration}ms`);
30 }
31 
32 // Call original end method
33 originalEnd.apply(this, args);
34 }.bind(this);
35
36 next();
37 }
38
39 private getLegionSection(url: string): string {
40 if (url.startsWith('/legion')) return 'legion_quarters';
41 if (url.startsWith('/tributes')) return 'tribute_hold';
42 if (url.startsWith('/cohort')) return 'cohort_bridge';
43 if (url.startsWith('/weapons')) return 'armory';
44 if (url.startsWith('/navigation')) return 'navigation_deck';
45 return 'main_deck';
46 }
47}

Alert System Architecture

Alert Manager Service

1// alerting/alert-manager.service.ts
2import { Injectable, Logger } from '@nestjs/common';
3import { ConfigService } from '@nestjs/config';
4import { EventEmitter2 } from '@nestjs/event-emitter';
5
6export interface Alert {
7 id: string;
8 severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
9 title: string;
10 description: string;
11 source: string;
12 timestamp: Date;
13 metadata?: Record<string, any>;
14 resolved?: boolean;
15 resolvedAt?: Date;
16}
17
18export interface AlertRule {
19 id: string;
20 name: string;
21 condition: (metrics: any) => boolean;
22 severity: Alert['severity'];
23 description: string;
24 cooldownMinutes: number;
25 enabled: boolean;
26}
27
28@Injectable()
29export class AlertManagerService {
30 private readonly logger = new Logger(AlertManagerService.name);
31 private readonly activeAlerts = new Map<string, Alert>();
32 private readonly alertHistory: Alert[] = [];
33 private readonly alertCooldowns = new Map<string, Date>();
34
35 constructor(
36 private readonly configService: ConfigService,
37 private readonly eventEmitter: EventEmitter2,
38 ) {
39 this.setupDefaultRules();
40 }
41
42 private alertRules: AlertRule[] = [];
43
44 private setupDefaultRules() {
45 this.alertRules = [
46 {
47 id: 'high_error_rate',
48 name: 'High Error Rate',
49 condition: (metrics) => metrics.errorRate > 5, // > 5% error rate
50 severity: 'HIGH',
51 description: 'Error rate exceeded acceptable threshold',
52 cooldownMinutes: 15,
53 enabled: true,
54 },
55 {
56 id: 'slow_response_time',
57 name: 'Slow Response Time',
58 condition: (metrics) => metrics.avgResponseTime > 2000, // > 2 seconds
59 severity: 'MEDIUM',
60 description: 'Average response time is too high',
61 cooldownMinutes: 10,
62 enabled: true,
63 },
64 {
65 id: 'memory_usage_critical',
66 name: 'Critical Memory Usage',
67 condition: (metrics) => metrics.memoryUsagePercent > 90,
68 severity: 'CRITICAL',
69 description: 'Memory usage exceeded 90%',
70 cooldownMinutes: 5,
71 enabled: true,
72 },
73 {
74 id: 'database_connections_high',
75 name: 'High Database Connections',
76 condition: (metrics) => metrics.dbConnections > 80,
77 severity: 'HIGH',
78 description: 'Database connection pool nearly exhausted',
79 cooldownMinutes: 10,
80 enabled: true,
81 },
82 {
83 id: 'tribute_theft_suspicious',
84 name: 'Suspicious Tribute Activity',
85 condition: (metrics) => metrics.tributeMovements > 10, // per minute
86 severity: 'HIGH',
87 description: 'Unusual tribute movement patterns detected',
88 cooldownMinutes: 30,
89 enabled: true,
90 },
91 ];
92 }
93
94 async evaluateAlerts(metrics: any) {
95 for (const rule of this.alertRules.filter(r => r.enabled)) {
96 try {
97 if (this.isInCooldown(rule.id)) {
98 continue;
99 }
100
101 if (rule.condition(metrics)) {
102 await this.triggerAlert(rule, metrics);
103 }
104 } catch (error) {
105 this.logger.error(`Error evaluating alert rule ${rule.id}:`, error);
106 }
107 }
108 }
109
110 private async triggerAlert(rule: AlertRule, metrics: any) {
111 const alertId = `${rule.id}_${Date.now()}`;
112
113 const alert: Alert = {
114 id: alertId,
115 severity: rule.severity,
116 title: rule.name,
117 description: rule.description,
118 source: 'AlertManager',
119 timestamp: new Date(),
120 metadata: {
121 ruleId: rule.id,
122 metrics,
123 threshold: rule.condition.toString(),
124 },
125 };
126
127 this.activeAlerts.set(alertId, alert);
128 this.alertHistory.push(alert);
129 this.alertCooldowns.set(rule.id, new Date(Date.now() + rule.cooldownMinutes * 60000));
130
131 this.logger.warn(` ALERT TRIGGERED: ${alert.title}`, alert);
132
133 // Emit event for other services to react
134 this.eventEmitter.emit('alert.triggered', alert);
135
136 // Send notifications based on severity
137 await this.sendNotifications(alert);
138
139 return alert;
140 }
141
142 private async sendNotifications(alert: Alert) {
143 const channels = this.getNotificationChannels(alert.severity);
144 
145 for (const channel of channels) {
146 try {
147 await this.sendToChannel(channel, alert);
148 } catch (error) {
149 this.logger.error(`Failed to send alert to ${channel}:`, error);
150 }
151 }
152 }
153
154 private getNotificationChannels(severity: Alert['severity']): string[] {
155 const channels = [];
156 
157 switch (severity) {
158 case 'CRITICAL':
159 channels.push('slack', 'email', 'sms', 'webhook');
160 break;
161 case 'HIGH':
162 channels.push('slack', 'email', 'webhook');
163 break;
164 case 'MEDIUM':
165 channels.push('slack', 'webhook');
166 break;
167 case 'LOW':
168 channels.push('webhook');
169 break;
170 }
171 
172 return channels;
173 }
174
175 private async sendToChannel(channel: string, alert: Alert) {
176 switch (channel) {
177 case 'slack':
178 await this.sendSlackNotification(alert);
179 break;
180 case 'email':
181 await this.sendEmailNotification(alert);
182 break;
183 case 'sms':
184 await this.sendSMSNotification(alert);
185 break;
186 case 'webhook':
187 await this.sendWebhookNotification(alert);
188 break;
189 }
190 }
191
192 private async sendSlackNotification(alert: Alert) {
193 const webhookUrl = this.configService.get('SLACK_WEBHOOK_URL');
194 if (!webhookUrl) return;
195
196 const color = this.getSeverityColor(alert.severity);
197 const emoji = this.getSeverityEmoji(alert.severity);
198 
199 const payload = {
200 text: `${emoji} Legionary Legion Alert: ${alert.title}`,
201 attachments: [
202 {
203 color,
204 fields: [
205 { title: 'Severity', value: alert.severity, short: true },
206 { title: 'Source', value: alert.source, short: true },
207 { title: 'Description', value: alert.description, short: false },
208 { title: 'Time', value: alert.timestamp.toISOString(), short: true },
209 ],
210 },
211 ],
212 };
213
214 // Send to Slack (implementation would use HTTP client)
215 console.log('Slack notification:', JSON.stringify(payload, null, 2));
216 }
217
218 private async sendEmailNotification(alert: Alert) {
219 const recipients = this.configService.get('ALERT_EMAIL_RECIPIENTS', '').split(',');
220 
221 const emailContent = {
222 to: recipients,
223 subject: ` Legionary Legion Alert: ${alert.title} [${alert.severity}]`,
224 html: `
225 <h2> Alert Triggered</h2>
226 <p><strong>Severity:</strong> <span style="color: ${this.getSeverityColor(alert.severity)}">${alert.severity}</span></p>
227 <p><strong>Title:</strong> ${alert.title}</p>
228 <p><strong>Description:</strong> ${alert.description}</p>
229 <p><strong>Source:</strong> ${alert.source}</p>
230 <p><strong>Time:</strong> ${alert.timestamp.toISOString()}</p>
231 <hr>
232 <p><em>This alert was generated by the Legionary Legion monitoring system.</em></p>
233 `,
234 };
235
236 console.log('Email notification:', emailContent);
237 }
238
239 private async sendSMSNotification(alert: Alert) {
240 const phoneNumbers = this.configService.get('ALERT_PHONE_NUMBERS', '').split(',');
241 
242 const message = ` LEGIONARY ALERT [${alert.severity}]: ${alert.title} - ${alert.description}`;
243 
244 console.log('SMS notification:', { phoneNumbers, message });
245 }
246
247 private async sendWebhookNotification(alert: Alert) {
248 const webhookUrls = this.configService.get('ALERT_WEBHOOK_URLS', '').split(',');
249 
250 const payload = {
251 event: 'alert.triggered',
252 alert,
253 cohort: 'Legio Nigra',
254 centurion: 'Caesar.js',
255 };
256
257 console.log('Webhook notification:', payload);
258 }
259
260 resolveAlert(alertId: string, resolvedBy?: string) {
261 const alert = this.activeAlerts.get(alertId);
262 if (!alert) {
263 throw new Error(`Alert ${alertId} not found`);
264 }
265
266 alert.resolved = true;
267 alert.resolvedAt = new Date();
268 alert.metadata = { ...alert.metadata, resolvedBy };
269
270 this.activeAlerts.delete(alertId);
271 
272 this.logger.log(`✅ Alert resolved: ${alert.title} by ${resolvedBy || 'system'}`);
273 this.eventEmitter.emit('alert.resolved', alert);
274
275 return alert;
276 }
277
278 getActiveAlerts(): Alert[] {
279 return Array.from(this.activeAlerts.values());
280 }
281
282 getAlertHistory(limit: number = 100): Alert[] {
283 return this.alertHistory.slice(-limit);
284 }
285
286 private isInCooldown(ruleId: string): boolean {
287 const cooldownEnd = this.alertCooldowns.get(ruleId);
288 return cooldownEnd && cooldownEnd > new Date();
289 }
290
291 private getSeverityColor(severity: Alert['severity']): string {
292 switch (severity) {
293 case 'CRITICAL': return '#FF0000';
294 case 'HIGH': return '#FF6600';
295 case 'MEDIUM': return '#FFAA00';
296 case 'LOW': return '#FFDD00';
297 default: return '#CCCCCC';
298 }
299 }
300
301 private getSeverityEmoji(severity: Alert['severity']): string {
302 switch (severity) {
303 case 'CRITICAL': return '';
304 case 'HIGH': return '';
305 case 'MEDIUM': return '';
306 case 'LOW': return '';
307 default: return '⚪';
308 }
309 }
310}

Real-time Dashboard System

Dashboard WebSocket Gateway

1// gateways/monitoring-dashboard.gateway.ts
2import {
3 WebSocketGateway,
4 SubscribeMessage,
5 MessageBody,
6 ConnectedSocket,
7 OnGatewayConnection,
8 OnGatewayDisconnect,
9 WebSocketServer,
10} from '@nestjs/websockets';
11import { Server, Socket } from 'socket.io';
12import { Injectable, Logger } from '@nestjs/common';
13import { OnEvent } from '@nestjs/event-emitter';
14import { AlertManagerService, Alert } from '../alerting/alert-manager.service';
15import { PrometheusService } from '../monitoring/prometheus.service';
16
17@Injectable()
18@WebSocketGateway({
19 namespace: '/monitoring',
20 cors: { origin: '*' },
21})
22export class MonitoringDashboardGateway implements OnGatewayConnection, OnGatewayDisconnect {
23 @WebSocketServer()
24 server: Server;
25
26 private readonly logger = new Logger(MonitoringDashboardGateway.name);
27 private connectedClients = new Map<string, Socket>();
28 private monitoringInterval: NodeJS.Timeout;
29
30 constructor(
31 private readonly alertManager: AlertManagerService,
32 private readonly prometheusService: PrometheusService,
33 ) {}
34
35 handleConnection(client: Socket) {
36 this.logger.log(` Dashboard client connected: ${client.id}`);
37 this.connectedClients.set(client.id, client);
38 
39 // Update connection count metric
40 this.prometheusService.setActiveConnections('monitoring_dashboard', this.connectedClients.size);
41 
42 // Send initial data
43 this.sendInitialData(client);
44 
45 // Start monitoring updates if first client
46 if (this.connectedClients.size === 1) {
47 this.startMonitoringUpdates();
48 }
49 }
50
51 handleDisconnect(client: Socket) {
52 this.logger.log(` Dashboard client disconnected: ${client.id}`);
53 this.connectedClients.delete(client.id);
54 
55 // Update connection count metric
56 this.prometheusService.setActiveConnections('monitoring_dashboard', this.connectedClients.size);
57 
58 // Stop monitoring updates if no clients
59 if (this.connectedClients.size === 0) {
60 this.stopMonitoringUpdates();
61 }
62 }
63
64 private async sendInitialData(client: Socket) {
65 const activeAlerts = this.alertManager.getActiveAlerts();
66 const recentAlerts = this.alertManager.getAlertHistory(20);
67 
68 client.emit('initial_data', {
69 activeAlerts,
70 recentAlerts,
71 cohortStatus: await this.getLegionStatus(),
72 systemMetrics: await this.getSystemMetrics(),
73 });
74 }
75
76 private startMonitoringUpdates() {
77 this.monitoringInterval = setInterval(async () => {
78 const metrics = await this.getSystemMetrics();
79 const cohortStatus = await this.getLegionStatus();
80 
81 this.server.emit('metrics_update', {
82 metrics,
83 cohortStatus,
84 timestamp: new Date(),
85 });
86 }, 5000); // Update every 5 seconds
87 }
88
89 private stopMonitoringUpdates() {
90 if (this.monitoringInterval) {
91 clearInterval(this.monitoringInterval);
92 }
93 }
94
95 @SubscribeMessage('get_cohort_status')
96 async handleGetLegionStatus(@ConnectedSocket() client: Socket) {
97 const status = await this.getLegionStatus();
98 client.emit('cohort_status', status);
99 }
100
101 @SubscribeMessage('get_system_metrics')
102 async handleGetSystemMetrics(@ConnectedSocket() client: Socket) {
103 const metrics = await this.getSystemMetrics();
104 client.emit('system_metrics', metrics);
105 }
106
107 @SubscribeMessage('resolve_alert')
108 async handleResolveAlert(
109 @MessageBody() data: { alertId: string; resolvedBy: string },
110 @ConnectedSocket() client: Socket,
111 ) {
112 try {
113 const resolvedAlert = this.alertManager.resolveAlert(data.alertId, data.resolvedBy);
114 
115 // Broadcast to all clients
116 this.server.emit('alert_resolved', resolvedAlert);
117 
118 client.emit('alert_resolve_success', { alertId: data.alertId });
119 } catch (error) {
120 client.emit('alert_resolve_error', { 
121 alertId: data.alertId, 
122 error: error.message 
123 });
124 }
125 }
126
127 @OnEvent('alert.triggered')
128 handleAlertTriggered(alert: Alert) {
129 this.server.emit('new_alert', alert);
130 }
131
132 @OnEvent('alert.resolved')
133 handleAlertResolved(alert: Alert) {
134 this.server.emit('alert_resolved', alert);
135 }
136
137 private async getLegionStatus() {
138 return {
139 name: 'Legio Nigra',
140 centurion: 'Caesar.js',
141 status: 'marching',
142 legion: 42,
143 health: 'excellent',
144 location: 'Mare Nostrum',
145 lastUpdate: new Date(),
146 };
147 }
148
149 private async getSystemMetrics() {
150 return {
151 cpu: Math.random() * 100,
152 memory: Math.random() * 100,
153 activeConnections: this.connectedClients.size,
154 errorRate: Math.random() * 5,
155 responseTime: 50 + Math.random() * 200,
156 };
157 }
158}

Advanced Business Metrics

Custom Business Metrics Service

1// services/business-metrics.service.ts
2import { Injectable, Logger } from '@nestjs/common';
3import { Counter, Histogram, Gauge, register } from 'prom-client';
4import { Cron } from '@nestjs/schedule';
5
6@Injectable()
7export class BusinessMetricsService {
8 private readonly logger = new Logger(BusinessMetricsService.name);
9
10 // Business metrics
11 private readonly tributesFoundCounter = new Counter({
12 name: 'legionaries_tributes_found_total',
13 help: 'Total number of tributes found by legionaries',
14 labelNames: ['legionaries_name', 'tribute_type', 'cohort_name'],
15 });
16
17 private readonly missionSuccessRate = new Gauge({
18 name: 'legionaries_mission_success_rate',
19 help: 'Success rate of legionaries missions',
20 labelNames: ['mission_type', 'cohort_name'],
21 });
22
23 private readonly resourceValueHistogram = new Histogram({
24 name: 'legionaries_tribute_value_distribution',
25 help: 'Distribution of tribute values',
26 buckets: [100, 500, 1000, 5000, 10000, 50000, 100000],
27 labelNames: ['tribute_type'],
28 });
29
30 private readonly activeLegionGauge = new Gauge({
31 name: 'legionaries_active_legion_members',
32 help: 'Number of active legion members',
33 labelNames: ['cohort_name', 'rank'],
34 });
35
36 private readonly cohortStatusGauge = new Gauge({
37 name: 'legionaries_cohort_status',
38 help: 'Legion status (1=marching, 0.5=garrisoned, 0=maintenance)',
39 labelNames: ['cohort_name'],
40 });
41
42 constructor() {
43 // Register metrics
44 register.registerMetric(this.tributesFoundCounter);
45 register.registerMetric(this.missionSuccessRate);
46 register.registerMetric(this.resourceValueHistogram);
47 register.registerMetric(this.activeLegionGauge);
48 register.registerMetric(this.cohortStatusGauge);
49 }
50
51 recordTributeFound(userName: string, tributeType: string, cohortName: string, value: number) {
52 this.tributesFoundCounter.inc({
53 legionaries_name: userName,
54 tribute_type: tributeType,
55 cohort_name: cohortName,
56 });
57
58 this.resourceValueHistogram.observe({ tribute_type: tributeType }, value);
59 
60 this.logger.log(` Tribute found: ${tributeType} worth ${value} denarii by ${userName}`);
61 }
62
63 updateMissionSuccessRate(missionType: string, cohortName: string, successRate: number) {
64 this.missionSuccessRate.set({ mission_type: missionType, cohort_name: cohortName }, successRate);
65 }
66
67 updateLegionCount(cohortName: string, rank: string, count: number) {
68 this.activeLegionGauge.set({ cohort_name: cohortName, rank }, count);
69 }
70
71 updateLegionStatus(cohortName: string, status: 'marching' | 'garrisoned' | 'maintenance') {
72 const statusValue = status === 'marching' ? 1 : status === 'garrisoned' ? 0.5 : 0;
73 this.cohortStatusGauge.set({ cohort_name: cohortName }, statusValue);
74 }
75
76 @Cron('0 */5 * * * *') // Every 5 minutes
77 async collectBusinessMetrics() {
78 try {
79 // Simulate fetching real business data
80 const cohorts = ['Legio Nigra', 'Ultio Reginae', 'Aquila Volans'];
81 const ranks = ['Centurion', 'Optio', 'Ballistarius', 'Legionary'];
82 
83 for (const cohort of cohorts) {
84 // Update legion counts
85 for (const rank of ranks) {
86 const count = Math.floor(Math.random() * 10) + 1;
87 this.updateLegionCount(cohort, rank, count);
88 }
89 
90 // Update cohort status
91 const statuses = ['marching', 'garrisoned', 'maintenance'];
92 const status = statuses[Math.floor(Math.random() * statuses.length)];
93 this.updateLegionStatus(cohort, status as any);
94 
95 // Update mission success rates
96 const missionTypes = ['tribute_hunt', 'trade', 'exploration', 'battle'];
97 for (const missionType of missionTypes) {
98 const successRate = Math.random();
99 this.updateMissionSuccessRate(missionType, cohort, successRate);
100 }
101 }
102 
103 this.logger.log(' Business metrics updated successfully');
104 } catch (error) {
105 this.logger.error('Failed to collect business metrics', error);
106 }
107 }
108
109 async getBusinessDashboard() {
110 return {
111 totalTributesFound: await this.getTotalTributesFound(),
112 averageTributeValue: await this.getAverageTributeValue(),
113 topPerformingLegions: await this.getTopPerformingLegions(),
114 legionDistribution: await this.getLegionDistribution(),
115 missionSuccessRates: await this.getMissionSuccessRates(),
116 generatedAt: new Date(),
117 };
118 }
119
120 private async getTotalTributesFound(): Promise<number> {
121 const metric = await register.getSingleMetric('legionaries_tributes_found_total');
122 return metric ? (metric as any).get().values.reduce((sum, v) => sum + v.value, 0) : 0;
123 }
124
125 private async getAverageTributeValue(): Promise<number> {
126 // Simulate calculation from histogram
127 return 2500 + Math.random() * 1000;
128 }
129
130 private async getTopPerformingLegions(): Promise<Array<{ cohort: string; tributes: number }>> {
131 return [
132 { cohort: 'Legio Nigra', tributes: 156 },
133 { cohort: 'Ultio Reginae', tributes: 134 },
134 { cohort: 'Aquila Volans', tributes: 98 },
135 ];
136 }
137
138 private async getLegionDistribution(): Promise<Record<string, number>> {
139 return {
140 'Centurion': 3,
141 'Optio': 6,
142 'Ballistarius': 18,
143 'Legionary': 45,
144 };
145 }
146
147 private async getMissionSuccessRates(): Promise<Record<string, number>> {
148 return {
149 'tribute_hunt': 0.78,
150 'trade': 0.92,
151 'exploration': 0.65,
152 'battle': 0.43,
153 };
154 }
155}

Monitoring and alerting in NestJS are powerful tools for maintaining application health. With Prometheus, custom metrics, and alert systems, you can have full control over the state of your Roman fort!

In the next module, we will learn advanced debugging techniques - we will learn to track errors like real detectives in the field!

Go to CodeWorlds