Skip to main content
Skip to main content

Advanced Debugging Guide for Mifty Applications

Master debugging techniques for Mifty applications, from basic console debugging to advanced profiling and production monitoring.

What You'll Learn

This comprehensive guide covers:

  • 🐛 Basic Debugging - Console logging and breakpoint debugging
  • 🔍 Advanced Debugging - VS Code debugging and remote debugging
  • 📊 Performance Profiling - Memory leaks and CPU profiling
  • 🚨 Error Monitoring - Production error tracking and alerting
  • 🔧 Database Debugging - Query optimization and connection issues
  • 🌐 API Debugging - Request/response debugging and network issues
  • 🧪 Test Debugging - Debugging failing tests and test environments
  • 📱 Production Debugging - Live debugging and monitoring techniques

Step 1: Basic Debugging Techniques

1.1 Console Logging Best Practices

Mifty includes structured logging out of the box:

// src/services/logger.service.ts
import { Injectable, Logger } from '@nestjs/common';

@Injectable()
export class LoggerService extends Logger {

// Debug level - development only
debug(message: string, context?: string, data?: any) {
if (process.env.NODE_ENV === 'development') {
super.debug(`${message} ${data ? JSON.stringify(data) : ''}`, context);
}
}

// Info level - general information
info(message: string, context?: string, data?: any) {
super.log(`${message} ${data ? JSON.stringify(data) : ''}`, context);
}

// Warning level - potential issues
warn(message: string, context?: string, data?: any) {
super.warn(`${message} ${data ? JSON.stringify(data) : ''}`, context);
}

// Error level - actual errors
error(message: string, trace?: string, context?: string, data?: any) {
super.error(`${message} ${data ? JSON.stringify(data) : ''}`, trace, context);
}

// Performance logging
performance(operation: string, duration: number, context?: string) {
const level = duration > 1000 ? 'warn' : 'log';
this[level](`Performance: ${operation} took ${duration}ms`, context);
}
}

1.2 Using the Logger in Your Code

// src/modules/user/user.service.ts
import { Injectable } from '@nestjs/common';
import { LoggerService } from '../../services/logger.service';

@Injectable()
export class UserService {
constructor(private logger: LoggerService) {}

async create(userData: CreateUserDto) {
this.logger.debug('Creating new user', 'UserService', { email: userData.email });

try {
const startTime = Date.now();

// Check if user exists
const existingUser = await this.userRepository.findByEmail(userData.email);
if (existingUser) {
this.logger.warn('Attempt to create user with existing email', 'UserService', {
email: userData.email
});
throw new Error('User already exists');
}

// Create user
const user = await this.userRepository.create(userData);

const duration = Date.now() - startTime;
this.logger.performance('User creation', duration, 'UserService');
this.logger.info('User created successfully', 'UserService', {
userId: user.id,
email: user.email
});

return user;

} catch (error) {
this.logger.error(
'Failed to create user',
error.stack,
'UserService',
{ email: userData.email, error: error.message }
);
throw error;
}
}
}

1.3 Environment-Based Logging

Configure different log levels for different environments:

// src/config/logger.config.ts
export const loggerConfig = {
development: {
level: 'debug',
format: 'pretty',
timestamp: true
},
test: {
level: 'error',
format: 'json',
timestamp: false
},
production: {
level: 'info',
format: 'json',
timestamp: true,
file: './logs/app.log'
}
};

Step 2: VS Code Debugging Setup

2.1 Launch Configuration

Create .vscode/launch.json:

{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Mifty App",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/src/main.ts",
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
"runtimeArgs": ["-r", "ts-node/register"],
"env": {
"NODE_ENV": "development",
"DATABASE_URL": "postgresql://postgres:password@localhost:5432/mifty_dev"
},
"console": "integratedTerminal",
"restart": true,
"protocol": "inspector"
},
{
"name": "Debug Tests",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/node_modules/.bin/jest",
"args": ["--runInBand", "--no-cache"],
"cwd": "${workspaceFolder}",
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"env": {
"NODE_ENV": "test"
}
},
{
"name": "Debug Current Test File",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/node_modules/.bin/jest",
"args": ["--runInBand", "--no-cache", "${relativeFile}"],
"cwd": "${workspaceFolder}",
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
},
{
"name": "Attach to Process",
"type": "node",
"request": "attach",
"port": 9229,
"restart": true,
"localRoot": "${workspaceFolder}",
"remoteRoot": "/app"
}
]
}

2.2 Debug-Friendly npm Scripts

Add to package.json:

{
"scripts": {
"debug": "node --inspect-brk=0.0.0.0:9229 -r ts-node/register src/main.ts",
"debug:test": "node --inspect-brk node_modules/.bin/jest --runInBand",
"debug:watch": "nodemon --inspect=0.0.0.0:9229 -r ts-node/register src/main.ts"
}
}

2.3 Debugging Techniques

Setting Breakpoints

// src/modules/user/user.service.ts
export class UserService {
async findById(id: string) {
// Set breakpoint here to inspect variables
debugger; // This will trigger debugger when debugging

const user = await this.userRepository.findById(id);

if (!user) {
// Another good breakpoint location
throw new Error('User not found');
}

return user;
}
}

Conditional Breakpoints

// Only break when specific condition is met
export class PostService {
async create(postData: CreatePostDto) {
const post = await this.postRepository.create(postData);

// Conditional breakpoint: post.authorId === 'specific-user-id'
return post;
}
}

Step 3: Performance Profiling

3.1 Memory Profiling

Create memory profiling utilities:

// src/utils/memory-profiler.ts
export class MemoryProfiler {
private snapshots: Map<string, NodeJS.MemoryUsage> = new Map();

takeSnapshot(label: string): void {
this.snapshots.set(label, process.memoryUsage());
console.log(`Memory snapshot '${label}':`, this.formatMemory(process.memoryUsage()));
}

compareSnapshots(label1: string, label2: string): void {
const snapshot1 = this.snapshots.get(label1);
const snapshot2 = this.snapshots.get(label2);

if (!snapshot1 || !snapshot2) {
console.error('One or both snapshots not found');
return;
}

const diff = {
rss: snapshot2.rss - snapshot1.rss,
heapTotal: snapshot2.heapTotal - snapshot1.heapTotal,
heapUsed: snapshot2.heapUsed - snapshot1.heapUsed,
external: snapshot2.external - snapshot1.external
};

console.log(`Memory diff between '${label1}' and '${label2}':`, this.formatMemory(diff));
}

private formatMemory(memory: NodeJS.MemoryUsage | any): string {
return Object.entries(memory)
.map(([key, value]) => `${key}: ${Math.round(value / 1024 / 1024 * 100) / 100} MB`)
.join(', ');
}

// Detect potential memory leaks
detectLeaks(): void {
const usage = process.memoryUsage();
const threshold = 500 * 1024 * 1024; // 500MB

if (usage.heapUsed > threshold) {
console.warn('Potential memory leak detected:', this.formatMemory(usage));
}
}
}

// Usage in your service
export class UserService {
private memoryProfiler = new MemoryProfiler();

async processLargeDataset(data: any[]) {
this.memoryProfiler.takeSnapshot('start');

// Process data
const results = await Promise.all(
data.map(item => this.processItem(item))
);

this.memoryProfiler.takeSnapshot('end');
this.memoryProfiler.compareSnapshots('start', 'end');

return results;
}
}

3.2 CPU Profiling

// src/utils/cpu-profiler.ts
export class CPUProfiler {
private startTime: number;
private startCPU: NodeJS.CpuUsage;

start(): void {
this.startTime = Date.now();
this.startCPU = process.cpuUsage();
}

end(operation: string): void {
const endTime = Date.now();
const endCPU = process.cpuUsage(this.startCPU);

const duration = endTime - this.startTime;
const cpuPercent = (endCPU.user + endCPU.system) / 1000 / duration * 100;

console.log(`CPU Profile for '${operation}':`);
console.log(` Duration: ${duration}ms`);
console.log(` CPU Usage: ${cpuPercent.toFixed(2)}%`);
console.log(` User CPU: ${endCPU.user / 1000}ms`);
console.log(` System CPU: ${endCPU.system / 1000}ms`);

if (cpuPercent > 80) {
console.warn(`High CPU usage detected for operation: ${operation}`);
}
}
}

// Usage
export class DataProcessingService {
private cpuProfiler = new CPUProfiler();

async processData(data: any[]) {
this.cpuProfiler.start();

// CPU-intensive operation
const result = data.map(item => {
// Complex processing
return this.complexCalculation(item);
});

this.cpuProfiler.end('Data Processing');
return result;
}
}

3.3 Database Query Profiling

// src/utils/query-profiler.ts
import { PrismaService } from '../services/prisma.service';

export class QueryProfiler {
constructor(private prisma: PrismaService) {
this.setupQueryLogging();
}

private setupQueryLogging(): void {
// Enable Prisma query logging
this.prisma.$on('query', (e) => {
const duration = e.duration;
const query = e.query;

console.log(`Query: ${query}`);
console.log(`Duration: ${duration}ms`);

if (duration > 100) {
console.warn(`Slow query detected (${duration}ms):`, query);
}
});
}

async profileQuery<T>(
queryName: string,
queryFn: () => Promise<T>
): Promise<T> {
const start = Date.now();

try {
const result = await queryFn();
const duration = Date.now() - start;

console.log(`Query '${queryName}' completed in ${duration}ms`);

if (duration > 1000) {
console.warn(`Very slow query: ${queryName} (${duration}ms)`);
}

return result;
} catch (error) {
const duration = Date.now() - start;
console.error(`Query '${queryName}' failed after ${duration}ms:`, error);
throw error;
}
}
}

// Usage in repository
export class UserRepository {
private queryProfiler = new QueryProfiler(this.prisma);

async findUsersWithPosts() {
return this.queryProfiler.profileQuery(
'findUsersWithPosts',
() => this.prisma.user.findMany({
include: {
posts: {
include: {
comments: true
}
}
}
})
);
}
}

Step 4: Error Monitoring and Tracking

4.1 Global Error Handler

// src/filters/global-exception.filter.ts
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
HttpStatus
} from '@nestjs/common';
import { Request, Response } from 'express';
import { LoggerService } from '../services/logger.service';

@Catch()
export class GlobalExceptionFilter implements ExceptionFilter {
constructor(private logger: LoggerService) {}

catch(exception: unknown, host: ArgumentsHost): void {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();

const status = exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;

const message = exception instanceof HttpException
? exception.getResponse()
: 'Internal server error';

// Log error with context
this.logger.error(
`HTTP ${status} Error`,
exception instanceof Error ? exception.stack : undefined,
'GlobalExceptionFilter',
{
method: request.method,
url: request.url,
userAgent: request.get('User-Agent'),
ip: request.ip,
body: request.body,
params: request.params,
query: request.query,
headers: this.sanitizeHeaders(request.headers)
}
);

// Send error response
response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
method: request.method,
message: typeof message === 'string' ? message : (message as any).message,
...(process.env.NODE_ENV === 'development' && {
stack: exception instanceof Error ? exception.stack : undefined
})
});
}

private sanitizeHeaders(headers: any): any {
const sanitized = { ...headers };
delete sanitized.authorization;
delete sanitized.cookie;
return sanitized;
}
}

4.2 Error Tracking Service

// src/services/error-tracking.service.ts
import { Injectable } from '@nestjs/common';

interface ErrorContext {
userId?: string;
requestId?: string;
userAgent?: string;
ip?: string;
url?: string;
method?: string;
body?: any;
params?: any;
query?: any;
}

@Injectable()
export class ErrorTrackingService {
private errors: Map<string, number> = new Map();
private errorPatterns: Map<string, RegExp> = new Map();

constructor() {
this.setupErrorPatterns();
}

trackError(error: Error, context?: ErrorContext): void {
const errorKey = this.generateErrorKey(error);
const count = this.errors.get(errorKey) || 0;
this.errors.set(errorKey, count + 1);

// Check if this is a recurring error
if (count > 5) {
this.alertRecurringError(error, count, context);
}

// Check for known error patterns
this.checkErrorPatterns(error, context);

// Log structured error
console.error('Error tracked:', {
message: error.message,
stack: error.stack,
count: count + 1,
context,
timestamp: new Date().toISOString()
});
}

private generateErrorKey(error: Error): string {
// Create a unique key based on error message and stack trace
const stackLines = error.stack?.split('\n').slice(0, 3).join('|') || '';
return `${error.message}|${stackLines}`;
}

private setupErrorPatterns(): void {
// Database connection errors
this.errorPatterns.set('database', /connection|timeout|ECONNREFUSED/i);

// Authentication errors
this.errorPatterns.set('auth', /unauthorized|forbidden|token|jwt/i);

// Validation errors
this.errorPatterns.set('validation', /validation|invalid|required|format/i);

// Rate limiting errors
this.errorPatterns.set('rateLimit', /rate.?limit|too.?many.?requests/i);
}

private checkErrorPatterns(error: Error, context?: ErrorContext): void {
for (const [category, pattern] of this.errorPatterns) {
if (pattern.test(error.message)) {
this.handleCategorizedError(category, error, context);
break;
}
}
}

private handleCategorizedError(category: string, error: Error, context?: ErrorContext): void {
switch (category) {
case 'database':
this.handleDatabaseError(error, context);
break;
case 'auth':
this.handleAuthError(error, context);
break;
case 'validation':
this.handleValidationError(error, context);
break;
case 'rateLimit':
this.handleRateLimitError(error, context);
break;
}
}

private handleDatabaseError(error: Error, context?: ErrorContext): void {
console.warn('Database error detected:', {
error: error.message,
context,
suggestion: 'Check database connection and query performance'
});
}

private handleAuthError(error: Error, context?: ErrorContext): void {
console.warn('Authentication error detected:', {
error: error.message,
userId: context?.userId,
ip: context?.ip,
suggestion: 'Check JWT token validity and user permissions'
});
}

private handleValidationError(error: Error, context?: ErrorContext): void {
console.warn('Validation error detected:', {
error: error.message,
body: context?.body,
suggestion: 'Check input validation rules and client-side validation'
});
}

private handleRateLimitError(error: Error, context?: ErrorContext): void {
console.warn('Rate limit error detected:', {
error: error.message,
ip: context?.ip,
suggestion: 'Consider implementing exponential backoff or increasing rate limits'
});
}

private alertRecurringError(error: Error, count: number, context?: ErrorContext): void {
console.error('ALERT: Recurring error detected!', {
error: error.message,
occurrences: count,
context,
action: 'Immediate investigation required'
});

// In production, send to monitoring service (e.g., Sentry, DataDog)
// this.sendToMonitoringService(error, count, context);
}

getErrorStats(): any {
return {
totalErrors: Array.from(this.errors.values()).reduce((sum, count) => sum + count, 0),
uniqueErrors: this.errors.size,
topErrors: Array.from(this.errors.entries())
.sort(([, a], [, b]) => b - a)
.slice(0, 10)
.map(([error, count]) => ({ error, count }))
};
}
}

Step 5: Database Debugging

5.1 Query Analysis

// src/utils/database-debugger.ts
import { PrismaService } from '../services/prisma.service';

export class DatabaseDebugger {
constructor(private prisma: PrismaService) {}

async analyzeSlowQueries(): Promise<void> {
// Enable query logging
this.prisma.$on('query', (e) => {
if (e.duration > 100) {
console.log('Slow Query Analysis:');
console.log(`Query: ${e.query}`);
console.log(`Duration: ${e.duration}ms`);
console.log(`Params: ${e.params}`);

// Suggest optimizations
this.suggestOptimizations(e.query, e.duration);
}
});
}

private suggestOptimizations(query: string, duration: number): void {
const suggestions: string[] = [];

if (query.includes('SELECT') && !query.includes('WHERE')) {
suggestions.push('Consider adding WHERE clause to limit results');
}

if (query.includes('JOIN') && duration > 500) {
suggestions.push('Consider adding indexes on JOIN columns');
}

if (query.includes('ORDER BY') && duration > 200) {
suggestions.push('Consider adding index on ORDER BY column');
}

if (query.includes('COUNT(*)') && duration > 300) {
suggestions.push('Consider using approximate count for large tables');
}

if (suggestions.length > 0) {
console.log('Optimization suggestions:');
suggestions.forEach(suggestion => console.log(` - ${suggestion}`));
}
}

async checkConnectionHealth(): Promise<void> {
try {
const start = Date.now();
await this.prisma.$queryRaw`SELECT 1`;
const duration = Date.now() - start;

console.log(`Database connection health check: ${duration}ms`);

if (duration > 1000) {
console.warn('Database connection is slow');
}
} catch (error) {
console.error('Database connection failed:', error);
}
}

async analyzeTableSizes(): Promise<void> {
try {
const tables = await this.prisma.$queryRaw`
SELECT
table_name,
pg_size_pretty(pg_total_relation_size(quote_ident(table_name))) as size,
pg_total_relation_size(quote_ident(table_name)) as bytes
FROM information_schema.tables
WHERE table_schema = 'public'
ORDER BY pg_total_relation_size(quote_ident(table_name)) DESC;
`;

console.log('Table sizes:');
console.table(tables);
} catch (error) {
console.error('Failed to analyze table sizes:', error);
}
}
}

5.2 Connection Pool Monitoring

// src/utils/connection-monitor.ts
export class ConnectionMonitor {
private connectionCount = 0;
private maxConnections = 0;
private connectionHistory: number[] = [];

trackConnection(): void {
this.connectionCount++;
this.maxConnections = Math.max(this.maxConnections, this.connectionCount);
this.connectionHistory.push(Date.now());

// Keep only last 100 connections
if (this.connectionHistory.length > 100) {
this.connectionHistory.shift();
}
}

releaseConnection(): void {
this.connectionCount = Math.max(0, this.connectionCount - 1);
}

getStats(): any {
const now = Date.now();
const recentConnections = this.connectionHistory.filter(
time => now - time < 60000 // Last minute
).length;

return {
currentConnections: this.connectionCount,
maxConnections: this.maxConnections,
connectionsLastMinute: recentConnections,
averageConnectionsPerMinute: this.connectionHistory.length > 0
? (this.connectionHistory.length / ((now - this.connectionHistory[0]) / 60000))
: 0
};
}

checkForLeaks(): void {
const stats = this.getStats();

if (stats.currentConnections > 50) {
console.warn('Potential connection leak detected:', stats);
}

if (stats.connectionsLastMinute > 100) {
console.warn('High connection rate detected:', stats);
}
}
}

Step 6: API Debugging

6.1 Request/Response Interceptor

// src/interceptors/logging.interceptor.ts
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
Logger
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
private readonly logger = new Logger(LoggingInterceptor.name);

intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const response = context.switchToHttp().getResponse();
const { method, url, body, query, params, headers } = request;

const startTime = Date.now();
const requestId = this.generateRequestId();

// Log request
this.logger.log(`[${requestId}] ${method} ${url}`, {
body: this.sanitizeBody(body),
query,
params,
userAgent: headers['user-agent'],
ip: request.ip
});

return next.handle().pipe(
tap({
next: (data) => {
const duration = Date.now() - startTime;
this.logger.log(
`[${requestId}] ${method} ${url} - ${response.statusCode} - ${duration}ms`,
{
responseSize: JSON.stringify(data).length,
duration
}
);
},
error: (error) => {
const duration = Date.now() - startTime;
this.logger.error(
`[${requestId}] ${method} ${url} - ERROR - ${duration}ms`,
error.stack,
{
error: error.message,
duration
}
);
}
})
);
}

private generateRequestId(): string {
return Math.random().toString(36).substr(2, 9);
}

private sanitizeBody(body: any): any {
if (!body) return body;

const sanitized = { ...body };

// Remove sensitive fields
const sensitiveFields = ['password', 'token', 'secret', 'key'];
sensitiveFields.forEach(field => {
if (sanitized[field]) {
sanitized[field] = '[REDACTED]';
}
});

return sanitized;
}
}

6.2 API Health Monitoring

// src/modules/health/health.controller.ts
import { Controller, Get } from '@nestjs/common';
import { HealthService } from './health.service';

@Controller('health')
export class HealthController {
constructor(private healthService: HealthService) {}

@Get()
async getHealth() {
return this.healthService.getHealthStatus();
}

@Get('detailed')
async getDetailedHealth() {
return this.healthService.getDetailedHealthStatus();
}
}

// src/modules/health/health.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../../services/prisma.service';

@Injectable()
export class HealthService {
constructor(private prisma: PrismaService) {}

async getHealthStatus() {
const checks = await Promise.allSettled([
this.checkDatabase(),
this.checkMemory(),
this.checkDisk()
]);

const status = checks.every(check => check.status === 'fulfilled') ? 'healthy' : 'unhealthy';

return {
status,
timestamp: new Date().toISOString(),
uptime: process.uptime(),
version: process.env.npm_package_version || '1.0.0'
};
}

async getDetailedHealthStatus() {
const [database, memory, disk, performance] = await Promise.allSettled([
this.checkDatabase(),
this.checkMemory(),
this.checkDisk(),
this.checkPerformance()
]);

return {
status: 'detailed',
timestamp: new Date().toISOString(),
checks: {
database: this.getCheckResult(database),
memory: this.getCheckResult(memory),
disk: this.getCheckResult(disk),
performance: this.getCheckResult(performance)
}
};
}

private async checkDatabase(): Promise<any> {
const start = Date.now();
await this.prisma.$queryRaw`SELECT 1`;
const duration = Date.now() - start;

return {
status: duration < 1000 ? 'healthy' : 'slow',
responseTime: `${duration}ms`,
message: duration < 1000 ? 'Database is responsive' : 'Database is slow'
};
}

private checkMemory(): any {
const usage = process.memoryUsage();
const totalMB = usage.heapTotal / 1024 / 1024;
const usedMB = usage.heapUsed / 1024 / 1024;
const usagePercent = (usedMB / totalMB) * 100;

return {
status: usagePercent < 80 ? 'healthy' : 'warning',
usage: `${usedMB.toFixed(2)}MB / ${totalMB.toFixed(2)}MB`,
percentage: `${usagePercent.toFixed(2)}%`,
message: usagePercent < 80 ? 'Memory usage is normal' : 'High memory usage detected'
};
}

private checkDisk(): any {
// Simplified disk check - in production, use actual disk monitoring
return {
status: 'healthy',
message: 'Disk space is adequate'
};
}

private async checkPerformance(): Promise<any> {
const start = process.hrtime.bigint();

// Simulate some work
await new Promise(resolve => setTimeout(resolve, 10));

const end = process.hrtime.bigint();
const duration = Number(end - start) / 1000000; // Convert to milliseconds

return {
status: duration < 50 ? 'healthy' : 'slow',
responseTime: `${duration.toFixed(2)}ms`,
message: duration < 50 ? 'Performance is good' : 'Performance is degraded'
};
}

private getCheckResult(result: PromiseSettledResult<any>): any {
if (result.status === 'fulfilled') {
return result.value;
} else {
return {
status: 'error',
message: result.reason.message || 'Check failed'
};
}
}
}

Step 7: Production Debugging

7.1 Remote Debugging Setup

For production debugging (use with caution):

// src/utils/remote-debugger.ts
export class RemoteDebugger {
private isEnabled = false;
private debugSessions: Map<string, any> = new Map();

enable(secret: string): void {
if (process.env.DEBUG_SECRET === secret) {
this.isEnabled = true;
console.log('Remote debugging enabled');
}
}

disable(): void {
this.isEnabled = false;
this.debugSessions.clear();
console.log('Remote debugging disabled');
}

createSession(userId: string): string {
if (!this.isEnabled) return null;

const sessionId = Math.random().toString(36).substr(2, 9);
this.debugSessions.set(sessionId, {
userId,
createdAt: new Date(),
logs: []
});

return sessionId;
}

log(sessionId: string, message: string, data?: any): void {
if (!this.isEnabled) return;

const session = this.debugSessions.get(sessionId);
if (session) {
session.logs.push({
timestamp: new Date(),
message,
data
});

// Keep only last 100 logs
if (session.logs.length > 100) {
session.logs.shift();
}
}
}

getSession(sessionId: string): any {
return this.debugSessions.get(sessionId);
}
}

7.2 Production Monitoring

// src/services/monitoring.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class MonitoringService {
private metrics: Map<string, number> = new Map();
private alerts: any[] = [];

recordMetric(name: string, value: number): void {
this.metrics.set(name, value);
this.checkThresholds(name, value);
}

incrementCounter(name: string): void {
const current = this.metrics.get(name) || 0;
this.metrics.set(name, current + 1);
}

recordTiming(name: string, duration: number): void {
this.recordMetric(`${name}_duration`, duration);

if (duration > 1000) {
this.createAlert('performance', `Slow operation: ${name} took ${duration}ms`);
}
}

private checkThresholds(name: string, value: number): void {
const thresholds = {
'memory_usage': 500 * 1024 * 1024, // 500MB
'cpu_usage': 80, // 80%
'error_rate': 10, // 10 errors per minute
'response_time': 1000 // 1 second
};

const threshold = thresholds[name];
if (threshold && value > threshold) {
this.createAlert('threshold', `${name} exceeded threshold: ${value} > ${threshold}`);
}
}

private createAlert(type: string, message: string): void {
const alert = {
type,
message,
timestamp: new Date(),
severity: this.calculateSeverity(type, message)
};

this.alerts.push(alert);

// Keep only last 50 alerts
if (this.alerts.length > 50) {
this.alerts.shift();
}

console.warn('ALERT:', alert);

// In production, send to monitoring service
// this.sendToMonitoringService(alert);
}

private calculateSeverity(type: string, message: string): string {
if (message.includes('memory') || message.includes('cpu')) {
return 'high';
}
if (message.includes('slow') || message.includes('error')) {
return 'medium';
}
return 'low';
}

getMetrics(): any {
return Object.fromEntries(this.metrics);
}

getAlerts(): any[] {
return this.alerts;
}
}

Step 8: Debugging Commands and Tools

8.1 Debug Commands

Add these npm scripts to package.json:

{
"scripts": {
"debug": "node --inspect-brk=0.0.0.0:9229 -r ts-node/register src/main.ts",
"debug:test": "node --inspect-brk node_modules/.bin/jest --runInBand",
"debug:memory": "node --inspect --expose-gc -r ts-node/register src/main.ts",
"debug:cpu": "node --prof -r ts-node/register src/main.ts",
"debug:heap": "node --inspect --max-old-space-size=4096 -r ts-node/register src/main.ts",
"analyze:bundle": "webpack-bundle-analyzer dist/stats.json",
"profile:memory": "clinic doctor -- node dist/main.js",
"profile:cpu": "clinic flame -- node dist/main.js"
}
}

8.2 Debugging Utilities

# Start application in debug mode
npm run debug

# Debug specific test
npm run debug:test -- user.test.ts

# Profile memory usage
npm run profile:memory

# Profile CPU usage
npm run profile:cpu

# Analyze heap dump
node --inspect --expose-gc dist/main.js
# Then in Chrome DevTools: Memory tab > Take heap snapshot

# Monitor in real-time
npm run dev -- --inspect=9229
# Connect Chrome DevTools to localhost:9229

What You've Mastered

🎉 Outstanding work! You now have advanced debugging skills:

  • Structured Logging - Comprehensive logging strategies and best practices
  • IDE Debugging - VS Code debugging setup and breakpoint techniques
  • Performance Profiling - Memory, CPU, and database performance analysis
  • Error Monitoring - Production error tracking and alerting systems
  • Database Debugging - Query optimization and connection monitoring
  • API Debugging - Request/response logging and health monitoring
  • Production Debugging - Safe production debugging and monitoring
  • Automated Monitoring - Continuous monitoring and alerting systems

Debugging Efficiency

Manual Debugging Time: ~8 hours per complex issue With Advanced Tools: ~1 hour per issue Time Saved: 7 hours per issue (87% faster resolution)

Next Steps

Enhance your debugging arsenal with:

  • 🔍 Distributed Tracing - Track requests across microservices
  • 📊 APM Integration - Application Performance Monitoring tools
  • 🤖 AI-Powered Debugging - Automated error analysis and suggestions
  • 🔄 Chaos Engineering - Proactive failure testing and debugging
  • 📱 Mobile Debugging - Debug mobile app integrations
  • 🌐 Browser Debugging - Client-side debugging and network analysis
  • 🛡️ Security Debugging - Debug security vulnerabilities and attacks
  • 📈 Predictive Monitoring - Predict and prevent issues before they occur

Ready to optimize performance? Check out our Performance Optimization Guide or explore Common Issues Solutions!