741 lines
15 KiB
Markdown
741 lines
15 KiB
Markdown
# Debugging Guide
|
|
|
|
Common debugging strategies and troubleshooting patterns for Flyer Crawler.
|
|
|
|
## Table of Contents
|
|
|
|
- [Quick Debugging Checklist](#quick-debugging-checklist)
|
|
- [Container Issues](#container-issues)
|
|
- [Database Issues](#database-issues)
|
|
- [Test Failures](#test-failures)
|
|
- [API Errors](#api-errors)
|
|
- [Authentication Problems](#authentication-problems)
|
|
- [Background Job Issues](#background-job-issues)
|
|
- [SSL Certificate Issues](#ssl-certificate-issues)
|
|
- [Frontend Issues](#frontend-issues)
|
|
- [Performance Problems](#performance-problems)
|
|
- [Debugging Tools](#debugging-tools)
|
|
|
|
---
|
|
|
|
## Quick Debugging Checklist
|
|
|
|
When something breaks, check these first:
|
|
|
|
1. **Are containers running?**
|
|
|
|
```bash
|
|
podman ps
|
|
```
|
|
|
|
2. **Is the database accessible?**
|
|
|
|
```bash
|
|
podman exec flyer-crawler-postgres pg_isready -U postgres
|
|
```
|
|
|
|
3. **Are environment variables set?**
|
|
|
|
```bash
|
|
# Check .env.local exists
|
|
cat .env.local
|
|
```
|
|
|
|
4. **Are there recent errors in logs?**
|
|
|
|
```bash
|
|
# Application logs
|
|
podman logs -f flyer-crawler-dev
|
|
|
|
# PM2 logs (production)
|
|
pm2 logs flyer-crawler-api
|
|
```
|
|
|
|
5. **Is Redis accessible?**
|
|
```bash
|
|
podman exec flyer-crawler-redis redis-cli ping
|
|
```
|
|
|
|
---
|
|
|
|
## Container Issues
|
|
|
|
### Container Won't Start
|
|
|
|
**Symptom**: `podman start` fails or container exits immediately
|
|
|
|
**Debug**:
|
|
|
|
```bash
|
|
# Check container status
|
|
podman ps -a
|
|
|
|
# View container logs
|
|
podman logs flyer-crawler-postgres
|
|
podman logs flyer-crawler-redis
|
|
podman logs flyer-crawler-dev
|
|
|
|
# Inspect container
|
|
podman inspect flyer-crawler-dev
|
|
```
|
|
|
|
**Common Causes**:
|
|
|
|
- Port already in use
|
|
- Insufficient resources
|
|
- Configuration error
|
|
|
|
**Solutions**:
|
|
|
|
```bash
|
|
# Check port usage
|
|
netstat -an | findstr "5432"
|
|
netstat -an | findstr "6379"
|
|
|
|
# Remove and recreate container
|
|
podman stop flyer-crawler-postgres
|
|
podman rm flyer-crawler-postgres
|
|
# ... recreate with podman run ...
|
|
```
|
|
|
|
### "Unable to connect to Podman socket"
|
|
|
|
**Symptom**: `Error: unable to connect to Podman socket`
|
|
|
|
**Solution**:
|
|
|
|
```bash
|
|
# Start Podman machine
|
|
podman machine start
|
|
|
|
# Verify it's running
|
|
podman machine list
|
|
```
|
|
|
|
### Port Already in Use
|
|
|
|
**Symptom**: `Error: port 5432 is already allocated`
|
|
|
|
**Solutions**:
|
|
|
|
**Option 1**: Stop conflicting service
|
|
|
|
```bash
|
|
# Find process using port
|
|
netstat -ano | findstr "5432"
|
|
|
|
# Stop the service or kill process
|
|
```
|
|
|
|
**Option 2**: Use different port
|
|
|
|
```bash
|
|
# Run container on different host port
|
|
podman run -d --name flyer-crawler-postgres -p 5433:5432 ...
|
|
|
|
# Update .env.local
|
|
DB_PORT=5433
|
|
```
|
|
|
|
---
|
|
|
|
## Database Issues
|
|
|
|
### Connection Refused
|
|
|
|
**Symptom**: `Error: connect ECONNREFUSED 127.0.0.1:5432`
|
|
|
|
**Debug**:
|
|
|
|
```bash
|
|
# 1. Check if PostgreSQL container is running
|
|
podman ps | grep postgres
|
|
|
|
# 2. Check if PostgreSQL is ready
|
|
podman exec flyer-crawler-postgres pg_isready -U postgres
|
|
|
|
# 3. Test connection
|
|
podman exec flyer-crawler-postgres psql -U postgres -d flyer_crawler_dev -c "SELECT 1;"
|
|
```
|
|
|
|
**Common Causes**:
|
|
|
|
- Container not running
|
|
- PostgreSQL still initializing
|
|
- Wrong credentials in `.env.local`
|
|
|
|
**Solutions**:
|
|
|
|
```bash
|
|
# Start container
|
|
podman start flyer-crawler-postgres
|
|
|
|
# Wait for initialization (check logs)
|
|
podman logs -f flyer-crawler-postgres
|
|
|
|
# Verify credentials match .env.local
|
|
cat .env.local | grep DB_
|
|
```
|
|
|
|
### Schema Out of Sync
|
|
|
|
**Symptom**: Tests fail with missing column or table errors
|
|
|
|
**Cause**: `master_schema_rollup.sql` not in sync with migrations
|
|
|
|
**Solution**:
|
|
|
|
```bash
|
|
# Reset database with current schema
|
|
podman exec -i flyer-crawler-postgres psql -U postgres -d flyer_crawler_dev < sql/drop_tables.sql
|
|
podman exec -i flyer-crawler-postgres psql -U postgres -d flyer_crawler_dev < sql/master_schema_rollup.sql
|
|
|
|
# Verify schema
|
|
podman exec flyer-crawler-postgres psql -U postgres -d flyer_crawler_dev -c "\dt"
|
|
```
|
|
|
|
### Query Performance Issues
|
|
|
|
**Debug**:
|
|
|
|
```sql
|
|
-- Enable query logging
|
|
ALTER DATABASE flyer_crawler_dev SET log_statement = 'all';
|
|
|
|
-- Check slow queries
|
|
SELECT query, mean_exec_time, calls
|
|
FROM pg_stat_statements
|
|
WHERE mean_exec_time > 100
|
|
ORDER BY mean_exec_time DESC
|
|
LIMIT 10;
|
|
|
|
-- Analyze query plan
|
|
EXPLAIN ANALYZE
|
|
SELECT * FROM flyers WHERE store_id = 1;
|
|
```
|
|
|
|
**Solutions**:
|
|
|
|
- Add missing indexes
|
|
- Optimize WHERE clauses
|
|
- Use connection pooling
|
|
- See [ADR-034](../adr/0034-repository-layer-method-naming-conventions.md)
|
|
|
|
---
|
|
|
|
## Test Failures
|
|
|
|
### Tests Pass on Windows, Fail in Container
|
|
|
|
**Cause**: Platform-specific behavior (ADR-014)
|
|
|
|
**Rule**: Container results are authoritative. Windows results are unreliable.
|
|
|
|
**Solution**:
|
|
|
|
```bash
|
|
# Always run tests in container
|
|
podman exec -it flyer-crawler-dev npm test
|
|
|
|
# For specific test
|
|
podman exec -it flyer-crawler-dev npm test -- --run src/path/to/test.test.ts
|
|
```
|
|
|
|
### Integration Tests Fail
|
|
|
|
**Common Issues**:
|
|
|
|
**1. Vitest globalSetup Context Isolation**
|
|
|
|
**Symptom**: Mocks or spies don't work in integration tests
|
|
|
|
**Cause**: `globalSetup` runs in separate Node.js context
|
|
|
|
**Solutions**:
|
|
|
|
- Mark test as `.todo()` and document limitation
|
|
- Create test-only API endpoints
|
|
- Use Redis-based mock flags
|
|
|
|
See [CLAUDE.md#integration-test-issues](../../CLAUDE.md#integration-test-issues) for details.
|
|
|
|
**2. Cache Stale After Direct SQL**
|
|
|
|
**Symptom**: Test reads stale data after direct database insert
|
|
|
|
**Cause**: Cache not invalidated
|
|
|
|
**Solution**:
|
|
|
|
```typescript
|
|
// After direct SQL insert
|
|
await cacheService.invalidateFlyers();
|
|
```
|
|
|
|
**3. Queue Interference**
|
|
|
|
**Symptom**: Cleanup worker processes test data before assertions
|
|
|
|
**Solution**:
|
|
|
|
```typescript
|
|
const { cleanupQueue } = await import('../../services/queues.server');
|
|
await cleanupQueue.drain();
|
|
await cleanupQueue.pause();
|
|
// ... test ...
|
|
await cleanupQueue.resume();
|
|
```
|
|
|
|
### Type Check Failures
|
|
|
|
**Symptom**: `npm run type-check` fails
|
|
|
|
**Debug**:
|
|
|
|
```bash
|
|
# Run type check in container
|
|
podman exec -it flyer-crawler-dev npm run type-check
|
|
|
|
# Check specific file
|
|
podman exec -it flyer-crawler-dev npx tsc --noEmit src/path/to/file.ts
|
|
```
|
|
|
|
**Common Causes**:
|
|
|
|
- Missing type definitions
|
|
- Incorrect imports
|
|
- Type mismatch in function calls
|
|
|
|
---
|
|
|
|
## API Errors
|
|
|
|
### 404 Not Found
|
|
|
|
**Debug**:
|
|
|
|
```bash
|
|
# Check route registration
|
|
grep -r "router.get" src/routes/
|
|
|
|
# Check route path matches request
|
|
# Verify middleware order
|
|
```
|
|
|
|
**Common Causes**:
|
|
|
|
- Route not registered in `server.ts`
|
|
- Typo in route path
|
|
- Middleware blocking request
|
|
|
|
### 500 Internal Server Error
|
|
|
|
**Debug**:
|
|
|
|
```bash
|
|
# Check application logs
|
|
podman logs -f flyer-crawler-dev
|
|
|
|
# Check Bugsink for errors
|
|
# Visit: http://localhost:8443 (dev) or https://bugsink.projectium.com (prod)
|
|
```
|
|
|
|
**Common Causes**:
|
|
|
|
- Unhandled exception
|
|
- Database error
|
|
- Missing environment variable
|
|
|
|
**Solution Pattern**:
|
|
|
|
```typescript
|
|
// Always wrap route handlers
|
|
app.get('/api/endpoint', async (req, res) => {
|
|
try {
|
|
const result = await service.doSomething();
|
|
return sendSuccess(res, result);
|
|
} catch (error) {
|
|
return sendError(res, error); // Handles error types automatically
|
|
}
|
|
});
|
|
```
|
|
|
|
### 401 Unauthorized
|
|
|
|
**Debug**:
|
|
|
|
```bash
|
|
# Check JWT token in request
|
|
# Verify token is valid and not expired
|
|
|
|
# Test token decoding
|
|
node -e "console.log(require('jsonwebtoken').decode('YOUR_TOKEN_HERE'))"
|
|
```
|
|
|
|
**Common Causes**:
|
|
|
|
- Token expired
|
|
- Invalid token format
|
|
- Missing Authorization header
|
|
- Wrong JWT_SECRET
|
|
|
|
---
|
|
|
|
## Authentication Problems
|
|
|
|
### OAuth Not Working
|
|
|
|
**Debug**:
|
|
|
|
```bash
|
|
# 1. Verify OAuth credentials
|
|
cat .env.local | grep GOOGLE_CLIENT
|
|
|
|
# 2. Check OAuth routes are registered
|
|
grep -r "passport.authenticate" src/routes/
|
|
|
|
# 3. Verify redirect URI matches Google Console
|
|
# Should be: http://localhost:3001/api/auth/google/callback
|
|
```
|
|
|
|
**Common Issues**:
|
|
|
|
- Redirect URI mismatch in Google Console
|
|
- OAuth not enabled (commented out in config)
|
|
- Wrong client ID/secret
|
|
|
|
See [AUTHENTICATION.md](../architecture/AUTHENTICATION.md) for setup.
|
|
|
|
### JWT Token Invalid
|
|
|
|
**Debug**:
|
|
|
|
```typescript
|
|
// Decode token to inspect
|
|
import jwt from 'jsonwebtoken';
|
|
|
|
const decoded = jwt.decode(token);
|
|
console.log('Token payload:', decoded);
|
|
console.log('Expired:', decoded.exp < Date.now() / 1000);
|
|
```
|
|
|
|
**Solutions**:
|
|
|
|
- Regenerate token
|
|
- Check JWT_SECRET matches between environments
|
|
- Verify token hasn't expired
|
|
|
|
---
|
|
|
|
## Background Job Issues
|
|
|
|
### Jobs Not Processing
|
|
|
|
**Debug**:
|
|
|
|
```bash
|
|
# Check if worker is running
|
|
pm2 list
|
|
|
|
# Check worker logs
|
|
pm2 logs flyer-crawler-worker
|
|
|
|
# Check Redis connection
|
|
podman exec flyer-crawler-redis redis-cli ping
|
|
|
|
# Check queue status
|
|
node -e "
|
|
const { flyerProcessingQueue } = require('./dist/services/queues.server.js');
|
|
flyerProcessingQueue.getJobCounts().then(console.log);
|
|
"
|
|
```
|
|
|
|
**Common Causes**:
|
|
|
|
- Worker not running
|
|
- Redis connection lost
|
|
- Queue paused
|
|
- Job stuck in failed state
|
|
|
|
**Solutions**:
|
|
|
|
```bash
|
|
# Restart worker
|
|
pm2 restart flyer-crawler-worker
|
|
|
|
# Clear failed jobs
|
|
node -e "
|
|
const { flyerProcessingQueue } = require('./dist/services/queues.server.js');
|
|
flyerProcessingQueue.clean(0, 1000, 'failed');
|
|
"
|
|
```
|
|
|
|
### Jobs Failing
|
|
|
|
**Debug**:
|
|
|
|
```bash
|
|
# Check failed jobs
|
|
node -e "
|
|
const { flyerProcessingQueue } = require('./dist/services/queues.server.js');
|
|
flyerProcessingQueue.getFailed().then(jobs => {
|
|
jobs.forEach(job => console.log(job.failedReason));
|
|
});
|
|
"
|
|
|
|
# Check worker logs for stack traces
|
|
pm2 logs flyer-crawler-worker --lines 100
|
|
```
|
|
|
|
**Common Causes**:
|
|
|
|
- Gemini API errors
|
|
- Database errors
|
|
- Invalid job data
|
|
|
|
---
|
|
|
|
## SSL Certificate Issues
|
|
|
|
### Images Not Loading (ERR_CERT_AUTHORITY_INVALID)
|
|
|
|
**Symptom**: Flyer images fail to load with `ERR_CERT_AUTHORITY_INVALID` in browser console
|
|
|
|
**Cause**: Mixed hostname origins - user accesses via `localhost` but images use `127.0.0.1` (or vice versa)
|
|
|
|
**Debug**:
|
|
|
|
```bash
|
|
# Check which hostname images are using
|
|
podman exec flyer-crawler-dev psql -U postgres -d flyer_crawler_dev \
|
|
-c "SELECT image_url FROM flyers LIMIT 1;"
|
|
|
|
# Verify certificate includes both hostnames
|
|
podman exec flyer-crawler-dev openssl x509 -in /app/certs/localhost.crt -text -noout | grep -A1 "Subject Alternative Name"
|
|
|
|
# Check NGINX accepts both hostnames
|
|
podman exec flyer-crawler-dev grep "server_name" /etc/nginx/sites-available/default
|
|
```
|
|
|
|
**Solution**: The dev container is configured to handle both hostnames:
|
|
|
|
1. Certificate includes SANs for `localhost`, `127.0.0.1`, and `::1`
|
|
2. NGINX `server_name` directive includes both `localhost` and `127.0.0.1`
|
|
|
|
If you still see errors:
|
|
|
|
```bash
|
|
# Rebuild container to regenerate certificate
|
|
podman-compose down
|
|
podman-compose build --no-cache flyer-crawler-dev
|
|
podman-compose up -d
|
|
```
|
|
|
|
See [FLYER-URL-CONFIGURATION.md](../FLYER-URL-CONFIGURATION.md#ssl-certificate-configuration-dev-container) for full details.
|
|
|
|
### Self-Signed Certificate Not Trusted
|
|
|
|
**Symptom**: Browser shows security warning for `https://localhost`
|
|
|
|
**Temporary Workaround**: Accept the warning by clicking "Advanced" > "Proceed to localhost"
|
|
|
|
**Permanent Fix (Recommended)**: Install the mkcert CA certificate to eliminate all SSL warnings.
|
|
|
|
The CA certificate is located at `certs/mkcert-ca.crt` in the project root. See [`certs/README.md`](../../certs/README.md) for platform-specific installation instructions (Windows, macOS, Linux, Firefox).
|
|
|
|
After installation:
|
|
|
|
- Your browser will trust all mkcert certificates without warnings
|
|
- Both `https://localhost/` and `https://127.0.0.1/` will work without SSL errors
|
|
- Flyer images will load without `ERR_CERT_AUTHORITY_INVALID` errors
|
|
|
|
### NGINX SSL Configuration Test
|
|
|
|
**Debug**:
|
|
|
|
```bash
|
|
# Test NGINX configuration
|
|
podman exec flyer-crawler-dev nginx -t
|
|
|
|
# Check if NGINX is listening on 443
|
|
podman exec flyer-crawler-dev netstat -tlnp | grep 443
|
|
|
|
# Verify certificate files exist
|
|
podman exec flyer-crawler-dev ls -la /app/certs/
|
|
```
|
|
|
|
---
|
|
|
|
## Frontend Issues
|
|
|
|
### Hot Reload Not Working
|
|
|
|
**Debug**:
|
|
|
|
```bash
|
|
# Check Vite is running
|
|
curl http://localhost:5173
|
|
|
|
# Check for port conflicts
|
|
netstat -an | findstr "5173"
|
|
```
|
|
|
|
**Solution**:
|
|
|
|
```bash
|
|
# Restart dev server
|
|
npm run dev
|
|
```
|
|
|
|
### API Calls Failing (CORS)
|
|
|
|
**Symptom**: `CORS policy: No 'Access-Control-Allow-Origin' header`
|
|
|
|
**Debug**:
|
|
|
|
```typescript
|
|
// Check CORS configuration in server.ts
|
|
import cors from 'cors';
|
|
|
|
app.use(
|
|
cors({
|
|
origin: env.FRONTEND_URL, // Should match http://localhost:5173 in dev
|
|
credentials: true,
|
|
}),
|
|
);
|
|
```
|
|
|
|
**Solution**: Verify `FRONTEND_URL` in `.env.local` matches the frontend URL
|
|
|
|
---
|
|
|
|
## Performance Problems
|
|
|
|
### Slow API Responses
|
|
|
|
**Debug**:
|
|
|
|
```typescript
|
|
// Add timing logs
|
|
const start = Date.now();
|
|
const result = await slowOperation();
|
|
console.log(`Operation took ${Date.now() - start}ms`);
|
|
```
|
|
|
|
**Common Causes**:
|
|
|
|
- N+1 query problem
|
|
- Missing database indexes
|
|
- Large payload size
|
|
- No caching
|
|
|
|
**Solutions**:
|
|
|
|
- Use JOINs instead of multiple queries
|
|
- Add indexes: `CREATE INDEX idx_name ON table(column);`
|
|
- Implement pagination
|
|
- Add Redis caching
|
|
|
|
### High Memory Usage
|
|
|
|
**Debug**:
|
|
|
|
```bash
|
|
# Check PM2 memory usage
|
|
pm2 monit
|
|
|
|
# Check container memory
|
|
podman stats flyer-crawler-dev
|
|
```
|
|
|
|
**Common Causes**:
|
|
|
|
- Memory leak
|
|
- Large in-memory cache
|
|
- Unbounded array growth
|
|
|
|
---
|
|
|
|
## Debugging Tools
|
|
|
|
### VS Code Debugger
|
|
|
|
**Launch Configuration** (`.vscode/launch.json`):
|
|
|
|
```json
|
|
{
|
|
"version": "0.2.0",
|
|
"configurations": [
|
|
{
|
|
"type": "node",
|
|
"request": "launch",
|
|
"name": "Debug Tests",
|
|
"program": "${workspaceFolder}/node_modules/vitest/vitest.mjs",
|
|
"args": ["--run", "${file}"],
|
|
"console": "integratedTerminal",
|
|
"internalConsoleOptions": "neverOpen"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Logging
|
|
|
|
```typescript
|
|
import { logger } from './utils/logger';
|
|
|
|
// Structured logging
|
|
logger.info('Processing flyer', { flyerId, userId });
|
|
logger.error('Failed to process', { error, context });
|
|
logger.debug('Cache hit', { key, ttl });
|
|
```
|
|
|
|
### Database Query Logging
|
|
|
|
```typescript
|
|
// In development, log all queries
|
|
if (env.NODE_ENV === 'development') {
|
|
pool.on('connect', () => {
|
|
console.log('Database connected');
|
|
});
|
|
|
|
// Log slow queries
|
|
const originalQuery = pool.query.bind(pool);
|
|
pool.query = async (...args) => {
|
|
const start = Date.now();
|
|
const result = await originalQuery(...args);
|
|
const duration = Date.now() - start;
|
|
if (duration > 100) {
|
|
console.log(`Slow query (${duration}ms):`, args[0]);
|
|
}
|
|
return result;
|
|
};
|
|
}
|
|
```
|
|
|
|
### Redis Debugging
|
|
|
|
```bash
|
|
# Monitor Redis commands
|
|
podman exec -it flyer-crawler-redis redis-cli monitor
|
|
|
|
# Check keys
|
|
podman exec flyer-crawler-redis redis-cli keys "*"
|
|
|
|
# Get key value
|
|
podman exec flyer-crawler-redis redis-cli get "flyer:123"
|
|
|
|
# Check cache stats
|
|
podman exec flyer-crawler-redis redis-cli info stats
|
|
```
|
|
|
|
---
|
|
|
|
## See Also
|
|
|
|
- [TESTING.md](TESTING.md) - Testing strategies
|
|
- [CODE-PATTERNS.md](CODE-PATTERNS.md) - Common patterns
|
|
- [MONITORING.md](../operations/MONITORING.md) - Production monitoring
|
|
- [Bugsink Setup](../tools/BUGSINK-SETUP.md) - Error tracking
|
|
- [DevOps Guide](../subagents/DEVOPS-GUIDE.md) - Container debugging
|