We use cookies to enhance your experience on the site
CodeWorlds

Debugging Techniques - Solving Problems in the Field

Roman mystery investigator! Consul Caesar.js has entrusted you with the most difficult task - you will become the chief detective in the application, responsible for tracking down and solving all technical problems. In the field, errors can be deadly dangerous, so you must master all debugging techniques!

What Is Debugging in the World of Legionaries?

Imagine you are the centurion of a fort, and suddenly:

  • Ballistae stop firing - error in the weapons system
  • Compass points the wrong way - navigation logic problem
  • Tributes disappear from the vault - memory leak or data management error
  • Legion does not respond to orders - communication deadlocks

Debugging is the art of systematically finding the causes of problems and fixing them. In NestJS, we have many powerful tools at our disposal!

NestJS Built-in Debugger

Enabling Debug Mode

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 for 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 with 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 barbarian horde 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 - legionaries encountered a barbarian ambush!');
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 legionaries 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 for 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 in NestJS is a detective art! With these tools, you can track down every problem like a real investigator at a Roman fort. Remember - every error is a puzzle to solve, and every puzzle leads to better code!

In the next module, we will learn about testing - we will test our fort before marching into the dangerous provinces of production!

Go to CodeWorlds