Utilizziamo i cookie per migliorare la tua esperienza sul sito
CodeWorlds

Error Logging - kronika niepowodzeń

Kronikarzu kastrum! Konsul Caesar.js zlecił Ci prowadzenie szczegółowej kroniki wszystkich wydarzeń w systemie - zarówno sukcesów jak i niepowodzeń. Error Logging to nie tylko zapisywanie błędów, to sztuka dokumentowania każdego incydentu tak, aby przyszłe legiony mogły się z nich uczyć!

W rzymskim kastrum każdy incydent może być kluczowy dla bezpieczeństwa całego legionu. Wyłom w murach, konflikt w szeregach, czy nawet drobny niedobór w skarbcu - wszystko musi być dokładnie udokumentowane. Error Logging w NestJS działa podobnie - tworzy szczegółową kronikę wszystkich problemów w aplikacji.

Strukturalny System Logowania

Podstawowa Konfiguracja Logger

1// logging/legionariusze-logger.service.ts
2import { Injectable, Logger, LoggerService } from '@nestjs/common';
3import { ConfigService } from '@nestjs/config';
4import * as winston from 'winston';
5import * as DailyRotateFile from 'winston-daily-rotate-file';
6
7@Injectable()
8export class LegionaryLoggerService implements LoggerService {
9 private readonly winston: winston.Logger;
10 private readonly context: string = 'Legionary';
11
12 constructor(private configService: ConfigService) {
13 this.winston = winston.createLogger({
14 level: this.configService.get('LOG_LEVEL', 'info'),
15 format: winston.format.combine(
16 winston.format.timestamp(),
17 winston.format.errors({ stack: true }),
18 winston.format.json(),
19 winston.format.printf(this.legionariuszeLogFormat)
20 ),
21 transports: [
22 // Konsola dla developmentu
23 new winston.transports.Console({
24 format: winston.format.combine(
25 winston.format.colorize(),
26 winston.format.simple(),
27 winston.format.printf(this.consoleFormat)
28 )
29 }),
30 
31 // Pliki rotacyjne dla produkcji
32 new DailyRotateFile({
33 filename: 'logs/legionariusze-cohort-%DATE%.log',
34 datePattern: 'YYYY-MM-DD',
35 maxFiles: '30d',
36 maxSize: '100m',
37 level: 'info'
38 }),
39 
40 // Oddzielny plik dla błędów
41 new DailyRotateFile({
42 filename: 'logs/legionariusze-errors-%DATE%.log',
43 datePattern: 'YYYY-MM-DD',
44 maxFiles: '90d',
45 maxSize: '100m',
46 level: 'error'
47 }),
48 
49 // Krytyczne błędy wymagające natychmiastowej uwagi
50 new DailyRotateFile({
51 filename: 'logs/legionariusze-critical-%DATE%.log',
52 datePattern: 'YYYY-MM-DD',
53 maxFiles: '365d',
54 level: 'fatal'
55 })
56 ]
57 });
58 }
59
60 private legionariuszeLogFormat = (info: any) => {
61 const { timestamp, level, message, context, trace, ...meta } = info;
62 
63 return JSON.stringify({
64 timestamp,
65 level: level.toUpperCase(),
66 cohort: 'Equus Niger',
67 centurion: 'Caesar.js',
68 context: context || this.context,
69 message,
70 ...(trace && { stack: trace }),
71 ...meta,
72 requestId: this.getCurrentRequestId(),
73 sessionId: this.getCurrentSessionId()
74 });
75 };
76
77 logLegionActivity(activity: string, legionariusze: any, details?: any) {
78 this.winston.info('Legion Activity', {
79 context: 'LegionManagement',
80 activity,
81 legionariusze: {
82 name: legionariusze.name,
83 rank: legionariusze.rank,
84 id: legionariusze.id
85 },
86 details,
87 category: 'legion_activity'
88 });
89 }
90
91 logTributeOperation(operation: string, tribute: any, legionariusze: any, result?: any) {
92 this.winston.info('Tribute Operation', {
93 context: 'TributeManagement',
94 operation,
95 tribute: {
96 id: tribute.id,
97 name: tribute.name,
98 value: tribute.value
99 },
100 legionariusze: {
101 name: legionariusze.name,
102 rank: legionariusze.rank
103 },
104 result,
105 category: 'tribute_operation'
106 });
107 }
108
109 logSecurityIncident(incident: string, severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL', details: any) {
110 const logLevel = severity === 'CRITICAL' ? 'error' : 
111 severity === 'HIGH' ? 'warn' : 'info';
112 
113 this.winston[logLevel]('Security Incident', {
114 context: 'Security',
115 incident,
116 severity,
117 details,
118 category: 'security_incident',
119 requiresAttention: severity === 'CRITICAL' || severity === 'HIGH'
120 });
121 }
122
123 log(message: string, context?: string) {
124 this.winston.info(message, { context });
125 }
126
127 error(message: string, trace?: string, context?: string) {
128 this.winston.error(message, { trace, context });
129 }
130
131 warn(message: string, context?: string) {
132 this.winston.warn(message, { context });
133 }
134
135 debug(message: string, context?: string) {
136 this.winston.debug(message, { context });
137 }
138
139 verbose(message: string, context?: string) {
140 this.winston.verbose(message, { context });
141 }
142
143 private getCurrentRequestId(): string {
144 return 'req_' + Math.random().toString(36).substr(2, 9);
145 }
146
147 private getCurrentSessionId(): string {
148 return 'session_' + Math.random().toString(36).substr(2, 9);
149 }
150}

Error Tracking Service

1// services/error-tracking.service.ts
2@Injectable()
3export class ErrorTrackingService {
4 constructor(
5 private logger: LegionaryLoggerService,
6 private dataSource: DataSource
7 ) {}
8
9 async trackError(error: any, context: any = {}) {
10 const errorRecord = {
11 timestamp: new Date(),
12 message: error.message,
13 stack: error.stack,
14 name: error.name,
15 code: error.code,
16 context,
17 severity: this.determineSeverity(error),
18 fingerprint: this.generateFingerprint(error),
19 environment: process.env.NODE_ENV,
20 version: process.env.APP_VERSION || '1.0.0'
21 };
22
23 this.logger.error('Error Tracked', {
24 context: 'ErrorTracking',
25 errorRecord,
26 requiresAttention: errorRecord.severity === 'CRITICAL'
27 });
28
29 await this.saveErrorToDatabase(errorRecord);
30
31 if (errorRecord.severity === 'CRITICAL') {
32 await this.sendCriticalErrorAlert(errorRecord);
33 }
34
35 return errorRecord;
36 }
37
38 async getErrorStatistics(timeframe: 'hour' | 'day' | 'week' | 'month' = 'day') {
39 const since = this.getTimeframeCutoff(timeframe);
40 
41 const stats = await this.dataSource.query(`
42 SELECT 
43 error_type,
44 severity,
45 COUNT(*) as occurrence_count,
46 COUNT(DISTINCT fingerprint) as unique_errors,
47 MIN(timestamp) as first_occurrence,
48 MAX(timestamp) as last_occurrence
49 FROM error_logs 
50 WHERE timestamp >= $1
51 GROUP BY error_type, severity
52 ORDER BY occurrence_count DESC
53 `, [since]);
54
55 return stats;
56 }
57
58 private determineSeverity(error: any): 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL' {
59 if (error.name === 'DatabaseConnectionError' || 
60 error.message?.includes('ECONNREFUSED') ||
61 error.code === 'COHORT_BREAKING') {
62 return 'CRITICAL';
63 }
64
65 if (error.status >= 500 || 
66 error.name === 'UnauthorizedError' ||
67 error.code?.startsWith('SECURITY_')) {
68 return 'HIGH';
69 }
70
71 if (error.status >= 400 || 
72 error.name === 'ValidationError') {
73 return 'MEDIUM';
74 }
75
76 return 'LOW';
77 }
78
79 private generateFingerprint(error: any): string {
80 const signature = `${error.name}:${error.message}:${error.code || 'no-code'}`;
81 return require('crypto').createHash('md5').update(signature).digest('hex');
82 }
83
84 private async saveErrorToDatabase(errorRecord: any) {
85 await this.dataSource.query(`
86 INSERT INTO error_logs (
87 timestamp, message, stack, error_type, error_code, 
88 context, severity, fingerprint, environment, version
89 ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
90 `, [
91 errorRecord.timestamp,
92 errorRecord.message,
93 errorRecord.stack,
94 errorRecord.name,
95 errorRecord.code,
96 JSON.stringify(errorRecord.context),
97 errorRecord.severity,
98 errorRecord.fingerprint,
99 errorRecord.environment,
100 errorRecord.version
101 ]);
102 }
103
104 private getTimeframeCutoff(timeframe: string): Date {
105 const now = new Date();
106 switch (timeframe) {
107 case 'hour':
108 return new Date(now.getTime() - 60 * 60 * 1000);
109 case 'day':
110 return new Date(now.getTime() - 24 * 60 * 60 * 1000);
111 case 'week':
112 return new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
113 case 'month':
114 return new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
115 default:
116 return new Date(now.getTime() - 24 * 60 * 60 * 1000);
117 }
118 }
119}

Error Logging to fundament niezawodnego systemu. Dobra kronika nie tylko dokumentuje wydarzenia, ale także pozwala przewidywać przyszłe problemy i uczenie się na błędach przeszłości!

Pamiętaj: mądry centurion uczy się na błędach innych, ale najlepszy centurion uczy się na własnej, dobrze prowadzonej kronice!

Vai a CodeWorlds