Debugging Guide
Comprehensive debugging techniques and tools for the Vertical Farm project.
🔍 Debugging Philosophy
- Reproduce First - Always reproduce the issue before attempting fixes
- Isolate the Problem - Narrow down to the smallest possible scope
- Use the Right Tools - Different problems require different approaches
- Document Findings - Help future developers (including yourself)
🛠️ Debugging Tools
Frontend Debugging
Browser DevTools
// Console debugging
console.log('Basic output:', variable)
console.table(arrayOfObjects) // Tabular display
console.time('operation') // Performance timing
// ... code to measure
console.timeEnd('operation')
console.trace() // Stack trace
// Conditional breakpoints
if (unexpectedCondition) {
debugger; // Pauses execution
}
// Grouped logging
console.group('User Actions')
console.log('Action 1')
console.log('Action 2')
console.groupEnd()
React DevTools
// Component debugging
// Install React DevTools browser extension
// Add display names for better debugging
const FarmCard = React.memo(({ farm }) => {
// Component logic
})
FarmCard.displayName = 'FarmCard'
// Use React.StrictMode for detecting issues
function App() {
return (
<React.StrictMode>
<YourApp />
</React.StrictMode>
)
}
// Debug renders with React DevTools Profiler
// Helps identify unnecessary re-renders
Next.js Debugging
// Debug configuration (.vscode/launch.json)
{
"version": "0.2.0",
"configurations": [
{
"name": "Next.js: debug",
"type": "node",
"request": "launch",
"runtimeExecutable": "npm",
"runtimeArgs": ["run", "dev"],
"cwd": "${workspaceFolder}/frontend",
"port": 9229,
"env": {
"NODE_OPTIONS": "--inspect"
}
}
]
}
// Server-side debugging
export default async function Page() {
console.log('Server-side log') // Appears in terminal
// Debug server components
const data = await fetchData()
console.dir(data, { depth: null }) // Deep object inspection
return <Component data={data} />
}
Service Layer Debugging
// Add debug logging to services
class FarmService extends BaseService {
private debug = process.env.NODE_ENV === 'development'
async getFarmsByUser(userId: string) {
if (this.debug) {
console.group(`FarmService.getFarmsByUser`)
console.log('userId:', userId)
console.time('Database query')
}
try {
const farms = await this.query(/* ... */)
if (this.debug) {
console.log('Found farms:', farms.length)
console.timeEnd('Database query')
}
return farms
} catch (error) {
if (this.debug) {
console.error('Error details:', error)
}
throw error
} finally {
if (this.debug) {
console.groupEnd()
}
}
}
}
Backend Debugging
Python Debugging with pdb
# Interactive debugging
import pdb
def problematic_function(data):
# Set breakpoint
pdb.set_trace() # Or use breakpoint() in Python 3.7+
# Code continues here
result = process_data(data)
return result
# Conditional debugging
if unexpected_condition:
breakpoint() # Drops into debugger
# Post-mortem debugging
try:
risky_operation()
except Exception:
import pdb
pdb.post_mortem() # Debug after exception
VS Code Python Debugging
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "FastAPI Debug",
"type": "python",
"request": "launch",
"module": "uvicorn",
"args": [
"app.main:app",
"--reload",
"--port", "8000"
],
"cwd": "${workspaceFolder}/backend",
"env": {
"PYTHONPATH": "${workspaceFolder}/backend",
"DEBUG": "true"
}
}
]
}
Logging Configuration
# backend/app/core/logging.py
import logging
import sys
from pathlib import Path
def setup_logging(debug: bool = False):
"""Configure application logging."""
level = logging.DEBUG if debug else logging.INFO
# Console handler
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setLevel(level)
# File handler
file_handler = logging.FileHandler('app.log')
file_handler.setLevel(logging.WARNING)
# Formatter
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)
# Root logger
root_logger = logging.getLogger()
root_logger.setLevel(level)
root_logger.addHandler(console_handler)
root_logger.addHandler(file_handler)
# Reduce noise from libraries
logging.getLogger('uvicorn').setLevel(logging.WARNING)
logging.getLogger('sqlalchemy').setLevel(logging.WARNING)
# Usage in code
logger = logging.getLogger(__name__)
async def process_sensor_data(sensor_id: str):
logger.debug(f"Processing sensor {sensor_id}")
try:
data = await fetch_sensor_data(sensor_id)
logger.info(f"Retrieved {len(data)} readings")
processed = await process_readings(data)
logger.debug(f"Processed data: {processed}")
return processed
except Exception as e:
logger.error(f"Error processing sensor {sensor_id}: {e}", exc_info=True)
raise
FastAPI Request Debugging
# Middleware for request/response logging
from fastapi import Request
import time
import json
@app.middleware("http")
async def debug_middleware(request: Request, call_next):
# Log request
start_time = time.time()
body = await request.body()
logger.debug(f"""
Request:
- Method: {request.method}
- URL: {request.url}
- Headers: {dict(request.headers)}
- Body: {body.decode() if body else 'No body'}
""")
# Process request
response = await call_next(request)
# Log response
process_time = time.time() - start_time
logger.debug(f"""
Response:
- Status: {response.status_code}
- Process Time: {process_time:.3f}s
""")
response.headers["X-Process-Time"] = str(process_time)
return response
Database Debugging
SQL Query Debugging
# Enable SQLAlchemy query logging
import logging
logging.basicConfig()
logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
# Or with echo parameter
from sqlalchemy import create_engine
engine = create_engine('postgresql://...', echo=True)
# Query performance analysis
from sqlalchemy import event
from sqlalchemy.engine import Engine
import time
@event.listens_for(Engine, "before_cursor_execute")
def before_cursor_execute(conn, cursor, statement, parameters, context, executemany):
conn.info.setdefault('query_start_time', []).append(time.time())
logger.debug("Start Query: %s", statement)
@event.listens_for(Engine, "after_cursor_execute")
def after_cursor_execute(conn, cursor, statement, parameters, context, executemany):
total = time.time() - conn.info['query_start_time'].pop(-1)
logger.debug("Query Complete in %.3fs", total)
if total > 0.5: # Log slow queries
logger.warning("Slow query detected (%.3fs): %s", total, statement)
Supabase Debugging
-- Check query performance
EXPLAIN ANALYZE
SELECT * FROM farms
WHERE user_id = 'user-123'
AND created_at > NOW() - INTERVAL '30 days';
-- View active queries
SELECT
pid,
now() - pg_stat_activity.query_start AS duration,
query,
state
FROM pg_stat_activity
WHERE (now() - pg_stat_activity.query_start) > interval '5 minutes';
-- Check table sizes
SELECT
schemaname AS table_schema,
tablename AS table_name,
pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS size
FROM pg_tables
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC;
-- Debug RLS policies
SELECT * FROM pg_policies WHERE tablename = 'farms';
Network Debugging
API Request Debugging
// Frontend API debugging
class DebugClient {
private baseURL: string
constructor(baseURL: string) {
this.baseURL = baseURL
}
async request(endpoint: string, options: RequestInit = {}) {
const url = `${this.baseURL}${endpoint}`
console.group(`API Request: ${options.method || 'GET'} ${endpoint}`)
console.log('URL:', url)
console.log('Options:', options)
console.time('Request duration')
try {
const response = await fetch(url, options)
const data = await response.json()
console.log('Status:', response.status)
console.log('Headers:', Object.fromEntries(response.headers.entries()))
console.log('Response:', data)
console.timeEnd('Request duration')
if (!response.ok) {
console.error('Request failed:', response.statusText)
}
return data
} catch (error) {
console.error('Request error:', error)
console.timeEnd('Request duration')
throw error
} finally {
console.groupEnd()
}
}
}
// Usage
const debug = new DebugClient('http://localhost:8000')
await debug.request('/api/v1/farms', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Test Farm' })
})
WebSocket Debugging
// Debug WebSocket connections
class DebugWebSocket {
private ws: WebSocket
private debug: boolean = true
connect(url: string) {
this.ws = new WebSocket(url)
this.ws.onopen = (event) => {
if (this.debug) console.log('WebSocket opened:', event)
}
this.ws.onmessage = (event) => {
if (this.debug) {
console.log('WebSocket message:', JSON.parse(event.data))
}
}
this.ws.onerror = (error) => {
console.error('WebSocket error:', error)
}
this.ws.onclose = (event) => {
if (this.debug) {
console.log('WebSocket closed:', event.code, event.reason)
}
}
}
send(data: any) {
const message = JSON.stringify(data)
if (this.debug) {
console.log('Sending:', data)
}
this.ws.send(message)
}
}
🐛 Common Issues & Solutions
Frontend Issues
Issue: Component Not Re-rendering
// Problem: State updates not triggering re-render
const [items, setItems] = useState([])
// ❌ Wrong: Mutating existing array
items.push(newItem)
setItems(items)
// ✅ Correct: Creating new array
setItems([...items, newItem])
// Debug: Check if props/state actually changed
useEffect(() => {
console.log('Items changed:', items)
}, [items])
Issue: Infinite Re-renders
// Problem: Effect causing infinite loop
useEffect(() => {
// ❌ Wrong: Updates dependency in effect
setData(processData(data))
}, [data])
// ✅ Correct: Use callback or memoization
const processedData = useMemo(() => processData(data), [data])
// Debug: Add counter to detect loops
const renderCount = useRef(0)
renderCount.current++
if (renderCount.current > 100) {
console.error('Possible infinite loop detected!')
}
Issue: Hydration Mismatch
// Problem: Server and client render differently
// Debug: Check for client-only code
const [mounted, setMounted] = useState(false)
useEffect(() => {
setMounted(true)
}, [])
if (!mounted) {
return <LoadingState /> // Same on server and client
}
return <ClientOnlyComponent /> // Only after hydration
Backend Issues
Issue: Slow API Response
# Debug: Profile the endpoint
import cProfile
import pstats
from io import StringIO
def profile_endpoint():
pr = cProfile.Profile()
pr.enable()
# Your slow code here
result = slow_function()
pr.disable()
s = StringIO()
ps = pstats.Stats(pr, stream=s).sort_stats('cumulative')
ps.print_stats(10) # Top 10 slow functions
print(s.getvalue())
return result
# Common solutions:
# 1. Add database indexes
# 2. Implement caching
# 3. Use async operations
# 4. Optimize queries (N+1 problem)
Issue: Memory Leaks
# Debug: Monitor memory usage
import tracemalloc
import gc
# Start tracing
tracemalloc.start()
# Your code here
result = process_large_dataset()
# Get memory snapshot
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')
print("[ Top 10 memory consumers ]")
for stat in top_stats[:10]:
print(stat)
# Force garbage collection
gc.collect()
# Common causes:
# 1. Circular references
# 2. Global variables holding large data
# 3. Unclosed connections/files
# 4. Cache without expiration
Issue: Database Connection Errors
# Debug: Connection pool monitoring
from sqlalchemy.pool import QueuePool
import logging
logging.basicConfig()
logging.getLogger('sqlalchemy.pool').setLevel(logging.DEBUG)
# Create engine with pool logging
engine = create_engine(
DATABASE_URL,
poolclass=QueuePool,
pool_size=5,
max_overflow=10,
pool_pre_ping=True, # Test connections before using
echo_pool=True # Log pool checkouts/checkins
)
# Monitor pool status
@app.on_event("startup")
async def startup_event():
# Log pool status periodically
async def log_pool_status():
while True:
pool = engine.pool
logger.info(f"""
Pool Status:
- Size: {pool.size()}
- Checked out: {pool.checked_out_connections}
- Overflow: {pool.overflow}
- Total: {pool.size() + pool.overflow}
""")
await asyncio.sleep(60)
asyncio.create_task(log_pool_status())
🔬 Advanced Debugging Techniques
Remote Debugging
# Python remote debugging with debugpy
import debugpy
# Enable debugging on port 5678
debugpy.listen(("0.0.0.0", 5678))
print("Waiting for debugger attach...")
debugpy.wait_for_client()
print("Debugger attached!")
# VS Code configuration for remote debugging
{
"name": "Python: Remote Attach",
"type": "python",
"request": "attach",
"connect": {
"host": "localhost",
"port": 5678
},
"pathMappings": [
{
"localRoot": "${workspaceFolder}/backend",
"remoteRoot": "/app"
}
]
}
Production Debugging
// Feature flags for debug mode
const DEBUG_MODE = process.env.NEXT_PUBLIC_DEBUG === 'true'
export function debugLog(...args: any[]) {
if (DEBUG_MODE) {
console.log('[DEBUG]', new Date().toISOString(), ...args)
}
}
// Conditional debug UI
export function DebugPanel({ data }: { data: any }) {
if (!DEBUG_MODE) return null
return (
<div className="fixed bottom-0 right-0 p-4 bg-black text-white">
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
)
}
// Performance monitoring
export function measurePerformance(name: string, fn: () => void) {
if (!DEBUG_MODE) return fn()
performance.mark(`${name}-start`)
const result = fn()
performance.mark(`${name}-end`)
performance.measure(name, `${name}-start`, `${name}-end`)
const measure = performance.getEntriesByName(name)[0]
debugLog(`Performance: ${name} took ${measure.duration}ms`)
return result
}
Docker Debugging
# Debug running container
docker exec -it <container_id> /bin/bash
# View container logs
docker logs -f --tail 100 <container_id>
# Inspect container configuration
docker inspect <container_id>
# Monitor resource usage
docker stats <container_id>
# Debug networking
docker network inspect bridge
# Debug volumes
docker volume inspect <volume_name>
# docker-compose.yml debugging configuration
services:
backend:
image: backend:latest
environment:
- DEBUG=true
- LOG_LEVEL=debug
volumes:
- ./backend:/app # Mount for hot reload
ports:
- "8000:8000"
- "5678:5678" # Debug port
command: python -m debugpy --listen 0.0.0.0:5678 -m uvicorn app.main:app --reload
📝 Debug Checklist
Before Debugging
- [ ] Can you reproduce the issue consistently?
- [ ] Have you checked the error messages/logs?
- [ ] Is this a regression? (Did it work before?)
- [ ] Have you isolated the problem area?
During Debugging
- [ ] Use appropriate debugging tools
- [ ] Add strategic logging/breakpoints
- [ ] Test your hypothesis systematically
- [ ] Document your findings
After Debugging
- [ ] Remove debug code/console.logs
- [ ] Add test to prevent regression
- [ ] Document the solution
- [ ] Share knowledge with team
🚀 Performance Debugging
Frontend Performance
// React DevTools Profiler
// Measures component render performance
// Performance Observer API
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log('LCP:', entry.startTime, entry)
}
})
observer.observe({ entryTypes: ['largest-contentful-paint'] })
// Custom performance marks
performance.mark('fetch-start')
await fetchData()
performance.mark('fetch-end')
performance.measure('fetch', 'fetch-start', 'fetch-end')
Backend Performance
# Line profiling
from line_profiler import LineProfiler
def profile_function(func):
lp = LineProfiler()
lp_wrapper = lp(func)
lp_wrapper()
lp.print_stats()
# Memory profiling
from memory_profiler import profile
@profile
def memory_intensive_function():
# Your code here
pass
# Async profiling
import asyncio
import time
async def profile_async(coro):
start = time.perf_counter()
result = await coro
elapsed = time.perf_counter() - start
print(f"Execution time: {elapsed:.3f}s")
return result
Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it. - Brian Kernighan