Files
dotcom-arcade/docs/08-development/debugging.md
T

18 KiB

Debugging Guide

Comprehensive guide to debugging Flashpoint Web application across all services.

Table of Contents


Development Tools

VS Code Configuration

Recommended Extensions:

  • ESLint
  • Prettier
  • TypeScript and JavaScript Language Features
  • Debugger for Chrome
  • REST Client
  • SQLite Viewer
  • GitLens

VS Code Debug Configuration (.vscode/launch.json):

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Backend: Debug",
      "type": "node",
      "request": "launch",
      "runtimeExecutable": "npm",
      "runtimeArgs": ["run", "dev"],
      "cwd": "${workspaceFolder}/backend",
      "console": "integratedTerminal",
      "internalConsoleOptions": "neverOpen",
      "skipFiles": ["<node_internals>/**"]
    },
    {
      "name": "Frontend: Debug Chrome",
      "type": "chrome",
      "request": "launch",
      "url": "http://localhost:5173",
      "webRoot": "${workspaceFolder}/frontend/src",
      "sourceMapPathOverrides": {
        "webpack:///src/*": "${webRoot}/*"
      }
    },
    {
      "name": "Game Service: Debug",
      "type": "node",
      "request": "launch",
      "runtimeExecutable": "npm",
      "runtimeArgs": ["run", "dev"],
      "cwd": "${workspaceFolder}/game-service",
      "console": "integratedTerminal"
    },
    {
      "name": "Backend: Attach",
      "type": "node",
      "request": "attach",
      "port": 9229,
      "restart": true,
      "skipFiles": ["<node_internals>/**"]
    }
  ],
  "compounds": [
    {
      "name": "Full Stack: Debug",
      "configurations": [
        "Backend: Debug",
        "Frontend: Debug Chrome",
        "Game Service: Debug"
      ]
    }
  ]
}

Browser DevTools

Chrome DevTools Features:

  • Elements tab: Inspect React components
  • Console: View logs and errors
  • Network tab: Monitor API requests
  • Application tab: Check localStorage, cookies
  • Performance tab: Profile rendering
  • React DevTools: Component tree and state

Useful Console Commands:

// Clear console
clear()

// Copy object to clipboard
copy(object)

// Get DOM element
$('#element-id')
$$('.class-name')

// Monitor function calls
monitor(functionName)
unmonitor(functionName)

// View localStorage
localStorage

// Clear all storage
localStorage.clear()
sessionStorage.clear()

Backend Debugging

Starting Backend with Debugger

Method 1: VS Code Debugger

  1. Set breakpoints in TypeScript files
  2. Press F5 or select "Backend: Debug" from Run panel
  3. Server starts with debugger attached

Method 2: Node Inspector

cd backend

# Start with inspector
node --inspect-brk -r tsx/cjs src/server.ts

# In Chrome, navigate to:
chrome://inspect

Method 3: tsx with inspect flag

cd backend
npm run dev -- --inspect

# Or with break on start
npm run dev -- --inspect-brk

Logging

Winston Logger Configuration:

// src/utils/logger.ts
import winston from 'winston';

export const logger = winston.createLogger({
  level: process.env.LOG_LEVEL || 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  transports: [
    new winston.transports.Console({
      format: winston.format.combine(
        winston.format.colorize(),
        winston.format.simple()
      )
    }),
    new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
    new winston.transports.File({ filename: 'logs/combined.log' })
  ]
});

Logging Best Practices:

// Include context in logs
logger.info('User login attempt', { username, ip: req.ip });

// Log errors with stack traces
try {
  await riskyOperation();
} catch (error) {
  logger.error('Operation failed', { error, context: additionalData });
  throw error;
}

// Use appropriate log levels
logger.debug('Detailed debug info');  // Development only
logger.info('Normal operations');     // General info
logger.warn('Warning condition');     // Potential issues
logger.error('Error occurred');       // Errors that need attention

Adjust Log Level:

# In .env
LOG_LEVEL=debug

# Or at runtime
export LOG_LEVEL=debug
npm run dev

Request/Response Debugging

Log all requests:

// Middleware to log requests
app.use((req, res, next) => {
  logger.debug('Incoming request', {
    method: req.method,
    path: req.path,
    query: req.query,
    body: req.body,
    headers: req.headers
  });
  next();
});

Log responses:

app.use((req, res, next) => {
  const originalSend = res.send;

  res.send = function (data) {
    logger.debug('Response', {
      path: req.path,
      statusCode: res.statusCode,
      data: data
    });
    return originalSend.call(this, data);
  };

  next();
});

Debugging Database Queries

Enable SQL logging:

// DatabaseService.ts
const db = new Database(dbPath, {
  verbose: console.log  // Log all SQL queries
});

// Or with custom logger
const db = new Database(dbPath, {
  verbose: (sql: string) => {
    logger.debug('SQL Query', { sql });
  }
});

Inspect query results:

const stmt = db.prepare('SELECT * FROM game WHERE id = ?');
const result = stmt.get(gameId);

logger.debug('Query result', {
  sql: stmt.source,
  params: [gameId],
  result: result
});

Debugging Authentication

Log JWT operations:

// utils/jwt.ts
export function verifyToken(token: string) {
  try {
    const decoded = jwt.verify(token, JWT_SECRET);
    logger.debug('Token verified', { userId: decoded.userId });
    return decoded;
  } catch (error) {
    logger.warn('Token verification failed', { error, token });
    throw error;
  }
}

Debug middleware:

// middleware/auth.ts
export function authMiddleware(req: Request, res: Response, next: NextFunction) {
  const authHeader = req.headers.authorization;

  logger.debug('Auth middleware', {
    path: req.path,
    hasAuthHeader: !!authHeader,
    authHeader: authHeader?.substring(0, 20) + '...'
  });

  // Rest of middleware
}

Debugging Services

Add debug points in services:

export class GameService {
  public getGameById(id: string): Game | null {
    logger.debug('GameService.getGameById called', { id });

    const game = this.db.prepare('SELECT * FROM game WHERE id = ?').get(id);

    logger.debug('GameService.getGameById result', {
      id,
      found: !!game,
      game: game
    });

    return game ?? null;
  }
}

Frontend Debugging

React DevTools

Installation:

  • Chrome: Install "React Developer Tools" extension
  • Firefox: Install "React Developer Tools" add-on

Usage:

  1. Open browser DevTools
  2. Select "Components" or "Profiler" tab
  3. Inspect component tree, props, state, hooks

Debug Component State:

// Add debug logging to components
export function GameCard({ game }: GameCardProps) {
  console.log('GameCard render', { game });

  const [isHovered, setIsHovered] = useState(false);

  useEffect(() => {
    console.log('GameCard mounted', { gameId: game.id });

    return () => {
      console.log('GameCard unmounted', { gameId: game.id });
    };
  }, [game.id]);

  return <div>...</div>;
}

React Query DevTools

Enable React Query DevTools:

// App.tsx
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

export default function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Router>
        {/* App content */}
      </Router>
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  );
}

Features:

  • View all queries and their states
  • Manually refetch queries
  • Clear cache
  • Inspect query data
  • Track loading states

Debugging API Calls

Log API requests:

// lib/api.ts
import axios from 'axios';

const api = axios.create({
  baseURL: import.meta.env.VITE_API_URL
});

// Request interceptor
api.interceptors.request.use(
  (config) => {
    console.log('API Request', {
      method: config.method,
      url: config.url,
      params: config.params,
      data: config.data
    });
    return config;
  },
  (error) => {
    console.error('API Request Error', error);
    return Promise.reject(error);
  }
);

// Response interceptor
api.interceptors.response.use(
  (response) => {
    console.log('API Response', {
      url: response.config.url,
      status: response.status,
      data: response.data
    });
    return response;
  },
  (error) => {
    console.error('API Response Error', {
      url: error.config?.url,
      status: error.response?.status,
      data: error.response?.data
    });
    return Promise.reject(error);
  }
);

Debugging State Management

Zustand DevTools:

// store/auth.ts
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';

interface AuthState {
  user: User | null;
  setUser: (user: User | null) => void;
}

export const useAuthStore = create<AuthState>()(
  devtools(
    (set) => ({
      user: null,
      setUser: (user) => set({ user }, false, 'setUser')
    }),
    { name: 'AuthStore' }
  )
);

Manual state logging:

export const useAuthStore = create<AuthState>()((set) => ({
  user: null,
  setUser: (user) => {
    console.log('Auth state update', { user });
    set({ user });
  }
}));

Debugging Hooks

Add debug logging to custom hooks:

export function useGameSelection(initialGameId?: string) {
  const [selectedGameId, setSelectedGameId] = useState(initialGameId);

  console.log('useGameSelection', {
    selectedGameId,
    initialGameId
  });

  const { data: game, isLoading, error } = useQuery({
    queryKey: ['game', selectedGameId],
    queryFn: () => fetchGame(selectedGameId!),
    enabled: !!selectedGameId
  });

  useEffect(() => {
    console.log('useGameSelection: game data updated', {
      gameId: selectedGameId,
      game,
      isLoading,
      error
    });
  }, [selectedGameId, game, isLoading, error]);

  return { selectedGameId, setSelectedGameId, game, isLoading, error };
}

Debugging Renders

React.StrictMode helps catch issues:

// main.tsx
import { StrictMode } from 'react';

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <App />
  </StrictMode>
);

Track render count:

import { useRef } from 'react';

export function GameCard({ game }: GameCardProps) {
  const renderCount = useRef(0);
  renderCount.current++;

  console.log(`GameCard rendered ${renderCount.current} times`, { game });

  return <div>...</div>;
}

Use React Profiler:

import { Profiler, ProfilerOnRenderCallback } from 'react';

const onRenderCallback: ProfilerOnRenderCallback = (
  id,
  phase,
  actualDuration,
  baseDuration,
  startTime,
  commitTime
) => {
  console.log('Profiler', {
    id,
    phase,
    actualDuration,
    baseDuration
  });
};

export function GameList() {
  return (
    <Profiler id="GameList" onRender={onRenderCallback}>
      {/* Component tree */}
    </Profiler>
  );
}

Game Service Debugging

Enabling Debug Logging

# In game-service/.env
LOG_LEVEL=debug

# Restart service
cd game-service
npm run dev

Debugging Proxy Requests

Log proxy requests:

// servers/proxy-server.ts
app.use((req, res, next) => {
  logger.debug('Proxy request', {
    url: req.url,
    method: req.method,
    headers: req.headers,
    ip: req.ip
  });
  next();
});

Debug file resolution:

// handlers/file-handler.ts
export function resolveFile(requestPath: string): string | null {
  const paths = [
    join(HTDOCS_PATH, requestPath),
    join(GAMES_PATH, requestPath),
    // ... other paths
  ];

  logger.debug('Resolving file', { requestPath, searchPaths: paths });

  for (const path of paths) {
    if (existsSync(path)) {
      logger.debug('File found', { requestPath, resolvedPath: path });
      return path;
    }
  }

  logger.debug('File not found', { requestPath });
  return null;
}

Debugging ZIP File Access

Log ZIP operations:

// services/zip-manager.ts
export class ZipManager {
  public getFileFromZip(zipPath: string, filePath: string): Buffer | null {
    logger.debug('Getting file from ZIP', { zipPath, filePath });

    try {
      const zip = new StreamZip.async({ file: zipPath });
      const data = await zip.entryData(filePath);

      logger.debug('File extracted from ZIP', {
        zipPath,
        filePath,
        size: data.length
      });

      return data;
    } catch (error) {
      logger.error('Failed to extract from ZIP', {
        zipPath,
        filePath,
        error
      });
      return null;
    }
  }
}

Database Debugging

SQLite Browser

Desktop Tools:

  • DB Browser for SQLite (free)
  • SQLiteStudio
  • TablePlus

VS Code Extensions:

  • SQLite Viewer
  • SQLite

Query Debugging

Test queries directly:

# Open database in SQLite CLI
sqlite3 backend/user.db

# Run queries
SELECT * FROM users;
SELECT * FROM play_sessions WHERE user_id = 'abc';

# Show table schema
.schema users

# Enable column headers
.headers on

# Enable column mode
.mode column

# Exit
.quit

Analyze query performance:

EXPLAIN QUERY PLAN
SELECT * FROM game
WHERE platform = 'Flash'
AND library = 'arcade';

Database Locks

Check for locks:

# Linux/Mac
lsof backend/user.db

# Windows
# Use Process Explorer to find processes with file handle

Handle lock errors in code:

try {
  const result = db.prepare('INSERT INTO users ...').run(data);
} catch (error) {
  if (error.code === 'SQLITE_BUSY') {
    logger.warn('Database locked, retrying...', { error });
    // Retry logic
  } else {
    throw error;
  }
}

Network Debugging

Chrome Network Tab

Filter requests:

  • XHR/Fetch: API calls only
  • JS/CSS: Asset loading
  • All: Complete picture

Inspect request:

  • Headers: Authorization, Content-Type
  • Payload: Request body
  • Preview: Response data
  • Timing: Performance metrics

Debugging CORS Issues

Check CORS headers in response:

Access-Control-Allow-Origin: http://localhost:5173
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization

Backend CORS configuration:

// server.ts
import cors from 'cors';

app.use(cors({
  origin: process.env.DOMAIN || 'http://localhost:5173',
  credentials: true,
  exposedHeaders: ['X-Total-Count']
}));

// Debug CORS
app.use((req, res, next) => {
  console.log('CORS check', {
    origin: req.headers.origin,
    method: req.method,
    path: req.path
  });
  next();
});

Using curl for API Testing

# GET request
curl http://localhost:3100/api/games

# GET with auth
curl -H "Authorization: Bearer TOKEN" \
  http://localhost:3100/api/games

# POST request
curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"username":"test","password":"test"}' \
  http://localhost:3100/api/auth/login

# Debug with verbose output
curl -v http://localhost:3100/api/games

# Save response to file
curl -o response.json http://localhost:3100/api/games

Using HTTPie

# Install
npm install -g httpie

# GET request
http GET http://localhost:3100/api/games

# POST with auth
http POST http://localhost:3100/api/auth/login \
  username=test \
  password=test

# With auth token
http GET http://localhost:3100/api/games \
  Authorization:"Bearer TOKEN"

Performance Debugging

Backend Performance

Measure request duration:

app.use((req, res, next) => {
  const start = Date.now();

  res.on('finish', () => {
    const duration = Date.now() - start;
    logger.info('Request completed', {
      method: req.method,
      path: req.path,
      status: res.statusCode,
      duration: `${duration}ms`
    });

    if (duration > 1000) {
      logger.warn('Slow request detected', {
        path: req.path,
        duration: `${duration}ms`
      });
    }
  });

  next();
});

Profile database queries:

function profileQuery<T>(name: string, query: () => T): T {
  const start = Date.now();
  const result = query();
  const duration = Date.now() - start;

  logger.debug('Query performance', { name, duration: `${duration}ms` });

  return result;
}

// Usage
const games = profileQuery('searchGames', () =>
  db.prepare('SELECT * FROM game WHERE title LIKE ?').all(`%${query}%`)
);

Frontend Performance

React Profiler:

import { Profiler } from 'react';

<Profiler id="GameList" onRender={(id, phase, actualDuration) => {
  console.log(`${id} ${phase}: ${actualDuration}ms`);
}}>
  <GameList />
</Profiler>

Chrome Performance Tab:

  1. Open DevTools > Performance
  2. Click Record
  3. Interact with application
  4. Click Stop
  5. Analyze flame graph for slow operations

Lighthouse Audit:

  1. Open DevTools > Lighthouse
  2. Select categories (Performance, Accessibility, etc.)
  3. Click "Generate report"
  4. Review recommendations

Common Issues

See Common Pitfalls for detailed troubleshooting of frequent issues.


Additional Resources