Utilizziamo i cookie per migliorare la tua esperienza sul sito
CodeWorlds

Monitoring i Alerting - systemy wczesnego ostrzegania

Strażniku obserwacyjny! Konsul Caesar.js powierzył Ci najważniejsze zadanie w aplikacji - bycie oczami i uszami całego legionu. Monitoring i Alerting to nasze systemy wczesnego ostrzegania, które pozwalają wykrywać problemy zanim staną się katastrofą!

W rzymskim kastrum dobry zwiadowca na wieży strażniczej potrafił wcześnie dostrzec zbliżające się zagrożenia - oddział barbarzyńców na horyzoncie, nadciągającą burzę piaskową, czy zasadzkę w wąwozie. Monitoring w NestJS działa podobnie - ciągle obserwuje stan aplikacji i ostrzega o problemach.

Podstawowy System Monitorowania

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 // Włącz domyślne metryki (CPU, memory, etc.)
15 collectDefaultMetrics({ register });
16
17 // HTTP Request Counter
18 this.httpRequestCounter = new Counter({
19 name: 'legionariusze_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: 'legionariusze_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: 'legionariusze_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: 'legionariusze_tribute_operations_total',
45 help: 'Total number of tribute operations',
46 labelNames: ['operation_type', 'tribute_type', 'legionariusze_rank'],
47 registers: [register],
48 });
49
50 // Legion Health Status
51 this.cohortHealth = new Gauge({
52 name: 'legionariusze_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, legionariuszeRank: string) {
84 this.tributeOperations.inc({
85 operation_type: operationType,
86 tribute_type: tributeType,
87 legionariusze_rank: legionariuszeRank,
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: 'Equus Niger',
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: 'Equus Niger',
140 centurion: 'Caesar.js',
141 status: 'marching',
142 legion: 42,
143 health: 'excellent',
144 location: 'Hispania Sea',
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}

Zaawansowane metryki biznesowe

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: 'legionariusze_tributes_found_total',
13 help: 'Total number of tributes found by legionariuszes',
14 labelNames: ['legionariusze_name', 'tribute_type', 'cohort_name'],
15 });
16
17 private readonly missionSuccessRate = new Gauge({
18 name: 'legionariusze_mission_success_rate',
19 help: 'Success rate of legionariusze missions',
20 labelNames: ['mission_type', 'cohort_name'],
21 });
22
23 private readonly resourceValueHistogram = new Histogram({
24 name: 'legionariusze_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: 'legionariusze_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: 'legionariusze_cohort_status',
38 help: 'Legion status (1=marching, 0.5=docked, 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 legionariusze_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} dubloons 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' | 'docked' | 'maintenance') {
72 const statusValue = status === 'marching' ? 1 : status === 'docked' ? 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 = ['Equus Niger', 'Ultio Reginae', 'Batavus 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', 'docked', '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('legionariusze_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: 'Equus Niger', tributes: 156 },
133 { cohort: 'Ultio Reginae', tributes: 134 },
134 { cohort: 'Batavus 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 i alerting w NestJS to potężne narzędzia do utrzymania zdrowia aplikacji. Dzięki Prometheus, custom metrykom i systemom alertów możesz mieć pełną kontrolę nad stanem swojego rzymskiego fortu!

W następnym module poznamy zaawansowane techniki debugowania - nauczymy się tropić błędy jak prawdziwi detektywi na rubieżach Imperium!

Vai a CodeWorlds