Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds

Debugging Techniques - rozwiązywanie problemów na rubieżach Imperium

Śledczy rzymskich tajemnic! Konsul Caesar.js powierzył Ci najtrudniejsze zadanie - zostaniesz głównym detektywem w aplikacji, odpowiedzialnym za tropienie i rozwiązywanie wszelkich problemów technicznych. Na rubieżach Imperium błędy mogą być śmiertelnie niebezpieczne, więc musisz opanować wszystkie techniki debugowania!

Czym jest debugging w świecie legionariuszów?

Wyobraź sobie, że jesteś dowódcą fortu, a nagle:

  • Machiny oblężnicze przestają działać - błąd w systemie uzbrojenia
  • Drogowskazy prowadzą w złym kierunku - problem z logiką nawigacji
  • tributy znikają ze skarbca - wyciek pamięci lub błąd w zarządzaniu danymi
  • legion nie reaguje na rozkazy - deadlocki w komunikacji

Debugging to sztuka systematycznego znajdowania przyczyn problemów i ich naprawiania. W NestJS mamy do dyspozycji wiele potężnych narzędzi!

NestJS Built-in Debugger

Włączanie trybu debug

1// main.ts
2import { NestFactory } from '@nestjs/core';
3import { AppModule } from './app.module';
4import { Logger } from '@nestjs/common';
5
6async function bootstrap() {
7 const logger = new Logger('Bootstrap');
8 
9 const app = await NestFactory.create(AppModule, {
10 logger: process.env.NODE_ENV === 'development' 
11 ? ['error', 'warn', 'log', 'verbose', 'debug'] 
12 : ['error', 'warn', 'log'],
13 });
14
15 // Enable debug mode dla development
16 if (process.env.NODE_ENV === 'development') {
17 app.useLogger(logger);
18 logger.debug(' Debug mode enabled - ready for investigation!');
19 }
20
21 await app.listen(3000);
22 logger.log(' Legionary cohort ready for debugging adventures!');
23}
24bootstrap();

Advanced Debugging Service

Debug Interceptor z context tracking

1// interceptors/debug.interceptor.ts
2import { Injectable, NestInterceptor, ExecutionContext, CallHandler, Logger } from '@nestjs/common';
3import { Observable } from 'rxjs';
4import { tap, catchError } from 'rxjs/operators';
5import { v4 as uuidv4 } from 'uuid';
6
7@Injectable()
8export class DebugInterceptor implements NestInterceptor {
9 private readonly logger = new Logger(DebugInterceptor.name);
10
11 intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
12 const request = context.switchToHttp().getRequest();
13 const response = context.switchToHttp().getResponse();
14 
15 // Generate unique request ID for tracking
16 const requestId = uuidv4().substring(0, 8);
17 request.debugId = requestId;
18 
19 const { method, url, body, headers, query } = request;
20 const startTime = Date.now();
21 
22 this.logger.debug(` [${requestId}] === REQUEST START ===`);
23 this.logger.debug(` [${requestId}] Method: ${method}`);
24 this.logger.debug(` [${requestId}] URL: ${url}`);
25 this.logger.debug(` [${requestId}] Query: ${JSON.stringify(query)}`);
26 this.logger.debug(` [${requestId}] Headers: ${JSON.stringify(this.sanitizeHeaders(headers))}`);
27 
28 if (body && Object.keys(body).length > 0) {
29 this.logger.debug(` [${requestId}] Body: ${JSON.stringify(this.sanitizeBody(body))}`);
30 }
31
32 return next.handle().pipe(
33 tap((data) => {
34 const duration = Date.now() - startTime;
35 this.logger.debug(` [${requestId}] === RESPONSE SUCCESS ===`);
36 this.logger.debug(` [${requestId}] Status: ${response.statusCode}`);
37 this.logger.debug(` [${requestId}] Duration: ${duration}ms`);
38 this.logger.debug(` [${requestId}] Response: ${JSON.stringify(data)}`);
39 this.logger.debug(` [${requestId}] === REQUEST END ===`);
40 }),
41 catchError((error) => {
42 const duration = Date.now() - startTime;
43 this.logger.error(`[${requestId}] === RESPONSE ERROR ===`);
44 this.logger.error(`[${requestId}] Duration: ${duration}ms`);
45 this.logger.error(`[${requestId}] Error: ${error.message}`);
46 this.logger.error(`[${requestId}] Stack: ${error.stack}`);
47 this.logger.error(`[${requestId}] === REQUEST END ===`);
48 throw error;
49 })
50 );
51 }
52
53 private sanitizeHeaders(headers: any): any {
54 const sensitive = ['authorization', 'cookie', 'x-api-key'];
55 const sanitized = { ...headers };
56 
57 sensitive.forEach(key => {
58 if (sanitized[key]) {
59 sanitized[key] = '***REDACTED***';
60 }
61 });
62 
63 return sanitized;
64 }
65
66 private sanitizeBody(body: any): any {
67 const sensitive = ['password', 'token', 'secret', 'apiKey'];
68 const sanitized = { ...body };
69 
70 sensitive.forEach(key => {
71 if (sanitized[key]) {
72 sanitized[key] = '***REDACTED***';
73 }
74 });
75 
76 return sanitized;
77 }
78}

Performance Debugging Service

1// services/performance-debug.service.ts
2import { Injectable, Logger } from '@nestjs/common';
3import { performance } from 'perf_hooks';
4
5interface PerformanceMark {
6 name: string;
7 startTime: number;
8 endTime?: number;
9 duration?: number;
10 metadata?: any;
11}
12
13@Injectable()
14export class PerformanceDebugService {
15 private readonly logger = new Logger(PerformanceDebugService.name);
16 private marks = new Map<string, PerformanceMark>();
17 private measureHistory: PerformanceMark[] = [];
18
19 startMark(name: string, metadata?: any): string {
20 const markId = `${name}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
21
22 const mark: PerformanceMark = {
23 name,
24 startTime: performance.now(),
25 metadata,
26 };
27 
28 this.marks.set(markId, mark);
29 this.logger.debug(`⏱ Started measuring: ${name} [${markId}]`);
30 
31 return markId;
32 }
33
34 endMark(markId: string): PerformanceMark | null {
35 const mark = this.marks.get(markId);
36 if (!mark) {
37 this.logger.warn(` Mark not found: ${markId}`);
38 return null;
39 }
40
41 mark.endTime = performance.now();
42 mark.duration = mark.endTime - mark.startTime;
43 
44 this.marks.delete(markId);
45 this.measureHistory.push(mark);
46 
47 // Keep only last 1000 measurements
48 if (this.measureHistory.length > 1000) {
49 this.measureHistory = this.measureHistory.slice(-1000);
50 }
51 
52 this.logger.debug(`⏱ Finished measuring: ${mark.name} - ${mark.duration.toFixed(2)}ms [${markId}]`);
53 
54 // Alert for slow operations
55 if (mark.duration > 1000) {
56 this.logger.warn(`Slow operation detected: ${mark.name} took ${mark.duration.toFixed(2)}ms`);
57 }
58 
59 return mark;
60 }
61
62 measureFunction<T>(name: string, fn: () => T, metadata?: any): T {
63 const markId = this.startMark(name, metadata);
64 
65 try {
66 const result = fn();
67 
68 // Handle async functions
69 if (result instanceof Promise) {
70 return result.finally(() => {
71 this.endMark(markId);
72 }) as T;
73 }
74 
75 this.endMark(markId);
76 return result;
77 } catch (error) {
78 this.endMark(markId);
79 this.logger.error(`❌ Error in measured function ${name}:`, error);
80 throw error;
81 }
82 }
83
84 async measureAsync<T>(name: string, fn: () => Promise<T>, metadata?: any): Promise<T> {
85 const markId = this.startMark(name, metadata);
86 
87 try {
88 const result = await fn();
89 this.endMark(markId);
90 return result;
91 } catch (error) {
92 this.endMark(markId);
93 this.logger.error(`❌ Error in measured async function ${name}:`, error);
94 throw error;
95 }
96 }
97
98 getPerformanceReport(limit: number = 50) {
99 const recent = this.measureHistory.slice(-limit);
100 
101 const byName = recent.reduce((acc, mark) => {
102 if (!acc[mark.name]) {
103 acc[mark.name] = {
104 count: 0,
105 totalTime: 0,
106 avgTime: 0,
107 minTime: Infinity,
108 maxTime: 0,
109 };
110 }
111 
112 const stats = acc[mark.name];
113 stats.count++;
114 stats.totalTime += mark.duration!;
115 stats.avgTime = stats.totalTime / stats.count;
116 stats.minTime = Math.min(stats.minTime, mark.duration!);
117 stats.maxTime = Math.max(stats.maxTime, mark.duration!);
118 
119 return acc;
120 }, {} as Record<string, any>);
121
122 return {
123 summary: byName,
124 recentMeasurements: recent.slice(-10),
125 totalMeasurements: this.measureHistory.length,
126 activeMeasurements: this.marks.size,
127 };
128 }
129
130 clearHistory(): void {
131 this.measureHistory = [];
132 this.logger.log('🧹 Performance history cleared');
133 }
134}

Memory Debugging

Memory Leak Detective Service

1// services/memory-debug.service.ts
2import { Injectable, Logger } from '@nestjs/common';
3import { Cron } from '@nestjs/schedule';
4
5@Injectable()
6export class MemoryDebugService {
7 private readonly logger = new Logger(MemoryDebugService.name);
8 private memoryHistory: Array<{
9 timestamp: Date;
10 heapUsed: number;
11 heapTotal: number;
12 external: number;
13 rss: number;
14 }> = [];
15
16 @Cron('0 */5 * * * *') // Every 5 minutes
17 collectMemoryStats() {
18 const memUsage = process.memoryUsage();
19 
20 const stats = {
21 timestamp: new Date(),
22 heapUsed: Math.round(memUsage.heapUsed / 1024 / 1024), // MB
23 heapTotal: Math.round(memUsage.heapTotal / 1024 / 1024), // MB
24 external: Math.round(memUsage.external / 1024 / 1024), // MB
25 rss: Math.round(memUsage.rss / 1024 / 1024), // MB
26 };
27 
28 this.memoryHistory.push(stats);
29 
30 // Keep only last 24 hours (288 samples at 5min intervals)
31 if (this.memoryHistory.length > 288) {
32 this.memoryHistory = this.memoryHistory.slice(-288);
33 }
34 
35 this.logger.debug(` Memory: Heap ${stats.heapUsed}/${stats.heapTotal}MB, RSS: ${stats.rss}MB`);
36 
37 // Check for memory leaks
38 this.detectMemoryLeaks(stats);
39 }
40
41 private detectMemoryLeaks(currentStats: any) {
42 if (this.memoryHistory.length < 12) return; // Need at least 1 hour of data
43 
44 const recent = this.memoryHistory.slice(-12); // Last hour
45 const older = this.memoryHistory.slice(-24, -12); // Previous hour
46 
47 const recentAvg = recent.reduce((sum, s) => sum + s.heapUsed, 0) / recent.length;
48 const olderAvg = older.reduce((sum, s) => sum + s.heapUsed, 0) / older.length;
49 
50 const growthRate = (recentAvg - olderAvg) / olderAvg;
51 
52 if (growthRate > 0.1) { // 10% growth in an hour
53 this.logger.warn(`MEMORY LEAK DETECTED! Heap grew by ${(growthRate * 100).toFixed(1)}% in the last hour`);
54 this.logger.warn(` Previous hour average: ${olderAvg.toFixed(1)}MB`);
55 this.logger.warn(` Recent hour average: ${recentAvg.toFixed(1)}MB`);
56 
57 // Force garbage collection if available
58 if (global.gc) {
59 this.logger.log('🧹 Forcing garbage collection...');
60 global.gc();
61 }
62 }
63 }
64
65 getMemoryReport() {
66 const currentMemory = process.memoryUsage();
67 const latest = this.memoryHistory[this.memoryHistory.length - 1];
68 
69 return {
70 current: {
71 heapUsed: Math.round(currentMemory.heapUsed / 1024 / 1024),
72 heapTotal: Math.round(currentMemory.heapTotal / 1024 / 1024),
73 external: Math.round(currentMemory.external / 1024 / 1024),
74 rss: Math.round(currentMemory.rss / 1024 / 1024),
75 heapUtilization: Math.round((currentMemory.heapUsed / currentMemory.heapTotal) * 100),
76 },
77 trend: this.calculateMemoryTrend(),
78 history: this.memoryHistory.slice(-20), // Last 20 samples
79 recommendations: this.getMemoryRecommendations(),
80 };
81 }
82
83 private calculateMemoryTrend() {
84 if (this.memoryHistory.length < 6) return 'INSUFFICIENT_DATA';
85 
86 const recent = this.memoryHistory.slice(-6);
87 const isIncreasing = recent.every((stat, i) => 
88 i === 0 || stat.heapUsed >= recent[i - 1].heapUsed
89 );
90 const isDecreasing = recent.every((stat, i) => 
91 i === 0 || stat.heapUsed <= recent[i - 1].heapUsed
92 );
93 
94 if (isIncreasing) return 'INCREASING';
95 if (isDecreasing) return 'DECREASING';
96 return 'STABLE';
97 }
98
99 private getMemoryRecommendations(): string[] {
100 const recommendations = [];
101 const current = process.memoryUsage();
102 const heapUtilization = (current.heapUsed / current.heapTotal) * 100;
103 
104 if (heapUtilization > 90) {
105 recommendations.push('High heap utilization! Consider increasing heap size or optimizing memory usage');
106 } else if (heapUtilization > 75) {
107 recommendations.push('Moderate heap utilization. Monitor closely for memory leaks');
108 }
109 
110 if (current.external > 50 * 1024 * 1024) { // 50MB
111 recommendations.push('High external memory usage. Check for large buffers or native modules');
112 }
113 
114 const trend = this.calculateMemoryTrend();
115 if (trend === 'INCREASING') {
116 recommendations.push('Memory usage is trending upward. Investigate potential memory leaks');
117 }
118 
119 if (recommendations.length === 0) {
120 recommendations.push('✅ Memory usage looks healthy');
121 }
122 
123 return recommendations;
124 }
125
126 createHeapSnapshot(): string {
127 if (!global.gc) {
128 this.logger.warn(' Heap snapshot requires --expose-gc flag');
129 return 'Heap snapshot requires --expose-gc flag';
130 }
131 
132 const filename = `heap-${Date.now()}.heapsnapshot`;
133 this.logger.log(`Creating heap snapshot: ${filename}`);
134 
135 // In a real implementation, you'd use v8.writeHeapSnapshot()
136 // This is a simplified placeholder
137 return filename;
138 }
139}

Database Query Debugging

Query Analysis Service

1// services/query-debug.service.ts
2import { Injectable, Logger } from '@nestjs/common';
3import { DataSource } from 'typeorm';
4
5@Injectable()
6export class QueryDebugService {
7 private readonly logger = new Logger(QueryDebugService.name);
8 private queryLogs: Array<{
9 query: string;
10 parameters: any[];
11 duration: number;
12 timestamp: Date;
13 stackTrace?: string;
14 }> = [];
15
16 constructor(private dataSource: DataSource) {
17 this.setupQueryLogging();
18 }
19
20 private setupQueryLogging() {
21 if (process.env.NODE_ENV === 'development') {
22 // Override the query runner to capture all queries
23 const originalQuery = this.dataSource.driver.query.bind(this.dataSource.driver);
24 
25 this.dataSource.driver.query = async (query: string, parameters?: any[]) => {
26 const startTime = Date.now();
27 
28 try {
29 const result = await originalQuery(query, parameters);
30 const duration = Date.now() - startTime;
31 
32 this.logQuery(query, parameters || [], duration);
33 
34 if (duration > 1000) {
35 this.logger.warn(`Slow query detected (${duration}ms): ${query.substring(0, 100)}...`);
36 }
37 
38 return result;
39 } catch (error) {
40 const duration = Date.now() - startTime;
41 this.logger.error(`❌ Query failed (${duration}ms): ${query.substring(0, 100)}...`, error);
42 throw error;
43 }
44 };
45 }
46 }
47
48 private logQuery(query: string, parameters: any[], duration: number) {
49 const logEntry = {
50 query: query.replace(/\s+/g, ' ').trim(),
51 parameters,
52 duration,
53 timestamp: new Date(),
54 stackTrace: process.env.DEBUG_STACK_TRACE === 'true' ? new Error().stack : undefined,
55 };
56
57 this.queryLogs.push(logEntry);
58 
59 // Keep only last 1000 queries
60 if (this.queryLogs.length > 1000) {
61 this.queryLogs = this.queryLogs.slice(-1000);
62 }
63
64 this.logger.debug(`[${duration}ms] ${logEntry.query}`);
65 if (parameters.length > 0) {
66 this.logger.debug(` Parameters: [${parameters.join(', ')}]`);
67 }
68 }
69
70 getSlowQueries(threshold: number = 500): any[] {
71 return this.queryLogs
72 .filter(log => log.duration > threshold)
73 .sort((a, b) => b.duration - a.duration)
74 .slice(0, 20);
75 }
76
77 getQueryStatistics() {
78 if (this.queryLogs.length === 0) {
79 return { message: 'No queries logged yet' };
80 }
81
82 const totalQueries = this.queryLogs.length;
83 const totalDuration = this.queryLogs.reduce((sum, log) => sum + log.duration, 0);
84 const avgDuration = totalDuration / totalQueries;
85 
86 const slowQueries = this.queryLogs.filter(log => log.duration > 500).length;
87 const verySlowQueries = this.queryLogs.filter(log => log.duration > 1000).length;
88
89 const queryTypes = this.queryLogs.reduce((acc, log) => {
90 const type = log.query.split(' ')[0].toUpperCase();
91 acc[type] = (acc[type] || 0) + 1;
92 return acc;
93 }, {} as Record<string, number>);
94
95 return {
96 totalQueries,
97 averageDuration: Math.round(avgDuration),
98 slowQueries,
99 verySlowQueries,
100 queryTypes,
101 recentQueries: this.queryLogs.slice(-10),
102 };
103 }
104
105 async analyzeTablePerformance() {
106 try {
107 // Get table sizes
108 const tableSizes = await this.dataSource.query(`
109 SELECT 
110 table_name,
111 ROUND(((data_length + index_length) / 1024 / 1024), 2) AS 'size_mb'
112 FROM information_schema.tables 
113 WHERE table_schema = DATABASE()
114 ORDER BY (data_length + index_length) DESC
115 `);
116
117 // Get index usage (MySQL specific)
118 const indexUsage = await this.dataSource.query(`
119 SELECT 
120 table_name,
121 index_name,
122 column_name,
123 cardinality
124 FROM information_schema.statistics 
125 WHERE table_schema = DATABASE()
126 ORDER BY table_name, index_name
127 `);
128
129 return {
130 tableSizes: tableSizes.slice(0, 10), // Top 10 largest tables
131 indexUsage: indexUsage.slice(0, 20), // Top 20 indexes
132 recommendations: this.generatePerformanceRecommendations(tableSizes),
133 };
134 } catch (error) {
135 this.logger.error('Failed to analyze table performance:', error);
136 return { error: 'Database analysis not available for this database type' };
137 }
138 }
139
140 private generatePerformanceRecommendations(tableSizes: any[]): string[] {
141 const recommendations = [];
142 
143 const largeTables = tableSizes.filter(table => table.size_mb > 100);
144 if (largeTables.length > 0) {
145 recommendations.push(` Large tables detected: ${largeTables.map(t => t.table_name).join(', ')}. Consider archiving old data.`);
146 }
147
148 const slowQueryCount = this.queryLogs.filter(log => log.duration > 1000).length;
149 if (slowQueryCount > 10) {
150 recommendations.push(`High number of slow queries (${slowQueryCount}). Review query optimization.`);
151 }
152
153 const selectQueries = this.queryLogs.filter(log => log.query.toUpperCase().startsWith('SELECT'));
154 if (selectQueries.length > this.queryLogs.length * 0.8) {
155 recommendations.push(` High ratio of SELECT queries. Consider implementing caching.`);
156 }
157
158 return recommendations.length > 0 ? recommendations : ['✅ Database performance looks good!'];
159 }
160
161 clearQueryLogs(): void {
162 this.queryLogs = [];
163 this.logger.log('🧹 Query logs cleared');
164 }
165}

Error Tracing and Debug Controller

Debug Controller for runtime investigation

1// controllers/debug.controller.ts
2import { Controller, Get, Post, Query, Body, UseGuards } from '@nestjs/common';
3import { PerformanceDebugService } from '../services/performance-debug.service';
4import { MemoryDebugService } from '../services/memory-debug.service';
5import { QueryDebugService } from '../services/query-debug.service';
6
7@Controller('debug')
8@UseGuards(AdminGuard) // Only for administrators
9export class DebugController {
10 constructor(
11 private readonly performanceDebug: PerformanceDebugService,
12 private readonly memoryDebug: MemoryDebugService,
13 private readonly queryDebug: QueryDebugService,
14 ) {}
15
16 @Get('health')
17 getSystemHealth() {
18 return {
19 status: 'Centurion Caesar.js debugging system operational! ',
20 timestamp: new Date(),
21 node: {
22 version: process.version,
23 platform: process.platform,
24 uptime: process.uptime(),
25 pid: process.pid,
26 },
27 memory: process.memoryUsage(),
28 };
29 }
30
31 @Get('performance')
32 getPerformanceReport(@Query('limit') limit?: string) {
33 const limitNum = limit ? parseInt(limit, 10) : 50;
34 return this.performanceDebug.getPerformanceReport(limitNum);
35 }
36
37 @Get('memory')
38 getMemoryReport() {
39 return this.memoryDebug.getMemoryReport();
40 }
41
42 @Get('queries')
43 getQueryStatistics() {
44 return this.queryDebug.getQueryStatistics();
45 }
46
47 @Get('queries/slow')
48 getSlowQueries(@Query('threshold') threshold?: string) {
49 const thresholdNum = threshold ? parseInt(threshold, 10) : 500;
50 return this.queryDebug.getSlowQueries(thresholdNum);
51 }
52
53 @Get('database/analysis')
54 async getDatabaseAnalysis() {
55 return await this.queryDebug.analyzeTablePerformance();
56 }
57
58 @Post('memory/gc')
59 forceGarbageCollection() {
60 if (global.gc) {
61 global.gc();
62 return { message: '🧹 Garbage collection forced successfully' };
63 } else {
64 return { 
65 error: 'Garbage collection not available. Start with --expose-gc flag',
66 hint: 'node --expose-gc dist/main.js'
67 };
68 }
69 }
70
71 @Post('memory/snapshot')
72 createHeapSnapshot() {
73 const filename = this.memoryDebug.createHeapSnapshot();
74 return { 
75 message: 'Heap snapshot created',
76 filename,
77 hint: 'Load the snapshot in Chrome DevTools > Memory tab'
78 };
79 }
80
81 @Post('performance/clear')
82 clearPerformanceHistory() {
83 this.performanceDebug.clearHistory();
84 return { message: '🧹 Performance history cleared' };
85 }
86
87 @Post('queries/clear')
88 clearQueryLogs() {
89 this.queryDebug.clearQueryLogs();
90 return { message: '🧹 Query logs cleared' };
91 }
92
93 @Get('environment')
94 getEnvironmentInfo() {
95 return {
96 nodeEnv: process.env.NODE_ENV,
97 debugMode: process.env.DEBUG,
98 databaseUrl: process.env.DATABASE_URL ? '***HIDDEN***' : 'Not set',
99 timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
100 environmentVariables: Object.keys(process.env).filter(key => 
101 key.startsWith('APP_') || key.startsWith('DEBUG_')
102 ).reduce((acc, key) => {
103 acc[key] = process.env[key];
104 return acc;
105 }, {} as Record<string, string>),
106 };
107 }
108
109 @Get('error-simulation')
110 simulateError(@Query('type') errorType: string = 'generic') {
111 switch (errorType) {
112 case 'memory':
113 // Simulate memory leak
114 const leak = [];
115 for (let i = 0; i < 1000000; i++) {
116 leak.push({ id: i, data: 'x'.repeat(1000) });
117 }
118 return { message: 'Memory leak simulated', size: leak.length };
119
120 case 'async':
121 // Simulate async error
122 setTimeout(() => {
123 throw new Error('Simulated async error - the Hydra attacked!');
124 }, 1000);
125 return { message: 'Async error will occur in 1 second' };
126
127 case 'promise':
128 // Simulate unhandled promise rejection
129 Promise.reject(new Error('Simulated promise rejection - tribute chest locked!'));
130 return { message: 'Promise rejection simulated' };
131
132 case 'stack-overflow':
133 // Simulate stack overflow
134 const recursive = () => recursive();
135 recursive();
136 return { message: 'This should not be reached' };
137
138 default:
139 throw new Error('Simulated generic error - legionariuszes encountered sea monster!');
140 }
141 }
142
143 @Post('trace')
144 traceFunction(@Body() body: { code: string }) {
145 try {
146 // DANGER: This is for debugging only - never use in production!
147 if (process.env.NODE_ENV !== 'development') {
148 throw new Error('Function tracing only available in development mode');
149 }
150
151 // Simulate function execution tracing
152 const startTime = performance.now();
153 
154 // WARNING: eval is dangerous - this is just for demo purposes
155 const result = eval(`(function() { ${body.code} })()`);
156 
157 const endTime = performance.now();
158 
159 return {
160 result,
161 executionTime: endTime - startTime,
162 warning: 'This feature should never be used in production!',
163 };
164 } catch (error) {
165 return {
166 error: error.message,
167 stack: error.stack,
168 advice: 'Check your legionariusze code for errors, legionary!',
169 };
170 }
171 }
172}
173
174// Guards for security
175@Injectable()
176class AdminGuard implements CanActivate {
177 canActivate(context: ExecutionContext): boolean {
178 // In development, allow all access
179 if (process.env.NODE_ENV === 'development') {
180 return true;
181 }
182 
183 // In production, require proper authentication
184 const request = context.switchToHttp().getRequest();
185 const adminKey = request.headers['x-debug-key'];
186 
187 return adminKey === process.env.DEBUG_ADMIN_KEY;
188 }
189}

Best Practices dla Debugging

1. Structured Logging

1// services/structured-logger.service.ts
2import { Injectable, LoggerService } from '@nestjs/common';
3import { createLogger, format, transports } from 'winston';
4
5@Injectable()
6export class StructuredLoggerService implements LoggerService {
7 private winston = createLogger({
8 level: process.env.LOG_LEVEL || 'info',
9 format: format.combine(
10 format.timestamp(),
11 format.errors({ stack: true }),
12 format.json()
13 ),
14 transports: [
15 new transports.Console({
16 format: format.combine(
17 format.colorize(),
18 format.printf(({ timestamp, level, message, context, trace, ...meta }) => {
19 return `${timestamp} [${context || 'Application'}] ${level}: ${message} ${
20 Object.keys(meta).length ? JSON.stringify(meta) : ''
21 }${trace ? `\n${trace}` : ''}`;
22 })
23 ),
24 }),
25 new transports.File({ 
26 filename: 'logs/error.log', 
27 level: 'error',
28 maxsize: 5242880, // 5MB
29 maxFiles: 5,
30 }),
31 new transports.File({ 
32 filename: 'logs/combined.log',
33 maxsize: 5242880, // 5MB
34 maxFiles: 5,
35 }),
36 ],
37 });
38
39 log(message: string, context?: string, meta?: any) {
40 this.winston.info(message, { context, ...meta });
41 }
42
43 error(message: string, trace?: string, context?: string, meta?: any) {
44 this.winston.error(message, { context, trace, ...meta });
45 }
46
47 warn(message: string, context?: string, meta?: any) {
48 this.winston.warn(message, { context, ...meta });
49 }
50
51 debug(message: string, context?: string, meta?: any) {
52 this.winston.debug(message, { context, ...meta });
53 }
54
55 verbose(message: string, context?: string, meta?: any) {
56 this.winston.verbose(message, { context, ...meta });
57 }
58}

2. Comprehensive Error Handling

1// filters/global-debug-exception.filter.ts
2import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus, Logger } from '@nestjs/common';
3import { Request, Response } from 'express';
4
5@Catch()
6export class GlobalDebugExceptionFilter implements ExceptionFilter {
7 private readonly logger = new Logger(GlobalDebugExceptionFilter.name);
8
9 catch(exception: unknown, host: ArgumentsHost): void {
10 const ctx = host.switchToHttp();
11 const response = ctx.getResponse<Response>();
12 const request = ctx.getRequest<Request>();
13
14 const status = exception instanceof HttpException 
15 ? exception.getStatus() 
16 : HttpStatus.INTERNAL_SERVER_ERROR;
17
18 const message = exception instanceof HttpException 
19 ? exception.getResponse()
20 : 'Internal server error';
21
22 const errorId = (request as any).debugId || 'unknown';
23 
24 const errorResponse = {
25 statusCode: status,
26 timestamp: new Date().toISOString(),
27 path: request.url,
28 method: request.method,
29 errorId,
30 message: typeof message === 'string' ? message : (message as any).message,
31 ...(process.env.NODE_ENV === 'development' && {
32 stack: exception instanceof Error ? exception.stack : undefined,
33 requestBody: request.body,
34 requestQuery: request.query,
35 requestHeaders: this.sanitizeHeaders(request.headers),
36 }),
37 };
38
39 // Log the error with context
40 this.logger.error(
41 `[${errorId}] ${request.method} ${request.url} - ${status}`,
42 exception instanceof Error ? exception.stack : String(exception),
43 'GlobalDebugExceptionFilter'
44 );
45
46 response.status(status).json(errorResponse);
47 }
48
49 private sanitizeHeaders(headers: any): any {
50 const { authorization, cookie, ...safeHeaders } = headers;
51 return {
52 ...safeHeaders,
53 ...(authorization && { authorization: '***REDACTED***' }),
54 ...(cookie && { cookie: '***REDACTED***' }),
55 };
56 }
57}

Debugging w NestJS to sztuka detektywistyczna! Dzięki tym narzędziom możesz tropić każdy problem jak prawdziwy śledczy w rzymskim kastrum. Pamiętaj - każdy błąd to zagadka do rozwiązania, a każda zagadka prowadzi do lepszego kodu!

W następnym module poznamy testing - będziemy testować nasz fort, zanim wyruszymy na niebezpieczne rubieże produkcji!

Przejdź do CodeWorlds